Sping AI接入deepseek
Sping AI接入deepseek
Sping AI 概述
在经历了八个里程碑式的版本之后(M1~M8),Spring AI 1.0 正式版本,终于在 2025 年 5 月 20 日正式发布了,这是另一个新高度的里程碑式的版本,标志着 Spring 生态系统正式全面拥抱人工智能技术,并且意味着 Spring AI 将会给企业带来稳定 API 支持。
Spring AI 是 Spring 官方推出的一个人工智能集成框架,旨在简化 AI 功能在 Spring 应用中的整合。它提供了一套标准化的 API 和工具,让开发者能够轻松接入多种主流 AI 服务(如 OpenAI、Azure AI、Hugging Face 等),同时保持 Spring 生态的简洁性和灵活性。
1、spring AI 是一个框架,这个框架是spring家族中的一员,是一个抽象层,用来继承大语言模型
2、只需要写一份代码即可,修改配置文件,切换大语言模型
核心特性
- 统一 API 设计
- 通过抽象层屏蔽不同 AI 提供商(如 OpenAI、Gemini、Ollama 等)的接口差异,开发者只需使用 Spring AI 的通用接口(如 ChatModel),即可切换底层 AI 服务。
- 开箱即用的功能支持
- 支持对话模型(Chat)类的大模型:与 ChatGPT 类似的交互。
- 支持嵌入模型(Embedding)类的大模型:嵌入模型是将文本、图像或其他数据转换为数值向量(即嵌入向量)的技术。
- 支持图像生成(Image Generation)类的大模型:图像生成是指AI根据文本描述或其他输入创建新图像的能力。
- 支持函数调用(Function Calling):函数调用功能使AI模型能够与外部API和服务交互。也就是说,你写一个函数,AI 也能调用你写的函数。
- 与 Spring 生态无缝集成
- 支持 Spring Boot 自动配置、依赖注入、Actuator 监控等,与 Spring Security、Spring Data 等组件协同工作。
- Prompt(提示) 工程支持
- 提供
PromptTemplate
等工具,方便动态生成提示词(Prompts),支持上下文管理。(根据会话记录,来回答问题 )
- 提供
- 模块化设计
- 开发者可以根据项目需求选择特定的AI服务模块
- 例如只需OpenAI功能就只引入 spring-ai-openai
- 如需Google Vertex AI则引入spring-ai-vertexai
- 每个AI供应商/服务有独立的Spring Starter模块
适用场景
- 快速构建 AI 驱动的应用(如智能客服、内容生成工具)。
- 需要灵活切换 AI 后端(如从本地模型切换到云服务)。
- 结合 Spring 生态实现企业级 AI 功能(如权限控制、数据持久化)。
官网与资源
- 官方仓库:https://github.com/spring-projects/spring-ai
- 文档:https://spring.io/projects/spring-ai
AI 提供商与模型类型
在SpringAI中,模型类型和AI提供商是两个不同维度的概念,但它们又相互关联。让我用更清晰的方式帮你区分和理解:
模型类型(Model Type)
指的是AI模型的功能类别,即它能完成什么任务。
特点:与具体厂商无关,是通用的能力分类。
常见模型类型:
模型类型 | 功能说明 | 典型应用场景 |
---|---|---|
Chat(对话型) | 对话交互(如ChatGPT) | 客服机器人、聊天助手 |
Embedding(嵌入型) | 将文本,视频,图片,声音转换为向量(数值数组) | 语义搜索、RAG + 传统搜索基于关键词匹配(如Google早期的搜索),而语义搜索通过理解查询的语义(含义)来返回更相关的结果。 + 它利用深度学习模型(如BERT、Embedding模型)将文本转换为向量(vector),通过向量相似度匹配内容,即使查询词和文档没有直接的关键词重叠。 |
Image(文生图型) | 生成/处理图像(如Stable Diffusion) | 设计辅助、内容生成 |
Text-to-Speech(文转语音型) | 将文本转为语音 | 语音助手、有声内容 |
Function Calling(函数回调型) | 让AI调用外部函数/API | 实时数据查询、工作流自动化 |
AI提供商(Provider)
提供具体AI模型服务的公司或平台。
特点: 同一提供商可能支持多种模型类型
常见提供商:
提供商 | 支持的模型类型 | 具体模型示例 |
---|---|---|
OpenAI | Chat, Embedding, Image | GPT-4o、text-embedding-3、DALL-E |
Google Vertex AI | Chat, Embedding, Image | PaLM 2、Imagen |
Azure OpenAI | Chat, Embedding | 微软托管的OpenAI服务 |
Hugging Face | Chat, Embedding, Image | 开源模型(如BLOOM、Stable Diffusion) |
Stability AI | Image | Stable Diffusion系列 |
DeepSeek | Chat,代码专用模型、Embedding、数学专用模型 | DeepSeek-V3、DeepSeek-Coder、DeepSeek-Embedding、DeepSeek-Math |
两者的关系
- 一个提供商支持多种模型类型
例如:OpenAI同时提供Chat模型(GPT-4)、Embedding模型(text-embedding-3)、Image模型(DALL-E)。 - 一种模型类型可由多个提供商实现
例如:Chat模型既可以用OpenAI的GPT-4,也可以用Google的PaLM 2。
Spring AI 框架支持哪些 AI 提供商的哪些模型
Spring AI 框架支持主流的 AI 提供商的主流模型,并且会随着 Spring AI 版本的升级而变化。
以下是截至 2024年6月,Spring AI 框架官方及社区支持的 AI 提供商及其对应的 模型类型的详细列表,包含国内和国外主流厂商。
AI 提供商 | 支持的模型类型 | 具体模型示例 | 是否国内厂商 | Spring AI 模块名 |
---|---|---|---|---|
OpenAI | Chat, Embedding, Image Generation, Function Calling | GPT-4, GPT-3.5, text-embedding-3, DALL-E 3 | ❌ | spring-ai-openai |
Azure OpenAI | Chat, Embedding, Image Generation | GPT-4, GPT-3.5, text-embedding-ada-002 | ❌ | spring-ai-azure-openai |
Google Vertex AI | Chat, Embedding, Code Generation | Gemini 1.5, PaLM 2, textembedding-gecko | ❌ | spring-ai-vertexai |
Hugging Face | Chat, Embedding, Text Generation, Image Generation | BLOOM, Llama 2, Stable Diffusion, BERT | ❌ | spring-ai-huggingface |
Stability AI | Image Generation | Stable Diffusion XL, Stable Diffusion 3 | ❌ | spring-ai-stabilityai |
Anthropic | Chat | Claude 3, Claude 2 | ❌ | spring-ai-anthropic (社区支持) |
Ollama | Chat, Embedding (本地运行开源模型) | Llama 3, Mistral, Gemma | ❌ | spring-ai-ollama |
DeepSeek | ❌ (尚未官方支持,但未来可能集成) | DeepSeek-V3, DeepSeek-Coder | ✅ | 暂无,虽然没有给 deepseek 提供专门的 starter,但是由于 deepseek API 接口规范与 OpenAI 保持一致,因此也可以使用 openai 的 starter。 |
百度文心大模型 | ❌ (尚未官方支持) | ERNIE-Bot 4.0, ERNIE-Embedding | ✅ | 暂无 |
阿里云通义千问 | ❌ (尚未官方支持) | Qwen-72B, Qwen-Embedding | ✅ | 暂无 |
智谱AI (GLM) | ❌ (尚未官方支持) | ChatGLM3, GLM-Embedding | ✅ | 暂无 |
讯飞星火 | ❌ (尚未官方支持) | SparkDesk 3.0 | ✅ | 暂无 |
MiniMax | ❌ (尚未官方支持) | ABAB 5.5 | ✅ | 暂无 |
spring-ChatModel
什么是chatModel
chat Model 即"聊天模型”,它是 spring AI 中处理对话的核心组件,负责将用户的输入(如文本、图像、语音等多模态数据)转换为AI模型的指令,并返回结构化的响应。
工作原理:
【源码】:
public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {// 简化的AI模型交互入口,无需掌握Prompt和chatResponse的细节即可立马上手
//1.message参数被封装为Prompt
//2.返回的是chatResponse的"文本"部分default String call(String message) {Prompt prompt = new Prompt(new UserMessage(message));Generation generation = call(prompt).getResult();return (generation != null) ? generation.getOutput().getText() : "";}default String call(Message... messages) {Prompt prompt = new Prompt(Arrays.asList(messages));Generation generation = call(prompt).getResult();return (generation != null) ? generation.getOutput().getText() : "";}//标准的AI交互入口@OverrideChatResponse call(Prompt prompt);default ChatOptions getDefaultOptions() {return ChatOptions.builder().build();}default Flux<ChatResponse> stream(Prompt prompt) {throw new UnsupportedOperationException("streaming is not supported");}}
streamingchatmodel 接口提供了"流式响应"的功能,使模型的输出内容展现出更优效果
什么是流式响应的效果?
答:输出的效果呈现打字机的形式,一个一个字出来。
import java.util.Arrays;import reactor.core.publisher.Flux;import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.StreamingModel;@FunctionalInterface
public interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {default Flux<String> stream(String message) {Prompt prompt = new Prompt(message);return stream(prompt).map(response -> (response.getResult() == null || response.getResult().getOutput() == null|| response.getResult().getOutput().getText() == null) ? "": response.getResult().getOutput().getText());}default Flux<String> stream(Message... messages) {Prompt prompt = new Prompt(Arrays.asList(messages));return stream(prompt).map(response -> (response.getResult() == null || response.getResult().getOutput() == null|| response.getResult().getOutput().getText() == null) ? "": response.getResult().getOutput().getText());}//标准的AI交互入口@OverrideFlux<ChatResponse> stream(Prompt prompt);}
ChatClient接囗
chatclient 接口提供了高层封装(如:Fluent API 流式API,提供链式调用)、简化了开发,但是底层仍调用 chatmodel。
public interface ChatClient {//1.手工模式创建指定模型的chatclient实例static ChatClient create(ChatModel chatModel) {return create(chatModel, ObservationRegistry.NOOP);}//2.建造者模式创建系统默认的chatclient实例interface Builder {ChatClient build();}}
注入实例
下面有整体的实例,这个只是拓展另一种方式,将下面的接入deepseek跑通只有再来看。
package com.gj.config;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author GJ* @date 2025/6/24 21:36*/
@Configuration
public class SpringAIConfig {/*** 有两种法式创建大模型客户端,任选其一* @param builder* @return*/
// 1、创建基于默认的大模型客户端@Beanpublic ChatClient openAiChatClient(ChatClient.Builder builder) {return builder.build();}// 2、创建基于模型的客户端,参数传不同的大模型,可以创建不同的客户端@Beanpublic ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {return ChatClient.create(openAiChatModel);}
}
在controller层进行调用:
package com.gj.controller;import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author GJ* @date 2025/6/24 21:41*/
@RestController
@RequestMapping("/api")
public class ChatModelController {@Resourceprivate ChatClient chatClient;@GetMapping("/chat")public String chat(@RequestParam(value = "msg",defaultValue = "deepseek") String msg) {return chatClient.prompt() //设置请求上下文(如角色和内容).user(msg)//设置用户输入消息.call()//发送请求并获取模型生成的响应.content(); //获取响应内容}
}
springAI-Prompt
什么是Prompt
Prompt
即“提示词",也可叫做"引导词"。它是引导 AI生成特定输出的输入指令,直接影响模型响应质量。
注意:本文从开发者角度讨讹Prompt而不是使用者角度
使用者角度给到的提示词越来越细致。
我们将提示词转换成对应的编码,这个就是我们要解决的问题?怎么来解决这个问题呢!
我们这时需要用到prompt
得到prompt 通过
1、SystemMessage 系统角色、系统消息
2、UserMessage 用的输入的信息
3、ChatOptions 聊天模型的参数信息
4、 将前三步的信息进行组合最终得到prompt
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author GJ* @date 2025/6/24 21:41*/
@RestController
@RequestMapping("/api")
public class ChatModelController {@Resourceprivate ChatClient chatClient;@GetMapping("/chat")public String chat(@RequestParam(value = "msg",defaultValue = "deepseek") String msg) {
// 1、系统角色SystemMessage systemMessage = new SystemMessage("你是项目经理");
// 2、用户消息UserMessage userMessage = new UserMessage(msg);
// 3、模型参数ChatOptions chatOptions = ChatOptions.builder().temperature(0.5) //多样化系数.maxTokens(500) //限制token用量,防止模型输出过长,这也导致可能输出内容的缺失,没有特殊要求一般注释.build();//4、进行组合Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions);return chatClient.prompt(prompt) //设置请求上下文(如角色和内容).call()//发送请求并获取模型生成的响应.content(); //获取响应内容}
}
简化写法
@GetMapping("/simpleChat")public String simpleChat(@RequestParam(value = "msg",defaultValue = "deepseek") String msg) {
// 3、模型参数ChatOptions chatOptions = ChatOptions.builder().temperature(0.5) //多样化系数// .maxTokens(500) //限制token用量,防止模型输出过长,这也导致可能输出内容的缺失.build();return chatClient.prompt() //设置请求上下文(如角色和内容).system("你是项目经理")//设置系统消息.user(msg)//设置用户输入消息.options(chatOptions)//设置模型参数.call()//发送请求并获取模型生成的响应.content(); //获取响应内容}
}
全局配置
“模型参数"等每次对话中相对固定的部分作为全局配置,如果需要使用可将"系统角色"、全局配置时可不再额外指定。
如果系统角色也想设置为全局:
// 1、创建基于默认的大模型客户端@Beanpublic ChatClient openAiChatClient(ChatClient.Builder builder) {
// return builder.build();return builder.defaultSystem("你是项目经理").build();}
角色定义
Prompt通过结构化消息角色(SYSTEM/USER/ASSISTANT)明确交互意图
1.SYSTEM:定义系统角色(给AI立个人设,如"你是项目经理")
2. USER: 定义用户请求(接收用户输入)
3.ASSISTANT:定义助手角色(存储和传递聊天内容")
@GetMapping("/assistan/chat")public String assistanChat(@RequestParam(value = "msg",defaultValue = "deepseek") String msg) {
// 1、系统角色SystemMessage systemMessage = new SystemMessage("你是项目经理");
// 2、用户消息UserMessage userMessage = new UserMessage(msg);
// 助手角色AssistantMessage assistantMessage = new AssistantMessage("用户曾经将时间标注上去");
// 3、模型参数ChatOptions chatOptions = ChatOptions.builder().temperature(0.5) //多样化系数// .maxTokens(500) //限制token用量,防止模型输出过长,这也导致可能输出内容的缺失.build();//4、进行组合,注意顺序一定要一致,否则会失效Prompt prompt = new Prompt(List.of(systemMessage,assistantMessage, userMessage), chatOptions);return chatClient.prompt(prompt) //设置请求上下文(如角色和内容).call()//发送请求并获取模型生成的响应.content(); //获取响应内容}
}
Spring AI 接入 DeepSeek
DeepSeek 的 API 设计兼容 OpenAI:DeepSeek 的 API 接口规范(如请求/响应格式、鉴权方式)与 OpenAI 保持一致,因此可以直接使用 OpenAI 的客户端库调用 DeepSeek。
spring ai会根据配置文件application. yml 中配置的大模型自动创建ChatModel对象
jdk>=17
springboot:3.3.9(注意:springboot版本要求为3.2.x和3.3.x)
引入依赖
<!-- Spring MVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--DeepSeek 的 API 设计兼容 OpenAI:DeepSeek 的 API 接口规范(如请求/响应格式、鉴权方式)与 OpenAI 保持一致,因此可以直接使用 OpenAI 的客户端库调用 DeepSeek。--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
配置文件
server:port: 8888
# Spring AI OpenAI/DeepSeek 配置
spring:ai:openai:api-key: sk-b7d2d672216b46209dd6e40exxxx # 设置 DeepSeek API 的访问密钥,永远不要将密钥提交到代码仓库,建议通过环境变量注入。-收费base-url: https://api.deepseek.com # 指定 DeepSeek API 的基础地址,格式与OpenAI相同。chat:options:model: deepseek-chat # 选择要调用的 DeepSeek 模型名称,必须与 DeepSeek 支持的模型列表匹配(如 deepseek-chat、deepseek-coder 等),不同模型可能有不同的计费标准和能力。temperature: 1.3 # temperature 值越高,AI 回答越随机和创意;值越低,回答越确定和保守。1.3 属于高值,适合需要发散性输出的场景,但可能牺牲准确性。
service层
package com.gj.service;import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.stereotype.Service;import java.util.Map;//这里会自动生成一个带有ChatModel参数的构造方法,显然这里使用的是棉造注入。
@RequiredArgsConstructor
@Service
public class AiService {//springai会自动根据application. yml配置文件自动创建ChatModel对象。并目纳入IoC方法来和大语言模型通信。//ChatModel是Spring AI的核接口,通过这个接口中的call方法来和大语言模型通信private final ChatModel chatModel;// 简单的直接调用一次性返回结果public String generate(String message) {return chatModel.call(message);}// 简单的直接调用,一个一个字将结果返回// public Flux<String>generate(String message) {// return chatModel.stream(message);//}// 使用系统提示模板public String generateWithSystemPrompt(String userMessage) {SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("""你是一个资深{domain}专家,回答需满足以下要求:1. 语言风格:{tone}2. 回答长度:{length}3. 用户问题:{userMessage}""");Prompt prompt = new Prompt(systemPromptTemplate.createMessage(Map.of("domain", "科技","tone", "幽默","length", "不超过100字","userMessage", userMessage)));return chatModel.call(prompt).getResult().getOutput().getText();}
}
controller 层
package com.gj.controller;import com.gj.service.AiService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/ai")
@RequiredArgsConstructor
public class AiController {private final AiService aiService;//普通聊天@GetMapping("/chat")public String chat(@RequestParam String message) {System.out.println("收到消息:" + message);return aiService.generate(message);}//带有提示的聊天@GetMapping("/chat-with-prompt")public String chatWithPrompt(@RequestParam String message) {return aiService.generateWithSystemPrompt(message);}
}
前端页面使用deepseek生成
后端地址是http://localhost:8888/api/ai/chat?message=xxx
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>DeepSeek AI - 智能助手</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script><script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.5/dist/purify.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;}body {background: linear-gradient(135deg, #0c0f1d 0%, #1a1e2e 50%, #1c1124 100%);color: #e0e0ff;min-height: 100vh;display: flex;justify-content: center;align-items: center;padding: 20px;overflow-x: hidden;}.container {width: 100%;max-width: 900px;background: rgba(15, 18, 32, 0.85);border-radius: 20px;overflow: hidden;box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);border: 1px solid rgba(92, 107, 192, 0.3);backdrop-filter: blur(10px);position: relative;}/* 顶部霓虹灯效果 */.header {background: linear-gradient(90deg, #0a0e1a, #1c1f3a);padding: 25px 30px;text-align: center;border-bottom: 1px solid rgba(92, 107, 192, 0.3);position: relative;overflow: hidden;}.header::before {content: "";position: absolute;top: 0;left: -100%;width: 200%;height: 100%;background: linear-gradient(90deg,transparent,rgba(92, 107, 192, 0.2),transparent);animation: shine 3s infinite;}@keyframes shine {0% { left: -100%; }100% { left: 100%; }}.logo {display: flex;align-items: center;justify-content: center;gap: 15px;}.logo-icon {font-size: 2.5rem;color: #6c7bff;text-shadow: 0 0 15px rgba(108, 123, 255, 0.7);}.logo-text {font-size: 2.2rem;font-weight: 700;background: linear-gradient(90deg, #6c7bff, #a66cff);-webkit-background-clip: text;-webkit-text-fill-color: transparent;text-shadow: 0 0 20px rgba(108, 123, 255, 0.4);}.tagline {margin-top: 10px;font-size: 1.1rem;color: #a0a8ff;letter-spacing: 1px;}/* 聊天区域 */.chat-container {height: 500px;padding: 20px;overflow-y: auto;display: flex;flex-direction: column;gap: 25px;}/* 自定义滚动条 */.chat-container::-webkit-scrollbar {width: 8px;}.chat-container::-webkit-scrollbar-track {background: rgba(20, 23, 42, 0.5);border-radius: 4px;}.chat-container::-webkit-scrollbar-thumb {background: linear-gradient(#6c7bff, #a66cff);border-radius: 4px;}.message {max-width: 80%;padding: 18px 22px;border-radius: 18px;line-height: 1.6;position: relative;animation: fadeIn 0.4s ease-out;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);}@keyframes fadeIn {from { opacity: 0; transform: translateY(10px); }to { opacity: 1; transform: translateY(0); }}.user-message {background: linear-gradient(135deg, #3a3f8f, #4a2f7a);align-self: flex-end;border-bottom-right-radius: 5px;}.ai-message {background: linear-gradient(135deg, #25294a, #1e223d);align-self: flex-start;border-bottom-left-radius: 5px;border: 1px solid rgba(92, 107, 192, 0.3);}.message-header {display: flex;align-items: center;margin-bottom: 10px;font-weight: 600;}.user-message .message-header {color: #b9c0ff;}.ai-message .message-header {color: #6c7bff;}.message-header i {margin-right: 10px;font-size: 1.2rem;}.message-content {font-size: 1.05rem;}.message-content p {margin: 8px 0;}/* Markdown样式增强 */.message-content pre {background: rgba(15, 20, 40, 0.8);border-radius: 8px;padding: 15px;overflow-x: auto;margin: 15px 0;border: 1px solid rgba(92, 107, 192, 0.2);}.message-content code {font-family: 'Fira Code', monospace;background: rgba(15, 20, 40, 0.5);padding: 2px 6px;border-radius: 4px;font-size: 0.95em;}.message-content blockquote {border-left: 4px solid #6c7bff;padding: 5px 15px;margin: 10px 0;background: rgba(92, 107, 192, 0.1);border-radius: 0 8px 8px 0;}.message-content table {width: 100%;border-collapse: collapse;margin: 15px 0;background: rgba(20, 25, 45, 0.5);}.message-content th, .message-content td {padding: 10px;border: 1px solid rgba(92, 107, 192, 0.2);text-align: left;}.message-content th {background: rgba(92, 107, 192, 0.2);}/* 输入区域 */.input-container {padding: 20px;background: rgba(18, 21, 36, 0.8);border-top: 1px solid rgba(92, 107, 192, 0.3);display: flex;gap: 15px;}.input-box {flex: 1;padding: 18px 25px;border-radius: 50px;border: none;background: rgba(25, 29, 50, 0.8);color: #e0e0ff;font-size: 1.1rem;border: 2px solid rgba(92, 107, 192, 0.2);outline: none;transition: all 0.3s;}.input-box:focus {border-color: rgba(108, 123, 255, 0.5);box-shadow: 0 0 15px rgba(108, 123, 255, 0.3);}.input-box::placeholder {color: #6a75b0;}.send-btn {width: 60px;height: 60px;border-radius: 50%;background: linear-gradient(135deg, #6c7bff, #a66cff);border: none;color: white;font-size: 1.4rem;cursor: pointer;transition: all 0.3s;display: flex;justify-content: center;align-items: center;box-shadow: 0 5px 15px rgba(108, 123, 255, 0.4);}.send-btn:hover {transform: translateY(-3px);box-shadow: 0 8px 20px rgba(108, 123, 255, 0.6);}.send-btn:active {transform: translateY(0);}.send-btn.loading {background: linear-gradient(135deg, #4a5080, #6a4f8f);cursor: not-allowed;}.send-btn.loading i {animation: spin 1s linear infinite;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}/* 加载动画 */.typing-indicator {display: flex;align-items: center;padding: 15px 25px;background: rgba(25, 29, 50, 0.8);border-radius: 30px;width: fit-content;margin-top: 10px;align-self: flex-start;animation: fadeIn 0.3s ease-out;}.typing-text {color: #a0a8ff;font-size: 1rem;margin-right: 10px;}.dots {display: flex;gap: 5px;}.dot {width: 10px;height: 10px;background: #6c7bff;border-radius: 50%;animation: pulse 1.5s infinite;}.dot:nth-child(2) {animation-delay: 0.2s;}.dot:nth-child(3) {animation-delay: 0.4s;}@keyframes pulse {0%, 100% { transform: scale(1); opacity: 0.7; }50% { transform: scale(1.2); opacity: 1; }}/* 错误提示 */.error-message {background: rgba(180, 50, 70, 0.2);border: 1px solid rgba(220, 80, 100, 0.5);color: #ffa0a8;padding: 10px 20px;border-radius: 10px;margin-top: 10px;align-self: flex-start;}/* 底部信息 */.footer {text-align: center;padding: 20px;font-size: 0.9rem;color: #6a75b0;border-top: 1px solid rgba(92, 107, 192, 0.2);}.status-indicator {display: inline-block;width: 10px;height: 10px;border-radius: 50%;margin-right: 8px;}.status-connected {background: #4caf50;box-shadow: 0 0 8px #4caf50;}.status-disconnected {background: #f44336;box-shadow: 0 0 8px #f44336;}/* 响应式设计 */@media (max-width: 768px) {.chat-container {height: 400px;padding: 15px;}.message {max-width: 90%;padding: 15px;}.header {padding: 20px;}.logo-text {font-size: 1.8rem;}.input-box {padding: 15px 20px;}.send-btn {width: 50px;height: 50px;}}@media (max-width: 480px) {.chat-container {height: 350px;}.logo {flex-direction: column;gap: 8px;}.logo-text {font-size: 1.5rem;}.tagline {font-size: 0.9rem;}.input-container {padding: 15px;}}</style>
</head>
<body>
<div class="container"><div class="header"><div class="logo"><div class="logo-icon"><i class="fas fa-brain"></i></div><div class="logo-text">DeepSeek AI</div></div><div class="tagline">下一代人工智能助手 | 提供深度洞察与智能解决方案</div></div><div class="chat-container" id="chatContainer"><div class="message ai-message"><div class="message-header"><i class="fas fa-robot"></i>DeepSeek 助手</div><div class="message-content"><p>👋 您好!我是DeepSeek AI助手,有什么可以帮您的吗?</p><p>我可以帮您解答问题、撰写内容、分析数据、编程辅助以及提供各种创意方案。</p><p>请直接在下方输入您的问题,我会尽力为您提供帮助!</p></div></div><div class="typing-indicator" id="typingIndicator" style="display: none;"><div class="typing-text">DeepSeek正在思考</div><div class="dots"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div></div><div class="input-container"><input type="text" class="input-box" id="userInput" placeholder="向DeepSeek提问..."><button class="send-btn" id="sendBtn"><i class="fas fa-paper-plane"></i></button></div><div class="footer"><span class="status-indicator status-connected" id="statusIndicator"></span><span id="statusText">已连接到后端API服务</span> |DeepSeek AI 智能助手 | 支持Markdown渲染 | 修复JSON解析问题</div>
</div><script>document.addEventListener('DOMContentLoaded', function() {const chatContainer = document.getElementById('chatContainer');const userInput = document.getElementById('userInput');const sendBtn = document.getElementById('sendBtn');const typingIndicator = document.getElementById('typingIndicator');const statusIndicator = document.getElementById('statusIndicator');const statusText = document.getElementById('statusText');// 配置const API_URL = 'http://localhost:8888/api/ai/chat';// 初始滚动到底部chatContainer.scrollTop = chatContainer.scrollHeight;// 发送消息到后端API(修复JSON解析问题)async function sendMessageToAPI(message) {try {// 显示加载状态sendBtn.classList.add('loading');const response = await fetch(`${API_URL}?message=${encodeURIComponent(message)}`);if (!response.ok) {throw new Error(`API请求失败: ${response.status}`);}// 修复:后端返回的是纯文本,而不是JSONconst responseText = await response.text();// 更新状态指示器statusIndicator.className = 'status-indicator status-connected';statusText.textContent = '后端API响应成功';return responseText;} catch (error) {console.error('API请求错误:', error);// 更新状态指示器statusIndicator.className = 'status-indicator status-disconnected';statusText.textContent = '后端API连接失败';// 添加错误消息addErrorMessage(`服务暂时不可用: ${error.message}`);return null;} finally {sendBtn.classList.remove('loading');}}// 添加用户消息function addUserMessage(content) {const messageDiv = document.createElement('div');messageDiv.classList.add('message', 'user-message');const headerDiv = document.createElement('div');headerDiv.classList.add('message-header');headerDiv.innerHTML = '<i class="fas fa-user"></i> 用户';const contentDiv = document.createElement('div');contentDiv.classList.add('message-content');contentDiv.innerHTML = `<p>${content}</p>`;messageDiv.appendChild(headerDiv);messageDiv.appendChild(contentDiv);chatContainer.appendChild(messageDiv);chatContainer.scrollTop = chatContainer.scrollHeight;}// 添加AI消息(Markdown渲染)function addAIMessage(content) {const messageDiv = document.createElement('div');messageDiv.classList.add('message', 'ai-message');const headerDiv = document.createElement('div');headerDiv.classList.add('message-header');headerDiv.innerHTML = '<i class="fas fa-robot"></i> DeepSeek 助手';const contentDiv = document.createElement('div');contentDiv.classList.add('message-content');// 使用Marked.js渲染Markdown并安全插入const renderedMarkdown = DOMPurify.sanitize(marked.parse(content));contentDiv.innerHTML = renderedMarkdown;messageDiv.appendChild(headerDiv);messageDiv.appendChild(contentDiv);chatContainer.appendChild(messageDiv);chatContainer.scrollTop = chatContainer.scrollHeight;}// 添加错误消息function addErrorMessage(message) {const errorDiv = document.createElement('div');errorDiv.classList.add('error-message');errorDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${message}`;chatContainer.appendChild(errorDiv);chatContainer.scrollTop = chatContainer.scrollHeight;}// 发送消息处理async function sendMessage() {const message = userInput.value.trim();if (message === '') return;// 添加用户消息addUserMessage(message);userInput.value = '';// 显示"正在输入"指示器typingIndicator.style.display = 'flex';chatContainer.scrollTop = chatContainer.scrollHeight;try {// 发送到API并获取响应const aiResponse = await sendMessageToAPI(message);if (aiResponse) {// 隐藏指示器并添加AI回复typingIndicator.style.display = 'none';addAIMessage(aiResponse);}} catch (error) {console.error('处理消息时出错:', error);typingIndicator.style.display = 'none';addErrorMessage('处理您的请求时出错,请稍后再试');}}// 事件监听sendBtn.addEventListener('click', sendMessage);userInput.addEventListener('keypress', function(e) {if (e.key === 'Enter') {sendMessage();}});// 初始焦点userInput.focus();// 添加示例消息setTimeout(() => {addUserMessage("你好,请帮我写一个Python函数计算斐波那契数列");typingIndicator.style.display = 'flex';setTimeout(() => {typingIndicator.style.display = 'none';addAIMessage("当然可以!以下是计算斐波那契数列的Python函数:\n\n```python\ndef fibonacci(n):\n \"\"\"\n 计算斐波那契数列的第n项\n \"\"\"\n if n <= 0:\n return 0\n elif n == 1:\n return 1\n else:\n a, b = 0, 1\n for _ in range(2, n+1):\n a, b = b, a + b\n return b\n\n# 示例:计算前10项\nfor i in range(1, 11):\n print(fibonacci(i))\n```\n\n这个函数高效地计算斐波那契数列,避免了递归的低效问题。");}, 1500);}, 1000);});
</script>
</body>
</html>
问题
需要充值即可:
充值之后进行访问