diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index d902a29..0b141bd 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -59,9 +59,10 @@ dependencies { implementation("org.flywaydb:flyway-database-postgresql:$flywayVersion") implementation("com.github.ben-manes.caffeine:caffeine:3.2.0") implementation("org.springframework.boot:spring-boot-starter-quartz") - implementation("dev.langchain4j:langchain4j-open-ai:1.0.0") - implementation("io.projectreactor:reactor-core:3.7.6") implementation("dev.langchain4j:langchain4j:1.0.0") + implementation("dev.langchain4j:langchain4j-open-ai:1.0.0") + implementation("dev.langchain4j:langchain4j-community-zhipu-ai:1.0.1-beta6") + implementation("io.projectreactor:reactor-core:3.7.6") testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion") testImplementation("org.testcontainers:postgresql:$testcontainersVersion") testImplementation("org.testcontainers:testcontainers-bom:$testcontainersVersion") diff --git a/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekChatAssistant.java b/backend/src/main/java/com/zl/mjga/config/ai/AiChatAssistant.java similarity index 81% rename from backend/src/main/java/com/zl/mjga/config/ai/DeepSeekChatAssistant.java rename to backend/src/main/java/com/zl/mjga/config/ai/AiChatAssistant.java index 7b2f9e1..30f5d5a 100644 --- a/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekChatAssistant.java +++ b/backend/src/main/java/com/zl/mjga/config/ai/AiChatAssistant.java @@ -5,6 +5,6 @@ import dev.langchain4j.service.TokenStream; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.memory.ChatMemoryAccess; -public interface DeepSeekChatAssistant extends ChatMemoryAccess { +public interface AiChatAssistant extends ChatMemoryAccess { TokenStream chat(@MemoryId String memoryId, @UserMessage String userMessage); } diff --git a/backend/src/main/java/com/zl/mjga/config/ai/ChatModelConfig.java b/backend/src/main/java/com/zl/mjga/config/ai/ChatModelConfig.java index 1dbefa4..0c43c1e 100644 --- a/backend/src/main/java/com/zl/mjga/config/ai/ChatModelConfig.java +++ b/backend/src/main/java/com/zl/mjga/config/ai/ChatModelConfig.java @@ -1,5 +1,6 @@ package com.zl.mjga.config.ai; +import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.service.AiServices; @@ -12,6 +13,18 @@ import org.springframework.context.annotation.Configuration; public class ChatModelConfig { private final DeepSeekConfiguration deepSeekConfiguration; + private final ZhiPuConfiguration zhiPuConfiguration; + private final PromptConfiguration promptConfiguration; + + @Bean + public ZhipuAiStreamingChatModel zhipuChatModel() { + return ZhipuAiStreamingChatModel.builder() + .model(zhiPuConfiguration.getModelName()) + .apiKey(zhiPuConfiguration.getApiKey()) + .logRequests(true) + .logResponses(true) + .build(); + } @Bean public OpenAiStreamingChatModel deepSeekChatModel() { @@ -23,10 +36,19 @@ public class ChatModelConfig { } @Bean - public DeepSeekChatAssistant deepSeekChatAssistant(OpenAiStreamingChatModel deepSeekChatModel) { - return AiServices.builder(DeepSeekChatAssistant.class) + public AiChatAssistant deepSeekChatAssistant(OpenAiStreamingChatModel deepSeekChatModel) { + return AiServices.builder(AiChatAssistant.class) .streamingChatModel(deepSeekChatModel) - .systemMessageProvider(chatMemoryId -> deepSeekConfiguration.getPrompt().getSystem()) + .systemMessageProvider(chatMemoryId -> promptConfiguration.getSystem()) + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) + .build(); + } + + @Bean + public AiChatAssistant zhiPuChatAssistant(ZhipuAiStreamingChatModel zhipuChatModel) { + return AiServices.builder(AiChatAssistant.class) + .streamingChatModel(zhipuChatModel) + .systemMessageProvider(chatMemoryId -> promptConfiguration.getSystem()) .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) .build(); } diff --git a/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekConfiguration.java b/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekConfiguration.java index cd2a51a..fcc3fed 100644 --- a/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekConfiguration.java +++ b/backend/src/main/java/com/zl/mjga/config/ai/DeepSeekConfiguration.java @@ -1,13 +1,7 @@ package com.zl.mjga.config.ai; -import jakarta.annotation.PostConstruct; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; @Data @@ -15,22 +9,7 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "deep-seek") public class DeepSeekConfiguration { - @jakarta.annotation.Resource private ResourceLoader resourceLoader; - private String baseUrl; private String apiKey; - private Prompt prompt; private String modelName; - - @Data - 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()))); - } } diff --git a/backend/src/main/java/com/zl/mjga/config/ai/PromptConfiguration.java b/backend/src/main/java/com/zl/mjga/config/ai/PromptConfiguration.java new file mode 100644 index 0000000..15a5e2f --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/config/ai/PromptConfiguration.java @@ -0,0 +1,24 @@ +package com.zl.mjga.config.ai; + +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import lombok.Data; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +@Data +@Component +public class PromptConfiguration { + + @jakarta.annotation.Resource private ResourceLoader resourceLoader; + private String system; + + @PostConstruct + public void init() throws IOException { + Resource resource = resourceLoader.getResource("classpath:prompt.txt"); + system = Files.readString(Paths.get(resource.getURI())); + } +} diff --git a/backend/src/main/java/com/zl/mjga/config/ai/ZhiPuConfiguration.java b/backend/src/main/java/com/zl/mjga/config/ai/ZhiPuConfiguration.java new file mode 100644 index 0000000..ea659a1 --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/config/ai/ZhiPuConfiguration.java @@ -0,0 +1,15 @@ +package com.zl.mjga.config.ai; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "zhipu") +public class ZhiPuConfiguration { + + private String baseUrl; + private String apiKey; + private String modelName; +} diff --git a/backend/src/main/java/com/zl/mjga/controller/AiController.java b/backend/src/main/java/com/zl/mjga/controller/AiController.java index d641e04..3c038d1 100644 --- a/backend/src/main/java/com/zl/mjga/controller/AiController.java +++ b/backend/src/main/java/com/zl/mjga/controller/AiController.java @@ -1,6 +1,6 @@ package com.zl.mjga.controller; -import com.zl.mjga.service.DeepSeekAiService; +import com.zl.mjga.service.AiChatService; import dev.langchain4j.service.TokenStream; import java.security.Principal; import java.time.Duration; @@ -17,12 +17,12 @@ import reactor.core.publisher.Sinks; @Slf4j public class AiController { - private final DeepSeekAiService deepSeekAiService; + private final AiChatService aiChatService; @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux chat(Principal principal, @RequestBody String userMessage) { Sinks.Many sink = Sinks.many().unicast().onBackpressureBuffer(); - TokenStream chat = deepSeekAiService.chat(principal.getName(), userMessage); + TokenStream chat = aiChatService.chatWithZhiPu(principal.getName(), userMessage); chat.onPartialResponse(text -> sink.tryEmitNext(text.replace(" ", "␣").replace("\t", "⇥"))) .onCompleteResponse( r -> { diff --git a/backend/src/main/java/com/zl/mjga/service/AiChatService.java b/backend/src/main/java/com/zl/mjga/service/AiChatService.java new file mode 100644 index 0000000..08ae6a9 --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/service/AiChatService.java @@ -0,0 +1,24 @@ +package com.zl.mjga.service; + +import com.zl.mjga.config.ai.AiChatAssistant; +import dev.langchain4j.service.TokenStream; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AiChatService { + + private final AiChatAssistant deepSeekChatAssistant; + private final AiChatAssistant zhiPuChatAssistant; + + public TokenStream chatWithDeepSeek(String sessionIdentifier, String userMessage) { + return deepSeekChatAssistant.chat(sessionIdentifier, userMessage); + } + + public TokenStream chatWithZhiPu(String sessionIdentifier, String userMessage) { + return zhiPuChatAssistant.chat(sessionIdentifier, userMessage); + } +} diff --git a/backend/src/main/java/com/zl/mjga/service/DeepSeekAiService.java b/backend/src/main/java/com/zl/mjga/service/DeepSeekAiService.java deleted file mode 100644 index 5bc818c..0000000 --- a/backend/src/main/java/com/zl/mjga/service/DeepSeekAiService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.zl.mjga.service; - -import com.zl.mjga.config.ai.DeepSeekChatAssistant; -import dev.langchain4j.service.TokenStream; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Slf4j -public class DeepSeekAiService { - - private final DeepSeekChatAssistant deepSeekChatAssistant; - - public TokenStream chat(String sessionIdentifier, String userMessage) { - return deepSeekChatAssistant.chat(sessionIdentifier, userMessage); - } -} diff --git a/backend/src/main/resources/ai.yml b/backend/src/main/resources/ai.yml index af6a6cf..fef3c8e 100644 --- a/backend/src/main/resources/ai.yml +++ b/backend/src/main/resources/ai.yml @@ -1,4 +1,8 @@ deep-seek: base-url: "https://api.deepseek.com" api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d" - model-name: "deepseek-chat" \ No newline at end of file + model-name: "deepseek-chat" +zhipu: + base-url: "https://open.bigmodel.cn/" + api-key: "" + model-name: "glm-4-flash" \ No newline at end of file diff --git a/frontend/src/views/AiChatView.vue b/frontend/src/views/AiChatView.vue index d1bd58c..384816b 100644 --- a/frontend/src/views/AiChatView.vue +++ b/frontend/src/views/AiChatView.vue @@ -80,16 +80,15 @@ marked.setOptions({ const renderMarkdown = (content: string) => { if (!content) return ''; - // 替换所有空白占位符(包括前后端约定的特殊字符) const restoredContent = content - .replace(/␣/g, ' ') // 普通空格 - .replace(/⇥/g, '\t') // 制表符 - .replace(/␤/g, '\n'); // 如果后端也处理了换行符 + .replace(/␣/g, ' ') + .replace(/⇥/g, '\t') + .replace(/␤/g, '\n'); + - // 处理Markdown中的代码块缩进 const processedContent = restoredContent - .replace(/^(\s*)(`{3,})/gm, '$1$2') // 保留代码块前的空格 - .replace(/(\s+)`/g, '$1`'); // 保留代码内联前的空格 + .replace(/^(\s*)(`{3,})/gm, '$1$2') + .replace(/(\s+)`/g, '$1`'); const rawHtml = marked(processedContent); return DOMPurify.sanitize(rawHtml as string); @@ -104,10 +103,10 @@ const chatElements = computed(() => { }); }); -watch(messages, (newVal) => { - console.log('原始消息:', newVal[newVal.length - 1]); - console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1])); -}, { deep: true }); +// watch(messages, (newVal) => { +// console.log('原始消息:', newVal[newVal.length - 1]); +// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1])); +// }, { deep: true }); watch( chatElements,