forked from Leaf/amber-ui
更新 src/components/chat/MessageList.vue
This commit is contained in:
parent
75b4d63b62
commit
e94697e245
@ -1,188 +1,138 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-base">
|
<div class="chat-container">
|
||||||
<n-divider> <small class="select-none">这是聊天的开头</small> </n-divider>
|
<div class="chat-messages">
|
||||||
|
|
||||||
<div v-for="(message, index) in chat_messages" :key="index">
|
|
||||||
<div class="mt-10 mb-10"></div>
|
|
||||||
<div v-if="message.role === 'file'">
|
|
||||||
<!-- 文件类型 -->
|
|
||||||
<n-flex justify="end">
|
|
||||||
<div class="flex items-center flex-nowrap">
|
|
||||||
<div class="flex items-end flex-col">
|
|
||||||
<div>
|
|
||||||
<n-divider class="!p-0 !m-0" title-placement="right">
|
|
||||||
{{ userStore.user.name }}
|
|
||||||
</n-divider>
|
|
||||||
</div>
|
|
||||||
<n-image
|
|
||||||
v-if="
|
|
||||||
message.file && message.file.mime_type?.startsWith('image/')
|
|
||||||
"
|
|
||||||
width="100"
|
|
||||||
:src="fileBaseUrl + '/download/' + message.file.file_hash"
|
|
||||||
/>
|
|
||||||
<n-text italic depth="3" v-else> 你上传了一个文件 </n-text>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative h-full">
|
//advice and textarea
|
||||||
<n-avatar
|
<div class="chat-input-container">
|
||||||
round
|
<textarea
|
||||||
size="large"
|
v-model="message"
|
||||||
:src="userStore.user.avatar"
|
@input="onInput"
|
||||||
class="ml-3 min-w-10 absolute top-0"
|
ref="inputBox"
|
||||||
/>
|
rows="1"
|
||||||
</div>
|
class="chat-input"
|
||||||
</div>
|
placeholder="输入你的消息..."
|
||||||
</n-flex>
|
></textarea>
|
||||||
</div>
|
<button @click="sendMessage" :disabled="sending">发送</button>
|
||||||
<div v-else-if="(message.role === 'user' || message.role === 'user_hide' || message.role === 'user_later') && message.content">
|
<div v-if="suggestions.length" class="suggestions">
|
||||||
<!-- 用户消息 -->
|
<ul>
|
||||||
<n-flex justify="end">
|
<li
|
||||||
<div class="flex items-center flex-nowrap">
|
v-for="(suggestion, index) in suggestions"
|
||||||
<!-- <vue-markdown-it
|
:key="index"
|
||||||
:source="message.content"
|
@click="selectSuggestion(suggestion)"
|
||||||
:options="markdownOptions"
|
|
||||||
:plugins="markdownPlugins"
|
|
||||||
/> -->
|
|
||||||
<!-- <v-md-preview :text="message.content" height="500px"></v-md-preview> -->
|
|
||||||
<div class="flex items-end flex-col">
|
|
||||||
<div>
|
|
||||||
<n-divider class="!p-0 !m-0" title-placement="right">
|
|
||||||
{{ userStore.user.name }}
|
|
||||||
</n-divider>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="mdInited"
|
|
||||||
class="break-all break-words markdown-body"
|
|
||||||
v-html="mdIt.render(message.content)"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative h-full">
|
|
||||||
<n-avatar
|
|
||||||
round
|
|
||||||
size="large"
|
|
||||||
:src="userStore.user.avatar"
|
|
||||||
class="ml-3 min-w-10 absolute top-0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-flex>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="message.role === 'assistant' && message.content">
|
|
||||||
<!-- 助理消息 -->
|
|
||||||
<n-flex justify="start" class="!flex-nowrap">
|
|
||||||
<div class="relative h-full">
|
|
||||||
<n-avatar
|
|
||||||
round
|
|
||||||
size="large"
|
|
||||||
:src="leaflowPng"
|
|
||||||
class="min-w-10 min-h-10 p-1.5 absolute top-0 !bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center flex-nowrap">
|
|
||||||
<!-- <vue-markdown-it
|
|
||||||
:source="message.content"
|
|
||||||
:options="markdownOptions"
|
|
||||||
:plugins="[markdownPlugins]"
|
|
||||||
/> -->
|
|
||||||
|
|
||||||
<!-- <div v-html="mdIt.render('# Math Rulez! \n $\\sqrt{3x-1}+(1+x)^2$')"></div> -->
|
|
||||||
|
|
||||||
<!-- 当 message.content 变化时,重新渲染 -->
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
message.assistant_id &&
|
|
||||||
message.assistant !== null &&
|
|
||||||
message.assistant?.name !== ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<n-divider class="!p-0 !m-0" title-placement="left">
|
{{ suggestion }}
|
||||||
{{ message.assistant?.name }}
|
</li>
|
||||||
</n-divider>
|
</ul>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="mdInited"
|
|
||||||
class="break-all break-words markdown-body"
|
|
||||||
v-html="mdIt.render(message.content)"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<!-- <div v-html="mdIt.render(message.content)"></div> -->
|
|
||||||
|
|
||||||
<!-- <v-md-preview :text="message.content" height="500px"></v-md-preview> -->
|
|
||||||
</div>
|
|
||||||
</n-flex>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 这里要加上输入框占的部分,但是那个占了很多,所以设置大一点 -->
|
|
||||||
<div style="margin-bottom: 10rem"></div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { EntityChatMessage } from "@/api";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
import leaflowPng from "@/assets/images/leaflow.png";
|
|
||||||
import markdownKatex from "@traptitech/markdown-it-katex";
|
|
||||||
import markdownIt from "markdown-it";
|
|
||||||
// highlightjs
|
|
||||||
import hljs from "highlight.js";
|
|
||||||
import config from "@/config/config";
|
|
||||||
|
|
||||||
const mdIt = markdownIt();
|
const message = ref("");
|
||||||
const mdInited = ref(true);
|
const suggestions = ref<string[]>([]);
|
||||||
|
const sending = ref(false);
|
||||||
|
|
||||||
const unsupportedLanguages = ["assembly", "blade", "vue"];
|
// 模拟从服务器获取输入建议
|
||||||
mdIt.options.highlight = function (str: string, lang: string) {
|
const fetchSuggestions = (query: string) => {
|
||||||
// TODO: 前面的区域以后再来探索吧
|
// 模拟异步请求
|
||||||
lang = "text"
|
return new Promise<string[]>((resolve) => {
|
||||||
if (!lang || unsupportedLanguages.includes(lang)) {
|
setTimeout(() => {
|
||||||
// return str;
|
resolve(["建议1", "建议2", "建议3"].filter((suggestion) => suggestion.includes(query)));
|
||||||
lang = "text"
|
}, 300);
|
||||||
}
|
});
|
||||||
|
|
||||||
return hljs.highlight(str, { language: lang }).value;
|
|
||||||
};
|
};
|
||||||
mdIt.use(markdownKatex, {
|
|
||||||
throwOnError: false,
|
|
||||||
errorColor: "#cc0000",
|
|
||||||
output: "html",
|
|
||||||
});
|
|
||||||
|
|
||||||
// async function initMD() {
|
const onInput = async () => {
|
||||||
// mdIt.use(
|
adjustHeight();
|
||||||
// await Shiki({
|
if (message.value) {
|
||||||
// themes: {
|
suggestions.value = await fetchSuggestions(message.value);
|
||||||
// light: "vitesse-light",
|
} else {
|
||||||
// dark: "vitesse-dark",
|
suggestions.value = [];
|
||||||
// },
|
}
|
||||||
// })
|
};
|
||||||
// );
|
|
||||||
|
|
||||||
// mdIt.use(markdownKatex, {
|
const adjustHeight = () => {
|
||||||
// throwOnError: false,
|
const inputBox = refs.inputBox as HTMLTextAreaElement;
|
||||||
// errorColor: "#cc0000",
|
inputBox.style.height = 'auto';
|
||||||
// output: "html",
|
inputBox.style.height = inputBox.scrollHeight + 'px';
|
||||||
// });
|
};
|
||||||
|
|
||||||
// mdInited.value = true;
|
const selectSuggestion = (suggestion: string) => {
|
||||||
// }
|
message.value = suggestion;
|
||||||
const userStore = useUserStore();
|
suggestions.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps({
|
const sendMessage = async () => {
|
||||||
chat_messages: {
|
if (!message.value.trim()) return;
|
||||||
required: true,
|
|
||||||
type: Array as () => EntityChatMessage[],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const chat_messages = toRef(props, "chat_messages") as Ref<EntityChatMessage[]>;
|
sending.value = true;
|
||||||
const fileBaseUrl = config.backend + "/api/v1/files";
|
try {
|
||||||
|
// 发送消息逻辑
|
||||||
onMounted(() => {
|
console.log("发送消息: ", message.value);
|
||||||
// initMD();
|
message.value = "";
|
||||||
});
|
suggestions.value = [];
|
||||||
|
adjustHeight();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("发送消息失败: ", error);
|
||||||
|
} finally {
|
||||||
|
sending.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: #fff;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions li {
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions li:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user