From 250db4de044f365037f9f0292306b6f5b2894349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Mon, 7 Sep 2026 15:56:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(buttons):=20=E8=A1=8C=E5=8A=A8=E5=88=86?= =?UTF-8?q?=E6=9E=90=E5=AE=BF=E4=B8=BB=E8=B7=AF=E7=94=B1=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=BB=BB=E5=8A=A1id/=E8=A1=8C=E5=8A=A8id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TaskButtonsPage: 宿主路由追加任务id时,行动分析(xdfx) tab 下增加 idKind 下拉框可选任务id/行动id,与外部链接类型一致 - runTaskButton: host 分支按 idKind 取 ctx.actionId 或 ctx.taskId(行动id缺失回退任务id),与 url 分支同规则 - AGENTS.md: 同步更新 idKind 适用范围说明(url/host 均适用) --- AGENTS.md | 2 +- frontend-web/src/core/auth/task-buttons.ts | 13 ++++++++----- frontend-web/src/pages/TaskButtonsPage.tsx | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8caed277..64d55405 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,7 +96,7 @@ The frontend (`frontend-web/`) is a React 19 + Vite SPA: - **智能体使用引导 (Agent onboarding tour, 三段式)**: 智能体「浏览管理 → 创建与配置 → 发起问答」全流程分步引导,组件 `components/workspace/agents/agent-guide.tsx`(同一 TDesign `Guide` 封装模式,与简洁模式引导共享记忆语义:**仅显式点「完成/跳过」才写 localStorage** `deerflow.agent-guide..finished`,中途刷新/离开不记忆)。**自动演示仅 create / chat 两段**(进智能体列表页不弹引导,点「创建智能体」进入新建页时才自动演示;list 段的 `AgentGuide` 传 `autoStart={false}`,只能由头部「使用引导」按钮手动触发)。三段各自挂载、各自记忆:① `list`(`agent-gallery.tsx`,/page/workspace/agents)——分区 Tabs、搜索排序、智能体卡片操作、右上「创建智能体」按钮;**完成/跳过就地结束(不跳转创建页,与 create 段无联动)**,最后一步仅提示进入创建页后会有配置引导;② `create`(`NewAgentPage.tsx`,/page/workspace/agents/new)——左侧对话式创建输入区、基本信息、运行模型+入库模板名、知识空间、SOUL.md、技能穿梭框、保存按钮(9 步);③ `chat`(`AgentChatPage.tsx`,agents/:id/chats)——左栏切换与历史、常用问题 chips、输入框(落地态/会话态两个挂点同一 `chat-input` 标记)、右侧详情面板;`eligible` 限定普通智能体对话页(`showAgentList && !isEmbedded && !taskCtxOn`,即排除通用问答原地嵌入与任务深链工作区),三段共用 `isAgentGuideEnvironment()` 排除 embed/hideChrome/无界环境。高亮锚点 `data-agent-guide`:列表页 `tabs`/`search`/`agent-card`(卡片+列表两种布局的根元素)/`new-agent-btn`,创建页 `create-chat-input`/`create-basic`/`create-model`/`create-knowledge`/`create-soul`/`create-skills`/`create-save-btn`,对话页 `chat-sidebar`/`chat-questions`/`chat-input`/`chat-detail`;同样**启动时按目标存在性过滤步骤**(新用户无卡片/无预设问题时自动剔除)。手动重放:三个页面头部各有「使用引导」按钮(列表页创建按钮左侧、创建页 header 右侧、对话页 header),广播 `agent-guide:start:` 窗口事件强制重放(忽略已看过记忆)。 - **敏感词管理 (Sensitive Word Management)**: configures the **展示层脱敏映射** (原始词 → 替换词). **Gated on the username `lqq`** (not the admin role) — `getAccountDisplayName().toLowerCase() === "lqq"` (username = email `@` prefix, else `user_id`, same as the profile page); the dropdown entry, the route guard, and the page-level guard all use this check. `src/lib/desensitize.ts` ships a frozen seed map `SEED_DESENSITIZE_MAP` (the deployment seed) plus a live mutable `DESENSITIZE_MAP` (initialized from the seed so `desensitize()` works before any fetch). `DesensitizeWordsLoader` (mounted in `App.tsx` `AppProviders`) fetches `/api/sensitive-words` on startup and, if non-empty, calls `replaceDesensitizeMap(...)` to make the backend config the source of truth (empty/failed → keep seed). Page `SensitiveWordsPage.tsx` (route `/page/strategy/admin/sensitive-words`, opened from the 「敏感词管理」 item in the **bottom-left settings dropdown** of `components/page-sidebar.tsx`, present in **both** full + collapsed sidebar modes) lists/adds/edits/启停/deletes words and saves the **whole set atomically** (replace-all); a 「导入种子词」 button merges any missing `SEED_DESENSITIZE_MAP` entries into the editor. Client + hooks in `strategy-components/api/sensitive-words.ts` (`wordsToMap` folds enabled rows into the runtime map). Backend: `/api/sensitive-words` (global/shared; open read, write allowed for admin **or** the `lqq` account), mirroring 菜单管理's store shape. **侧边栏 + 导航栏 menu labels are desensitized** at the data source: `useSidebarMenuItems`/`useActiveThirdLevel` (`core/page-layout/use-sidebar-menu.ts`) map every node's `label` through a desensitizer built **directly from the `/api/sensitive-words` query** (`makeDesensitizer(wordsToMap(words))`, falling back to the global seed `desensitize` while empty) — this sidesteps the `replaceDesensitizeMap` effect-timing race. Only `label` is rewritten; `path`/`id`/`icon` are untouched so routing/highlight are unaffected. All nav surfaces (`page-sidebar.tsx`, `page-sidebar-dark.tsx`, `page-sidebar-v1.tsx`, `strategy-components/Header.tsx`, `PageRoutes.tsx`) consume this hook and are covered; `MenuManagementPage` uses the raw tree so admins still edit real labels. - **技能地址管理 (Skill Address Management, admin-only)**: 扫描运行中的技能(`skills/{public,custom}` 下所有 `.md`/`.py`)里的 **http(s) URL 与 IP/IP:端口**,按唯一地址聚合全部出现位置(跨 .md/.py),支持**批量替换**(如把 UAT 地址一次性改成生产)。主体抽成可复用组件 `SkillAddressesPanel`(`pages/SkillAddressesPage.tsx` 导出,默认导出 `SkillAddressesPage` 仍保留旧路由 `/page/strategy/admin/skill-addresses` 作直链回退),**作为「技能地址管理」标签页嵌入管理员「技能管理」页 `pages/AdminSkillsPage.tsx`**(该页加 Tabs:「技能列表」+「技能地址管理」;入口在「设置和更多」下拉的「技能管理」`workspace-nav-menu.tsx` → `/page/workspace/admin/skills`,不再单列于 `components/page-sidebar.tsx` 左下角设置下拉):打开自动扫描并带**进度条**(扫描逐技能 / 应用逐文件 X/N,应对 150+ 技能),URL/IP 类型过滤 + 搜索 + 「片段批量套用」(如所有 `uat.4.cn` → `prod.4.cn`)、每地址「替换为」输入框(可展开看 `file:line` 片段)、内联**预览**、**应用**、**多关键词检索**(空格分隔全部命中),以及**修改记录**面板——每次应用生成一条记录(显示时间/改了哪些地址 from→to/文件数/处数),可**针对单次记录回退**(二次确认)。client + NDJSON 流式解析在 `strategy-components/api/skill-addresses.ts`。后端 `/api/skill-addresses`(**仅管理员**):所有文件 IO 走 `asyncio.to_thread` 不阻塞事件循环;`GET /scan`(+`/scan/stream`)、`POST /preview`、`POST /apply`(+`/apply/stream`)、`GET /backups`、`POST /rollback/{id}`;**子串安全替换**(基于 token span,不会误伤子串)、`expected_total` 乐观锁(409)、写前自动磁盘备份到 `{skills_root}/.address-edits/`(同时写一份 `{id}.manifest.json` 记录本次替换明细,供修改记录展示)、应用后刷新技能系统提示缓存。纯引擎 `deerflow/skills/address_scan.py`,router `app/gateway/routers/skill_addresses.py`,tests `tests/test_skill_addresses.py`。范围仅运行中技能,不动 `deploy/minimal/skills/`。 -- **按钮管理 (Task Button Management, admin-only)**: configures the **可配置跳转按钮** rendered in the left task panel of the task deep-link workspaces (rwfx / 3qfx / xdfx, see `TaskFxWorkspace`). Each button = `{business, label, linkType, target, appendTaskId, idParam, idKind, enabled, sortOrder}`; `linkType` is `url` (外链 `window.open`, optional append of `?{idParam}={id}` — **`idParam` is the editable query-param name**, e.g. `id`/`task_id`), `business` (内部切换到另一业务, reuses `resolveTaskDeeplinkTarget`, always task id), or `purpose` (弹出当前任务的「任务目的」表格 via the shared `TaskPurposeDialog`, no target — configurable for any page incl. xdfx). **`host`(宿主路由)** asks the embedding parent to navigate: `runTaskButton` sends `window.parent.postMessage({ type:'HOST_NAVIGATE', path, queryParams:{ [idParam]: Number(taskId) } }, hostOrigin)` — `target` is the host route, `appendTaskId`/`idParam` become numeric `queryParams` (task id only), origin from `runtime-config.js` `VITE_HOST_NAVIGATE_ORIGIN` (else ancestorOrigins/referrer; never `*`). **`idKind` (`task`/`action`) only matters for xdfx**: its deep-link `?taskId=` is an 行动id and the 任务id is resolved at login (`fetchTaskIdFromAction`) — `TaskContext` now also stashes the raw `actionId`, so a url button can append either (default `task`). Page `TaskButtonsPage.tsx` (route `/page/strategy/admin/task-buttons`, **admin-gated**, entry「按钮管理」in the bottom-left settings dropdown of `components/page-sidebar.tsx`, both full + collapsed modes) has per-business tabs, edits name/type/target/启停/排序/增删, saves the **whole set atomically** (replace-all), plus 「导入默认」 to merge code-seed defaults. **Seed/fallback** (like 敏感词管理): only when the whole table is empty (fresh deploy, never saved) do the workspace + page fall back to `defaultTaskButtons(cfg)` — rwfx's url buttons (编辑/下一步/3q详情) built from the **live** `/api/public/config/task-deeplink` plus cross-business switch buttons for **all three** businesses (each switches to the other two — rwfx→三情/行动, 3qfx→任务/行动, xdfx→任务/三情). Once saved, the store is the sole source of truth. The old hardcoded jump buttons were migrated out: `TaskFxWorkspace` no longer hardcodes 切换到三情分析, `TaskActionsPanel` keeps only the non-jump 清除记录, and `TaskPurposePanel` dropped its 3q详情 button (now configurable) **and defaults the 目的树 table to collapsed** (chevron toggle). Helpers `core/auth/task-buttons.ts` (`TaskButton`, `defaultTaskButtons`, `resolveTaskButtons`, `buttonsForBusiness`, `runTaskButton`), client+hooks `strategy-components/api/task-buttons.ts`. Backend: `/api/task-buttons` (global/shared; open read, admin replace-all), store `deerflow.persistence.task_buttons` (table `task_buttons`, auto-created by `create_all`), wired `app.state.task_button_store`. Tests: `tests/test_task_buttons.py`. +- **按钮管理 (Task Button Management, admin-only)**: configures the **可配置跳转按钮** rendered in the left task panel of the task deep-link workspaces (rwfx / 3qfx / xdfx, see `TaskFxWorkspace`). Each button = `{business, label, linkType, target, appendTaskId, idParam, idKind, enabled, sortOrder}`; `linkType` is `url` (外链 `window.open`, optional append of `?{idParam}={id}` — **`idParam` is the editable query-param name**, e.g. `id`/`task_id`), `business` (内部切换到另一业务, reuses `resolveTaskDeeplinkTarget`, always task id), or `purpose` (弹出当前任务的「任务目的」表格 via the shared `TaskPurposeDialog`, no target — configurable for any page incl. xdfx). **`host`(宿主路由)** asks the embedding parent to navigate: `runTaskButton` sends `window.parent.postMessage({ type:'HOST_NAVIGATE', path, queryParams:{ [idParam]: Number(taskId) } }, hostOrigin)` — `target` is the host route, `appendTaskId`/`idParam` become numeric `queryParams` (task id, or action id when `idKind:"action"` — same selector as url buttons, xdfx only), origin from `runtime-config.js` `VITE_HOST_NAVIGATE_ORIGIN` (else ancestorOrigins/referrer; never `*`). **`idKind` (`task`/`action`) only matters for xdfx**: its deep-link `?taskId=` is an 行动id and the 任务id is resolved at login (`fetchTaskIdFromAction`) — `TaskContext` now also stashes the raw `actionId`, so a url **or host** button can append either (default `task`). Page `TaskButtonsPage.tsx` (route `/page/strategy/admin/task-buttons`, **admin-gated**, entry「按钮管理」in the bottom-left settings dropdown of `components/page-sidebar.tsx`, both full + collapsed modes) has per-business tabs, edits name/type/target/启停/排序/增删, saves the **whole set atomically** (replace-all), plus 「导入默认」 to merge code-seed defaults. **Seed/fallback** (like 敏感词管理): only when the whole table is empty (fresh deploy, never saved) do the workspace + page fall back to `defaultTaskButtons(cfg)` — rwfx's url buttons (编辑/下一步/3q详情) built from the **live** `/api/public/config/task-deeplink` plus cross-business switch buttons for **all three** businesses (each switches to the other two — rwfx→三情/行动, 3qfx→任务/行动, xdfx→任务/三情). Once saved, the store is the sole source of truth. The old hardcoded jump buttons were migrated out: `TaskFxWorkspace` no longer hardcodes 切换到三情分析, `TaskActionsPanel` keeps only the non-jump 清除记录, and `TaskPurposePanel` dropped its 3q详情 button (now configurable) **and defaults the 目的树 table to collapsed** (chevron toggle). Helpers `core/auth/task-buttons.ts` (`TaskButton`, `defaultTaskButtons`, `resolveTaskButtons`, `buttonsForBusiness`, `runTaskButton`), client+hooks `strategy-components/api/task-buttons.ts`. Backend: `/api/task-buttons` (global/shared; open read, admin replace-all), store `deerflow.persistence.task_buttons` (table `task_buttons`, auto-created by `create_all`), wired `app.state.task_button_store`. Tests: `tests/test_task_buttons.py`. - **State**: TanStack Query for server state, context providers (`SubtasksProvider`, `ArtifactsProvider`, `PromptInputProvider`) scoped to `WorkspaceRoutes` - **LLM streaming**: `@langchain/langgraph-sdk` streams messages from the backend via SSE - **UI**: Radix UI primitives + TailwindCSS v4 + shadcn-style components in `src/components/ui/` diff --git a/frontend-web/src/core/auth/task-buttons.ts b/frontend-web/src/core/auth/task-buttons.ts index 4246bb43..0bc52306 100644 --- a/frontend-web/src/core/auth/task-buttons.ts +++ b/frontend-web/src/core/auth/task-buttons.ts @@ -109,8 +109,8 @@ export interface TaskButton { /** 追加的查询参数名(如 ``id`` / ``task_id``);空则用 ``id``。 */ idParam: string; /** - * ``url`` 追加 id 时取任务id 还是行动id。仅 xdfx(行动分析)二者不同:深链原始 ``?taskId=`` - * 实为行动id,任务id 由 action 接口解析。其它业务两者相同。 + * ``url`` / ``host`` 追加 id 时取任务id 还是行动id。仅 xdfx(行动分析)二者不同:深链原始 + * ``?taskId=`` 实为行动id,任务id 由 action 接口解析。其它业务两者相同。 */ idKind: TaskButtonIdKind; /** ``url`` 外链打开方式:``blank``=新窗口(默认)/ ``self``=当前窗口。仅 ``url`` 类型有意义。 */ @@ -327,7 +327,8 @@ export interface RunTaskButtonCtx { * 执行一个跳转按钮: * - ``url``:拼地址(可选追加 ``?id=``,取任务id 或行动id 视 ``idKind``)新标签打开;地址为空提示未配置。 * - ``business``:复用深链解析(会按目标业务重绑任务上下文)后 ``navigate``(始终用任务id)。 - * - ``host``:向 ``window.parent`` 发 ``HOST_NAVIGATE``,由宿主系统跳转(仅 iframe 嵌入时生效)。 + * - ``host``:向 ``window.parent`` 发 ``HOST_NAVIGATE``,由宿主系统跳转(仅 iframe 嵌入时生效; + * 追加 id 时同样按 ``idKind`` 取任务id / 行动id)。 */ export async function runTaskButton(btn: TaskButton, ctx: RunTaskButtonCtx): Promise { const fail = (m: string) => ctx.onError?.(m); @@ -355,9 +356,11 @@ export async function runTaskButton(btn: TaskButton, ctx: RunTaskButtonCtx): Pro } const queryParams: Record = {}; if (btn.appendTaskId) { - const id = parseHostTaskId(ctx.taskId); + // 行动分析(xdfx)可选发行动id;与 url 外链同规则:行动id 缺失时回退任务id。 + const raw = btn.idKind === "action" ? ctx.actionId || ctx.taskId : ctx.taskId; + const id = parseHostTaskId(raw); if (id === null) { - fail("任务id不是数字,无法跳转宿主系统"); + fail(`${btn.idKind === "action" ? "行动id" : "任务id"}不是数字,无法跳转宿主系统`); return; } queryParams[(btn.idParam || "id").trim() || "id"] = id; diff --git a/frontend-web/src/pages/TaskButtonsPage.tsx b/frontend-web/src/pages/TaskButtonsPage.tsx index 554d18cf..37b1ee0a 100644 --- a/frontend-web/src/pages/TaskButtonsPage.tsx +++ b/frontend-web/src/pages/TaskButtonsPage.tsx @@ -309,7 +309,24 @@ export default function TaskButtonsPage() { className="h-8 w-24" title="发给宿主的参数名(默认 id)" /> - = 任务id(数字) + = + {/* 行动分析(xdfx):行动id 与任务id 不同,可选发哪种;其它业务固定任务id */} + {tab === "xdfx" ? ( + + ) : ( + 任务id(数字) + )} )} From 1244fb1f2f83a415dfc2597ef68caaa96dcdcd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Mon, 7 Sep 2026 17:30:53 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/EightBfStep1Workspace.tsx | 54 ++- .../pages/EightBfRoundtablePage.tsx | 10 + .../deep-research/WorkflowConfigDialog.tsx | 21 +- .../components/PositionIntentInput.tsx | 18 +- .../src/roundtable-planning/api/intent.ts | 3 + .../components/Step1Panel.tsx | 14 +- .../hooks/useStep2Orchestration.ts | 42 ++- .../pages/RoundtablePlanningPage.tsx | 34 ++ .../routers/_builtin_agent_tag_seed.py | 2 + .../app/gateway/routers/_roundtable_seed.py | 5 + .../eightbf-roundtable-intent/SOUL.md | 324 ++++++++++++++++++ .../eightbf-roundtable-intent/config.yaml | 3 + .../backend/app/gateway/routers/intent.py | 11 +- offline-backend-20260512/backend/config.yaml | 12 +- 14 files changed, 515 insertions(+), 38 deletions(-) create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/eightbf-roundtable-intent/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/eightbf-roundtable-intent/config.yaml diff --git a/frontend-web/src/eightbf-roundtable/components/EightBfStep1Workspace.tsx b/frontend-web/src/eightbf-roundtable/components/EightBfStep1Workspace.tsx index 62d06b13..ef351e87 100644 --- a/frontend-web/src/eightbf-roundtable/components/EightBfStep1Workspace.tsx +++ b/frontend-web/src/eightbf-roundtable/components/EightBfStep1Workspace.tsx @@ -31,6 +31,7 @@ import { syncPositionOptionsFromRoles, } from "@/position-roundtable/lib/positions"; import type { DraftStep3SummarySnapshot } from "@/roundtable-planning/api/drafts"; +import { EIGHTBF_INTENT_AGENT_ID } from "@/roundtable-planning/api/intent"; import type { ThreadIdMap } from "@/roundtable-planning/api/multi-agent"; import "@/position-roundtable/styles/intent-understanding.css"; import type { Step1PanelProps } from "@/roundtable-planning/components/Step1Panel"; @@ -96,6 +97,8 @@ export function EightBfStep1Panel({ }: EightBfStep1PanelProps) { const [rightPanelOpen, setRightPanelOpen] = useState(true); const [rightTab, setRightTab] = useState<"agents" | "results">("agents"); + // 第一步内置任务理解智能体:其「常用问题」显示在输入框左上角,点击直接发送。 + const { agent: intentAgent } = useAgent(EIGHTBF_INTENT_AGENT_ID); const openPanel = (tab: "agents" | "results") => { if (rightPanelOpen && rightTab === tab) { @@ -167,6 +170,8 @@ export function EightBfStep1Panel({ modelOptions={modelOptions} selectedModel={selectedModel} onSelectedModelChange={onSelectedModelChange} + commonQuestions={intentAgent?.common_questions ?? null} + sendCommonQuestionOnClick /> )} intentReady={intentReady} @@ -331,8 +336,26 @@ function EightBfAgentWorkspace({ summaryReport: step3Summary, }); + // 第一步绑定的内置任务理解智能体:不在业务链席位里,固定置顶展示, + // 供管理员在本面板查看 / 配置其提示词与常用问题。 + const intentAgent = useMemo( + () => agents.find((agent) => agent.id === EIGHTBF_INTENT_AGENT_ID) ?? null, + [agents], + ); + const intentAgentMatchesKeyword = useMemo(() => { + const keyword = agentKeyword.trim().toLowerCase(); + if (!keyword) return true; + const name = (intentAgent?.name || "8BF 任务理解").toLowerCase(); + const description = (intentAgent?.description || "").toLowerCase(); + return name.includes(keyword) || description.includes(keyword); + }, [agentKeyword, intentAgent]); + useEffect(() => { - if (selectedAgentId && !seats.some((seat) => seat.agent_id === selectedAgentId)) { + if ( + selectedAgentId && + selectedAgentId !== EIGHTBF_INTENT_AGENT_ID && + !seats.some((seat) => seat.agent_id === selectedAgentId) + ) { setSelectedAgentId(null); } }, [seats, selectedAgentId]); @@ -412,7 +435,12 @@ function EightBfAgentWorkspace({ ) : ( toast.info("请在中间对话区向任务理解智能体发起问题")} showRecentChats={false} avatarSrc={agentConfigLogo} @@ -468,7 +496,7 @@ function EightBfAgentWorkspace({ {isLoading && seats.length > 0 ? (
正在加载智能体…
- ) : filteredSeatAgents.length === 0 ? ( + ) : filteredSeatAgents.length === 0 && !intentAgentMatchesKeyword ? (

{seatAgents.length ? "没有符合条件的智能体" : "当前岗位尚未配置智能体"}

{!seatAgents.length && canConfigureChain && ( @@ -477,6 +505,13 @@ function EightBfAgentWorkspace({
) : (
+ {intentAgentMatchesKeyword && ( + setSelectedAgentId(EIGHTBF_INTENT_AGENT_ID)} + /> + )} {filteredSeatAgents.map(({ seat, agent }) => ( ; +} + +/** 智能体列表卡片:席位智能体与置顶的内置任务理解智能体共用同一展示结构。 */ +function EightBfAgentCard({ + name, + description, + onSelect, +}: { + name: string; + description: string; + onSelect: () => void; +}) { const avatarText = firstAgentAvatarChar(name); return (

- 点击卡片选择报告结构(自动填充到报告结构配置);可启停、编辑、删除。 + 点击卡片选择报告结构(自动填充到报告结构配置);点击卡片上的眼睛图标查看大纲详情;可启停、编辑、删除。

)} + {/* 8BF:第二步「人工干预」开关。开启后第二步每走一步暂停,等待用户输入 / + 恢复后继续;关闭则自动推进(与原行为一致)。仅独立业务页传入受控回调时渲染。 */} + {onStep2InterventionChange && ( + + )} + {/* 深链接(taskId)模式:任务固定,把「新建任务」换成「重新分析」——清空会话、对同一任务重做意图分析。 */} {prefillRef.current?.taskId ? (