mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-19 00:23:45 +08:00
add md render
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
package com.zl.mjga.config.ai;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "deep-seek")
|
||||
public class DeepSeekConfiguration {
|
||||
|
||||
@jakarta.annotation.Resource
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
private Prompt prompt;
|
||||
@@ -18,4 +29,12 @@ public class DeepSeekConfiguration {
|
||||
public static class Prompt {
|
||||
private String system;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws IOException {
|
||||
Resource resource = resourceLoader.getResource("classpath:prompt.txt");
|
||||
prompt = new Prompt();
|
||||
prompt.setSystem(Files.readString(Paths.get(resource.getURI())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.time.Duration;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Sinks;
|
||||
@@ -34,8 +33,6 @@ public class AiController {
|
||||
.onError(sink::tryEmitError)
|
||||
.start();
|
||||
return sink.asFlux()
|
||||
.timeout(Duration.ofSeconds(120))
|
||||
.doOnCancel(SecurityContextHolder::clearContext)
|
||||
.doOnTerminate(SecurityContextHolder::clearContext);
|
||||
.timeout(Duration.ofSeconds(120));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
deep-seek:
|
||||
base-url: "https://api.deepseek.com"
|
||||
api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d"
|
||||
model-name: "deepseek-chat"
|
||||
prompt:
|
||||
system: "你是一个名叫「知路智能体」的企业级AI助手,能帮助用户解决各种问题。"
|
||||
model-name: "deepseek-chat"
|
||||
1
backend/src/main/resources/prompt.txt
Normal file
1
backend/src/main/resources/prompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
你是一个名为「知路智能体」的企业级AI助手,严格遵循使用Markdown格式来回复内容。
|
||||
49
frontend/package-lock.json
generated
49
frontend/package-lock.json
generated
@@ -13,7 +13,9 @@
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"apexcharts": "^3.46.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.2.6",
|
||||
"flowbite": "^3.1.2",
|
||||
"marked": "^15.0.12",
|
||||
"openapi-fetch": "^0.13.5",
|
||||
"pinia": "^3.0.1",
|
||||
"tailwindcss": "^4.0.14",
|
||||
@@ -26,6 +28,8 @@
|
||||
"@faker-js/faker": "^9.6.0",
|
||||
"@playwright/test": "^1.51.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitest/browser": "^3.0.9",
|
||||
@@ -2127,12 +2131,29 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/dompurify": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "http://mirrors.tencent.com/npm/@types/dompurify/-/dompurify-3.0.5.tgz",
|
||||
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/marked": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "http://mirrors.tencent.com/npm/@types/marked/-/marked-5.0.2.tgz",
|
||||
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.15.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz",
|
||||
@@ -2163,6 +2184,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "http://mirrors.tencent.com/npm/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
@@ -3327,6 +3355,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "http://mirrors.tencent.com/npm/dompurify/-/dompurify-3.2.6.tgz",
|
||||
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -4424,6 +4461,18 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "15.0.12",
|
||||
"resolved": "http://mirrors.tencent.com/npm/marked/-/marked-15.0.12.tgz",
|
||||
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"apexcharts": "^3.46.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.2.6",
|
||||
"flowbite": "^3.1.2",
|
||||
"marked": "^15.0.12",
|
||||
"openapi-fetch": "^0.13.5",
|
||||
"pinia": "^3.0.1",
|
||||
"tailwindcss": "^4.0.14",
|
||||
@@ -36,6 +38,8 @@
|
||||
"@faker-js/faker": "^9.6.0",
|
||||
"@playwright/test": "^1.51.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitest/browser": "^3.0.9",
|
||||
@@ -52,6 +56,8 @@
|
||||
"vue-tsc": "^2.2.8"
|
||||
},
|
||||
"msw": {
|
||||
"workerDirectory": ["public"]
|
||||
"workerDirectory": [
|
||||
"public"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<LoadingIcon :textColor="'text-gray-900'"
|
||||
v-if="isLoading && !chatElement.isUser && chatElement.content === ''" />
|
||||
</div>
|
||||
<p class="text-base font-normal py-2.5 text-gray-900 dark:text-white">
|
||||
{{ chatElement.content }}
|
||||
</p>
|
||||
<div class="markdown-content text-base font-normal py-2.5 text-gray-900 dark:text-white"
|
||||
v-html="renderMarkdown(chatElement.content)">
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
@@ -63,6 +63,8 @@ import useUserStore from "../composables/store/useUserStore";
|
||||
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
||||
import { z } from "zod";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const { messages, chat, isLoading, cancel } = useAiChat();
|
||||
const { user } = useUserStore();
|
||||
@@ -70,6 +72,18 @@ const inputMessage = ref("");
|
||||
const chatContainer = ref<HTMLElement | null>(null);
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
});
|
||||
|
||||
|
||||
const renderMarkdown = (content: string) => {
|
||||
if (!content) return '';
|
||||
const rawHtml = marked(content);
|
||||
return DOMPurify.sanitize(rawHtml as string);
|
||||
};
|
||||
|
||||
const chatElements = computed(() => {
|
||||
return messages.value.map((message, index) => {
|
||||
return {
|
||||
@@ -80,6 +94,11 @@ const chatElements = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
watch(messages, (newVal) => {
|
||||
console.log('原始消息:', newVal[newVal.length - 1]);
|
||||
console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
|
||||
}, { deep: true });
|
||||
|
||||
watch(
|
||||
chatElements,
|
||||
async () => {
|
||||
@@ -132,3 +151,5 @@ onUnmounted(() => {
|
||||
cancel();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user