Browse Source
Merge branch 'huangyongxing/feature-20260327161252-学习笔记' into milestone-20260325-学习笔记
milestone-20260325-学习笔记
Merge branch 'huangyongxing/feature-20260327161252-学习笔记' into milestone-20260325-学习笔记
milestone-20260325-学习笔记
31 changed files with 1121 additions and 0 deletions
-
BIN黄永兴学习笔记/3.30黄永兴-软件功能总结.docx
-
BIN黄永兴学习笔记/3.30黄永兴.docx
-
10黄永兴学习笔记/StudySpringAI/.idea/.gitignore
-
7黄永兴学习笔记/StudySpringAI/.idea/MarsCodeWorkspaceAppSettings.xml
-
20黄永兴学习笔记/StudySpringAI/.idea/compiler.xml
-
7黄永兴学习笔记/StudySpringAI/.idea/encodings.xml
-
35黄永兴学习笔记/StudySpringAI/.idea/jarRepositories.xml
-
15黄永兴学习笔记/StudySpringAI/.idea/misc.xml
-
8黄永兴学习笔记/StudySpringAI/.idea/modules.xml
-
6黄永兴学习笔记/StudySpringAI/.idea/vcs.xml
-
33黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.gitignore
-
3黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.mvn/wrapper/maven-wrapper.properties
-
100黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/pom.xml
-
13黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/SpringAiEmbeddingApplication.java
-
54黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/EmbeddingController.java
-
22黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/RagController.java
-
87黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/RagService.java
-
93黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/TextSimilarityService.java
-
28黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/tool/DocumentLoader.java
-
21黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/application.properties
-
8黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/knowledge.txt
-
13黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/test/java/com/example/springaiembedding/SpringAiEmbeddingApplicationTests.java
-
33黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.gitignore
-
3黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.mvn/wrapper/maven-wrapper.properties
-
104黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/pom.xml
-
13黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/SpringAiQuickStartApplication.java
-
127黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/controller/ChatController.java
-
13黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/application.properties
-
224黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/static/index.html
-
13黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/test/java/com/example/springaiquickstart/SpringAiQuickStartApplicationTests.java
-
8黄永兴学习笔记/StudySpringAI/StudySpringAI.iml
@ -0,0 +1,10 @@ |
|||||
|
# 默认忽略的文件 |
||||
|
/shelf/ |
||||
|
/workspace.xml |
||||
|
# 已忽略包含查询文件的默认文件夹 |
||||
|
/queries/ |
||||
|
# Datasource local storage ignored files |
||||
|
/dataSources/ |
||||
|
/dataSources.local.xml |
||||
|
# 基于编辑器的 HTTP 客户端请求 |
||||
|
/httpRequests/ |
||||
@ -0,0 +1,7 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState"> |
||||
|
<option name="chatAppRouterInfo" value="builder/69c9f63d3b54644cf72884fa" /> |
||||
|
<option name="progress" value="1.0" /> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="CompilerConfiguration"> |
||||
|
<annotationProcessing> |
||||
|
<profile name="Maven default annotation processors profile" enabled="true"> |
||||
|
<sourceOutputDir name="target/generated-sources/annotations" /> |
||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> |
||||
|
<outputRelativeToContentRoot value="true" /> |
||||
|
<module name="SpringAIEmbedding" /> |
||||
|
<module name="SpringAIQuickStart" /> |
||||
|
</profile> |
||||
|
</annotationProcessing> |
||||
|
</component> |
||||
|
<component name="JavacSettings"> |
||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE"> |
||||
|
<module name="SpringAIEmbedding" options="-parameters" /> |
||||
|
<module name="SpringAIQuickStart" options="-parameters" /> |
||||
|
</option> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="Encoding"> |
||||
|
<file url="file://$PROJECT_DIR$/SpringAIEmbedding/src/main/java" charset="UTF-8" /> |
||||
|
<file url="file://$PROJECT_DIR$/SpringAIQuickStart/src/main/java" charset="UTF-8" /> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,35 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="RemoteRepositoriesConfiguration"> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="central-portal-snapshots" /> |
||||
|
<option name="name" value="Central Portal Snapshots" /> |
||||
|
<option name="url" value="https://central.sonatype.com/repository/maven-snapshots/" /> |
||||
|
</remote-repository> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="central" /> |
||||
|
<option name="name" value="Central Repository" /> |
||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" /> |
||||
|
</remote-repository> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="spring-milestones" /> |
||||
|
<option name="name" value="Spring Milestones" /> |
||||
|
<option name="url" value="https://repo.spring.io/milestone" /> |
||||
|
</remote-repository> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="central" /> |
||||
|
<option name="name" value="Maven Central repository" /> |
||||
|
<option name="url" value="https://repo1.maven.org/maven2" /> |
||||
|
</remote-repository> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="spring-snapshots" /> |
||||
|
<option name="name" value="Spring Snapshots" /> |
||||
|
<option name="url" value="https://repo.spring.io/snapshot" /> |
||||
|
</remote-repository> |
||||
|
<remote-repository> |
||||
|
<option name="id" value="jboss.community" /> |
||||
|
<option name="name" value="JBoss Community repository" /> |
||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> |
||||
|
</remote-repository> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" /> |
||||
|
<component name="MavenProjectsManager"> |
||||
|
<option name="originalFiles"> |
||||
|
<list> |
||||
|
<option value="$PROJECT_DIR$/SpringAIQuickStart/pom.xml" /> |
||||
|
<option value="$PROJECT_DIR$/SpringAIEmbedding/pom.xml" /> |
||||
|
</list> |
||||
|
</option> |
||||
|
</component> |
||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK"> |
||||
|
<output url="file://$PROJECT_DIR$/out" /> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="ProjectModuleManager"> |
||||
|
<modules> |
||||
|
<module fileurl="file://$PROJECT_DIR$/StudySpringAI.iml" filepath="$PROJECT_DIR$/StudySpringAI.iml" /> |
||||
|
</modules> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,6 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project version="4"> |
||||
|
<component name="VcsDirectoryMappings"> |
||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" /> |
||||
|
</component> |
||||
|
</project> |
||||
@ -0,0 +1,33 @@ |
|||||
|
HELP.md |
||||
|
target/ |
||||
|
.mvn/wrapper/maven-wrapper.jar |
||||
|
!**/src/main/**/target/ |
||||
|
!**/src/test/**/target/ |
||||
|
|
||||
|
### STS ### |
||||
|
.apt_generated |
||||
|
.classpath |
||||
|
.factorypath |
||||
|
.project |
||||
|
.settings |
||||
|
.springBeans |
||||
|
.sts4-cache |
||||
|
|
||||
|
### IntelliJ IDEA ### |
||||
|
.idea |
||||
|
*.iws |
||||
|
*.iml |
||||
|
*.ipr |
||||
|
|
||||
|
### NetBeans ### |
||||
|
/nbproject/private/ |
||||
|
/nbbuild/ |
||||
|
/dist/ |
||||
|
/nbdist/ |
||||
|
/.nb-gradle/ |
||||
|
build/ |
||||
|
!**/src/main/**/build/ |
||||
|
!**/src/test/**/build/ |
||||
|
|
||||
|
### VS Code ### |
||||
|
.vscode/ |
||||
@ -0,0 +1,3 @@ |
|||||
|
wrapperVersion=3.3.4 |
||||
|
distributionType=only-script |
||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip |
||||
@ -0,0 +1,100 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||
|
<version>3.5.13</version> |
||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||
|
</parent> |
||||
|
<groupId>com.example</groupId> |
||||
|
<artifactId>SpringAIEmbedding</artifactId> |
||||
|
<version>0.0.1-SNAPSHOT</version> |
||||
|
<name>SpringAIEmbedding</name> |
||||
|
<description>SpringAIEmbedding</description> |
||||
|
<url/> |
||||
|
<licenses> |
||||
|
<license/> |
||||
|
</licenses> |
||||
|
<developers> |
||||
|
<developer/> |
||||
|
</developers> |
||||
|
<scm> |
||||
|
<connection/> |
||||
|
<developerConnection/> |
||||
|
<tag/> |
||||
|
<url/> |
||||
|
</scm> |
||||
|
<properties> |
||||
|
<java.version>17</java.version> |
||||
|
<spring-ai.version>1.1.3</spring-ai.version> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencyManagement> |
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.ai</groupId> |
||||
|
<artifactId>spring-ai-bom</artifactId> |
||||
|
<version>1.0.0-SNAPSHOT</version> |
||||
|
<type>pom</type> |
||||
|
<scope>import</scope> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
</dependencyManagement> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
<!-- Spring AI Zhipuai 模型依赖 --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.ai</groupId> |
||||
|
<artifactId>spring-ai-starter-model-zhipuai</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- Spring AI Chat 客户端依赖 中包括TokenTextSplitter、TextReader、Document 等工具类 --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.ai</groupId> |
||||
|
<artifactId>spring-ai-client-chat</artifactId> |
||||
|
<version>1.0.0</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
|
||||
|
<!-- 声明仓库,用于获取 Spring AI 以及相关预发布版本 --> |
||||
|
<repositories> |
||||
|
<!-- Spring Milestones 仓库 --> |
||||
|
<repository> |
||||
|
<id>spring-milestones</id> |
||||
|
<name>Spring Milestones</name> |
||||
|
<url>https://repo.spring.io/milestone</url> |
||||
|
<snapshots> |
||||
|
<enabled>false</enabled> |
||||
|
</snapshots> |
||||
|
</repository> |
||||
|
<!-- Spring Snapshots 仓库 --> |
||||
|
<repository> |
||||
|
<id>spring-snapshots</id> |
||||
|
<name>Spring Snapshots</name> |
||||
|
<url>https://repo.spring.io/snapshot</url> |
||||
|
<releases> |
||||
|
<enabled>false</enabled> |
||||
|
</releases> |
||||
|
<snapshots> |
||||
|
<enabled>true</enabled> |
||||
|
</snapshots> |
||||
|
</repository> |
||||
|
</repositories> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
package com.example.springaiembedding; |
||||
|
|
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
|
||||
|
@SpringBootApplication |
||||
|
public class SpringAiEmbeddingApplication { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(SpringAiEmbeddingApplication.class, args); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
package com.example.springaiembedding.controller; |
||||
|
|
||||
|
import com.example.springaiembedding.service.TextSimilarityService; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import jakarta.servlet.http.HttpServlet; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
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.Arrays; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/ai") |
||||
|
public class EmbeddingController { |
||||
|
|
||||
|
@Autowired |
||||
|
private EmbeddingModel embeddingModel; |
||||
|
|
||||
|
//对用户传入的文本进行向量化处理,测试embedding模型 |
||||
|
|
||||
|
@RequestMapping("/embedding") |
||||
|
public Map<String,Object> embedding( |
||||
|
@RequestParam(value = "message",defaultValue = "给我讲个笑话") String message |
||||
|
){ |
||||
|
|
||||
|
// 对用户传入的文本进行向量化处理 |
||||
|
float[] embedding = embeddingModel.embed(message); |
||||
|
|
||||
|
return Map.of("message",message, |
||||
|
"vector",embedding); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Resource |
||||
|
private TextSimilarityService textSimilarityService; |
||||
|
|
||||
|
/** |
||||
|
* 查找相似文本接口 |
||||
|
* @param message 查询文本 |
||||
|
* @return 相似文本+相似度 |
||||
|
*/ |
||||
|
@GetMapping("/similarity") |
||||
|
public Map<String, Double> findSimilarText(@RequestParam String message) { |
||||
|
// 查询Top3相似文本 |
||||
|
return textSimilarityService.findSimilarTexts(message, 3); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
package com.example.springaiembedding.controller; |
||||
|
|
||||
|
import com.example.springaiembedding.service.RagService; |
||||
|
import jakarta.annotation.Resource; |
||||
|
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("/ai/rag") |
||||
|
public class RagController { |
||||
|
@Resource |
||||
|
private RagService ragService; |
||||
|
|
||||
|
// 问答接口 |
||||
|
@GetMapping("/query") |
||||
|
public String query(@RequestParam String question) { |
||||
|
return ragService.generateAnswer(question); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
package com.example.springaiembedding.service; |
||||
|
|
||||
|
import com.example.springaiembedding.tool.DocumentLoader; |
||||
|
import jakarta.annotation.PostConstruct; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import org.springframework.ai.chat.client.ChatClient; |
||||
|
import org.springframework.ai.chat.model.ChatModel; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.embedding.EmbeddingResponse; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.*; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
public class RagService { |
||||
|
@Resource |
||||
|
private EmbeddingModel embeddingModel; |
||||
|
@Resource |
||||
|
private ChatModel chatModel; |
||||
|
|
||||
|
// 内存存储:知识库片段 → 对应向量 |
||||
|
private Map<String, float[]> knowledgeVectors = new HashMap<>(); |
||||
|
|
||||
|
// 项目启动时自动加载并向量化知识库 |
||||
|
@PostConstruct |
||||
|
public void initKnowledgeBase() throws IOException { |
||||
|
// 1. 加载并拆分文档 |
||||
|
List<String> chunks = DocumentLoader.loadAndSplit("knowledge.txt"); |
||||
|
// 2. 批量向量化(一次请求生成所有片段向量) |
||||
|
EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(chunks); |
||||
|
// 3. 存储片段与向量的映射 |
||||
|
for (int i = 0; i < chunks.size(); i++) { |
||||
|
knowledgeVectors.put(chunks.get(i), embeddingResponse.getResults().get(i).getOutput()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检索与用户问题最相似的 Top N 片段 |
||||
|
private List<String> retrieveSimilarChunks(String query, int topN) { |
||||
|
// 1. 用户问题向量化 |
||||
|
float[] queryVector = embeddingModel.embed(query); |
||||
|
// 2. 计算与所有知识库片段的相似度 |
||||
|
Map<String, Double> similarityMap = new HashMap<>(); |
||||
|
//Entry是什么?Map.Entry<String, float[]> entry 是一个 Map.Entry 对象,用于遍历 Map 中的键值对。 |
||||
|
for (Map.Entry<String, float[]> entry : knowledgeVectors.entrySet()) { |
||||
|
similarityMap.put(entry.getKey(), CosineSimilarityCalculator.calculate(queryVector, entry.getValue())); |
||||
|
} |
||||
|
// 3. 按相似度降序排序,取 Top N |
||||
|
return similarityMap.entrySet().stream() |
||||
|
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) |
||||
|
.limit(topN) |
||||
|
.map(Map.Entry::getKey) |
||||
|
.collect(Collectors.toList()); |
||||
|
} |
||||
|
|
||||
|
// 生成最终回答 |
||||
|
public String generateAnswer(String query) { |
||||
|
// 1. 检索相似片段(取 Top 2) |
||||
|
List<String> similarChunks = retrieveSimilarChunks(query, 2); |
||||
|
// 2. 拼接 Prompt(约束模型基于知识库回答) |
||||
|
StringBuilder prompt = new StringBuilder(); |
||||
|
prompt.append("请基于以下知识库内容回答用户问题,禁止编造信息:\n"); |
||||
|
for (String chunk : similarChunks) { |
||||
|
prompt.append("- ").append(chunk).append("\n"); |
||||
|
} |
||||
|
prompt.append("\n用户问题:").append(query); |
||||
|
// 3. 调用 Chat 模型生成回答 |
||||
|
ChatClient chatClient = ChatClient.builder(chatModel).build(); |
||||
|
return chatClient.prompt().user(prompt.toString()).call().content(); |
||||
|
} |
||||
|
|
||||
|
public class CosineSimilarityCalculator { |
||||
|
// 计算两个 float[] 向量的余弦相似度 |
||||
|
public static double calculate(float[] vectorA, float[] vectorB) { |
||||
|
double dotProduct = 0.0; |
||||
|
double normA = 0.0; |
||||
|
double normB = 0.0; |
||||
|
for (int i = 0; i < vectorA.length; i++) { |
||||
|
dotProduct += vectorA[i] * vectorB[i]; |
||||
|
normA += Math.pow(vectorA[i], 2); |
||||
|
normB += Math.pow(vectorB[i], 2); |
||||
|
} |
||||
|
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
package com.example.springaiembedding.service; |
||||
|
|
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.embedding.EmbeddingResponse; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
public class TextSimilarityService { |
||||
|
|
||||
|
@Autowired |
||||
|
private EmbeddingModel embeddingModel; |
||||
|
|
||||
|
// 模拟本地文本库(可替换为数据库/向量库) |
||||
|
private final List<String> TEXT_LIBRARY = Arrays.asList( |
||||
|
"我爱Java编程", |
||||
|
"SpringBoot是最流行的后端框架", |
||||
|
"人工智能改变世界", |
||||
|
"大模型应用开发", |
||||
|
"我喜欢学习编程", |
||||
|
"向量数据库用于存储Embedding", |
||||
|
"SpringAI简化大模型开发" |
||||
|
); |
||||
|
|
||||
|
/** |
||||
|
* 查找最相似的文本 |
||||
|
* @param queryText 查询文本 |
||||
|
* @param topN 返回前N条结果 |
||||
|
* @return 相似文本+相似度 |
||||
|
*/ |
||||
|
|
||||
|
public Map<String, Double> findSimilarTexts(String queryText, int topN) { |
||||
|
// 1. 将查询文本转为向量 |
||||
|
//float[] queryVector = embeddingModel.embed(queryText); |
||||
|
EmbeddingResponse queryEmbedding = embeddingModel.embedForResponse(List.of(queryText)); |
||||
|
float[] queryVector = queryEmbedding.getResults().get(0).getOutput(); |
||||
|
|
||||
|
// 2. 将文本库所有文本转为向量 |
||||
|
// 2. 将文本库所有文本转为向量 |
||||
|
Map<String, float[]> textVectorMap = new HashMap<>(); |
||||
|
for (String text : TEXT_LIBRARY) { |
||||
|
float[] textVector = embeddingModel.embed(text); |
||||
|
//put是将文本和向量存储到Map中 |
||||
|
textVectorMap.put(text, textVector); |
||||
|
} |
||||
|
|
||||
|
// 3. 计算相似度并排序 |
||||
|
Map<String, Double> similarityMap = new HashMap<>(); |
||||
|
for (Map.Entry<String, float[]> entry : textVectorMap.entrySet()) { |
||||
|
double similarity = CosineSimilarityCalculator.calculate(queryVector, entry.getValue()); |
||||
|
similarityMap.put(entry.getKey(), similarity); |
||||
|
} |
||||
|
|
||||
|
// 4. 按相似度降序排序,取TopN |
||||
|
return similarityMap.entrySet().stream() |
||||
|
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) |
||||
|
.limit(topN) |
||||
|
.collect(Collectors.toMap( |
||||
|
Map.Entry::getKey, |
||||
|
Map.Entry::getValue, |
||||
|
(oldValue, newValue) -> oldValue, |
||||
|
LinkedHashMap::new |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 余弦相似度工具类(计算文本向量相似度) |
||||
|
*/ |
||||
|
public class CosineSimilarityCalculator { |
||||
|
|
||||
|
/** |
||||
|
* 计算两个向量的余弦相似度 |
||||
|
* @param vectorA 向量A |
||||
|
* @param vectorB 向量B |
||||
|
* @return 相似度 0~1,值越大越相似 |
||||
|
*/ |
||||
|
public static double calculate(float[] vectorA, float[] vectorB) { |
||||
|
double dotProduct = 0.0; |
||||
|
double normA = 0.0; |
||||
|
double normB = 0.0; |
||||
|
|
||||
|
for (int i = 0; i < vectorA.length; i++) { |
||||
|
dotProduct += vectorA[i] * vectorB[i]; |
||||
|
normA += Math.pow(vectorA[i], 2); |
||||
|
normB += Math.pow(vectorB[i], 2); |
||||
|
} |
||||
|
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
package com.example.springaiembedding.tool; |
||||
|
|
||||
|
import org.springframework.core.io.ClassPathResource; |
||||
|
import java.io.BufferedReader; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStreamReader; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
|
||||
|
// 文档加载工具类 |
||||
|
public class DocumentLoader { |
||||
|
// 加载并拆分知识库文档 |
||||
|
public static List<String> loadAndSplit(String resourcePath) throws IOException { |
||||
|
ClassPathResource resource = new ClassPathResource(resourcePath); // 从类路径加载资源 |
||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream())); // 按行读取 |
||||
|
String content = reader.lines().collect(Collectors.joining("\n")); // 读取文件内容,将所有行连接起来一个字符串 |
||||
|
|
||||
|
// 按 "---" 拆分片段,过滤空内容 |
||||
|
List<String> chunks = new ArrayList<>(); |
||||
|
for (String chunk : content.split("---")) { |
||||
|
String trimmed = chunk.trim(); |
||||
|
if (!trimmed.isEmpty()) chunks.add(trimmed); |
||||
|
} |
||||
|
return chunks; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
spring.application.name=SpringAIEmbedding |
||||
|
|
||||
|
server.port=8080 |
||||
|
|
||||
|
## ????AI ???? |
||||
|
# Embedding ?? |
||||
|
spring.ai.zhipuai.api-key=c721340a438942d0942148b48a22e50e.5VCKagzmQdyHpwyc |
||||
|
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas |
||||
|
|
||||
|
# Embedding ?? |
||||
|
spring.ai.zhipuai.embedding-model=embedding-2 |
||||
|
# Chat ?? |
||||
|
spring.ai.zhipuai.chat.options.model=GLM-4.7-Flash |
||||
|
|
||||
|
# ?????? |
||||
|
logging.pattern.console=%-5level %logger - %msg%n |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,8 @@ |
|||||
|
--- |
||||
|
Spring AI 是一个用于简化大模型应用开发的框架,支持智谱AI、OpenAI 等多种大模型厂商。 |
||||
|
--- |
||||
|
智谱AI Embedding 模型可将文本转换为向量,用于语义检索、相似匹配等场景。 |
||||
|
--- |
||||
|
RAG(检索增强生成)通过检索本地知识库内容,辅助大模型生成更准确的回答,避免幻觉。 |
||||
|
--- |
||||
|
Spring AI 提供 EmbeddingModel 和 ChatModel 接口,让开发者快速接入大模型能力。 |
||||
@ -0,0 +1,13 @@ |
|||||
|
package com.example.springaiembedding; |
||||
|
|
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
|
||||
|
@SpringBootTest |
||||
|
class SpringAiEmbeddingApplicationTests { |
||||
|
|
||||
|
@Test |
||||
|
void contextLoads() { |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
HELP.md |
||||
|
target/ |
||||
|
.mvn/wrapper/maven-wrapper.jar |
||||
|
!**/src/main/**/target/ |
||||
|
!**/src/test/**/target/ |
||||
|
|
||||
|
### STS ### |
||||
|
.apt_generated |
||||
|
.classpath |
||||
|
.factorypath |
||||
|
.project |
||||
|
.settings |
||||
|
.springBeans |
||||
|
.sts4-cache |
||||
|
|
||||
|
### IntelliJ IDEA ### |
||||
|
.idea |
||||
|
*.iws |
||||
|
*.iml |
||||
|
*.ipr |
||||
|
|
||||
|
### NetBeans ### |
||||
|
/nbproject/private/ |
||||
|
/nbbuild/ |
||||
|
/dist/ |
||||
|
/nbdist/ |
||||
|
/.nb-gradle/ |
||||
|
build/ |
||||
|
!**/src/main/**/build/ |
||||
|
!**/src/test/**/build/ |
||||
|
|
||||
|
### VS Code ### |
||||
|
.vscode/ |
||||
@ -0,0 +1,3 @@ |
|||||
|
wrapperVersion=3.3.4 |
||||
|
distributionType=only-script |
||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip |
||||
@ -0,0 +1,104 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||
|
<version>3.5.13</version> |
||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||
|
</parent> |
||||
|
<groupId>com.example</groupId> |
||||
|
<artifactId>SpringAIQuickStart</artifactId> |
||||
|
<version>0.0.1-SNAPSHOT</version> |
||||
|
<name>SpringAIQuickStart</name> |
||||
|
<description>SpringAIQuickStart</description> |
||||
|
<url/> |
||||
|
<licenses> |
||||
|
<license/> |
||||
|
</licenses> |
||||
|
<developers> |
||||
|
<developer/> |
||||
|
</developers> |
||||
|
<scm> |
||||
|
<connection/> |
||||
|
<developerConnection/> |
||||
|
<tag/> |
||||
|
<url/> |
||||
|
</scm> |
||||
|
<properties> |
||||
|
<java.version>17</java.version> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencyManagement> |
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.ai</groupId> |
||||
|
<artifactId>spring-ai-bom</artifactId> |
||||
|
<version>1.0.0-SNAPSHOT</version> |
||||
|
<type>pom</type> |
||||
|
<scope>import</scope> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
</dependencyManagement> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- Spring AI Deepseek 模型依赖 --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.ai</groupId> |
||||
|
<artifactId>spring-ai-starter-model-deepseek</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<!-- 声明仓库,用于获取 Spring AI 以及相关预发布版本 --> |
||||
|
<repositories> |
||||
|
<!-- Spring Milestones 仓库 --> |
||||
|
<repository> |
||||
|
<id>spring-milestones</id> |
||||
|
<name>Spring Milestones</name> |
||||
|
<url>https://repo.spring.io/milestone</url> |
||||
|
<snapshots> |
||||
|
<enabled>false</enabled> |
||||
|
</snapshots> |
||||
|
</repository> |
||||
|
<!-- Spring Snapshots 仓库 --> |
||||
|
<repository> |
||||
|
<id>spring-snapshots</id> |
||||
|
<name>Spring Snapshots</name> |
||||
|
<url>https://repo.spring.io/snapshot</url> |
||||
|
<releases> |
||||
|
<enabled>false</enabled> |
||||
|
</releases> |
||||
|
<snapshots> |
||||
|
<enabled>true</enabled> |
||||
|
</snapshots> |
||||
|
</repository> |
||||
|
</repositories> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
package com.example.springaiquickstart; |
||||
|
|
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
|
||||
|
@SpringBootApplication |
||||
|
public class SpringAiQuickStartApplication { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(SpringAiQuickStartApplication.class, args); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
package com.example.springaiquickstart.controller; |
||||
|
|
||||
|
|
||||
|
import jakarta.servlet.http.HttpServlet; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import org.springframework.ai.chat.messages.AssistantMessage; |
||||
|
import org.springframework.ai.chat.messages.Message; |
||||
|
import org.springframework.ai.chat.messages.SystemMessage; |
||||
|
import org.springframework.ai.chat.messages.UserMessage; |
||||
|
import org.springframework.ai.chat.model.ChatResponse; |
||||
|
import org.springframework.ai.chat.model.Generation; |
||||
|
import org.springframework.ai.chat.prompt.ChatOptions; |
||||
|
import org.springframework.ai.chat.prompt.Prompt; |
||||
|
import org.springframework.ai.deepseek.DeepSeekChatModel; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.http.HttpRequest; |
||||
|
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 reactor.core.publisher.Flux; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/ai") |
||||
|
public class ChatController { |
||||
|
|
||||
|
@Autowired |
||||
|
private DeepSeekChatModel chatModel; |
||||
|
|
||||
|
// 与模型直接对话,返回字符串响应 |
||||
|
@GetMapping("/generate") |
||||
|
public String generate(@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message) { |
||||
|
System.out.println("message: " + message); |
||||
|
|
||||
|
// 与模型直接对话,调用chatModel的call方法生成响应 |
||||
|
String response = chatModel.call(message); |
||||
|
|
||||
|
System.out.println("response: " + response); |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
// 与模型对话,流式返回内容 |
||||
|
@GetMapping("/generateStream1") |
||||
|
public Flux<ChatResponse> generateStream1(@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message) { |
||||
|
System.out.println("message: " + message); |
||||
|
|
||||
|
// 与模型对话,流式返回内容 |
||||
|
Prompt prompt = new Prompt(message); |
||||
|
Flux<ChatResponse> stream = chatModel.stream(prompt); |
||||
|
|
||||
|
System.out.println("stream: " + stream); |
||||
|
|
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
// 与模型对话,流式返回内容,转换为字符串流 |
||||
|
// 解决中文乱码问题 |
||||
|
// 用lambda表达式简化 |
||||
|
@GetMapping("/generateStream2") |
||||
|
public Flux<String> generateStream2( |
||||
|
@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message, |
||||
|
HttpServletResponse response |
||||
|
) { |
||||
|
//设置字符编码为UTF-8,解决中文乱码问题 |
||||
|
response.setCharacterEncoding("UTF-8"); |
||||
|
System.out.println("message: " + message); |
||||
|
|
||||
|
// 与模型对话,流式返回内容 |
||||
|
Prompt prompt = new Prompt(message); |
||||
|
Flux<ChatResponse> stream = chatModel.stream(prompt); |
||||
|
|
||||
|
// 转换为字符串流,用lambda表达式简化 |
||||
|
Flux<String> result = stream.map(ChatResponse -> |
||||
|
ChatResponse.getResult().getOutput().getText() |
||||
|
); |
||||
|
// 转换为字符串流,用方法引用简化 |
||||
|
// Flux<String> result = stream.map(ChatResponse::getResult) |
||||
|
// .map(Generation::getOutput) |
||||
|
// .map(AssistantMessage::getText); |
||||
|
|
||||
|
|
||||
|
System.out.println("result: " + result); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
//运行时设置模型参数 |
||||
|
@GetMapping("/runtimeOptions") |
||||
|
public Flux<String> runtimeOptions( |
||||
|
@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message, |
||||
|
@RequestParam(value = "temperature",required = false) Double temp, |
||||
|
HttpServletResponse response |
||||
|
) { |
||||
|
//设置字符编码为UTF-8,解决中文乱码问题 |
||||
|
response.setCharacterEncoding("UTF-8"); |
||||
|
// 构建系统提示 |
||||
|
SystemMessage systemMessage = new SystemMessage("你是一个资深Java开发工程师,回答要简洁专业"); |
||||
|
// 构建用户消息 |
||||
|
UserMessage userMessage = new UserMessage(message); |
||||
|
// 构建历史消息(多轮对话时加入) |
||||
|
List<Message> messages = Arrays.asList(systemMessage, userMessage); |
||||
|
|
||||
|
// 封装成 Prompt,还可以设置模型参数 |
||||
|
Prompt prompt = new Prompt(messages, |
||||
|
ChatOptions.builder() |
||||
|
.temperature(temp) |
||||
|
.maxTokens(1000) |
||||
|
.build() |
||||
|
); |
||||
|
|
||||
|
// 流式调用 |
||||
|
Flux<ChatResponse> stream = chatModel.stream(prompt); |
||||
|
// 转换为字符串流,用lambda表达式简化 |
||||
|
Flux<String> result = stream.map(ChatResponse -> |
||||
|
ChatResponse.getResult().getOutput().getText() |
||||
|
); |
||||
|
|
||||
|
System.out.println("result: " + result); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
spring.application.name=SpringAIQuickStart |
||||
|
|
||||
|
server.port=8080 |
||||
|
|
||||
|
#?? Deepseek ???????url????????? |
||||
|
spring.ai.deepseek.base-url=https://api.deepseek.com |
||||
|
spring.ai.deepseek.api-key=sk-ccbfe09f433148129cd98df6150653e8 |
||||
|
spring.ai.deepseek.chat.options.model=deepseek-chat |
||||
|
|
||||
|
#??0-2???0????????2??????? |
||||
|
spring.ai.deepseek.chat.options.temperature=0.8 |
||||
|
|
||||
|
|
||||
@ -0,0 +1,224 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="zh-CN"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>Spring AI 聊天助手</title> |
||||
|
<script src="https://cdn.tailwindcss.com"></script> |
||||
|
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> |
||||
|
<script> |
||||
|
tailwind.config = { |
||||
|
theme: { |
||||
|
extend: { |
||||
|
colors: { |
||||
|
primary: '#3b82f6', |
||||
|
secondary: '#64748b', |
||||
|
neutral: '#f8fafc', |
||||
|
dark: '#1e293b' |
||||
|
}, |
||||
|
fontFamily: { |
||||
|
sans: ['Inter', 'system-ui', 'sans-serif'] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style type="text/tailwindcss"> |
||||
|
@layer utilities { |
||||
|
.content-auto { |
||||
|
content-visibility: auto; |
||||
|
} |
||||
|
.chat-message { |
||||
|
@apply p-4 rounded-lg mb-4 max-w-[80%]; |
||||
|
} |
||||
|
.user-message { |
||||
|
@apply bg-primary text-white self-end; |
||||
|
} |
||||
|
.ai-message { |
||||
|
@apply bg-neutral border border-gray-200 text-dark self-start; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body class="bg-gray-50 min-h-screen flex flex-col"> |
||||
|
<!-- 顶部导航栏 --> |
||||
|
<header class="bg-white shadow-sm"> |
||||
|
<div class="container mx-auto px-4 py-4 flex justify-between items-center"> |
||||
|
<div class="flex items-center space-x-2"> |
||||
|
<i class="fa fa-robot text-primary text-2xl"></i> |
||||
|
<h1 class="text-xl font-bold text-dark">Spring AI 聊天助手</h1> |
||||
|
</div> |
||||
|
<div class="text-sm text-secondary"> |
||||
|
<span id="status" class="flex items-center"> |
||||
|
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> |
||||
|
在线 |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</header> |
||||
|
|
||||
|
<!-- 主内容区 --> |
||||
|
<main class="flex-1 container mx-auto px-4 py-8 max-w-4xl"> |
||||
|
<!-- 聊天区域 --> |
||||
|
<div class="bg-white rounded-xl shadow-md overflow-hidden"> |
||||
|
<!-- 聊天头部 --> |
||||
|
<div class="bg-primary text-white p-4"> |
||||
|
<h2 class="text-lg font-semibold">AI 助手</h2> |
||||
|
<p class="text-sm opacity-80">基于 DeepSeek 模型</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 聊天消息区 --> |
||||
|
<div id="chat-messages" class="p-4 h-[500px] overflow-y-auto flex flex-col space-y-4"> |
||||
|
<!-- 欢迎消息 --> |
||||
|
<div class="chat-message ai-message"> |
||||
|
<div class="flex items-start"> |
||||
|
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center mr-3 flex-shrink-0"> |
||||
|
<i class="fa fa-robot"></i> |
||||
|
</div> |
||||
|
<div> |
||||
|
<p class="font-semibold mb-1">AI 助手</p> |
||||
|
<p>你好!我是基于 DeepSeek 模型的 AI 助手,有什么可以帮助你的吗?</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 输入区域 --> |
||||
|
<div class="p-4 border-t"> |
||||
|
<form id="chat-form" class="flex space-x-2"> |
||||
|
<input |
||||
|
type="text" |
||||
|
id="message-input" |
||||
|
placeholder="输入消息..." |
||||
|
class="flex-1 border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" |
||||
|
> |
||||
|
<button |
||||
|
type="submit" |
||||
|
id="send-button" |
||||
|
class="bg-primary text-white rounded-lg px-6 py-2 hover:bg-primary/90 transition-colors flex items-center space-x-2" |
||||
|
> |
||||
|
<span>发送</span> |
||||
|
<i class="fa fa-paper-plane"></i> |
||||
|
</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 功能说明 --> |
||||
|
<div class="mt-8 bg-white rounded-xl shadow-md p-6"> |
||||
|
<h3 class="text-lg font-semibold mb-4">功能说明</h3> |
||||
|
<ul class="space-y-2 text-secondary"> |
||||
|
<li class="flex items-center"> |
||||
|
<i class="fa fa-check-circle text-green-500 mr-2"></i> |
||||
|
支持自然语言对话 |
||||
|
</li> |
||||
|
<li class="flex items-center"> |
||||
|
<i class="fa fa-check-circle text-green-500 mr-2"></i> |
||||
|
基于 DeepSeek 大语言模型 |
||||
|
</li> |
||||
|
<li class="flex items-center"> |
||||
|
<i class="fa fa-check-circle text-green-500 mr-2"></i> |
||||
|
实时响应生成 |
||||
|
</li> |
||||
|
<li class="flex items-center"> |
||||
|
<i class="fa fa-check-circle text-green-500 mr-2"></i> |
||||
|
简洁美观的用户界面 |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</main> |
||||
|
|
||||
|
<!-- 页脚 --> |
||||
|
<footer class="bg-white shadow-sm mt-8"> |
||||
|
<div class="container mx-auto px-4 py-4 text-center text-secondary text-sm"> |
||||
|
<p>© 2026 Spring AI 聊天助手 | 基于 DeepSeek 模型</p> |
||||
|
</div> |
||||
|
</footer> |
||||
|
|
||||
|
<script> |
||||
|
document.addEventListener('DOMContentLoaded', function() { |
||||
|
const chatForm = document.getElementById('chat-form'); |
||||
|
const messageInput = document.getElementById('message-input'); |
||||
|
const chatMessages = document.getElementById('chat-messages'); |
||||
|
const sendButton = document.getElementById('send-button'); |
||||
|
const status = document.getElementById('status'); |
||||
|
|
||||
|
// 发送消息 |
||||
|
chatForm.addEventListener('submit', function(e) { |
||||
|
e.preventDefault(); |
||||
|
const message = messageInput.value.trim(); |
||||
|
if (message) { |
||||
|
// 添加用户消息到聊天区 |
||||
|
addMessage('user', message); |
||||
|
messageInput.value = ''; |
||||
|
|
||||
|
// 禁用发送按钮 |
||||
|
sendButton.disabled = true; |
||||
|
sendButton.classList.add('opacity-50'); |
||||
|
status.innerHTML = '<span class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></span>处理中...'; |
||||
|
|
||||
|
// 调用后端 API |
||||
|
fetch(`/ai/generate?message=${encodeURIComponent(message)}`) |
||||
|
.then(response => response.text()) |
||||
|
.then(data => { |
||||
|
// 添加 AI 响应到聊天区 |
||||
|
addMessage('ai', data); |
||||
|
}) |
||||
|
.catch(error => { |
||||
|
console.error('Error:', error); |
||||
|
addMessage('ai', '抱歉,处理请求时出错,请稍后再试。'); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
// 恢复发送按钮状态 |
||||
|
sendButton.disabled = false; |
||||
|
sendButton.classList.remove('opacity-50'); |
||||
|
status.innerHTML = '<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>在线'; |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 添加消息到聊天区 |
||||
|
function addMessage(type, content) { |
||||
|
const messageDiv = document.createElement('div'); |
||||
|
messageDiv.className = `chat-message ${type === 'user' ? 'user-message' : 'ai-message'} self-${type === 'user' ? 'end' : 'start'}`; |
||||
|
|
||||
|
if (type === 'user') { |
||||
|
messageDiv.innerHTML = ` |
||||
|
<div class="flex items-start justify-end"> |
||||
|
<div> |
||||
|
<p class="font-semibold mb-1 text-right">你</p> |
||||
|
<p>${content}</p> |
||||
|
</div> |
||||
|
<div class="w-8 h-8 rounded-full bg-gray-300 text-dark flex items-center justify-center ml-3 flex-shrink-0"> |
||||
|
<i class="fa fa-user"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
`; |
||||
|
} else { |
||||
|
messageDiv.innerHTML = ` |
||||
|
<div class="flex items-start"> |
||||
|
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center mr-3 flex-shrink-0"> |
||||
|
<i class="fa fa-robot"></i> |
||||
|
</div> |
||||
|
<div> |
||||
|
<p class="font-semibold mb-1">AI 助手</p> |
||||
|
<p>${content}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
`; |
||||
|
} |
||||
|
|
||||
|
chatMessages.appendChild(messageDiv); |
||||
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
||||
|
} |
||||
|
|
||||
|
// 回车键发送消息 |
||||
|
messageInput.addEventListener('keypress', function(e) { |
||||
|
if (e.key === 'Enter') { |
||||
|
chatForm.dispatchEvent(new Event('submit')); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
</script> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,13 @@ |
|||||
|
package com.example.springaiquickstart; |
||||
|
|
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
|
||||
|
@SpringBootTest |
||||
|
class SpringAiQuickStartApplicationTests { |
||||
|
|
||||
|
@Test |
||||
|
void contextLoads() { |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<module type="GENERAL_MODULE" version="4"> |
||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true"> |
||||
|
<exclude-output /> |
||||
|
<content url="file://$MODULE_DIR$" /> |
||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||
|
</component> |
||||
|
</module> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue