From dd70cadcc1be733bdf78bf03bb54ba1d643d7067 Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Sat, 11 Apr 2026 14:42:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20P0=20=E5=89=8D=E7=AB=AF=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E5=AE=8C=E5=96=84=20+=20=E6=B5=8B=E8=AF=95=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ToolCallStatus 组件增强 - main.tsx P0 交互改进(textarea/光标/编辑消息) - 更新测试报告:9/12 通过,3 个外部服务问题 Co-Authored-By: Claude Opus 4.6 (1M context) --- langgraph/src/components/ToolCallStatus.tsx | 108 ++++++++++-- langgraph/src/main.tsx | 76 +++++++-- test.md | 172 +++++++++----------- 3 files changed, 230 insertions(+), 126 deletions(-) diff --git a/langgraph/src/components/ToolCallStatus.tsx b/langgraph/src/components/ToolCallStatus.tsx index c5b6ea7..433a0f1 100644 --- a/langgraph/src/components/ToolCallStatus.tsx +++ b/langgraph/src/components/ToolCallStatus.tsx @@ -1,4 +1,6 @@ -import { Loader2, CheckCircle2 } from "lucide-react"; +import { Loader2, CheckCircle2, ChevronRight, ChevronDown } from "lucide-react"; +import { useState } from "react"; +import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; const TOOL_NAME_MAP: Record = { kb_search: "搜索知识库", @@ -21,39 +23,115 @@ interface ToolCall { id?: string; } +interface UIMsgLocal { + id: string; + type: string; + name: string; + props: Record; + metadata?: { message_id?: string }; +} + interface ToolCallStatusProps { toolCalls: ToolCall[]; isLoading: boolean; - // IDs of tool messages that exist (completed) completedToolIds?: Set; + uiItems?: UIMsgLocal[]; + stream?: Parameters[0]["stream"]; + components?: Parameters[0]["components"]; +} + +function ToolCallRow({ + tc, + isDone, + uiItem, + stream, + components, +}: { + tc: ToolCall; + isDone: boolean; + uiItem?: UIMsgLocal; + stream?: ToolCallStatusProps["stream"]; + components?: ToolCallStatusProps["components"]; +}) { + const [expanded, setExpanded] = useState(false); + const label = tc.name ? (TOOL_NAME_MAP[tc.name] ?? tc.name) : "工具调用"; + const canExpand = isDone && !!uiItem; + + return ( +
+ + {expanded && uiItem && stream && components && ( +
+ +
+ )} +
+ ); } export default function ToolCallStatus({ toolCalls, isLoading, completedToolIds, + uiItems = [], + stream, + components, }: ToolCallStatusProps) { if (!toolCalls.length) return null; return ( -
+
{toolCalls.map((tc, i) => { - const label = tc.name ? (TOOL_NAME_MAP[tc.name] ?? tc.name) : "工具调用"; const isDone = !isLoading || (tc.id && completedToolIds?.has(tc.id)); + // Match UI item by tool name + const uiItem = uiItems.find((ui) => { + if (!tc.name) return false; + const nameMap: Record = { + kb_search: "knowledge-result", + ticket_list: "ticket-summary", + ticket_detail: "ticket-detail", + web_search: "search-result", + google_search: "search-result", + sandbox_run: "sandbox-result", + }; + return ui.name === nameMap[tc.name]; + }); return ( -
- {isDone ? ( - - ) : ( - - )} - {label} - {isDone ? "已完成" : "执行中..."} -
+ tc={tc} + isDone={!!isDone} + uiItem={uiItem} + stream={stream} + components={components} + /> ); })}
diff --git a/langgraph/src/main.tsx b/langgraph/src/main.tsx index e9fb11f..c0dc1b9 100644 --- a/langgraph/src/main.tsx +++ b/langgraph/src/main.tsx @@ -107,9 +107,14 @@ function App() { threadId: currentThreadId ?? undefined, }); - // Auto-scroll to bottom when messages update + // Auto-scroll to bottom only when user is near the bottom useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + const el = scrollContainerRef.current; + if (!el) return; + const isNearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 200; + if (isNearBottom) { + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + } }, [thread.messages]); // Listen for open-canvas events from Gen-UI cards @@ -335,7 +340,7 @@ function App() { {/* ── Right main area ── */}
-
+
{/* Header */}
{/* Hamburger for mobile */} @@ -360,7 +365,15 @@ function App() {
{/* Messages */} -
+
{ + const el = scrollContainerRef.current; + if (!el) return; + setShowScrollBtn(el.scrollHeight - el.scrollTop - el.clientHeight > 200); + }} + > {thread.messages.length === 0 && (
@@ -460,15 +473,42 @@ function App() { return (
- {/* Tool call status */} + {/* Tool call status (with inline expand/collapse for UI cards) */} {toolCalls.length > 0 && ( )} + {/* UI cards not matched to any tool call (standalone) */} + {uiItems + .filter((ui) => !toolCalls.some((tc) => { + const nameMap: Record = { + kb_search: "knowledge-result", + ticket_list: "ticket-summary", + ticket_detail: "ticket-detail", + web_search: "search-result", + google_search: "search-result", + sandbox_run: "sandbox-result", + }; + return tc.name && ui.name === nameMap[tc.name]; + })) + .map((ui: UIMsgLocal) => ( + + ))} + {/* Text reply */} {textContent && (
@@ -483,18 +523,6 @@ function App() {
)} - {/* UI cards */} - {uiItems.map((ui: UIMsgLocal) => ( - - ))} - {/* Regenerate button — only on last AI message, only when not loading */} {isLastAi && !thread.isLoading && (
@@ -538,6 +566,20 @@ function App() {
+ {/* Scroll-to-bottom floating button */} + {showScrollBtn && ( +
+ +
+ )} + {/* Control bar: tools (left) + model selector (right) */}
{/* Tool toggles */} diff --git a/test.md b/test.md index e2cb726..c160c16 100644 --- a/test.md +++ b/test.md @@ -4,8 +4,8 @@ - **开发机**: 192.168.30.30 - **后端**: http://localhost:2024 (LangGraph Server) - **前端**: http://localhost:5173 (Vite dev server) -- **测试时间**: 2026-04-11 -- **测试范围**: 全端点功能测试 + ChatGPT Enterprise 对标分析 +- **测试时间**: 2026-04-11 (第二轮 -- 代码改动后复测) +- **测试范围**: 全端点功能测试 + 新增功能验证 --- @@ -14,77 +14,63 @@ | # | 端点 / 功能 | 方法 | 状态 | 结果 | |---|------------|------|------|------| | 1 | `/ok` 健康检查 | GET | 200 | PASS - 返回 `{"ok":true}` | -| 2 | 前端 `localhost:5173` | GET | 200 | PASS - 返回 Vite dev HTML | -| 3 | Vite Proxy `/threads` | POST | 200 | PASS - 前端端口代理到后端 API,正确返回 thread JSON | +| 2 | `/info` 服务信息 | GET | 200 | PASS - 返回 `{"flags":{"assistants":true,"crons":false}}` | +| 3 | 前端 `localhost:5173` | GET | 200 | PASS - 返回 Vite dev HTML | | 4 | `/threads` 创建 Thread | POST | 200 | PASS - 返回 thread_id | -| 5 | `/assistants/search` | POST | 200 | PASS - 返回 agent + chat 两个 assistant | -| 6 | Supervisor 路由 → generalInput | POST stream | 200 | PASS - "你好" 正确路由到通用对话 | -| 7 | Supervisor 路由 → enterprise (kb_search) | POST stream | 200 | PASS - 路由到 enterprise,触发 kb_search 工具 | +| 5 | Vite Proxy `/threads` | POST | 200 | PASS - 前端端口代理到后端 API | +| 6 | Supervisor 路由 → generalInput | POST stream | 200 | PASS - "你好" 正确路由到通用对话,生成流畅回复 | +| 7 | Supervisor 路由 → enterprise (kb_search) | POST stream | 200 | PARTIAL - 路由正确,触发 kb_search 工具,但 KB Agent 超时 | | 8 | Supervisor 路由 → enterprise (ticket_list) | POST stream | 200 | PASS - 返回 3 条工单 + ticket-summary Gen-UI 卡片 | -| 9 | Supervisor 路由 → searcher (google_search) | POST stream | 200 | PASS - 返回搜索结果 | -| 10 | Supervisor 路由 → coder (sandbox_run) | POST stream | 200 | PARTIAL - 路由正确,LLM 生成代码正确,但 Daytona sandbox 执行失败 (400) | -| 11 | Supervisor 路由 → writer | POST stream | 200 | PASS - 生成 canvas-doc Gen-UI 卡片,含完整 markdown 文档 | +| 9 | Supervisor 路由 → enterprise (ticket_detail) | POST stream | 200 | PARTIAL - 路由正确,参数解析正确,但 Gongdan API 返回 404 | +| 10 | Supervisor 路由 → searcher (google_search) | POST stream | 200 | PASS - 返回丰富搜索结果 (8 条新闻),内容质量高 | +| 11 | Supervisor 路由 → coder (sandbox_run) | POST stream | 200 | PARTIAL - 路由正确,LLM 生成代码正确,Daytona sandbox 执行失败 (400) | +| 12 | Supervisor 路由 → writer (canvas-doc) | POST stream | 200 | PASS - 生成 canvas-doc Gen-UI 卡片,含完整 markdown 文档 (833字) | -### 通过率: 10/11 通过 (90.9%), 1 个部分通过 +### 通过率: 9/12 通过 (75%), 3 个部分通过 (路由均正确,外部服务异常) --- -## 2. Vite Proxy 状态 +## 2. 启动问题修复记录 -**结论: 正常工作** - -通过 `http://localhost:5173/threads` POST 请求成功代理到后端 `http://localhost:2024/threads`,返回正确的 thread JSON。前端 SPA 可以在同源策略下无缝访问后端 API。 +### pdf-parse ESM 导入错误 +- **文件**: `src/agent/utils/file-service.ts` +- **错误**: `SyntaxError: The requested module 'pdf-parse' does not provide an export named 'default'` +- **原因**: pdf-parse v2.x 在 ESM 环境下没有 default export +- **修复**: 改为 `import * as pdfParseModule from "pdf-parse"` + 运行时 fallback +- **状态**: 已修复,服务正常启动 --- -## 3. ChatGPT Enterprise 对标功能清单 +## 3. Gen-UI 卡片验证 -### 已实现功能 - -| 功能 | ChatGPT Enterprise | SOC 实现 | 对标程度 | -|------|-------------------|----------|---------| -| 多轮对话 | 有 | 有 (LangGraph checkpointer) | 完整 | -| 对话历史管理 | 有 (侧边栏) | 有 (ThreadSidebar + CRUD) | 完整 | -| 模型切换 | GPT-4/4o | Flash/Auto/Pro 三模式 | 完整 | -| 知识库检索 (RAG) | 有 (自定义 GPT + Files) | 有 (kb_search + Azure AI Search) | 完整 | -| 网络搜索 | 有 (Browse) | 有 (google_search + web_search) | 完整 | -| 代码解释器 | 有 (Code Interpreter) | 有 (coder agent + Daytona sandbox) | 部分 (sandbox 400 错误) | -| 文档编辑/Canvas | 有 (Canvas) | 有 (writer agent + CanvasPanel) | 完整 | -| Gen-UI 卡片渲染 | 无 (纯文本) | 有 (7 种卡片组件) | 超越 | -| 工单系统集成 | 无 | 有 (ticket_list/detail + Gen-UI) | 超越 (企业特色) | -| 深色/浅色主题 | 有 | 有 (ThemeToggle) | 完整 | -| 文件上传 | 有 | 有 (图片+文件,拖拽+粘贴) | 完整 | -| 图表生成 | 有 (Code Interpreter) | 有 (chart-result Gen-UI) | 完整 | -| Markdown 渲染 | 有 | 有 (MessageBubble) | 完整 | -| SSE 流式输出 | 有 | 有 (useStream) | 完整 | -| 多 Agent 协作 | 无 (单 Agent) | 有 (Supervisor + 5 子 Agent) | 超越 | - -### 尚未实现 - -| 功能 | ChatGPT Enterprise | SOC 状态 | -|------|-------------------|---------| -| 用户认证/SSO | 有 (SAML/OIDC) | 未实现 | -| 用户权限管理 | 有 (Admin Console) | 未实现 | -| 审计日志 | 有 | 未实现 | -| 数据保留策略 | 有 (30 天) | 未实现 | -| 自定义 GPT 创建 | 有 | 未实现 | -| DALL-E 图片生成 | 有 | 未实现 | -| 语音对话 | 有 | 未实现 | -| 插件/Action 市场 | 有 | 未实现 (但 Agent 架构可扩展) | +| 组件 | 对应工具/Agent | 是否触发 | SSE 中 ui 数组 | +|------|--------------|---------|---------------| +| ticket-summary | ticket_list | 是 | 有 props (total, tickets) | +| canvas-doc | writer agent | 是 | 有 props (doc_id, title, _doc_content) | +| search-result | google_search | 待验证 | 工具返回数据正确 | +| knowledge-result | kb_search | 未触发 | KB Agent 超时 | +| ticket-detail | ticket_detail | 未触发 | Gongdan API 404 | +| sandbox-result | sandbox_run | 未触发 | Daytona 400 | --- -## 4. 已实现的 Gen-UI 组件 +## 4. 新增改动验证 -| 组件 | 对应工具/Agent | -|------|--------------| -| knowledge-result | kb_search | -| ticket-summary | ticket_list | -| ticket-detail | ticket_detail | -| search-result | google_search / web_search | -| sandbox-result | sandbox_run / code_execute | -| canvas-doc | writer agent | -| chart-result | coder agent | +### 后端改动 +| 改动项 | 状态 | 备注 | +|--------|------|------| +| truncateMessages 上下文压缩 | PASS | TypeScript 编译通过,各 Agent 集成正确 | +| router.ts 路由改进 | PASS | 路由到正确的 Agent (enterprise/generalInput/searcher/coder/writer) | +| process-attachment.ts 附件处理 | PASS | 新文件已创建,TypeScript 编译通过 | +| retry.ts 工具重试 | PASS | 新文件已创建,TypeScript 编译通过 | +| pdf-parse ESM 修复 | PASS | 修复后服务正常启动 | + +### 前端改动 +| 改动项 | 状态 | 备注 | +|--------|------|------| +| ToolCallStatus.tsx 新组件 | PASS | TypeScript 编译通过 | +| ThreadSidebar.tsx 修改 | PASS | TypeScript 编译通过 | +| main.tsx UX 改进 | PASS | TypeScript 编译通过 | --- @@ -94,26 +80,35 @@ - **端点**: coder agent → code_execute → Daytona API - **错误**: `Daytona create failed: 400` - **影响**: 代码解释器功能完全不可用 -- **建议**: 检查 DAYTONA_API_KEY 和 DAYTONA_API_URL 环境变量配置,验证 Daytona 服务可用性 +- **建议**: 检查 DAYTONA_API_KEY 和 DAYTONA_API_URL 环境变量配置 + +### P1 - KB Agent 超时 +- **端点**: enterprise agent → kb_search → KB Agent API +- **错误**: `TimeoutError: The operation was aborted due to timeout` +- **影响**: 知识库检索功能间歇性不可用 +- **建议**: 增加超时时间或检查 KB Agent 服务状态,已有 retry 工具可集成 + +### P2 - Gongdan API ticket_detail 404 +- **端点**: enterprise agent → ticket_detail → Gongdan API +- **错误**: `Ticket detail failed: 404` +- **影响**: 工单详情查看功能不可用 +- **建议**: 检查 Gongdan API 的 ticket_detail 端点路径和参数格式 --- -## 6. 最大的 3 个质量风险 +## 6. 与上一次测试对比 -### 风险 1: Daytona Sandbox 不可用 (严重) -代码解释器是核心差异化功能之一。当前 Daytona sandbox 返回 400 错误,整个 coder agent 链路虽然路由和代码生成正常,但执行环节断裂。用户体验直接受损。 - -### 风险 2: 无用户认证体系 (高) -当前系统无任何认证机制,所有 API 完全开放。对于企业级产品,这意味着: -- 任何人都可以访问所有对话历史 -- 无法区分用户身份 -- 无法满足企业合规要求 -- 部署到公网后存在安全风险 - -### 风险 3: 错误处理和降级策略不够健壮 (中) -- 工具调用失败时(如 Daytona 400),错误信息直接暴露给用户("工具执行失败") -- 外部服务(KB Agent, Gongdan API, Jina, Serper)任一不可用时,缺乏优雅降级 -- SSE 流中断后无自动重连机制 +| 项目 | 上次 (4/11 第1轮) | 本次 (4/11 第2轮) | 变化 | +|------|----------|----------|------| +| 通过率 | 10/11 (90.9%) | 9/12 (75%) | 新增 3 项测试,发现更多外部服务问题 | +| Router 路由正确率 | 6/6 (100%) | 7/7 (100%) | 新增 writer/coder 路由验证 | +| kb_search | PASS | PARTIAL (超时) | 退化 -- KB Agent 服务不稳定 | +| ticket_detail | 未测试 | PARTIAL (404) | 新增测试项 | +| Daytona sandbox | PARTIAL (400) | PARTIAL (400) | 未修复 | +| writer agent | PASS | PASS | 保持 | +| google_search | PASS | PASS | 结果质量提升 | +| pdf-parse 启动错误 | 无此问题 | 发现并修复 | 新增文件引入的问题 | +| TypeScript 编译 | - | PASS | 所有新增代码编译通过 | --- @@ -122,29 +117,18 @@ ### 7.5 / 10 **加分项:** -- 架构设计成熟 -- Supervisor + 5 子 Agent 路由,可扩展性强 -- Gen-UI 卡片系统是亮点,超越 ChatGPT 的纯文本回复 -- 工具链丰富 (知识库、工单、搜索、代码、文档) 且均正确路由 -- 前端完成度高 (主题切换、文件上传、拖拽、Canvas) -- SSE 流式输出 + useStream 集成流畅 +- 架构设计成熟 -- Supervisor + 多子 Agent 路由,所有路由 100% 正确 +- Gen-UI 卡片系统正常工作 (ticket-summary, canvas-doc) +- TypeScript 编译 0 错误,代码质量好 +- 新增功能 (truncateMessages, retry, process-attachment) 集成完整 +- 错误处理改进 -- 工具失败时给出用户友好的建议 **减分项:** - Daytona sandbox 不可用 (-1) -- 无认证体系 (-1) -- 缺少自动化测试覆盖 (-0.5) +- KB Agent 超时不稳定 (-0.5) +- Gongdan ticket_detail 404 (-0.5) +- 无认证体系 (-0.5) --- -## 与上一次测试 (2026-04-10) 对比 - -| 项目 | 上次 | 本次 | 变化 | -|------|------|------|------| -| 通过率 | 6/8 (75%) | 10/11 (90.9%) | 提升 | -| Router 路由正确率 | 6/6 (100%) | 6/6 (100%) | 保持 | -| kb_search | PARTIAL (超时) | PASS | 修复 | -| Daytona sandbox | PARTIAL (400) | PARTIAL (400) | 未修复 | -| 新增测试 | - | Vite Proxy, assistants API, writer Gen-UI | +3 项 | - ---- - -*测试人: SOC Test Agent | 生成时间: 2026-04-11* +*测试人: SOC Test Agent | 生成时间: 2026-04-11 (第二轮复测)*