From eaef9a0e4ca724ac6ec6ae4452b05ac2979f4a04 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sat, 11 Apr 2026 18:13:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=BB=BA=E9=A3=8E=E6=A0=BC=E4=B8=8E=E6=99=BA=E8=83=BD=E4=BD=93?= =?UTF-8?q?=E5=8F=8C=E6=A8=A1=E5=BC=8F=E5=AF=B9=E8=AF=9D=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `source` 字段区分智能体(`agent`)与自建风格(`prompt`)两种对话来源 - 前端统一对话组件,根据来源动态构建请求参数、显示不同样式与文案 - 后端重构 Dify 会话与消息获取逻辑,支持合并查询 Pro 与 Standard 两个 Dify App 的会话历史 - 实现复合游标分页机制,支持跨双数据源的高效分页 - 新增 `clipboard-polyfill` 依赖,统一剪贴板复制功能,提升非 HTTPS 环境兼容性 - 扩展历史记录面板,支持按来源加载对应会话与消息 - 调整侧边抽屉宽度,优化大屏显示体验 --- frontend/app/web-gold/package.json | 1 + frontend/app/web-gold/src/api/agent.js | 13 +- .../src/components/agents/ChatDrawer.vue | 69 +++--- .../components/agents/ChatDrawerHeader.vue | 22 +- .../src/components/agents/HistoryPanel.vue | 19 +- .../components/agents/MyFavoritesModal.vue | 1 + .../src/components/ui/sheet/SheetContent.vue | 4 +- frontend/app/web-gold/src/utils/clipboard.ts | 34 +-- .../app/web-gold/src/views/agents/Agents.vue | 1 + .../dify/controller/AppDifyController.java | 18 +- .../module/tik/dify/service/DifyService.java | 13 +- .../tik/dify/service/DifyServiceImpl.java | 205 +++++++++++++++--- .../module/tik/dify/vo/DifyChatReqVO.java | 3 + .../dify/vo/DifyConversationListRespVO.java | 5 + .../tik/dify/vo/DifyConversationRespVO.java | 4 + 15 files changed, 292 insertions(+), 120 deletions(-) diff --git a/frontend/app/web-gold/package.json b/frontend/app/web-gold/package.json index a9549f367f..da48fff6a2 100644 --- a/frontend/app/web-gold/package.json +++ b/frontend/app/web-gold/package.json @@ -25,6 +25,7 @@ "ai": "^6.0.39", "aplayer": "^1.10.1", "class-variance-authority": "^0.7.1", + "clipboard-polyfill": "^4.1.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "dayjs": "^1.11.18", diff --git a/frontend/app/web-gold/src/api/agent.js b/frontend/app/web-gold/src/api/agent.js index 06a92e746d..7e109e16e2 100644 --- a/frontend/app/web-gold/src/api/agent.js +++ b/frontend/app/web-gold/src/api/agent.js @@ -38,6 +38,7 @@ export async function sendChatStream(options) { conversationId, modelMode = 'pro', customSystemPrompt, + source, ctrl, onMessage, onError, @@ -59,7 +60,8 @@ export async function sendChatStream(options) { content, conversationId, modelMode, - customSystemPrompt + customSystemPrompt, + source }), onmessage: (event) => { if (typeof onMessage === 'function') { @@ -89,10 +91,11 @@ export async function sendChatStream(options) { } /** - * 获取会话列表 + * 获取会话列表(合并 pro + standard 两个 Dify 工作流) * @param {Object} params - 请求参数 * @param {number} params.agentId - 智能体ID - * @param {string} [params.lastId] - 上一页最后一条记录ID + * @param {string} [params.source] - 来源类型:agent-智能体 prompt-自建风格 + * @param {string} [params.cursor] - 复合游标(首页不传) * @param {number} [params.limit] - 返回条数,默认20 */ export function getConversations(params) { @@ -104,10 +107,12 @@ export function getConversations(params) { } /** - * 获取会话历史消息 + * 获取会话历史消息(自动定位 pro/standard App) * @param {Object} params - 请求参数 * @param {number} params.agentId - 智能体ID + * @param {string} [params.source] - 来源类型:agent-智能体 prompt-自建风格 * @param {string} params.conversationId - 会话ID + * @param {string} [params.appSource] - 来源应用标识:pro/standard * @param {string} [params.firstId] - 当前页第一条记录ID * @param {number} [params.limit] - 返回条数,默认20 */ diff --git a/frontend/app/web-gold/src/components/agents/ChatDrawer.vue b/frontend/app/web-gold/src/components/agents/ChatDrawer.vue index b31bece763..84bac91842 100644 --- a/frontend/app/web-gold/src/components/agents/ChatDrawer.vue +++ b/frontend/app/web-gold/src/components/agents/ChatDrawer.vue @@ -1,5 +1,5 @@ - + - + {{ agent?.name || 'AI 助手' }} - {{ agent?.categoryName || '通用' }} + + + + {{ agent?.categoryName || '我的风格' }} + + {{ agent?.categoryName || '通用' }} + { loading.value = true try { - const params = { agentId: props.agentId, limit: 20 } - if (loadMore && lastId.value) { - params.lastId = lastId.value + const params = { agentId: props.agentId, source: props.source, limit: 20 } + if (loadMore && cursor.value) { + params.cursor = cursor.value } const res = await getConversations(params) if (res.code === 0) { @@ -241,9 +242,9 @@ const loadConversations = async (loadMore = false) => { } else { conversationList.value = data } - // 更新分页状态 - lastId.value = data.length > 0 ? data[data.length - 1].id : null - hasMore.value = data.length === 20 && res.data?.hasMore !== false + // 更新分页状态:使用后端返回的复合游标 + cursor.value = res.data?.nextCursor || null + hasMore.value = res.data?.hasMore !== false && data.length > 0 } } catch (e) { console.error('加载会话列表失败:', e) @@ -259,7 +260,9 @@ const selectConversation = async (conversation) => { try { const res = await getMessages({ agentId: props.agentId, + source: props.source, conversationId: conversation.id, + appSource: conversation.appSource, limit: 50 }) if (res.code === 0) { diff --git a/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue b/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue index 86c321c9a1..c407d12a6f 100644 --- a/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue +++ b/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue @@ -94,6 +94,7 @@ async function handleDelete(id) { function handleUse(item) { emit('chat', { + source: 'prompt', id: item.id, name: item.name, categoryName: item.category || '我的风格', diff --git a/frontend/app/web-gold/src/components/ui/sheet/SheetContent.vue b/frontend/app/web-gold/src/components/ui/sheet/SheetContent.vue index e0c4b8f66e..0fe01826a6 100644 --- a/frontend/app/web-gold/src/components/ui/sheet/SheetContent.vue +++ b/frontend/app/web-gold/src/components/ui/sheet/SheetContent.vue @@ -39,9 +39,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits) :class="cn( 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500', side === 'right' - && 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm', + && 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-2xl', side === 'left' - && 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm', + && 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-2xl', side === 'top' && 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b', side === 'bottom' diff --git a/frontend/app/web-gold/src/utils/clipboard.ts b/frontend/app/web-gold/src/utils/clipboard.ts index 9f3e11c420..3e5bb3c8ab 100644 --- a/frontend/app/web-gold/src/utils/clipboard.ts +++ b/frontend/app/web-gold/src/utils/clipboard.ts @@ -1,41 +1,17 @@ +import * as clipboardPolyfill from 'clipboard-polyfill' + /** * 复制文本到剪贴板 - * 兼容非 HTTPS 环境的降级方案 + * 使用 clipboard-polyfill 兼容非 HTTPS 环境 */ export async function copyToClipboard(text: string): Promise { if (!text?.trim()) { return false } - // 优先使用 Clipboard API(需要 HTTPS 或 localhost) - if (navigator.clipboard?.writeText) { - try { - await navigator.clipboard.writeText(text) - return true - } catch { - // 降级到 execCommand 方案 - } - } - - // 降级方案:使用 textarea + execCommand - return fallbackCopy(text) -} - -/** - * 降级复制方案 - */ -function fallbackCopy(text: string): boolean { try { - const textarea = document.createElement('textarea') - textarea.value = text - textarea.style.position = 'fixed' - textarea.style.opacity = '0' - textarea.style.left = '-9999px' - document.body.appendChild(textarea) - textarea.select() - const success = document.execCommand('copy') - document.body.removeChild(textarea) - return success + await clipboardPolyfill.writeText(text) + return true } catch { return false } diff --git a/frontend/app/web-gold/src/views/agents/Agents.vue b/frontend/app/web-gold/src/views/agents/Agents.vue index 3b7cc7e760..d31423f73c 100644 --- a/frontend/app/web-gold/src/views/agents/Agents.vue +++ b/frontend/app/web-gold/src/views/agents/Agents.vue @@ -280,6 +280,7 @@ const fetchAgentList = async () => { const res = await getAgentList() if (res.code === 0 && res.data) { agentList.value = res.data.map(item => ({ + source: 'agent', id: item.id, agentId: item.agentId, name: item.agentName, diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/controller/AppDifyController.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/controller/AppDifyController.java index 51878186b4..0a17d33d71 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/controller/AppDifyController.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/controller/AppDifyController.java @@ -68,29 +68,35 @@ public class AppDifyController { } @GetMapping("/conversations") - @Operation(summary = "获取会话列表") + @Operation(summary = "获取会话列表(合并 pro + standard)") @Parameter(name = "agentId", description = "智能体ID", required = true) - @Parameter(name = "lastId", description = "上一页最后一条记录ID") + @Parameter(name = "source", description = "来源类型:agent-智能体 prompt-自建风格") + @Parameter(name = "cursor", description = "复合游标(首页不传)") @Parameter(name = "limit", description = "返回条数,默认20") public CommonResult getConversations( @RequestParam("agentId") Long agentId, - @RequestParam(value = "lastId", required = false) String lastId, + @RequestParam(value = "source", required = false, defaultValue = "agent") String source, + @RequestParam(value = "cursor", required = false) String cursor, @RequestParam(value = "limit", required = false) Integer limit) { - return CommonResult.success(difyService.getConversations(agentId, getCurrentUserId(), lastId, limit)); + return CommonResult.success(difyService.getConversations(agentId, source, getCurrentUserId(), cursor, limit)); } @GetMapping("/messages") - @Operation(summary = "获取会话历史消息") + @Operation(summary = "获取会话历史消息(自动定位 App)") @Parameter(name = "agentId", description = "智能体ID", required = true) + @Parameter(name = "source", description = "来源类型:agent-智能体 prompt-自建风格") @Parameter(name = "conversationId", description = "会话ID", required = true) + @Parameter(name = "appSource", description = "来源应用标识:pro/standard") @Parameter(name = "firstId", description = "当前页第一条记录ID") @Parameter(name = "limit", description = "返回条数,默认20") public CommonResult getMessages( @RequestParam("agentId") Long agentId, + @RequestParam(value = "source", required = false, defaultValue = "agent") String source, @RequestParam("conversationId") String conversationId, + @RequestParam(value = "appSource", required = false) String appSource, @RequestParam(value = "firstId", required = false) String firstId, @RequestParam(value = "limit", required = false) Integer limit) { - return CommonResult.success(difyService.getMessages(agentId, conversationId, getCurrentUserId(), firstId, limit)); + return CommonResult.success(difyService.getMessages(agentId, source, conversationId, appSource, getCurrentUserId(), firstId, limit)); } private String getCurrentUserId() { diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyService.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyService.java index 4bc1e159c9..63f082760b 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyService.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyService.java @@ -53,26 +53,29 @@ public interface DifyService { Flux promptAnalysisStream(PromptAnalysisReqVO reqVO, String userId); /** - * 获取会话列表 + * 获取会话列表(合并 pro + standard 两个 Dify App 的会话) * * @param agentId 智能体ID + * @param source 来源类型(agent/prompt) * @param userId 用户ID - * @param lastId 上一页最后一条记录ID + * @param cursor 复合游标(Base64 编码,首页传 null) * @param limit 返回条数 * @return 会话列表 */ - DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit); + DifyConversationListRespVO getConversations(Long agentId, String source, String userId, String cursor, Integer limit); /** - * 获取会话历史消息 + * 获取会话历史消息(自动定位 pro/standard App) * * @param agentId 智能体ID + * @param source 来源类型(agent/prompt) * @param conversationId 会话ID + * @param appSource 来源应用标识(pro/standard),用于选择 API Key * @param userId 用户ID * @param firstId 当前页第一条记录ID * @param limit 返回条数 * @return 消息列表 */ - DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit); + DifyMessageListRespVO getMessages(Long agentId, String source, String conversationId, String appSource, String userId, String firstId, Integer limit); } diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyServiceImpl.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyServiceImpl.java index 1911622915..545e99e0f2 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyServiceImpl.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/service/DifyServiceImpl.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO; import cn.iocoder.yudao.module.tik.dify.vo.PromptAnalysisReqVO; import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO; import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO; +import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationRespVO; import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO; import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO; import cn.iocoder.yudao.module.tik.enums.AiModelTypeEnum; @@ -21,7 +22,13 @@ import org.springframework.validation.annotation.Validated; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -50,9 +57,7 @@ public class DifyServiceImpl implements DifyService { AtomicLong pendingRecordId = new AtomicLong(); AtomicReference conversationIdRef = new AtomicReference<>(reqVO.getConversationId()); AtomicReference tokenUsageRef = new AtomicReference<>(); - String difyUserId = reqVO.getAgentId() != null - ? "user-" + userId + "-agent-" + reqVO.getAgentId() - : "user-" + userId + "-prompt"; + String difyUserId = buildDifyUserId(userId, reqVO.getSource(), reqVO.getAgentId()); String logPrefix = "chatStream"; return Mono.fromCallable(() -> { @@ -380,55 +385,169 @@ public class DifyServiceImpl implements DifyService { } @Override - public DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit) { - // 获取智能体配置 - AiAgentDO agent = aiAgentService.getAiAgent(agentId); - if (agent == null) { - throw new RuntimeException("智能体不存在"); + public DifyConversationListRespVO getConversations(Long agentId, String source, String userId, String cursor, Integer limit) { + if (limit == null || limit <= 0) { + limit = 20; } - // 获取积分配置(使用标准模式的 API Key) - AiServiceConfigDO config = pointsService.getConfig( - AiPlatformEnum.DIFY.getPlatform(), - AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode()); + AiServiceConfigDO proConfig = getDifyConfig(false); + AiServiceConfigDO standardConfig = getDifyConfig(true); - // Dify 用户标识(按 agentId 隔离会话) - String difyUserId = "user-" + userId + "-agent-" + agentId; + String difyUserId = buildDifyUserId(userId, source, agentId); - DifyConversationListRespVO result = difyClient.getConversations(config.getApiKey(), difyUserId, lastId, limit); + String proLastId = null; + String standardLastId = null; + if (cursor != null && !cursor.isEmpty()) { + try { + String json = new String(Base64.getDecoder().decode(cursor)); + com.fasterxml.jackson.databind.JsonNode node = JsonUtils.parseTree(json); + proLastId = node.has("pro") ? node.get("pro").asText(null) : null; + standardLastId = node.has("standard") ? node.get("standard").asText(null) : null; + } catch (Exception e) { + log.warn("[getConversations] 游标解析失败,从头开始: {}", cursor, e); + } + } + + List proList = Collections.emptyList(); + List standardList = Collections.emptyList(); + boolean proHasMore = false; + boolean standardHasMore = false; + + try { + DifyConversationListRespVO proResult = difyClient.getConversations( + proConfig.getApiKey(), difyUserId, proLastId, limit); + if (proResult != null && proResult.getData() != null) { + proList = proResult.getData(); + proList.forEach(c -> c.setAppSource("pro")); + proHasMore = proResult.getHasMore() != null && proResult.getHasMore(); + } + } catch (Exception e) { + log.warn("[getConversations] 查询 Pro 会话列表失败", e); + } + + try { + DifyConversationListRespVO standardResult = difyClient.getConversations( + standardConfig.getApiKey(), difyUserId, standardLastId, limit); + if (standardResult != null && standardResult.getData() != null) { + standardList = standardResult.getData(); + standardList.forEach(c -> c.setAppSource("standard")); + standardHasMore = standardResult.getHasMore() != null && standardResult.getHasMore(); + } + } catch (Exception e) { + log.warn("[getConversations] 查询 Standard 会话列表失败", e); + } + + // 合并两个列表,按 updatedAt 降序排序,截取 limit 条 + List merged = new ArrayList<>(); + merged.addAll(proList); + merged.addAll(standardList); + merged.sort((a, b) -> { + long timeA = a.getUpdatedAt() != null ? a.getUpdatedAt() : (a.getCreatedAt() != null ? a.getCreatedAt() : 0); + long timeB = b.getUpdatedAt() != null ? b.getUpdatedAt() : (b.getCreatedAt() != null ? b.getCreatedAt() : 0); + return Long.compare(timeB, timeA); // 降序 + }); + + // 判断是否有更多数据 + boolean hasMore = proHasMore || standardHasMore; + + List pageData; + if (merged.size() > limit) { + pageData = new ArrayList<>(merged.subList(0, limit)); + hasMore = true; + } else { + pageData = merged; + } // 过滤掉 inputs 中的敏感字段(如 sysPrompt) - if (result != null && result.getData() != null) { - result.getData().forEach(conv -> { - if (conv.getInputs() != null) { - conv.getInputs().remove("sysPrompt"); - } - }); - } + pageData.forEach(conv -> { + if (conv.getInputs() != null) { + conv.getInputs().remove("sysPrompt"); + } + }); + // 构建下一页游标:收集当前页中 pro 和 standard 的最后一条记录ID + String nextCursor = buildNextCursor(pageData, proHasMore, standardHasMore); + + DifyConversationListRespVO result = new DifyConversationListRespVO(); + result.setLimit(limit); + result.setHasMore(hasMore); + result.setData(pageData); + result.setNextCursor(nextCursor); return result; } - @Override - public DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit) { - // 获取智能体配置 - AiAgentDO agent = aiAgentService.getAiAgent(agentId); - if (agent == null) { - throw new RuntimeException("智能体不存在"); + /** + * 构建下一页复合游标 + */ + private String buildNextCursor(List pageData, + boolean proHasMore, boolean standardHasMore) { + if (pageData.isEmpty()) return null; + + String proLastId = null; + String standardLastId = null; + + for (int i = pageData.size() - 1; i >= 0; i--) { + DifyConversationRespVO conv = pageData.get(i); + if ("pro".equals(conv.getAppSource()) && proLastId == null && proHasMore) { + proLastId = conv.getId(); + } + if ("standard".equals(conv.getAppSource()) && standardLastId == null && standardHasMore) { + standardLastId = conv.getId(); + } + if (proLastId != null && standardLastId != null) break; } - // 获取积分配置(使用标准模式的 API Key) - AiServiceConfigDO config = pointsService.getConfig( - AiPlatformEnum.DIFY.getPlatform(), - AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode()); + if (proLastId == null && standardLastId == null) return null; - // Dify 用户标识(按 agentId 隔离会话) - String difyUserId = "user-" + userId + "-agent-" + agentId; + try { + Map cursorMap = new HashMap<>(); + if (proLastId != null) cursorMap.put("pro", proLastId); + if (standardLastId != null) cursorMap.put("standard", standardLastId); + String json = JsonUtils.toJsonString(cursorMap); + return Base64.getEncoder().encodeToString(json.getBytes()); + } catch (Exception e) { + log.warn("[buildNextCursor] 构建游标失败", e); + return null; + } + } - DifyMessageListRespVO result = difyClient.getMessages(config.getApiKey(), conversationId, difyUserId, firstId, limit); + @Override + public DifyMessageListRespVO getMessages(Long agentId, String source, String conversationId, + String appSource, String userId, String firstId, Integer limit) { + String difyUserId = buildDifyUserId(userId, source, agentId); + + boolean isStandard = "standard".equals(appSource); + AiServiceConfigDO primaryConfig = getDifyConfig(isStandard); + AiServiceConfigDO fallbackConfig = null; + + DifyMessageListRespVO result = null; + try { + result = difyClient.getMessages(primaryConfig.getApiKey(), conversationId, difyUserId, firstId, limit); + } catch (Exception e) { + log.warn("[getMessages] 主 Key 查询失败,appSource: {}, 尝试降级", appSource, e); + } + + if (result == null || result.getData() == null || result.getData().isEmpty()) { + fallbackConfig = getDifyConfig(!isStandard); + try { + DifyMessageListRespVO fallbackResult = difyClient.getMessages( + fallbackConfig.getApiKey(), conversationId, difyUserId, firstId, limit); + if (fallbackResult != null && fallbackResult.getData() != null && !fallbackResult.getData().isEmpty()) { + result = fallbackResult; + } + } catch (Exception e) { + log.warn("[getMessages] 降级 Key 查询也失败", e); + } + } + + if (result == null) { + result = new DifyMessageListRespVO(); + result.setData(Collections.emptyList()); + result.setHasMore(false); + } // 过滤掉 inputs 中的敏感字段(如 sysPrompt) - if (result != null && result.getData() != null) { + if (result.getData() != null) { result.getData().forEach(msg -> { if (msg.getInputs() != null) { msg.getInputs().remove("sysPrompt"); @@ -439,4 +558,18 @@ public class DifyServiceImpl implements DifyService { return result; } + private static String resolveScope(String source) { + return "prompt".equals(source) ? "prompt" : "agent"; + } + + private String buildDifyUserId(String userId, String source, Long agentId) { + return "user-" + userId + "-" + resolveScope(source) + "-" + agentId; + } + + private AiServiceConfigDO getDifyConfig(boolean standard) { + return pointsService.getConfig( + AiPlatformEnum.DIFY.getPlatform(), + (standard ? AiModelTypeEnum.DIFY_WRITING_STANDARD : AiModelTypeEnum.DIFY_WRITING_PRO).getModelCode()); + } + } diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyChatReqVO.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyChatReqVO.java index 2da2ef9d82..14819b0914 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyChatReqVO.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyChatReqVO.java @@ -28,4 +28,7 @@ public class DifyChatReqVO { @Schema(description = "自定义系统提示词(使用用户自建风格时传入)") private String customSystemPrompt; + @Schema(description = "来源类型:agent-智能体 prompt-自建风格", example = "agent") + private String source; + } diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationListRespVO.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationListRespVO.java index a8abab6c03..5efae4896b 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationListRespVO.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationListRespVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.tik.dify.vo; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -23,4 +24,8 @@ public class DifyConversationListRespVO { @Schema(description = "会话列表", requiredMode = Schema.RequiredMode.REQUIRED) private List data; + @Schema(description = "下一页游标(Base64 编码的复合游标)") + @JsonProperty("next_cursor") + private String nextCursor; + } diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationRespVO.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationRespVO.java index c4fae07a18..e25f4f648c 100644 --- a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationRespVO.java +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/dify/vo/DifyConversationRespVO.java @@ -38,4 +38,8 @@ public class DifyConversationRespVO { @JsonProperty("updated_at") private Long updatedAt; + @Schema(description = "来源应用标识:pro/standard", example = "pro") + @JsonProperty("app_source") + private String appSource; + }