From 48c6f84239e19ae1ec46beefad9d17e331ed6fcc Mon Sep 17 00:00:00 2001 From: Ding Shuo Date: Sat, 14 Mar 2026 01:40:48 +0800 Subject: [PATCH] feat: update PlanningAgent to support SSE streaming with reasoning process display and fix UI spacing issues --- API_Gateway_Rules.md | 116 +++++++++++++ Artifact_SplitScreen_Design.md | 140 ++++++++++++++++ src/doc/WebUI_Stream_API_前端对接说明.md | 197 +++++++++++++++++++++++ src/pages/DevOpsAgent.tsx | 82 +++++----- src/pages/PlanningAgent.tsx | 79 ++++++--- src/pages/QualityGate.tsx | 74 ++++----- template/devops.html | 8 +- template/index.html | 6 +- template/planning.html | 6 +- template/quality.html | 12 +- 10 files changed, 606 insertions(+), 114 deletions(-) create mode 100644 API_Gateway_Rules.md create mode 100644 Artifact_SplitScreen_Design.md create mode 100644 src/doc/WebUI_Stream_API_前端对接说明.md diff --git a/API_Gateway_Rules.md b/API_Gateway_Rules.md new file mode 100644 index 0000000..ee5d193 --- /dev/null +++ b/API_Gateway_Rules.md @@ -0,0 +1,116 @@ +# Safe OS UI - API Gateway 路由转发规则文档 + +本系统包含多个独立的后端AI服务(规划系统、开发平台、质量门禁等)。为了使前端统一指向单一入口并简化跨域和请求分发配置,建议使用 API Gateway 进行统一路由和负载均衡。 + +以下是当前前端所有与后端服务交互的接口清单以及转发规则。Gateway 服务需要解析相应的路径前缀(Ingress Prefix),将请求 Rewrite 后转发至对应的内部后端服务。 + +--- + +## 整体架构与入口规则 + +- **统一入口 URL**:`http://:` (前端将所有 API 请求指向此地址) +- **核心路由匹配策略**:基于 URL前缀 进行正则匹配或前缀匹配。 + +| 业务模块 | Ingress Prefix (外部路径) | Forward Target (内部服务地址) | Rewrite Rule (路径重写规则) | +| --- | --- | --- | --- | +| **Planning System** | `/planning-api/*` | `http://localhost:8090` | `/planning-api/(.*)` ➔ `/api/$1` | +| **DevOps System** | `/devops-api/*` | `http://localhost:8000` | `/devops-api/(.*)` ➔ `/$1` | +| **Quality Gate System** | `/quality-api/*` | `http://localhost:5000` | `/quality-api/(.*)` ➔ `/api/$1` | + +--- + +## 1. 规划子系统接口 (Planning System) +**底层服务**: `http://localhost:8090` +前缀重写规则:请求至统一入口 `.../planning-api/X` 会被转发至内部的 `.../api/X`。 + +| 方法 | 外部暴露路径 (Gateway URL) | 内部转发路径 (Target URL) | 接口描述 | +| --- | --- | --- | --- | +| `POST` | `/planning-api/chat/stream` | `/api/chat/stream` | 铁三角 Agent:发送大系统规划/史诗业务需求内容(SSE流式数据返回) | +| `POST` | `/planning-api/upload` | `/api/upload` | 文档上传:支持用户上传附件作为系统知识或输入来源 | + +--- + +## 2. 研发自动引擎接口 (DevOps System) +**底层服务**: `http://localhost:8000` +前缀重写规则:请求至统一入口 `.../devops-api/X` 会被去掉前缀,转为后端的 `.../X`。该系统重度依赖**SSE (Server-Sent Events) 流式连接**,请在Gateway配置中确保不会缓冲或阻断流数据。 + +| 方法 | 外部暴露路径 (Gateway URL) | 内部转发路径 (Target URL) | 接口描述 | +| --- | --- | --- | --- | +| `POST` | `/devops-api/session/start` | `/session/start` | 新建研发自动化任务会话 (Session) | +| `POST` | `/devops-api/session/{sessionId}/clarify` | `/session/{sessionId}/clarify` | 基于需求说明进行需求澄清和补充 | +| `GET` | `/devops-api/session/{sessionId}/pm/stream` | `/session/{sessionId}/pm/stream` | PM角色执行需求细化分析流程 (SSE流) | +| `GET` | `/devops-api/session/{sessionId}/pm/refine/stream` | `/session/{sessionId}/pm/refine/stream` | 追加PM反馈,继续流式提炼需求 (SSE流) | +| `GET` | `/devops-api/session/{sessionId}/qa/stream` | `/session/{sessionId}/qa/stream` | QA角色基于需求生成测试用例并执行评估 (SSE流) | +| `GET` | `/devops-api/session/{sessionId}/qa/refine/stream` | `/session/{sessionId}/qa/refine/stream` | 追加QA用例反馈并更新测试集 (SSE流) | +| `GET` | `/devops-api/session/{sessionId}/dev/stream` | `/session/{sessionId}/dev/stream` | 核心编码过程:依据需求、架构与测试生成最终业务/Java等工程代码 (SSE流) | +| `POST` | `/devops-api/session/{sessionId}/test/run` | `/session/{sessionId}/test/run` | 运行集成编译与单元/端到端测试并获取结果 | +| `GET` | `/devops-api/session/{sessionId}/test/fix/stream` | `/session/{sessionId}/test/fix/stream` | 针对测试错误进行的AI代码自动修复 (SSE流) | + +*注意: `{sessionId}` 属于路径层级中的动态参数,具体网关转发时使用泛类型或通配符放行。* + +--- + +## 3. 代码质量门禁接口 (Quality Gate) +**底层服务**: `http://localhost:5000` +前缀重写规则:请求至统一入口 `.../quality-api/X` 会被转发至后端内部的 `.../api/X`。 + +| 方法 | 外部暴露路径 (Gateway URL) | 内部转发路径 (Target URL) | 接口描述 | +| --- | --- | --- | --- | +| `GET` | `/quality-api/prs` | `/api/prs` | 获取 PR 扫描工单列表(支持多种查询参数) | +| `GET` | `/quality-api/prs/history` | `/api/prs/history` | 获取 PR 处理历史,用于趋势看板 (e.g., `?limit=15`) | +| `GET` | `/quality-api/prs/{prId}` | `/api/prs/{prId}` | 获取某一条指定 PR 扫描记录的详细执行状态及摘要 | +| `GET` | `/quality-api/prs/{prId}/files` | `/api/prs/{prId}/files` | 拉取这条 PR 的被影响/改动的文件目录结构 | +| `GET` | `/quality-api/prs/{prId}/file` | `/api/prs/{prId}/file` | 获取PR里指定文件的 Diff 对象(包含行内审查反馈),附带 `?path=xxx` 查询参数 | +| `POST`| `/quality-api/prs/{prId}/merge` | `/api/prs/{prId}/merge` | 在门禁界面确认问题修缮无误后,合并该次 PR | +| `POST`| `/quality-api/prs/{prId}/close` | `/api/prs/{prId}/close` | 门禁审查不通过,拒绝/关闭此条扫描和合并申请 | + +--- + +## Nginx Gateway 配置示例 (参考) + +如果网关选用 Nginx,可快速参考如下配置来完成上述 Rewrite 与 Proxy: + +```nginx +server { + listen 80; + server_name api-gateway.safe-os.local; + + # 1. Planning API 转发 + location /planning-api/ { + # 截取 /planning-api/ 后的内容,拼接到 /api/ + rewrite ^/planning-api/(.*)$ /api/$1 break; + proxy_pass http://localhost:8090; + + # 启用流连接所需的头信息 + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + proxy_buffering off; + proxy_cache off; + } + + # 2. DevOps API 转发 + location /devops-api/ { + # 截取 /devops-api/ 后的内容,直接拼接 + rewrite ^/devops-api/(.*)$ /$1 break; + proxy_pass http://localhost:8000; + + # 支撑 SSE (Server-Sent Events) 的流配置 + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 3600s; # 考虑大模型生成代码时间较长 + } + + # 3. Quality Gate API 转发 + location /quality-api/ { + # 截取 /quality-api/ 后的内容,拼接到 /api/ + rewrite ^/quality-api/(.*)$ /api/$1 break; + proxy_pass http://localhost:5000; + } +} +``` + +以上文档即针对前端当前服务现状所整理的 API Gateway 路由规划规范,您可以直接根据此规范开发 API Gateway 或调整 Nginx 等负载均衡器的配置。 \ No newline at end of file diff --git a/Artifact_SplitScreen_Design.md b/Artifact_SplitScreen_Design.md new file mode 100644 index 0000000..7f475cb --- /dev/null +++ b/Artifact_SplitScreen_Design.md @@ -0,0 +1,140 @@ +# Vibe Coding Design Docs: Workspace/Artifact Split-Screen Pattern + +## 1. Context & Objective +The goal is to implement a UI/UX pattern similar to **Claude Artifacts** or **Gemini Deep Research**. When a specific complex task is triggered (e.g., "Project Planning Skill"), the single-column chat interface should smoothly transition into a split-screen layout: +- **Left Panel (35%)**: Conversational context, CoT (Chain of Thought) traces, tool calls, and user input. +- **Right Panel (65%)**: A dedicated "Workspace" or "Artifact" rendering area to display long-form content (Markdown, code, diagrams) generated by the Agent's skills. + +Crucially, this system must support **Reflexion/Iterative generation**. The user can comment on the generated artifact in the left panel, and the agent should update the artifact in the right panel based on the feedback. + +--- + +## 2. Frontend Implementation Guide (React + Vite + Tailwind) + +### 2.1 State Management (State & Types) +Extend the existing frontend state to track the workspace status and content. + +```typescript +// 1. Extend the StreamEvent type to support UI control and artifact streaming +type StreamEvent = { + type: + | "thought" + | "tool_call" + | "tool_result" + | "message" // Standard chat message + | "error" + | "workspace_start" // Trigger right panel open + | "workspace_delta" // Streaming text for the right panel + | "workspace_end"; // Streaming completed + content: string; + step?: number; + tool_name?: string; + workspace_title?: string; // Optional title for the artifact +}; + +// 2. Add Workspace State (Can be added to useReducer or a separate useState) +type WorkspaceState = { + isOpen: boolean; + title: string; + content: string; + isGenerating: boolean; +}; +``` + +### 2.2 SSE Parsing Logic +Modify the `onEvent` handler inside `streamChat` to intercept `workspace_*` events. +- When `workspace_start` arrives: Set `workspace.isOpen = true`, clear previous content, set `isGenerating = true`. +- When `workspace_delta` arrives: Append text to `workspace.content`. Do **not** append this text to the left-panel chat history to avoid redundancy. +- When `workspace_end` arrives: Set `isGenerating = false`. + +### 2.3 Layout & UI Re-architecture +Refactor the root `
` of `PlanningAgent.tsx` to handle dynamic flex layouts. Use Tailwind's transition utilities for smooth scaling. + +```tsx +
+ + {/* Left Panel: Chat & Controls */} +
+ {/* Existing Message List & Input Area */} +
+ + {/* Right Panel: Workspace / Deep Research Output */} + {workspace.isOpen && ( +
+ {/* Header */} +
+

+ {workspace.title || 'Project Planning Document'} +

+ {workspace.isGenerating && ( + + Generating... + + )} +
+ {/* Markdown Content Area */} +
+ + {workspace.content} + +
+
+ )} +
+``` + +--- + +## 3. Backend Implementation Guide (Agent / ReAct Loop) + +The backend agent requires structural changes to understand the "Artifact" concept, emit correct SSE events, and maintain the artifact in its memory for iterative edits. + +### 3.1 Tool / Skill Definition +When defining the `Project Planning Skill` for the LLM, clearly state its output behavior so the LLM knows *when* to use it. +- **Tool Description**: `use_planning_workspace`: "Invoke this tool to generate, structure, or update a major project planning document. The output will be rendered in a dedicated UI workspace." + +### 3.2 Context Injection (Memory for Reflexion) +To allow the user to say *"extend the testing phase to 2 weeks"*, the LLM **must know what is currently in the right panel**. +- **Before sending the prompt to the LLM**, query the database/session for the current Artifact state. +- **Prompt Assembly**: + ```text + [System Prompt / ReAct Instructions] + ... + + [Current Workspace Artifact (if exists)] + + # Project Plan + 1. Dev Phase: 1 week + 2. Testing Phase: 1 week + + + [Chat History] + User: extend the testing phase to 2 weeks. + ``` + +### 3.3 Streaming Control (Hijacking the Stream) +Within the ReAct execution loop, when the Agent decides to execute the `Project Planning Skill`: +1. The Backend normally streams `thought` or `tool_call` events. +2. Upon entering the specific Skill execution, the backend emits `{"type": "workspace_start", "workspace_title": "Update: Project Plan"}`. +3. As the LLM (or a sub-agent) generates the Markdown schema, the backend maps these tokens to `workspace_delta` events and flushes them to the frontend. +4. (CRITICAL) Do **not** send these tokens as `message` or `final` chat events. The chat bubble should only say something like: *"I have updated the project plan in the workspace area."* +5. Save the final generated Markdown text into the session memory as the `Current Artifact` for future context injection. + +--- + +## 4. Work Flow Summary (For LLM context generation) + +1. `User` sends prompt: "Plan the new feature". +2. `Agent` thinks (`type: thought`), decides to use `Project Planning Skill` (`type: tool_call`). +3. `Agent` emits `{"type": "workspace_start"}`. +4. `Frontend` expands right panel (65% width). +5. `Agent` streams `{"type": "workspace_delta", "content": "..."}`. +6. `Frontend` live-renders Markdown in the right panel. +7. `Agent` finishes, saves artifact to backend session. +8. `User` reads right panel, types in left panel: "Change point 2". +9. `Agent` receives Left Panel history + Right Panel Artifact Content. +10. `Agent` updates document, streaming new `workspace_delta`. Frontend live-updates the right panel. \ No newline at end of file diff --git a/src/doc/WebUI_Stream_API_前端对接说明.md b/src/doc/WebUI_Stream_API_前端对接说明.md new file mode 100644 index 0000000..669cb6c --- /dev/null +++ b/src/doc/WebUI_Stream_API_前端对接说明.md @@ -0,0 +1,197 @@ +# WebUI `/api/chat/stream` 前端对接说明 + +本文档用于指导前端项目接入 LaodingBot 的流式聊天接口,并可直接作为提示词输入给 LLM,批量改造其他前端代码。 + +## 1. 接口总览 + +- 方法: `POST` +- 路径: `/api/chat/stream` +- 请求头: `Content-Type: application/json` +- 响应类型: `text/event-stream` +- 协议: SSE (Server-Sent Events) + +说明: 该接口为单次请求、多次推送。后端会持续推送 `data: \n\n` 格式的事件。 + +## 2. 请求体 + +```json +{ + "text": "请帮我分析当前目录", + "session_id": "sess_abc", + "user_id": "user_001" +} +``` + +字段说明: + +- `text` (string, required): 用户输入文本,去除空白后不能为空。 +- `session_id` (string, optional): 会话 ID,不传时后端自动生成。 +- `user_id` (string, optional): 用户 ID,不传时后端自动生成。 + +兼容字段: + +- `sessionId` 等价于 `session_id` +- `userId` 等价于 `user_id` + +## 3. SSE 事件格式 + +每条 SSE 消息只包含 `data` 字段,内容是 JSON: + +```text +data: {"type":"thought","content":"我先判断是否需要调用工具","step":1} + +data: {"type":"tool_call","content":"{\"input\":\"pwd\"}","step":1,"tool_name":"shell"} + +data: {"type":"tool_result","content":"C:/Project/MyProject","step":1,"tool_name":"shell"} + +data: {"type":"final","content":"当前目录是 C:/Project/MyProject","step":2} + +``` + +事件字段: + +- `type` (string): 事件类型 +- `content` (string): 事件文本内容 +- `step` (number, optional): ReAct 步骤编号 +- `tool_name` (string, optional): 工具名 + +事件类型: + +- `thought`: LLM 思考片段(可选透传) +- `tool_call`: 工具调用请求(可选透传) +- `tool_result`: 工具执行结果(可选透传) +- `final`: 最终回答 +- `error`: 错误信息 + +说明: + +- 默认情况下(`WEBUI_EXPOSE_REASONING=false`),WebUI 只向前端返回 `final` 和 `error`。 +- 当设置 `WEBUI_EXPOSE_REASONING=true` 时,WebUI 会额外透传 `thought`、`tool_call`、`tool_result` 事件。 +- 无论是否透传推理事件,`final` 都会分段累计推送,以便前端实现打字机效果。 + +## 4. 连接生命周期 + +- 正常结束: 收到 `type=final` 后结束渲染,连接可由浏览器自然关闭。 +- 异常结束: 收到 `type=error`,前端应显示错误并结束当前轮次。 +- 网络中断: 前端应允许用户重试,并保留已收到的事件记录。 + +## 5. 前端渲染建议 + +推荐前端仅处理两类结果: + +- 答案区: 显示最后一个 `final` +- 错误区: 显示 `error` + +如果开启了 `WEBUI_EXPOSE_REASONING=true`,建议额外提供“思考面板”: + +- 思考区: 渲染 `thought` +- 工具区: 渲染 `tool_call` / `tool_result` + +建议状态机: + +- `idle`: 初始状态 +- `streaming`: 请求中且持续接收事件 +- `done`: 收到 `final` +- `failed`: 收到 `error` 或请求异常 + +## 6. TypeScript 对接示例 (fetch + ReadableStream) + +```ts +type StreamEventType = 'thought' | 'tool_call' | 'tool_result' | 'final' | 'error'; + +interface StreamEvent { + type: StreamEventType; + content: string; + step?: number; + tool_name?: string; +} + +export async function streamChat( + payload: { text: string; session_id?: string; user_id?: string }, + onEvent: (event: StreamEvent) => void, + signal?: AbortSignal, +): Promise { + const resp = await fetch('/api/chat/stream', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal, + }); + + if (!resp.ok) { + throw new Error(`HTTP ${resp.status}`); + } + if (!resp.body) { + throw new Error('ReadableStream is not available'); + } + + const reader = resp.body.getReader(); + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + + while (true) { + const { value, done } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + // SSE message delimiter: blank line + let idx = buffer.indexOf('\n\n'); + while (idx >= 0) { + const chunk = buffer.slice(0, idx).trim(); + buffer = buffer.slice(idx + 2); + + for (const line of chunk.split('\n')) { + const text = line.trim(); + if (!text.startsWith('data:')) continue; + const raw = text.slice(5).trim(); + if (!raw) continue; + + const event = JSON.parse(raw) as StreamEvent; + onEvent(event); + } + + idx = buffer.indexOf('\n\n'); + } + } +} +``` + +## 7. 给 LLM 的改造任务提示词模板 + +将下面模板发给 LLM,可用于自动改造其他前端项目: + +```text +你要改造一个前端项目的聊天页面,把非流式接口 `/api/chat` 改为流式接口 `/api/chat/stream`。 + +后端协议约束: +1) 请求方法 POST,Content-Type=application/json +2) 请求体: { text, session_id?, user_id? } +3) 响应是 SSE 文本流,事件格式为 `data: \n\n` +4) JSON 结构: + - type: thought | tool_call | tool_result | final | error + - content: string + - step?: number + - tool_name?: string +5) 收到 final 视为本轮完成;收到 error 视为失败 +6) 默认不要假设前端一定会收到 thought/tool_call/tool_result;仅当 `WEBUI_EXPOSE_REASONING=true` 才会透传 + +你的改造要求: +1) 保留现有 UI 风格和组件结构,不做无关重构 +2) 新增流式读取逻辑,支持中途取消 (AbortController) +3) 将事件按 step 渲染到消息区 +4) 兼容旧会话字段命名 (session_id / sessionId, user_id / userId) +5) 增加错误态与重试按钮 +6) 不破坏原有上传、历史消息和输入框行为 +7) 输出改动文件列表 + 每个文件的关键变更说明 + +请直接给出可运行代码补丁。 +``` + +## 8. 调试清单 + +- 检查响应头是否为 `text/event-stream` +- 检查每条事件是否以 `data:` 开头并以空行结尾 +- 确认 `final` 和 `error` 都能正确结束当前轮次 +- 验证弱网下不会丢失已收到的事件 +- 验证用户快速连续提问时,旧流可被取消 diff --git a/src/pages/DevOpsAgent.tsx b/src/pages/DevOpsAgent.tsx index 43c7e4d..69eb53a 100644 --- a/src/pages/DevOpsAgent.tsx +++ b/src/pages/DevOpsAgent.tsx @@ -1,6 +1,26 @@ import { useCallback, useEffect, useRef, useState, useMemo } from "react"; import { API } from "../config"; +/* ─── Steps & Labels ─── */ +const STEPS = [ + { title: "需求", icon: "💬" }, + { title: "产品分析", icon: "📋" }, + { title: "测试用例", icon: "🧪" }, + { title: "开发代码", icon: "💻" }, + { title: "测试执行", icon: "▶" }, +]; + +const STATUS_LABEL: Record = { + clarifying: "澄清中", + pm_ready: "就绪", + pm_done: "产品分析完成", + qa_ready: "产品分析完成", + qa_done: "QA 完成", + dev_ready: "QA 完成", + dev_done: "代码就绪", + test_done: "测试完成", +}; + /* ─── Types ─── */ type Step = 0 | 1 | 2 | 3 | 4; type ClarifyMsg = { role: "user" | "assistant"; content: string }; @@ -200,13 +220,6 @@ function parseDevStream(text: string): DevStreamResult { } /* ── 步骤标签 ── */ -const STEPS = [ - { title: "Requirements", icon: "💬" }, - { title: "PM Analysis", icon: "📋" }, - { title: "QA Cases", icon: "🧪" }, - { title: "Dev Code", icon: "💻" }, - { title: "Test Run", icon: "▶" }, -]; const STATUS_STEP: Record = { clarifying: 0, pm_ready: 0, @@ -214,13 +227,7 @@ const STATUS_STEP: Record = { qa_done: 2, dev_ready: 2, dev_done: 3, test_done: 4, }; - -const STATUS_LABEL: Record = { - clarifying: "Clarifying", pm_ready: "Ready", - pm_done: "PM Done", qa_ready: "PM Done", - qa_done: "QA Done", dev_ready: "QA Done", - dev_done: "Code Ready", test_done: "Tests Done", -}; + const TYPE_EMOJI: Record = { "Functional": "🧩", "Performance": "⚡", "Security": "🔒", @@ -228,9 +235,12 @@ const TYPE_EMOJI: Record = { }; const TYPE_LABEL: Record = { - "功能测试": "Functional", "性能测试": "Performance", - "安全测试": "Security", "边界测试": "Boundary", - "异常测试": "Exception", "集成测试": "Integration", + "Functional": "功能测试", "Performance": "性能测试", + "Security": "安全测试", "Boundary": "边界测试", + "Exception": "异常测试", "Integration": "集成测试", + "功能测试": "功能测试", "性能测试": "性能测试", + "安全测试": "安全测试", "边界测试": "边界测试", + "异常测试": "异常测试", "集成测试": "集成测试", }; /* ━━━━━━━━━━━━━━━━━━━━━ Component ━━━━━━━━━━━━━━━━━━━━━ */ @@ -548,22 +558,22 @@ export default function DevOpsAgent() {
📋
-

Enter Requirements

-

Describe your product requirements. AI will complete analysis → test cases → code generation automatically.

+

输入需求

+

描述你的产品需求,AI 会自动完成分析 → 用例 → 代码生成。