Keldos commited on
Commit
5314ca6
1 Parent(s): 3fc8c4b

feat: 一键复制语言模型输出到剪贴板

Browse files
Files changed (4) hide show
  1. assets/Kelpy-Codos.js +3 -3
  2. assets/custom.css +26 -1
  3. assets/custom.js +57 -16
  4. modules/utils.py +2 -0
assets/Kelpy-Codos.js CHANGED
@@ -3,9 +3,9 @@
3
  // @namespace https://github.com/Keldos-Li/Kelpy-Codos
4
  // @version 1.0.5
5
  // @author Keldos; https://keldos.me/
6
- // @description Add copy button to PRE tags before CODE tag, for Chuanhu ChatGPT especially.
7
- // Based on Chuanhu ChatGPT version: ac04408 (2023-3-22)
8
- // @license GPL-3.0
9
  // @grant none
10
  // ==/UserScript==
11
 
 
3
  // @namespace https://github.com/Keldos-Li/Kelpy-Codos
4
  // @version 1.0.5
5
  // @author Keldos; https://keldos.me/
6
+ // @description Add copy button to PRE tags before CODE tag, for Chuanhu Chat especially.
7
+ // Based on Chuanhu Chat version: ac04408 (2023-3-22)
8
+ // @license Apache-2.0
9
  // @grant none
10
  // ==/UserScript==
11
 
assets/custom.css CHANGED
@@ -4,7 +4,7 @@
4
  }
5
 
6
  .message p { margin-bottom: 0.6rem !important;}
7
- .message p:last-child { margin-bottom: 0 !important; }
8
 
9
  #app_title {
10
  font-weight: var(--prose-header-text-weight);
@@ -247,6 +247,31 @@ ol:not(.options), ul:not(.options) {
247
  width: auto !important;
248
  border-bottom-right-radius: 0 !important;
249
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  .message-wrap>div img{
252
  border-radius: 10px !important;
 
4
  }
5
 
6
  .message p { margin-bottom: 0.6rem !important;}
7
+ .message p:last-of-type { margin-bottom: 0 !important; }
8
 
9
  #app_title {
10
  font-weight: var(--prose-header-text-weight);
 
247
  width: auto !important;
248
  border-bottom-right-radius: 0 !important;
249
  }
250
+ .raw-message {
251
+ display: none;
252
+ }
253
+
254
+ .copy-bot-btn {
255
+ border-radius: 5px;
256
+ /* background-color: #E6E6E6 !important; */
257
+ color: rgba(120, 120, 120, 0.64) !important;
258
+ padding: 4px !important;
259
+ position: absolute;
260
+ right: -22px;
261
+ bottom: 0;
262
+ cursor: pointer !important;
263
+ transition: color .2s ease, background-color .2s ease;
264
+ }
265
+ .copy-bot-btn:hover {
266
+ background-color: rgba(167, 167, 167, 0.25) !important;
267
+ color: unset !important;
268
+ }
269
+ .copy-bot-btn:active {
270
+ background-color: rgba(167, 167, 167, 0.5) !important;
271
+ }
272
+ .copy-bot-btn:focus {
273
+ outline: none;
274
+ }
275
 
276
  .message-wrap>div img{
277
  border-radius: 10px !important;
assets/custom.js CHANGED
@@ -272,6 +272,36 @@ function setChatbotHeight() {
272
  }
273
  }
274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  var rendertime = 0; // for debugging
276
  var mathjaxUpdated = false;
277
 
