From caf5e6cde58f22e1d6f04b177aced82e34e360f2 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Thu, 4 Jun 2026 13:48:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20v0.2=20drop=20Agent=20workspace=20i?= =?UTF-8?q?ntro=20System=20cards,=20fold=20=E7=95=8C=E9=9D=A2=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=20into=20panel=20title=20hint=20(HWLAB=20#803=20follo?= =?UTF-8?q?wup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #807 collapsed the top #code-agent-summary and per-message debug panels into a 调试信息 dialog, but the conversation list still showed two hard-coded System cards (界面模式 / Code Agent 状态) that fed availability into the agent run path. Both appeared as full
bubbles before any user message on every session load, contradicting the issue goal of a clean agent / user conversation feed. - remove the ConversationPanel intro useMemo + [...intro, ...messages] spread; the conversation list now only contains real agent / user messages. - move the static 界面模式 content to a small muted hint line under the panel title (id=workspace-hint, .conversation-panel-hint). - the live Code Agent availability snapshot is already covered by the existing #code-agent-summary + 调试信息 dialog, so it is dropped. - add a conversation-list empty-state placeholder so the panel keeps height for layout smoke (messages.length === 0). - delete the now-orphan MessageRuntimePath / MessageSessionContinuity / MessagePendingContext components (no remaining callers per rg). - extend tools/capture-issue-803-noise.mjs to report each .message-card role plus systemMessageCount / roleCounts so layout-level evidence can assert "no message-system in the conversation list". web:check 12 pass / 0 fail; web:layout:build pass (desktop, narrow, mobile). Issue: pikasTech/HWLAB#803 --- tools/capture-issue-803-noise.mjs | 12 ++++ .../conversation/ConversationPanel.tsx | 30 ++-------- .../conversation/MessagePendingContext.tsx | 43 -------------- .../conversation/MessageRuntimePath.tsx | 37 ------------ .../conversation/MessageSessionContinuity.tsx | 59 ------------------- web/hwlab-cloud-web/src/styles/workbench.css | 2 + 6 files changed, 19 insertions(+), 164 deletions(-) delete mode 100644 web/hwlab-cloud-web/src/components/conversation/MessagePendingContext.tsx delete mode 100644 web/hwlab-cloud-web/src/components/conversation/MessageRuntimePath.tsx delete mode 100644 web/hwlab-cloud-web/src/components/conversation/MessageSessionContinuity.tsx diff --git a/tools/capture-issue-803-noise.mjs b/tools/capture-issue-803-noise.mjs index ac7b5c0e..d07552e8 100644 --- a/tools/capture-issue-803-noise.mjs +++ b/tools/capture-issue-803-noise.mjs @@ -82,6 +82,9 @@ const data = await page.evaluate(() => { const convListRect = convList?.getBoundingClientRect(); const messages = [...document.querySelectorAll(".message-card")].map((card) => { const rect = card.getBoundingClientRect(); + const role = card.getAttribute("data-message-role") + ?? [...card.classList].find((cls) => cls.startsWith("message-"))?.replace(/^message-/, "") + ?? null; const meta = card.querySelector(".message-meta")?.textContent?.replace(/\s+/gu, " ").trim() ?? null; const pending = card.querySelector(".message-pending-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null; const session = card.querySelector(".message-session-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null; @@ -92,6 +95,7 @@ const data = await page.evaluate(() => { const cardWidth = rect.width; const tracePct = cardWidth > 0 && traceEventsRect ? Math.round((traceEventsRect.width / cardWidth) * 100) : null; return { + role, meta, pending, session, @@ -103,6 +107,12 @@ const data = await page.evaluate(() => { traceEventsPct: tracePct }; }); + const systemCount = messages.filter((m) => m.role === "system").length; + const roleCounts = messages.reduce((acc, m) => { + const key = m.role ?? "unknown"; + acc[key] = (acc[key] ?? 0) + 1; + return acc; + }, {}); return { summaryText, summaryDetailText, @@ -112,6 +122,8 @@ const data = await page.evaluate(() => { summaryDetailRect: summaryDetailRect ? { width: Math.round(summaryDetailRect.width), height: Math.round(summaryDetailRect.height) } : null, convListRect: convListRect ? { width: Math.round(convListRect.width), height: Math.round(convListRect.height) } : null, messageCount: messages.length, + systemMessageCount: systemCount, + roleCounts, messages }; }); diff --git a/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx b/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx index 2e8165df..3511e25f 100644 --- a/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx +++ b/web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx @@ -23,25 +23,6 @@ interface ConversationPanelProps { } export function ConversationPanel({ messages, availability, live, chatPending, codeAgentTimeoutMs, onCopySession, onCancelMessage, onRetryMessage, onReplayTrace }: ConversationPanelProps & { codeAgentTimeoutMs: number }): ReactElement { - const intro = useMemo(() => [ - { - id: "intro-mode", - role: "system", - title: "界面模式", - text: "这里是用户工作台:可以整理任务、查看 Device Pod summary 和纯文本事件流。对话会发送到受控 Code Agent 后端;页面不会直接发送硬件变更。", - status: "source", - createdAt: new Date(0).toISOString() - }, - { - id: "intro-code-agent", - role: "system", - title: "Code Agent 状态", - text: availabilitySummary(availability), - status: availability?.ready === true ? "completed" : "source", - createdAt: new Date(0).toISOString() - } - ], [availability]); - return (
@@ -50,6 +31,7 @@ export function ConversationPanel({ messages, availability, live, chatPending, c

Agent 工作区

Agent 对话

+

这里是用户工作台:可以整理任务、查看 Device Pod summary 和纯文本事件流。对话会发送到受控 Code Agent 后端;页面不会直接发送硬件变更。

@@ -58,7 +40,10 @@ export function ConversationPanel({ messages, availability, live, chatPending, c
- {[...intro, ...messages].map((message) => ( + {messages.length === 0 ? ( +

暂无对话。在下方 Command Bar 输入任务,Agent 回复会出现在这里。

+ ) : null} + {messages.map((message) => ( = [ - ["状态", "处理中", false], - ["traceId", typeof message.traceId === "string" ? message.traceId : null, true], - ["conversation", typeof message.conversationId === "string" ? message.conversationId : null, true], - ["session", message.sessionId ?? message.runnerTrace?.sessionId ?? "等待后端分配", true], - ["thread", message.threadId ?? message.runnerTrace?.threadId ?? "等待后端分配", false], - ["sessionStatus", typeof message.runnerTrace?.sessionStatus === "string" ? message.runnerTrace.sessionStatus : null, false], - ["lifecycle", typeof message.runnerTrace?.sessionLifecycleStatus === "string" ? message.runnerTrace.sessionLifecycleStatus : null, false], - ["activityTimeout", `${formatDuration(codeAgentTimeoutMs)} 无新事件`, false] - ]; - return ( -
-
- 处理中 - {message.runnerTrace?.waitingFor ? `等待 ${message.runnerTrace.waitingFor}` : "等待后端事件"} -
-
- {fields.map(([label, value, copyable]) => { - if (value === undefined || value === null || value === "") return null; - return ( -
- {label} - {copyable ? shortToken(value, 32) : value} - {formatBeijingTime(message.updatedAt ?? message.createdAt, { withSeconds: true })} -
- ); - })} -
-
- ); -} diff --git a/web/hwlab-cloud-web/src/components/conversation/MessageRuntimePath.tsx b/web/hwlab-cloud-web/src/components/conversation/MessageRuntimePath.tsx deleted file mode 100644 index b98375f9..00000000 --- a/web/hwlab-cloud-web/src/components/conversation/MessageRuntimePath.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { ReactElement } from "react"; - -import type { ChatMessage } from "../../types/domain"; -import { codeAgentRuntimePathFromMessage } from "../../state/code-agent-status"; -import { toneClass } from "../../utils"; - -interface MessageRuntimePathProps { - message: ChatMessage; -} - -export function MessageRuntimePath({ message }: MessageRuntimePathProps): ReactElement | null { - if (message.role !== "agent") return null; - const runtimePath = codeAgentRuntimePathFromMessage(message as unknown as Record); - if (!runtimePath) return null; - return ( -
- - {runtimePath.label} - {runtimePath.summary} - -
- {runtimePath.rows.map((row) => ( -
- {row.label} - {row.value} -
- ))} - {runtimePath.missingFields.length > 0 ? ( -
- 缺失字段 - {runtimePath.missingFields.join(", ")} -
- ) : null} -
-
- ); -} diff --git a/web/hwlab-cloud-web/src/components/conversation/MessageSessionContinuity.tsx b/web/hwlab-cloud-web/src/components/conversation/MessageSessionContinuity.tsx deleted file mode 100644 index 0001e1ae..00000000 --- a/web/hwlab-cloud-web/src/components/conversation/MessageSessionContinuity.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { ReactElement } from "react"; - -import type { ChatMessage } from "../../types/domain"; -import { nonEmptyString } from "../../utils"; -import { formatBeijingTime, shortToken, toneClass } from "../../utils"; - -interface MessageSessionContinuityProps { - message: ChatMessage; -} - -interface ContinuityField { - label: string; - value: string | null; - copyable: boolean; -} - -export function MessageSessionContinuity({ message }: MessageSessionContinuityProps): ReactElement | null { - if (message.role !== "agent" || message.status === "running") return null; - const sessionLifecycle = pickString(message.runnerTrace?.sessionLifecycleStatus); - const hint = pickString(message.runnerTrace?.sessionLifecycleHint); - const lastEventLabel = pickString(message.runnerTrace?.lastEventLabel); - const fields: ContinuityField[] = [ - { label: "状态", value: statusLabel(message.status), copyable: false }, - { label: "conversation", value: message.conversationId ?? "未确认", copyable: true }, - { label: "session", value: message.sessionId ?? "未确认", copyable: true }, - { label: "thread", value: message.threadId ?? "未确认", copyable: true }, - { label: "lifecycle", value: sessionLifecycle, copyable: false }, - { label: "提示", value: hint, copyable: false }, - { label: "traceId", value: message.traceId ?? "未确认", copyable: true }, - { label: "lastEvent", value: lastEventLabel, copyable: false } - ].filter((field): field is ContinuityField => Boolean(field.value && field.value !== "未确认")); - if (fields.length === 0) return null; - return ( -
- - 会话 {statusLabel(message.status)} - {hint ?? lastEventLabel ?? "会话上下文已记录"} - -
- {fields.map((field) => ( -
- {field.label} - {field.copyable ? shortToken(field.value, 32) : field.value} - {formatBeijingTime(message.updatedAt ?? message.createdAt, { withSeconds: true })} -
- ))} -
-
- ); -} - -function statusLabel(status: string): string { - const map: Record = { completed: "完成", failed: "失败", blocked: "阻塞", timeout: "超时", canceled: "已取消", source: "SOURCE" }; - return map[status] ?? status ?? "未知"; -} -function pickString(value: unknown): string | null { - return typeof value === "string" && value.trim() ? value.trim() : null; -} - diff --git a/web/hwlab-cloud-web/src/styles/workbench.css b/web/hwlab-cloud-web/src/styles/workbench.css index e16f763d..d6ccd5a7 100644 --- a/web/hwlab-cloud-web/src/styles/workbench.css +++ b/web/hwlab-cloud-web/src/styles/workbench.css @@ -53,6 +53,8 @@ button:disabled { cursor: not-allowed; opacity: 0.55; } .session-sidebar { min-width: 0; display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; gap: 10px; padding: 14px; border-right: 1px solid var(--line); background: #f9faf6; overflow: hidden; } .session-sidebar-head, .panel-title-row, .right-sidebar-head, .gate-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .session-sidebar h2, .panel-title-row h2, .right-sidebar h2 { margin: 0; font-size: 18px; } +.conversation-panel-hint { margin: 4px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; max-width: 60ch; } +.conversation-empty { margin: 12px 0; padding: 16px; border: 1px dashed var(--line); border-radius: 6px; color: var(--muted); font-size: 13px; text-align: center; } .session-icon-button, .icon-button, .right-sidebar-toggle, .session-delete-button, .command-button.secondary, #device-event-follow, #device-event-jump { border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); padding: 8px 10px; font-weight: 700; } .session-tabs { min-height: 0; display: grid; align-content: start; gap: 8px; overflow-x: hidden; overflow-y: auto; } .session-tab { display: grid; gap: 3px; text-align: left; border: 1px solid var(--line); border-radius: 6px; padding: 9px; background: var(--surface); }