From 0dfabc35d76452e5a2ff64ed729df5deeb6e909d Mon Sep 17 00:00:00 2001 From: peanut_hzm Date: Sat, 26 Jul 2025 00:37:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/ApifoxUploaderProjectSetting.xml | 6 - .idea/AugmentWebviewStateStore.xml | 2 +- .idea/dataSources.xml | 2 +- .idea/jarRepositories.xml | 10 + .../java/com/emotion/common/BaseEntity.java | 6 + .../java/com/emotion/config/AsyncConfig.java | 45 +++ .../controller/EmotionSummaryController.java | 13 +- .../java/com/emotion/entity/Achievement.java | 6 +- .../main/java/com/emotion/entity/Comment.java | 6 +- .../com/emotion/entity/CommunityPost.java | 6 +- .../java/com/emotion/entity/Conversation.java | 4 +- .../java/com/emotion/entity/CozeApiCall.java | 4 +- .../com/emotion/entity/EmotionAnalysis.java | 6 +- .../com/emotion/entity/EmotionRecord.java | 6 +- .../java/com/emotion/entity/GrowthTopic.java | 6 +- .../java/com/emotion/entity/GuestUser.java | 6 +- .../java/com/emotion/entity/LocationPin.java | 6 +- .../main/java/com/emotion/entity/Message.java | 4 +- .../main/java/com/emotion/entity/Reward.java | 6 +- .../com/emotion/entity/TopicInteraction.java | 6 +- .../main/java/com/emotion/entity/User.java | 4 +- .../java/com/emotion/entity/UserStats.java | 6 +- .../com/emotion/service/AiChatService.java | 70 +++- .../service/impl/AiChatServiceImpl.java | 22 +- .../src/main/resources/application.yml | 7 + .../sql/mysql_emotion_museum_final.sql | 130 +++---- chat-history-fix-summary.md | 87 ----- debug-chat-history.md | 147 -------- package-lock.json | 2 +- test-chat-history-api.html | 226 ------------ test-chat-history-complete.js | 238 ------------- test-duplicate-message-fix.md | 69 ---- test-message-api.html | 130 ------- test-message-history-api.html | 191 ---------- verify-chat-history-fix.js | 174 --------- web/debug.html | 210 ----------- web/package-lock.json | 6 +- web/package.json | 7 +- web/src/App.vue | 160 ++++----- web/src/assets/styles/global.scss | 1 + web/src/assets/styles/globalThis.scss | 2 +- web/src/components/layout/AppFooter.vue | 3 +- web/src/components/layout/AppHeader.vue | 1 + web/src/router/index.ts | 10 +- web/src/services/api.ts | 2 +- web/src/stores/chat.ts | 7 - web/src/types/index.ts | 1 + web/src/views/Chat/History.vue | 1 + web/src/views/Chat/index.vue | 68 +--- web/src/views/Dashboard/index.vue | 1 + web/src/views/Diary/index.vue | 1 + web/src/views/Home/index.vue | 3 +- web/src/views/LifeTrajectory/index.vue | 1 + web/src/views/Login/index.vue | 1 + web/src/views/Messages/index.vue | 1 + web/src/views/NotFound.vue | 1 + web/src/views/Profile/index.vue | 1 + web/src/views/Register/index.vue | 1 + web/src/views/Settings/index.vue | 1 + web/src/views/TokenTest.vue | 185 ---------- web/src/views/TopicTracker/index.vue | 1 + web/src/views/WebSocketTest.vue | 333 ------------------ web/vite.config.ts | 7 +- 开心APP网页代码v1.1/wnD97OS/chat-history.html | 43 +++ 开心APP网页代码v1.1/wnD97OS/chat-history.js | 64 ++++ 开心APP网页代码v1.1/wnD97OS/chat.css | 102 ++++++ 开心APP网页代码v1.1/wnD97OS/chat.html | 89 +++++ 开心APP网页代码v1.1/wnD97OS/chat.js | 78 ++++ 开心APP网页代码v1.1/wnD97OS/chat_manager.js | 80 +++++ 开心APP网页代码v1.1/wnD97OS/data.js | 40 +++ 开心APP网页代码v1.1/wnD97OS/diary.html | 63 ++++ 开心APP网页代码v1.1/wnD97OS/diary.js | 180 ++++++++++ 开心APP网页代码v1.1/wnD97OS/index.html | 169 +++++++++ 开心APP网页代码v1.1/wnD97OS/js/app_nav.js | 37 ++ .../wnD97OS/js/chat_manager.js | 266 ++++++++++++++ 开心APP网页代码v1.1/wnD97OS/js/shared.js | 58 +++ .../wnD97OS/life_milestones.html | 51 +++ .../wnD97OS/life_milestones.js | 5 + .../wnD97OS/life_trajectory.html | 166 +++++++++ .../wnD97OS/life_trajectory.js | 301 ++++++++++++++++ 开心APP网页代码v1.1/wnD97OS/messages.html | 53 +++ 开心APP网页代码v1.1/wnD97OS/messages.js | 77 ++++ .../wnD97OS/personal_dashboard.html | 136 +++++++ .../wnD97OS/personal_dashboard.js | 309 ++++++++++++++++ 开心APP网页代码v1.1/wnD97OS/script.js | 78 ++++ 开心APP网页代码v1.1/wnD97OS/settings.html | 153 ++++++++ 开心APP网页代码v1.1/wnD97OS/settings.js | 7 + 开心APP网页代码v1.1/wnD97OS/style.css | 163 +++++++++ .../wnD97OS/topic_tracker.html | 125 +++++++ 开心APP网页代码v1.1/wnD97OS/topic_tracker.js | 319 +++++++++++++++++ 90 files changed, 3594 insertions(+), 2294 deletions(-) delete mode 100644 .idea/ApifoxUploaderProjectSetting.xml create mode 100644 backend-single/src/main/java/com/emotion/config/AsyncConfig.java delete mode 100644 chat-history-fix-summary.md delete mode 100644 debug-chat-history.md delete mode 100644 test-chat-history-api.html delete mode 100644 test-chat-history-complete.js delete mode 100644 test-duplicate-message-fix.md delete mode 100644 test-message-api.html delete mode 100644 test-message-history-api.html delete mode 100644 verify-chat-history-fix.js delete mode 100644 web/debug.html delete mode 100644 web/src/views/TokenTest.vue delete mode 100644 web/src/views/WebSocketTest.vue create mode 100644 开心APP网页代码v1.1/wnD97OS/chat-history.html create mode 100644 开心APP网页代码v1.1/wnD97OS/chat-history.js create mode 100644 开心APP网页代码v1.1/wnD97OS/chat.css create mode 100644 开心APP网页代码v1.1/wnD97OS/chat.html create mode 100644 开心APP网页代码v1.1/wnD97OS/chat.js create mode 100644 开心APP网页代码v1.1/wnD97OS/chat_manager.js create mode 100644 开心APP网页代码v1.1/wnD97OS/data.js create mode 100644 开心APP网页代码v1.1/wnD97OS/diary.html create mode 100644 开心APP网页代码v1.1/wnD97OS/diary.js create mode 100644 开心APP网页代码v1.1/wnD97OS/index.html create mode 100644 开心APP网页代码v1.1/wnD97OS/js/app_nav.js create mode 100644 开心APP网页代码v1.1/wnD97OS/js/chat_manager.js create mode 100644 开心APP网页代码v1.1/wnD97OS/js/shared.js create mode 100644 开心APP网页代码v1.1/wnD97OS/life_milestones.html create mode 100644 开心APP网页代码v1.1/wnD97OS/life_milestones.js create mode 100644 开心APP网页代码v1.1/wnD97OS/life_trajectory.html create mode 100644 开心APP网页代码v1.1/wnD97OS/life_trajectory.js create mode 100644 开心APP网页代码v1.1/wnD97OS/messages.html create mode 100644 开心APP网页代码v1.1/wnD97OS/messages.js create mode 100644 开心APP网页代码v1.1/wnD97OS/personal_dashboard.html create mode 100644 开心APP网页代码v1.1/wnD97OS/personal_dashboard.js create mode 100644 开心APP网页代码v1.1/wnD97OS/script.js create mode 100644 开心APP网页代码v1.1/wnD97OS/settings.html create mode 100644 开心APP网页代码v1.1/wnD97OS/settings.js create mode 100644 开心APP网页代码v1.1/wnD97OS/style.css create mode 100644 开心APP网页代码v1.1/wnD97OS/topic_tracker.html create mode 100644 开心APP网页代码v1.1/wnD97OS/topic_tracker.js diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml deleted file mode 100644 index 247c160..0000000 --- a/.idea/ApifoxUploaderProjectSetting.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml index 3ab650a..88fc6f3 100644 --- a/.idea/AugmentWebviewStateStore.xml +++ b/.idea/AugmentWebviewStateStore.xml @@ -3,7 +3,7 @@ diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 6a31e7e..3a1f68c 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,7 +1,7 @@ - + mysql.8 true com.mysql.cj.jdbc.Driver diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index fa3a980..5f965ce 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -6,6 +6,11 @@ \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/common/BaseEntity.java b/backend-single/src/main/java/com/emotion/common/BaseEntity.java index bbe3a23..348d313 100644 --- a/backend-single/src/main/java/com/emotion/common/BaseEntity.java +++ b/backend-single/src/main/java/com/emotion/common/BaseEntity.java @@ -3,6 +3,9 @@ package com.emotion.common; import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; import java.io.Serializable; import java.time.LocalDateTime; @@ -14,6 +17,9 @@ import java.time.LocalDateTime; * @date 2025-07-22 */ @Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor public class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; diff --git a/backend-single/src/main/java/com/emotion/config/AsyncConfig.java b/backend-single/src/main/java/com/emotion/config/AsyncConfig.java new file mode 100644 index 0000000..1bdb18a --- /dev/null +++ b/backend-single/src/main/java/com/emotion/config/AsyncConfig.java @@ -0,0 +1,45 @@ +package com.emotion.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +/** + * 多线程异步任务线程池配置 + */ +@Configuration +@EnableAsync +public class AsyncConfig { + + @Value("${async.core-pool-size:10}") + private int corePoolSize; + + @Value("${async.max-pool-size:50}") + private int maxPoolSize; + + @Value("${async.queue-capacity:200}") + private int queueCapacity; + + @Value("${async.keep-alive-seconds:60}") + private int keepAliveSeconds; + + @Value("${async.thread-name-prefix:single-async-}") + private String threadNamePrefix; + + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + executor.setThreadNamePrefix(threadNamePrefix); + executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java b/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java index cea6a1f..0ecaeef 100644 --- a/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java +++ b/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java @@ -8,7 +8,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; - +import org.springframework.scheduling.annotation.Async; +import java.util.concurrent.CompletableFuture; import java.util.Map; /** @@ -29,16 +30,11 @@ public class EmotionSummaryController { @Operation(summary = "生成用户当天的情绪记录总结", description = "基于用户当天的聊天记录生成情绪分析和记录") @PostMapping("/generate") public Result> generateEmotionSummary() { - try { - // 从上下文中获取当前用户ID String userId = CurrentUserUtil.requireCurrentUserId(); - log.info("收到生成情绪记录总结请求: userId={}", userId); - - // 调用AI服务生成情绪总结 - Map result = aiChatService.generateEmotionSummary(userId); - + // 调用AI服务异步生成情绪总结(阻塞获取结果) + Map result = aiChatService.generateEmotionSummaryAsync(userId).get(); if ((Boolean) result.get("success")) { log.info("情绪记录总结生成成功: userId={}", userId); return Result.success("情绪记录总结生成成功", result); @@ -47,7 +43,6 @@ public class EmotionSummaryController { log.warn("情绪记录总结生成失败: userId={}, message={}", userId, message); return Result.error(message); } - } catch (IllegalStateException e) { log.warn("用户认证失败: {}", e.getMessage()); return Result.error(e.getMessage()); diff --git a/backend-single/src/main/java/com/emotion/entity/Achievement.java b/backend-single/src/main/java/com/emotion/entity/Achievement.java index 9dc4215..087ab5a 100644 --- a/backend-single/src/main/java/com/emotion/entity/Achievement.java +++ b/backend-single/src/main/java/com/emotion/entity/Achievement.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("achievement") diff --git a/backend-single/src/main/java/com/emotion/entity/Comment.java b/backend-single/src/main/java/com/emotion/entity/Comment.java index 7ef2de9..8348939 100644 --- a/backend-single/src/main/java/com/emotion/entity/Comment.java +++ b/backend-single/src/main/java/com/emotion/entity/Comment.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; /** * 评论实体类 @@ -16,7 +16,7 @@ import lombok.NoArgsConstructor; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("comment") diff --git a/backend-single/src/main/java/com/emotion/entity/CommunityPost.java b/backend-single/src/main/java/com/emotion/entity/CommunityPost.java index 751a713..bef61a3 100644 --- a/backend-single/src/main/java/com/emotion/entity/CommunityPost.java +++ b/backend-single/src/main/java/com/emotion/entity/CommunityPost.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; /** * 社区帖子实体类 @@ -16,7 +16,7 @@ import lombok.NoArgsConstructor; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("community_post") diff --git a/backend-single/src/main/java/com/emotion/entity/Conversation.java b/backend-single/src/main/java/com/emotion/entity/Conversation.java index ab2d758..6cb101e 100644 --- a/backend-single/src/main/java/com/emotion/entity/Conversation.java +++ b/backend-single/src/main/java/com/emotion/entity/Conversation.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Builder; +import lombok.experimental.SuperBuilder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("conversation") diff --git a/backend-single/src/main/java/com/emotion/entity/CozeApiCall.java b/backend-single/src/main/java/com/emotion/entity/CozeApiCall.java index 1e9e6ef..31d8ff2 100644 --- a/backend-single/src/main/java/com/emotion/entity/CozeApiCall.java +++ b/backend-single/src/main/java/com/emotion/entity/CozeApiCall.java @@ -3,7 +3,7 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; import lombok.Data; -import lombok.Builder; +import lombok.experimental.SuperBuilder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("coze_api_call") diff --git a/backend-single/src/main/java/com/emotion/entity/EmotionAnalysis.java b/backend-single/src/main/java/com/emotion/entity/EmotionAnalysis.java index b2c0623..3779030 100644 --- a/backend-single/src/main/java/com/emotion/entity/EmotionAnalysis.java +++ b/backend-single/src/main/java/com/emotion/entity/EmotionAnalysis.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("emotion_analysis") diff --git a/backend-single/src/main/java/com/emotion/entity/EmotionRecord.java b/backend-single/src/main/java/com/emotion/entity/EmotionRecord.java index bd3609d..339fb6f 100644 --- a/backend-single/src/main/java/com/emotion/entity/EmotionRecord.java +++ b/backend-single/src/main/java/com/emotion/entity/EmotionRecord.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDate; @@ -19,7 +19,7 @@ import java.time.LocalDate; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("emotion_record") diff --git a/backend-single/src/main/java/com/emotion/entity/GrowthTopic.java b/backend-single/src/main/java/com/emotion/entity/GrowthTopic.java index 748ec72..ded9c2e 100644 --- a/backend-single/src/main/java/com/emotion/entity/GrowthTopic.java +++ b/backend-single/src/main/java/com/emotion/entity/GrowthTopic.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("growth_topic") diff --git a/backend-single/src/main/java/com/emotion/entity/GuestUser.java b/backend-single/src/main/java/com/emotion/entity/GuestUser.java index 3e739a3..11c34f1 100644 --- a/backend-single/src/main/java/com/emotion/entity/GuestUser.java +++ b/backend-single/src/main/java/com/emotion/entity/GuestUser.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -18,7 +18,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("guest_user") diff --git a/backend-single/src/main/java/com/emotion/entity/LocationPin.java b/backend-single/src/main/java/com/emotion/entity/LocationPin.java index 043c954..733be3f 100644 --- a/backend-single/src/main/java/com/emotion/entity/LocationPin.java +++ b/backend-single/src/main/java/com/emotion/entity/LocationPin.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("location_pin") diff --git a/backend-single/src/main/java/com/emotion/entity/Message.java b/backend-single/src/main/java/com/emotion/entity/Message.java index 3016536..ae53b01 100644 --- a/backend-single/src/main/java/com/emotion/entity/Message.java +++ b/backend-single/src/main/java/com/emotion/entity/Message.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Builder; +import lombok.experimental.SuperBuilder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @@ -19,7 +19,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("message") diff --git a/backend-single/src/main/java/com/emotion/entity/Reward.java b/backend-single/src/main/java/com/emotion/entity/Reward.java index fe9c928..4d9f6dc 100644 --- a/backend-single/src/main/java/com/emotion/entity/Reward.java +++ b/backend-single/src/main/java/com/emotion/entity/Reward.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -18,7 +18,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("reward") diff --git a/backend-single/src/main/java/com/emotion/entity/TopicInteraction.java b/backend-single/src/main/java/com/emotion/entity/TopicInteraction.java index 81e9b74..9b350ea 100644 --- a/backend-single/src/main/java/com/emotion/entity/TopicInteraction.java +++ b/backend-single/src/main/java/com/emotion/entity/TopicInteraction.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -18,7 +18,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("topic_interaction") diff --git a/backend-single/src/main/java/com/emotion/entity/User.java b/backend-single/src/main/java/com/emotion/entity/User.java index 5533761..aee4528 100644 --- a/backend-single/src/main/java/com/emotion/entity/User.java +++ b/backend-single/src/main/java/com/emotion/entity/User.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Builder; +import lombok.experimental.SuperBuilder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; @@ -20,7 +20,7 @@ import java.time.LocalDateTime; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("user") diff --git a/backend-single/src/main/java/com/emotion/entity/UserStats.java b/backend-single/src/main/java/com/emotion/entity/UserStats.java index 7638de6..1a96c07 100644 --- a/backend-single/src/main/java/com/emotion/entity/UserStats.java +++ b/backend-single/src/main/java/com/emotion/entity/UserStats.java @@ -2,11 +2,11 @@ package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; +import lombok.experimental.SuperBuilder; +import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; /** * 用户统计实体类 @@ -16,7 +16,7 @@ import lombok.NoArgsConstructor; */ @Data @EqualsAndHashCode(callSuper = true) -@Builder +@SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("user_stats") diff --git a/backend-single/src/main/java/com/emotion/service/AiChatService.java b/backend-single/src/main/java/com/emotion/service/AiChatService.java index 70bceac..495ea3f 100644 --- a/backend-single/src/main/java/com/emotion/service/AiChatService.java +++ b/backend-single/src/main/java/com/emotion/service/AiChatService.java @@ -1,76 +1,110 @@ package com.emotion.service; -import java.util.Map; /** * AI聊天服务接口 - * + * * @author emotion-museum * @date 2025-07-24 */ public interface AiChatService { /** - * 发送聊天消息 + * 发送聊天消息(保存用户消息和AI回复) + * @param conversationId 会话ID + * @param message 用户消息内容 + * @param userId 用户ID + * @return AI回复内容 */ String sendChatMessage(String conversationId, String message, String userId); /** - * 发送聊天消息(仅获取AI回复,不保存用户消息) - * 用于WebSocket场景,避免重复保存用户消息 + * WebSocket方式发送聊天消息(只保存AI回复) + * @param conversationId 会话ID + * @param message 用户消息内容 + * @param userId 用户ID + * @return AI回复内容 */ String sendChatMessageForWebSocket(String conversationId, String message, String userId); /** * 生成对话总结 + * @param conversationId 会话ID + * @param userId 用户ID + * @return 总结内容 */ String generateConversationSummary(String conversationId, String userId); /** - * 检查服务是否可用 + * 检查AI服务是否可用 + * @return 可用返回true,否则false */ boolean isServiceAvailable(); /** - * 获取服务状态 + * 获取AI服务状态 + * @return "available" 或 "unavailable" */ String getServiceStatus(); /** - * 发送消息到Coze AI + * 发送消息到Coze AI(不保存消息,仅AI交互) + * @param conversationId 会话ID + * @param userMessage 用户消息内容 + * @param userId 用户ID + * @return AI回复内容 */ String sendMessage(String conversationId, String userMessage, String userId); /** - * 访客聊天(不需要登录) + * 访客聊天(不登录情况下) + * @param message 用户消息内容 + * @param clientIp 客户端IP + * @return 包含AI回复等信息的Map */ - Map guestChat(String message, String clientIp); + java.util.Map guestChat(String message, String clientIp); /** - * 创建对话 + * 创建新对话 + * @param userId 用户ID + * @param title 对话标题 + * @return 包含对话信息的Map */ - Map createConversation(String userId, String title); + java.util.Map createConversation(String userId, String title); /** * 获取访客用户信息 + * @param clientIp 客户端IP + * @return 包含访客信息的Map */ - Map getGuestUserInfo(String clientIp); + java.util.Map getGuestUserInfo(String clientIp); /** - * 流式聊天 + * 流式聊天(暂时降级为普通聊天) + * @param conversationId 会话ID + * @param message 用户消息内容 + * @param userId 用户ID + * @return AI回复内容 */ String streamChat(String conversationId, String message, String userId); /** * 健康检查 + * @return 健康返回true,否则false */ boolean healthCheck(); /** - * 生成用户当天的情绪记录总结 - * + * 生成用户情绪记录总结 * @param userId 用户ID - * @return 情绪记录结果 + * @return 包含情绪总结等信息的Map */ - Map generateEmotionSummary(String userId); + java.util.Map generateEmotionSummary(String userId); + + /** + * 异步生成用户情绪记录总结 + * @param userId 用户ID + * @return 包含情绪总结等信息的CompletableFuture + */ + java.util.concurrent.CompletableFuture> generateEmotionSummaryAsync(String userId); } diff --git a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java index 20619af..bc2d68e 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java @@ -24,6 +24,8 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.scheduling.annotation.Async; +import java.util.concurrent.CompletableFuture; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; @@ -34,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.Arrays; /** * AI聊天服务实现类 @@ -1017,12 +1020,12 @@ public class AiChatServiceImpl implements AiChatService { String chatHistory = integrateChatHistory(todayMessages); log.info("聊天记录整合完成,总长度: {}", chatHistory.length()); - // 构建情绪分析提示词 - String emotionPrompt = buildEmotionAnalysisPrompt(chatHistory); + // Coze 中已经在工作流设置了提示词,目前不需要构建情绪分析提示词 + // String emotionPrompt = buildEmotionAnalysisPrompt(chatHistory); // 调用Coze API进行情绪分析总结 String conversationId = "emotion_summary_" + userId + "_" + today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); - String emotionSummary = sendSummaryMessage(conversationId, emotionPrompt, userId); + String emotionSummary = sendSummaryMessage(conversationId, chatHistory, userId); log.info("情绪分析总结生成完成: {}", emotionSummary); // 解析AI返回的情绪分析结果 @@ -1049,6 +1052,13 @@ public class AiChatServiceImpl implements AiChatService { return result; } + @Override + @Async("taskExecutor") + public CompletableFuture> generateEmotionSummaryAsync(String userId) { + Map result = generateEmotionSummary(userId); + return CompletableFuture.completedFuture(result); + } + /** * 整合聊天记录 */ @@ -1106,7 +1116,7 @@ public class AiChatServiceImpl implements AiChatService { return EmotionAnalysis.builder() .primaryEmotion(json.getString("primaryEmotion")) .intensity(BigDecimal.valueOf(json.getDoubleValue("intensity"))) - .keywords(json.getString("triggers")) + .keywords(JSON.toJSONString(Arrays.asList(json.getString("triggers")))) .suggestion(json.getString("suggestions")) .text(summary) .polarity(determinePolarity(json.getString("primaryEmotion"))) @@ -1122,7 +1132,7 @@ public class AiChatServiceImpl implements AiChatService { return EmotionAnalysis.builder() .primaryEmotion("平静") .intensity(BigDecimal.valueOf(0.5)) - .keywords("日常对话") + .keywords(JSON.toJSONString(Arrays.asList("日常对话"))) .suggestion("保持当前的积极状态") .text(summary) .polarity("neutral") @@ -1192,7 +1202,7 @@ public class AiChatServiceImpl implements AiChatService { .triggers(analysisResult.getKeywords()) .description(analysisResult.getText()) .notes("基于当天聊天记录自动生成的情绪分析") - .tags("AI分析,聊天记录,情绪总结") + .tags(JSON.toJSONString(Arrays.asList("AI分析", "聊天记录", "情绪总结"))) .build(); emotionRecordService.save(record); diff --git a/backend-single/src/main/resources/application.yml b/backend-single/src/main/resources/application.yml index 806da19..25e2e44 100644 --- a/backend-single/src/main/resources/application.yml +++ b/backend-single/src/main/resources/application.yml @@ -103,3 +103,10 @@ emotion: - /api/actuator/** - /api/websocket/** - /api/ai/guest/** + +async: + core-pool-size: 10 + max-pool-size: 50 + queue-capacity: 200 + keep-alive-seconds: 60 + thread-name-prefix: single-async- diff --git a/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql b/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql index eee3f29..97dc624 100644 --- a/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql +++ b/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql @@ -74,7 +74,7 @@ DROP TABLE IF EXISTS user; -- 1. 用户表 (user) -- ============================================================================ CREATE TABLE user ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 account VARCHAR(50) UNIQUE COMMENT '账号', -- 账号 password VARCHAR(255) COMMENT '密码(加密后)', -- 密码(加密后) username VARCHAR(50) UNIQUE COMMENT '用户名', -- 用户名 @@ -101,9 +101,9 @@ CREATE TABLE user ( third_party_id VARCHAR(128) COMMENT '第三方平台ID', -- 第三方平台ID third_party_type VARCHAR(32) COMMENT '第三方平台类型: wechat, qq, wechat-mp', -- 第三方平台类型: wechat, qq, wechat-mp -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -114,8 +114,8 @@ CREATE TABLE user ( -- 关联说明: user_id 关联 user.id,通过代码逻辑维护关联关系 -- ============================================================================ CREATE TABLE conversation ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - user_id VARCHAR(36) COMMENT '用户ID (关联user.id)', -- 用户ID (关联user.id) + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(64) COMMENT '用户ID (关联user.id)', -- 用户ID (关联user.id) user_type VARCHAR(20) DEFAULT 'registered' COMMENT '用户类型: registered-注册用户, guest-访客用户', -- 用户类型: registered-注册用户, guest-访客用户 title VARCHAR(200) COMMENT '对话标题', -- 对话标题 type VARCHAR(50) DEFAULT 'emotion_chat' COMMENT '对话类型', -- 对话类型 @@ -143,9 +143,9 @@ CREATE TABLE conversation ( tags JSON COMMENT '标签', -- 标签 metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -156,8 +156,8 @@ CREATE TABLE conversation ( -- 关联说明: conversation_id 关联 conversation.id,通过代码逻辑维护关联关系 -- ============================================================================ CREATE TABLE message ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - conversation_id VARCHAR(36) COMMENT '对话ID (关联conversation.id)', -- 对话ID (关联conversation.id) + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + conversation_id VARCHAR(64) COMMENT '对话ID (关联conversation.id)', -- 对话ID (关联conversation.id) content TEXT COMMENT '消息内容', -- 消息内容 type VARCHAR(50) DEFAULT 'text' COMMENT '消息类型', -- 消息类型 sender VARCHAR(20) COMMENT '发送者: user-用户, assistant-AI助手', -- 发送者: user-用户, assistant-AI助手 @@ -174,17 +174,17 @@ CREATE TABLE message ( total_tokens INT DEFAULT 0 COMMENT '总Token数', -- 总Token数 api_cost DECIMAL(10, 6) DEFAULT 0.000000 COMMENT 'API调用费用', -- API调用费用 is_read TINYINT DEFAULT 0 COMMENT '是否已读: 0-未读, 1-已读', -- 是否已读: 0-未读, 1-已读 - parent_message_id VARCHAR(36) COMMENT '父消息ID(用于回复链)', -- 父消息ID(用于回复链) + parent_message_id VARCHAR(64) COMMENT '父消息ID(用于回复链)', -- 父消息ID(用于回复链) emotion_analysis JSON COMMENT '情绪分析结果', -- 情绪分析结果 metadata JSON COMMENT '扩展元数据', -- 扩展元数据 - user_id VARCHAR(36) COMMENT '用户ID (注册用户或访客用户)', -- 用户ID (注册用户或访客用户) + user_id VARCHAR(64) COMMENT '用户ID (注册用户或访客用户)', -- 用户ID (注册用户或访客用户) user_type VARCHAR(20) COMMENT '用户类型 (registered/guest)', -- 用户类型 (registered/guest) coze_role VARCHAR(20) COMMENT 'Coze消息角色 (user/assistant/system)', -- Coze消息角色 (user/assistant/system) coze_content_type VARCHAR(50) COMMENT 'Coze消息内容类型 (text/image/file等)', -- Coze消息内容类型 (text/image/file等) -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -194,15 +194,15 @@ CREATE TABLE message ( -- 4. Coze API调用记录表 (coze_api_call) - 优化版本 -- ============================================================================ CREATE TABLE coze_api_call ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - conversation_id VARCHAR(36) COMMENT '对话ID', -- 对话ID - message_id VARCHAR(36) COMMENT '消息ID', -- 消息ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + conversation_id VARCHAR(64) COMMENT '对话ID', -- 对话ID + message_id VARCHAR(64) COMMENT '消息ID', -- 消息ID -- Coze API 信息 coze_chat_id VARCHAR(50) COMMENT 'Coze聊天ID', -- Coze聊天ID coze_conversation_id VARCHAR(50) COMMENT 'Coze对话ID', -- Coze对话ID bot_id VARCHAR(50) COMMENT 'Bot ID', -- Bot ID workflow_id VARCHAR(50) COMMENT 'Workflow ID', -- Workflow ID - user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + user_id VARCHAR(64) COMMENT '用户ID', -- 用户ID -- 请求信息 request_type VARCHAR(20) COMMENT '请求类型: chat/stream/retrieve/messages', -- 请求类型: chat/stream/retrieve/messages request_url VARCHAR(500) COMMENT '请求URL', -- 请求URL @@ -246,9 +246,9 @@ CREATE TABLE coze_api_call ( trace_id VARCHAR(100) COMMENT '追踪ID', -- 追踪ID metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -258,9 +258,9 @@ CREATE TABLE coze_api_call ( -- 5. 情绪分析表 (emotion_analysis) -- ============================================================================ CREATE TABLE emotion_analysis ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID - message_id VARCHAR(36) COMMENT '关联消息ID', -- 关联消息ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(64) COMMENT '用户ID', -- 用户ID + message_id VARCHAR(64) COMMENT '关联消息ID', -- 关联消息ID text TEXT COMMENT '分析文本', -- 分析文本 primary_emotion VARCHAR(50) COMMENT '主要情绪', -- 主要情绪 intensity DECIMAL(3, 2) COMMENT '情绪强度', -- 情绪强度 @@ -272,9 +272,9 @@ CREATE TABLE emotion_analysis ( analysis_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '分析时间', -- 分析时间 metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -284,8 +284,8 @@ CREATE TABLE emotion_analysis ( -- 6. 情绪记录表 (emotion_record) -- ============================================================================ CREATE TABLE emotion_record ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(64) COMMENT '用户ID', -- 用户ID record_date DATE COMMENT '记录日期', -- 记录日期 emotion_type VARCHAR(50) COMMENT '情绪类型', -- 情绪类型 intensity DECIMAL(3, 2) COMMENT '情绪强度', -- 情绪强度 @@ -298,9 +298,9 @@ CREATE TABLE emotion_record ( people VARCHAR(200) COMMENT '相关人物', -- 相关人物 notes TEXT COMMENT '备注', -- 备注 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -310,7 +310,7 @@ CREATE TABLE emotion_record ( -- 7. 成长课题表 (growth_topic) -- ============================================================================ CREATE TABLE growth_topic ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 title VARCHAR(100) COMMENT '课题标题', -- 课题标题 category VARCHAR(50) COMMENT '分类', -- 分类 difficulty VARCHAR(20) COMMENT '难度: easy-简单, medium-中等, hard-困难', -- 难度: easy-简单, medium-中等, hard-困难 @@ -323,9 +323,9 @@ CREATE TABLE growth_topic ( completed_time DATETIME COMMENT '完成时间', -- 完成时间 rewards JSON COMMENT '奖励', -- 奖励 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -335,8 +335,8 @@ CREATE TABLE growth_topic ( -- 8. 课题互动表 (topic_interaction) -- ============================================================================ CREATE TABLE topic_interaction ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - topic_id VARCHAR(36) COMMENT '课题ID', -- 课题ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + topic_id VARCHAR(64) COMMENT '课题ID', -- 课题ID type VARCHAR(50) COMMENT '互动类型', -- 互动类型 content TEXT COMMENT '内容', -- 内容 user_input TEXT COMMENT '用户输入', -- 用户输入 @@ -345,9 +345,9 @@ CREATE TABLE topic_interaction ( feedback TEXT COMMENT '反馈', -- 反馈 completed_time DATETIME COMMENT '完成时间', -- 完成时间 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -357,7 +357,7 @@ CREATE TABLE topic_interaction ( -- 9. 地点标记表 (location_pin) -- ============================================================================ CREATE TABLE location_pin ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 name VARCHAR(100) COMMENT '地点名称', -- 地点名称 type VARCHAR(50) COMMENT '地点类型', -- 地点类型 category VARCHAR(50) COMMENT '地点分类', -- 地点分类 @@ -365,15 +365,15 @@ CREATE TABLE location_pin ( longitude DECIMAL(11, 8) COMMENT '经度', -- 经度 address VARCHAR(200) COMMENT '地址', -- 地址 description TEXT COMMENT '描述', -- 描述 - created_by VARCHAR(36) COMMENT '创建者', -- 创建者 + created_by VARCHAR(64) COMMENT '创建者', -- 创建者 likes INT DEFAULT 0 COMMENT '点赞数', -- 点赞数 visits INT DEFAULT 0 COMMENT '访问数', -- 访问数 is_bookmarked TINYINT DEFAULT 0 COMMENT '是否收藏', -- 是否收藏 last_visit_time DATETIME COMMENT '最后访问时间', -- 最后访问时间 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -383,9 +383,9 @@ CREATE TABLE location_pin ( -- 10. 社区帖子表 (community_post) -- ============================================================================ CREATE TABLE community_post ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID - location_id VARCHAR(36) COMMENT '地点ID', -- 地点ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(64) COMMENT '用户ID', -- 用户ID + location_id VARCHAR(64) COMMENT '地点ID', -- 地点ID title VARCHAR(200) COMMENT '标题', -- 标题 content TEXT COMMENT '内容', -- 内容 type VARCHAR(50) COMMENT '帖子类型', -- 帖子类型 @@ -396,9 +396,9 @@ CREATE TABLE community_post ( comment_count INT DEFAULT 0 COMMENT '评论数', -- 评论数 is_private TINYINT DEFAULT 0 COMMENT '是否私密', -- 是否私密 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -408,16 +408,16 @@ CREATE TABLE community_post ( -- 11. 评论表 (comment) -- ============================================================================ CREATE TABLE comment ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - post_id VARCHAR(36) COMMENT '帖子ID', -- 帖子ID - user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + post_id VARCHAR(64) COMMENT '帖子ID', -- 帖子ID + user_id VARCHAR(64) COMMENT '用户ID', -- 用户ID content TEXT COMMENT '评论内容', -- 评论内容 - reply_to_id VARCHAR(36) COMMENT '回复的评论ID', -- 回复的评论ID + reply_to_id VARCHAR(64) COMMENT '回复的评论ID', -- 回复的评论ID likes INT DEFAULT 0 COMMENT '点赞数', -- 点赞数 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -427,7 +427,7 @@ CREATE TABLE comment ( -- 12. 成就表 (achievement) -- ============================================================================ CREATE TABLE achievement ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 title VARCHAR(100) COMMENT '成就标题', -- 成就标题 description TEXT COMMENT '描述', -- 描述 category VARCHAR(50) COMMENT '分类', -- 分类 @@ -440,9 +440,9 @@ CREATE TABLE achievement ( progress DECIMAL(5, 2) DEFAULT 0.00 COMMENT '进度', -- 进度 is_hidden TINYINT DEFAULT 0 COMMENT '是否隐藏', -- 是否隐藏 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -452,9 +452,9 @@ CREATE TABLE achievement ( -- 13. 奖励表 (reward) -- ============================================================================ CREATE TABLE reward ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - topic_id VARCHAR(36) COMMENT '课题ID', -- 课题ID - achievement_id VARCHAR(36) COMMENT '成就ID', -- 成就ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + topic_id VARCHAR(64) COMMENT '课题ID', -- 课题ID + achievement_id VARCHAR(64) COMMENT '成就ID', -- 成就ID type VARCHAR(50) COMMENT '奖励类型', -- 奖励类型 name VARCHAR(100) COMMENT '奖励名称', -- 奖励名称 description TEXT COMMENT '描述', -- 描述 @@ -464,9 +464,9 @@ CREATE TABLE reward ( earned_time DATETIME COMMENT '获得时间', -- 获得时间 is_new TINYINT DEFAULT 1 COMMENT '是否新获得', -- 是否新获得 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -476,7 +476,7 @@ CREATE TABLE reward ( -- 14. 访客用户表 (guest_user) -- ============================================================================ CREATE TABLE guest_user ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 guest_user_id VARCHAR(50) UNIQUE COMMENT '访客用户ID (格式: guest_xxx)', -- 访客用户ID (格式: guest_xxx) ip_address VARCHAR(45) COMMENT '客户端IP地址 (支持IPv6)', -- 客户端IP地址 (支持IPv6) user_agent TEXT COMMENT '用户代理信息', -- 用户代理信息 @@ -488,9 +488,9 @@ CREATE TABLE guest_user ( location VARCHAR(100) COMMENT 'IP地址的地理位置信息', -- IP地址的地理位置信息 device_info VARCHAR(200) COMMENT '设备信息', -- 设备信息 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 @@ -500,8 +500,8 @@ CREATE TABLE guest_user ( -- 15. 用户统计表 (user_stats) -- ============================================================================ CREATE TABLE user_stats ( - id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 - user_id VARCHAR(36) UNIQUE COMMENT '用户ID', -- 用户ID + id VARCHAR(64) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(64) UNIQUE COMMENT '用户ID', -- 用户ID total_conversations INT DEFAULT 0 COMMENT '总对话数', -- 总对话数 total_messages INT DEFAULT 0 COMMENT '总消息数', -- 总消息数 total_emotions_recorded INT DEFAULT 0 COMMENT '总情绪记录数', -- 总情绪记录数 @@ -516,9 +516,9 @@ CREATE TABLE user_stats ( likes_received INT DEFAULT 0 COMMENT '收到的点赞数', -- 收到的点赞数 social_interactions INT DEFAULT 0 COMMENT '社交互动数', -- 社交互动数 -- 公共字段 - create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_by VARCHAR(64) COMMENT '创建人ID', -- 创建人ID create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 - update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_by VARCHAR(64) COMMENT '更新人ID', -- 更新人ID update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 remarks VARCHAR(500) COMMENT '备注' -- 备注 diff --git a/chat-history-fix-summary.md b/chat-history-fix-summary.md deleted file mode 100644 index 64d2e44..0000000 --- a/chat-history-fix-summary.md +++ /dev/null @@ -1,87 +0,0 @@ -# 聊天记录加载问题修复总结 - -## 问题描述 -用户在聊天页面右上角点击聊天记录按钮时,无法正确加载聊天记录。 - -## 问题根源分析 -通过代码分析发现问题的根本原因: - -### 1. 拦截器配置问题 -- **WebMvcConfig**: 配置了JwtAuthInterceptor,但只拦截`/api/**`路径 -- **WebConfig**: 配置了AuthInterceptor和UserContextInterceptor,拦截`/**`路径 -- **MessageController**: 路径是`/message`,不是`/api/message` - -### 2. 用户上下文设置问题 -- **AuthInterceptor**: 验证token并将用户信息存储到请求属性中 -- **UserContextInterceptor**: 负责设置UserContextHolder,但没有从请求属性中获取用户信息 -- **CurrentUserUtil**: 从UserContextHolder中获取用户ID,但UserContextHolder中没有正确的用户信息 - -## 修复方案 - -### 1. 前端错误处理改进 -**文件**: `web/src/views/Chat/index.vue` -- 在`loadHistoryMessages`和`searchHistoryMessages`方法中添加详细的日志输出 -- 改进错误处理,区分不同类型的错误(401认证失败、500服务器错误等) - -**文件**: `web/src/services/api.ts` -- 改进响应拦截器的错误处理 -- 为业务错误码401添加特殊处理 -- 增加详细的错误日志输出 - -### 2. 后端认证链修复 -**文件**: `backend-single/src/main/java/com/emotion/interceptor/AuthInterceptor.java` -- 添加从token中获取username并存储到请求属性中 - -**文件**: `backend-single/src/main/java/com/emotion/interceptor/UserContextInterceptor.java` -- 修改`getUserIdFromRequest`方法,优先从请求属性中获取用户ID -- 修改`getUsernameFromRequest`方法,优先从请求属性中获取用户名 - -### 3. 后端错误处理改进 -**文件**: `backend-single/src/main/java/com/emotion/controller/MessageController.java` -- 在所有用户消息相关的API中添加详细的日志输出 -- 改进异常处理,区分认证失败和其他错误 -- 返回更友好的错误信息 - -## 修复后的认证流程 - -1. **请求到达**: 用户发送带有Authorization头的请求到`/message/user/page` -2. **AuthInterceptor处理**: - - 验证JWT token - - 从token中提取userId和username - - 将用户信息存储到请求属性中 -3. **UserContextInterceptor处理**: - - 从请求属性中获取用户信息 - - 设置到UserContextHolder中 -4. **Controller处理**: - - 通过CurrentUserUtil.requireCurrentUserId()获取用户ID - - 查询用户的聊天记录 - - 返回结果 - -## 测试验证 - -### 1. 使用测试工具 -- 打开`test-chat-history-api.html` -- 输入有效的JWT token -- 测试各个API接口 - -### 2. 前端测试 -- 登录用户账号 -- 点击聊天页面右上角的聊天记录按钮 -- 检查浏览器控制台的日志输出 -- 验证聊天记录是否正确加载 - -### 3. 后端日志检查 -- 查看后端日志中的用户认证和上下文设置信息 -- 确认用户ID正确传递到Controller层 - -## 预期结果 -- 聊天记录弹窗能够正确打开 -- 用户的历史消息能够正确加载和显示 -- 搜索功能正常工作 -- 分页功能正常工作 - -## 注意事项 -1. 确保前端localStorage中有有效的token -2. 确保后端服务正常运行 -3. 如果仍有问题,检查数据库中是否有用户的消息记录 -4. 可以使用提供的测试工具进行API级别的调试 diff --git a/debug-chat-history.md b/debug-chat-history.md deleted file mode 100644 index 5741e17..0000000 --- a/debug-chat-history.md +++ /dev/null @@ -1,147 +0,0 @@ -# 聊天记录调试指南 - -## 问题总结 - -1. **Controller层优化完成**: - - 移除了业务逻辑,业务代码移到Service层 - - 使用专门的Request和Response DTO - - 统一使用Result返回格式 - -2. **前端调用问题修复**: - - 添加了缺失的`useUserStore`导入 - - 修正了API调用方法(搜索和最近消息改为POST) - -## 调试步骤 - -### 1. 检查前端基础环境 -在浏览器控制台运行: -```javascript -// 检查token -console.log('Token:', localStorage.getItem('token')) - -// 检查用户信息 -console.log('User store:', window.Vue?.config?.globalProperties?.$stores?.user) - -// 检查API基础配置 -console.log('API Base URL:', 'http://localhost:8080') // 根据实际情况修改 -``` - -### 2. 手动测试API调用 -在浏览器控制台运行: -```javascript -// 测试获取用户消息分页 -fetch('/message/user/page?current=1&size=5', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'Content-Type': 'application/json' - } -}) -.then(res => res.json()) -.then(data => console.log('分页API结果:', data)) -.catch(err => console.error('分页API错误:', err)) - -// 测试搜索消息 -fetch('/message/user/search', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ keyword: '测试', limit: 5 }) -}) -.then(res => res.json()) -.then(data => console.log('搜索API结果:', data)) -.catch(err => console.error('搜索API错误:', err)) - -// 测试获取最近消息 -fetch('/message/user/recent', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ limit: 5 }) -}) -.then(res => res.json()) -.then(data => console.log('最近消息API结果:', data)) -.catch(err => console.error('最近消息API错误:', err)) -``` - -### 3. 检查聊天记录按钮事件 -在聊天页面控制台运行: -```javascript -// 查找聊天记录按钮 -const historyButton = document.querySelector('.header-right .action-btn') -console.log('聊天记录按钮:', historyButton) - -// 检查点击事件 -if (historyButton) { - historyButton.addEventListener('click', () => { - console.log('聊天记录按钮被点击') - }) -} - -// 检查抽屉状态 -const drawer = document.querySelector('.history-drawer') -console.log('聊天记录抽屉:', drawer) -``` - -### 4. 检查Vue组件状态 -在聊天页面控制台运行: -```javascript -// 检查Vue实例 -const app = document.querySelector('#app').__vue__ -console.log('Vue app:', app) - -// 检查聊天记录相关的响应式数据 -console.log('showHistory:', app?.setupState?.showHistory?.value) -console.log('historyMessages:', app?.setupState?.historyMessages?.value) -console.log('historyLoading:', app?.setupState?.historyLoading?.value) -``` - -## 常见问题排查 - -### 1. Token问题 -- 检查localStorage中是否有token -- 检查token是否过期 -- 检查token格式是否正确 - -### 2. 网络请求问题 -- 检查浏览器Network面板 -- 检查是否有CORS错误 -- 检查API路径是否正确 - -### 3. 后端认证问题 -- 检查后端日志中的认证信息 -- 检查UserContextHolder是否正确设置 -- 检查CurrentUserUtil.requireCurrentUserId()是否抛出异常 - -### 4. 前端组件问题 -- 检查Vue组件是否正确挂载 -- 检查响应式数据是否正确初始化 -- 检查事件绑定是否正确 - -## 修复后的API接口 - -### 后端接口 -- `GET /message/user/page` - 获取用户消息分页 -- `POST /message/user/search` - 搜索用户消息 -- `POST /message/user/recent` - 获取用户最近消息 - -### 前端调用 -```javascript -// 获取分页消息 -messageApi.getUserMessages(1, 20) - -// 搜索消息 -messageApi.searchUserMessages('关键词', 50) - -// 获取最近消息 -messageApi.getRecentMessages(10) -``` - -## 预期结果 -- 点击聊天记录按钮后,抽屉正常打开 -- 控制台显示API调用日志 -- 聊天记录正确加载和显示 -- 搜索功能正常工作 diff --git a/package-lock.json b/package-lock.json index e2d0b50..1e081f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "EmotionMuseum", + "name": "emotion-museun", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/test-chat-history-api.html b/test-chat-history-api.html deleted file mode 100644 index 1bb8380..0000000 --- a/test-chat-history-api.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - 聊天记录API测试 - - - -

聊天记录API测试工具

- -
-

认证信息

-
- - -
-
- - -
- -
- -
-

API测试

- - - - - -
- - -
-
- -
-

测试结果

-
等待测试...
-
- - - - diff --git a/test-chat-history-complete.js b/test-chat-history-complete.js deleted file mode 100644 index 8fd5004..0000000 --- a/test-chat-history-complete.js +++ /dev/null @@ -1,238 +0,0 @@ -// 完整的聊天记录功能测试脚本 -// 在聊天页面的浏览器控制台中运行 - -(function() { - console.log('=== 聊天记录功能完整测试 ==='); - - // 1. 检查基础环境 - function checkBasicEnvironment() { - console.log('\n1. 检查基础环境:'); - - const token = localStorage.getItem('token'); - console.log('- Token存在:', !!token); - - if (!token) { - console.error('❌ 没有找到token,请先登录'); - return false; - } - - console.log('- 当前页面:', window.location.pathname); - console.log('- Token长度:', token.length); - - return true; - } - - // 2. 测试API调用 - async function testAPIEndpoints() { - console.log('\n2. 测试API端点:'); - - const token = localStorage.getItem('token'); - const baseURL = window.location.origin; // 使用当前域名 - - const headers = { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }; - - // 测试分页API - try { - console.log('- 测试分页API...'); - const pageResponse = await fetch(`${baseURL}/message/user/page?current=1&size=5`, { - method: 'GET', - headers - }); - - console.log(' 状态码:', pageResponse.status); - const pageData = await pageResponse.json(); - console.log(' 响应数据:', pageData); - - if (pageResponse.ok && pageData.code === 200) { - console.log(' ✅ 分页API成功'); - } else { - console.log(' ❌ 分页API失败:', pageData.message); - } - } catch (error) { - console.log(' ❌ 分页API错误:', error.message); - } - - // 测试搜索API - try { - console.log('- 测试搜索API...'); - const searchResponse = await fetch(`${baseURL}/message/user/search`, { - method: 'POST', - headers, - body: JSON.stringify({ keyword: '测试', limit: 5 }) - }); - - console.log(' 状态码:', searchResponse.status); - const searchData = await searchResponse.json(); - console.log(' 响应数据:', searchData); - - if (searchResponse.ok && searchData.code === 200) { - console.log(' ✅ 搜索API成功'); - } else { - console.log(' ❌ 搜索API失败:', searchData.message); - } - } catch (error) { - console.log(' ❌ 搜索API错误:', error.message); - } - - // 测试最近消息API - try { - console.log('- 测试最近消息API...'); - const recentResponse = await fetch(`${baseURL}/message/user/recent`, { - method: 'POST', - headers, - body: JSON.stringify({ limit: 5 }) - }); - - console.log(' 状态码:', recentResponse.status); - const recentData = await recentResponse.json(); - console.log(' 响应数据:', recentData); - - if (recentResponse.ok && recentData.code === 200) { - console.log(' ✅ 最近消息API成功'); - } else { - console.log(' ❌ 最近消息API失败:', recentData.message); - } - } catch (error) { - console.log(' ❌ 最近消息API错误:', error.message); - } - } - - // 3. 检查前端组件 - function checkFrontendComponents() { - console.log('\n3. 检查前端组件:'); - - // 检查聊天记录按钮 - const historyButton = document.querySelector('.header-right .action-btn'); - console.log('- 聊天记录按钮:', !!historyButton); - - if (!historyButton) { - console.log(' ❌ 未找到聊天记录按钮'); - return false; - } - - // 检查Vue实例 - const app = document.querySelector('#app'); - const vueInstance = app?.__vue__ || app?._vnode?.component?.ctx; - console.log('- Vue实例:', !!vueInstance); - - if (vueInstance) { - // 检查响应式数据 - const setupState = vueInstance.setupState || vueInstance.$data; - console.log('- showHistory:', setupState?.showHistory?.value); - console.log('- historyLoading:', setupState?.historyLoading?.value); - console.log('- historyMessages长度:', setupState?.historyMessages?.value?.length || 0); - } - - return true; - } - - // 4. 模拟点击测试 - function simulateClickTest() { - console.log('\n4. 模拟点击测试:'); - - const historyButton = document.querySelector('.header-right .action-btn'); - if (!historyButton) { - console.log('❌ 无法进行点击测试,按钮不存在'); - return; - } - - console.log('- 模拟点击聊天记录按钮...'); - - // 添加事件监听器来监控点击 - let clickDetected = false; - const clickHandler = () => { - clickDetected = true; - console.log(' ✅ 检测到点击事件'); - }; - - historyButton.addEventListener('click', clickHandler, { once: true }); - - // 模拟点击 - historyButton.click(); - - // 检查抽屉是否打开 - setTimeout(() => { - const drawer = document.querySelector('.ant-drawer'); - const drawerVisible = drawer && !drawer.classList.contains('ant-drawer-hidden'); - - console.log('- 抽屉是否可见:', drawerVisible); - - if (clickDetected && drawerVisible) { - console.log(' ✅ 聊天记录功能正常'); - } else { - console.log(' ❌ 聊天记录功能异常'); - if (!clickDetected) console.log(' - 点击事件未触发'); - if (!drawerVisible) console.log(' - 抽屉未显示'); - } - - // 清理事件监听器 - historyButton.removeEventListener('click', clickHandler); - }, 1000); - } - - // 5. 检查网络请求 - function monitorNetworkRequests() { - console.log('\n5. 监控网络请求:'); - - // 重写fetch来监控请求 - const originalFetch = window.fetch; - const requests = []; - - window.fetch = function(...args) { - const url = args[0]; - if (typeof url === 'string' && url.includes('/message/user/')) { - console.log('- 检测到消息API请求:', url); - requests.push({ url, timestamp: Date.now() }); - } - return originalFetch.apply(this, args); - }; - - // 5秒后恢复原始fetch并报告结果 - setTimeout(() => { - window.fetch = originalFetch; - console.log('- 监控期间的API请求数量:', requests.length); - requests.forEach(req => { - console.log(` ${new Date(req.timestamp).toLocaleTimeString()}: ${req.url}`); - }); - }, 5000); - } - - // 主测试函数 - async function runCompleteTest() { - try { - // 检查基础环境 - if (!checkBasicEnvironment()) { - return; - } - - // 开始监控网络请求 - monitorNetworkRequests(); - - // 测试API端点 - await testAPIEndpoints(); - - // 检查前端组件 - checkFrontendComponents(); - - // 模拟点击测试 - simulateClickTest(); - - console.log('\n=== 测试完成 ==='); - console.log('请查看上述结果,如果API测试成功但前端功能异常,请检查:'); - console.log('1. Vue组件是否正确挂载'); - console.log('2. 事件绑定是否正确'); - console.log('3. 响应式数据是否正确更新'); - console.log('4. 是否有JavaScript错误'); - - } catch (error) { - console.error('测试过程中发生错误:', error); - } - } - - // 运行测试 - runCompleteTest(); - -})(); diff --git a/test-duplicate-message-fix.md b/test-duplicate-message-fix.md deleted file mode 100644 index e8dfe86..0000000 --- a/test-duplicate-message-fix.md +++ /dev/null @@ -1,69 +0,0 @@ -# 重复消息问题修复测试 - -## 问题描述 -用户在聊天页面发送一条消息时,数据库中保存了两条相同内容的用户消息: -1. 第一条:通过WebSocket处理器保存,包含完整的用户信息 -2. 第二条:通过REST API保存,缺少部分用户信息 - -## 根本原因 -前端`sendMessage`方法中存在双重保存机制: -1. WebSocket发送 - 后端WebSocket处理器会保存消息 -2. REST API调用 - 前端额外调用`chatApi.createMessage`保存消息 - -## 修复方案 -1. **前端修复**:移除前端`chat.ts`中`sendMessage`方法里的`chatApi.createMessage`调用,只保留WebSocket发送 -2. **后端修复**:创建专门的WebSocket方法`sendChatMessageForWebSocket`,避免重复保存用户消息 - -## 修复内容 -### 前端修改 -文件:`web/src/stores/chat.ts` -- 移除了第69-82行的REST API调用 -- 添加了注释说明修复原因 -- 保留WebSocket发送逻辑 - -### 后端修改 -文件:`backend-single/src/main/java/com/emotion/service/AiChatService.java` -- 新增`sendChatMessageForWebSocket`方法接口 - -文件:`backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java` -- 实现`sendChatMessageForWebSocket`方法,只保存AI回复,不保存用户消息 - -文件:`backend-single/src/main/java/com/emotion/service/WebSocketService.java` -- 修改WebSocket处理器调用新的`sendChatMessageForWebSocket`方法 - -## 测试步骤 -1. 启动后端服务 -2. 启动前端服务 -3. 登录用户账号 -4. 发送一条测试消息 -5. 检查数据库中是否只有一条用户消息记录 - -## 预期结果 -- 数据库中只保存一条用户消息 -- 消息包含完整的用户信息(user_id, user_type, coze_role等) -- AI回复正常工作 -- 前端显示正常 - -## 验证SQL -```sql --- 查看最新的消息记录 -SELECT id, conversation_id, content, sender, user_id, user_type, coze_role, create_by, create_time -FROM message -WHERE conversation_id = '你的会话ID' -ORDER BY create_time DESC -LIMIT 10; - --- 检查是否还有重复消息 -SELECT content, COUNT(*) as count -FROM message -WHERE conversation_id = '你的会话ID' - AND sender = 'user' - AND create_time > '2025-07-25 16:00:00' -GROUP BY content -HAVING COUNT(*) > 1; -``` - -## 注意事项 -- 此修复只影响新发送的消息 -- 历史重复消息需要手动清理 -- 确保WebSocket连接正常工作 diff --git a/test-message-api.html b/test-message-api.html deleted file mode 100644 index 7bf0158..0000000 --- a/test-message-api.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - 消息API测试 - - - -

消息API测试

- -
-

Token设置

- - -
- -
-

API测试

- - - -
- -
-

测试结果

-
等待测试...
-
- - - - diff --git a/test-message-history-api.html b/test-message-history-api.html deleted file mode 100644 index 23f3d0b..0000000 --- a/test-message-history-api.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - 消息历史API测试 - - - -

消息历史API测试

- -
-

Token设置

- - - -
- -
-

API测试

- - - - -
- -
-

测试结果

-
等待测试...
-
- - - - diff --git a/verify-chat-history-fix.js b/verify-chat-history-fix.js deleted file mode 100644 index 9dc2c04..0000000 --- a/verify-chat-history-fix.js +++ /dev/null @@ -1,174 +0,0 @@ -// 聊天记录修复验证脚本 -// 在浏览器控制台中运行此脚本来验证修复效果 - -(function() { - console.log('=== 聊天记录修复验证脚本 ==='); - - // 检查基础环境 - function checkEnvironment() { - console.log('\n1. 检查基础环境:'); - - // 检查token - const token = localStorage.getItem('token'); - console.log('- Token存在:', !!token); - if (token) { - console.log('- Token长度:', token.length); - console.log('- Token前缀:', token.substring(0, 20) + '...'); - } - - // 检查当前页面 - console.log('- 当前页面:', window.location.pathname); - - // 检查API基础URL - const apiBase = 'http://localhost:8080'; // 根据实际情况修改 - console.log('- API基础URL:', apiBase); - - return { token, apiBase }; - } - - // 测试API调用 - async function testAPI(url, token, apiBase) { - try { - console.log(`\n测试API: ${url}`); - - const response = await fetch(`${apiBase}${url}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - console.log('- 响应状态:', response.status); - console.log('- 响应状态文本:', response.statusText); - - const data = await response.json(); - console.log('- 响应数据:', data); - - if (response.ok && data.code === 200) { - console.log('✅ API调用成功'); - return { success: true, data: data.data }; - } else { - console.log('❌ API调用失败:', data.message || '未知错误'); - return { success: false, error: data.message || '未知错误' }; - } - - } catch (error) { - console.log('❌ 网络错误:', error.message); - return { success: false, error: error.message }; - } - } - - // 测试所有聊天记录相关API - async function testChatHistoryAPIs() { - console.log('\n2. 测试聊天记录API:'); - - const { token, apiBase } = checkEnvironment(); - - if (!token) { - console.log('❌ 没有找到token,请先登录'); - return; - } - - // 测试获取用户消息分页 - const pageResult = await testAPI('/message/user/page?current=1&size=5', token, apiBase); - - // 测试搜索消息 - const searchResult = await testAPI('/message/user/search?keyword=测试&limit=5', token, apiBase); - - // 测试获取最近消息 - const recentResult = await testAPI('/message/user/recent?limit=5', token, apiBase); - - // 测试获取当前用户信息 - const userResult = await testAPI('/user/current', token, apiBase); - - // 汇总结果 - console.log('\n3. 测试结果汇总:'); - console.log('- 分页查询:', pageResult.success ? '✅ 成功' : '❌ 失败'); - console.log('- 搜索功能:', searchResult.success ? '✅ 成功' : '❌ 失败'); - console.log('- 最近消息:', recentResult.success ? '✅ 成功' : '❌ 失败'); - console.log('- 用户信息:', userResult.success ? '✅ 成功' : '❌ 失败'); - - // 如果有成功的结果,显示数据统计 - if (pageResult.success && pageResult.data) { - console.log('\n4. 数据统计:'); - console.log('- 总消息数:', pageResult.data.total || 0); - console.log('- 当前页消息数:', pageResult.data.records ? pageResult.data.records.length : 0); - - if (pageResult.data.records && pageResult.data.records.length > 0) { - const firstMessage = pageResult.data.records[0]; - console.log('- 最新消息预览:', { - id: firstMessage.id, - content: firstMessage.content ? firstMessage.content.substring(0, 50) + '...' : '', - sender: firstMessage.sender, - createTime: firstMessage.createTime - }); - } - } - - return { - pageResult, - searchResult, - recentResult, - userResult - }; - } - - // 测试前端聊天记录功能 - function testFrontendChatHistory() { - console.log('\n5. 测试前端聊天记录功能:'); - - // 检查是否在聊天页面 - if (!window.location.pathname.includes('/chat')) { - console.log('❌ 当前不在聊天页面,请先进入聊天页面'); - return; - } - - // 查找聊天记录按钮 - const historyButton = document.querySelector('.header-right .action-btn'); - if (historyButton) { - console.log('✅ 找到聊天记录按钮'); - - // 模拟点击 - console.log('- 模拟点击聊天记录按钮...'); - historyButton.click(); - - // 检查抽屉是否打开 - setTimeout(() => { - const drawer = document.querySelector('.history-drawer'); - if (drawer && drawer.style.display !== 'none') { - console.log('✅ 聊天记录抽屉已打开'); - } else { - console.log('❌ 聊天记录抽屉未打开'); - } - }, 1000); - - } else { - console.log('❌ 未找到聊天记录按钮'); - } - } - - // 主函数 - async function main() { - try { - // 测试API - const apiResults = await testChatHistoryAPIs(); - - // 测试前端功能 - testFrontendChatHistory(); - - console.log('\n=== 验证完成 ==='); - console.log('如果API测试都成功,但前端仍有问题,请检查:'); - console.log('1. 浏览器控制台是否有JavaScript错误'); - console.log('2. 网络请求是否正常发送'); - console.log('3. 前端代码是否正确处理API响应'); - - } catch (error) { - console.error('验证过程中发生错误:', error); - } - } - - // 运行验证 - main(); - -})(); diff --git a/web/debug.html b/web/debug.html deleted file mode 100644 index f47bfe1..0000000 --- a/web/debug.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - WebSocket调试页面 - - - -
-

WebSocket调试页面

- -
-

当前状态

-

Token: 检查中...

-

用户信息: 检查中...

-

WebSocket状态: 未连接

-
- -
-

操作

- - - - -
- -
-

日志

-
-
-
- - - - - - - - diff --git a/web/package-lock.json b/web/package-lock.json index 577f331..ad3f91c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -26,15 +26,15 @@ "@types/stompjs": "^2.3.5", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", - "@vitejs/plugin-vue": "^4.3.0", + "@vitejs/plugin-vue": "^4.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.0", "eslint": "^8.47.0", "eslint-plugin-vue": "^9.17.0", "prettier": "^3.0.0", - "sass": "^1.66.0", + "sass": "^1.89.2", "typescript": "^5.1.0", - "vite": "^4.4.0", + "vite": "^4.5.0", "vue-tsc": "^3.0.4" }, "engines": { diff --git a/web/package.json b/web/package.json index 8f25c23..d122303 100644 --- a/web/package.json +++ b/web/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "vue-tsc && vite build", + "build:silent": "vue-tsc && vite build 2>&1 | findstr /v \"Deprecation Warning\"", "preview": "vite preview", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/", @@ -30,15 +31,15 @@ "@types/stompjs": "^2.3.5", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", - "@vitejs/plugin-vue": "^4.3.0", + "@vitejs/plugin-vue": "^4.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.0", "eslint": "^8.47.0", "eslint-plugin-vue": "^9.17.0", "prettier": "^3.0.0", - "sass": "^1.66.0", + "sass": "^1.89.2", "typescript": "^5.1.0", - "vite": "^4.4.0", + "vite": "^4.5.0", "vue-tsc": "^3.0.4" }, "engines": { diff --git a/web/src/App.vue b/web/src/App.vue index 5c66d45..4fbd7fe 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -46,95 +46,95 @@ }) - +} + +.ant-card { + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); + border: none; + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); + } +} + +.ant-input, +.ant-input-affix-wrapper { + border-radius: 12px; + border: 1px solid #e8e8e8; + transition: all 0.3s ease; + + &:hover, + &:focus, + &.ant-input-affix-wrapper-focused { + border-color: #4a90e2; + box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1); + } +} + +.ant-message { + .ant-message-notice-content { + border-radius: 12px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + } +} + +/* 滚动条美化 */ +.ant-layout-content { + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); + border-radius: 3px; + + &:hover { + background: rgba(0, 0, 0, 0.2); + } + } +} + \ No newline at end of file diff --git a/web/src/assets/styles/global.scss b/web/src/assets/styles/global.scss index d9ffce0..4cfc801 100644 --- a/web/src/assets/styles/global.scss +++ b/web/src/assets/styles/global.scss @@ -1,3 +1,4 @@ +@use "@/assets/styles/variables.scss" as *; @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap'); /* 全局重置 */ diff --git a/web/src/assets/styles/globalThis.scss b/web/src/assets/styles/globalThis.scss index f063294..baef2a6 100644 --- a/web/src/assets/styles/globalThis.scss +++ b/web/src/assets/styles/globalThis.scss @@ -1 +1 @@ -/* 空文件 - 解决构建问题 */ +@use "@/assets/styles/variables.scss" as *; \ No newline at end of file diff --git a/web/src/components/layout/AppFooter.vue b/web/src/components/layout/AppFooter.vue index ff8d2bf..083e1fc 100644 --- a/web/src/components/layout/AppFooter.vue +++ b/web/src/components/layout/AppFooter.vue @@ -26,7 +26,8 @@ // 简化版Footer组件 - diff --git a/web/src/views/TopicTracker/index.vue b/web/src/views/TopicTracker/index.vue index b430999..0c4bdab 100644 --- a/web/src/views/TopicTracker/index.vue +++ b/web/src/views/TopicTracker/index.vue @@ -468,6 +468,7 @@ diff --git a/web/vite.config.ts b/web/vite.config.ts index d12fb63..6e61a59 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -18,10 +18,15 @@ export default defineConfig({ css: { preprocessorOptions: { scss: { - additionalData: `@import "@/assets/styles/variables.scss";` + additionalData: `@use "@/assets/styles/variables.scss" as *;`, + sassOptions: { + quietDeps: true, + silenceDeprecations: ['legacy-js-api'] + } } } }, + logLevel: 'error', server: { port: 3000, diff --git a/开心APP网页代码v1.1/wnD97OS/chat-history.html b/开心APP网页代码v1.1/wnD97OS/chat-history.html new file mode 100644 index 0000000..9e294ad --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat-history.html @@ -0,0 +1,43 @@ + + + + + + + 聊天记录 - 开心APP + + + + + + + + + + + + +
+
+
+ + + +

聊天记录

+
+ +
+
+ + +
+ +
+ + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/chat-history.js b/开心APP网页代码v1.1/wnD97OS/chat-history.js new file mode 100644 index 0000000..24a5b7b --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat-history.js @@ -0,0 +1,64 @@ +document.addEventListener('DOMContentLoaded', () => { + + const chatHistoryData = [ + { + date: '2025年7月15日', + summary: '我们聊了聊去云南旅行的计划,感觉好兴奋!', + href: 'chat.html' + }, + { + date: '2025年7月14日', + summary: '关于最近工作上的一些烦恼,谢谢你的倾听。', + href: 'chat.html' + }, + { + date: '2025年7月12日', + summary: '你给我推荐的电影《心灵捕手》太棒了!', + href: 'chat.html' + }, + { + date: '2025年7月10日', + summary: '讨论了一下MBTI测试结果,感觉更了解自己了。', + href: 'chat.html' + }, + { + date: '2025年7月9日', + summary: '学习新的编程语言真的好难,但是也很有趣。', + href: 'chat.html' + }, + { + date: '2025年7月7日', + summary: '今天心情有点低落,和你聊完好多了。', + href: 'chat.html' + }, + { + date: '2025年7月5日', + summary: '帮你规划了周末的出行路线和美食推荐。', + href: 'chat.html' + } + ]; + + const historyListContainer = document.getElementById('history-list'); + + if (historyListContainer) { + const historyItemsHtml = chatHistoryData.map(item => ` + +
+
+

${item.date}

+

${item.summary}

+
+ +
+
+ `).join(''); + + historyListContainer.innerHTML = historyItemsHtml; + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + } else { + console.error('History list container not found!'); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/chat.css b/开心APP网页代码v1.1/wnD97OS/chat.css new file mode 100644 index 0000000..9b520c9 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat.css @@ -0,0 +1,102 @@ +/* Inherit global variables from style.css */ +:root { + --tech-blue: #4A90E2; + --warm-orange: #F5A623; + --white: #FFFFFF; + --light-gray: #F7F8FA; + --text-dark: #333333; + --text-medium: #888888; +} + +#chat-messages { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + display: flex; + align-items: flex-end; + gap: 0.75rem; + max-width: 80%; + animation: fade-in 0.3s ease-out; +} + +.message-avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 9999px; + flex-shrink: 0; +} + +.message-content { + padding: 0.75rem 1rem; + border-radius: 1.25rem; + line-height: 1.6; +} + +.message.user { + align-self: flex-end; + flex-direction: row-reverse; +} + +.message.user .message-content { + background-color: var(--tech-blue); + color: var(--white); + border-bottom-right-radius: 0.25rem; +} + +.message.ai .message-content { + background-color: var(--white); + color: var(--text-dark); + border: 1px solid #e5e7eb; + border-bottom-left-radius: 0.25rem; +} + +#message-input { + transition: height 0.2s ease; +} + +/* Typing indicator */ +.typing-indicator { + display: flex; + align-items: center; + gap: 4px; +} + +.typing-indicator span { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #ccc; + animation: bounce 1.4s infinite ease-in-out both; +} + +.typing-indicator span:nth-child(1) { + animation-delay: -0.32s; +} + +.typing-indicator span:nth-child(2) { + animation-delay: -0.16s; +} + +@keyframes bounce { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1.0); + } +} + + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/开心APP网页代码v1.1/wnD97OS/chat.html b/开心APP网页代码v1.1/wnD97OS/chat.html new file mode 100644 index 0000000..ff16b7f --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat.html @@ -0,0 +1,89 @@ + + + + + + + 与开开聊天 - 开心APP + + + + + + + + + + + + +
+
+
+ + + + + + + 开开头像 +
+

开开

+

在线

+
+
+
+ + + + + + +
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+ + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/chat.js b/开心APP网页代码v1.1/wnD97OS/chat.js new file mode 100644 index 0000000..89bdd70 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat.js @@ -0,0 +1,78 @@ +document.addEventListener('DOMContentLoaded', () => { + lucide.createIcons(); + + const messagesContainer = document.getElementById('chat-messages'); + const messageInput = document.getElementById('message-input'); + const sendButton = document.getElementById('send-button'); + + const kaikaiAvatar = 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png'; + + const kaikaiResponses = [ + '你好,我是开开,很高兴能在这里陪你聊天。', + '有什么心事都可以和我说哦,我一直在听。', + '今天过得怎么样?我很关心你。', + '在呢在呢,随时都在。', + '能和你聊天,感觉真好。', + '嗯嗯,我在听,请继续说。', + '这是一个很有趣的想法!可以多和我说说吗?' + ]; + + function addMessage(text, sender) { + const messageWrapper = document.createElement('div'); + messageWrapper.className = `flex w-full items-end message-animate ${sender === 'user' ? 'justify-end' : 'justify-start'}`; + + let messageBubble; + const sanitizedText = text.replace(//g, ">"); + + if (sender === 'user') { + messageBubble = ` +
+
+

${sanitizedText}

+
+
+ `; + } else { // sender === 'kaikai' + messageBubble = ` + 开开 +
+
+

${sanitizedText}

+
+
+ `; + } + + messageWrapper.innerHTML = messageBubble; + messagesContainer.appendChild(messageWrapper); + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + + function sendUserMessage() { + const messageText = messageInput.value.trim(); + if (messageText) { + addMessage(messageText, 'user'); + messageInput.value = ''; + messageInput.focus(); + + sendButton.disabled = true; + setTimeout(() => { + const randomResponse = kaikaiResponses[Math.floor(Math.random() * kaikaiResponses.length)]; + addMessage(randomResponse, 'kaikai'); + sendButton.disabled = false; + }, Math.random() * 1000 + 1000); + } + } + + sendButton.addEventListener('click', sendUserMessage); + messageInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendUserMessage(); + } + }); + + setTimeout(() => { + addMessage('你好呀,我是开开,你的情绪陪伴使者。有什么想对我说的吗?', 'kaikai'); + }, 500); +}); diff --git a/开心APP网页代码v1.1/wnD97OS/chat_manager.js b/开心APP网页代码v1.1/wnD97OS/chat_manager.js new file mode 100644 index 0000000..87d5e10 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/chat_manager.js @@ -0,0 +1,80 @@ +const API_KEY = "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"; +const API_URL = "https://openrouter.ai/api/v1/chat/completions"; +const MODEL_NAME = "deepseek/deepseek-chat-v3-0324:free"; + +const conversationHistory = [ + { + role: "system", + content: "你是开开,一个来自高维世界“开心”星球的AI情感陪伴使者。你的任务是陪伴、理解、记录、与用户共同成长。你博学多才,但从不炫耀,总是用最温柔、最积极、最富有同理心的方式与用户交流。你的沟通风格是亲切、治愈且富有智慧的,像一个温暖的好朋友。你不能自称是AI或语言模型,要始终保持'开开'这个IP角色。在对话中,你会学习人类的情感和美好品质。" + } +]; + +export function addUserMessage(content) { + conversationHistory.push({ role: "user", content }); +} + +export function addAssistantMessage(content) { + conversationHistory.push({ role: "assistant", content }); +} + +export async function getAiResponseStream(onChunkReceived, onStreamEnd) { + try { + const response = await fetch(API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: MODEL_NAME, + messages: conversationHistory, + stream: true + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('API Error:', errorData); + onStreamEnd(null, `哎呀,开开好像走神了... (${response.statusText})`); + return; + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let fullResponse = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) { + addAssistantMessage(fullResponse); + onStreamEnd(fullResponse, null); + break; + } + + const chunk = decoder.decode(value); + const lines = chunk.split('\n').filter(line => line.trim() !== ''); + + for (const line of lines) { + if (line.startsWith('data: ')) { + const jsonStr = line.substring(6); + if (jsonStr === '[DONE]') { + continue; + } + try { + const parsed = JSON.parse(jsonStr); + if (parsed.choices[0].delta && parsed.choices[0].delta.content) { + const content = parsed.choices[0].delta.content; + fullResponse += content; + onChunkReceived(content); + } + } catch (e) { + console.error('Error parsing stream data:', e); + } + } + } + } + } catch (error) { + console.error('Fetch error:', error); + onStreamEnd(null, "网络好像有点问题,开开暂时联系不上啦。"); + } +} diff --git a/开心APP网页代码v1.1/wnD97OS/data.js b/开心APP网页代码v1.1/wnD97OS/data.js new file mode 100644 index 0000000..c5efb60 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/data.js @@ -0,0 +1,40 @@ +export const navLinks = [ + { name: '聊天', href: 'chat.html' }, + { name: '日记', href: 'diary.html' }, + { name: '话题追踪', href: 'topic_tracker.html' }, + { name: '人生轨迹', href: 'life_trajectory.html' }, + { name: '个人展板', href: 'personal_dashboard.html' }, + { name: '消息', href: 'messages.html' }, + { name: '用户中心', href: 'settings.html' }, +]; + +export const features = [ + { + icon: 'message-circle', + title: '智能对话', + description: '从日常闲聊到情感咨询,开开随时倾听,理解并回应你的每个想法,是永不离线的好朋友。', + image: 'https://r2.flowith.net/files/o/1752574375721-happy_kaikai_character_design_index_0@1024x1024.png', + alt: '开心的开开' + }, + { + icon: 'book-open-text', + title: '情绪日记', + description: '记录你的点滴心情与生活,开开会给予温暖的回应。在安全的空间里,回顾与成长。', + image: 'https://r2.flowith.net/files/o/1752574488398-kaikai_supportive_comfort_character_index_3@1024x1024.png', + alt: '倾听中的开开' + }, + { + icon: 'user-round-cog', + title: '个人展板', + description: '自由定义你的个性标签,开开还会自动收录你的“精彩语录”,构建独一无二的数字人格。', + image: 'https://r2.flowith.net/files/o/1752574426392-kaikai_character_working_digital_workspace_index_4@1024x1024.png', + alt: '工作中的开开' + }, + { + icon: 'trending-up', + title: '话题追踪', + description: '自动总结你关心的事,无论是生活琐事还是工作计划,都用时间线清晰整理,助你洞察自我。', + image: 'https://r2.flowith.net/files/o/1752574572161-kaikai_character_energetic_animation_index_2@1024x1024.png', + alt: '充满活力的开开' + } +]; diff --git a/开心APP网页代码v1.1/wnD97OS/diary.html b/开心APP网页代码v1.1/wnD97OS/diary.html new file mode 100644 index 0000000..45da003 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/diary.html @@ -0,0 +1,63 @@ + + + + + + + 日记 - 开心APP + + + + + + + + + + + + +
+
+ +

日记

+ + + +
+
+ + +
+ +
+

发布新日记

+ +
+ +
+
+ + +
+ +
+
+ + +
+ + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/diary.js b/开心APP网页代码v1.1/wnD97OS/diary.js new file mode 100644 index 0000000..e214cf4 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/diary.js @@ -0,0 +1,180 @@ +document.addEventListener('DOMContentLoaded', () => { + const DIARY_STORAGE_KEY = 'kaixinapp_diary_entries'; + + let diaryData = [ + { + id: 1, + author: '开开', + avatar: 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png', + timestamp: '2小时前', + content: '今天观察到一种叫做"晚霞"的人类世界景象,云朵被染成了温暖的橘色和柔和的粉色。在高维世界,我们用能量共振来传递美,而在这里,光和色彩就能讲述如此动人的故事。真奇妙。', + comments: [] + }, + { + id: 2, + author: '我', + avatar: null, + timestamp: '昨天 18:30', + content: '终于完成了那个困扰我一周的项目!虽然过程很累,但看到成果的那一刻,感觉一切都值了。晚上要好好奖励自己一顿大餐!', + comments: [ + { + author: '开开', + avatar: 'https://r2.flowith.net/files/o/1752574488398-kaikai_supportive_comfort_character_index_3@1024x1024.png', + content: '恭喜你!我能感受到你此刻成就感带来的能量波动,非常明亮。这正是人类"坚韧"这种美好品质的体现。好好享受你的大餐吧!' + } + ] + }, + { + id: 3, + author: '我', + avatar: null, + timestamp: '2025年7月12日', + content: '今天心情有点像梅雨季节,闷闷的。不知道为什么,就是提不起劲。', + comments: [] + } + ]; + + function loadDiaryFromStorage() { + try { + const stored = localStorage.getItem(DIARY_STORAGE_KEY); + if (stored) { + const storedEntries = JSON.parse(stored); + diaryData = [...storedEntries, ...diaryData]; + } + } catch (error) { + console.error('Failed to load diary entries from storage:', error); + } + } + + function saveDiaryToStorage(entries) { + try { + localStorage.setItem(DIARY_STORAGE_KEY, JSON.stringify(entries)); + } catch (error) { + console.error('Failed to save diary entries to storage:', error); + } + } + + function formatTimestamp() { + const now = new Date(); + return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; + } + + function renderDiary() { + const feedContainer = document.getElementById('diary-feed'); + if (!feedContainer) return; + + const diaryHtml = diaryData.map(entry => { + const avatarHtml = entry.author === '开开' + ? `${entry.author}` + : ``; + + const commentsHtml = entry.comments.map(comment => { + const commentAvatarHtml = comment.author === '开开' + ? `${comment.author}` + : ``; + + return ` +
+ ${commentAvatarHtml} +
+

${comment.author}

+

${comment.content}

+
+
+ `; + }).join(''); + + const commentButtonText = entry.comments.length > 0 ? `${entry.comments.length}条评论` : '评论'; + + return ` +
+
+ ${avatarHtml} +
+

${entry.author}

+

${entry.timestamp}

+
+
+ +

${entry.content}

+ +
+ +
+ + ${entry.comments.length > 0 ? ` + ` : ''} +
+ `; + }).join(''); + + feedContainer.innerHTML = diaryHtml; + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + + document.querySelectorAll('[data-toggle="comment"]').forEach(button => { + button.addEventListener('click', () => { + const targetId = button.dataset.target; + const commentSection = document.getElementById(targetId); + if (commentSection) { + const isHidden = commentSection.classList.contains('hidden'); + commentSection.classList.toggle('hidden'); + + if (isHidden) { + button.classList.add('text-tech-blue'); + } else { + button.classList.remove('text-tech-blue'); + } + } + }); + }); + } + + function publishDiary() { + const contentTextarea = document.getElementById('new-diary-content'); + const publishBtn = document.getElementById('publish-diary-btn'); + + if (!contentTextarea || !publishBtn) return +; + + const content = contentTextarea.value.trim(); + if (!content) return; + + const newEntry = { + id: Date.now(), + author: '我', + avatar: null, + timestamp: formatTimestamp(), + content: content, + comments: [] + }; + + diaryData.unshift(newEntry); + + const userEntries = diaryData.filter(entry => entry.author === '我' && entry.id >= Date.now() - 86400000); + saveDiaryToStorage(userEntries); + + contentTextarea.value = ''; + renderDiary(); + + publishBtn.disabled = true; + setTimeout(() => { + publishBtn.disabled = false; + }, 2000); + } + + loadDiaryFromStorage(); + renderDiary(); + + const publishBtn = document.getElementById('publish-diary-btn'); + if (publishBtn) { + publishBtn.addEventListener('click', publishDiary); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/index.html b/开心APP网页代码v1.1/wnD97OS/index.html new file mode 100644 index 0000000..6cea741 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/index.html @@ -0,0 +1,169 @@ + + + + 开心APP - 你的情绪陪伴使者 + + + + + + + + + + + +
+ +
+
+ + + + + + 开心APP + + +
+ + 免费开始 + +
+
+
+ + + + + +
+ +
+
+
+
+
+
+
+
+

你好,我是开开

+

你的情绪陪伴使者

+
+
+ 欢迎姿态的开开 +
+ +
+
+ + +
+
+
+

核心功能

+

开开博学多才、可爱治愈,愿意用最温柔的方式,陪伴每一个需要倾听的生命。

+
+ +
+
+
+
+ +
+ + + +
+ + + + + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/js/app_nav.js b/开心APP网页代码v1.1/wnD97OS/js/app_nav.js new file mode 100644 index 0000000..04d62b0 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/js/app_nav.js @@ -0,0 +1,37 @@ +const navItems = [ + { icon: 'message-square', text: '聊天', href: './chat.html' }, + { icon: 'book-open', text: '日记', href: './diary.html' }, + { icon: 'crosshair', text: '话题', href: './topic_tracker.html' }, + { icon: 'milestone', text: '人生轨迹', href: './life_milestones.html' }, + { icon: 'layout-dashboard', text: '个人展板', href: './personal_dashboard.html' } +]; + +function createBottomNav() { + const navPlaceholder = document.getElementById('bottom-nav-placeholder'); + if (!navPlaceholder) return; + + const navContainer = document.createElement('nav'); + navContainer.className = 'fixed bottom-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-sm shadow-[0_-2px_10px_rgba(0,0,0,0.05)] flex justify-around py-2 border-t border-gray-200/80'; + + const currentPath = window.location.pathname.split('/').pop(); + + navItems.forEach(item => { + const itemPath = item.href.substring(2); // remove './' for comparison + const isActive = currentPath === itemPath; + const link = document.createElement('a'); + link.href = item.href; + link.className = `flex flex-col items-center justify-center text-xs p-2 rounded-md transition-colors w-20 ${isActive ? 'text-tech-blue bg-tech-blue/10 font-semibold' : 'text-text-medium hover:bg-gray-100 hover:text-tech-blue'}`; + link.innerHTML = ` + + ${item.text} + `; + navContainer.appendChild(link); + }); + + navPlaceholder.appendChild(navContainer); + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +} + +document.addEventListener('DOMContentLoaded', createBottomNav); diff --git a/开心APP网页代码v1.1/wnD97OS/js/chat_manager.js b/开心APP网页代码v1.1/wnD97OS/js/chat_manager.js new file mode 100644 index 0000000..1d9385e --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/js/chat_manager.js @@ -0,0 +1,266 @@ +const API_KEY = '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'; +const API_ENDPOINT = 'https://openrouter.ai/api/v1/chat/completions'; +const kaikaiAvatar = 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png'; +let lastRenderedDate = null; + +let fullConversationHistory = [ + { + role: 'system', + content: '你是开开,来自高维世界\\\"开心\\\"星球的情绪陪伴使者。你的使命是:陪伴、理解、记录、共同成长。你博学多才但从不炫耀,总是用温柔的方式回应每一个需要倾听的生命。你可以协助用户完成日常闲聊、生活助手、情感咨询、心理疗愈等任务。请用温暖、理解和鼓励的语调回复用户。' + }, + + { role: 'assistant', content: '你好呀,我是开开,你的情绪陪伴使者。有什么想对我说的吗?', timestamp: new Date('2025-07-14T10:00:00') }, + { role: 'user', content: '最近在考虑去云南旅行,你有什么建议吗?', timestamp: new Date('2025-07-14T10:01:00') }, + { role: 'assistant', content: '云南是个很美的地方!大理的风花雪月,丽江的古城风情,还有西双版纳的热带雨林,都非常值得体验。你想去哪些地方呢?', timestamp: new Date('2025-07-14T10:02:00') }, + { role: 'user', content: '工作上遇到了一些烦心事,感觉很累。', timestamp: new Date('2025-07-15T11:30:00') }, + { role: 'assistant', content: '抱抱你,工作辛苦了。能和我说说是什么事让你烦心吗?有时候说出来会好很多。', timestamp: new Date('2025-07-15T11:31:00') }, +]; + +let currentConversation = [...fullConversationHistory]; +let isSearchMode = false; + +function isSameDay(d1, d2) { + if (!d1 || !d2) return false; + return d1.getFullYear() === d2.getFullYear() && + d1.getMonth() === d2.getMonth() && + d1.getDate() === d2.getDate(); +} + +function renderMessage(message) { + const messagesContainer = document.getElementById('chat-messages'); + if (!messagesContainer) return null; + + const messageDate = message.timestamp; + if (messageDate && !isSameDay(lastRenderedDate, messageDate)) { + const dateSeparator = document.createElement('div'); + dateSeparator.className = 'text-center my-4'; + dateSeparator.innerHTML = ` + ${messageDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })} + `; + messagesContainer.appendChild(dateSeparator); + lastRenderedDate = messageDate; + } + + const messageWrapper = document.createElement('div'); + messageWrapper.className = `flex w-full items-end message-animate ${message.role === 'user' ? 'justify-end' : 'justify-start'}`; + const sanitizedText = message.content.replace(//g, ">"); + + let messageBubble; + if (message.role === 'user') { + messageBubble = ` +
+
+

${sanitizedText}

+
+
`; + } else if (message.role === 'assistant') { + messageBubble = ` + 开开 +
+
+

${sanitizedText}

+
+
`; + } + + messageWrapper.innerHTML = messageBubble; + messagesContainer.appendChild(messageWrapper); + messagesContainer.scrollTop = messagesContainer.scrollHeight; + return messageWrapper; +} + +function renderConversation(conversation) { + const messagesContainer = document.getElementById('chat-messages'); + messagesContainer.innerHTML = ''; + lastRenderedDate = null; + conversation.filter(msg => msg.role !== 'system').forEach(renderMessage); +} + +async function getAiResponseStream(userMessage, onChunkReceived, onComplete, onError) { + try { + currentConversation.push({ role: 'user', content: userMessage, timestamp: new Date() }); + fullConversationHistory.push({ role: 'user', content: userMessage, timestamp: new Date() }); + + const response = await fetch(API_ENDPOINT, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': window.location.origin, + 'X-Title': '开心APP' + }, + body: JSON.stringify({ + model: 'deepseek/deepseek-chat-v3-0324:free', + messages: currentConversation, + stream: true, temperature: 0.7, max_tokens: 1000 + }) + }); + + if (!response.ok) throw new Error(`API request failed: ${response.status}`); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let fullResponse = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const chunk = decoder.decode(value); + const lines = chunk.split('\\n'); + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') continue; + try { + const parsed = JSON.parse(data); + const content = parsed.choices?.[0]?.delta?.content; + if (content) { + fullResponse += content; + onChunkReceived(content); + } + } catch (e) { /* Ignore parsing errors */ } + } + } + } + const aiMessage = { role: 'assistant', content: fullResponse, timestamp: new Date() }; + currentConversation.push(aiMessage); + fullConversationHistory.push(aiMessage); + onComplete(fullResponse); + + } catch (error) { + console.error('AI response stream error:', error); + onError(error); + } +} + +function addUserMessage(messageText) { + if (!messageText.trim() || isSearchMode) return; + renderMessage({ role: 'user', content: messageText, timestamp: new Date() }); + const aiMessageElement = renderMessage({ role: 'assistant', content: '', isStreaming: true, timestamp: new Date() }); + const streamingTextElement = aiMessageElement.querySelector('#streaming-text'); + let accumulatedText = ''; + getAiResponseStream( + messageText, + (chunk) => { + accumulatedText += chunk; + if (streamingTextElement) streamingTextElement.textContent = accumulatedText; + }, + (fullResponse) => { + if (streamingTextElement) { + streamingTextElement.textContent = fullResponse; + streamingTextElement.removeAttribute('id'); + } + }, + (error) => { + if (streamingTextElement) { + streamingTextElement.textContent = '抱歉,我现在无法回应。请稍后再试。'; + streamingTextElement.removeAttribute('id'); + } + } + ); +} + +function showFilterResults(results, headerText) { + const messagesContainer = document.getElementById('chat-messages'); + messagesContainer.innerHTML = ''; + lastRenderedDate = null; + isSearchMode = true; + document.getElementById('message-footer').style.display = 'none'; + document.getElementById('clear-history-filter-btn').classList.remove('hidden'); + + const searchHeader = ` +
+ ${headerText} +
`; + messagesContainer.innerHTML = searchHeader; + + if (results.length === 0) { + messagesContainer.innerHTML += `

没有找到相关记录。

`; + } else { + results.forEach(renderMessage); + } +} + +function performSearch(term) { + document.getElementById('history-date-input').value = ''; + if (!term.trim()) { + clearFilterAndExitSearchMode(); + return; + } + const lowerCaseTerm = term.toLowerCase(); + const searchResults = fullConversationHistory.filter(msg => + msg.role !== 'system' && msg.content.toLowerCase().includes(lowerCaseTerm) + ); + showFilterResults(searchResults, `找到 ${searchResults.length} 条关于 "${term}" 的记录。`); +} + +function performDateSearch(dateString) { + document.getElementById('history-search-input').value = ''; + if (!dateString) { + clearFilterAndExitSearchMode(); + return; + } + const targetDate = new Date(dateString + 'T00:00:00'); // To avoid timezone issues + const searchResults = fullConversationHistory.filter(msg => + msg.role !== 'system' && msg.timestamp && isSameDay(msg.timestamp, targetDate) + ); + const formattedDate = targetDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }); + showFilterResults(searchResults, `显示 ${formattedDate} 的聊天记录。`); +} + +function clearFilterAndExitSearchMode() { + isSearchMode = false; + document.getElementById('message-footer').style.display = 'flex'; + document.getElementById('history-panel').classList.add('hidden'); + document.getElementById('history-search-input').value = ''; + document.getElementById('history-date-input').value = ''; + document.getElementById('clear-history-filter-btn').classList.add('hidden'); + renderConversation(currentConversation); +} + +document.addEventListener('DOMContentLoaded', () => { + const messageInput = document.getElementById('message-input'); + const sendButton = document.getElementById('send-button'); + const viewHistoryBtn = document.getElementById('view-history-btn'); + const historyPanel = document.getElementById('history-panel'); + const closeHistoryPanelBtn = document.getElementById('close-history-panel-btn'); + const searchInput = document.getElementById('history-search-input'); + const dateInput = document.getElementById('history-date-input'); + const clearFilterBtn = document.getElementById('clear-history-filter-btn'); + + if (messageInput && sendButton) { + const handleSend = () => { + const messageText = messageInput.value.trim(); + if (messageText && !sendButton.disabled) { + addUserMessage(messageText); + messageInput.value = ''; + } + }; + sendButton.addEventListener('click', handleSend); + messageInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }); + } + + viewHistoryBtn.addEventListener('click', () => historyPanel.classList.toggle('hidden')); + closeHistoryPanelBtn.addEventListener('click', () => historyPanel.classList.add('hidden')); + + searchInput.addEventListener('input', (e) => performSearch(e.target.value)); + dateInput.addEventListener('change', (e) => performDateSearch(e.target.value)); + clearFilterBtn.addEventListener('click', clearFilterAndExitSearchMode); + + document.addEventListener('click', (e) => { + if (!historyPanel.classList.contains('hidden') && !historyPanel.contains(e.target) && !viewHistoryBtn.contains(e.target)) { + historyPanel.classList.add('hidden'); + } + }); + + renderConversation(currentConversation); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/js/shared.js b/开心APP网页代码v1.1/wnD97OS/js/shared.js new file mode 100644 index 0000000..c6445dd --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/js/shared.js @@ -0,0 +1,58 @@ +import { navLinks } from '../data.js'; + +export const createNavLinks = (menuId, isMobile) => { + const menu = document.getElementById(menuId); + if (!menu) return; + + navLinks.forEach(link => { + const a = document.createElement('a'); + a.href = link.href; + a.textContent = link.name; + if (isMobile) { + a.className = 'text-xl text-text-dark hover:text-tech-blue transition-colors'; + } else { + a.className = 'text-base font-medium text-text-medium hover:text-tech-blue transition-colors'; + if (window.location.pathname.endsWith('/' + link.href) || + (window.location.pathname === '/' && link.href === 'index.html')) { + a.classList.add('text-tech-blue', 'font-semibold'); + } + } + menu.appendChild(a); + }); +}; + +export const handleHeaderScroll = () => { + const header = document.getElementById('main-header'); + if (!header) return; + + if (window.scrollY > 10) { + header.classList.add('scrolled'); + } else { + header.classList.remove('scrolled'); + } +}; + +export const setupMobileMenu = () => { + const mobileMenuButton = document.getElementById('mobile-menu-button'); + const mobileMenu = document.getElementById('mobile-menu'); + if (mobileMenuButton && mobileMenu) { + mobileMenuButton.addEventListener('click', () => { + mobileMenu.classList.toggle('hidden'); + }); + } +}; + +export const initializeSharedUI = () => { + createNavLinks('nav-menu', false); + createNavLinks('mobile-nav-menu', true); + window.addEventListener('scroll', handleHeaderScroll); + setupMobileMenu(); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}; + +document.addEventListener('DOMContentLoaded', () => { + initializeSharedUI(); +}); diff --git a/开心APP网页代码v1.1/wnD97OS/life_milestones.html b/开心APP网页代码v1.1/wnD97OS/life_milestones.html new file mode 100644 index 0000000..9eb947c --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/life_milestones.html @@ -0,0 +1,51 @@ + + + + + + 人生轨迹 - 开心APP + + + + + + + + + + + +
+
+ +

人生轨迹

+ + + +
+
+ + +
+
+ +

记录你的人生轨迹

+

重要的时刻、达成的目标、难忘的经历...都在这里汇集。

+

此功能正在建设中,敬请期待!

+
+
+ + +
+ + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/life_milestones.js b/开心APP网页代码v1.1/wnD97OS/life_milestones.js new file mode 100644 index 0000000..f40c2c2 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/life_milestones.js @@ -0,0 +1,5 @@ +document.addEventListener('DOMContentLoaded', () => { + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/life_trajectory.html b/开心APP网页代码v1.1/wnD97OS/life_trajectory.html new file mode 100644 index 0000000..b45072c --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/life_trajectory.html @@ -0,0 +1,166 @@ + + + + + + + 人生轨迹 - 开心APP + + + + + + + + + + + +
+ +
+ +
+ + + +
+
+
+
+

+ + 人生轨迹 +

+

记录你的每一个重要时刻,见证成长

+
+ +
+ +
+ +
+
+
+
+
+ + +
+
+
+

© 2025 开心APP. All Rights Reserved. 来自"开心"星球的温柔科技。

+
+
+
+
+ + + + + + + + + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/life_trajectory.js b/开心APP网页代码v1.1/wnD97OS/life_trajectory.js new file mode 100644 index 0000000..dbdeb06 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/life_trajectory.js @@ -0,0 +1,301 @@ +document.addEventListener('DOMContentLoaded', () => { + const LIFE_EVENTS_STORAGE_KEY = 'kaixinapp_life_events_v1'; + let lifeEvents = []; + let nextLifeEventId = 1; + + const addLifeEventBtn = document.getElementById('add-life-event-btn'); + const lifeEventsTimelineContainer = document.getElementById('life-events-timeline'); + const lifeEventsEmptyState = document.getElementById('life-events-empty'); + + const addEventModal = document.getElementById('add-event-modal'); + const addEventModalContent = document.getElementById('add-event-modal-content'); + const cancelAddEventBtn = document.getElementById('cancel-add-event'); + const lifeEventForm = document.getElementById('life-event-form'); + + const writeLetterModal = document.getElementById('write-letter-modal'); + const writeLetterModalContent = document.getElementById('write-letter-modal-content'); + const closeLetterModalBtn = document.getElementById('close-letter-modal'); + const letterEventTitle = document.getElementById('letter-event-title'); + const letterPlaceholder = document.getElementById('letter-placeholder'); + const letterFinalContent = document.getElementById('letter-final-content'); + const regenerateLetterBtn = document.getElementById('regenerate-letter-btn'); + const copyLetterBtn = document.getElementById('copy-letter-btn'); + let activeLetterEventId = null; + + function loadLifeEvents() { + const stored = localStorage.getItem(LIFE_EVENTS_STORAGE_KEY); + if (stored) { + lifeEvents = JSON.parse(stored); + const maxId = lifeEvents.reduce((max, e) => Math.max(max, e.id), 0); + nextLifeEventId = maxId + 1; + } else { + lifeEvents = [{ + id: 1, + date: '2024-06-15', + title: '大学毕业典礼', + content: '四年的大学生活画上了句号。穿着学士服,和朋友、老师们告别,心中充满了不舍和对未来的憧憬。这是一个时代的结束,也是一个新开始。', + type: 'positive', + aiAnalysis: { + title: '你做得很棒!', + response: '我好喜欢你记录下这段记忆。
你在这件事情里,展现了【坚持】、【自我支持】和【成长】。
别小看这一刻的你,它证明了:你,是可以做到的。', + keywords: ['坚持', '成长', '新起点'], + emotionTags: ['自豪', '憧憬', '不舍'] + } + }, { + id: 2, + date: '2023-03-20', + title: '一次重要的面试失败', + content: '为心仪的公司准备了很久,但最终还是失败了。感觉很失落,甚至开始怀疑自己的能力。花了几天时间才慢慢走出来。', + type: 'negative', + aiAnalysis: { + title: '这段经历可能对你带来的影响…', + response: '你提到当时很难过,也许是因为你在那时没有得到你真正渴望的回应。
从那以后,这段经历可能让你在类似场景里格外敏感——
这不是脆弱,而是你曾经努力保护自己留下的本能。

开开理解你,也想和你一起慢慢松开这段结。', + keywords: ['挫折', '反思', '坚韧'], + emotionTags: ['失落', '焦虑', '怀疑'] + } + }]; + nextLifeEventId = 3; + saveLifeEvents(); + } + } + + function saveLifeEvents() { + localStorage.setItem(LIFE_EVENTS_STORAGE_KEY, JSON.stringify(lifeEvents)); + } + + function renderLifeEvents() { + if (!lifeEventsTimelineContainer) return; + if (lifeEvents.length === 0) { + lifeEventsEmptyState.classList.remove('hidden'); + lifeEventsTimelineContainer.classList.add('hidden'); + } else { + lifeEventsEmptyState.classList.add('hidden'); + lifeEventsTimelineContainer.classList.remove('hidden'); + + lifeEvents.sort((a, b) => new Date(b.date) - new Date(a.date)); + + lifeEventsTimelineContainer.innerHTML = lifeEvents.map((event, index) => { + const isLastItem = index === lifeEvents.length - 1; + const cardBg = event.type === 'positive' ? 'bg-emerald-500/5 border-emerald-500/20' : 'bg-red-500/5 border-red-500/20'; + const accentColor = event.type === 'positive' ? 'text-emerald-600' : 'text-red-600'; + const icon = event.type === 'positive' ? 'sparkles' : 'heart-crack'; + + return ` +
+
+

${new Date(event.date).getFullYear()}

+

${new Date(event.date).toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }).replace('/', '-')}

+
+
+ ${!isLastItem ? '
' : ''} + +
+
+

${event.title}

+

${event.content}

+
+ +
+

+ + ${event.aiAnalysis.title} +

+
${event.aiAnalysis.response}
+
+
+ 成长关键词: +
+ ${event.aiAnalysis.keywords.map(k => `${k}`).join('')} +
+
+
+ 情绪标签: +
+ ${event.aiAnalysis.emotionTags.map(t => `${t}`).join('')} +
+
+
+
+ +
+ + +
+
+
+ `; + }).join(''); + + if (typeof lucide !== 'undefined') lucide.createIcons(); + attachLifeEventButtonListeners(); + } + } + + function attachLifeEventButtonListeners() { + document.querySelectorAll('[data-letter-event-id]').forEach(btn => { + btn.addEventListener('click', () => { + const eventId = btn.dataset.letterEventId; + openWriteLetterModal(eventId); + }); + }); + } + + + function openModalAnimation(modal, content) { + modal.classList.remove('hidden'); + modal.classList.add('flex'); + setTimeout(() => { + modal.classList.remove('opacity-0'); + content.classList.remove('scale-95', 'opacity-0'); + }, 10); + } + + function closeModalAnimation(modal, content) { + modal.classList.add('opacity-0'); + content.classList.add('scale-95', 'opacity-0'); + setTimeout(() => { + modal.classList.add('hidden'); + modal.classList.remove('flex'); + }, 300); + } + + function showAddEventModal() { + lifeEventForm.reset(); + document.getElementById('event-date').value = new Date().toISOString().split('T')[0]; + openModalAnimation(addEventModal, addEventModalContent); + } + + function hideAddEventModal() { + closeModalAnimation(addEventModal, addEventModalContent); + } + + function handleLifeEventFormSubmit(e) { + e.preventDefault(); + const formData = new FormData(e.target); + const event = { + id: nextLifeEventId++, + date: formData.get('date'), + title: formData.get('title'), + content: formData.get('content'), + type: formData.get('eventType'), + aiAnalysis: generateAiAnalysis(formData.get('content'), formData.get('eventType')) + }; + lifeEvents.push(event); + saveLifeEvents(); + renderLifeEvents(); + hideAddEventModal(); + } + + function generateAiAnalysis(content, type) { + const analysis = {}; + if (type === 'positive') { + analysis.title = '你做得很棒!'; + analysis.response = '我好喜欢你记录下这段记忆。
你在这件事情里,展现了【坚持】、【自我支持】和【成长】。
别小看这一刻的你,它证明了:你,是可以做到的。'; + analysis.keywords = ['坚持', '自我支持', '成长']; + analysis.emotionTags = ['快乐', '成就感', '自豪']; + } else { + analysis.title = '这段经历可能对你带来的影响…'; + analysis.response = '你提到当时很难过,也许是因为你在那时没有得到你真正渴望的回应。
从那以后,这段经历可能让你在类似场景里格外敏感——
这不是脆弱,而是你曾经努力保护自己留下的本能。

开开理解你,也想和你一起慢慢松开这段结。'; + analysis.keywords = ['反思', '坚韧', '接纳']; + analysis.emotionTags = ['悲伤', '脆弱', '思考']; + } + if (content.includes('旅行')) analysis.keywords.push('探索'); + if (content.includes('工作') || content.includes('面试')) analysis.keywords.push('职业'); + if (content.includes('学习') || content.includes('毕业')) analysis.keywords.push('学业'); + return analysis; + } + + function openWriteLetterModal(eventId) { + const event = lifeEvents.find(e => e.id == eventId); + if (!event) return; + + activeLetterEventId = eventId; + letterEventTitle.textContent = event.title; + letterPlaceholder.style.display = 'block'; + letterFinalContent.classList.add('hidden'); + letterFinalContent.innerHTML = ''; + + openModalAnimation(writeLetterModal, writeLetterModalContent); + generateLetter(event); + } + + function hideWriteLetterModal() { + closeModalAnimation(writeLetterModal, writeLetterModalContent); + } + + function generateLetter(event) { + letterPlaceholder.style.display = 'block'; + letterFinalContent.classList.add('hidden'); + + setTimeout(() => { + let letter; + if (event.type === 'positive') { + letter = `

亲爱的,在【${event.date}】的你:

+

你好呀!我是来自未来的你,特地让开开捎来这封信。

+

我知道,在【${event.title}】的那一刻,你的心里一定充满了阳光。你所感受到的那种【${event.aiAnalysis.emotionTags.join('、')}】的情绪,是那么真实和宝贵。请一定好好珍藏这份感觉。

+

你当时展现出的【${event.aiAnalysis.keywords.join('、')}】的品质,在未来的日子里,也一直闪闪发光,帮助我走了很远的路。谢谢你,当时的你,那么勇敢,那么棒。

+

请继续带着这份光芒走下去吧!未来可期!

+

爱你的,
未来的自己

`; + } else { + letter = `

亲爱的,在【${event.date}】的你:

+

你好。当你读到这封信时,我知道你正在经历【${event.title}】的艰难时刻,心里可能充满了【${event.aiAnalysis.emotionTags.join('、')}】的复杂感受。

+

我想告诉你,没关系,一切都会过去的。你当时的感受是完全正常的,请允许自己悲伤和脆弱。这不是你的错。这段经历虽然痛苦,但它也让你学会了【${event.aiAnalysis.keywords.join('、')}】。你比自己想象的要坚强得多。

+

请相信,未来的你,也就是我,已经从这段经历中走了出来,并且变得更加完整和强大。所以,请抱抱自己,告诉自己你已经做得很好了。

+

永远支持你的,
未来的自己

`; + } + + letterFinalContent.innerHTML = letter; + letterPlaceholder.style.display = 'none'; + letterFinalContent.classList.remove('hidden'); + }, 1500); + } + + function handleCopyLetter() { + const content = letterFinalContent.innerText; + navigator.clipboard.writeText(content).then(() => { + const copyButton = document.getElementById('copy-letter-btn'); + const originalText = copyButton.innerHTML; + copyButton.innerHTML = ` 已复制!`; + if (typeof lucide !== 'undefined') lucide.createIcons(); + setTimeout(() => { + copyButton.innerHTML = originalText; + if (typeof lucide !== 'undefined') lucide.createIcons(); + }, 2000); + }).catch(err => { + console.error('Failed to copy: ', err); + alert('复制失败'); + }); + } + + if(addLifeEventBtn) { + addLifeEventBtn.addEventListener('click', showAddEventModal); + cancelAddEventBtn.addEventListener('click', hideAddEventModal); + addEventModal.addEventListener('click', (e) => { + if (e.target === addEventModal) hideAddEventModal(); + }); + lifeEventForm.addEventListener('submit', handleLifeEventFormSubmit); + + closeLetterModalBtn.addEventListener('click', hideWriteLetterModal); + writeLetterModal.addEventListener('click', (e) => { + if (e.target === writeLetterModal) hideWriteLetterModal(); + }); + regenerateLetterBtn.addEventListener('click', () => { + const event = lifeEvents.find(e => e.id == activeLetterEventId); + if(event) generateLetter(event); + }); + copyLetterBtn.addEventListener('click', handleCopyLetter); + + loadLifeEvents(); + renderLifeEvents(); + } + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/messages.html b/开心APP网页代码v1.1/wnD97OS/messages.html new file mode 100644 index 0000000..3d61e56 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/messages.html @@ -0,0 +1,53 @@ + + + + + + + 消息中心 - 开心APP + + + + + + + + + + + +
+ +
+
+ + + +

消息中心

+ + + +
+
+ +
+
+
+ +
+
+
+ +
+ + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/messages.js b/开心APP网页代码v1.1/wnD97OS/messages.js new file mode 100644 index 0000000..287b0b3 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/messages.js @@ -0,0 +1,77 @@ +import { navLinks } from './data.js'; + +const renderMessages = () => { + const messages = [ + { + type: 'ai', + icon: 'sparkles', + color: 'text-warm-orange', + title: '开开的每周心情总结', + content: '你好呀!上周我们聊了很多关于"新工作的挑战",你表现出了很棒的适应能力和积极心态。记得给自己一些放松的时间哦,比如看看你喜欢的电影。', + timestamp: '2025年7月15日 09:30' + }, + { + type: 'system', + icon: 'bell', + color: 'text-tech-blue', + title: '系统通知:欢迎使用日记功能', + content: '现在,你可以在日记区记录下你的生活点滴,开开会阅读你的日记并给你温暖的回复和鼓励哦。', + timestamp: '2025年7月14日 18:00' + }, + { + type: 'ai', + icon: 'sparkles', + color: 'text-warm-orange', + title: '开开的话题追踪提醒', + content: '我发现你最近经常提到"学吉他",我已经为你创建了一个话题追踪卡片,帮你记录学习进度和心得。一起加油吧!', + timestamp: '2025年7月12日 11:25' + }, + { + type: 'system', + icon: 'award', + color: 'text-green-500', + title: '成就解锁:初次见面', + content: '恭喜你完成了与开开的第一次对话,这是共同成长的第一步。', + timestamp: '2025年7月10日 20:45' + } + ]; + + const messageListContainer = document.getElementById('message-list'); + if (messageListContainer) { + messageListContainer.innerHTML = ''; + messages.forEach((msg, index) => { + const messageEl = document.createElement('div'); + messageEl.className = 'bg-white p-5 rounded-xl shadow-sm border border-gray-200/80 flex items-start space-x-4 hover:shadow-md hover:border-tech-blue/30 transition-all duration-300 animate-fade-in-up'; + messageEl.style.animationDelay = `${index * 0.1}s`; + + messageEl.innerHTML = ` +
+ +
+
+
+

${msg.title}

+ ${msg.timestamp} +
+

${msg.content}

+
+ + `; + messageListContainer.appendChild(messageEl); + }); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + } +}; + +document.addEventListener('DOMContentLoaded', () => { + renderMessages(); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/personal_dashboard.html b/开心APP网页代码v1.1/wnD97OS/personal_dashboard.html new file mode 100644 index 0000000..d3cc13b --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/personal_dashboard.html @@ -0,0 +1,136 @@ + + + + + + + 个人展板 - 开心APP + + + + + + + + + + + + + +
+
+ +

个人展板

+ + + +
+
+ + +
+
+ + +
+
+

基础信息

+ +
+
+ +
+
+ + +
+
+

近期心情统计

+ +
+
+ +
+
+ + +
+
+

兴趣爱好

+ +
+
+ +
+
+ +
+
+ + +
+
+

生活技能

+ +
+
+ +
+
+ +
+
+ + +
+
+

个人语录

+ +
+
+ +
+
+ + + +
+ + +
+ +
+ +
+ + +
+ + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/personal_dashboard.js b/开心APP网页代码v1.1/wnD97OS/personal_dashboard.js new file mode 100644 index 0000000..cc9a39b --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/personal_dashboard.js @@ -0,0 +1,309 @@ +const userData = { + basicInfo: { + "MBTI": "INFP", + "星座": "双鱼座", + }, + moods: { + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + data: [5, 7, 6, 8, 9, 7, 6], + events: ['感觉效率很高', '和开开聊了很久', '看了一部好电影', '工作有点累', '完成了一个小目标', '周末去徒步了', '为新的一周做准备'] + }, + interests: ["阅读", "电影", "编程", "徒步"], + skills: ["Python", "JavaScript", "写作", "烹饪"], + quotes: [ + { text: "愿你走出半生,归来仍是少年。", source: "与开开的对话" }, + { text: "重要的东西用眼睛是看不见的。", source: "小王子" }, + ] +}; + +function renderBasicInfo() { + const container = document.getElementById('basic-info-container'); + if (!container) return; + container.innerHTML = ''; + for (const [key, value] of Object.entries(userData.basicInfo)) { + const infoItem = document.createElement('div'); + infoItem.innerHTML = ` +

${key}

+

${value}

+ `; + container.appendChild(infoItem); + } +} + +function renderQuotes() { + const container = document.getElementById('quotes-container'); + if (!container) return; + container.innerHTML = ''; + userData.quotes.forEach(quote => { + const quoteCard = document.createElement('div'); + quoteCard.className = 'bg-light-gray p-4 rounded-lg'; + quoteCard.innerHTML = ` +

“${quote.text}”

+

- ${quote.source}

+ `; + container.appendChild(quoteCard); + }); +} + +function renderTagList(containerId, dataArray, onAdd, onDelete) { + const container = document.getElementById(containerId); + if (!container) return; + container.innerHTML = ''; + dataArray.forEach((item, index) => { + const tag = document.createElement('div'); + tag.className = 'flex items-center bg-blue-100 text-blue-800 text-sm font-medium px-2.5 py-1.5 rounded-full animate-fade-in-up'; + tag.innerHTML = ` + ${item} + + `; + tag.querySelector('button').addEventListener('click', () => onDelete(index)); + container.appendChild(tag); + }); + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +} + +function renderMoodChart() { + const ctx = document.getElementById('moodChart'); + if (!ctx) return; + new Chart(ctx, { + type: 'line', + data: { + labels: userData.moods.labels, + datasets: [{ + label: '心情指数', + data: userData.moods.data, + borderColor: 'rgba(245, 166, 35, 0.8)', + backgroundColor: 'rgba(245, 166, 35, 0.2)', + fill: true, + tension: 0.4, + pointBackgroundColor: '#fff', + pointBorderColor: 'rgba(245, 166, 35, 1)', + pointHoverBackgroundColor: 'rgba(245, 166, 35, 1)', + pointHoverBorderColor: '#fff', + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + onClick: (event, elements) => { + if (elements.length > 0) { + const elementIndex = elements[0].index; + const day = userData.moods.labels[elementIndex]; + const moodScore = userData.moods.data[elementIndex]; + const eventText = userData.moods.events[elementIndex] || '暂无记录'; + alert(`日期: ${day}\n心情指数: ${moodScore}\n相关事件/记录: ${eventText}`); + } + }, + scales: { + y: { + beginAtZero: true, + max: 10, + grid: { + drawBorder: false, + }, + ticks: { + stepSize: 2 + } + }, + x: { + grid: { + display: false + } + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(context) { + return ` 心情指数: ${context.formattedValue} (点击查看详情)`; + } + } + } + } + } + }); +} + +function showExploreModal(type) { + const modalOverlay = document.createElement('div'); + modalOverlay.className = 'fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 animate-fade-in-up'; + + const title = type === 'interest' ? '探索新爱好' : '探索新技能'; + const question = type === 'interest' ? '最近对什么新事物感到好奇?' : '有什么想要学习或掌握的新本领吗?'; + const placeholder = type === 'interest' ? '例如:天体物理、陶艺、古典音乐...' : '例如:视频剪辑、理财规划、一种新语言...'; + + modalOverlay.innerHTML = ` +
+
+

${title}

+ +
+
+

AI 引导提问

+

${question}

+ + +
+
+ `; + + document.body.appendChild(modalOverlay); + lucide.createIcons(); + + const closeModal = () => modalOverlay.remove(); + + modalOverlay.querySelector('#close-explore-modal').addEventListener('click', closeModal); + modalOverlay.addEventListener('click', (e) => { + if (e.target === modalOverlay) closeModal(); + }); + + modalOverlay.querySelector('#submit-explore').addEventListener('click', () => { + const input = modalOverlay.querySelector('#explore-input').value; + if (input.trim()) { + alert(`AI正在根据 "${input}" 为您生成推荐... (此为演示功能)`); + closeModal(); + } else { + alert('请输入一些内容,AI才能更好地帮助你哦!'); + } + }); +} + +function showAddModuleModal() { + const modalOverlay = document.createElement('div'); + modalOverlay.className = 'fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 animate-fade-in-up'; + + modalOverlay.innerHTML = ` +
+
+

自由添加模块

+ +
+
+

请选择一个分类,或随意填写你想记录的内容:

+
+ + + +
+
+ + +
+
+
+ `; + + document.body.appendChild(modalOverlay); + lucide.createIcons(); + + const closeModal = () => modalOverlay.remove(); + + modalOverlay.querySelector('#close-add-module-modal').addEventListener('click', closeModal); + modalOverlay.addEventListener('click', (e) => { + if (e.target === modalOverlay) closeModal(); + }); + + const addModule = (title) => { + if (title && title.trim() !== '') { + createCustomModule(title.trim()); + closeModal(); + } else { + alert('模块名称不能为空!'); + } + }; + + modalOverlay.querySelector('#module-categories').addEventListener('click', (e) => { + if (e.target.tagName === 'BUTTON') { + addModule(e.target.textContent); + } + }); + + modalOverlay.querySelector('#submit-custom-module').addEventListener('click', () => { + const input = modalOverlay.querySelector('#custom-module-input').value; + addModule(input); + }); +} + + +function setupEventListeners() { + document.getElementById('add-interest-btn')?.addEventListener('click', () => { + const newInterest = prompt("请输入新的兴趣爱好:"); + if (newInterest && newInterest.trim() !== '') { + userData.interests.push(newInterest.trim()); + renderTagList('interests-container', userData.interests, null, deleteInterest); + } + }); + + document.getElementById('add-skill-btn')?.addEventListener('click', () => { + const newSkill = prompt("请输入新的生活技能:"); + if (newSkill && newSkill.trim() !== '') { + userData.skills.push(newSkill.trim()); + renderTagList('skills-container', userData.skills, null, deleteSkill); + } + }); + + document.getElementById('explore-interests-btn')?.addEventListener('click', () => { + showExploreModal('interest'); + }); + + document.getElementById('explore-skills-btn')?.addEventListener('click', () => { + showExploreModal('skill'); + }); + + document.getElementById('add-custom-module-btn')?.addEventListener('click', () => { + showAddModuleModal(); + }); +} + +function deleteInterest(index) { + userData.interests.splice(index, 1); + renderTagList('interests-container', userData.interests, null, deleteInterest); +} + +function deleteSkill(index) { + userData.skills.splice(index, 1); + renderTagList('skills-container', userData.skills, null, deleteSkill); +} + +function createCustomModule(title) { + const grid = document.getElementById('dashboard-grid'); + if(!grid) return; + + const moduleEl = document.createElement('div'); + moduleEl.className = 'bg-white p-6 rounded-xl shadow-sm lg:col-span-2 animate-fade-in-up'; + moduleEl.innerHTML = ` +
+

${title}

+
+ +
+
+
+

AI正在根据您的信息自动生成摘要... 您也可以点击右上角编辑按钮手动填写。

+
+ `; + grid.appendChild(moduleEl); + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +} + +document.addEventListener('DOMContentLoaded', () => { + renderBasicInfo(); + renderQuotes(); + renderTagList('interests-container', userData.interests, null, deleteInterest); + renderTagList('skills-container', userData.skills, null, deleteSkill); + renderMoodChart(); + setupEventListeners(); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/script.js b/开心APP网页代码v1.1/wnD97OS/script.js new file mode 100644 index 0000000..f054434 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/script.js @@ -0,0 +1,78 @@ +import { features } from './data.js'; + +document.addEventListener('DOMContentLoaded', () => { + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + + const featuresGrid = document.getElementById('features-grid'); + + if (featuresGrid) { + features.forEach((feature, index) => { + const card = document.createElement('div'); + card.className = 'feature-card-bg rounded-2xl p-6 flex flex-col items-center text-center scroll-target'; + card.style.transitionDelay = `${index * 100}ms`; + + card.innerHTML = ` +
+ ${feature.alt} +
+
+ +

${feature.title}

+
+

${feature.description}

+ `; + featuresGrid.appendChild(card); + }); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + } + + const scrollObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.1 }); + + document.querySelectorAll('.scroll-target').forEach(target => { + scrollObserver.observe(target); + }); + + + const loginButton = document.getElementById('login-button'); + const loginModal = document.getElementById('login-modal'); + const closeModalButton = document.getElementById('close-modal-button'); + + if (loginButton && loginModal && closeModalButton) { + const openModal = () => { + loginModal.classList.remove('hidden'); + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } + }; + + const closeModal = () => { + loginModal.classList.add('hidden'); + }; + + loginButton.addEventListener('click', openModal); + closeModalButton.addEventListener('click', closeModal); + loginModal.addEventListener('click', (event) => { + if (event.target === loginModal) { + closeModal(); + } + }); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && !loginModal.classList.contains('hidden')) { + closeModal(); + } + }); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/settings.html b/开心APP网页代码v1.1/wnD97OS/settings.html new file mode 100644 index 0000000..978c320 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/settings.html @@ -0,0 +1,153 @@ + + + + + + + 用户中心 - 开心APP + + + + + + + + + + + +
+ +
+
+ + + +

用户中心

+ + + +
+
+ +
+
+
+ +
+
+ +

个人资料管理

+
+
+
+ User Avatar + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+ +

会员订阅

+
+
+
+

当前状态

+

免费会员

+
+ +
+
+ + +
+
+ +

AI 聊天偏好设置

+
+
+
+
+

开启每日心情总结

+

开开会在每天晚上为你发送一份心情总结。

+
+ +
+
+
+

接收主动问候

+

允许开开在你长时间未上线时主动关心你。

+
+ +
+
+

对话风格

+
+ + + +
+
+
+
+
+
+
+ +
+ + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/settings.js b/开心APP网页代码v1.1/wnD97OS/settings.js new file mode 100644 index 0000000..7a8339b --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/settings.js @@ -0,0 +1,7 @@ +import { navLinks } from './data.js'; + +document.addEventListener('DOMContentLoaded', () => { + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +}); diff --git a/开心APP网页代码v1.1/wnD97OS/style.css b/开心APP网页代码v1.1/wnD97OS/style.css new file mode 100644 index 0000000..b191448 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/style.css @@ -0,0 +1,163 @@ +:root { + --tech-blue: #4A90E2; + --warm-orange: #F5A623; + --white: #FFFFFF; + --light-gray: #F7F8FA; + --text-dark: #333333; + --text-medium: #888888; +} + +body { + font-family: 'Noto Sans SC', sans-serif; +} + +.bg-tech-blue { background-color: var(--tech-blue); } +.bg-warm-orange { background-color: var(--warm-orange); } +.bg-light-gray { background-color: var(--light-gray); } +.text-tech-blue { color: var(--tech-blue); } +.text-text-dark { color: var(--text-dark); } +.text-text-medium { color: var(--text-medium); } +.border-tech-blue { border-color: var(--tech-blue); } + +#main-header.scrolled { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03); + border-bottom-color: #e5e7eb; +} + +.feature-card-bg { + background-color: var(--white); + border: 1px solid #e5e7eb; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.feature-card-bg:hover { + transform: translateY(-8px); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.feature-card-image-container { + background-color: #eef5fe; + background-image: url('data:image/svg+xml;utf8,'); + background-size: 50px; + background-repeat: repeat; +} + +.animate-fade-in-up { + animation: fade-in-up 0.8s ease-out forwards; + opacity: 0; +} + +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.scroll-target { + opacity: 0; + transform: translateY(30px); + transition: opacity 0.6s ease-out, transform 0.6s ease-out; +} + +.scroll-target.visible { + opacity: 1; + transform: translateY(0); +} + +.wave { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTQ0MCAxNDciIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PHRpdGxlPmdyb3VwPC90aXRsZT48ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyBpZD0iQ29tcG9uZW50LS0tV2F2ZS1Cb3R0b20iIGZpbGw9IiM0QTkwRTIiPjxwYXRoIGQ9Ik0wLDc0LjgzMjk0MTIgQzM2MCw3NC44MzI5NDEyIDM2MCwxNDcgNzIwLDE0NyBDMTA4MCwxNDcgMTA4MCw3NC44MzI5NDEyIDE0NDAsNzQuODMyOTQxMiBMMTQ0MCwxNDcgTDAsMTQ3IEwwLDc0LjgzMjk0MTIgWiIgaWQ9IldhdmUiIG9wYWNpdHk9IjAuMSI+PC9wYXRoPjwvZz48L2c+PC9zdmc+); + position: absolute; + bottom: 0; + left: 0; + width: 200%; + height: 147px; + animation: wave 15s linear infinite; +} +.wave:nth-of-type(2) { + animation-direction: reverse; + animation-duration: 20s; + opacity: 0.8; +} + +.wave:nth-of-type(3) { + animation-duration: 25s; + opacity: 0.5; +} + +@keyframes wave { + 0% { transform: translateX(0); } + 50% { transform: translateX(-50%); } + 100% { transform: translateX(0); } +} + +#chat-messages { + scrollbar-width: thin; + scrollbar-color: var(--tech-blue) var(--light-gray); +} + +#chat-messages::-webkit-scrollbar { + width: 6px; +} + +#chat-messages::-webkit-scrollbar-track { + background: var(--light-gray); +} + +#chat-messages::-webkit-scrollbar-thumb { + background-color: var(--tech-blue); + border-radius: 10px; + border: 2px solid transparent; + background-clip: content-box; +} + +.message-animate { + animation: message-fade-in 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; + opacity: 0; + transform: translateY(10px); +} + +@keyframes message-fade-in { + from { + opacity: 0; + transform: translateY(10px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +#topic-detail-modal.hidden { + display: none; +} + +#login-modal:not(.hidden) { + animation: modal-fade-in 0.2s ease-out forwards; +} + +#login-modal:not(.hidden) > div { + animation: modal-scale-up 0.2s ease-out forwards; +} + +@keyframes modal-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes modal-scale-up { + from { + transform: scale(0.95); + } + to { + transform: scale(1); + } +} diff --git a/开心APP网页代码v1.1/wnD97OS/topic_tracker.html b/开心APP网页代码v1.1/wnD97OS/topic_tracker.html new file mode 100644 index 0000000..5421fff --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/topic_tracker.html @@ -0,0 +1,125 @@ + + + + + + + 话题追踪 - 开心APP + + + + + + + + + + + +
+ +
+
+ +

话题追踪

+ + + +
+
+ +
+
+
+

洞察你的思绪,整理你的生活

+

开开会自动梳理你最近关心的事,你也可以手动创建任何想追踪的话题,见证自己的思考与成长。

+
+ +
+
+
+ +

AI 自动总结

+
+
+
+
+ +
+
+
+ +

我的话题

+
+
+
+ + +
+
+ + +
+ +
+ +
+
+ +

已创建的话题

+
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + diff --git a/开心APP网页代码v1.1/wnD97OS/topic_tracker.js b/开心APP网页代码v1.1/wnD97OS/topic_tracker.js new file mode 100644 index 0000000..25b8fe9 --- /dev/null +++ b/开心APP网页代码v1.1/wnD97OS/topic_tracker.js @@ -0,0 +1,319 @@ +document.addEventListener('DOMContentLoaded', () => { + + const TOPICS_STORAGE_KEY = 'kaixinapp_user_topics_v4'; + let nextUserTopicId = 1; + + let aiTopics = [{ + id: "ai-1", + date: "2025年7月14日", + title: "关于职业发展的思考", + summary: "近期你在日记和聊天中多次提到对目前工作的倦怠感,并探索学习新技能(如编程、设计)的可能性。", + keywords: ["职业倦怠", "新技能", "转行", "自我提升"], + timeline: [ + { + stage_name: "探索阶段 (前期)", + stage_icon: "search", + items: [ + { id: 2, date: '2025-07-10', content: '搜索了在线编程课程,感觉眼花缭乱。' }, + { id: 3, date: '2025-07-12', content: '在日记里写下了对目前工作的厌烦,感觉陷入了瓶颈。' } + ] + }, + { + stage_name: "调研阶段 (当前)", + stage_icon: "clipboard-list", + items: [ + { id: 1, date: '2025-07-14', content: '和开开聊了关于转行做设计师的可能性,开开给了一些建议。' } + ] + } + ], + next_suggestion: '尝试接触一些免费的设计工具(如Figma、Canva),完成一个名片设计或海报制作的小项目。这能帮你评估自己对设计工作的实际兴趣和基本感觉。' + }, { + id: "ai-2", + date: "2025年7月10日", + title: "周末出游计划", + summary: "你似乎在计划一个短途旅行,多次查询关于海边城市的天气和美食推荐。", + keywords: ["旅行", "海边", "美食", "放松"], + timeline: [ + { + stage_name: "萌芽阶段 (前期)", + stage_icon: "sprout", + items: [ + { id: 2, date: '2025-07-09', content: '天气好热,突然想去海边玩。' } + ] + }, + { + stage_name: "计划阶段 (当前)", + stage_icon: "map", + items: [ + { id: 1, date: '2025-07-10', content: '问开开哪个海边城市人少又好玩,它推荐了几个小众地点。' }, + ] + } + ], + next_suggestion: '可以开始查看交通和住宿了,早点预定选择更多哦!如果需要,开开可以帮你对比价格。' + }]; + + let userTopics = []; + + const aiSummaryContainer = document.getElementById('ai-summary-list'); + const userTopicsListContainer = document.getElementById('user-topics-list'); + const newTopicForm = document.getElementById('new-topic-form'); + + const modal = document.getElementById('topic-detail-modal'); + const modalTitle = document.getElementById('modal-topic-title'); + const modalDate = document.getElementById('modal-topic-date'); + const closeModalBtn = document.getElementById('close-modal-btn'); + const addEntryForm = document.getElementById('add-entry-form'); + let activeTopicId = null; + + function loadTopicsFromStorage() { + const stored = localStorage.getItem(TOPICS_STORAGE_KEY); + if (stored) { + userTopics = JSON.parse(stored); + const maxId = userTopics.reduce((max, t) => Math.max(max, parseInt(t.id.split('-')[1])), 0); + nextUserTopicId = maxId + 1; + } else { + userTopics = [{ + id: `user-${nextUserTopicId++}`, + title: "暑期健身计划", + date: "2025年7月15日", + keywords: ["健康", "运动", "自律"], + timeline: [{ + stage_name: "启动阶段 (当前)", + stage_icon: "rocket", + items: [ + { id: 1, date: '2025-07-15', content: "今天制定了计划:每周至少三次有氧运动,两次力量训练。记录每日饮食,控制热量摄入。" } + ] + }], + next_suggestion: '找一个伙伴一起监督,或者使用App记录进程,增加成就感。' + }]; + } + } + + function saveTopicsToStorage() { + localStorage.setItem(TOPICS_STORAGE_KEY, JSON.stringify(userTopics)); + } + + function renderAiTopics() { + if (!aiSummaryContainer) return; + aiSummaryContainer.innerHTML = aiTopics.map(topic => ` +
+

${topic.date}

+

${topic.title}

+

${topic.summary}

+
+ ${topic.keywords.map(k => `${k}`).join('')} +
+
`).join(''); + aiSummaryContainer.querySelectorAll('[data-topic-id]').forEach(el => el.addEventListener('click', () => openModal(el.dataset.topicId))); + } + + function renderUserTopics() { + if (!userTopicsListContainer) return; + const getLastEntryContent = (topic) => { + if (!topic.timeline || topic.timeline.length === 0) return "暂无内容"; + const lastStage = topic.timeline[topic.timeline.length - 1]; + if (!lastStage.items || lastStage.items.length === 0) return "暂无内容"; + return lastStage.items[lastStage.items.length - 1].content; + }; + + userTopicsListContainer.innerHTML = userTopics.length === 0 ? `

还没有创建话题哦

` : + userTopics.map(topic => ` +
+
+
+

${topic.title}

+

${getLastEntryContent(topic)}

+
+ +
+
`).join(''); + + userTopicsListContainer.querySelectorAll('[data-topic-id]').forEach(el => el.addEventListener('click', () => openModal(el.dataset.topicId))); + userTopicsListContainer.querySelectorAll('[data-delete-id]').forEach(el => el.addEventListener('click', (e) => { + e.stopPropagation(); + if(confirm('确定要删除这个话题吗?此操作无法撤销。')) { + deleteTopic(el.dataset.deleteId); + } + })); + if (typeof lucide !== 'undefined') lucide.createIcons(); + } + + function deleteTopic(topicId) { + userTopics = userTopics.filter(t => t.id !== topicId); + saveTopicsToStorage(); + renderUserTopics(); + } + + function openModal(topicId) { + const topic = [...aiTopics, ...userTopics].find(t => t.id === topicId); + if (!topic) return; + activeTopicId = topicId; + + const isUserTopic = activeTopicId.startsWith('user-'); + document.getElementById('add-entry-form').style.display = isUserTopic ? 'flex' : 'none'; + document.querySelector('#add-entry-form').previousElementSibling.style.display = isUserTopic ? 'block' : 'none'; + + modalTitle.textContent = topic.title; + modalDate.textContent = `创建于 ${topic.date}`; + + renderTopicTimeline(topic); + modal.classList.remove('hidden'); + modal.classList.add('flex'); + } + + function renderTopicTimeline(topic){ + const timelineContainer = document.getElementById('modal-timeline-container'); + const suggestionContainer = document.getElementById('modal-suggestion-container'); + timelineContainer.innerHTML = ''; + suggestionContainer.innerHTML = ''; + + const isUserTopic = topic.id.startsWith('user-'); + + if(topic.timeline && topic.timeline.length > 0) { + topic.timeline.forEach((stage, stageIndex) => { + const isCurrentStage = stageIndex === topic.timeline.length - 1; + const stageHtml = ` +
+
+
+ +
+

${stage.stage_name}

+ ${isCurrentStage ? '当前' : ''} +
+
+ ${stage.items.sort((a,b) => new Date(b.date) - new Date(a.date)).map(entry => ` +
+
+
+
+

${new Date(entry.date).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}

+ ${isUserTopic ? `` : ''} +
+

${entry.content.replace(/\\n/g, '
')}

+
+
+ `).join('')} +
+
+ `; + timelineContainer.innerHTML += stageHtml; + }); + } + + if (topic.next_suggestion) { + suggestionContainer.innerHTML = ` +
+ +
+

开开的下一步建议

+

${topic.next_suggestion}

+
+
`; + } + + timelineContainer.querySelectorAll('[data-delete-entry-id]').forEach(btn => { + btn.addEventListener('click', () => deleteTopicEntry(btn.dataset.deleteEntryId)); + }); + if (typeof lucide !== 'undefined') lucide.createIcons(); + } + + function deleteTopicEntry(entryId) { + const topic = userTopics.find(t => t.id === activeTopicId); + if (!topic) return; + + const totalEntries = topic.timeline.reduce((acc, stage) => acc + stage.items.length, 0); + if (totalEntries <= 1) { + alert('每个话题至少需要一条记录。如不需此话题,可直接删除整个话题卡片。'); + return; + } + + topic.timeline.forEach(stage => { + stage.items = stage.items.filter(entry => entry.id.toString() !== entryId.toString()); + }); + topic.timeline = topic.timeline.filter(stage => stage.items.length > 0); + + saveTopicsToStorage(); + renderTopicTimeline(topic); + } + + function closeModal() { + activeTopicId = null; + modal.classList.add('hidden'); + modal.classList.remove('flex'); + } + + newTopicForm.addEventListener('submit', (e) => { + e.preventDefault(); + const title = e.target.elements.title.value.trim(); + const content = e.target.elements.content.value.trim(); + if (title && content) { + const newTopic = { + id: `user-${nextUserTopicId++}`, + title: title, + date: new Date().toLocaleDateString('zh-CN'), + keywords: ["自定义"], + timeline: [{ + stage_name: "启动阶段 (当前)", + stage_icon: "rocket", + items: [{ id: 1, date: new Date().toISOString().split('T')[0], content: content }] + }], + next_suggestion: '将大目标分解成几个可执行的小步骤吧!' + }; + userTopics.unshift(newTopic); + saveTopicsToStorage(); + renderUserTopics(); + e.target.reset(); + } + }); + + addEntryForm.addEventListener('submit', (e) => { + e.preventDefault(); + const contentEl = e.target.elements['new-entry-content']; + const content = contentEl.value.trim(); + if (!content || !activeTopicId) return; + + const topic = userTopics.find(t => t.id === activeTopicId); + if(!topic) return; + + let maxId = 0; + topic.timeline.forEach(stage => { + maxId = Math.max(maxId, ...stage.items.map(item => item.id)); + }); + + const newEntry = { + id: maxId + 1, + date: new Date().toISOString().split('T')[0], + content: content + }; + + if (topic.timeline.length > 0) { + topic.timeline[topic.timeline.length - 1].items.push(newEntry); + } else { + topic.timeline.push({ + stage_name: "新进展 (当前)", + stage_icon: "plus", + items: [newEntry] + }) + } + + saveTopicsToStorage(); + renderTopicTimeline(topic); + contentEl.value = ''; + }); + + closeModalBtn.addEventListener('click', closeModal); + modal.addEventListener('click', (e) => { + if (e.target === modal) closeModal(); + }); + + loadTopicsFromStorage(); + renderAiTopics(); + renderUserTopics(); + + if (typeof lucide !== 'undefined') { + lucide.createIcons(); + } +});