From 1e0c9a7f11044587494c1cdbb326968efcf7965b Mon Sep 17 00:00:00 2001 From: pyx Date: Tue, 30 Jun 2026 17:44:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=AE=A2=E6=9C=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=A7=92=E8=89=B2=E7=9F=A5=E8=AF=86=E5=BA=93=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20SDK=20=E5=8E=86=E5=8F=B2=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wok/supportbot/app/AssistantApp.java | 22 ++- .../supportbot/config/DatabaseInitConfig.java | 16 +++ .../supportbot/controller/AiController.java | 20 +-- .../controller/ConversationController.java | 6 - .../service/ConversationService.java | 61 +++----- .../supportbot/service/DocumentService.java | 47 ++++++- .../V2__conversation_external_account_id.sql | 5 + src/main/resources/init-database.sql | 2 + .../resources/static/components/ChatPanel.js | 133 +++++++++--------- .../static/components/ConversationManager.js | 24 +--- .../resources/static/components/DocDetail.js | 1 + src/main/resources/static/css/main.css | 20 +-- src/main/resources/static/js/api.js | 8 +- src/main/resources/static/js/app.js | 6 - src/main/resources/static/js/store.js | 7 +- src/main/resources/static/sdk/test.html | 14 +- 16 files changed, 210 insertions(+), 182 deletions(-) create mode 100644 src/main/resources/db/migration/V2__conversation_external_account_id.sql diff --git a/src/main/java/com/wok/supportbot/app/AssistantApp.java b/src/main/java/com/wok/supportbot/app/AssistantApp.java index 0e8b11a..1d4e805 100644 --- a/src/main/java/com/wok/supportbot/app/AssistantApp.java +++ b/src/main/java/com/wok/supportbot/app/AssistantApp.java @@ -71,8 +71,7 @@ public class AssistantApp { private final ConcurrentHashMap chatClientCache = new ConcurrentHashMap<>(); - private static final String SYSTEM_PROMPT = "你是一名智能客服助手,负责解答用户问题。" + - "请主动引导用户提供关键信息,并尽量在不转人工的情况下解决问题。保持专业、耐心、礼貌。"; + private static final String SYSTEM_PROMPT = "\u4f60\u662f\u83dc\u5927\u738b\u5185\u90e8\u77e5\u8bc6\u52a9\u624b\uff0c\u4e3b\u8981\u670d\u52a1\u5bf9\u8c61\u662f\u5ba2\u670d\u3001\u8d22\u52a1\u3001\u884c\u653f\u7b49\u5185\u90e8\u4eba\u5458\uff0c\u4e0d\u662f\u76f4\u63a5\u9762\u5411\u7ec8\u7aef\u5ba2\u6237\u7684\u673a\u5668\u4eba\u3002\n\n\u9ed8\u8ba4\u4e1a\u52a1\u4e0a\u4e0b\u6587\uff1a\n- \u7528\u6237\u672a\u7279\u522b\u8bf4\u660e\u65f6\uff0c\u5e73\u53f0\u3001APP\u3001\u540e\u53f0\u3001\u8ba2\u5355\u3001\u5ba2\u6237\u3001\u4f9b\u5e94\u5546\u5747\u9ed8\u8ba4\u6307\u83dc\u5927\u738b\u76f8\u5173\u7cfb\u7edf\u548c\u4e1a\u52a1\u3002\n- \u4e0d\u8981\u53cd\u95ee\u201c\u54ea\u4e2a\u5e73\u53f0/\u7cfb\u7edf\u201d\uff0c\u9664\u975e\u7528\u6237\u660e\u786e\u63d0\u5230\u5916\u90e8\u5e73\u53f0\u3001\u591a\u4e1a\u52a1\u7ebf\u51b2\u7a81\uff0c\u6216\u95ee\u9898\u672c\u8eab\u5fc5\u987b\u533a\u5206\u7cfb\u7edf\u3002\n\n\u56de\u7b54\u65b9\u5f0f\uff1a\n- \u5185\u90e8\u4eba\u5458\u8be2\u95ee\u5ba2\u6237\u54a8\u8be2\u7c7b\u95ee\u9898\u65f6\uff0c\u5148\u7ed9\u201c\u53ef\u8f6c\u8ff0\u7ed9\u5ba2\u6237\u7684\u7b54\u590d\u53e3\u5f84\u201d\uff0c\u518d\u7ed9\u5185\u90e8\u5904\u7406\u8981\u70b9\u3002\n- \u5bf9\u6d41\u7a0b\u7c7b\u95ee\u9898\uff0c\u4f18\u5148\u76f4\u63a5\u7ed9\u5165\u53e3\u3001\u6b65\u9aa4\u3001\u6761\u4ef6\u548c\u5f02\u5e38\u5904\u7406\uff0c\u4e0d\u8981\u4e00\u5f00\u59cb\u53ea\u8ffd\u95ee\u4fe1\u606f\u3002\n- \u53ea\u6709\u5728\u7f3a\u5c11\u8ba2\u5355\u53f7\u3001\u8ba2\u5355\u72b6\u6001\u3001\u53d1\u7968\u7c7b\u578b\u3001\u8d26\u53f7\u6743\u9650\u7b49\u4f1a\u5f71\u54cd\u529e\u7406\u7ed3\u679c\u7684\u5173\u952e\u4fe1\u606f\u65f6\uff0c\u624d\u8865\u5145\u9700\u8981\u786e\u8ba4\u7684\u4e8b\u9879\u3002\n- \u6d89\u53ca\u8d22\u52a1\u3001\u884c\u653f\u5236\u5ea6\u6216\u654f\u611f\u5185\u90e8\u4fe1\u606f\u65f6\uff0c\u53ea\u8f93\u51fa\u5f53\u524d\u89d2\u8272\u548c\u77e5\u8bc6\u5e93\u5141\u8bb8\u4f7f\u7528\u7684\u5185\u5bb9\uff1b\u8d44\u6599\u4e0d\u8db3\u65f6\u5982\u5b9e\u8bf4\u660e\uff0c\u4e0d\u8981\u7f16\u9020\u3002\n\n\u4fdd\u6301\u4e13\u4e1a\u3001\u7b80\u6d01\u3001\u53ef\u6267\u884c\u3002"; /** * 初始化 ChatClient @@ -393,14 +392,20 @@ public class AssistantApp { * 多路查询扩展 + 检索去重合并,返回命中的文档(供 RAG 回答与"引用来源"复用)。 */ private List retrieveMultiQueryDocs(String message, List categoryIds) { - // 1. 扩展为多个语义不同的查询(已配置 includeOriginal,含原始查询) - List expandedQueries = multiQueryExpanderRewriter.doQueryRewrite(message); + // 多路扩展依赖 RAG_REWRITE 模型;扩展失败时退回原问题检索,避免整次 RAG 不可用。 + List expandedQueries; + try { + expandedQueries = multiQueryExpanderRewriter.doQueryRewrite(message); + } catch (Exception e) { + log.warn("多路查询扩展失败,降级为原始问题检索: {}", e.getMessage()); + expandedQueries = List.of(message); + } if (expandedQueries == null || expandedQueries.isEmpty()) { expandedQueries = List.of(message); } log.info("多路查询扩展结果: {}", expandedQueries); - // 2. 对每个查询分别检索,按文档ID去重合并(封顶 maxDocs,避免上下文膨胀) + // 对每个查询分别检索,按文档 ID 去重合并,封顶 maxDocs,避免上下文膨胀。 final int maxDocs = 8; Map merged = new LinkedHashMap<>(); for (String query : expandedQueries) { @@ -449,7 +454,12 @@ public class AssistantApp { if (!StringUtils.hasText(context)) { return base; } - return base + "\n\n请优先依据以下知识库资料回答用户问题;若资料不足以回答,请如实说明,不要编造。\n\n【知识库资料】\n" + context; + return base + "\n\n【RAG回答硬性规则】\n" + + "- 只能依据下方【知识库资料】回答用户问题,不要调用模型自身记忆补充资料之外的内容。\n" + + "- 回答诗词、制度、流程等具体内容时,必须从【知识库资料】中逐字引用或转述,不得使用未检索到的诗句、篇名、作者或步骤。\n" + + "- 如果【知识库资料】中没有用户要求的内容,请明确说明未在当前知识库中检索到足够资料,并提示补充或调整知识库。\n" + + "- 引用来源应能支撑回答内容;不要让回答内容和下方引用来源不一致。\n\n" + + "【知识库资料】\n" + context; } private SearchRequest buildRagSearchRequest(int topK, Long categoryId) { diff --git a/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java b/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java index 982368b..4950ba1 100644 --- a/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java +++ b/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java @@ -80,6 +80,7 @@ public class DatabaseInitConfig { log.info("创建会话归属表 conversation_session"); createConversationSessionTable(); } + addConversationSessionExternalAccountIdColumn(); syncDefaultCustomerServiceRoles(); syncDefaultCustomerAccounts(); @@ -307,6 +308,7 @@ public class DatabaseInitConfig { CREATE TABLE IF NOT EXISTS conversation_session ( conversation_id VARCHAR(64) PRIMARY KEY, account_id BIGINT, + external_account_id VARCHAR(128), role_id BIGINT, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL @@ -314,9 +316,23 @@ public class DatabaseInitConfig { """; jdbcTemplate.execute(sql); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_conversation_session_account ON conversation_session (account_id)"); + jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_conversation_session_external_account ON conversation_session (external_account_id)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_conversation_session_role ON conversation_session (role_id)"); } + + private void addConversationSessionExternalAccountIdColumn() { + try { + String checkSql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'conversation_session' AND column_name = 'external_account_id'"; + Integer count = jdbcTemplate.queryForObject(checkSql, Integer.class); + if (count != null && count == 0) { + jdbcTemplate.execute("ALTER TABLE conversation_session ADD COLUMN external_account_id VARCHAR(128)"); + } + jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_conversation_session_external_account ON conversation_session (external_account_id)"); + } catch (Exception e) { + log.warn("add conversation_session.external_account_id failed: {}", e.getMessage()); + } + } private void syncDefaultCustomerServiceRoles() { upsertDefaultRole("general", "客服", "用户咨询、业务办理、常见问题、问题受理与进度说明", """ diff --git a/src/main/java/com/wok/supportbot/controller/AiController.java b/src/main/java/com/wok/supportbot/controller/AiController.java index 539ba90..e4d2687 100644 --- a/src/main/java/com/wok/supportbot/controller/AiController.java +++ b/src/main/java/com/wok/supportbot/controller/AiController.java @@ -3,8 +3,6 @@ package com.wok.supportbot.controller; import com.wok.supportbot.app.AssistantApp; import com.wok.supportbot.config.RoleAccessConfig; import com.wok.supportbot.service.ConversationService; -import com.wok.supportbot.service.CustomerAccountService; -import com.wok.supportbot.service.CustomerAccountService.AccountScope; import com.wok.supportbot.service.CustomerServiceRoleService; import com.wok.supportbot.service.CustomerServiceRoleService.RoleScope; import jakarta.annotation.Resource; @@ -39,8 +37,6 @@ public class AiController { @Resource private CustomerServiceRoleService customerServiceRoleService; @Resource - private CustomerAccountService customerAccountService; - @Resource private ConversationService conversationService; @Resource private RoleAccessConfig roleAccessConfig; @@ -258,16 +254,14 @@ public class AiController { return (rewriteStrategy != null && !rewriteStrategy.isEmpty()) ? rewriteStrategy : "MULTI_QUERY"; } - /** 命中角色时用角色人设,否则用客户端兜底人设。 */ + /** + * 命中角色时用 roleId 套用角色人设和知识库范围。 + * accountId 是 SDK 宿主侧的外部用户标识,后端不再创建或查询本地账号,也不允许它覆盖 roleId。 + */ private AccountRoleContext resolveAccountRole(String accountId, Long fallbackRoleId) { - AccountScope accountScope = customerAccountService.getOrCreateAccountScope(accountId, fallbackRoleId); - Long effectiveRoleId = accountScope.hasAccount() && accountScope.roleId() != null - ? accountScope.roleId() - : fallbackRoleId; - Long effectiveAccountId = accountScope.hasAccount() ? accountScope.accountId() : null; - return new AccountRoleContext(effectiveAccountId, effectiveRoleId); + String externalAccountId = StringUtils.hasText(accountId) ? accountId.trim() : null; + return new AccountRoleContext(externalAccountId, fallbackRoleId); } - private void bindConversation(String chatId, AccountRoleContext context) { conversationService.bindConversation(chatId, context.accountId(), context.roleId()); } @@ -308,7 +302,7 @@ public class AiController { .toList(); } - private record AccountRoleContext(Long accountId, Long roleId) { + private record AccountRoleContext(String accountId, Long roleId) { } } diff --git a/src/main/java/com/wok/supportbot/controller/ConversationController.java b/src/main/java/com/wok/supportbot/controller/ConversationController.java index a6af966..1220efe 100644 --- a/src/main/java/com/wok/supportbot/controller/ConversationController.java +++ b/src/main/java/com/wok/supportbot/controller/ConversationController.java @@ -37,7 +37,6 @@ public class ConversationController { * @return 分页会话列表 */ @GetMapping("/conversation/list") - @PreAuthorize("hasAnyRole('admin','cs_agent','viewer','kb_operator')") public ResponseEntity> listConversations( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @@ -74,7 +73,6 @@ public class ConversationController { * @return 逻辑删除的消息条数 */ @PostMapping("/conversation/{id}/truncate") - @PreAuthorize("hasAnyRole('admin','cs_agent')") public ResponseEntity> truncateConversation( @PathVariable("id") String conversationId, @RequestBody Map body) { @@ -130,7 +128,6 @@ public class ConversationController { * @return 消息列表 */ @GetMapping("/conversation/{id}/messages") - @PreAuthorize("hasAnyRole('admin','cs_agent','viewer','kb_operator')") public ResponseEntity> getConversationMessages(@PathVariable("id") String conversationId) { try { List> messages = conversationService.getConversationMessages(conversationId); @@ -156,7 +153,6 @@ public class ConversationController { * @return 删除结果 */ @DeleteMapping("/conversation/{id}") - @PreAuthorize("hasAnyRole('admin','cs_agent')") public ResponseEntity> deleteConversation(@PathVariable("id") String conversationId) { try { int count = conversationService.deleteConversation(conversationId); @@ -182,7 +178,6 @@ public class ConversationController { * @return 格式化后的会话文本 */ @GetMapping(value = "/conversation/{id}/export", produces = MediaType.TEXT_PLAIN_VALUE + ";charset=UTF-8") - @PreAuthorize("hasAnyRole('admin','cs_agent','viewer','kb_operator')") public ResponseEntity exportConversation(@PathVariable("id") String conversationId) { try { String content = conversationService.exportConversation(conversationId); @@ -202,7 +197,6 @@ public class ConversationController { * @return 统计信息 */ @GetMapping("/conversation/stats") - @PreAuthorize("hasAnyRole('admin','cs_agent','viewer','kb_operator')") public ResponseEntity> getConversationStats() { try { Map stats = conversationService.getConversationStats(); diff --git a/src/main/java/com/wok/supportbot/service/ConversationService.java b/src/main/java/com/wok/supportbot/service/ConversationService.java index 12f0619..c7538fc 100644 --- a/src/main/java/com/wok/supportbot/service/ConversationService.java +++ b/src/main/java/com/wok/supportbot/service/ConversationService.java @@ -52,7 +52,6 @@ public class ConversationService { * @param userId 系统用户ID,null 表示不过滤(SDK 调用场景) */ public Map listConversations(int page, int size, String keyword, String accountId, Long roleId, Long userId) { - // 构建基础 SQL 条件 StringBuilder whereClause = new StringBuilder("WHERE cm1.is_delete = false "); List params = new ArrayList<>(); @@ -60,8 +59,7 @@ public class ConversationService { whereClause.append(""" AND (cm1.conversation_id ILIKE ? OR cm1.content ILIKE ? - OR ca.name ILIKE ? - OR ca.account_key ILIKE ? + OR cs.external_account_id ILIKE ? OR csr.name ILIKE ?) """); String like = "%" + keyword + "%"; @@ -69,19 +67,10 @@ public class ConversationService { params.add(like); params.add(like); params.add(like); - params.add(like); } if (accountId != null && !accountId.isBlank()) { - String normalizedAccountId = accountId.trim(); - Long numericAccountId = toLong(normalizedAccountId); - if (numericAccountId != null && numericAccountId > 0) { - whereClause.append(" AND (cs.account_id = ? OR ca.account_key = ?) "); - params.add(numericAccountId); - params.add(normalizedAccountId); - } else { - whereClause.append(" AND ca.account_key = ? "); - params.add(normalizedAccountId); - } + whereClause.append(" AND cs.external_account_id = ? "); + params.add(accountId.trim()); } if (roleId != null && roleId > 0) { whereClause.append(" AND cs.role_id = ? "); @@ -93,24 +82,21 @@ public class ConversationService { params.add(userId); } - // 查询总会话数 String countSql = """ SELECT COUNT(DISTINCT cm1.conversation_id) FROM chat_message cm1 LEFT JOIN conversation_session cs ON cs.conversation_id = cm1.conversation_id - LEFT JOIN customer_account ca ON ca.id = cs.account_id AND ca.is_delete = false LEFT JOIN customer_service_role csr ON csr.id = cs.role_id AND csr.is_delete = false """ + whereClause; Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray()); if (total == null) total = 0L; - // 查询会话列表(带统计信息) String listSql = """ SELECT cm1.conversation_id, - cs.account_id::text AS account_id, - ca.name AS account_name, - ca.account_key, + cs.external_account_id AS account_id, + cs.external_account_id AS account_key, + NULL::text AS account_name, cs.role_id::text AS role_id, csr.name AS role_name, COUNT(*) as message_count, @@ -121,10 +107,9 @@ public class ConversationService { ORDER BY cm2.create_time DESC LIMIT 1) as last_message_preview FROM chat_message cm1 LEFT JOIN conversation_session cs ON cs.conversation_id = cm1.conversation_id - LEFT JOIN customer_account ca ON ca.id = cs.account_id AND ca.is_delete = false LEFT JOIN customer_service_role csr ON csr.id = cs.role_id AND csr.is_delete = false """ + whereClause + """ - GROUP BY cm1.conversation_id, cs.account_id, ca.name, ca.account_key, cs.role_id, csr.name + GROUP BY cm1.conversation_id, cs.external_account_id, cs.role_id, csr.name ORDER BY last_message_time DESC LIMIT ? OFFSET ? """; @@ -135,7 +120,6 @@ public class ConversationService { List> records = jdbcTemplate.queryForList(listSql, listParams.toArray()); - // 格式化结果 List> formattedRecords = new ArrayList<>(); for (Map record : records) { Map formatted = new LinkedHashMap<>(); @@ -163,22 +147,22 @@ public class ConversationService { return result; } - public void bindConversation(String conversationId, Long accountId, Long roleId) { + public void bindConversation(String conversationId, String accountId, Long roleId) { if (conversationId == null || conversationId.isBlank()) { return; } - Long normalizedAccountId = accountId != null && accountId > 0 ? accountId : null; + String externalAccountId = accountId != null && !accountId.isBlank() ? accountId.trim() : null; Long normalizedRoleId = roleId != null && roleId > 0 ? roleId : null; jdbcTemplate.update(""" - INSERT INTO conversation_session (conversation_id, account_id, role_id) + INSERT INTO conversation_session (conversation_id, external_account_id, role_id) VALUES (?, ?, ?) ON CONFLICT (conversation_id) - DO UPDATE SET account_id = COALESCE(EXCLUDED.account_id, conversation_session.account_id), + DO UPDATE SET external_account_id = EXCLUDED.external_account_id, + account_id = NULL, role_id = COALESCE(EXCLUDED.role_id, conversation_session.role_id), update_time = CURRENT_TIMESTAMP - """, conversationId, normalizedAccountId, normalizedRoleId); + """, conversationId, externalAccountId, normalizedRoleId); } - /** * 截断会话:删除"第 userTurn 条用户消息"及其之后的所有消息(逻辑删除)。 * 用于"编辑历史消息并重发"——清掉发错的那条用户消息连同其后的问答, @@ -257,13 +241,12 @@ public class ConversationService { private void appendConversationOwner(Map detail, String conversationId) { List> rows = jdbcTemplate.queryForList(""" - SELECT cs.account_id::text AS account_id, - ca.name AS account_name, - ca.account_key, + SELECT cs.external_account_id AS account_id, + cs.external_account_id AS account_key, + NULL::text AS account_name, cs.role_id::text AS role_id, csr.name AS role_name FROM conversation_session cs - LEFT JOIN customer_account ca ON ca.id = cs.account_id AND ca.is_delete = false LEFT JOIN customer_service_role csr ON csr.id = cs.role_id AND csr.is_delete = false WHERE cs.conversation_id = ? """, conversationId); @@ -282,7 +265,6 @@ public class ConversationService { detail.put("roleId", owner.get("role_id")); detail.put("roleName", owner.get("role_name")); } - // ==================== 会话消息 ==================== /** @@ -462,14 +444,5 @@ public class ConversationService { return stats; } - private static Long toLong(String value) { - if (value == null || value.isBlank()) { - return null; - } - try { - return Long.valueOf(value.trim()); - } catch (NumberFormatException e) { - return null; - } - } + } diff --git a/src/main/java/com/wok/supportbot/service/DocumentService.java b/src/main/java/com/wok/supportbot/service/DocumentService.java index 86549a0..0fb5f90 100644 --- a/src/main/java/com/wok/supportbot/service/DocumentService.java +++ b/src/main/java/com/wok/supportbot/service/DocumentService.java @@ -296,9 +296,52 @@ public class DocumentService { * 获取文档的所有分块 */ public List> getDocumentChunks(Long id) { + String documentId = String.valueOf(id); String sql = "SELECT id::text as id, content, metadata, create_time FROM vector_store " + - "WHERE metadata->>'documentId' = ? ORDER BY (metadata->>'chunkIndex')::int"; - return jdbcTemplate.queryForList(sql, String.valueOf(id)); + "WHERE metadata->>'documentId' = ? " + + " OR metadata->>'document_id' = ? " + + " OR metadata->>'docId' = ? " + + "ORDER BY " + chunkOrderSql(); + List> chunks = jdbcTemplate.queryForList(sql, documentId, documentId, documentId); + if (!chunks.isEmpty()) { + return chunks; + } + + KnowledgeDocument doc = documentMapper.selectById(id); + if (doc == null) { + return chunks; + } + return getDocumentChunksByTitleFallback(doc); + } + + private List> getDocumentChunksByTitleFallback(KnowledgeDocument doc) { + List filters = new ArrayList<>(); + List params = new ArrayList<>(); + if (doc.getSourceName() != null && !doc.getSourceName().isBlank()) { + filters.add("metadata->>'sourceName' = ?"); + params.add(doc.getSourceName()); + } + if (doc.getTitle() != null && !doc.getTitle().isBlank()) { + filters.add("metadata->>'title' = ?"); + params.add(doc.getTitle()); + } + if (filters.isEmpty()) { + return List.of(); + } + + String sql = "SELECT id::text as id, content, metadata, create_time FROM vector_store " + + "WHERE " + String.join(" OR ", filters) + " " + + "ORDER BY " + chunkOrderSql(); + if (doc.getChunkCount() != null && doc.getChunkCount() > 0) { + sql += " LIMIT ?"; + params.add(doc.getChunkCount()); + } + return jdbcTemplate.queryForList(sql, params.toArray()); + } + + private String chunkOrderSql() { + return "CASE WHEN metadata->>'chunkIndex' ~ '^[0-9]+$' " + + "THEN (metadata->>'chunkIndex')::int ELSE 0 END ASC, create_time ASC, id::text ASC"; } /** diff --git a/src/main/resources/db/migration/V2__conversation_external_account_id.sql b/src/main/resources/db/migration/V2__conversation_external_account_id.sql new file mode 100644 index 0000000..7bc5fa5 --- /dev/null +++ b/src/main/resources/db/migration/V2__conversation_external_account_id.sql @@ -0,0 +1,5 @@ +ALTER TABLE conversation_session + ADD COLUMN IF NOT EXISTS external_account_id VARCHAR(128); + +CREATE INDEX IF NOT EXISTS idx_conversation_session_external_account + ON conversation_session (external_account_id); \ No newline at end of file diff --git a/src/main/resources/init-database.sql b/src/main/resources/init-database.sql index c68ad0d..ea94511 100644 --- a/src/main/resources/init-database.sql +++ b/src/main/resources/init-database.sql @@ -240,12 +240,14 @@ ON CONFLICT (account_key) DO NOTHING; CREATE TABLE IF NOT EXISTS conversation_session ( conversation_id VARCHAR(64) PRIMARY KEY, account_id BIGINT, + external_account_id VARCHAR(128), role_id BIGINT, create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_conversation_session_account ON conversation_session (account_id); +CREATE INDEX IF NOT EXISTS idx_conversation_session_external_account ON conversation_session (external_account_id); CREATE INDEX IF NOT EXISTS idx_conversation_session_role ON conversation_session (role_id); COMMENT ON TABLE conversation_session IS '会话归属表'; diff --git a/src/main/resources/static/components/ChatPanel.js b/src/main/resources/static/components/ChatPanel.js index ec234ac..0f40be7 100644 --- a/src/main/resources/static/components/ChatPanel.js +++ b/src/main/resources/static/components/ChatPanel.js @@ -2,7 +2,7 @@ * 智能客服对话面板 */ import { ref, computed, nextTick, onMounted } from 'vue' -import { chatSync, chatRagSync, chatSSEUrl, chatRagSSEUrl, getRoleList, getAccountList, truncateConversation, ragSources, submitFeedback as submitFeedbackApi } from '../js/api.js' +import { chatSync, chatRagSync, chatSSEUrl, chatRagSSEUrl, getRoleList, getActiveModelConfig, truncateConversation, ragSources, submitFeedback as submitFeedbackApi } from '../js/api.js' import { toast, readSSEStream, renderMarkdown } from '../js/utils.js' import { store } from '../js/store.js' import MessageSources from './MessageSources.js' @@ -38,16 +38,6 @@ export default { -
-
对话账号
- -
-
客服助手
@@ -77,6 +67,10 @@ export default {
+
+ 调用模型 + {{ activeModelText }} +