改进 聊天

This commit is contained in:
ivamp 2024-09-12 15:03:24 +08:00
parent 20574c967e
commit 48196a4942
3 changed files with 110 additions and 40 deletions

View File

@ -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",

View File

@ -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({

View File

@ -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;
} }
} }
@ -193,7 +221,7 @@ function sendText() {
input.innerText = ""; input.innerText = "";
updateInputHeight(); updateInputHeight();
chatMessages.value?.push({ chatMessages.value?.push({
content: textContent, content: textContent,
role: "user", role: "user",
@ -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" });
// //
} }