From 1d5dd8fc90339752fe9f978ef751c6c9bf0e3543 Mon Sep 17 00:00:00 2001 From: gozeros Date: Tue, 10 Nov 2026 14:04:09 +0800 Subject: [PATCH] sync update from LAN repo (source master-nj @ 067eb808) --- .gitignore | 9 +- CLAUDE.md | 1 + frontend-web/.env.development | 2 +- frontend-web/docs/analysis-projects.md | 35 + .../workspace/agents/agent-chat-sidebar.tsx | 16 +- .../workspace/agents/agent-detail-panel.tsx | 6 + .../workspace/agents/agent-recent-chats.tsx | 25 +- .../components/workspace/chats/chat-box.tsx | 4 +- .../workspace/general-qa-sidebar.tsx | 16 +- .../src/components/workspace/input-box.tsx | 45 +- .../workspace/knowledge-product-nav.tsx | 28 +- .../workspace/llmwiki-knowledge-selector.tsx | 8 +- .../workspace/model-name-select.tsx | 4 +- .../workspace/my-project-thread-indicator.tsx | 51 + .../components/workspace/recent-chat-list.tsx | 30 +- .../settings/memory-runtime-settings-page.tsx | 67 + .../workspace/settings/settings-dialog.tsx | 19 +- .../workspace/skills/SkillKnowledgePanel.tsx | 6 +- .../workspace/workspace-nav-chat-list.tsx | 38 +- .../workspace/workspace-sidebar.tsx | 1 + .../src/core/analysis-projects/api.ts | 137 + .../src/core/analysis-projects/strategy.ts | 141 + frontend-web/src/core/messages/utils.ts | 32 +- .../src/core/page-layout/sidebar-menu.ts | 31 +- frontend-web/src/core/system-settings/api.ts | 96 + .../src/core/system-settings/hooks.ts | 49 + frontend-web/src/core/threads/hooks.ts | 13 +- .../src/core/threads/project-metadata.test.ts | 46 + .../src/core/threads/project-metadata.ts | 94 + frontend-web/src/core/threads/types.ts | 1 - frontend-web/src/core/threads/utils.ts | 30 +- .../eightbf-roundtable/api/task-context.ts | 26 +- .../components/EightBfStep1Workspace.tsx | 93 + .../pages/EightBfRoundtablePage.tsx | 47 +- frontend-web/src/lib/knowledge-message.ts | 4 + .../src/pages/AnalysisProjectsPage.tsx | 808 ++++ .../src/pages/AssistantKnowledgePage.tsx | 42 +- frontend-web/src/pages/ChatPage.tsx | 42 +- .../src/pages/EnterpriseResearchPage.tsx | 13 +- frontend-web/src/pages/IframeChatPage.tsx | 18 - .../src/pages/LlmWikiKnowledgePage.tsx | 2 +- .../src/pages/OfficialDeerFlowEntry.tsx | 126 +- frontend-web/src/pages/StrategyRoutes.tsx | 25 +- frontend-web/src/pages/WorkspaceRoutes.tsx | 23 +- .../analysis-projects/ProjectConversation.tsx | 242 ++ .../pages/analysis-projects/StrategyBoard.tsx | 215 + .../deep-research/WorkflowConfigDialog.tsx | 11 + .../pages/iframe-chat-mode-buttons.test.ts | 21 +- .../src/pages/iframe-chat-mode-buttons.ts | 15 +- .../src/pages/official-deerflow-entry.css | 2 + .../components/PositionBusinessChainFlow.tsx | 8 +- .../components/PersonaDrawer.tsx | 25 +- .../components/Step2Panel.tsx | 4 + .../hooks/useTaskArtifactSubmission.ts | 15 +- .../roundtable-planning/lib/business-chain.ts | 7 + .../lib/task-artifact-submission.ts | 7 + .../pages/BusinessChainEditorPage.tsx | 2 + .../pages/RoundtablePlanningPage.tsx | 8 + .../src/strategy-components/Header.tsx | 11 +- .../api/assistant-knowledge.ts | 16 +- .../api/enterprise-research.ts | 5 +- .../src/strategy-components/api/llmwiki.ts | 62 +- .../api/skill-knowledge.ts | 3 +- .../llmwiki/WeKnoraKnowledgeDetailPage.tsx | 27 +- .../llmwiki/WeKnoraKnowledgeWorkspace.tsx | 147 +- .../llmwiki/WeKnoraPackageTransfersPage.tsx | 3 +- .../llmwiki/WeKnoraWikiManagementPage.tsx | 33 +- .../llmwiki/WeKnoraWikiPageView.tsx | 21 +- .../llmwiki/WikiPackageTransfers.tsx | 3 +- .../strategy-selection/AgentConfigDialog.tsx | 736 ++++ .../StrategyProjectsPage.tsx | 1180 ++++++ .../StrategySelectionPage.tsx | 3666 +++++++++++++++++ frontend-web/src/strategy-selection/api.ts | 44 + .../src/strategy-selection/constants.ts | 350 ++ .../src/strategy-selection/lib.test.ts | 329 ++ frontend-web/src/strategy-selection/lib.ts | 408 ++ .../strategy-selection/project-routes.test.ts | 29 + .../src/strategy-selection/project-routes.ts | 28 + .../strategy-selection/project-wal.test.ts | 147 + .../src/strategy-selection/project-wal.ts | 362 ++ .../src/strategy-selection/projects.ts | 469 +++ .../strategy-agent-mapping.ts | 112 + .../strategy-agents.test.ts | 66 + .../src/strategy-selection/strategy-agents.ts | 128 + frontend-web/src/strategy-selection/types.ts | 128 + offline-backend-20260512/backend/CLAUDE.md | 76 +- offline-backend-20260512/backend/README.md | 54 +- .../backend/app/gateway/analysis_projects.py | 143 + .../backend/app/gateway/app.py | 31 +- .../app/gateway/assistant_file_ingest.py | 191 + .../backend/app/gateway/authz.py | 9 +- .../backend/app/gateway/deps.py | 106 +- .../app/gateway/llmwiki_index_runtime.py | 369 ++ .../app/gateway/llmwiki_index_scheduler.py | 59 +- .../routers/_strategy_selection_seed.py | 207 + .../SOUL.md | 35 + .../config.yaml | 8 + .../strategy-selection-angle-economy/SOUL.md | 33 + .../config.yaml | 8 + .../SOUL.md | 35 + .../config.yaml | 8 + .../SOUL.md | 34 + .../config.yaml | 8 + .../SOUL.md | 34 + .../config.yaml | 8 + .../strategy-selection-angle-security/SOUL.md | 34 + .../config.yaml | 8 + .../strategy-selection-intent/SOUL.md | 52 + .../strategy-selection-intent/config.yaml | 6 + .../strategy-selection-judge/SOUL.md | 65 + .../strategy-selection-judge/config.yaml | 5 + .../strategy-selection-solution/SOUL.md | 42 + .../strategy-selection-solution/config.yaml | 6 + .../backend/app/gateway/routers/agents.py | 278 +- .../app/gateway/routers/analysis_projects.py | 164 + .../gateway/routers/assistant_knowledge.py | 89 +- .../backend/app/gateway/routers/llmwiki.py | 122 +- .../app/gateway/routers/llmwiki_index.py | 104 +- .../gateway/routers/parallel_agent_tasks.py | 20 +- .../app/gateway/routers/parallel_agents.py | 300 +- .../app/gateway/routers/roundtable_chains.py | 4 + .../app/gateway/routers/strategy_agents.py | 802 ++++ .../app/gateway/routers/system_settings.py | 83 + .../backend/app/gateway/services.py | 10 +- offline-backend-20260512/backend/config.yaml | 25 +- .../backend/docs/summarization.md | 21 +- .../backend/extensions_config.json | 2 +- .../deerflow/agents/analysis_project.py | 82 + .../collection/thread_harvester.py | 11 + .../deerflow/agents/lead_agent/agent.py | 17 +- .../deerflow/agents/lead_agent/prompt.py | 12 +- .../deerflow/agents/memory/v1/queue.py | 5 +- .../agents/memory/v1/summarization_hook.py | 4 +- .../deerflow/agents/memory/v1/updater.py | 5 +- .../llm_error_handling_middleware.py | 14 +- .../agents/middlewares/memory_middleware.py | 25 +- .../middlewares/summarization_middleware.py | 25 +- .../harness/deerflow/config/app_config.py | 31 + .../harness/deerflow/config/llmwiki_config.py | 1 + .../harness/deerflow/config/memory_config.py | 23 +- .../deerflow/config/system_settings.py | 60 +- .../weknora/local_index/embedding.py | 112 +- .../integrations/weknora/local_index/sync.py | 4 +- .../harness/deerflow/models/factory.py | 25 +- .../deerflow/models/message_ordering.py | 37 +- .../harness/deerflow/models/patched_openai.py | 197 +- .../deerflow/models/timing_callback.py | 123 + .../deerflow/persistence/analysis_projects.py | 197 + .../persistence/assistant_knowledge/store.py | 2 + .../deerflow/persistence/models/__init__.py | 7 + .../persistence/parallel_agent_tasks/sql.py | 53 +- .../strategy_agent_roles/__init__.py | 24 + .../persistence/strategy_agent_roles/base.py | 39 + .../strategy_agent_roles/memory.py | 114 + .../persistence/strategy_agent_roles/model.py | 36 + .../persistence/strategy_agent_roles/sql.py | 141 + .../builtins/deep_research_report_tool.py | 25 +- .../packages/harness/deerflow/tools/tools.py | 10 +- .../harness/deerflow/utils/stream_text.py | 39 +- .../harness/tests/test_memory_disabled.py | 49 + .../harness/tests/test_message_ordering.py | 33 +- .../tests/test_model_timing_callback.py | 32 + .../tests/test_patched_openai_reasoning.py | 40 + .../backend/scripts/analyze_llm_timing_log.py | 57 + .../backend/scripts/diagnose_llm_latency.py | 171 + .../backend/tests/test_analysis_projects.py | 227 + .../tests/test_assistant_file_ingest.py | 100 + .../test_llm_error_handling_middleware.py | 100 +- .../tests/test_llmwiki_encoder_runtime.py | 283 ++ .../backend/tests/test_llmwiki_weknora.py | 123 + .../backend/tests/test_llmwiki_wiki_sync.py | 95 +- .../backend/tests/test_prompt_prefix.py | 17 + .../tests/test_roundtable_model_fallback.py | 520 +++ .../tests/test_sandbox_mount_config.py | 46 + .../tests/test_strategy_agent_management.py | 667 +++ .../tests/test_strategy_project_cas.py | 81 + .../tests/test_strategy_project_settings.py | 72 + .../tests/test_strategy_selection_seed.py | 276 ++ .../tests/test_summarization_human_message.py | 38 +- .../tests/test_wiki_package_roundtrip.py | 41 +- offline-backend-20260512/config.yaml | 27 + scripts/start-backend.sh | 4 +- 182 files changed, 18973 insertions(+), 769 deletions(-) create mode 100644 frontend-web/docs/analysis-projects.md create mode 100644 frontend-web/src/components/workspace/my-project-thread-indicator.tsx create mode 100644 frontend-web/src/components/workspace/settings/memory-runtime-settings-page.tsx create mode 100644 frontend-web/src/core/analysis-projects/api.ts create mode 100644 frontend-web/src/core/analysis-projects/strategy.ts create mode 100644 frontend-web/src/core/threads/project-metadata.test.ts create mode 100644 frontend-web/src/core/threads/project-metadata.ts create mode 100644 frontend-web/src/lib/knowledge-message.ts create mode 100644 frontend-web/src/pages/AnalysisProjectsPage.tsx create mode 100644 frontend-web/src/pages/analysis-projects/ProjectConversation.tsx create mode 100644 frontend-web/src/pages/analysis-projects/StrategyBoard.tsx create mode 100644 frontend-web/src/strategy-selection/AgentConfigDialog.tsx create mode 100644 frontend-web/src/strategy-selection/StrategyProjectsPage.tsx create mode 100644 frontend-web/src/strategy-selection/StrategySelectionPage.tsx create mode 100644 frontend-web/src/strategy-selection/api.ts create mode 100644 frontend-web/src/strategy-selection/constants.ts create mode 100644 frontend-web/src/strategy-selection/lib.test.ts create mode 100644 frontend-web/src/strategy-selection/lib.ts create mode 100644 frontend-web/src/strategy-selection/project-routes.test.ts create mode 100644 frontend-web/src/strategy-selection/project-routes.ts create mode 100644 frontend-web/src/strategy-selection/project-wal.test.ts create mode 100644 frontend-web/src/strategy-selection/project-wal.ts create mode 100644 frontend-web/src/strategy-selection/projects.ts create mode 100644 frontend-web/src/strategy-selection/strategy-agent-mapping.ts create mode 100644 frontend-web/src/strategy-selection/strategy-agents.test.ts create mode 100644 frontend-web/src/strategy-selection/strategy-agents.ts create mode 100644 frontend-web/src/strategy-selection/types.ts create mode 100644 offline-backend-20260512/backend/app/gateway/analysis_projects.py create mode 100644 offline-backend-20260512/backend/app/gateway/assistant_file_ingest.py create mode 100644 offline-backend-20260512/backend/app/gateway/llmwiki_index_runtime.py create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed.py create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-diplomacy/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-diplomacy/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-economy/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-economy/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-governance/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-governance/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-law-policy/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-law-policy/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-public-opinion/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-public-opinion/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-security/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-angle-security/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-intent/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-intent/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-judge/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-judge/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-solution/SOUL.md create mode 100644 offline-backend-20260512/backend/app/gateway/routers/_strategy_selection_seed_assets/strategy-selection-solution/config.yaml create mode 100644 offline-backend-20260512/backend/app/gateway/routers/analysis_projects.py create mode 100644 offline-backend-20260512/backend/app/gateway/routers/strategy_agents.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/analysis_project.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/models/timing_callback.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/analysis_projects.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/strategy_agent_roles/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/strategy_agent_roles/base.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/strategy_agent_roles/memory.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/strategy_agent_roles/model.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/strategy_agent_roles/sql.py create mode 100644 offline-backend-20260512/backend/packages/harness/tests/test_memory_disabled.py create mode 100644 offline-backend-20260512/backend/packages/harness/tests/test_model_timing_callback.py create mode 100644 offline-backend-20260512/backend/packages/harness/tests/test_patched_openai_reasoning.py create mode 100644 offline-backend-20260512/backend/scripts/analyze_llm_timing_log.py create mode 100644 offline-backend-20260512/backend/scripts/diagnose_llm_latency.py create mode 100644 offline-backend-20260512/backend/tests/test_analysis_projects.py create mode 100644 offline-backend-20260512/backend/tests/test_assistant_file_ingest.py create mode 100644 offline-backend-20260512/backend/tests/test_llmwiki_encoder_runtime.py create mode 100644 offline-backend-20260512/backend/tests/test_sandbox_mount_config.py create mode 100644 offline-backend-20260512/backend/tests/test_strategy_agent_management.py create mode 100644 offline-backend-20260512/backend/tests/test_strategy_project_cas.py create mode 100644 offline-backend-20260512/backend/tests/test_strategy_project_settings.py create mode 100644 offline-backend-20260512/backend/tests/test_strategy_selection_seed.py diff --git a/.gitignore b/.gitignore index 60964980..ab7109d0 100644 --- a/.gitignore +++ b/.gitignore @@ -100,9 +100,11 @@ offline-backend-20260512/backend/tests/* # Keep the model configuration regression coverage versioned even though # this deployment repository otherwise ignores newly added backend tests. !offline-backend-20260512/backend/tests/test_model_management.py -# Keep the forced-research agent runtime gate and sandbox-policy regressions versioned. -!offline-backend-20260512/backend/tests/test_forced_research_agent.py -# Keep fixed-question exact-match and streaming regression coverage versioned. +# Keep the forced-research agent runtime gate and sandbox-policy regressions versioned. +!offline-backend-20260512/backend/tests/test_forced_research_agent.py +# Keep strategy-selection built-in agent seed and safety-contract regressions versioned. +!offline-backend-20260512/backend/tests/test_strategy_selection_seed.py +# Keep fixed-question exact-match and streaming regression coverage versioned. !offline-backend-20260512/backend/tests/test_fixed_questions.py # Keep configurable es_query system-prompt routing regression coverage versioned. !offline-backend-20260512/backend/tests/test_es_query_routing_prompt.py @@ -110,6 +112,7 @@ offline-backend-20260512/backend/tests/* !offline-backend-20260512/backend/tests/test_llmwiki_local_index_repository.py !offline-backend-20260512/backend/tests/test_llmwiki_vector_search.py !offline-backend-20260512/backend/tests/test_llmwiki_wiki_sync.py +!offline-backend-20260512/backend/tests/test_llmwiki_encoder_runtime.py !offline-backend-20260512/backend/tests/test_external_llmwiki_search.py !offline-backend-20260512/backend/tests/test_llmwiki_wiki_retrieval.py !offline-backend-20260512/backend/tests/test_public_llmwiki_embed.py diff --git a/CLAUDE.md b/CLAUDE.md index ff99545b..755bf4dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,6 +83,7 @@ The frontend (`frontend-web/`) is a React 19 + Vite SPA: - **Pages**: `ChatPage` (thread chat), `AgentChatPage` (custom agent chat), `AgentsPage`/`NewAgentPage` (agent management), `ScheduledTasksPage`/`ScheduledTaskRunDetailPage` (scheduled tasks), `LightAppManagementPage`/`LightAppListPage`/`LightAppIframeView` (轻应用中心 — register/list/embed mini-apps) - **定时任务「JSON → HTML 模板填充」(模板数据转换器)**: a third scheduled-task execution mode. In the create/edit dialog (`ScheduledTasksPage.tsx`), selecting the built-in agent **「模板数据转换器」** (`execution_agent_name === "template-json-builder"`, `TEMPLATE_BUILDER_AGENT_ID`) reveals a 「HTML模板」 select (`useHtmlTemplates` → `GET /api/scheduled-tasks/html-templates`) + a **.json 上传** button (reads the file's text into the prompt box) and **hides** the 必须生成 HTML/Markdown switches (this mode always outputs HTML). The chosen template persists as `execution_context.template_config.template_id` (`TemplateFillConfig` in `core/scheduled-tasks/types.ts`). At run time the backend converts the source JSON (the task prompt) into that template's `DATA` structure, validates (retry-on-fail), fills the template, and stores an HTML page previewable via the existing run-detail HTML preview. The source can be **pure JSON, plain text, or text-with-embedded-JSON** — the backend pre-extracts any embedded JSON and the agent's `SOUL.md` maps it (or the plain text's semantics) to the template's schema skeleton. **Chat-page debug entry (only this agent's `AgentChatPage`)**: when `agent_id === "template-json-builder"`, the chat header shows a 「页面模板」 select + a 「渲染预览」 button — after a normal Q&A run, the button takes the **latest AI reply**, POSTs it to `POST /api/scheduled-tasks/template-fill/render` (`renderTemplateFill`), and pops a dialog rendering the filled HTML in a `sandbox="allow-scripts"` iframe plus the structure-validation status/errors/gaps — for debugging the agent's conversion effect without creating a scheduled task. Gated strictly on the agent id, so other agents' chat pages are untouched. See the backend CLAUDE.md「Template-fill mode」for the engine (`deerflow/runtime/scheduler/template_fill.py`), agent seed, dispatch, and the render API. - **AI 写作·对话版(对话驱动写作台)**: a **parallel new entry** beside the classic AI-writing page (classic page/routes untouched) — route `/page/canvas/ai-writing-chat` (`OpenCanvasRoutes.tsx`), sidebar 「问答管理 → 课题研究(对话版)」. Page `open-canvas/pages/AIWritingChatPage.tsx` fully **reuses the engine** (`AIWritingContext` state machine + LangGraph `useStream`, `AIWritingHistoryProvider`, right-side `AIWritingDraftPanel`) but replaces the presentation: left = `components/ai-writing-chat/WritingChatPanel.tsx` (timeline + **button-only intervention cards** `ChatInterventionCards.tsx` — the five pause cards copied from `InterventionCard.tsx` with all inline textareas removed, confirm/action buttons kept — plus a bottom **unified smart composer**). Free text goes through two-level intent resolution (`open-canvas/api/ai-writing-intent.ts`): deterministic short-phrase fast path (确认/继续/重新搜索/重写大纲…; high-cost actions never fast-pathed) then `POST /api/ai-writing/sessions/{id}/intent` — the backend reads the graph checkpoint (authoritative pause point from the pending interrupt) and routes via `deerflow/agents/ai_writing/intent_router.py` (LLM + strict per-pause action whitelist aligned to the graph's real resume actions, confidence gates: finalize/force_finalize/覆盖式 re_search need ≥0.85 else clarify, payload key-whitelist cleaning). Results map to `submitIntervention`/control (暂停/继续/后台挂起)/QA bypass (`answer` rendered as a chat bubble, never touches graph state)/clarify (amber follow-up bubble with multi-turn context). **Two-state layout**: idle (未开始写作) renders the **classic setup experience unchanged** — `WritingSetupChat` + collapsible `AIWritingPanel` form under `WritingFormProvider` (same as `AIWritingPage`'s idle branch); once writing starts (or history view opens) it switches to the chat workbench. **Unified chronological feed** (`ChatFlowTimeline.tsx`): intent-layer chat bubbles are interleaved INTO the progress-event timeline by anchor (= `progressEvents.length` at message creation), so events triggered by a user message render **below** it (Google-AI-Studio-style single stream, not timeline-above/chat-below); composer-driven interventions suppress the duplicate 「我的要求」card (`suppressedRequirementIndices`). **素材不足自动续跑** only at `material_confirm` (the only pause the graph supports re-search from): needResearch caches the payload as a pending intent, submits `re_search + userQuery`, and auto-submits the pending intent when the new material-confirm pause arrives (cancellable pill). Stage chips + per-stage placeholder + 「当前交互对象」status badge. Backend tests `tests/test_ai_writing_intent.py`. Plan doc: `frontend-web/docs/ai-writing-对话驱动写作台-实现方案.md`. +- **策略优选**: route `/page/strategy/qa/strategy-selection`, sidebar 「问答管理 → 策略优选」. The standalone `src/strategy-selection/` workbench has two horizontal split stages: Step 1 keeps intent-understanding conversation cards on the left and an editable task draft plus angle selection on the right; Step 2 keeps multiplexed parallel strategy cards and automatic judging on the left while the right stays dedicated to the selected-strategy basket and final Markdown synthesis. It consumes the existing `POST /api/parallel-agents/run/stream` protocol, isolates output by stable agent id, supports expanded per-agent dialogs, user selection, Markdown editing/copy/download, and shared server-side DOCX export. A clearly marked neutral Taiwan-Strait risk-management demo is loaded by default for offline UI acceptance. Administrators can edit the SOUL and skills of the nine built-in functional agents through the page dialog; the backend seeds `strategy-selection-{intent,angle-*,judge,solution}` only when their directories are missing, preserving later admin changes. Pure parsing/SSE tests live in `src/strategy-selection/lib.test.ts`; backend seed tests live in `tests/test_strategy_selection_seed.py`. - **轻应用中心 (Light App Center)**: a sibling of 任务管理 in the sidebar with two pages — 应用管理 (admin registers `window_open` or `iframe` mini-apps with a route-param builder: login params resolved from `localStorage.userInfo`, theme params literal) and 应用列表 (card grid of `window_open` apps, `window.open` launch). `iframe` apps are mounted under a chosen parent menu (XK/PG/TY/综合管理) and **dynamically injected into the sidebar** by `core/page-layout/use-sidebar-menu.ts` (`useSidebarMenuItems`), opening in the `LightAppIframeView` host. Client + React Query hooks in `strategy-components/api/light-apps.ts`; URL builder/constants in `strategy-components/lib/light-app.ts`. Backend: `/api/light-apps` (global/shared, admin-gated writes). - **菜单管理 (Menu Management, admin-only)**: an **overlay** over the static sidebar menu — `MenuManagementPage` (`pages/MenuManagementPage.tsx`, route `/page/strategy/admin/menu-management`, opened from the admin-only 「菜单管理」 item in the **bottom-left settings dropdown** of `components/page-sidebar.tsx`). Admins rename / 停用·启用 / reorder (↑↓ or @dnd-kit drag) / re-parent (horizontal drag **or** the edit dialog's 「移动到」parent selector) any node across 一级/二级/三级, **including injected iframe light-app nodes** (`light-app-`). The static tree (`core/page-layout/sidebar-menu.ts`) stays the source of icons/routes; only per-node deltas (`label`/`disabled`/`parentId`/`sortOrder`) persist to the backend. Merge logic in `core/page-layout/menu-overrides.ts` (`applyMenuOverrides`, applied in `use-sidebar-menu.ts` after light-app injection, before role filter; disabling a node hides its whole subtree) — strict **3-level cap** (`wouldExceedDepth`). Client `strategy-components/api/menu-overrides.ts`. Backend: `/api/menu-overrides` (global/shared; open read, admin atomic replace-all). **简介模式** left-nav is a separate overlay: `/api/compact-menu-overrides` stores two independent sets (type `A` / `B`); each website picks one via `runtime-config.js` `VITE_COMPACT_MENU_TYPE`. - **白名单管理 (Login Whitelist, admin-only)**: gates **who may log in**, run as a **blocklist (黑名单模式)**. Two pieces: a global master switch (`system_settings.json → login_whitelist.enabled`, surfaced by **admin-only** `GET|PUT /api/system-settings/login-whitelist`) + a per-user **放行** flag (`users.approved` column, migration `20260625_02`). Users **self-register** as usual and **every new account defaults to `approved=True`** — so a user who has never logged into this system before is allowed straight through; the switch only blocks users an admin has explicitly **取消放行** (`approved=False`). (`create_user`/`User`/`UserRow` all default `approved=True`; admins are always approved; existing rows are backfilled `True`.) Login interception is the single helper `enforce_user_approved(user)` in `app/gateway/routers/auth.py`, called in all three login endpoints (`/login/{local,username,token}`) **after** the account is resolved/created and **before** the JWT is issued: switch off → everyone passes; `system_role=="admin"` → always passes (anti-lockout); else `approved=False` (admin-revoked) → 403 `{code:"PENDING_APPROVAL"}` (the frontend's `extractErrorMessage` surfaces the message and login throws before writing any auth state, so no half-login). Admin approves on `pages/LoginWhitelistPage.tsx` (route `/page/strategy/admin/login-whitelist`, entry「白名单管理」in the bottom-left settings dropdown of `components/page-sidebar.tsx`, both sidebar modes): a top master-switch + a list of all registered users (`GET /api/v1/auth/users`, now carrying `approved`) with a per-row 放行 Switch (`PUT /api/v1/auth/users/{id}/approval`, admin-only; revoking only blocks the **next** login — `token_version` is left untouched, existing sessions are not force-killed). Client/hooks: switch in `strategy-components/api/login-whitelist.ts`, user list + approval in `core/auth/{api,hooks}.ts` (`useSetUserApproval`). Backend tests: `tests/test_login_approval.py`. **账号级「登录口令」(per-account password — 临时开通)**: the same page also lets an admin assign one specific account an individual **plaintext** password (`users.login_password` column, migration `20260625_04`, nullable; auto-added on startup by `_ensure_orm_columns_sync`). Once set, **that account's** username login (`POST /api/v1/auth/login/username`) **requires** `?password=` to exactly match it and that personal password **takes precedence over** the global shared-password gate (`username_login.require_password`); accounts without one keep today's shared-gate / passwordless behavior. The user then logs in via the **existing** frontend URL `#/login/<用户名>?password=xxxx` (LoginPage already forwards `?password=`; `<用户名>` = the account's email `@`-prefix). **Plaintext is intentional** — the password rides in the URL query anyway, so storing it readably lets the admin re-copy the full login link from the page; it is exposed only on the **admin-only** `GET /api/v1/auth/users` (`UserListItem.login_password`) and the new admin-only `PUT /api/v1/auth/users/{id}/login-password` (set non-empty / clear with null·empty; doesn't touch `token_version`). The whitelist page renders a per-user 「登录口令」 column with a set/修改 editor + a live 「复制登录链接」 button (`useSetUserLoginPassword` in `core/auth/hooks.ts`, `setUserLoginPassword` in `core/auth/api.ts`). Backend gate helper `_lookup_user_by_username` (lookup-only, no auto-register) in `routers/auth.py`; tests `tests/test_login_personal_password.py`. diff --git a/frontend-web/.env.development b/frontend-web/.env.development index 5077f8d4..ab23b6c7 100644 --- a/frontend-web/.env.development +++ b/frontend-web/.env.development @@ -18,7 +18,7 @@ VITE_BACKEND_BASE_URL=http://127.0.0.1:8001 VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api # 开发环境普通登录后进入 Vite 内隔离的官方风格页面;/page/* 仍访问当前定制版。 -VITE_USE_OFFICIAL_DEERFLOW_ENTRY=true +VITE_USE_OFFICIAL_DEERFLOW_ENTRY=false # 轻应用「新建笔记」系列接口 VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000 diff --git a/frontend-web/docs/analysis-projects.md b/frontend-web/docs/analysis-projects.md new file mode 100644 index 00000000..31bc0fd1 --- /dev/null +++ b/frontend-web/docs/analysis-projects.md @@ -0,0 +1,35 @@ +# 项目空间 + +入口:全量与简洁导航中的「项目空间」,路由 `/page/workspace/projects`。 + +## 使用流程 + +1. 新建项目,填写名称,在问答、策略智能、方案生成中选择一种模式。 +2. 选择项目默认智能体,填写目标、约束等项目说明。新建项目对话时仍可换用另一个可访问的智能体。 +3. 创建后进入嵌入式问答工作区,左侧保留普通聊天记录;项目对话也会出现在聊天记录中并返回所属项目。 +4. 方案生成可从「工作流配置 · 报告结构」选择、启停、编辑、删除结构,选择后自动填充项目报告结构配置;也可直接编辑大纲及固定/弹性约束。 +5. 策略智能项目保存默认 Strategy Lens;每轮依次进行问题重构、各 Lens 独立分析、策略辩论和方案生成,并分为「意图理解」「多视角策略分析」「完整策略方案」三栏。右栏以文档形式给出执行摘要、目标原则、分阶段行动、资源配置、风险应对、评估复盘、形成依据与备选策略切换条件;原始对话和引用仍可切换查看。 +6. 策略智能对话底部提供改变约束、增删视角、重新推演和挑战当前策略快捷动作;项目主页面可直接添加或删除 Lens。 + +模式在创建后不可修改,前后端都不提供修改入口。项目、对话归属和智能体访问权限在服务端校验;方案结构及每条对话所选智能体也以服务端配置为准。 + +## 代码与运行 + +- `src/pages/AnalysisProjectsPage.tsx`:项目列表、新建、设置、Lens 配置、历史与工作台布局。 +- `src/pages/analysis-projects/ProjectConversation.tsx`:现有聊天、研究进度、方案画布的项目级组合。 +- `src/core/analysis-projects/api.ts`:Gateway 项目 API 与按账号隔离的缓存。 +- 后端:`/api/analysis-projects`,对话仍通过现有 `/api/threads/{thread_id}/runs/*` 流式接口发送;服务端从不可变归属读取项目配置。 + +启动新版 Gateway 后自动创建项目表。生产持久化依赖已配置的数据库;memory 模式中的项目随服务重启丢失。前端沿用仓库的 Vite 开发与构建命令。 + +## 验证范围 + +后端回归命令:在 `offline-backend-20260512/backend` 执行 + +```shell +python -m pytest tests/test_analysis_projects.py tests/test_deep_research_report_tool.py tests/test_writing_mode_report_middleware.py -q +``` + +涵盖内存与 SQLite 存储、跨用户隔离、禁止修改模式、运行时覆盖伪造模式与知识库、更新配置、对话归属与失败清理、固定方案模板及知识库资料进入研究管线。 + +部署前还需使用实际知识库和模型验证三类问答、流式断线续接及方案后续修改。接口模拟与单元测试不能替代真实模型联调。 diff --git a/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx b/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx index 45f39694..cf44531f 100644 --- a/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx +++ b/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx @@ -51,6 +51,10 @@ import { SelectValue, } from "@/components/ui/select"; import { ShareConversationDialog } from "@/components/workspace/thread-share-dialogs"; +import { + MyProjectThreadBadge, + MyProjectThreadIcon, +} from "@/components/workspace/my-project-thread-indicator"; import { useTheme } from "@/contexts/ThemeContext"; import { useAgentsPaginated } from "@/core/agents"; import { getAPIClient } from "@/core/api"; @@ -69,6 +73,7 @@ import { useToggleThreadPin, } from "@/core/threads/hooks"; import type { AgentThread, AgentThreadState } from "@/core/threads/types"; +import { myProjectConversationTypeOfThread } from "@/core/threads/project-metadata"; import { bootstrapResumePath, isBootstrapThread, @@ -479,6 +484,7 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { ) : ( filteredThreads.map((thread) => { const isBoot = isBootstrapThread(thread); + const projectType = myProjectConversationTypeOfThread(thread); const href = isBoot ? bootstrapResumePath(thread.thread_id) : pathOf(thread); const isActive = href === pathname; const pinned = isThreadPinned(thread); @@ -505,6 +511,8 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { )} aria-label="智能体创建会话" /> + ) : projectType ? ( + ) : ( )} - + {desensitize(titleOfThread(thread, t.pages.newChat))} + {projectType && ( + + )} {isActive && ( )} diff --git a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx index 69cfc6a4..57c61149 100644 --- a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx +++ b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx @@ -152,6 +152,7 @@ export function AgentDetailPanel({ positionName, skillsIcon, skillsTitle, + belowSkillsSlot, showCommonQuestions = false, jobResponsibilities, panelTitle, @@ -181,6 +182,8 @@ export function AgentDetailPanel({ positionName?: string; skillsIcon?: ReactNode; skillsTitle?: string; + /** 渲染在技能列表区块下方的自定义内容(8BF 用它放「产物提交 position」配置)。 */ + belowSkillsSlot?: ReactNode; showCommonQuestions?: boolean; /** * 业务链席位上配置的岗位职责。传入(含空字符串)时在常用问题上方展示; @@ -940,6 +943,9 @@ export function AgentDetailPanel({ /> + {/* ── 技能列表下方插槽(8BF 产物提交 position 等)────────────────── */} + {belowSkillsSlot} + {/* ── 最近对话(右下角,仅智能体问答页展示,编辑态隐藏)──────────── */} {showRecentChats && !editing && } diff --git a/frontend-web/src/components/workspace/agents/agent-recent-chats.tsx b/frontend-web/src/components/workspace/agents/agent-recent-chats.tsx index 3bdf1204..3207c8a3 100644 --- a/frontend-web/src/components/workspace/agents/agent-recent-chats.tsx +++ b/frontend-web/src/components/workspace/agents/agent-recent-chats.tsx @@ -4,7 +4,12 @@ import { MessageSquareIcon } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { + MyProjectThreadBadge, + MyProjectThreadIcon, +} from "@/components/workspace/my-project-thread-indicator"; import { useThreadsPage } from "@/core/threads/hooks"; +import { myProjectConversationTypeOfThread } from "@/core/threads/project-metadata"; import { pathOfThread, titleOfThread, isHiddenFromRecentChats, isBootstrapThread } from "@/core/threads/utils"; import { desensitize } from "@/lib/desensitize"; import { cn } from "@/lib/utils"; @@ -45,6 +50,7 @@ export function AgentRecentChats({ agentId }: { agentId: string }) { {threads.map((thread) => { const href = pathOfThread(thread); const isActive = href === pathname; + const projectType = myProjectConversationTypeOfThread(thread); return ( - - + {projectType ? ( + + ) : ( + + )} + {desensitize(titleOfThread(thread, "新对话"))} + {projectType && } ); })} diff --git a/frontend-web/src/components/workspace/chats/chat-box.tsx b/frontend-web/src/components/workspace/chats/chat-box.tsx index 705bdcbd..b04c745d 100644 --- a/frontend-web/src/components/workspace/chats/chat-box.tsx +++ b/frontend-web/src/components/workspace/chats/chat-box.tsx @@ -83,10 +83,12 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string; disableArtifacts?: boolean; + writingMode?: boolean; }> = ({ children, threadId, disableArtifacts = false, + writingMode, }) => { const { isMock, @@ -114,7 +116,7 @@ const ChatBox: React.FC<{ const [settings] = useLocalSettings(); // 写作模式专属的产物拦截 / 沙箱打开策略只在开关开启时生效,普通模式保持 // 原有行为(新产物自动打开 + in-flight 预览不过滤)。 - const writingModeEnabled = !!settings.context.writing_mode; + const writingModeEnabled = writingMode ?? !!settings.context.writing_mode; const artifactDirectoryModeEnabled = settings.display.artifactDirectoryModeEnabled ?? false; const { data: citationDisplay } = useCitationDisplaySettings(); diff --git a/frontend-web/src/components/workspace/general-qa-sidebar.tsx b/frontend-web/src/components/workspace/general-qa-sidebar.tsx index 43e6062d..2ff4ecba 100644 --- a/frontend-web/src/components/workspace/general-qa-sidebar.tsx +++ b/frontend-web/src/components/workspace/general-qa-sidebar.tsx @@ -60,6 +60,10 @@ import { } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { ShareConversationDialog } from "@/components/workspace/thread-share-dialogs"; +import { + MyProjectThreadBadge, + MyProjectThreadIcon, +} from "@/components/workspace/my-project-thread-indicator"; import { useAgentsPaginated, type Agent } from "@/core/agents"; import { getAPIClient } from "@/core/api"; import { useI18n } from "@/core/i18n/hooks"; @@ -75,6 +79,7 @@ import { useToggleThreadPin, } from "@/core/threads/hooks"; import type { AgentThread, AgentThreadState } from "@/core/threads/types"; +import { myProjectConversationTypeOfThread } from "@/core/threads/project-metadata"; import { isBootstrapThread, isHiddenFromRecentChats, @@ -483,6 +488,7 @@ export function GeneralQaSidebar() { ); const isActive = href === pathname; const isBoot = isBootstrapThread(thread); + const projectType = myProjectConversationTypeOfThread(thread); const pinned = isThreadPinned(thread); const favorited = isThreadFavorite(thread); return ( @@ -507,6 +513,8 @@ export function GeneralQaSidebar() { )} aria-label="智能体创建会话" /> + ) : projectType ? ( + ) : ( )} - + {desensitize(titleOfThread(thread, t.pages.newChat))} + {projectType && ( + + )} {isActive && ( )} diff --git a/frontend-web/src/components/workspace/input-box.tsx b/frontend-web/src/components/workspace/input-box.tsx index 3deab8dc..4c278dc4 100644 --- a/frontend-web/src/components/workspace/input-box.tsx +++ b/frontend-web/src/components/workspace/input-box.tsx @@ -2,7 +2,6 @@ import type { ChatStatus } from "ai"; import { - BrainCircuitIcon, BookMarkedIcon, CheckIcon, FileTextIcon, @@ -143,7 +142,6 @@ export function InputBox({ hideAdminRecommendedQuestions, compact, hideContextTools, - hideMemoryTool = true, ...props }: Omit, "onSubmit"> & { assistantId?: string | null; @@ -151,14 +149,9 @@ export function InputBox({ /** 紧凑模式(正常 App)新对话落地态:去掉旋转彩边、用 CM 参考图的白卡输入框, * 并在推荐问题上方加「试试这些高频业务场景」标题。仅落地态生效,默认 false。 */ compact?: boolean; - /** 隐藏输入框工具条上的「写作 / 记忆 / 参考文献」上下文开关按钮(任务深链工作区等 + /** 隐藏输入框工具条上的「写作 / 参考文献」上下文开关按钮(任务深链工作区等 * 受控场景用;文件/模型等其他按钮不受影响)。 */ hideContextTools?: boolean; - /** - * 仅控制「记忆」开关的可见性。产品当前全局隐藏,传入 false 即可无损恢复。 - * 写作和参考文献开关不受影响。 - */ - hideMemoryTool?: boolean; disabled?: boolean; context: Omit< AgentThreadContext, @@ -167,7 +160,6 @@ export function InputBox({ mode: "flash" | "thinking" | "pro" | "ultra" | undefined; reasoning_effort?: "minimal" | "low" | "medium" | "high"; writing_mode?: boolean; - memory_injection_enabled?: boolean; }; extraHeader?: React.ReactNode; isNewThread?: boolean; @@ -184,7 +176,6 @@ export function InputBox({ mode: "flash" | "thinking" | "pro" | "ultra" | undefined; reasoning_effort?: "minimal" | "low" | "medium" | "high"; writing_mode?: boolean; - memory_injection_enabled?: boolean; }, ) => void; onFollowupsVisibilityChange?: (visible: boolean) => void; @@ -397,14 +388,6 @@ export function InputBox({ }); }, [artifactDirectoryModeEnabled, setLocalSettings]); - const memoryInjectionEnabled = context.memory_injection_enabled ?? true; - const handleMemoryInjectionToggle = useCallback(() => { - onContextChange?.({ - ...context, - memory_injection_enabled: !memoryInjectionEnabled, - }); - }, [context, memoryInjectionEnabled, onContextChange]); - const ragModeLocked = isThreadRagModeLocked(threadId); const ragModeEnabled = localSettings.display.ragModeEnabled || ragModeLocked; const handleRagModeToggle = useCallback(() => { @@ -813,32 +796,6 @@ export function InputBox({ 文件 } - {!frontendRagActive && !hideContextTools && !hideMemoryTool && - - {memoryInjectionEnabled && ( - - )} - - 记忆 - - } {referenceModeEntryEnabled && !frontendRagActive && !hideContextTools && { client.setQueryData(LANDING_KEY, mode); toast.success("普通知识库默认页面已更新"); }, + onError: (error) => toast.error(error.message), + }); + const runtime = useLlmWikiRuntime(); + const normalAvailable = runtime.data?.provider === "weknora" && Boolean(runtime.data.api_base_url?.trim()); const isAdmin = getStoredAuth()?.system_role === "admin"; const save = useMutation({ mutationFn: updateKnowledgeBaseProductVersion, @@ -36,15 +53,20 @@ export function KnowledgeProductNav() { const assistant = location.pathname.includes("/knowledge/assistant"); return ( -
- {isAdmin && ( -
+
+ 普通知识库默认页面 + 默认产品版本 setValue(event.target.value)} + className="h-7 w-20 rounded-md border border-[#2b8ffe]/40 bg-[#032680]/60 px-2 text-xs font-semibold tabular-nums text-[#e8f2ff] outline-none focus:border-[#2b8ffe] [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" + /> + +
+
+

+ 该席位提交过程产物时传给任务系统的 position 值,留空按 0 提交。保存会写入业务链「{chain.title}」,仅链所有者 / 管理员可保存。 +

+
+ ); +} diff --git a/frontend-web/src/eightbf-roundtable/pages/EightBfRoundtablePage.tsx b/frontend-web/src/eightbf-roundtable/pages/EightBfRoundtablePage.tsx index 52ddb987..bfdb101c 100644 --- a/frontend-web/src/eightbf-roundtable/pages/EightBfRoundtablePage.tsx +++ b/frontend-web/src/eightbf-roundtable/pages/EightBfRoundtablePage.tsx @@ -88,14 +88,50 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa () => buildEightBfTaskContext(task, taskDictionaries), [task, taskDictionaries], ); + // 席位「产物提交 position」有效取值映射(提交产物时按席位智能体取值): + // 1) 绑定链的席位 —— 键收录小写 agent_id + 小写席位名(会话侧席位标识在不同 + // 表面可能以 id 或名称出现,双键匹配避免查不到); + // 2) 其它公共链上同一 agent_id 配置的值(历史会话的席位可能来自切换绑定前的 + // 旧链,agent_id 跨链唯一指向同一智能体,可安全回退;**名称不跨链匹配**, + // 避免不同链的同名席位互相误配)。 + // 绑定链优先(先写入),未配置的席位不进 map(提交时回退默认 0)。 + const seatArtifactPositions = useMemo(() => { + const map: Record = {}; + const put = (key: string, value: number) => { + const normalized = key.trim().toLowerCase(); + if (normalized && !(normalized in map)) map[normalized] = value; + }; + for (const seat of configuredChain?.seats ?? []) { + if (typeof seat.artifact_position === "number" && Number.isFinite(seat.artifact_position)) { + put(seat.agent_id, seat.artifact_position); + if (seat.name?.trim()) put(seat.name, seat.artifact_position); + } + } + for (const chain of chains) { + if (chain.id === configuredChain?.id) continue; + for (const seat of chain.seats ?? []) { + if (typeof seat.artifact_position === "number" && Number.isFinite(seat.artifact_position)) { + put(seat.agent_id, seat.artifact_position); + } + } + } + return map; + }, [chains, configuredChain]); const taskArtifactSubmissionConfig = useMemo( () => taskId ? { taskId, position: 0, + positionFor: (target) => { + const agentId = target.agentId?.trim(); + if (!agentId) return null; + return seatArtifactPositions[agentId.toLowerCase()] + ?? seatArtifactPositions[target.agentName?.trim().toLowerCase() ?? ""] + ?? null; + }, createProcessReport: createEightBfTaskProcessReport, updateProcessReport: updateEightBfTaskProcessReport, } : undefined, - [taskId], + [taskId, seatArtifactPositions], ); useEffect(() => { @@ -199,6 +235,12 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa if (canConfigureChain) setIsSettingsOpen(true); }, [canConfigureChain]); + // 「智能体配置」面板里保存产物 position 后,把最新链数据写回页面状态, + // 让 seatArtifactPositions / 提交产物取值即时生效(无需刷新页面)。 + const handleChainUpdated = useCallback((updated: BusinessChain) => { + setChains((prev) => prev.map((chain) => (chain.id === updated.id ? updated : chain))); + }, []); + const toolbarSlot = ( <> {canConfigureChain && isSettingsOpen && ( @@ -293,6 +335,7 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa chain={configuredChain} canConfigureChain={canConfigureChain} onConfigureChain={openChainSettings} + onChainUpdated={handleChainUpdated} /> )} renderStep1LeftSidebar={({ intentReady, onCollapse, submittedSeatKeys, onSeatClick }) => ( @@ -343,6 +386,8 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa } rootClassName="eightbf-roundtable" taskArtifactSubmissionConfig={taskArtifactSubmissionConfig} + // 会商「智能体配置」抽屉里只读展示各席位在业务链上配置的产物提交 position。 + seatArtifactPositions={seatArtifactPositions} /> ); diff --git a/frontend-web/src/lib/knowledge-message.ts b/frontend-web/src/lib/knowledge-message.ts new file mode 100644 index 00000000..77e5ac70 --- /dev/null +++ b/frontend-web/src/lib/knowledge-message.ts @@ -0,0 +1,4 @@ +/** Neutral product terminology for system messages; never apply to knowledge content or API identifiers. */ +export function userFacingKnowledgeMessage(value: string): string { + return value.replace(/weknora/gi, "知识服务"); +} diff --git a/frontend-web/src/pages/AnalysisProjectsPage.tsx b/frontend-web/src/pages/AnalysisProjectsPage.tsx new file mode 100644 index 00000000..0a9628fc --- /dev/null +++ b/frontend-web/src/pages/AnalysisProjectsPage.tsx @@ -0,0 +1,808 @@ +import { useEffect, useState } from "react"; +import { + Link, + Route, + Routes, + useLocation, + useMatch, + useNavigate, + useParams, +} from "react-router-dom"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { + ArrowLeft, + Bot, + FolderOpen, + Loader2, + LockKeyhole, + Plus, + Settings, + X, +} from "lucide-react"; +import { toast } from "sonner"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { SidebarTrigger } from "@/components/ui/sidebar"; +import { useLiveAccount } from "@/core/auth/use-live-account"; +import { useAgentSelector } from "@/core/agents/hooks"; +import { WorkflowConfigButton } from "./deep-research/WorkflowConfigDialog"; +import { + createAnalysisProject, + createProjectThread, + DEFAULT_STRATEGY_LENSES, + PROJECT_MODES, + PROJECT_OUTLINE, + projectKeys, + updateAnalysisProject, + useAnalysisProject, + useAnalysisProjects, + useProjectThreadAccess, + useProjectThreads, + type AnalysisProject, + type ProjectFields, + type ProjectMode, + type ProjectReportConfig, + type ProjectStrategyConfig, +} from "@/core/analysis-projects/api"; +import { cn } from "@/lib/utils"; +import { ProjectConversation } from "./analysis-projects/ProjectConversation"; + +const BASE = "/page/workspace/projects"; +const defaultReport: ProjectReportConfig = { + custom_outline: PROJECT_OUTLINE.map((s, i) => `## ${i + 1}. ${s}`).join("\n"), + structure_mode: "fixed", + retrieval_directions: [], + retrieval_skills: [], +}; +const defaultStrategy: ProjectStrategyConfig = { + lenses: [...DEFAULT_STRATEGY_LENSES], +}; +function ErrorState({ error, retry }: { error: unknown; retry?: () => void }) { + return ( +
+

{error instanceof Error ? error.message : "读取失败,请重试"}

+ {retry && ( + + )} +
+ ); +} +function Loading() { + return ( +
+ + 加载中… +
+ ); +} + +function AgentSelect({ + value, + onChange, + label = "选择智能体", +}: { + value: string | null; + onChange: (id: string | null) => void; + label?: string; +}) { + const { agents, isLoading, error } = useAgentSelector(); + return ( + + ); +} + +function ReportConfiguration({ + value, + onChange, +}: { + value: ProjectReportConfig; + onChange: (next: ProjectReportConfig) => void; +}) { + return ( +
+
+
+

工作流配置 · 报告结构

+

+ 选择结构后自动填充;结构库支持新增、启停、编辑和删除。 +

+
+ + onChange({ + custom_outline: content, + structure_mode: structureMode, + retrieval_directions: retrievalDirections, + retrieval_skills: retrievalSkills, + }) + } + /> +
+