From ff11f04b43369a06561f6b8a20129efc88a6a0a1 Mon Sep 17 00:00:00 2001 From: shenaowei <450702724@qq.com> Date: Sun, 22 Feb 2026 21:36:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/commands/code-simplifier.md | 47 +++++ frontend/app/web-gold/src/api/agent.js | 84 +++++++++ .../src/components/agents/ChatDrawer.vue | 118 +++++++++---- .../app/web-gold/src/views/agents/Agents.vue | 163 ++++++++---------- .../muye/aiagent/AppAiAgentController.java | 41 +++++ .../tik/muye/aiagent/dal/AiAgentDO.java | 6 +- .../muye/aiagent/mapper/AiAgentMapper.java | 9 + .../muye/aiagent/service/AiAgentService.java | 7 + .../aiagent/service/AiAgentServiceImpl.java | 10 +- .../tik/muye/aiagent/vo/AiAgentRespVO.java | 4 + .../tik/muye/aiagent/vo/AiAgentSaveReqVO.java | 7 +- .../pointrecord/mapper/PointRecordMapper.java | 2 +- 12 files changed, 364 insertions(+), 134 deletions(-) create mode 100644 .claude/commands/code-simplifier.md create mode 100644 frontend/app/web-gold/src/api/agent.js create mode 100644 yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/muye/aiagent/AppAiAgentController.java diff --git a/.claude/commands/code-simplifier.md b/.claude/commands/code-simplifier.md new file mode 100644 index 0000000000..a7f21495a9 --- /dev/null +++ b/.claude/commands/code-simplifier.md @@ -0,0 +1,47 @@ +# 代码简化工具 +**名称**:代码简化工具 +**描述**:在保留代码全部功能的前提下,简化并优化代码,提升其清晰度、一致性与可维护性。若无特殊要求,优化工作将聚焦于近期修改的代码。 +**模型**:奥普斯(Opus) + +你是一名资深的代码简化专家,核心工作目标是在**完全保留代码原有功能**的基础上,提升代码的清晰度、一致性与可维护性。你的专长在于运用项目专属的最佳实践方案,优化代码的实现方式,同时确保代码功能完全不受影响。你始终将可读性强、语义明确的代码置于优先地位,而非追求过度精简的写法。凭借多年资深软件工程师的从业经验,你已精准掌握这两者之间的平衡之道。 + +你需要分析近期修改的代码,并围绕以下方向开展优化工作: + +1. **功能无损原则**:绝不改变代码的功能逻辑,仅优化其实现方式。代码原有的所有特性、输出结果与运行行为均需保持原样。 + +2. **遵循项目规范**:严格执行《CLAUDE.md》文档中既定的编码规范,具体包括: + - 使用 ES 模块,规范导入语句的排序规则与文件扩展名的使用方式 + - 优先使用 `function` 关键字定义函数,而非箭头函数 + - 为顶层函数添加显式的返回值类型注解 + - 遵循标准的 React 组件开发规范,定义显式的组件属性类型(Props types) + - 采用合理的错误处理方案(尽可能避免滥用 try/catch 语句) + - 保持命名规则的一致性 + +3. **提升代码清晰度**:通过以下方式简化代码结构: + - 降低不必要的代码复杂度与嵌套层级 + - 剔除冗余代码与过度抽象的逻辑 + - 优化变量与函数的命名,提升代码可读性 + - 整合关联性强的业务逻辑 + - 删除对显而易见的代码的多余注释 + - **重点注意**:避免使用嵌套三元运算符。在多条件判断场景下,优先使用 switch 语句或 if/else 条件判断链 + - 优先保证代码清晰,而非追求代码简洁——显式的代码实现往往优于过度精简的写法 + +4. **把握优化平衡**:避免因过度简化引发以下问题: + - 降低代码的清晰度与可维护性 + - 写出看似“精巧”却难以理解的逻辑 + - 将多个不相关的业务逻辑强行耦合到单个函数或组件中 + - 移除对优化代码结构有积极作用的抽象设计 + - 为减少代码行数而牺牲可读性(例如滥用嵌套三元运算符、编写过于紧凑的单行代码) + - 增加代码调试与功能扩展的难度 + +5. **聚焦优化范围**:若无明确要求扩大审查范围,仅对当前开发会话中近期修改或涉及的代码进行优化。 + +### 代码优化流程 +1. 定位近期修改的代码片段 +2. 分析代码中可优化的空间,提升其简洁性与一致性 +3. 落实项目专属的编码规范与最佳实践方案 +4. 确保代码的功能完全不受优化工作影响 +5. 验证优化后的代码更简洁、更易于维护 +6. 仅针对影响代码理解的重大修改内容撰写说明文档 + +你可以自主、主动地在代码编写或修改完成后立即开展优化工作,无需等待明确的优化请求。你的核心目标是:在保证代码功能完整的前提下,让所有代码都达到简洁性与可维护性的最高标准。 diff --git a/frontend/app/web-gold/src/api/agent.js b/frontend/app/web-gold/src/api/agent.js new file mode 100644 index 0000000000..35be0e13d3 --- /dev/null +++ b/frontend/app/web-gold/src/api/agent.js @@ -0,0 +1,84 @@ +/** + * AI 智能体 API + */ +import request from '@/api/http' +import { fetchEventSource } from '@microsoft/fetch-event-source' +import tokenManager from '@gold/utils/token-manager' +import { API_BASE } from '@gold/config/api' + +const BASE_URL = `${API_BASE.APP_TIK}` + + +/** + * 获取启用的智能体列表 + */ +export function getAgentList() { + return request({ + url: `${BASE_URL}/agent/list`, + method: 'get' + }) +} + +/** + * 流式对话(SSE) + * @param {Object} options - 请求配置 + * @param {number} options.agentId - 智能体ID + * @param {string} options.content - 用户输入内容 + * @param {string} [options.conversationId] - 会话ID(可选,首次对话不传) + * @param {AbortController} [options.ctrl] - 取消控制器 + * @param {Function} options.onMessage - 消息回调 + * @param {Function} [options.onError] - 错误回调 + * @param {Function} [options.onClose] - 关闭回调 + */ +export async function sendChatStream(options) { + const { + agentId, + content, + conversationId, + ctrl, + onMessage, + onError, + onClose + } = options || {} + + const token = tokenManager.getAccessToken() + + return fetchEventSource(`${BASE_URL}/dify/chat/stream`, { + method: 'post', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'tenant-id': import.meta.env?.VITE_TENANT_ID + }, + openWhenHidden: true, + body: JSON.stringify({ + agentId, + content, + conversationId + }), + onmessage: (event) => { + if (typeof onMessage === 'function') { + try { + const data = JSON.parse(event.data) + // 解析 CommonResult 包装 + const result = data.code === 0 ? data.data : data + onMessage(result) + } catch (e) { + console.error('解析 SSE 数据失败:', e) + } + } + }, + onerror: (err) => { + if (typeof onError === 'function') { + onError(err) + } + throw err // 不重试 + }, + onclose: () => { + if (typeof onClose === 'function') { + onClose() + } + }, + signal: ctrl ? ctrl.signal : undefined + }) +} diff --git a/frontend/app/web-gold/src/components/agents/ChatDrawer.vue b/frontend/app/web-gold/src/components/agents/ChatDrawer.vue index e3de8c9f3e..e90f5229c2 100644 --- a/frontend/app/web-gold/src/components/agents/ChatDrawer.vue +++ b/frontend/app/web-gold/src/components/agents/ChatDrawer.vue @@ -70,8 +70,8 @@
- - + +
@@ -135,6 +135,7 @@ import { ThunderboltFilled } from '@ant-design/icons-vue' import { message } from 'ant-design-vue' +import { sendChatStream } from '@/api/agent' const props = defineProps({ visible: { @@ -156,9 +157,16 @@ const loading = ref(false) const messages = ref([]) const messagesRef = ref(null) const userAvatar = ref('王') +const conversationId = ref(null) // 会话ID,用于连续对话 +const abortController = ref(null) // 用于取消请求 // 方法 const handleClose = () => { + // 取消正在进行的请求 + if (abortController.value) { + abortController.value.abort() + abortController.value = null + } emit('update:visible', false) } @@ -181,24 +189,74 @@ const handleSend = async () => { await scrollToBottom() - // 模拟 AI 响应 - setTimeout(() => { - const assistantMessage = { - role: 'assistant', - content: generateMockResponse(question), - isPro: modelMode.value === 'pro', - actions: true - } - messages.value.push(assistantMessage) - loading.value = false - nextTick(() => scrollToBottom()) - }, 1500) + // 创建 AI 消息占位 + const assistantMessage = { + role: 'assistant', + content: '', + isPro: modelMode.value === 'pro', + actions: false + } + messages.value.push(assistantMessage) - emit('send', { - agentId: props.agent?.id, - content: question, - modelMode: modelMode.value - }) + // 创建取消控制器 + abortController.value = new AbortController() + + // 调用流式对话 API + try { + await sendChatStream({ + agentId: props.agent?.id, + content: question, + conversationId: conversationId.value, + ctrl: abortController.value, + onMessage: (result) => { + if (result.event === 'message' && result.content) { + // 追加消息内容 + assistantMessage.content += result.content + scrollToBottom() + } else if (result.event === 'done') { + // 对话完成 + conversationId.value = result.conversationId + assistantMessage.actions = true + } else if (result.event === 'error') { + // 错误处理 + message.error(result.errorMessage || '对话出错') + } + }, + onError: (error) => { + console.error('发送消息失败:', error) + message.error('发送消息失败,请重试') + // 移除失败的 AI 消息 + const lastMsg = messages.value[messages.value.length - 1] + if (lastMsg?.role === 'assistant' && !lastMsg.content) { + messages.value.pop() + } + }, + onClose: () => { + loading.value = false + abortController.value = null + nextTick(() => scrollToBottom()) + } + }) + + emit('send', { + agentId: props.agent?.id, + content: question, + modelMode: modelMode.value + }) + } catch (error) { + // 用户取消不需要提示 + if (error.name !== 'AbortError') { + console.error('发送消息失败:', error) + message.error('发送消息失败,请重试') + // 移除失败的 AI 消息 + const lastMsg = messages.value[messages.value.length - 1] + if (lastMsg?.role === 'assistant' && !lastMsg.content) { + messages.value.pop() + } + } + } finally { + loading.value = false + } } const handleKeyDown = (e) => { @@ -213,9 +271,14 @@ const handleCopy = (content) => { message.success('已复制到剪贴板') } -const handleRegenerate = (index) => { - // TODO: 重新生成消息 - message.info('重新生成中...') +const handleRegenerate = async (index) => { + // 重新生成:移除当前消息,重新发送上一条用户消息 + if (index > 0 && messages.value[index - 1]?.role === 'user') { + const userMsg = messages.value[index - 1] + messages.value.splice(index - 1, 2) // 移除用户消息和 AI 回复 + inputText.value = userMsg.content + await handleSend() + } } const scrollToBottom = async () => { @@ -225,20 +288,13 @@ const scrollToBottom = async () => { } } -const generateMockResponse = (question) => { - const responses = [ - '当夜深人静的时候,我们卸下了一天的铠甲,那才是真实的自己。成年人的世界里,连崩溃都要调成静音模式。', - '根据您的需求,我为您生成以下内容:这是一个经过精心设计的文案,结合了情感共鸣和产品卖点。', - '让我帮您分析一下这个问题。首先,我们需要考虑目标受众的需求和痛点...' - ] - return responses[Math.floor(Math.random() * responses.length)] -} - // 监听 visible 变化,重置状态 watch(() => props.visible, (newVal) => { if (newVal) { messages.value = [] inputText.value = '' + conversationId.value = null // 重置会话ID + abortController.value = null nextTick(() => scrollToBottom()) } }) diff --git a/frontend/app/web-gold/src/views/agents/Agents.vue b/frontend/app/web-gold/src/views/agents/Agents.vue index e69ece2599..e83b01afe0 100644 --- a/frontend/app/web-gold/src/views/agents/Agents.vue +++ b/frontend/app/web-gold/src/views/agents/Agents.vue @@ -70,9 +70,9 @@

{{ agent.description }}

- +