Skip to content

Commit 698a97c

Browse files
authored
Support input editing and clipboard copying
1 parent 241eed7 commit 698a97c

File tree

1 file changed

+130
-76
lines changed

1 file changed

+130
-76
lines changed

index.html

Lines changed: 130 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@
179179
display: flex;
180180
}
181181

182+
.conversation-area {
183+
margin: 6px 4px 4px 4px;
184+
/* top left bot right*/
185+
}
186+
182187
.fade-enter-active,
183188
.fade-leave-active {
184189
transition: all 0.5s ease;
@@ -217,6 +222,11 @@
217222
}
218223
}
219224

225+
.md-preview {
226+
width: 100vw;
227+
max-width: 100%;
228+
}
229+
220230
.md-editor-preview p {
221231
word-break: break-word;
222232
}
@@ -369,7 +379,7 @@
369379
@click="messageStore.sendMessage" icon="mdi-arrow-up">
370380
</v-btn>
371381
<v-btn v-else-if="messageStore.generating" size="small" color="primary" variant="elevated"
372-
@click="snackbarStore.showInfoMessage('$vuetify.dataIterator.snackbar.stopped')"
382+
@click="messageStore.stop"
373383
icon="mdi-stop"></v-btn>
374384
<v-btn v-else-if="messageStore.conversation.length > 0" size="small" color="primary"
375385
variant="elevated" @click="messageStore.resendMessage" icon="mdi-autorenew"></v-btn>
@@ -722,20 +732,27 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
722732
<div v-for="(message, index) in messages">
723733
<div v-if="message.role === 'user'">
724734
<div class="px-2 py-5 user-message">
725-
<div class="message text-pre-wrap">
735+
<div class="message">
726736
<v-avatar :size=size class="mt-3 mr-3 mr-lg-6" color="primary" icon="mdi-account-circle">
727737
</v-avatar>
728-
<tuui-chat-card class="gradient text-pre-wrap" :index="index" :messages="messages">
729-
<v-card-text v-if="Array.isArray(message.content)">
730-
<div v-for="(item, index) in message.content" :key="index">
731-
<div v-if="item.type=='text'">{{ item.text }}</div>
732-
<tuui-img-dialog v-if="item.type=='image_url'"
733-
:src="item.image_url.url"></tuui-img-dialog>
734-
</div>
735-
</v-card-text>
736-
<v-card-text v-else>
737-
{{ message.content }}
738-
</v-card-text>
738+
<tuui-chat-card class="gradient text-pre-wrap" :index="index" :messages="messages"
739+
:show-modify="true">
740+
<template v-slot:default="{ showmodify }">
741+
<v-card-text v-if="Array.isArray(message.content)" class="md-preview">
742+
<div v-for="(item, index) in message.content" :key="index">
743+
<tuui-img-dialog v-if="item.type=='image_url'"
744+
:src="item.image_url.url"></tuui-img-dialog>
745+
<v-textarea class="conversation-area" variant="plain" density='compact'
746+
auto-grow :counter="showmodify" :hide-details="!showmodify" rows="1"
747+
:readonly="!showmodify" v-model="item.text"></v-textarea>
748+
</div>
749+
</v-card-text>
750+
<v-card-text v-else class="md-preview pt-1">
751+
<v-textarea class="conversation-area" variant="plain" density='compact' auto-grow
752+
rows="1" :readonly="!showmodify" :counter="showmodify"
753+
:hide-details="!showmodify" v-model="message.content"></v-textarea>
754+
</v-card-text>
755+
</template>
739756
</tuui-chat-card>
740757
</div>
741758
</div>
@@ -748,12 +765,14 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
748765
<tuui-chat-card :index="index" :messages="messages" :show-content="true">
749766
<template v-slot:default="{ showcontent }">
750767
<div v-if="showcontent">
751-
<v-card-text style="white-space: pre-wrap;">
752-
{{ message.content }}
768+
<v-card-text class="md-preview pt-1">
769+
<v-textarea class="conversation-area" variant="plain" density='compact'
770+
auto-grow hide-details rows="1" readonly
771+
v-model="message.content"></v-textarea>
753772
</v-card-text>
754773
</div>
755774
<div v-else>
756-
<md-preview :model-value="message.content"
775+
<md-preview :model-value="message.content" class="md-preview"
757776
:language="language == 'zhHans' ? 'zh-CN' : 'en-US'" :code-foldable="true"
758777
auto-fold-threshold="Infinity"></md-preview>
759778
</div>
@@ -782,16 +801,22 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
782801
<v-hover open-delay="100">
783802
<template v-slot:default="{ isHovering, props }">
784803
<v-card v-bind="props" :elevation="isHovering ? 4 : 2">
785-
<slot :showcontent="showcontent"></slot>
804+
<slot :showcontent="showcontent" :showmodify="showmodify"></slot>
786805
<v-expand-transition>
787806
<div v-if="isHovering">
788807
<v-divider></v-divider>
789808
<v-card-actions>
790809
<v-spacer></v-spacer>
810+
<v-btn color="primary" icon="mdi-content-copy" size="x-small" variant="plain"
811+
@click="copyToClipboard(messages[index])"></v-btn>
812+
<v-btn v-if="showModify" color="primary"
813+
:icon=" showmodify ? 'mdi-check-bold' : 'mdi-lead-pencil'" size="x-small"
814+
variant="plain" @click="showmodify = !showmodify" v-bind="showmodify"></v-btn>
815+
<v-btn v-if="showContent" color="primary"
816+
:icon=" showcontent ? 'mdi-eye-remove' : 'mdi-eye'" size="x-small" variant="plain"
817+
@click="showcontent = !showcontent" v-bind="showcontent"></v-btn>
791818
<v-btn v-if="showDelete" color="error" icon="mdi-delete-off-outline" size="x-small"
792819
variant="plain" @click="messages.splice(index, 1)"></v-btn>
793-
<v-btn v-if="showContent" color="primary" icon="mdi-eye" size="x-small" variant="plain"
794-
@click="showcontent = !showcontent" v-bind="showcontent"></v-btn>
795820
</v-card-actions>
796821
</div>
797822
</v-expand-transition>
@@ -807,6 +832,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
807832
const { createI18n, useI18n } = VueI18n
808833
const { en, it, ja, sv, zhHans } = 'vuetify/locale'
809834

