From e099eff4cb47cd1a82d074037a65e6753bcb0578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 9 Aug 2026 21:35:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20MCP=20=E6=94=B6=E5=B0=BE(?= =?UTF-8?q?update=E5=8E=9F=E5=AD=90CAS=E6=B2=BBTOCTOU/advance=5Ftask?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8F=98=E6=9B=B4=E6=98=A0=E5=B0=84/list=5Ft?= =?UTF-8?q?rash=E5=88=86=E9=A1=B5)=20+=20miniapp=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E6=94=B6=E5=B0=BE(mention=20chip=E5=8F=AF=E8=A7=86=E5=8C=96/?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=9D=97=E8=AF=AD=E8=A8=80=E6=A0=87=E7=AD=BE?= =?UTF-8?q?)=20+=20=E9=94=80=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/df-miniapp/src/pages/chat/index.vue | 74 +++++++++++- apps/df-miniapp/src/utils/mdRenderer.ts | 17 ++- crates/df-mcp/src/tools.rs | 113 +++++++++++++++--- crates/df-storage/src/crud/idea_repo.rs | 83 +++++++++++++ crates/df-storage/src/crud/project_repo.rs | 106 ++++++++++++++++ crates/df-storage/src/crud/task_repo.rs | 84 +++++++++++++ docs/todo.md | 34 +++--- .../src/commands/ai/audit/data_change.rs | 4 + 8 files changed, 479 insertions(+), 36 deletions(-) diff --git a/apps/df-miniapp/src/pages/chat/index.vue b/apps/df-miniapp/src/pages/chat/index.vue index 6189836..2de3e71 100644 --- a/apps/df-miniapp/src/pages/chat/index.vue +++ b/apps/df-miniapp/src/pages/chat/index.vue @@ -350,6 +350,50 @@ function shouldRenderMsg(m: ChatMessage): boolean { return true } +// ──────────────────────────────────────────────────────────────── +// MR-P2-2 用户消息 mention chip:解析 `[项目:名]`/`[任务:名]`/`[灵感:名]` 标记分段渲染。 +// +// 对齐桌面 MessageItem.segmentUserContent:命中已知实体的标记渲染为 chip(浅底圆角徽标), +// 未命中保持字面(防误伤正常方括号文本)。 +// 标记格式与 @ 选中插入一致(onEntitySelect):冒号后是实体展示名 —— project 用 name, +// task/idea 用 title。miniapp 消息内容仅存原始文本(AiUserMessage 只回 message,无 span 元数据), +// 故「已解析成功」以实体名是否命中当前 entities 列表判定(与插入时同字段)。 +// ──────────────────────────────────────────────────────────────── +type UserSegment = + | { type: 'text'; text: string } + | { type: 'chip'; kind: 'project' | 'task' | 'idea'; label: string } + +/** 实体名是否命中已知实体(项目 name / 任务·灵感 title,与 @ 选中插入同字段) */ +function isKnownEntity(kind: 'project' | 'task' | 'idea', name: string): boolean { + const e = entities.value + if (kind === 'project') return e.projects.some((p) => p.name === name) + if (kind === 'task') return e.tasks.some((t) => t.title === name) + return e.ideas.some((i) => i.title === name) +} + +/** 用户消息内容 → 分段(文本段 + chip 段),纯文本返回单文本段(零回归) */ +function segmentUserContent(content: string): UserSegment[] { + const segs: UserSegment[] = [] + const re = /\[(项目|任务|灵感):([^\]]+)\]/g + let last = 0 + let m: RegExpExecArray | null + while ((m = re.exec(content)) !== null) { + if (m.index > last) segs.push({ type: 'text', text: content.slice(last, m.index) }) + const kind = m[1] === '项目' ? 'project' : m[1] === '任务' ? 'task' : 'idea' + const name = m[2].trim() + // 仅命中已知实体渲染 chip;未命中(正常方括号文本/无效引用)保持字面 + segs.push(isKnownEntity(kind, name) ? { type: 'chip', kind, label: name } : { type: 'text', text: m[0] }) + last = m.index + m[0].length + } + if (last < content.length) segs.push({ type: 'text', text: content.slice(last) }) + return segs.length ? segs : [{ type: 'text', text: content }] +} + +/** 用户消息内容是否含 mention 标记段(决定是否走分段渲染;纯文本走原单一 text 零回归) */ +function hasMentionMarker(content: string): boolean { + return /\[(项目|任务|灵感):[^\]]+\]/.test(content) +} + /** * 输入变化时检测联想触发条件。 * @@ -771,8 +815,17 @@ function tokenInOf(t: TokenUsage): number { - - {{ m.content }} + + + {{ m.content }}