@@ -326,12 +356,13 @@ function updateMathJax() {
326
 
327
  let timeoutId;
328
  let isThrottled = false;
329
- // 监听所有元素中message的变化,用来查找需要渲染的mathjax
330
- var mObserver = new MutationObserver(function (mutationsList, observer) {
331
- for (var mutation of mutationsList) {
332
- if (mutation.type === 'childList') {
333
- for (var node of mutation.addedNodes) {
334
- if (node.nodeType === 1 && node.classList.contains('message') && node.classList.contains('bot')) {
 
335
  if (shouldRenderLatex) {
336
  renderMathJax();
337
  mathjaxUpdated = false;
@@ -339,8 +370,8 @@ var mObserver = new MutationObserver(function (mutationsList, observer) {
339
  saveHistoryHtml();
340
  }
341
  }
342
- for (var node of mutation.removedNodes) {
343
- if (node.nodeType === 1 && node.classList.contains('message') && node.classList.contains('bot')) {
344
  if (shouldRenderLatex) {
345
  renderMathJax();
346
  mathjaxUpdated = false;
@@ -348,25 +379,25 @@ var mObserver = new MutationObserver(function (mutationsList, observer) {
348
  saveHistoryHtml();
349
  }
350
  }
351
- } else if (mutation.type === 'attributes') {
352
- if (mutation.target.nodeType === 1 && mutation.target.classList.contains('message') && mutation.target.classList.contains('bot')) {
353
  if (isThrottled) break; // 为了防止重复不断疯狂渲染,加上等待_(:з」∠)_
354
  isThrottled = true;
355
  clearTimeout(timeoutId);
356
  timeoutId = setTimeout(() => {
357
  isThrottled = false;
358
  if (shouldRenderLatex) {
359
- // console.log("changed");
360
  renderMathJax();
361
  mathjaxUpdated = false;
362
  }
 
363
  saveHistoryHtml();
364
  }, 500);
365
  }
366
  }
367
  }
368
  });
369
- mObserver.observe(targetNode, { attributes: true, childList: true, subtree: true });
370
 
371
  var loadhistorytime = 0; // for debugging
372
  function saveHistoryHtml() {
@@ -387,10 +418,20 @@ function loadHistoryHtml() {
387
  return; // logged in, do nothing
388
  }
389
  if (!historyLoaded) {
390
- var fakeHistory = document.createElement('div');
391
- fakeHistory.classList.add('history-message');
392
- fakeHistory.innerHTML = historyHtml;
393
- chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild);
 
 
 
 
 
 
 
 
 
 
394
  historyLoaded = true;
395
  console.log("History Loaded");
396
  loadhistorytime += 1; // for debugging
 
272
  }
273
  }
274
 
275
+ const copyIcon = '<span><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height=".8em" width=".8em" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></span>';
276
+ const copiedIcon = '<span><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height=".8em" width=".8em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg></span>';
277
+ function addCopyBotButton(botElement) {
278
+ var copyButton = null;
279
+ var rawMessage = null;
280
+ rawMessage = botElement.querySelector('.raw-message');
281
+ copyButton = botElement.querySelector('button.copy-bot-btn');
282
+ if (!rawMessage) return;
283
+ if (copyButton) {
284
+ copyButton.remove();
285
+ }
286
+ var button = document.createElement('button');
287
+ button.classList.add('copy-bot-btn');
288
+ button.setAttribute('aria-label', 'Copy');
289
+ button.innerHTML = copyIcon;
290
+ button.addEventListener('click', () => {
291
+ const textToCopy = rawMessage.innerText;
292
+ navigator.clipboard
293
+ .writeText(textToCopy)
294
+ .catch(() => {
295
+ console.error("copy failed");
296
+ });
297
+ button.innerHTML = copiedIcon;
298
+ setTimeout(() => {
299
+ button.innerHTML = copyIcon;
300
+ }, 1500);
301
+ });
302
+ botElement.appendChild(button);
303
+ };
304
+
305
  var rendertime = 0; // for debugging
306
  var mathjaxUpdated = false;
307
 
 
356
 
357
  let timeoutId;
358
  let isThrottled = false;
359
+ var mmutation
360
+ // 监听所有元素中 bot message 的变化,用来查找需要渲染的mathjax, 并为 bot 消息添加复制按钮。
361
+ var mObserver = new MutationObserver(function (mutationsList) {
362
+ for (mmutation of mutationsList) {
363
+ if (mmutation.type === 'childList') {
364
+ for (var node of mmutation.addedNodes) {
365
+ if (node.nodeType === 1 && node.classList.contains('message') && node.getAttribute('data-testid') === 'bot') {
366
  if (shouldRenderLatex) {
367
  renderMathJax();
368
  mathjaxUpdated = false;
 
370
  saveHistoryHtml();
371
  }
372
  }
373
+ for (var node of mmutation.removedNodes) {
374
+ if (node.nodeType === 1 && node.classList.contains('message') && node.getAttribute('data-testid') === 'bot') {
375
  if (shouldRenderLatex) {
376
  renderMathJax();
377
  mathjaxUpdated = false;
 
379
  saveHistoryHtml();
380
  }
381
  }
382
+ } else if (mmutation.type === 'attributes') {
383
+ if (mmutation.target.nodeType === 1 && mmutation.target.classList.contains('message') && mmutation.target.getAttribute('data-testid') === 'bot') {
384
  if (isThrottled) break; // 为了防止重复不断疯狂渲染,加上等待_(:з」∠)_
385
  isThrottled = true;
386
  clearTimeout(timeoutId);
387
  timeoutId = setTimeout(() => {
388
  isThrottled = false;
389
  if (shouldRenderLatex) {
 
390
  renderMathJax();
391
  mathjaxUpdated = false;
392
  }
393
+ document.querySelectorAll('#chuanhu_chatbot>.wrap>.message-wrap .message.bot').forEach(addCopyBotButton);
394
  saveHistoryHtml();
395
  }, 500);
396
  }
397
  }
398
  }
399
  });
400
+ mObserver.observe(document.documentElement, { attributes: true, childList: true, subtree: true });
401
 
402
  var loadhistorytime = 0; // for debugging
403
  function saveHistoryHtml() {
 
418
  return; // logged in, do nothing
419
  }
420
  if (!historyLoaded) {
421
+ var tempDiv = document.createElement('div');
422
+ tempDiv.innerHTML = historyHtml;
423
+ var buttons = tempDiv.querySelectorAll('button.copy-bot-btn');
424
+ for (var i = 0; i < buttons.length; i++) {
425
+ buttons[i].parentNode.removeChild(buttons[i]);
426
+ }
427
+ var fakeHistory = document.createElement('div');
428
+ fakeHistory.classList.add('history-message');
429
+ fakeHistory.innerHTML = tempDiv.innerHTML;
430
+ chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild);
431
+ // var fakeHistory = document.createElement('div');
432
+ // fakeHistory.classList.add('history-message');
433
+ // fakeHistory.innerHTML = historyHtml;
434
+ // chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild);
435
  historyLoaded = true;
436
  console.log("History Loaded");
437
  loadhistorytime += 1; // for debugging
modules/utils.py CHANGED
@@ -183,6 +183,7 @@ def convert_mdtext(md_text):
183
  non_code_parts = code_block_pattern.split(md_text)[::2]
184
 
185
  result = []
 
186
  for non_code, code in zip(non_code_parts, code_blocks + [""]):
187
  if non_code.strip():
188
  non_code = normalize_markdown(non_code)
@@ -194,6 +195,7 @@ def convert_mdtext(md_text):
194
  code = markdown_to_html_with_syntax_highlight(code)
195
  result.append(code)
196
  result = "".join(result)
 
197
  result += ALREADY_CONVERTED_MARK
198
  return result
199
 
 
183
  non_code_parts = code_block_pattern.split(md_text)[::2]
184
 
185
  result = []
186
+ raw = f'<span class="raw-message">{html.escape(md_text)}</span>'
187
  for non_code, code in zip(non_code_parts, code_blocks + [""]):
188
  if non_code.strip():
189
  non_code = normalize_markdown(non_code)
 
195
  code = markdown_to_html_with_syntax_highlight(code)
196
  result.append(code)
197
  result = "".join(result)
198
+ result += raw
199
  result += ALREADY_CONVERTED_MARK
200
  return result
201