From 9dea8f3b2a4e102cea359bc1fcc8e8d5e9d6793c Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Tue, 23 Dec 2025 16:56:22 +0800 Subject: [PATCH] =?UTF-8?q?workcase=20=20web=20=20ai=E8=81=8A=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/sql/createTableWorkcase.sql | 2 +- .../xyzh/ai/controller/ChatController.java | 24 +- .../ai/service/impl/AgentChatServiceImpl.java | 27 ++ .../xyzh/api/ai/service/AgentChatService.java | 9 + .../controller/WorkcaseChatContorller.java | 7 +- .../packages/shared/src/api/ai/aichat.ts | 7 +- .../packages/workcase/src/config/index.ts | 2 +- .../packages/workcase/src/types/shared.d.ts | 11 +- .../src/views/public/AIChat/AIChatView.scss | 49 +++ .../src/views/public/AIChat/AIChatView.vue | 327 ++++++++++++++---- .../views/public/ChatRoom/ChatRoomView.vue | 2 +- .../workcase_wechat/pages/index/index.uvue | 51 ++- 12 files changed, 430 insertions(+), 88 deletions(-) diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql index 127f732f..b2118862 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql @@ -30,7 +30,7 @@ DROP TABLE IF EXISTS workcase.tb_chat_room CASCADE; CREATE TABLE workcase.tb_chat_room( optsn VARCHAR(50) NOT NULL, -- 流水号 room_id VARCHAR(50) NOT NULL, -- 聊天室ID - workcase_id VARCHAR(50) NOT NULL, -- 关联工单ID + workcase_id VARCHAR(50) DEFAULT NULL, -- 关联工单ID room_name VARCHAR(200) NOT NULL, -- 聊天室名称(如:工单#12345的客服支持) room_type VARCHAR(20) NOT NULL DEFAULT 'workcase', -- 聊天室类型:workcase-工单客服 status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-活跃 closed-已关闭 archived-已归档 diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/ChatController.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/ChatController.java index ff5ae510..bc009ef0 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/ChatController.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/ChatController.java @@ -16,6 +16,8 @@ import org.xyzh.api.ai.service.AgentChatService; import org.xyzh.common.auth.utils.LoginUtil; import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.core.page.PageDomain; +import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.utils.NonUtils; import org.xyzh.common.utils.validation.ValidationParam; import org.xyzh.common.utils.validation.ValidationResult; @@ -128,7 +130,7 @@ public class ChatController { * @author yslg * @since 2025-12-17 */ - @GetMapping("/conversations") + @PostMapping("/conversation/list") public ResultDomain getChatList(@RequestBody TbChat filter, @RequestHeader("Authorization") String token) { log.info("获取会话列表: agentId={}", filter.getAgentId()); @@ -142,6 +144,26 @@ public class ChatController { return chatService.getChatList(filter); } + /** + * @description 分页获取对话列表 + * @param pageRequest 分页请求参数 + * @author yslg + * @since 2025-12-17 + */ + @PostMapping("/conversation/page") + public ResultDomain> getChatPage(@RequestBody PageRequest pageRequest, @RequestHeader("Authorization") String token) { + log.info("分页获取会话列表: agentId={}", pageRequest.getFilter().getAgentId()); + + pageRequest.getFilter().setUserType(false); + if(NonUtils.isNotEmpty(token)){ + LoginDomain loginDomain = LoginUtil.getCurrentLogin(); + if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") { + pageRequest.getFilter().setUserType(true); + } + } + return chatService.getChatPage(pageRequest); + } + // ====================== 消息管理 ====================== /** diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/AgentChatServiceImpl.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/AgentChatServiceImpl.java index 40416218..4fdd10ef 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/AgentChatServiceImpl.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/AgentChatServiceImpl.java @@ -22,6 +22,9 @@ import org.xyzh.api.ai.dto.TbChatMessage; import org.xyzh.api.ai.service.AgentChatService; import org.xyzh.api.ai.service.AgentService; import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.core.page.PageDomain; +import org.xyzh.common.core.page.PageParam; +import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.redis.service.RedisService; import org.xyzh.common.utils.id.IdUtil; import org.xyzh.common.auth.utils.LoginUtil; @@ -200,6 +203,30 @@ public class AgentChatServiceImpl implements AgentChatService { return ResultDomain.success("查询成功", chatList); } + @Override + public ResultDomain> getChatPage(PageRequest pageRequest) { + TbChat filter = pageRequest.getFilter(); + // 判断agent是否是outer(来客才需要校验) + if (!filter.getUserType() && !isOuterAgent(filter.getAgentId())) { + return ResultDomain.>failure("智能体不可用"); + } + // 获取用户ID + String userId = getUserIdByType(filter); + if (userId == null) { + return ResultDomain.>failure("用户信息获取失败"); + } + filter.setUserId(userId); + + // 分页查询 + PageParam pageParam = pageRequest.getPageParam(); + List chatList = chatMapper.selectChatPage(filter, pageParam); + long total = chatMapper.countChats(filter); + pageParam.setTotal((int) total); + + PageDomain pageDomain = new PageDomain<>(pageParam, chatList); + return ResultDomain.>success("查询成功", pageDomain); + } + // ====================== 智能体聊天管理 ====================== @Override diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/AgentChatService.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/AgentChatService.java index 454cc272..dc0fe924 100644 --- a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/AgentChatService.java +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/AgentChatService.java @@ -5,6 +5,8 @@ import org.xyzh.api.ai.dto.ChatPrepareData; import org.xyzh.api.ai.dto.TbChat; import org.xyzh.api.ai.dto.TbChatMessage; import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.core.page.PageDomain; +import org.xyzh.common.core.page.PageRequest; public interface AgentChatService { @@ -41,6 +43,13 @@ public interface AgentChatService { */ ResultDomain getChatList(TbChat filter); + /** + * 分页获取会话列表 + * @param pageRequest 分页请求参数 + * @return 分页会话列表 + */ + ResultDomain> getChatPage(PageRequest pageRequest); + // ====================== 智能体聊天管理 ====================== diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java index 39b6f912..4eff66f3 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java @@ -68,7 +68,12 @@ public class WorkcaseChatContorller { if (!vr.isValid()) { return ResultDomain.failure(vr.getAllErrors()); } - return chatRoomService.createChatRoom(chatRoom); + chatRoom.setCreator(chatRoom.getGuestId()); + try { + return chatRoomService.createChatRoom(chatRoom); + } catch (Exception e) { + return ResultDomain.failure(e.getMessage()); + } } @Operation(summary = "更新聊天室") diff --git a/urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts b/urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts index 9306ce04..104eb959 100644 --- a/urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts +++ b/urbanLifelineWeb/packages/shared/src/api/ai/aichat.ts @@ -1,4 +1,5 @@ import { api } from '@/api/index' +import { API_BASE_URL } from '@/config' import type { ResultDomain } from '@/types' import type { TbChat, TbChatMessage, ChatPrepareData, StopChatParam, CommentMessageParam, DifyFileInfo } from '@/types/ai' @@ -37,7 +38,7 @@ export const aiChatAPI = { * @param chat 会话信息 */ async deleteChat(chat: TbChat): Promise> { - const response = await api.delete(`${this.baseUrl}/conversation`, { data: chat }) + const response = await api.delete(`${this.baseUrl}/conversation`, chat ) return response.data }, @@ -46,7 +47,7 @@ export const aiChatAPI = { * @param filter 筛选条件 */ async getChatList(filter: TbChat): Promise> { - const response = await api.get(`${this.baseUrl}/conversations`, { data: filter }) + const response = await api.post(`${this.baseUrl}/conversation/list`, filter) return response.data }, @@ -77,7 +78,7 @@ export const aiChatAPI = { * @param sessionId 会话ID */ getStreamChatUrl(sessionId: string): string { - return `${this.baseUrl}/stream?sessionId=${sessionId}` + return `${API_BASE_URL}${this.baseUrl}/stream?sessionId=${sessionId}` }, /** diff --git a/urbanLifelineWeb/packages/workcase/src/config/index.ts b/urbanLifelineWeb/packages/workcase/src/config/index.ts index 27fa0b63..ed2be281 100644 --- a/urbanLifelineWeb/packages/workcase/src/config/index.ts +++ b/urbanLifelineWeb/packages/workcase/src/config/index.ts @@ -22,7 +22,7 @@ * Base64 编码的 32 字节密钥(256 位) */ export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节) - +export const AGENT_ID = '17664699513920001' // ============================================ // 类型定义 // ============================================ diff --git a/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts b/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts index 89e856fc..dd2db806 100644 --- a/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts +++ b/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts @@ -108,8 +108,15 @@ declare module 'shared/types' { TbChatMessage, DifyFileInfo, ChatPrepareData, - StopChatParams, - CommentMessageParams + CreateChatParam, + PrepareChatParam, + StopChatParam, + CommentMessageParam, + ChatListParam, + ChatMessageListParam, + SSEMessageData, + SSECallbacks, + SSETask } from '../../../shared/src/types/ai' // 重新导出 menu diff --git a/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.scss b/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.scss index 5b820fcc..e636a50e 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.scss +++ b/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.scss @@ -365,6 +365,20 @@ $brand-color-hover: #004488; margin: 0 auto; padding: 24px 16px; + .chat-header { + text-align: center; + padding: 16px 0 24px; + border-bottom: 1px solid #f1f5f9; + margin-bottom: 24px; + + .chat-title { + font-size: 18px; + font-weight: 600; + color: #1e293b; + margin: 0; + } + } + .message-row { display: flex; gap: 12px; @@ -421,6 +435,31 @@ $brand-color-hover: #004488; word-wrap: break-word; } + .typing-cursor { + display: inline-block; + animation: blink 0.8s infinite; + color: $brand-color; + font-weight: bold; + } + + .loading-dots { + display: flex; + gap: 4px; + padding: 4px 0; + + span { + width: 6px; + height: 6px; + background: #94a3b8; + border-radius: 50%; + animation: bounce 1.4s infinite ease-in-out both; + + &:nth-child(1) { animation-delay: -0.32s; } + &:nth-child(2) { animation-delay: -0.16s; } + &:nth-child(3) { animation-delay: 0s; } + } + } + .message-time { font-size: 12px; color: #94a3b8; @@ -429,6 +468,16 @@ $brand-color-hover: #004488; } } +@keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } +} + +@keyframes bounce { + 0%, 80%, 100% { transform: scale(0); } + 40% { transform: scale(1); } +} + // ==================== 快捷命令栏 ==================== .quick-bar { padding: 8px 16px; diff --git a/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.vue b/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.vue index e69cde4b..c10f1fac 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.vue +++ b/urbanLifelineWeb/packages/workcase/src/views/public/AIChat/AIChatView.vue @@ -8,10 +8,10 @@
-
{{ conv.title }}
-
{{ conv.time }}
+
{{ conv.title || '新对话' }}
+
{{ formatTime(conv.createTime) }}
-
+ +
+

{{ currentChatTitle }}

+
-

{{ msg.text }}

+

+ {{ msg.text }} + | +

+
+ +
{{ msg.time }}
@@ -185,7 +194,7 @@