fix: bound Workbench trace rendering
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-010401080313 Workbench实时权威 draft-2026-07-14-p0-pure-kafka-authority.
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
@@ -270,6 +271,42 @@ describe("TraceTimeline sequence authority", () => {
|
||||
expect(completedSummary.text()).not.toMatch(/\b(?:ok|fail|run|running)\b|total=|duration=|exit=|工具调用成功/u);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it("bounds renderer input and DOM rows for a long Kafka Trace", async () => {
|
||||
const events = Array.from({ length: 1_000 }, (_, index) => ({
|
||||
sourceEventId: `evt_long_${index + 1}`,
|
||||
runId: "run_long_trace",
|
||||
sourceSeq: index + 1,
|
||||
source: "agentrun.kafka",
|
||||
type: "assistant",
|
||||
label: "agentrun:assistant:message",
|
||||
message: `long trace event ${index + 1}`
|
||||
}));
|
||||
const wrapper = mount(TraceTimeline, {
|
||||
props: {
|
||||
autoExpanded: true,
|
||||
trace: {
|
||||
traceId: "trc_long_timeline",
|
||||
eventSource: "hwlab-kafka-sse",
|
||||
status: "running",
|
||||
eventCount: events.length,
|
||||
events
|
||||
}
|
||||
}
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
const disclosure = wrapper.get(".trace-disclosure");
|
||||
expect(disclosure.attributes("data-event-count")).toBe("1000");
|
||||
expect(disclosure.attributes("data-render-input-count")).toBe("240");
|
||||
expect(disclosure.attributes("data-row-windowed")).toBe("true");
|
||||
const rows = wrapper.findAll('[data-testid="trace-render-row"]');
|
||||
expect(rows).toHaveLength(120);
|
||||
expect(Number(rows[0]?.attributes("data-event-seq"))).toBeGreaterThanOrEqual(881);
|
||||
expect(rows.at(-1)?.attributes("data-event-seq")).toBe("1000");
|
||||
expect(wrapper.text()).toContain("共 1000 events");
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
function liveTrace(status: "running" | "completed") {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-010401080313 Workbench实时权威 draft-2026-07-14-p0-pure-kafka-authority. Render a bounded, stable Trace tail from the Kafka SSE projection. -->
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import type { RunnerTrace } from "@/types";
|
||||
@@ -18,13 +19,17 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const TRACE_RENDER_ROW_WINDOW = 120;
|
||||
const TRACE_RENDER_EVENT_WINDOW = TRACE_RENDER_ROW_WINDOW * 2;
|
||||
const listRef = ref<HTMLOListElement | null>(null);
|
||||
const expanded = ref(false);
|
||||
const { following, keepBottomAfterUpdate, onScroll, scrollToBottom } = useWorkbenchBottomFollowScroll(listRef, { threshold: 24 });
|
||||
|
||||
const events = computed(() => Array.isArray(props.trace?.events) ? props.trace.events : []);
|
||||
const eventCount = computed(() => props.trace?.eventCount ?? events.value.length);
|
||||
const rawReadableRows = computed(() => expanded.value ? traceDisplayRows(traceRecord(props.trace), events.value as Record<string, unknown>[], {
|
||||
const renderEvents = computed(() => events.value.length > TRACE_RENDER_EVENT_WINDOW
|
||||
? events.value.slice(-TRACE_RENDER_EVENT_WINDOW)
|
||||
: events.value);
|
||||
const rawReadableRows = computed(() => expanded.value ? traceDisplayRows(traceRecord(props.trace), renderEvents.value as Record<string, unknown>[], {
|
||||
formatClock: formatDisplayClock,
|
||||
finalResponsePlacement: props.finalResponsePlacement,
|
||||
outerFinalResponseText: props.outerFinalResponseText
|
||||
@@ -32,6 +37,7 @@ const rawReadableRows = computed(() => expanded.value ? traceDisplayRows(traceRe
|
||||
const readableRows = computed(() => rawReadableRows.value);
|
||||
const hiddenRowCount = computed(() => Math.max(0, readableRows.value.length - TRACE_RENDER_ROW_WINDOW));
|
||||
const renderedRows = computed(() => hiddenRowCount.value > 0 ? readableRows.value.slice(-TRACE_RENDER_ROW_WINDOW) : readableRows.value);
|
||||
const inputEventWindowed = computed(() => renderEvents.value.length < events.value.length);
|
||||
const traceSummaryLabel = computed(() => {
|
||||
const count = eventCount.value;
|
||||
if (count <= 0) return String(props.trace?.status ?? "").trim() || "pending";
|
||||
@@ -159,14 +165,14 @@ function firstNonEmptyString(...values: unknown[]): string | null {
|
||||
|
||||
<template>
|
||||
<section v-if="trace" class="trace-timeline" :data-trace-id="trace.traceId || undefined" :data-status="trace.status" :data-projection-health="trace.projection?.projectionHealth || trace.projectionHealth || undefined" :data-following="following ? 'true' : 'false'">
|
||||
<details class="trace-disclosure" :open="expanded" :data-event-count="eventCount" :data-readable-row-count="readableRows.length" :data-rendered-row-count="renderedRows.length" :data-row-windowed="hiddenRowCount > 0 ? 'true' : 'false'" @toggle="onDisclosureToggle">
|
||||
<details class="trace-disclosure" :open="expanded" :data-event-count="eventCount" :data-render-input-count="renderEvents.length" :data-readable-row-count="readableRows.length" :data-rendered-row-count="renderedRows.length" :data-row-windowed="hiddenRowCount > 0 || inputEventWindowed ? 'true' : 'false'" @toggle="onDisclosureToggle">
|
||||
<summary class="trace-disclosure-summary" aria-label="折叠运行记录">
|
||||
<span class="trace-disclosure-caret" aria-hidden="true" />
|
||||
<span class="trace-disclosure-label">运行记录</span>
|
||||
<span class="trace-disclosure-count">{{ traceSummaryLabel }}</span>
|
||||
</summary>
|
||||
<div v-if="expanded" class="trace-disclosure-body">
|
||||
<p v-if="hiddenRowCount > 0" class="trace-window-notice">显示最近 {{ renderedRows.length }} / {{ readableRows.length }} 行</p>
|
||||
<p v-if="hiddenRowCount > 0 || inputEventWindowed" class="trace-window-notice">显示最近 {{ renderedRows.length }} 行,共 {{ eventCount }} events</p>
|
||||
<ol v-if="renderedRows.length > 0" ref="listRef" @scroll="onScroll">
|
||||
<li v-for="(row, index) in renderedRows" :key="rowKey(row, index)" class="trace-render-row" data-testid="trace-render-row" :data-trace-id="trace.traceId || undefined" :data-event-seq="row.seq ?? undefined" :data-event-seq-authority="row.sequenceAuthority" :data-projected-seq="row.sequenceAuthority === 'projected' ? row.seq ?? undefined : undefined" :data-event-kind="row.rowId" :data-event-status="row.tone" :data-tool-state="row.toolState ?? undefined" :data-terminal="row.terminal ? 'true' : 'false'" :data-row-kind="rowIsTool(row) ? 'tool' : 'message'" :data-row-id="row.rowId" :aria-posinset="hiddenRowCount + index + 1">
|
||||
<details v-if="rowIsTool(row)" class="trace-tool-details" :aria-busy="row.toolState === 'running' ? 'true' : undefined">
|
||||
|
||||
Reference in New Issue
Block a user