From ea1145c457ee42f812038f670087793bbae9203a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Jul 2026 07:39:20 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20AgentRun=20?= =?UTF-8?q?=E8=A7=82=E5=AF=9F=E9=A1=B5=E4=BA=A7=E5=93=81=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent-observer-ui-contract.test.ts | 85 +++++++++ .../components/agents/AgentRunInspector.vue | 22 ++- .../src/components/common/VirtualGrid.vue | 95 ++++++++++ .../src/components/common/virtual-grid.ts | 41 +++++ .../src/components/console/InspectorPanel.vue | 9 +- .../src/styles/agent-observer.css | 163 +++++++++++------- .../src/views/agents/AgentRunsView.vue | 121 +++++++++---- 7 files changed, 435 insertions(+), 101 deletions(-) create mode 100644 web/hwlab-cloud-web/scripts/agent-observer-ui-contract.test.ts create mode 100644 web/hwlab-cloud-web/src/components/common/VirtualGrid.vue create mode 100644 web/hwlab-cloud-web/src/components/common/virtual-grid.ts diff --git a/web/hwlab-cloud-web/scripts/agent-observer-ui-contract.test.ts b/web/hwlab-cloud-web/scripts/agent-observer-ui-contract.test.ts new file mode 100644 index 00000000..95d828d2 --- /dev/null +++ b/web/hwlab-cloud-web/scripts/agent-observer-ui-contract.test.ts @@ -0,0 +1,85 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; + +import { + calculateVirtualGridColumnCount, + calculateVirtualGridRowWindowLimit, + chunkVirtualGridItems, +} from "../src/components/common/virtual-grid.ts"; + +const viewSource = readSource("../src/views/agents/AgentRunsView.vue"); +const inspectorSource = readSource("../src/components/agents/AgentRunInspector.vue"); +const inspectorPanelSource = readSource("../src/components/console/InspectorPanel.vue"); +const virtualGridSource = readSource("../src/components/common/VirtualGrid.vue"); +const styleSource = readSource("../src/styles/agent-observer.css"); + +test("VirtualGrid calculates three responsive column tiers", () => { + assert.equal(calculateVirtualGridColumnCount(1920, 360, 3, 10), 3); + assert.equal(calculateVirtualGridColumnCount(960, 360, 3, 10), 2); + assert.equal(calculateVirtualGridColumnCount(390, 360, 3, 10), 1); +}); + +test("VirtualGrid chunks stable rows and bounds the rendered row window", () => { + const items = Array.from({ length: 10 }, (_, index) => ({ id: `run-${index}` })); + const rows = chunkVirtualGridItems(items, 3, (item) => item.id); + assert.deepEqual(rows.map((row) => row.items.length), [3, 3, 3, 1]); + assert.deepEqual(rows.map((row) => row.startIndex), [0, 3, 6, 9]); + assert.equal(rows[0]?.key, "0:run-0|run-1|run-2"); + assert.equal(calculateVirtualGridRowWindowLimit(12, 3), 4); + assert.equal(calculateVirtualGridRowWindowLimit(12, 1), 12); + assert.match(virtualGridSource, /ResizeObserver/); + assert.match(virtualGridSource, /:window-limit="rowWindowLimit"/); + assert.match(virtualGridSource, /role="listitem"/); +}); + +test("AgentRun table exposes the eight-column product contract", () => { + const headers = [ + "状态", + "Run / Task / Command identity", + "Project / Workspace", + "Agent / Profile", + "阶段", + "活动 / 耗时", + "Runner / Node / Lane", + "结果 / 错误", + ]; + let previousIndex = -1; + for (const header of headers) { + const index = viewSource.indexOf(`role="columnheader">${header}`); + assert.ok(index > previousIndex, `missing or unordered table header: ${header}`); + previousIndex = index; + } +}); + +test("AgentRun page reuses shared status, viewer and embedded inspector contracts", () => { + assert.match(viewSource, /import StatusStrip/); + assert.match(viewSource, / { + assert.match(styleSource, /@media \(max-width: 1100px\)/); + assert.match(styleSource, /agent-observer-secondary-filters \{ order: 5; flex: 1 0 100%; \}/); + assert.match(styleSource, /@media \(max-width: 600px\)/); + assert.match(styleSource, /agent-observer-secondary-filters,[\s\S]*agent-observer-density \{ display: none; \}/); + assert.match(styleSource, /agent-observer-filter-trigger \{ grid-column: 2; display: inline-flex; \}/); + assert.match(styleSource, /agent-observer-segment \{ grid-column: 1 \/ -1; width: 100%; \}/); + assert.match(styleSource, /@media \(prefers-reduced-motion: reduce\)/); + assert.match(viewSource, /class="agent-observer-search"/); + assert.match(viewSource, /class="agent-observer-primary-status"/); + assert.match(viewSource, /class="agent-observer-segment" role="group" aria-label="视图切换"/); + assert.match(viewSource, /class="agent-observer-stream"/); +}); + +function readSource(relativePath: string): string { + return readFileSync(new URL(relativePath, import.meta.url), "utf8"); +} diff --git a/web/hwlab-cloud-web/src/components/agents/AgentRunInspector.vue b/web/hwlab-cloud-web/src/components/agents/AgentRunInspector.vue index c64a2198..2426195d 100644 --- a/web/hwlab-cloud-web/src/components/agents/AgentRunInspector.vue +++ b/web/hwlab-cloud-web/src/components/agents/AgentRunInspector.vue @@ -3,20 +3,24 @@ diff --git a/web/hwlab-cloud-web/src/components/common/VirtualGrid.vue b/web/hwlab-cloud-web/src/components/common/VirtualGrid.vue new file mode 100644 index 00000000..7783acc1 --- /dev/null +++ b/web/hwlab-cloud-web/src/components/common/VirtualGrid.vue @@ -0,0 +1,95 @@ + + + + + + diff --git a/web/hwlab-cloud-web/src/components/common/virtual-grid.ts b/web/hwlab-cloud-web/src/components/common/virtual-grid.ts new file mode 100644 index 00000000..504337bc --- /dev/null +++ b/web/hwlab-cloud-web/src/components/common/virtual-grid.ts @@ -0,0 +1,41 @@ +export interface VirtualGridRow { + key: string; + items: T[]; + startIndex: number; +} + +export function calculateVirtualGridColumnCount( + width: number, + minimumColumnWidth: number, + maximumColumns: number, + gap: number, +): number { + if (!Number.isFinite(width) || width <= 0) return 1; + const safeMinimum = Math.max(1, minimumColumnWidth); + const safeMaximum = Math.max(1, Math.floor(maximumColumns)); + const safeGap = Math.max(0, gap); + return Math.max(1, Math.min(safeMaximum, Math.floor((width + safeGap) / (safeMinimum + safeGap)))); +} + +export function chunkVirtualGridItems( + items: T[], + columns: number, + itemKey: (item: T) => string, +): Array> { + const safeColumns = Math.max(1, Math.floor(columns)); + const rows: Array> = []; + for (let startIndex = 0; startIndex < items.length; startIndex += safeColumns) { + const rowItems = items.slice(startIndex, startIndex + safeColumns); + rows.push({ + key: `${startIndex}:${rowItems.map(itemKey).join("|")}`, + items: rowItems, + startIndex, + }); + } + return rows; +} + +export function calculateVirtualGridRowWindowLimit(itemWindowLimit: number | undefined, columns: number): number | undefined { + if (!itemWindowLimit || itemWindowLimit <= 0) return undefined; + return Math.max(1, Math.ceil(itemWindowLimit / Math.max(1, Math.floor(columns)))); +} diff --git a/web/hwlab-cloud-web/src/components/console/InspectorPanel.vue b/web/hwlab-cloud-web/src/components/console/InspectorPanel.vue index 1f4e179c..dc8ec7eb 100644 --- a/web/hwlab-cloud-web/src/components/console/InspectorPanel.vue +++ b/web/hwlab-cloud-web/src/components/console/InspectorPanel.vue @@ -4,13 +4,16 @@