Skip to content

Commit

Permalink
Support input editing and clipboard copying
Browse files Browse the repository at this point in the history
  • Loading branch information
aiqlcom authored Nov 21, 2024
1 parent 241eed7 commit 698a97c
Showing 1 changed file with 130 additions and 76 deletions.
206 changes: 130 additions & 76 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@
display: flex;
}

.conversation-area {
margin: 6px 4px 4px 4px;
/* top left bot right*/
}

.fade-enter-active,
.fade-leave-active {
transition: all 0.5s ease;
Expand Down Expand Up @@ -217,6 +222,11 @@
}
}

.md-preview {
width: 100vw;
max-width: 100%;
}

.md-editor-preview p {
word-break: break-word;
}
Expand Down Expand Up @@ -369,7 +379,7 @@
@click="messageStore.sendMessage" icon="mdi-arrow-up">
</v-btn>
<v-btn v-else-if="messageStore.generating" size="small" color="primary" variant="elevated"
@click="snackbarStore.showInfoMessage('$vuetify.dataIterator.snackbar.stopped')"
@click="messageStore.stop"
icon="mdi-stop"></v-btn>
<v-btn v-else-if="messageStore.conversation.length > 0" size="small" color="primary"
variant="elevated" @click="messageStore.resendMessage" icon="mdi-autorenew"></v-btn>
Expand Down Expand Up @@ -722,20 +732,27 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
<div v-for="(message, index) in messages">
<div v-if="message.role === 'user'">
<div class="px-2 py-5 user-message">
<div class="message text-pre-wrap">
<div class="message">
<v-avatar :size=size class="mt-3 mr-3 mr-lg-6" color="primary" icon="mdi-account-circle">
</v-avatar>
<tuui-chat-card class="gradient text-pre-wrap" :index="index" :messages="messages">
<v-card-text v-if="Array.isArray(message.content)">
<div v-for="(item, index) in message.content" :key="index">
<div v-if="item.type=='text'">{{ item.text }}</div>
<tuui-img-dialog v-if="item.type=='image_url'"
:src="item.image_url.url"></tuui-img-dialog>
</div>
</v-card-text>
<v-card-text v-else>
{{ message.content }}
</v-card-text>
<tuui-chat-card class="gradient text-pre-wrap" :index="index" :messages="messages"
:show-modify="true">
<template v-slot:default="{ showmodify }">
<v-card-text v-if="Array.isArray(message.content)" class="md-preview">
<div v-for="(item, index) in message.content" :key="index">
<tuui-img-dialog v-if="item.type=='image_url'"
:src="item.image_url.url"></tuui-img-dialog>
<v-textarea class="conversation-area" variant="plain" density='compact'
auto-grow :counter="showmodify" :hide-details="!showmodify" rows="1"
:readonly="!showmodify" v-model="item.text"></v-textarea>
</div>
</v-card-text>
<v-card-text v-else class="md-preview pt-1">
<v-textarea class="conversation-area" variant="plain" density='compact' auto-grow
rows="1" :readonly="!showmodify" :counter="showmodify"
:hide-details="!showmodify" v-model="message.content"></v-textarea>
</v-card-text>
</template>
</tuui-chat-card>
</div>
</div>
Expand All @@ -748,12 +765,14 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
<tuui-chat-card :index="index" :messages="messages" :show-content="true">
<template v-slot:default="{ showcontent }">
<div v-if="showcontent">
<v-card-text style="white-space: pre-wrap;">
{{ message.content }}
<v-card-text class="md-preview pt-1">
<v-textarea class="conversation-area" variant="plain" density='compact'
auto-grow hide-details rows="1" readonly
v-model="message.content"></v-textarea>
</v-card-text>
</div>
<div v-else>
<md-preview :model-value="message.content"
<md-preview :model-value="message.content" class="md-preview"
:language="language == 'zhHans' ? 'zh-CN' : 'en-US'" :code-foldable="true"
auto-fold-threshold="Infinity"></md-preview>
</div>
Expand Down Expand Up @@ -782,16 +801,22 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
<v-hover open-delay="100">
<template v-slot:default="{ isHovering, props }">
<v-card v-bind="props" :elevation="isHovering ? 4 : 2">
<slot :showcontent="showcontent"></slot>
<slot :showcontent="showcontent" :showmodify="showmodify"></slot>
<v-expand-transition>
<div v-if="isHovering">
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" icon="mdi-content-copy" size="x-small" variant="plain"
@click="copyToClipboard(messages[index])"></v-btn>
<v-btn v-if="showModify" color="primary"
:icon=" showmodify ? 'mdi-check-bold' : 'mdi-lead-pencil'" size="x-small"
variant="plain" @click="showmodify = !showmodify" v-bind="showmodify"></v-btn>
<v-btn v-if="showContent" color="primary"
:icon=" showcontent ? 'mdi-eye-remove' : 'mdi-eye'" size="x-small" variant="plain"
@click="showcontent = !showcontent" v-bind="showcontent"></v-btn>
<v-btn v-if="showDelete" color="error" icon="mdi-delete-off-outline" size="x-small"
variant="plain" @click="messages.splice(index, 1)"></v-btn>
<v-btn v-if="showContent" color="primary" icon="mdi-eye" size="x-small" variant="plain"
@click="showcontent = !showcontent" v-bind="showcontent"></v-btn>
</v-card-actions>
</div>
</v-expand-transition>
Expand All @@ -807,6 +832,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
const { createI18n, useI18n } = VueI18n
const { en, it, ja, sv, zhHans } = 'vuetify/locale'

