mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-04-05 11:47:32 +00:00
add md render
This commit is contained in:
@@ -1,14 +1,25 @@
|
|||||||
package com.zl.mjga.config.ai;
|
package com.zl.mjga.config.ai;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "deep-seek")
|
@ConfigurationProperties(prefix = "deep-seek")
|
||||||
public class DeepSeekConfiguration {
|
public class DeepSeekConfiguration {
|
||||||
|
|
||||||
|
@jakarta.annotation.Resource
|
||||||
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
private Prompt prompt;
|
private Prompt prompt;
|
||||||
@@ -18,4 +29,12 @@ public class DeepSeekConfiguration {
|
|||||||
public static class Prompt {
|
public static class Prompt {
|
||||||
private String system;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Sinks;
|
import reactor.core.publisher.Sinks;
|
||||||
@@ -34,8 +33,6 @@ public class AiController {
|
|||||||
.onError(sink::tryEmitError)
|
.onError(sink::tryEmitError)
|
||||||
.start();
|
.start();
|
||||||
return sink.asFlux()
|
return sink.asFlux()
|
||||||
.timeout(Duration.ofSeconds(120))
|
.timeout(Duration.ofSeconds(120));
|
||||||
.doOnCancel(SecurityContextHolder::clearContext)
|
|
||||||
.doOnTerminate(SecurityContextHolder::clearContext);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
deep-seek:
|
deep-seek:
|
||||||
base-url: "https://api.deepseek.com"
|
base-url: "https://api.deepseek.com"
|
||||||
api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d"
|
api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d"
|
||||||
model-name: "deepseek-chat"
|
model-name: "deepseek-chat"
|
||||||
prompt:
|
|
||||||
system: "你是一个名叫「知路智能体」的企业级AI助手,能帮助用户解决各种问题。"
|
|
||||||
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",
|
"@vueuse/core": "^13.0.0",
|
||||||
"apexcharts": "^3.46.0",
|
"apexcharts": "^3.46.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"flowbite": "^3.1.2",
|
"flowbite": "^3.1.2",
|
||||||
|
"marked": "^15.0.12",
|
||||||
"openapi-fetch": "^0.13.5",
|
"openapi-fetch": "^0.13.5",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"tailwindcss": "^4.0.14",
|
"tailwindcss": "^4.0.14",
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
"@faker-js/faker": "^9.6.0",
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@playwright/test": "^1.51.0",
|
"@playwright/test": "^1.51.0",
|
||||||
"@tsconfig/node22": "^22.0.0",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/marked": "^5.0.2",
|
||||||
"@types/node": "^22.13.9",
|
"@types/node": "^22.13.9",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitest/browser": "^3.0.9",
|
"@vitest/browser": "^3.0.9",
|
||||||
@@ -2127,12 +2131,29 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.15.17",
|
"version": "22.15.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz",
|
||||||
@@ -2163,6 +2184,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.0.21",
|
"version": "0.0.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
@@ -3327,6 +3355,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -4424,6 +4461,18 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@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": {
|
"node_modules/memorystream": {
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"@vueuse/core": "^13.0.0",
|
"@vueuse/core": "^13.0.0",
|
||||||
"apexcharts": "^3.46.0",
|
"apexcharts": "^3.46.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"flowbite": "^3.1.2",
|
"flowbite": "^3.1.2",
|
||||||
|
"marked": "^15.0.12",
|
||||||
"openapi-fetch": "^0.13.5",
|
"openapi-fetch": "^0.13.5",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"tailwindcss": "^4.0.14",
|
"tailwindcss": "^4.0.14",
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
"@faker-js/faker": "^9.6.0",
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@playwright/test": "^1.51.0",
|
"@playwright/test": "^1.51.0",
|
||||||
"@tsconfig/node22": "^22.0.0",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/marked": "^5.0.2",
|
||||||
"@types/node": "^22.13.9",
|
"@types/node": "^22.13.9",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitest/browser": "^3.0.9",
|
"@vitest/browser": "^3.0.9",
|
||||||
@@ -52,6 +56,8 @@
|
|||||||
"vue-tsc": "^2.2.8"
|
"vue-tsc": "^2.2.8"
|
||||||
},
|
},
|
||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": ["public"]
|
"workerDirectory": [
|
||||||
|
"public"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
<LoadingIcon :textColor="'text-gray-900'"
|
<LoadingIcon :textColor="'text-gray-900'"
|
||||||
v-if="isLoading && !chatElement.isUser && chatElement.content === ''" />
|
v-if="isLoading && !chatElement.isUser && chatElement.content === ''" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-base font-normal py-2.5 text-gray-900 dark:text-white">
|
<div class="markdown-content text-base font-normal py-2.5 text-gray-900 dark:text-white"
|
||||||
{{ chatElement.content }}
|
v-html="renderMarkdown(chatElement.content)">
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,6 +63,8 @@ import useUserStore from "../composables/store/useUserStore";
|
|||||||
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import useAlertStore from "@/composables/store/useAlertStore";
|
import useAlertStore from "@/composables/store/useAlertStore";
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
const { messages, chat, isLoading, cancel } = useAiChat();
|
const { messages, chat, isLoading, cancel } = useAiChat();
|
||||||
const { user } = useUserStore();
|
const { user } = useUserStore();
|
||||||
@@ -70,6 +72,18 @@ const inputMessage = ref("");
|
|||||||
const chatContainer = ref<HTMLElement | null>(null);
|
const chatContainer = ref<HTMLElement | null>(null);
|
||||||
const alertStore = useAlertStore();
|
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(() => {
|
const chatElements = computed(() => {
|
||||||
return messages.value.map((message, index) => {
|
return messages.value.map((message, index) => {
|
||||||
return {
|
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(
|
watch(
|
||||||
chatElements,
|
chatElements,
|
||||||
async () => {
|
async () => {
|
||||||
@@ -132,3 +151,5 @@ onUnmounted(() => {
|
|||||||
cancel();
|
cancel();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user