当前位置: 首页 > news >正文

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、只需要写一份代码即可,修改配置文件,切换大语言模型

核心特性

  1. 统一 API 设计
    • 通过抽象层屏蔽不同 AI 提供商(如 OpenAI、Gemini、Ollama 等)的接口差异,开发者只需使用 Spring AI 的通用接口(如 ChatModel),即可切换底层 AI 服务。
  2. 开箱即用的功能支持
    • 支持对话模型(Chat)类的大模型:与 ChatGPT 类似的交互。
    • 支持嵌入模型(Embedding)类的大模型:嵌入模型是将文本、图像或其他数据转换为数值向量(即嵌入向量)的技术。
    • 支持图像生成(Image Generation)类的大模型:图像生成是指AI根据文本描述或其他输入创建新图像的能力。
    • 支持函数调用(Function Calling):函数调用功能使AI模型能够与外部API和服务交互。也就是说,你写一个函数,AI 也能调用你写的函数。
  3. 与 Spring 生态无缝集成
    • 支持 Spring Boot 自动配置、依赖注入、Actuator 监控等,与 Spring Security、Spring Data 等组件协同工作。
  4. Prompt(提示) 工程支持
    • 提供 PromptTemplate 等工具,方便动态生成提示词(Prompts),支持上下文管理。(根据会话记录,来回答问题 )
  5. 模块化设计
  • 开发者可以根据项目需求选择特定的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模型服务的公司或平台。
特点: 同一提供商可能支持多种模型类型

常见提供商

提供商支持的模型类型具体模型示例
OpenAIChat, Embedding, ImageGPT-4o、text-embedding-3、DALL-E
Google Vertex AIChat, Embedding, ImagePaLM 2、Imagen
Azure OpenAIChat, Embedding微软托管的OpenAI服务
Hugging FaceChat, Embedding, Image开源模型(如BLOOM、Stable Diffusion)
Stability AIImageStable Diffusion系列
DeepSeekChat,代码专用模型、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 模块名
OpenAIChat, Embedding, Image Generation, Function CallingGPT-4, GPT-3.5, text-embedding-3, DALL-E 3spring-ai-openai
Azure OpenAIChat, Embedding, Image GenerationGPT-4, GPT-3.5, text-embedding-ada-002spring-ai-azure-openai
Google Vertex AIChat, Embedding, Code GenerationGemini 1.5, PaLM 2, textembedding-geckospring-ai-vertexai
Hugging FaceChat, Embedding, Text Generation, Image GenerationBLOOM, Llama 2, Stable Diffusion, BERTspring-ai-huggingface
Stability AIImage GenerationStable Diffusion XL, Stable Diffusion 3spring-ai-stabilityai
AnthropicChatClaude 3, Claude 2spring-ai-anthropic (社区支持)
OllamaChat, Embedding (本地运行开源模型)Llama 3, Mistral, Gemmaspring-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>

问题
在这里插入图片描述
需要充值即可:
在这里插入图片描述

充值之后进行访问

在这里插入图片描述

相关文章:

  • 苏州工业园区劳动局网站做不了合同怎么样推广最有效最快速
  • 企业网站推广解决方案排名优化软件
  • 网站后台管理系统 asp市场营销策划方案书
  • 长治网站制作小程序百度手机助手app下载安装
  • 做网站英文怎么说天津做优化好的公司
  • 帮别人做高仿产品网站 违法么在线bt种子
  • 微信小程序中 rpx与px的区别
  • 【FreeRTOS】FreeRTOS源码概述
  • HSA22HSA29美光固态芯片D8BJVC8BJW
  • 机器学习×第十三卷:集成学习上篇——她不再独断,而是召集小队贴贴你
  • 多线程的同步
  • k8s强制删除podpvpvc和nsnamespace
  • 人机交互动画制作新突破!文本驱动扩散框架HOIDiNi:一句话驱动虚拟人高精度操作物体。
  • GitHub Actions 的深度解析与概念介绍
  • Java8新特性_加强版
  • 采用ArcGIS10.8.2 进行插值图绘制
  • STM32MPU6050 dmp库
  • MyBatis-Plus:从入门到进阶
  • 如何将OBJ文件转成GLB文件
  • NVIDIA GPU架构学习笔记
  • 时序数据库IoTDB可实现的基本操作及命令汇总
  • Linux操作系统Nginx Web服务
  • 16、nrf52840蓝牙学习(唯一ID加密与解密)
  • VRRP:解决路由器单点故障的终极方案
  • wpa_supplicant连接到了路由,但是 udhcpc会分配到不同网段的ip,路由器ip为192.168.0网段,板子分配ip为192.168.1的网段
  • 2025.6.24总结