Add a "copy" button below each message (#6654)

This commit is contained in:
oobabooga 2025-01-11 16:59:21 -03:00 committed by GitHub
parent 58342740a5
commit a5d64b586d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 26 deletions

View File

@ -48,12 +48,14 @@
.chat .user-message { .chat .user-message {
background: #f4f4f4; background: #f4f4f4;
padding: 1.5rem 1rem; padding: 1.5rem 1rem;
padding-bottom: 2rem;
border-radius: 0; border-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.chat .assistant-message { .chat .assistant-message {
padding: 1.5rem 1rem; padding: 1.5rem 1rem;
padding-bottom: 2rem;
border-radius: 0; border-radius: 0;
border: 0; border: 0;
} }

View File

@ -1142,7 +1142,6 @@ div.svelte-362y77>*, div.svelte-362y77>.form>* {
} }
.dark svg { .dark svg {
fill: white;
color: white; color: white;
} }
@ -1221,3 +1220,55 @@ div.svelte-362y77>*, div.svelte-362y77>.form>* {
background: var(--light-theme-gray); background: var(--light-theme-gray);
} }
} }
/* ----------------------------------------------
Copy button for chat messages
---------------------------------------------- */
.message .text,
.message .text-you,
.message .text-bot,
.user-message .text,
.assistant-message .text {
position: relative;
}
.message, .user-message, .assistant-message {
position: relative;
}
.copy-button {
position: absolute;
bottom: -23px;
left: 0;
padding: 0;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0;
display: flex;
align-items: center;
transition: opacity 0.2s;
}
.message:hover .copy-button,
.user-message:hover .copy-button,
.assistant-message:hover .copy-button {
opacity: 1;
}
.copy-button svg {
stroke: rgb(156 163 175);
transition: stroke 0.2s;
}
.copy-button:hover svg {
stroke: rgb(107 114 128);
}
.dark .copy-button svg {
stroke: rgb(156 163 175);
}
.dark .copy-button:hover svg {
stroke: rgb(209 213 219);
}

View File

@ -3,7 +3,7 @@ import io
import requests import requests
from modules import shared from modules import shared, ui
from modules.logging_colors import logger from modules.logging_colors import logger
original_open = open original_open = open
@ -58,6 +58,7 @@ def my_open(*args, **kwargs):
'\n <script src="file/js/morphdom/morphdom-umd.min.js"></script>' '\n <script src="file/js/morphdom/morphdom-umd.min.js"></script>'
f'\n <link id="highlight-css" rel="stylesheet" href="file/css/highlightjs/{"github-dark" if shared.settings["dark_theme"] else "github"}.min.css">' f'\n <link id="highlight-css" rel="stylesheet" href="file/css/highlightjs/{"github-dark" if shared.settings["dark_theme"] else "github"}.min.css">'
'\n <script>hljs.addPlugin(new CopyButtonPlugin());</script>' '\n <script>hljs.addPlugin(new CopyButtonPlugin());</script>'
f'\n <script>{ui.global_scope_js}</script>'
'\n </head>' '\n </head>'
) )

View File

