Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
e94697e245 | |||
75b4d63b62 | |||
255dbe2abf |
@ -1,188 +1,138 @@
|
||||
<template>
|
||||
<div class="text-base">
|
||||
<n-divider> <small class="select-none">这是聊天的开头</small> </n-divider>
|
||||
<div class="chat-container">
|
||||
<div class="chat-messages">
|
||||
</div>
|
||||
|
||||
<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 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 === 'user' || message.role === 'user_hide' || message.role === 'user_later') && message.content">
|
||||
<!-- 用户消息 -->
|
||||
<n-flex justify="end">
|
||||
<div class="flex items-center flex-nowrap">
|
||||
<!-- <vue-markdown-it
|
||||
:source="message.content"
|
||||
: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">
|
||||
{{ message.assistant?.name }}
|
||||
</n-divider>
|
||||
</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>
|
||||
//advice and textarea
|
||||
<div class="chat-input-container">
|
||||
<textarea
|
||||
v-model="message"
|
||||
@input="onInput"
|
||||
ref="inputBox"
|
||||
rows="1"
|
||||
class="chat-input"
|
||||
placeholder="输入你的消息..."
|
||||
></textarea>
|
||||
<button @click="sendMessage" :disabled="sending">发送</button>
|
||||
<div v-if="suggestions.length" class="suggestions">
|
||||
<ul>
|
||||
<li
|
||||
v-for="(suggestion, index) in suggestions"
|
||||
:key="index"
|
||||
@click="selectSuggestion(suggestion)"
|
||||
>
|
||||
{{ suggestion }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 这里要加上输入框占的部分,但是那个占了很多,所以设置大一点 -->
|
||||
<div style="margin-bottom: 10rem"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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";
|
||||
import { ref } from "vue";
|
||||
|
||||
const mdIt = markdownIt();
|
||||
const mdInited = ref(true);
|
||||
const message = ref("");
|
||||
const suggestions = ref<string[]>([]);
|
||||
const sending = ref(false);
|
||||
|
||||
const unsupportedLanguages = ["assembly", "blade", "vue"];
|
||||
mdIt.options.highlight = function (str: string, lang: string) {
|
||||
// TODO: 前面的区域以后再来探索吧
|
||||
lang = "text"
|
||||
if (!lang || unsupportedLanguages.includes(lang)) {
|
||||
// return str;
|
||||
lang = "text"
|
||||
}
|
||||
|
||||
return hljs.highlight(str, { language: lang }).value;
|
||||
// 模拟从服务器获取输入建议
|
||||
const fetchSuggestions = (query: string) => {
|
||||
// 模拟异步请求
|
||||
return new Promise<string[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(["建议1", "建议2", "建议3"].filter((suggestion) => suggestion.includes(query)));
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
mdIt.use(markdownKatex, {
|
||||
throwOnError: false,
|
||||
errorColor: "#cc0000",
|
||||
output: "html",
|
||||
});
|
||||
|
||||
// async function initMD() {
|
||||
// mdIt.use(
|
||||
// await Shiki({
|
||||
// themes: {
|
||||
// light: "vitesse-light",
|
||||
// dark: "vitesse-dark",
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
const onInput = async () => {
|
||||
adjustHeight();
|
||||
if (message.value) {
|
||||
suggestions.value = await fetchSuggestions(message.value);
|
||||
} else {
|
||||
suggestions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// mdIt.use(markdownKatex, {
|
||||
// throwOnError: false,
|
||||
// errorColor: "#cc0000",
|
||||
// output: "html",
|
||||
// });
|
||||
const adjustHeight = () => {
|
||||
const inputBox = refs.inputBox as HTMLTextAreaElement;
|
||||
inputBox.style.height = 'auto';
|
||||
inputBox.style.height = inputBox.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
// mdInited.value = true;
|
||||
// }
|
||||
const userStore = useUserStore();
|
||||
const selectSuggestion = (suggestion: string) => {
|
||||
message.value = suggestion;
|
||||
suggestions.value = [];
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
chat_messages: {
|
||||
required: true,
|
||||
type: Array as () => EntityChatMessage[],
|
||||
},
|
||||
});
|
||||
const sendMessage = async () => {
|
||||
if (!message.value.trim()) return;
|
||||
|
||||
const chat_messages = toRef(props, "chat_messages") as Ref<EntityChatMessage[]>;
|
||||
const fileBaseUrl = config.backend + "/api/v1/files";
|
||||
|
||||
onMounted(() => {
|
||||
// initMD();
|
||||
});
|
||||
sending.value = true;
|
||||
try {
|
||||
// 发送消息逻辑
|
||||
console.log("发送消息: ", message.value);
|
||||
message.value = "";
|
||||
suggestions.value = [];
|
||||
adjustHeight();
|
||||
} catch (error) {
|
||||
console.error("发送消息失败: ", error);
|
||||
} finally {
|
||||
sending.value = false;
|
||||
}
|
||||
};
|
||||
</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>
|
||||
|
@ -147,6 +147,14 @@ const features = ref([
|
||||
longDescription: '文档管理是 Amber 知识库的核心功能之一。对话中上传的文件将自动保存至知识库,您只需设置助理关联知识库,即可实现快速检索和使用。假设您是一位项目经理,您可以在对话中上传项目计划书,并在需要时快速检索相关内容。Amber 在您发送消息时,将会自动搜索资料库。此外,我们正在开发桌面端软件,该软件可以根据您的需要同步文档至 Amber,以便随时调用🗂️。',
|
||||
showLong: false
|
||||
},
|
||||
{
|
||||
icon: "🔍",
|
||||
title: "AI 搜索功能",
|
||||
shortDescription: "利用 AI 技术进行快速准确的信息搜索。",
|
||||
longDescription:
|
||||
"Amber 的 AI 搜索功能让信息检索变得更加高效和智能。通过自然语言处理技术,您可以用简单的对话方式从海量数据中快速找到所需信息。无论是搜索文档、网页内容还是数据库记录,Amber 都能提供准确且相关的搜索结果,节省您的时间和精力。",
|
||||
showLong: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const partners = ref([
|
||||
|
Loading…
Reference in New Issue
Block a user