summaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
authorsigoden <sigoden@gmail.com>2024-05-19 10:08:44 +0800
committerGitHub <noreply@github.com>2024-05-19 10:08:44 +0800
commit1b9aed6afbf16094c9ae12f5f2c3f1208bf72c73 (patch)
tree52d37189ef7829148e88b1bb116aa2d7ad70fbeb /assets
parent31977f9642d61885543da109dc3f7972bf3250f7 (diff)
downloadaichat-1b9aed6afbf16094c9ae12f5f2c3f1208bf72c73.tar.gz
feat: webui add toolbox(copy-bt/regenerate-btn) to message (#521)
Diffstat (limited to 'assets')
-rw-r--r--assets/arena.html78
-rw-r--r--assets/playground.html79
2 files changed, 144 insertions, 13 deletions
diff --git a/assets/arena.html b/assets/arena.html
index 35be8a6..edd19e1 100644
--- a/assets/arena.html
+++ b/assets/arena.html
@@ -14,6 +14,7 @@
<script src="//unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
<script src="//unpkg.com/marked@12.0.2/lib/marked.umd.js" defer></script>
<script src="//unpkg.com/alpinejs@3.13.10/dist/cdn.min.js" defer></script>
+ <script src="//unpkg.com/clipboard@2.0.1/dist/clipboard.min.js" def></script>
<style>
:root {
--fg-primary: #1652f1;
@@ -109,9 +110,11 @@
}
.chat-message-content {
+ position: relative;
display: flex;
flex-direction: column;
width: 100%;
+ margin-top: -2px;
padding-left: 0.625rem;
flex-grow: 1;
}
@@ -164,6 +167,27 @@
width: 100%;
}
+ .message-toolbox {
+ display: flex;
+ position: absolute;
+ bottom: -1.25rem;
+ }
+
+ .copy-message-btn,
+ .regenerate-message-btn {
+ top: 0.7rem;
+ right: 0.7rem;
+ cursor: pointer;
+ font-size: 0.9rem;
+ padding-right: 4px;
+ }
+
+ .copy-message-btn svg,
+ .regenerate-message-btn svg {
+ width: 1rem;
+ height: 1rem;
+ }
+
.copy-code-btn {
position: absolute;
top: 0.7rem;
@@ -283,8 +307,9 @@
}
.spinner {
- width: 1.25rem;
- height: 1.25rem;
+ width: 1.1rem;
+ height: 1.1rem;
+ margin-top: 3px;
border: 2px solid #000;
border-bottom-color: transparent;
border-radius: 50%;
@@ -299,7 +324,6 @@
left: 50%;
text-align: center;
transform: translate(-50%, 0);
- min-width: 200px;
background-color: var(--fg-default);
color: var(--bg-primary);
padding: 1rem;
@@ -364,8 +388,8 @@
</select>
</div>
<div class="chat-body" :id="'chat-body-' + index" @scroll="(event) => handleScrollChatBody(event, index)">
- <template x-for="message in chat.messages" :key="message.id">
- <div class="chat-message">
+ <template x-for="(message, messageIndex) in chat.messages" :key="message.id">
+ <div class="chat-message" @mouseover="chat.hoveredMessageIndex = messageIndex" @mouseleave="chat.messageHoveredIndex = null">
<div class="chat-avatar" :class="message.role == 'user' ? 'chat-avatar user' : 'chat-avatar assistant'">
<template x-if="message.role == 'user'">
<svg fill="currentColor" viewBox="0 0 16 16">
@@ -384,6 +408,7 @@
</template>
</div>
<div class="chat-message-content">
+ <!-- message -->
<template x-if="message.role == 'assistant' && message.html">
<div class="markdown-body" x-html="message.html"></div>
</template>
@@ -410,6 +435,28 @@
x-if="message.role == 'user' && Object.prototype.toString.call(message.content) == '[object String]'">
<div class="message-text" x-text="message.content"></div>
</template>
+ <!-- toolbox -->
+ <template x-if="messageIndex == chat.hoveredMessageIndex">
+ <div class="message-toolbox">
+ <div :id="'copy-message-btn-' + index" class="copy-message-btn" :data-clipboard-text="message.content"
+ x-init="copyMessageClipboard = new ClipboardJS('#copy-message-btn-' + index); copyMessageClipboard.on('success', () => {toast('Copied Message')})" title="Copy">
+ <svg fill="currentColor" viewBox="0 0 16 16">
+ <path fill-rule="evenodd"
+ d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z" />
+ </svg>
+ </div>
+ <template
+ x-if="messageIndex == chat.messages.length - 1 && (message.state == 'succeed' || message.state == 'failed')">
+ <div class="regenerate-message-btn" @click="(event) => handleRegenerateMessage(index)" title="Regenerate">
+ <svg fill="currentColor" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z" />
+ <path
+ d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466" />
+ </svg>
+ </div>
+ </template>
+ </div>
+ </template>
</div>
</div>
</template>
@@ -505,6 +552,8 @@
chats: Array.from(Array(NUM)).map(_ => ({
model_id: "",
messages: [],
+ hoveredMessageIndex: null,
+ copyMessageClipboard: null,
askAbortController: null,
shouldScrollChatBodyToBottom: true,
isShowScrollToBottomBtn: false,
@@ -590,6 +639,22 @@
this.images = [];
},
+ handleRegenerateMessage(index) {
+ const chat = this.chats[index];
+ const lastIndex = chat.messages.length - 1;
+ if (lastIndex !== chat.hoveredMessageIndex) {
+ return
+ }
+ let lastMessage = chat.messages[lastIndex];
+ lastMessage.content = "";
+ lastMessage.state = "loading";
+ lastMessage.error = "";
+ lastMessage.html = "";
+ this.asking++;
+ this.ask(index);
+ },
+
+
handleCancelAsk() {
for (const chat of this.chats) {
chat.askAbortController?.abort();
@@ -618,6 +683,7 @@
$chatBody.scrollTop = $chatBody.scrollHeight;
$chatBody._prevScrollTop = $chatBody.scrollTop;
chat.isShowScrollToBottomBtn = false;
+ chat.autoScrollChatBodyToBottom = true;
},
handleNewlineInput(event) {
@@ -644,7 +710,7 @@
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
- toast("Copied code to clipboard");
+ toast("Copied Code");
}
},
diff --git a/assets/playground.html b/assets/playground.html
index 6160a9f..7e73297 100644
--- a/assets/playground.html
+++ b/assets/playground.html
@@ -14,6 +14,7 @@
<script src="//unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
<script src="//unpkg.com/marked@12.0.2/lib/marked.umd.js" defer></script>
<script src="//unpkg.com/alpinejs@3.13.10/dist/cdn.min.js" defer></script>
+ <script src="//unpkg.com/clipboard@2.0.1/dist/clipboard.min.js" def></script>
<style>
:root {
--fg-primary: #1652f1;
@@ -168,7 +169,7 @@
.chat-body {
display: flex;
flex-direction: column;
- padding: 1.25rem;
+ padding: 0.5rem;
flex-grow: 1;
overflow-x: hidden;
overflow-y: auto;
@@ -186,9 +187,11 @@
}
.chat-message-content {
+ position: relative;
display: flex;
flex-direction: column;
width: 100%;
+ margin-top: -2px;
padding-left: 0.625rem;
flex-grow: 1;
}
@@ -241,6 +244,27 @@
width: 100%;
}
+ .message-toolbox {
+ display: flex;
+ position: absolute;
+ bottom: -1.25rem;
+ }
+
+ .copy-message-btn,
+ .regenerate-message-btn {
+ top: 0.7rem;
+ right: 0.7rem;
+ cursor: pointer;
+ font-size: 0.9rem;
+ padding-right: 4px;
+ }
+
+ .copy-message-btn svg,
+ .regenerate-message-btn svg {
+ width: 1rem;
+ height: 1rem;
+ }
+
.copy-code-btn {
position: absolute;
top: 0.7rem;
@@ -362,8 +386,9 @@
}
.spinner {
- width: 1.25rem;
- height: 1.25rem;
+ width: 1.1rem;
+ height: 1.1rem;
+ margin-top: 3px;
border: 2px solid #000;
border-bottom-color: transparent;
border-radius: 50%;
@@ -378,7 +403,6 @@
left: 50%;
text-align: center;
transform: translate(-50%, 0);
- min-width: 200px;
background-color: var(--fg-default);
color: var(--bg-primary);
padding: 1rem;
@@ -527,8 +551,8 @@
</div>
</div>
<div class="chat-body" x-ref="chat-body" @scroll="handleScrollChatBody">
- <template x-for="message in messages" :key="message.id">
- <div class="chat-message">
+ <template x-for="(message, index) in messages" :key="message.id">
+ <div class="chat-message" @mouseover="hoveredMessageIndex = index" @mouseleave="messageHoveredIndex = null">
<div class="chat-avatar" :class="message.role == 'user' ? 'chat-avatar user' : 'chat-avatar assistant'">
<template x-if="message.role == 'user'">
<svg fill="currentColor" viewBox="0 0 16 16">
@@ -547,6 +571,7 @@
</template>
</div>
<div class="chat-message-content">
+ <!-- message -->
<template x-if="message.role == 'assistant' && message.html">
<div class="markdown-body" x-html="message.html"></div>
</template>
@@ -573,6 +598,28 @@
x-if="message.role == 'user' && Object.prototype.toString.call(message.content) == '[object String]'">
<div class="message-text" x-text="message.content"></div>
</template>
+ <!-- toolbox -->
+ <template x-if="index == hoveredMessageIndex">
+ <div class="message-toolbox">
+ <div class="copy-message-btn" :data-clipboard-text="message.content"
+ x-init="copyMessageClipboard = new ClipboardJS('.copy-message-btn'); copyMessageClipboard.on('success', () => {toast('Copied Message')})" title="Copy">
+ <svg fill="currentColor" viewBox="0 0 16 16">
+ <path fill-rule="evenodd"
+ d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z" />
+ </svg>
+ </div>
+ <template
+ x-if="index == messages.length - 1 && (message.state == 'succeed' || message.state == 'failed')">
+ <div class="regenerate-message-btn" @click="handleRegenerateMessage" title="Regenerate">
+ <svg fill="currentColor" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z" />
+ <path
+ d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466" />
+ </svg>
+ </div>
+ </template>
+ </div>
+ </template>
</div>
</div>
</template>
@@ -679,6 +726,8 @@
roles: [{ name: "", prompt: "" }],
currentModel: {},
messages: [],
+ hoveredMessageIndex: null,
+ copyMessageClipboard: null,
skipMessageId: -1,
input: "",
images: [],
@@ -750,6 +799,21 @@
this.ask();
},
+ handleRegenerateMessage() {
+ const lastIndex = this.messages.length - 1;
+ if (lastIndex !== this.hoveredMessageIndex) {
+ return
+ }
+ let lastMessage = this.messages[lastIndex];
+ lastMessage.content = "";
+ lastMessage.state = "loading";
+ lastMessage.error = "";
+ lastMessage.html = "";
+ this.asking = true;
+ this.ask();
+ },
+
+
handleCancelAsk() {
this.askAbortController?.abort();
},
@@ -773,6 +837,7 @@
$chatBody.scrollTop = $chatBody.scrollHeight;
$chatBody._prevScrollTop = $chatBody.scrollTop;
this.isShowScrollToBottomBtn = false;
+ this.autoScrollChatBodyToBottom = true;
},
handleShowSidebarBtnClick() {
@@ -810,7 +875,7 @@
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
- toast("Copied code to clipboard");
+ toast("Copied Code");
}
},