改进 聊天
This commit is contained in:
parent
20574c967e
commit
48196a4942
@ -11,11 +11,15 @@
|
|||||||
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
|
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@f3ve/vue-markdown-it": "^0.2.2",
|
||||||
"@kangc/v-md-editor": "^2.3.18",
|
"@kangc/v-md-editor": "^2.3.18",
|
||||||
|
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
|
"katex": "^0.16.11",
|
||||||
"lottie-web": "^5.12.2",
|
"lottie-web": "^5.12.2",
|
||||||
|
"markdown-it-latex2img": "^0.0.6",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
"pinia-plugin-persistedstate": "^4.0.0",
|
"pinia-plugin-persistedstate": "^4.0.0",
|
||||||
"vooks": "^0.2.12",
|
"vooks": "^0.2.12",
|
||||||
@ -24,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.18.10",
|
"@babel/types": "^7.18.10",
|
||||||
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@vicons/ionicons5": "^0.12.0",
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
|
@ -1,52 +1,87 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- flex 布局 -->
|
<div class="text-base">
|
||||||
<n-flex> </n-flex>
|
<n-divider> <small>这是聊天的开头</small> </n-divider>
|
||||||
|
|
||||||
<n-divider> <small>这是聊天的开头</small> </n-divider>
|
<div v-for="(message, index) in chat_messages" :key="index">
|
||||||
|
<div v-if="message.role === 'user' && message.content">
|
||||||
|
<!-- 用户消息 -->
|
||||||
|
<n-flex justify="end">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<!-- <vue-markdown-it
|
||||||
|
:source="message.content"
|
||||||
|
:options="markdownOptions"
|
||||||
|
:plugins="markdownPlugins"
|
||||||
|
/> -->
|
||||||
|
<!-- <v-md-preview :text="message.content" height="500px"></v-md-preview> -->
|
||||||
|
<div v-html="mdIt.render(message.content)"></div>
|
||||||
|
</div>
|
||||||
|
<n-avatar round size="large" :src="userStore.user.avatar" />
|
||||||
|
</n-flex>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="message.role === 'assistant' && message.content" class="mt-3">
|
||||||
|
<!-- 助理消息 -->
|
||||||
|
<n-flex justify="start">
|
||||||
|
<n-avatar round size="large" :src="leaflowPng" />
|
||||||
|
|
||||||
<div v-for="(message, index) in chat_messages" :key="index">
|
<div class="align-middle">
|
||||||
<div v-if="message.role === 'user'">
|
<!-- <vue-markdown-it
|
||||||
<!-- 用户消息 -->
|
:source="message.content"
|
||||||
<n-flex justify="end">
|
:options="markdownOptions"
|
||||||
<div class="align-middle">
|
:plugins="[markdownPlugins]"
|
||||||
<v-md-preview :text="message.content" height="500px"></v-md-preview>
|
/> -->
|
||||||
</div>
|
|
||||||
<n-avatar round size="large" :src="userStore.user.avatar" />
|
|
||||||
</n-flex>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="message.role === 'assistant'">
|
|
||||||
<!-- 助理消息 -->
|
|
||||||
<n-flex justify="start">
|
|
||||||
<n-avatar round size="large" :src="leaflowPng" />
|
|
||||||
|
|
||||||
<div class="align-middle">
|
<!-- <div v-html="mdIt.render('# Math Rulez! \n $\\sqrt{3x-1}+(1+x)^2$')"></div> -->
|
||||||
<v-md-preview :text="message.content" height="500px"></v-md-preview>
|
|
||||||
</div>
|
<div v-html="mdIt.render(message.content)"></div>
|
||||||
</n-flex>
|
|
||||||
|
<!-- <v-md-preview :text="message.content" height="500px"></v-md-preview> -->
|
||||||
|
</div>
|
||||||
|
</n-flex>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 这里要加上输入框占的部分,但是那个占了很多,所以设置大一点 -->
|
<!-- 这里要加上输入框占的部分,但是那个占了很多,所以设置大一点 -->
|
||||||
<div style="margin-bottom: 10rem;"></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 { EntityChatMessage } from "@/api";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import VMdPreview from "@kangc/v-md-editor/lib/preview";
|
|
||||||
import "@kangc/v-md-editor/lib/style/preview.css";
|
|
||||||
import githubTheme from "@kangc/v-md-editor/lib/theme/github.js";
|
|
||||||
import "@kangc/v-md-editor/lib/theme/style/github.css";
|
|
||||||
import leaflowPng from "@/assets/images/leaflow.png";
|
import leaflowPng from "@/assets/images/leaflow.png";
|
||||||
|
import markdownKatex from "@traptitech/markdown-it-katex";
|
||||||
|
import markdownIt from "markdown-it";
|
||||||
|
import markdownLatex from "markdown-it-latex2img";
|
||||||
// highlightjs
|
// highlightjs
|
||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
|
|
||||||
VMdPreview.use(githubTheme, {
|
import { VueMarkdownIt } from "@f3ve/vue-markdown-it";
|
||||||
Hljs: hljs,
|
|
||||||
|
const mdIt = markdownIt();
|
||||||
|
|
||||||
|
// set options
|
||||||
|
mdIt.options.highlight = function (str: string, lang: string) {
|
||||||
|
if (!lang) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return hljs.highlight(str, { language: lang }).value;
|
||||||
|
};
|
||||||
|
|
||||||
|
mdIt.use(markdownKatex, {
|
||||||
|
throwOnError: false,
|
||||||
|
errorColor: "#cc0000",
|
||||||
|
output: "html",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mdIt.use(markdownKatex)
|
||||||
|
mdIt.use(markdownLatex);
|
||||||
|
|
||||||
|
const markdownOptions = {
|
||||||
|
html: true,
|
||||||
|
linkify: true,
|
||||||
|
};
|
||||||
|
const markdownPlugins = [markdownKatex];
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MessageList :chat_messages="chatMessages" />
|
<MessageList :chat_messages="chatMessages" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -23,7 +23,6 @@
|
|||||||
<div
|
<div
|
||||||
ref="inputContainer"
|
ref="inputContainer"
|
||||||
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
|
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
|
||||||
@keyup.enter="sendText"
|
|
||||||
>
|
>
|
||||||
<div class="overflow-x-hidden h-full w-full flex items-center">
|
<div class="overflow-x-hidden h-full w-full flex items-center">
|
||||||
<n-scrollbar class="max-h-96">
|
<n-scrollbar class="max-h-96">
|
||||||
@ -33,7 +32,10 @@
|
|||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
placeholder="请输入文本..."
|
placeholder="请输入文本..."
|
||||||
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
|
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
|
||||||
|
@keydown="onKeydown"
|
||||||
@input="updateInputHeight"
|
@input="updateInputHeight"
|
||||||
|
@compositionstart="handleCompositionStart"
|
||||||
|
@compositionend="handleCompositionEnd"
|
||||||
></div>
|
></div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
@ -73,6 +75,7 @@ import {
|
|||||||
} from "@vicons/ionicons5";
|
} from "@vicons/ionicons5";
|
||||||
import { EntityChatMessage } from "@/api";
|
import { EntityChatMessage } from "@/api";
|
||||||
import getApi from "@/plugins/api";
|
import getApi from "@/plugins/api";
|
||||||
|
import MessageList from "./MessageList.vue";
|
||||||
|
|
||||||
// 获取组件传入的 chatId
|
// 获取组件传入的 chatId
|
||||||
const chatId: Ref<string | number | undefined | null> = ref(null);
|
const chatId: Ref<string | number | undefined | null> = ref(null);
|
||||||
@ -103,8 +106,28 @@ const isPlaceholderVisible = ref(true);
|
|||||||
const triggerTimes = ref(0);
|
const triggerTimes = ref(0);
|
||||||
const showSendBtn = ref(false);
|
const showSendBtn = ref(false);
|
||||||
const content = ref("");
|
const content = ref("");
|
||||||
|
const inputExpanded = ref(false);
|
||||||
const chatMessages: Ref<EntityChatMessage[] | undefined> = ref([]);
|
const chatMessages: Ref<EntityChatMessage[] | undefined> = ref([]);
|
||||||
|
|
||||||
|
function onKeydown(e: KeyboardEvent) {
|
||||||
|
// 带 shift 不触发
|
||||||
|
if (e.shiftKey || inputExpanded.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 content.value 是空的,但是同时按了 Shift 和 Enter,则阻止
|
||||||
|
if (content.value.trim() === "" && e.code === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁止换行起手
|
||||||
|
if (!compositionStart.value && e.code === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
sendText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateInputHeight() {
|
function updateInputHeight() {
|
||||||
if (!inputText?.value || !inputContainer.value || !actionContainer.value) {
|
if (!inputText?.value || !inputContainer.value || !actionContainer.value) {
|
||||||
return;
|
return;
|
||||||
@ -132,7 +155,10 @@ function updateInputHeight() {
|
|||||||
|
|
||||||
const lines = input.innerText.split("\n").length;
|
const lines = input.innerText.split("\n").length;
|
||||||
|
|
||||||
if (lines > 3 || height > 50) {
|
if (lines > 1) {
|
||||||
|
triggerTimes.value += 8;
|
||||||
|
}
|
||||||
|
if (height > 50) {
|
||||||
triggerTimes.value += 1;
|
triggerTimes.value += 1;
|
||||||
} else {
|
} else {
|
||||||
triggerTimes.value -= 1;
|
triggerTimes.value -= 1;
|
||||||
@ -141,28 +167,30 @@ function updateInputHeight() {
|
|||||||
if (triggerTimes.value > 8) {
|
if (triggerTimes.value > 8) {
|
||||||
container.classList.add("rounded-lg");
|
container.classList.add("rounded-lg");
|
||||||
container.classList.remove("rounded-full");
|
container.classList.remove("rounded-full");
|
||||||
container.classList.remove("max-w-2xl");
|
// container.classList.remove("max-w-2xl");
|
||||||
container.classList.remove("w-2xl");
|
// container.classList.remove("w-2xl");
|
||||||
container.classList.add("flex-col");
|
container.classList.add("flex-col");
|
||||||
action.classList.add("w-full");
|
// action.classList.add("w-full");
|
||||||
action.classList.add("text-right");
|
action.classList.add("text-right");
|
||||||
action.classList.add("pt-4");
|
action.classList.add("pt-4");
|
||||||
action.classList.add("pb-0");
|
action.classList.add("pb-0");
|
||||||
action.classList.add("mt-2");
|
action.classList.add("mt-2");
|
||||||
showSendBtn.value = true;
|
showSendBtn.value = true;
|
||||||
|
inputExpanded.value = true;
|
||||||
} else {
|
} else {
|
||||||
container.classList.remove("rounded-lg");
|
container.classList.remove("rounded-lg");
|
||||||
container.classList.add("rounded-full");
|
container.classList.add("rounded-full");
|
||||||
container.classList.remove("flex-col");
|
container.classList.remove("flex-col");
|
||||||
container.classList.add("w-2xl");
|
// container.classList.add("w-2xl");
|
||||||
container.classList.add("max-w-2xl");
|
// container.classList.add("max-w-2xl");
|
||||||
|
|
||||||
action.classList.remove("w-full");
|
// action.classList.remove("w-full");
|
||||||
action.classList.remove("text-right");
|
action.classList.remove("text-right");
|
||||||
action.classList.remove("pt-4");
|
action.classList.remove("pt-4");
|
||||||
action.classList.remove("pt-0");
|
action.classList.remove("pt-0");
|
||||||
action.classList.remove("mt-2");
|
action.classList.remove("mt-2");
|
||||||
showSendBtn.value = false;
|
showSendBtn.value = false;
|
||||||
|
inputExpanded.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +230,8 @@ function sendText() {
|
|||||||
|
|
||||||
function sendMessage(text: string) {
|
function sendMessage(text: string) {
|
||||||
console.log("发送文本:", text);
|
console.log("发送文本:", text);
|
||||||
|
|
||||||
|
chatMessages.value?.push({ content: text, role: "user" });
|
||||||
// 实际发送文本到服务器的逻辑
|
// 实际发送文本到服务器的逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user