add md render

This commit is contained in:
Chuck1sn
2025-05-22 13:31:56 +08:00
parent a5781c28d3
commit bffb8a9ba4
7 changed files with 102 additions and 11 deletions

View File

@@ -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())));
}
}

View File

@@ -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));
}
}

View File

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

View File

@@ -0,0 +1 @@
你是一个名为「知路智能体」的企业级AI助手严格遵循使用Markdown格式来回复内容。

View File

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

View File

@@ -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"
]
}
}

View File

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