fix: move Workbench timing metadata below messages
This commit is contained in:
@@ -46,6 +46,41 @@ describe("WorkbenchMessageCard startup visibility", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkbenchMessageCard metadata layout", () => {
|
||||
it("renders recent activity at the footer left and elapsed time at the footer right", () => {
|
||||
const now = Date.now();
|
||||
const wrapper = mount(WorkbenchMessageCard, {
|
||||
props: {
|
||||
detailEnabled: false,
|
||||
message: {
|
||||
id: "msg_timing_layout",
|
||||
messageId: "msg_timing_layout",
|
||||
role: "agent",
|
||||
title: "Code Agent",
|
||||
text: "",
|
||||
status: "running",
|
||||
traceId: "trc_timing_layout",
|
||||
sessionId: "ses_timing_layout",
|
||||
createdAt: new Date(now - 10_000).toISOString(),
|
||||
timing: {
|
||||
startedAt: new Date(now - 10_000).toISOString(),
|
||||
lastEventAt: new Date(now - 4_000).toISOString()
|
||||
}
|
||||
}
|
||||
},
|
||||
global: { stubs: componentStubs() }
|
||||
});
|
||||
|
||||
const footer = wrapper.get(".message-meta-footer");
|
||||
expect(wrapper.find("article > header").exists()).toBe(false);
|
||||
expect(footer.get(".message-activity-meta").text()).toMatch(/^最近更新:\d+ 秒前$/u);
|
||||
expect(footer.get(".message-duration-meta").text()).toMatch(/^耗时 \d+ 秒$/u);
|
||||
expect(footer.get(".message-footer-timing").element.firstElementChild).toBe(footer.get(".message-activity-meta").element);
|
||||
expect(footer.get(".message-footer-timing").element.lastElementChild).toBe(footer.get(".message-duration-meta").element);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkbenchMessageCard semantic retry visibility", () => {
|
||||
it("shows Git mirror clone progress as runtime preparation", () => {
|
||||
const wrapper = mount(WorkbenchMessageCard, {
|
||||
|
||||
@@ -78,7 +78,7 @@ function messageActivityMeta(message: ChatMessage): { text: string; label: strin
|
||||
const ageMs = durationSince(timing?.lastEventAt);
|
||||
if (ageMs === null) return null;
|
||||
const value = formatDuration(ageMs) + "前";
|
||||
return { text: `最近 ${value}`, label: `最近事件:${value}` };
|
||||
return { text: `最近更新:${value}`, label: `最近更新:${value}` };
|
||||
}
|
||||
|
||||
function messageTimingForDisplay(message: ChatMessage): ChatMessage["timing"] {
|
||||
@@ -136,18 +136,6 @@ function traceStorageKey(message: ChatMessage): string {
|
||||
:data-trace-id="message.traceId || message.runnerTrace?.traceId || undefined"
|
||||
:data-turn-id="message.turnId || message.traceId || message.runnerTrace?.traceId || undefined"
|
||||
>
|
||||
<header v-if="message.role !== 'user'">
|
||||
<div class="message-header-main">
|
||||
<strong>{{ message.title }}</strong>
|
||||
<span v-if="durationMeta" class="message-timing-meta message-duration-meta" :aria-label="durationMeta.label" :title="durationMeta.label">{{ durationMeta.text }}</span>
|
||||
</div>
|
||||
<div class="message-header-actions">
|
||||
<span v-if="activityMeta" class="message-activity-meta" :aria-label="activityMeta.label" :title="activityMeta.label">{{ activityMeta.text }}</span>
|
||||
<span v-if="running" class="agent-running-indicator" aria-label="运行中" role="status"><span aria-hidden="true" /></span>
|
||||
<StatusBadge v-if="showStatusBadge(message)" :status="message.status" />
|
||||
<button v-if="detailEnabled && message.role === 'agent'" class="message-detail-button" type="button" aria-label="运行详情" title="运行详情" @click="emit('details', message)">!</button>
|
||||
</div>
|
||||
</header>
|
||||
<section v-if="semanticRuntimeStatus" class="semantic-failure-banner" :data-tone="semanticRuntimeStatus.tone" :data-emphasis="semanticRuntimeStatus.emphasis" role="status" aria-live="polite" :title="semanticRuntimeStatus.code ? `${semanticRuntimeStatus.title} · ${semanticRuntimeStatus.detail} · ${semanticRuntimeStatus.code}` : undefined">
|
||||
<span class="semantic-status-marker" aria-hidden="true" />
|
||||
<strong>{{ semanticRuntimeStatus.title }}</strong>
|
||||
@@ -158,6 +146,20 @@ function traceStorageKey(message: ChatMessage): string {
|
||||
<LoadingState v-if="isAwaitingAgentBody(message)" class="message-loading" label="启动中..." compact />
|
||||
<MessageMarkdown v-if="visibleText" class="message-text" :source="visibleText" />
|
||||
<ApiErrorDiagnostic v-if="diagnostic.visible" class="message-diagnostic projection-diagnostic" :error="diagnostic.text" :api-error="diagnostic.apiError" :diagnostic="diagnostic.diagnostic" :show-message="diagnostic.showMessage" compact />
|
||||
<footer v-if="message.role !== 'user'" class="message-meta-footer">
|
||||
<div class="message-header-main">
|
||||
<strong>{{ message.title }}</strong>
|
||||
</div>
|
||||
<div class="message-header-actions">
|
||||
<span v-if="running" class="agent-running-indicator" aria-label="运行中" role="status"><span aria-hidden="true" /></span>
|
||||
<StatusBadge v-if="showStatusBadge(message)" :status="message.status" />
|
||||
<button v-if="detailEnabled && message.role === 'agent'" class="message-detail-button" type="button" aria-label="运行详情" title="运行详情" @click="emit('details', message)">!</button>
|
||||
</div>
|
||||
<div class="message-footer-timing">
|
||||
<span v-if="activityMeta" class="message-activity-meta" :aria-label="activityMeta.label" :title="activityMeta.label">{{ activityMeta.text }}</span>
|
||||
<span v-if="durationMeta" class="message-timing-meta message-duration-meta" :aria-label="durationMeta.label" :title="durationMeta.label">{{ durationMeta.text }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -493,7 +493,7 @@
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-card header,
|
||||
.message-meta-footer,
|
||||
.trace-timeline header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
@@ -502,6 +502,14 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.message-meta-footer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
margin-top: 6px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.message-header-main {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
@@ -554,6 +562,24 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.message-footer-timing {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.message-footer-timing .message-activity-meta {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-footer-timing .message-duration-meta {
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message-detail-button {
|
||||
display: inline-flex;
|
||||
width: 22px;
|
||||
|
||||
@@ -8,10 +8,10 @@ test("Code Agent message headers render canonical timing metadata", async ({ pag
|
||||
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
||||
|
||||
const running = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).first();
|
||||
await expect(running.locator(`.message-header-main ${selectors.messageDurationMeta}`)).toContainText("耗时 4 秒");
|
||||
await expect(running.locator(`.message-header-actions ${selectors.messageActivityMeta}`)).toContainText("最近 0 秒前");
|
||||
await expect(running.locator(`.message-meta-footer ${selectors.messageDurationMeta}`)).toContainText("耗时 4 秒");
|
||||
await expect(running.locator(`.message-meta-footer ${selectors.messageActivityMeta}`)).toContainText("最近更新:0 秒前");
|
||||
await expect(running.locator(selectors.messageDurationMeta)).toHaveAttribute("aria-label", "总耗时:4 秒");
|
||||
await expect(running.locator(selectors.messageActivityMeta)).toHaveAttribute("aria-label", "最近事件:0 秒前");
|
||||
await expect(running.locator(selectors.messageActivityMeta)).toHaveAttribute("aria-label", "最近更新:0 秒前");
|
||||
await expect(running.locator(selectors.messageDurationMeta)).not.toContainText("最近");
|
||||
|
||||
await page.clock.fastForward(5000);
|
||||
|
||||
Reference in New Issue
Block a user