@ -292,24 +292,34 @@ def get_image_cache(path):
return image_cache[path][1] return image_cache[path][1]
copy_svg = '''<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-copy"><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg>'''
copy_button = f'<button class="copy-button" onclick="copyToClipboard(this)">{copy_svg}</button>'
def generate_instruct_html(history): def generate_instruct_html(history):
output = f'<style>{instruct_css}</style><div class="chat" id="chat"><div class="messages">' output = f'<style>{instruct_css}</style><div class="chat" id="chat"><div class="messages">'
for i, _row in enumerate(history):
row = [convert_to_markdown_wrapped(entry, use_cache=i != len(history) - 1) for entry in _row]
if row[0]: # Don't display empty user messages for i in range(len(history['visible'])):
row_visible = history['visible'][i]
row_internal = history['internal'][i]
converted_visible = [convert_to_markdown_wrapped(entry, use_cache=i != len(history['visible']) - 1) for entry in row_visible]
if converted_visible[0]: # Don't display empty user messages
output += ( output += (
f'<div class="user-message">' f'<div class="user-message" '
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
f'<div class="text">' f'<div class="text">'
f'<div class="message-body">{row[0]}</div>' f'<div class="message-body">{converted_visible[0]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
output += ( output += (
f'<div class="assistant-message">' f'<div class="assistant-message" '
f'data-raw="{html.escape(row_internal[1], quote=True)}">'
f'<div class="text">' f'<div class="text">'
f'<div class="message-body">{row[1]}</div>' f'<div class="message-body">{converted_visible[1]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
@ -332,26 +342,32 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=
if Path("cache/pfp_me.png").exists() else '' if Path("cache/pfp_me.png").exists() else ''
) )
for i, _row in enumerate(history): for i in range(len(history['visible'])):
row = [convert_to_markdown_wrapped(entry, use_cache=i != len(history) - 1) for entry in _row] row_visible = history['visible'][i]
row_internal = history['internal'][i]
converted_visible = [convert_to_markdown_wrapped(entry, use_cache=i != len(history['visible']) - 1) for entry in row_visible]
if row[0]: # Don't display empty user messages if converted_visible[0]: # Don't display empty user messages
output += ( output += (
f'<div class="message">' f'<div class="message" '
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
f'<div class="circle-you">{img_me}</div>' f'<div class="circle-you">{img_me}</div>'
f'<div class="text">' f'<div class="text">'
f'<div class="username">{name1}</div>' f'<div class="username">{name1}</div>'
f'<div class="message-body">{row[0]}</div>' f'<div class="message-body">{converted_visible[0]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
output += ( output += (
f'<div class="message">' f'<div class="message" '
f'data-raw="{html.escape(row_internal[1], quote=True)}">'
f'<div class="circle-bot">{img_bot}</div>' f'<div class="circle-bot">{img_bot}</div>'
f'<div class="text">' f'<div class="text">'
f'<div class="username">{name2}</div>' f'<div class="username">{name2}</div>'
f'<div class="message-body">{row[1]}</div>' f'<div class="message-body">{converted_visible[1]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
@ -363,22 +379,28 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=
def generate_chat_html(history, name1, name2, reset_cache=False): def generate_chat_html(history, name1, name2, reset_cache=False):
output = f'<style>{chat_styles["wpp"]}</style><div class="chat" id="chat"><div class="messages">' output = f'<style>{chat_styles["wpp"]}</style><div class="chat" id="chat"><div class="messages">'
for i, _row in enumerate(history): for i in range(len(history['visible'])):
row = [convert_to_markdown_wrapped(entry, use_cache=i != len(history) - 1) for entry in _row] row_visible = history['visible'][i]
row_internal = history['internal'][i]
converted_visible = [convert_to_markdown_wrapped(entry, use_cache=i != len(history['visible']) - 1) for entry in row_visible]
if row[0]: # Don't display empty user messages if converted_visible[0]: # Don't display empty user messages
output += ( output += (
f'<div class="message">' f'<div class="message" '
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
f'<div class="text-you">' f'<div class="text-you">'
f'<div class="message-body">{row[0]}</div>' f'<div class="message-body">{converted_visible[0]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
output += ( output += (
f'<div class="message">' f'<div class="message" '
f'data-raw="{html.escape(row_internal[1], quote=True)}">'
f'<div class="text-bot">' f'<div class="text-bot">'
f'<div class="message-body">{row[1]}</div>' f'<div class="message-body">{converted_visible[1]}</div>'
f'{copy_button}'
f'</div>' f'</div>'
f'</div>' f'</div>'
) )
@ -389,8 +411,8 @@ def generate_chat_html(history, name1, name2, reset_cache=False):
def chat_html_wrapper(history, name1, name2, mode, style, character, reset_cache=False): def chat_html_wrapper(history, name1, name2, mode, style, character, reset_cache=False):
if mode == 'instruct': if mode == 'instruct':
return generate_instruct_html(history['visible']) return generate_instruct_html(history)
elif style == 'wpp': elif style == 'wpp':
return generate_chat_html(history['visible'], name1, name2) return generate_chat_html(history, name1, name2)
else: else:
return generate_cai_chat_html(history['visible'], name1, name2, style, character, reset_cache) return generate_cai_chat_html(history, name1, name2, style, character, reset_cache)

View File

@ -19,6 +19,8 @@ with open(Path(__file__).resolve().parent / '../css/highlightjs/highlightjs-copy
css += f.read() css += f.read()
with open(Path(__file__).resolve().parent / '../js/main.js', 'r') as f: with open(Path(__file__).resolve().parent / '../js/main.js', 'r') as f:
js = f.read() js = f.read()
with open(Path(__file__).resolve().parent / '../js/global_scope_js.js', 'r') as f:
global_scope_js = f.read()
with open(Path(__file__).resolve().parent / '../js/save_files.js', 'r') as f: with open(Path(__file__).resolve().parent / '../js/save_files.js', 'r') as f:
save_files_js = f.read() save_files_js = f.read()
with open(Path(__file__).resolve().parent / '../js/switch_tabs.js', 'r') as f: with open(Path(__file__).resolve().parent / '../js/switch_tabs.js', 'r') as f: