3v324v23 commited on
Commit
894f93a
·
0 Parent(s):

Initial commit: Enriched AI Sentiment & Keywords App

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ target/
2
+ *.class
3
+ .idea/
4
+ *.iml
5
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用多阶段构建来减小最终镜像大小
2
+ # 第一阶段:构建应用
3
+ FROM maven:3.9.1-eclipse-temurin-21 AS build
4
+ WORKDIR /app
5
+ COPY pom.xml .
6
+ COPY src ./src
7
+ RUN mvn clean package -DskipTests
8
+
9
+ # 第二阶段:运行应用
10
+ FROM eclipse-temurin:21-jre
11
+ WORKDIR /app
12
+ COPY --from=build /app/target/*.jar app.jar
13
+
14
+ # 暴露 Hugging Face Spaces 默认端口
15
+ EXPOSE 7860
16
+
17
+ # 运行命令
18
+ ENTRYPOINT ["java", "-jar", "app.jar"]
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "AI 智能文本分析助手"
3
+ emoji: "🧠"
4
+ colorFrom: "indigo"
5
+ colorTo: "purple"
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: "基于 Spring Boot 3 & DeepSeek AI 驱动的情感分析、关键词提取与对话建议(全中文汉化版)"
9
+ ---
10
+
11
+ # AI 智能文本分析助手 (AI Text Analysis Pro)
12
+
13
+ 这是一个使用 **Spring Boot 3** 和 **Java 21** 开发的高级文本分析应用,集成了 **DeepSeek-V3 (SiliconFlow API)**。
14
+
15
+ ## 🌟 核心功能
16
+
17
+ - **📊 情感倾向分析**:深度分析文本情感,识别积极、消极或中性情绪,并提供专业建议。
18
+ - **🏷️ 核心关键词提取**:自动从长文本中提取最具代表性的 3-5 个关键词。
19
+ - **🕒 智能历史记录**:在当前会话中自动保存分析历史,方便对比查看。
20
+ - **📱 响应式 UI**:适配手机和电脑,提供流畅的中文交互体验。
21
+
22
+ ## 🛠️ 技术实现
23
+
24
+ - **后端**: Spring Boot 3.4.3, Java 21 (LTS)
25
+ - **AI 引擎**: DeepSeek-V3 (经由 SiliconFlow 高速接口)
26
+ - **前端**: Thymeleaf + Bootstrap 5 + jQuery
27
+ - **容器化**: Docker (多阶段构建)
28
+ - **部署**: 已针对 Hugging Face Spaces 优化 (端口 7860)
29
+
30
+ ## 🚀 快速启动
31
+
32
+ 1. **配置 API Key**:
33
+ 在 `src/main/resources/application.properties` 中填入你的 SiliconFlow API Key。
34
+
35
+ 2. **本地运行**:
36
+ ```bash
37
+ mvn spring-boot:run
38
+ ```
39
+ 访问 `http://localhost:7860`。
40
+
41
+ 3. **Docker 构建**:
42
+ ```bash
43
+ docker build -t sentiment-app .
44
+ docker run -p 7860:7860 sentiment-app
45
+ ```
46
+
47
+ ## 📄 开源说明
48
+
49
+ 本项目为汉化增强版,旨在提供开箱即用的 Java AI 集成方案。
pom.xml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <groupId>org.springframework.boot</groupId>
7
+ <artifactId>spring-boot-starter-parent</artifactId>
8
+ <version>3.4.3</version>
9
+ <relativePath/> <!-- lookup parent from repository -->
10
+ </parent>
11
+ <groupId>com.example</groupId>
12
+ <artifactId>sentiment-analysis-app</artifactId>
13
+ <version>0.0.1-SNAPSHOT</version>
14
+ <name>sentiment-analysis-app</name>
15
+ <description>基于 Spring Boot 的情感分析与 AI 对话应用</description>
16
+ <properties>
17
+ <java.version>21</java.version>
18
+ </properties>
19
+ <dependencies>
20
+ <dependency>
21
+ <groupId>org.springframework.boot</groupId>
22
+ <artifactId>spring-boot-starter-thymeleaf</artifactId>
23
+ </dependency>
24
+ <dependency>
25
+ <groupId>org.springframework.boot</groupId>
26
+ <artifactId>spring-boot-starter-web</artifactId>
27
+ </dependency>
28
+ <dependency>
29
+ <groupId>org.springframework.boot</groupId>
30
+ <artifactId>spring-boot-starter-test</artifactId>
31
+ <scope>test</scope>
32
+ </dependency>
33
+ </dependencies>
34
+
35
+ <build>
36
+ <plugins>
37
+ <plugin>
38
+ <groupId>org.springframework.boot</groupId>
39
+ <artifactId>spring-boot-maven-plugin</artifactId>
40
+ </plugin>
41
+ </plugins>
42
+ </build>
43
+ </project>
src/main/java/com/example/sentiment/Application.java ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.example.sentiment;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+ /**
7
+ * Spring Boot 应用程序入口点
8
+ * 情感分析与 AI 对话助手
9
+ */
10
+ @SpringBootApplication
11
+ public class Application {
12
+
13
+ public static void main(String[] args) {
14
+ // 启动 Spring Boot 应用
15
+ SpringApplication.run(Application.class, args);
16
+ }
17
+ }
src/main/java/com/example/sentiment/controller/ChatController.java ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.example.sentiment.controller;
2
+
3
+ import com.example.sentiment.service.SentimentService;
4
+ import jakarta.servlet.http.HttpSession;
5
+ import org.springframework.beans.factory.annotation.Autowired;
6
+ import org.springframework.stereotype.Controller;
7
+ import org.springframework.web.bind.annotation.GetMapping;
8
+ import org.springframework.web.bind.annotation.PostMapping;
9
+ import org.springframework.web.bind.annotation.RequestParam;
10
+ import org.springframework.web.bind.annotation.ResponseBody;
11
+
12
+ import java.util.ArrayList;
13
+ import java.util.HashMap;
14
+ import java.util.List;
15
+ import java.util.Map;
16
+
17
+ /**
18
+ * Web 控制器
19
+ * 处理页面渲染、情感分析、关键词提取和历史记录
20
+ */
21
+ @Controller
22
+ public class ChatController {
23
+
24
+ @Autowired
25
+ private SentimentService sentimentService;
26
+
27
+ @GetMapping("/")
28
+ public String index() {
29
+ return "index";
30
+ }
31
+
32
+ /**
33
+ * 进行情感分析
34
+ */
35
+ @PostMapping("/analyze")
36
+ @ResponseBody
37
+ public Map<String, String> analyze(@RequestParam String text, HttpSession session) {
38
+ Map<String, String> result = new HashMap<>();
39
+ if (text == null || text.trim().isEmpty()) {
40
+ result.put("error", "请输入内容后再试。");
41
+ return result;
42
+ }
43
+
44
+ String analysis = sentimentService.analyzeSentiment(text);
45
+ saveHistory(session, "情感分析", text, analysis);
46
+
47
+ result.put("result", analysis);
48
+ return result;
49
+ }
50
+
51
+ /**
52
+ * 提取关键词
53
+ */
54
+ @PostMapping("/keywords")
55
+ @ResponseBody
56
+ public Map<String, String> keywords(@RequestParam String text, HttpSession session) {
57
+ Map<String, String> result = new HashMap<>();
58
+ if (text == null || text.trim().isEmpty()) {
59
+ result.put("error", "请输入内容后再试。");
60
+ return result;
61
+ }
62
+
63
+ String keywords = sentimentService.extractKeywords(text);
64
+ saveHistory(session, "关键词提取", text, keywords);
65
+
66
+ result.put("result", keywords);
67
+ return result;
68
+ }
69
+
70
+ /**
71
+ * 获取当前会话的历史记录
72
+ */
73
+ @GetMapping("/history")
74
+ @ResponseBody
75
+ public List<Map<String, String>> getHistory(HttpSession session) {
76
+ List<Map<String, String>> history = (List<Map<String, String>>) session.getAttribute("history");
77
+ return history != null ? history : new ArrayList<>();
78
+ }
79
+
80
+ /**
81
+ * 清空历史记录
82
+ */
83
+ @PostMapping("/clearHistory")
84
+ @ResponseBody
85
+ public String clearHistory(HttpSession session) {
86
+ session.removeAttribute("history");
87
+ return "success";
88
+ }
89
+
90
+ /**
91
+ * 私有方法:保存操作历史到 Session
92
+ */
93
+ private void saveHistory(HttpSession session, String type, String input, String output) {
94
+ List<Map<String, String>> history = (List<Map<String, String>>) session.getAttribute("history");
95
+ if (history == null) {
96
+ history = new ArrayList<>();
97
+ }
98
+
99
+ Map<String, String> entry = new HashMap<>();
100
+ entry.put("type", type);
101
+ entry.put("input", input.length() > 50 ? input.substring(0, 47) + "..." : input);
102
+ entry.put("output", output);
103
+
104
+ // 只保留最近 5 条
105
+ if (history.size() >= 5) {
106
+ history.remove(0);
107
+ }
108
+ history.add(entry);
109
+ session.setAttribute("history", history);
110
+ }
111
+ }
src/main/java/com/example/sentiment/model/ChatRequest.java ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.example.sentiment.model;
2
+
3
+ import java.util.List;
4
+
5
+ /**
6
+ * SiliconFlow API 请求模型
7
+ */
8
+ public class ChatRequest {
9
+ private String model;
10
+ private List<Message> messages;
11
+ private Double temperature;
12
+
13
+ public ChatRequest() {}
14
+
15
+ public ChatRequest(String model, List<Message> messages, Double temperature) {
16
+ this.model = model;
17
+ this.messages = messages;
18
+ this.temperature = temperature;
19
+ }
20
+
21
+ public String getModel() { return model; }
22
+ public void setModel(String model) { this.model = model; }
23
+
24
+ public List<Message> getMessages() { return messages; }
25
+ public void setMessages(List<Message> messages) { this.messages = messages; }
26
+
27
+ public Double getTemperature() { return temperature; }
28
+ public void setTemperature(Double temperature) { this.temperature = temperature; }
29
+
30
+ public static class Message {
31
+ private String role;
32
+ private String content;
33
+
34
+ public Message() {}
35
+
36
+ public Message(String role, String content) {
37
+ this.role = role;
38
+ this.content = content;
39
+ }
40
+
41
+ public String getRole() { return role; }
42
+ public void setRole(String role) { this.role = role; }
43
+
44
+ public String getContent() { return content; }
45
+ public void setContent(String content) { this.content = content; }
46
+ }
47
+ }
src/main/java/com/example/sentiment/model/ChatResponse.java ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.example.sentiment.model;
2
+
3
+ import java.util.List;
4
+
5
+ /**
6
+ * SiliconFlow API 响应模型
7
+ */
8
+ public class ChatResponse {
9
+ private List<Choice> choices;
10
+
11
+ public ChatResponse() {}
12
+
13
+ public ChatResponse(List<Choice> choices) {
14
+ this.choices = choices;
15
+ }
16
+
17
+ public List<Choice> getChoices() { return choices; }
18
+ public void setChoices(List<Choice> choices) { this.choices = choices; }
19
+
20
+ public static class Choice {
21
+ private Message message;
22
+
23
+ public Choice() {}
24
+
25
+ public Choice(Message message) {
26
+ this.message = message;
27
+ }
28
+
29
+ public Message getMessage() { return message; }
30
+ public void setMessage(Message message) { this.message = message; }
31
+ }
32
+
33
+ public static class Message {
34
+ private String role;
35
+ private String content;
36
+
37
+ public Message() {}
38
+
39
+ public Message(String role, String content) {
40
+ this.role = role;
41
+ this.content = content;
42
+ }
43
+
44
+ public String getRole() { return role; }
45
+ public void setRole(String role) { this.role = role; }
46
+
47
+ public String getContent() { return content; }
48
+ public void setContent(String content) { this.content = content; }
49
+ }
50
+ }
src/main/java/com/example/sentiment/service/SentimentService.java ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.example.sentiment.service;
2
+
3
+ import com.example.sentiment.model.ChatRequest;
4
+ import com.example.sentiment.model.ChatResponse;
5
+ import org.springframework.beans.factory.annotation.Value;
6
+ import org.springframework.http.HttpEntity;
7
+ import org.springframework.http.HttpHeaders;
8
+ import org.springframework.http.MediaType;
9
+ import org.springframework.stereotype.Service;
10
+ import org.springframework.web.client.RestTemplate;
11
+
12
+ import java.util.ArrayList;
13
+ import java.util.List;
14
+
15
+ /**
16
+ * 情感分析与 AI 对话服务
17
+ * 负责与 SiliconFlow API 通信,支持多种分析模式
18
+ */
19
+ @Service
20
+ public class SentimentService {
21
+
22
+ @Value("${siliconflow.api.key}")
23
+ private String apiKey;
24
+
25
+ @Value("${siliconflow.api.url}")
26
+ private String apiUrl;
27
+
28
+ @Value("${siliconflow.model}")
29
+ private String model;
30
+
31
+ private final RestTemplate restTemplate = new RestTemplate();
32
+
33
+ /**
34
+ * 调用 AI 模型进行对话或分析
35
+ * @param prompt 用户输入
36
+ * @param systemPrompt 系统提示词,定义分析模式
37
+ * @return AI 响应内容
38
+ */
39
+ public String callAi(String prompt, String systemPrompt) {
40
+ try {
41
+ HttpHeaders headers = new HttpHeaders();
42
+ headers.setContentType(MediaType.APPLICATION_JSON);
43
+ headers.setBearerAuth(apiKey);
44
+
45
+ List<ChatRequest.Message> messages = new ArrayList<>();
46
+ messages.add(new ChatRequest.Message("system", systemPrompt));
47
+ messages.add(new ChatRequest.Message("user", prompt));
48
+
49
+ ChatRequest request = new ChatRequest(model, messages, 0.7);
50
+
51
+ HttpEntity<ChatRequest> entity = new HttpEntity<>(request, headers);
52
+ ChatResponse response = restTemplate.postForObject(apiUrl, entity, ChatResponse.class);
53
+
54
+ if (response != null && response.getChoices() != null && !response.getChoices().isEmpty()) {
55
+ return response.getChoices().get(0).getMessage().getContent();
56
+ }
57
+ return "抱歉,AI 响应为空。";
58
+ } catch (Exception e) {
59
+ return "AI 调用出错:" + e.getMessage();
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 进行情感分析
65
+ */
66
+ public String analyzeSentiment(String prompt) {
67
+ String systemPrompt = "你是一个专业的情感分析助手。请分析用户输入的情感倾向(积极、消极或中性),并给出简短的建议。请始终使用中文回复。";
68
+ return callAi(prompt, systemPrompt);
69
+ }
70
+
71
+ /**
72
+ * 提取关键词
73
+ */
74
+ public String extractKeywords(String prompt) {
75
+ String systemPrompt = "你是一个语言专家。请从用户提供的文本中提取出最重要的 3-5 个关键词,并以列表形式展示。请始终使用中文回复。";
76
+ return callAi(prompt, systemPrompt);
77
+ }
78
+ }
src/main/resources/application.properties ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 应用名称
2
+ spring.application.name=sentiment-analysis-app
3
+ # 服务器端口 (Hugging Face Spaces 默认 7860)
4
+ server.port=7860
5
+
6
+ # SiliconFlow API 配置
7
+ siliconflow.api.key=sk-uuejewptzohwsbbutfnrkbcloaqxydjmxbeqptwphnhiuopl
8
+ siliconflow.api.url=https://api.siliconflow.cn/v1/chat/completions
9
+ siliconflow.model=deepseek-ai/DeepSeek-V3
10
+
11
+ # Thymeleaf 缓存配置(开发环境关闭)
12
+ spring.thymeleaf.cache=false
src/main/resources/templates/index.html ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI 智能文本分析助手</title>
7
+ <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #4a90e2;
11
+ --secondary-color: #f5f7fa;
12
+ }
13
+ body {
14
+ background-color: #f0f2f5;
15
+ font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
16
+ }
17
+ .main-card {
18
+ border: none;
19
+ border-radius: 20px;
20
+ box-shadow: 0 10px 30px rgba(0,0,0,0.08);
21
+ overflow: hidden;
22
+ }
23
+ .header-gradient {
24
+ background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
25
+ color: white;
26
+ padding: 40px 20px;
27
+ }
28
+ .nav-tabs .nav-link {
29
+ border: none;
30
+ color: #666;
31
+ padding: 12px 25px;
32
+ font-weight: 500;
33
+ }
34
+ .nav-tabs .nav-link.active {
35
+ color: var(--primary-color);
36
+ border-bottom: 3px solid var(--primary-color);
37
+ background: none;
38
+ }
39
+ .result-area {
40
+ background-color: white;
41
+ border-radius: 12px;
42
+ padding: 25px;
43
+ border: 1px solid #e1e4e8;
44
+ min-height: 100px;
45
+ margin-top: 20px;
46
+ }
47
+ .history-item {
48
+ background-color: white;
49
+ border-radius: 10px;
50
+ padding: 15px;
51
+ margin-bottom: 15px;
52
+ border-left: 4px solid var(--primary-color);
53
+ transition: transform 0.2s;
54
+ }
55
+ .history-item:hover {
56
+ transform: translateY(-2px);
57
+ box-shadow: 0 4px 12px rgba(0,0,0,0.05);
58
+ }
59
+ .badge-type {
60
+ font-size: 0.75rem;
61
+ padding: 4px 8px;
62
+ border-radius: 4px;
63
+ background-color: #eef2f7;
64
+ color: #555;
65
+ }
66
+ #loading { display: none; }
67
+ </style>
68
+ </head>
69
+ <body>
70
+
71
+ <div class="container py-5">
72
+ <div class="row justify-content-center">
73
+ <div class="col-lg-9">
74
+ <div class="card main-card mb-4">
75
+ <div class="header-gradient text-center">
76
+ <h1 class="display-6 fw-bold mb-2">AI 智能文本分析助手</h1>
77
+ <p class="lead mb-0">情感分析 · 关键词提取 · 智能建议</p>
78
+ </div>
79
+
80
+ <div class="card-body p-4">
81
+ <div class="mb-4">
82
+ <label for="textInput" class="form-label fw-bold">请输入待分析的文本内容:</label>
83
+ <textarea class="form-control" id="textInput" rows="5" placeholder="在此输入文字,例如:最近工作压力有点大,但看到同事们的进步我也感到很欣慰..."></textarea>
84
+ </div>
85
+
86
+ <div class="row g-3">
87
+ <div class="col-md-6">
88
+ <button class="btn btn-primary w-100 py-3 fw-bold" id="analyzeBtn">
89
+ <span class="btn-text">🔍 情感倾向分析</span>
90
+ </button>
91
+ </div>
92
+ <div class="col-md-6">
93
+ <button class="btn btn-outline-primary w-100 py-3 fw-bold" id="keywordsBtn">
94
+ <span class="btn-text">🏷️ 提取核心关键词</span>
95
+ </button>
96
+ </div>
97
+ </div>
98
+
99
+ <div id="loading" class="text-center mt-4">
100
+ <div class="spinner-border text-primary" role="status"></div>
101
+ <p class="mt-2 text-muted">AI 正在思考中,请稍候...</p>
102
+ </div>
103
+
104
+ <div id="resultBox" style="display: none;">
105
+ <div class="result-area mt-4">
106
+ <div class="d-flex justify-content-between align-items-center mb-3">
107
+ <h5 class="mb-0 text-primary fw-bold" id="resultTitle">分析结果</h5>
108
+ <button class="btn btn-sm btn-light" onclick="$('#resultBox').hide()">关闭</button>
109
+ </div>
110
+ <div id="analysisResult" class="lh-lg"></div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- 历史记录部分 -->
117
+ <div class="card main-card">
118
+ <div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
119
+ <h5 class="mb-0 fw-bold">🕒 最近分析历史</h5>
120
+ <button class="btn btn-sm btn-outline-danger" id="clearHistoryBtn">清空历史</button>
121
+ </div>
122
+ <div class="card-body p-4 bg-light" id="historyList">
123
+ <p class="text-center text-muted py-4">暂无历史记录</p>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
131
+ <script>
132
+ function updateHistory() {
133
+ $.get('/history', function(data) {
134
+ const container = $('#historyList');
135
+ if (data.length === 0) {
136
+ container.html('<p class="text-center text-muted py-4">暂无历史记录</p>');
137
+ return;
138
+ }
139
+ let html = '';
140
+ data.reverse().forEach(item => {
141
+ html += `
142
+ <div class="history-item">
143
+ <div class="d-flex justify-content-between mb-2">
144
+ <span class="badge-type">${item.type}</span>
145
+ <small class="text-muted">刚才</small>
146
+ </div>
147
+ <div class="text-truncate text-muted small mb-2">输入: ${item.input}</div>
148
+ <div class="fw-medium">${item.output.replace(/\n/g, '<br>')}</div>
149
+ </div>
150
+ `;
151
+ });
152
+ container.html(html);
153
+ });
154
+ }
155
+
156
+ function processAction(url, title) {
157
+ const text = $('#textInput').val().trim();
158
+ if (!text) {
159
+ alert('请输入内容后再试。');
160
+ return;
161
+ }
162
+
163
+ $('#loading').show();
164
+ $('.btn').prop('disabled', true);
165
+ $('#resultBox').hide();
166
+
167
+ $.post(url, { text: text }, function(data) {
168
+ if (data.error) {
169
+ alert(data.error);
170
+ } else {
171
+ $('#resultTitle').text(title);
172
+ $('#analysisResult').html(data.result.replace(/\n/g, '<br>'));
173
+ $('#resultBox').fadeIn();
174
+ updateHistory();
175
+ }
176
+ }).fail(function() {
177
+ alert('服务器请求失败,请检查后端运行状态。');
178
+ }).always(function() {
179
+ $('#loading').hide();
180
+ $('.btn').prop('disabled', false);
181
+ });
182
+ }
183
+
184
+ $(document).ready(function() {
185
+ $('#analyzeBtn').click(() => processAction('/analyze', '📊 情感分析报告'));
186
+ $('#keywordsBtn').click(() => processAction('/keywords', '🏷️ 核心关键词提取'));
187
+
188
+ $('#clearHistoryBtn').click(function() {
189
+ $.post('/clearHistory', function() {
190
+ updateHistory();
191
+ });
192
+ });
193
+
194
+ updateHistory();
195
+ });
196
+ </script>
197
+
198
+ </body>
199
+ </html>