835+
const useSnackbarStore = defineStore({
836+
id: "snackbarStore",
837+
state: () => ({
838+
isShow: false,
839+
message: "",
840+
type: "",
841+
}),
842+
843+
actions: {
844+
showMessage(message, type = "") {
845+
this.isShow = true;
846+
this.message = message;
847+
this.type = type;
848+
},
849+
850+
showErrorMessage(message) {
851+
this.showMessage(message, "error");
852+
},
853+
showSuccessMessage(message) {
854+
this.showMessage(message, "success");
855+
},
856+
showInfoMessage(message) {
857+
this.showMessage(message, "info");
858+
},
859+
showWarningMessage(message) {
860+
this.showMessage(message, "warning");
861+
},
862+
getIcon() {
863+
const icon = {
864+
info: "mdi-information",
865+
success: "mdi-check-circle",
866+
error: "mdi-alert-circle",
867+
warning: "mdi-alert",
868+
};
869+
870+
return icon[this.type];
871+
},
872+
},
873+
});
874+
810875
const TuuiImgDialog = {
811876
template: '#tuui-img-dialog-template',
812877
props: { src: { type: String, required: true } }
@@ -818,22 +883,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
818883
props: {
819884
index: { type: Number, required: true },
820885
messages: { type: Object, required: true },
821-
showDelete: { type: Boolean, default: true },
822886
showContent: { type: Boolean, default: false },
887+
showDelete: { type: Boolean, default: true },
888+
showModify: { type: Boolean, default: false },
823889
},
824890
setup(props) {
825891
const showcontent = ref(false);
892+
const showmodify = ref(false);
893+
const snackbarStore = useSnackbarStore();
894+
895+
const copyToClipboard = async (msg) => {
896+
let textToCopy = '';
897+
try {
898+
if (typeof msg.content === 'string') {
899+
textToCopy = msg.content;
900+
} else if (Array.isArray(msg.content)) {
901+
for (const item of msg.content) {
902+
if (item.type === 'text' && typeof item.text === 'string') {
903+
textToCopy = item.text;
904+
}
905+
}
906+
}
907+
await navigator.clipboard.writeText(textToCopy);
908+
snackbarStore.showSuccessMessage('$vuetify.dataIterator.snackbar.copied')
909+
} catch (err) {
910+
snackbarStore.showErrorMessage(err)
911+
}
912+
};
826913

827914
return {
915+
copyToClipboard,
828916
showcontent,
917+
showmodify,
829918
};
830919
}
831920
});
832921
const TuuiChatBox = {
833922
template: '#tuui-chat-box-template',
834923
components: {
835924
TuuiImgDialog,
836-
TuuiChatCard
925+
TuuiChatCard,
837926
},
838927
props: {
839928
messages: { type: Object, required: true },
@@ -846,9 +935,12 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
846935
components: {
847936
vuedraggable,
848937
TuuiChatBox,
938+
TuuiChatCard
849939
},
850940
setup() {
851941

942+
const snackbarStore = useSnackbarStore();
943+
852944
const useAgentStore = defineStore({
853945
id: "agentStore",
854946
state: () => ({
@@ -1065,54 +1157,6 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
10651157
},
10661158
});
10671159

1068-
const useSnackbarStore = defineStore({
1069-
id: "snackbarStore",
1070-
state: () => ({
1071-
isShow: false,
1072-
message: "",
1073-
type: "",
1074-
}),
1075-
1076-
persist: {
1077-
enabled: true,
1078-
strategies: [{ storage: localStorage, paths: [""] }],
1079-
},
1080-
1081-
getters: {},
1082-
actions: {
1083-
showMessage(message, type = "") {
1084-
this.isShow = true;
1085-
this.message = message;
1086-
this.type = type;
1087-
messageStore.generating = false
1088-
},
1089-
1090-
showErrorMessage(message) {
1091-
this.showMessage(message, "error");
1092-
},
1093-
showSuccessMessage(message) {
1094-
this.showMessage(message, "success");
1095-
},
1096-
showInfoMessage(message) {
1097-
this.showMessage(message, "info");
1098-
},
1099-
showWarningMessage(message) {
1100-
this.showMessage(message, "warning");
1101-
},
1102-
getIcon() {
1103-
const icon = {
1104-
info: "mdi-information",
1105-
success: "mdi-check-circle",
1106-
error: "mdi-alert-circle",
1107-
warning: "mdi-alert",
1108-
};
1109-
1110-
return icon[this.type];
1111-
},
1112-
},
1113-
});
1114-
1115-
11161160
const useMessageStore = defineStore({
11171161
id: "messageStore",
11181162
state: () => ({
@@ -1131,6 +1175,10 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
11311175
snackbarStore.showSuccessMessage('$vuetify.dataIterator.snackbar.addnew')
11321176
}
11331177
},
1178+
stop() {
1179+
this.generating = false,
1180+
snackbarStore.showInfoMessage('$vuetify.dataIterator.snackbar.stopped')
1181+
},
11341182
clear() {
11351183
this.userMessage = "";
11361184
this.images = [];
@@ -1184,16 +1232,15 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
11841232
role: "user",
11851233
});
11861234

1235+
if (this.conversation.length == 1) {
1236+
historyStore.init(this.conversation)
1237+
}
1238+
11871239
this.startInference();
11881240
}
11891241
},
11901242
startInference: async function () {
11911243
this.clear();
1192-
// Clear the input
1193-
// Create a completion
1194-
if (this.conversation.length == 1) {
1195-
historyStore.init(this.conversation)
1196-
}
11971244

11981245
// Image is too large, only latest query could be kept
11991246
const conversation = this.conversation.reduce((newConversation, item) => {
@@ -1362,7 +1409,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
13621409
});
13631410

13641411
const chatbotStore = useChatbotStore();
1365-
const snackbarStore = useSnackbarStore();
1412+
13661413
const settingStore = useSettingStore();
13671414
const agentStore = useAgentStore();
13681415
const messageStore = useMessageStore();
@@ -1540,7 +1587,9 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
15401587

15411588
watch(computed(() => messageStore.conversation),
15421589
(newValue, oldValue) => {
1543-
asyncScrollToBottom()
1590+
if (newValue[newValue.length - 1] !== oldValue[oldValue.length - 1]) {
1591+
asyncScrollToBottom();
1592+
}
15441593
}, { deep: true });
15451594

15461595
watch(computed(() => messageStore.images),
@@ -1760,6 +1809,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
17601809
stopped: 'Generating stopped.',
17611810
parseStreamFail: 'Cannot read the stream.',
17621811
parseConfigFail: 'Cannot parse the config file.',
1812+
copied: 'Copied to clipboard.'
17631813
}
17641814
},
17651815
},
@@ -1793,6 +1843,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
17931843
stopped: 'Generazione interrotta.',
17941844
parseStreamFail: 'Impossibile leggere il flusso.',
17951845
parseConfigFail: 'Impossibile analizzare il file di configurazione.',
1846+
copied: 'Copiato negli appunti.'
17961847
}
17971848
},
17981849
},
@@ -1817,6 +1868,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18171868
stopped: 'ビルドを停止します',
18181869
parseStreamFail: 'データフローを解決できませんでした',
18191870
parseConfigFail: '設定ファイルを解決できません',
1871+
copied: 'クリップボードにコピーされました'
18201872
}
18211873
},
18221874
},
@@ -1843,6 +1895,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18431895
stopped: 'Genereringen har stoppats.',
18441896
parseStreamFail: 'Det går inte att läsa strömmen.',
18451897
parseConfigFail: 'Det går inte att parsa konfigurationsfilen.',
1898+
copied: 'Kopierad till urklipp.'
18461899
}
18471900
},
18481901
},
@@ -1876,6 +1929,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18761929
stopped: '停止生成',
18771930
parseStreamFail: '无法解析数据流',
18781931
parseConfigFail: '无法解析配置文件',
1932+
copied: '已复制到剪切板'
18791933
}
18801934
},
18811935
},

0 commit comments

Comments
 (0)