527c5b0dd5
Continues HWLAB #775 round 4 by restoring the three message-level context panels that the React migration dropped: - `messagePendingContextPanel` (`app-conv.ts:1228`) - `messageSessionContinuityPanel` (`app-conv.ts:1183`) - `messageRuntimePathPanel` (`app-conv.ts:1268`) ## 修复对照 | 类别 | 迁移前 (`893f46bc`) | 迁移后 (round 4) | 状态 | |---|---|---|---| | **Pending context 面板** | `app-conv.ts:1228` `messagePendingContextPanel`(traceId/conversation/session/thread/activityTimeout) | `components/conversation/MessagePendingContext.tsx`:grid 2 列 + activity timeout 用 `formatDuration` | ✅ 对齐 | | **Session continuity 面板** | `app-conv.ts:1183` `messageSessionContinuityPanel`(conversation/session/thread/lifecycle/retryOf/missing/changed) | `components/conversation/MessageSessionContinuity.tsx`:grid 2 列 + lifecycleHint/lastEvent | ✅ 对齐 | | **Runtime path 面板** | `app-conv.ts:1268` `messageRuntimePathPanel`(rows + missingFields + compact details) | `components/conversation/MessageRuntimePath.tsx`:复用 round-1 `codeAgentRuntimePathFromMessage`,加 missingFields 行 | ✅ 对齐 | ## 改动 - 新增 `web/hwlab-cloud-web/src/components/conversation/MessagePendingContext.tsx`(43 行) - 新增 `web/hwlab-cloud-web/src/components/conversation/MessageRuntimePath.tsx`(37 行) - 新增 `web/hwlab-cloud-web/src/components/conversation/MessageSessionContinuity.tsx`(58 行) - `web/hwlab-cloud-web/src/components/conversation/ConversationPanel.tsx` — `MessageCard` 顺序:title → body → pending/continuity/runtime → footer → trace → actions - `web/hwlab-cloud-web/src/App.tsx` — `<ConversationPanel>` 接 `codeAgentTimeoutMs` - `web/hwlab-cloud-web/src/styles/workbench.css` — `.message-pending-context` / `.message-session-context` / `.message-runtime-path` 共享 grid 样式 ## 验收 - `bun run check` 通过(Vite build + 2/2 dist freshness test pass) - `bun run scripts/tsc-check.ts` 通过(strict React TSX, 0 explicit any) - `bun test` 2 pass / 0 fail - `bun run build` 9 dist files verified fresh;`app.js` 221256 → 226176 bytes;`index.css` 16041 → 17152 bytes - dist `app.js` 含 `message-pending-context` / `message-session-context` / `message-runtime-path` / `message-pending-grid` / `message-session-grid` / `message-runtime-grid` - 所有改动文件 < 400 行前端 guard Refs: pikasTech/HWLAB#775 (round 4 of 7) Co-authored-by: HWLAB <ci@hwlab.local>
44 lines
2.4 KiB
TypeScript
44 lines
2.4 KiB
TypeScript
import type { ReactElement } from "react";
|
|
|
|
import type { ChatMessage } from "../../types/domain";
|
|
import { formatBeijingTime, formatDuration, shortToken, toneClass } from "../../utils";
|
|
|
|
interface MessagePendingContextProps {
|
|
message: ChatMessage;
|
|
codeAgentTimeoutMs: number;
|
|
}
|
|
|
|
export function MessagePendingContext({ message, codeAgentTimeoutMs }: MessagePendingContextProps): ReactElement | null {
|
|
if (message.status !== "running") return null;
|
|
const fields: Array<[string, string | null | undefined, boolean]> = [
|
|
["状态", "处理中", 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 (
|
|
<section className="message-pending-context tone-border-pending" data-message-pending-context>
|
|
<div className="message-pending-head">
|
|
<span className={`state-tag tone-${toneClass("pending")}`} data-pending-tag>处理中</span>
|
|
<span className="message-pending-summary">{message.runnerTrace?.waitingFor ? `等待 ${message.runnerTrace.waitingFor}` : "等待后端事件"}</span>
|
|
</div>
|
|
<div className="message-pending-grid" role="list">
|
|
{fields.map(([label, value, copyable]) => {
|
|
if (value === undefined || value === null || value === "") return null;
|
|
return (
|
|
<div key={label} className="message-pending-row" role="listitem">
|
|
<span className="message-pending-key">{label}</span>
|
|
<span className="message-pending-value mono">{copyable ? shortToken(value, 32) : value}</span>
|
|
<span className="message-pending-time">{formatBeijingTime(message.updatedAt ?? message.createdAt, { withSeconds: true })}</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|