From 29cc004b69a87ba471f4983c5e3746645cc1cef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Thu, 18 Jun 2026 16:52:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(frontend):=20=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8C=BA/=E7=9B=AE=E7=9A=84=E6=A0=91?= =?UTF-8?q?=E8=84=B1=E6=95=8F=20=E2=80=94=20=E4=B8=9A=E5=8A=A1=E5=90=8D?= =?UTF-8?q?=E2=86=92=E4=B8=9A=E5=8A=A1=E7=A0=81=20+=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=80=BC=E8=BF=87=20desensitize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 种子脱敏表新增 任务分析→rwfx / 三情分析→3qfx / 行动分析→xdfx(整词在前,优先于「任务」「行动」匹配) - TaskFxWorkspace:左上业务名 + 底部快捷跳转按钮标签过 desensitize - TaskPurposePanel:目的树表头/单元格正文/理由过 desensitize - 硬编码 UI 文案与 admin 按钮管理页保持原文(与菜单管理一致) Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/lib/desensitize.ts | 4 ++++ .../components/task-components/TaskFxWorkspace.tsx | 6 +++--- .../components/task-components/TaskPurposePanel.tsx | 7 ++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend-web/src/lib/desensitize.ts b/frontend-web/src/lib/desensitize.ts index 38546f01..5440451d 100644 --- a/frontend-web/src/lib/desensitize.ts +++ b/frontend-web/src/lib/desensitize.ts @@ -11,6 +11,10 @@ * {@link DESENSITIZE_MAP}。 */ export const SEED_DESENSITIZE_MAP: Readonly> = Object.freeze({ + // 任务工作区业务名 → 业务码(脱敏展示)。整词在前,优先于下面更短的「任务」「行动」匹配。 + 任务分析: "rwfx", + 三情分析: "3qfx", + 行动分析: "xdfx", 方案: "FA", 设计: "SJ", 任务: "RW", diff --git a/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx b/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx index ace8bd25..91a337f0 100644 --- a/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx +++ b/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx @@ -148,9 +148,9 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str key={btn.id} className="h-11 gap-2 border-transparent bg-[var(--color-main)] px-6 text-base text-white shadow-sm hover:bg-[var(--brand-950)]" onClick={() => handleRun(btn)} - title={btn.label} + title={desensitize(btn.label)} > - {btn.label} + {desensitize(btn.label)} {btn.linkType === "url" ? ( ) : btn.linkType === "purpose" ? ( @@ -191,7 +191,7 @@ function TaskConversationsList({ const navigate = useNavigate(); const { threads, isLoading } = useTaskThreads(taskId, agentId); const currentThreadId = useMemo(() => parseThreadId(pathname, chatsBase), [pathname, chatsBase]); - const businessName = TASK_BUSINESSES.find((b) => b.code === business)?.name ?? ""; + const businessName = desensitize(TASK_BUSINESSES.find((b) => b.code === business)?.name ?? ""); return (
diff --git a/frontend-web/src/strategy-components/components/task-components/TaskPurposePanel.tsx b/frontend-web/src/strategy-components/components/task-components/TaskPurposePanel.tsx index 56bc1f1c..e1b052b5 100644 --- a/frontend-web/src/strategy-components/components/task-components/TaskPurposePanel.tsx +++ b/frontend-web/src/strategy-components/components/task-components/TaskPurposePanel.tsx @@ -31,6 +31,7 @@ import { type TaskPurposeResult, } from "@/core/auth/task-purpose"; import { fetchTaskIdFromAction } from "@/core/auth/task-deeplink"; +import { desensitize } from "@/lib/desensitize"; import { cn } from "@/lib/utils"; /** 目的树表格:拍平成行,列数随数据自适应;层级缺失补空、字段变动不崩。 */ @@ -58,7 +59,7 @@ export function PurposeTable({ tree, className }: { tree: PurposeTreeNode[]; cla key={i} className="bg-muted/60 border-border/60 sticky top-0 border px-3 py-1.5 text-left font-medium whitespace-nowrap" > - {h} + {desensitize(h)} ))} @@ -72,9 +73,9 @@ export function PurposeTable({ tree, className }: { tree: PurposeTreeNode[]; cla {cell ? ( <> - {cell.text || "(空)"} + {cell.text ? desensitize(cell.text) : "(空)"} {cell.reason ? ( - 理由:{cell.reason} + 理由:{desensitize(cell.reason)} ) : null} ) : ( From e5ca717783da69d8899bbafaf56bedc148bd7c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Thu, 18 Jun 2026 17:16:19 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=203qfx/?= =?UTF-8?q?xdfx=20=E4=BB=BB=E5=8A=A1=E6=B7=B1=E9=93=BE=E3=80=8C=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E5=AF=B9=E8=AF=9D=E3=80=8D=E5=BC=80=E5=9C=BA=E7=99=BD?= =?UTF-8?q?=E5=8F=91=E8=BF=9B=E4=B8=8A=E4=B8=80=E4=B8=AA=E4=BC=9A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因竞态:从已有会话点「新建对话」时 isNewThread 立刻翻 true,但 useThreadStream 的 onStreamThreadId 慢一帧才归零;自动发送 effect 同帧 submit,而 SDK 的 SubmitOptions.threadId 仅在 hook 线程为空时生效,于是开场白发进了上一个对话 (表现为「一直在同一个聊天框里发消息」,首次正常、之后每次都串)。 - useThreadStream 追加返回当前绑定的流线程 id onStreamThreadId(向后兼容) - AgentChatPage 自动发送 effect 增加守卫 streamThreadId == null:等底层 stream 切到全新线程后再发,开场白即落到新建对话 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/core/threads/hooks.ts | 5 ++++- frontend-web/src/pages/AgentChatPage.tsx | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend-web/src/core/threads/hooks.ts b/frontend-web/src/core/threads/hooks.ts index 6d34117b..a84e1c32 100644 --- a/frontend-web/src/core/threads/hooks.ts +++ b/frontend-web/src/core/threads/hooks.ts @@ -785,7 +785,10 @@ export function useThreadStream({ } as typeof thread) : thread; - return [mergedThread, sendMessage, isUploading] as const; + // 第 4 个返回值 `onStreamThreadId` = 底层 useStream 当前实际绑定的线程 id(新建/未保存时为 null)。 + // 调用方据此判断「流是否已切到全新线程」,避免在切换会话的那一帧把消息发进上一个会话 + // (submit 的 threadId 选项仅在 hook 线程为 null/undefined 时才生效)。 + return [mergedThread, sendMessage, isUploading, onStreamThreadId] as const; } export function useThreads( diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index 12c09a2b..19008977 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -130,7 +130,7 @@ export default function AgentChatPage({ }, [isNewThread, threadId, agent_id]); const { showNotification } = useNotification(); - const [thread, sendMessage] = useThreadStream({ + const [thread, sendMessage, , streamThreadId] = useThreadStream({ threadId: isNewThread ? undefined : threadId, assistantId: agent_id, context: { ...settings.context, agent_id }, @@ -229,13 +229,19 @@ export default function AgentChatPage({ // 纯绑定任务深链(3qfx/xdfx,无闸门):进入新建对话即自动发出任务开场信息。 // 每次新建对话都带(与 rwfx 闸门「开始问答」发出等价);ref 防止同一会话内重复发出。 + // + // 必须等 `streamThreadId == null`(底层 stream 已切到全新线程)才发:从已有会话点「新建对话」时, + // `isNewThread` 先翻 true,但 useThreadStream 的 onStreamThreadId 要慢一帧才重置为 null。 + // 若在那一帧就 submit,hook 仍绑定上一个会话,submit 的 threadId 选项被忽略 → 开场信息发进 + // 上一个对话(表现为「一直在同一个聊天框里发消息」)。等它归零再发即落到新建线程。 useEffect(() => { if (!taskAutoSendText) return; if (!isNewThread) return; + if (streamThreadId != null) return; if (taskAutoSentRef.current) return; taskAutoSentRef.current = true; handleSubmit({ text: taskAutoSendText, files: [] }); - }, [taskAutoSendText, isNewThread, handleSubmit]); + }, [taskAutoSendText, isNewThread, streamThreadId, handleSubmit]); const handleClarificationSubmit = useCallback( (answer: string) => {