const useSnackbarStore = defineStore({
id: "snackbarStore",
state: () => ({
isShow: false,
message: "",
type: "",
}),

actions: {
showMessage(message, type = "") {
this.isShow = true;
this.message = message;
this.type = type;
},

showErrorMessage(message) {
this.showMessage(message, "error");
},
showSuccessMessage(message) {
this.showMessage(message, "success");
},
showInfoMessage(message) {
this.showMessage(message, "info");
},
showWarningMessage(message) {
this.showMessage(message, "warning");
},
getIcon() {
const icon = {
info: "mdi-information",
success: "mdi-check-circle",
error: "mdi-alert-circle",
warning: "mdi-alert",
};

return icon[this.type];
},
},
});

const TuuiImgDialog = {
template: '#tuui-img-dialog-template',
props: { src: { type: String, required: true } }
Expand All @@ -818,22 +883,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
props: {
index: { type: Number, required: true },
messages: { type: Object, required: true },
showDelete: { type: Boolean, default: true },
showContent: { type: Boolean, default: false },
showDelete: { type: Boolean, default: true },
showModify: { type: Boolean, default: false },
},
setup(props) {
const showcontent = ref(false);
const showmodify = ref(false);
const snackbarStore = useSnackbarStore();

const copyToClipboard = async (msg) => {
let textToCopy = '';
try {
if (typeof msg.content === 'string') {
textToCopy = msg.content;
} else if (Array.isArray(msg.content)) {
for (const item of msg.content) {
if (item.type === 'text' && typeof item.text === 'string') {
textToCopy = item.text;
}
}
}
await navigator.clipboard.writeText(textToCopy);
snackbarStore.showSuccessMessage('$vuetify.dataIterator.snackbar.copied')
} catch (err) {
snackbarStore.showErrorMessage(err)
}
};

return {
copyToClipboard,
showcontent,
showmodify,
};
}
});
const TuuiChatBox = {
template: '#tuui-chat-box-template',
components: {
TuuiImgDialog,
TuuiChatCard
TuuiChatCard,
},
props: {
messages: { type: Object, required: true },
Expand All @@ -846,9 +935,12 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
components: {
vuedraggable,
TuuiChatBox,
TuuiChatCard
},
setup() {

const snackbarStore = useSnackbarStore();

const useAgentStore = defineStore({
id: "agentStore",
state: () => ({
Expand Down Expand Up @@ -1065,54 +1157,6 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
},
});

const useSnackbarStore = defineStore({
id: "snackbarStore",
state: () => ({
isShow: false,
message: "",
type: "",
}),

persist: {
enabled: true,
strategies: [{ storage: localStorage, paths: [""] }],
},

getters: {},
actions: {
showMessage(message, type = "") {
this.isShow = true;
this.message = message;
this.type = type;
messageStore.generating = false
},

showErrorMessage(message) {
this.showMessage(message, "error");
},
showSuccessMessage(message) {
this.showMessage(message, "success");
},
showInfoMessage(message) {
this.showMessage(message, "info");
},
showWarningMessage(message) {
this.showMessage(message, "warning");
},
getIcon() {
const icon = {
info: "mdi-information",
success: "mdi-check-circle",
error: "mdi-alert-circle",
warning: "mdi-alert",
};

return icon[this.type];
},
},
});


const useMessageStore = defineStore({
id: "messageStore",
state: () => ({
Expand All @@ -1131,6 +1175,10 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
snackbarStore.showSuccessMessage('$vuetify.dataIterator.snackbar.addnew')
}
},
stop() {
this.generating = false,
snackbarStore.showInfoMessage('$vuetify.dataIterator.snackbar.stopped')
},
clear() {
this.userMessage = "";
this.images = [];
Expand Down Expand Up @@ -1184,16 +1232,15 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
role: "user",
});

if (this.conversation.length == 1) {
historyStore.init(this.conversation)
}

this.startInference();
}
},
startInference: async function () {
this.clear();
// Clear the input
// Create a completion
if (this.conversation.length == 1) {
historyStore.init(this.conversation)
}

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

const chatbotStore = useChatbotStore();
const snackbarStore = useSnackbarStore();

const settingStore = useSettingStore();
const agentStore = useAgentStore();
const messageStore = useMessageStore();
Expand Down Expand Up @@ -1540,7 +1587,9 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>

watch(computed(() => messageStore.conversation),
(newValue, oldValue) => {
asyncScrollToBottom()
if (newValue[newValue.length - 1] !== oldValue[oldValue.length - 1]) {
asyncScrollToBottom();
}
}, { deep: true });

watch(computed(() => messageStore.images),
Expand Down Expand Up @@ -1760,6 +1809,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
stopped: 'Generating stopped.',
parseStreamFail: 'Cannot read the stream.',
parseConfigFail: 'Cannot parse the config file.',
copied: 'Copied to clipboard.'
}
},
},
Expand Down Expand Up @@ -1793,6 +1843,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
stopped: 'Generazione interrotta.',
parseStreamFail: 'Impossibile leggere il flusso.',
parseConfigFail: 'Impossibile analizzare il file di configurazione.',
copied: 'Copiato negli appunti.'
}
},
},
Expand All @@ -1817,6 +1868,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
stopped: 'ビルドを停止します',
parseStreamFail: 'データフローを解決できませんでした',
parseConfigFail: '設定ファイルを解決できません',
copied: 'クリップボードにコピーされました'
}
},
},
Expand All @@ -1843,6 +1895,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
stopped: 'Genereringen har stoppats.',
parseStreamFail: 'Det går inte att läsa strömmen.',
parseConfigFail: 'Det går inte att parsa konfigurationsfilen.',
copied: 'Kopierad till urklipp.'
}
},
},
Expand Down Expand Up @@ -1876,6 +1929,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
stopped: '停止生成',
parseStreamFail: '无法解析数据流',
parseConfigFail: '无法解析配置文件',
copied: '已复制到剪切板'
}
},
},
Expand Down

0 comments on commit 698a97c

Please sign in to comment.