fix(conversation): keep Turn history rows visible in the Latency column
The rows were added to the DOM but the overflow:auto list was shrunk to zero height by the flex column panel once the charts filled it. Adds tests for #n + time rows, error rows and stats arriving after done. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a3ba3581fe
commit
caee289bcc
@ -4407,7 +4407,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.conv-pipe-total .conv-pipe-val { color: var(--accent); font-size: 15px; }
|
||||
|
||||
/* Turn history */
|
||||
.conv-turn-history { display: flex; flex-direction: column; gap: 5px; overflow-y: auto; max-height: 220px; }
|
||||
.conv-turn-history { display: flex; flex-direction: column; gap: 5px; overflow-y: auto; max-height: 220px; flex-shrink: 0; /* else the flex column panel squeezes it to 0 and hides the rows */ }
|
||||
.conv-history-empty { font-size: 12px; color: var(--subtext); font-style: italic; }
|
||||
.conv-hist-item { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 4px 6px; border-radius: 5px; background: var(--panel); }
|
||||
.conv-hist-num { font-weight: 700; color: var(--subtext); min-width: 20px; }
|
||||
|
||||
@ -4,16 +4,17 @@ const { test, expect } = require('./_guard');
|
||||
|
||||
const sse = (/** @type {any[]} */ evts) => evts.map(e => `data: ${JSON.stringify(e)}\n\n`).join('');
|
||||
|
||||
async function boot(page, { usage = true, lang = '' } = {}) {
|
||||
async function boot(page, { usage = true, lang = '', errorTurns = [], statsAfterDone = false } = {}) {
|
||||
let n = 0;
|
||||
await page.route('**/api/**', route => route.request().method() === 'GET' ? route.fulfill({ json: {} }) : route.fulfill({ json: { ok: true } }));
|
||||
await page.route('**/api/conversation/turn', route => {
|
||||
n++;
|
||||
if (errorTurns.includes(n)) return route.fulfill({ contentType: 'text/event-stream', body: sse([{ type: 'error', stage: 'llm', message: 'boom' }]) });
|
||||
const stats = { type: 'stats', stt_ms: 100 + n, llm_ttft_ms: 200 + n * 10, llm_total_ms: 900, tts_ms: 300 + n, total_ms: 1500,
|
||||
usage: usage ? { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 } : null };
|
||||
return route.fulfill({ contentType: 'text/event-stream', body: sse([
|
||||
{ type: 'transcript', text: 'hi', stt_ms: null }, { type: 'token', delta: 'Hello.' },
|
||||
{ type: 'llm_done', text: 'Hello.', llm_ttft_ms: 1, llm_total_ms: 2 }, stats, { type: 'done' }]) });
|
||||
{ type: 'llm_done', text: 'Hello.', llm_ttft_ms: 1, llm_total_ms: 2 }, ...(statsAfterDone ? [{ type: 'done' }, stats] : [stats, { type: 'done' }])]) });
|
||||
});
|
||||
await page.route('**/static/dist/main.min.js*', r => r.abort());
|
||||
if (lang) await page.addInitScript(l => { try { localStorage.setItem('app-lang', l); } catch (e) {} }, lang);
|
||||
@ -27,6 +28,7 @@ async function turn(page, i) {
|
||||
await page.locator('#conv-text-input').fill('hello ' + i);
|
||||
await page.locator('#conv-text-input').press('Enter');
|
||||
await expect(page.locator('#conv-turn-history .conv-hist-item')).toHaveCount(i);
|
||||
await expect(page.locator('#conv-turn-history .conv-hist-item').first().locator('.conv-hist-num')).toHaveText('#' + i);
|
||||
await expect(page.locator('#conv-send-btn')).toBeEnabled();
|
||||
}
|
||||
|
||||
@ -75,3 +77,41 @@ test('empty state and German labels', async ({ page }) => {
|
||||
await expect(page.locator('#cm-tok-total')).toHaveText('k. A.');
|
||||
await expect(page.locator('#conv-stats-panel .conv-stats-head', { hasText: 'Token-Verbrauch' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('turn history rows are visible with #n and a formatted time (normal and error turns)', async ({ page }) => {
|
||||
await boot(page, { errorTurns: [3] });
|
||||
for (let i = 1; i <= 3; i++) await turn(page, i);
|
||||
const rows = page.locator('#conv-turn-history .conv-hist-item');
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const row = rows.nth(i); // newest first
|
||||
await expect(row).toBeVisible(); // not squeezed to 0 height by the flex panel
|
||||
await expect(row.locator('.conv-hist-num')).toHaveText('#' + (3 - i));
|
||||
await expect(row.locator('.conv-hist-time')).toHaveText(/^\d+(\.\d+)?(ms|s)$/);
|
||||
}
|
||||
await expect(rows.first().locator('.conv-hist-err')).toHaveCount(2); // error row: icon + time
|
||||
await expect(rows.nth(1).locator('.conv-hist-ok')).toHaveCount(2);
|
||||
await expect(rows.nth(1).locator('.conv-hist-time')).toHaveText('1.50s');
|
||||
});
|
||||
|
||||
test('stats arriving after done still yield a history row with a time', async ({ page }) => {
|
||||
await boot(page, { statsAfterDone: true });
|
||||
await turn(page, 1);
|
||||
await expect(page.locator('#conv-turn-history .conv-hist-time').first()).toHaveText(/^\d+(\.\d+)?(ms|s)$/);
|
||||
});
|
||||
|
||||
test('chart text is legible in the narrow column, single point is drawn', async ({ page }) => {
|
||||
await boot(page);
|
||||
await turn(page, 1);
|
||||
const chart = page.locator('.conv-chart[data-metric="llm"]');
|
||||
await expect(chart.locator('circle')).toHaveCount(1);
|
||||
const dot = await chart.locator('circle').boundingBox();
|
||||
const box = await chart.locator('svg').boundingBox();
|
||||
expect(dot && box && dot.width >= 6 && dot.x > box.x + 40 && dot.x < box.x + box.width).toBeTruthy();
|
||||
await expect(chart.locator('svg')).toContainText('0');
|
||||
const sizes = await chart.locator('svg text').evaluateAll(els => els.map(e => {
|
||||
const r = e.getBoundingClientRect(); return { fs: parseFloat(getComputedStyle(e).fontSize), scale: r.height / (e.getBBox().height || 1) };
|
||||
}));
|
||||
const vb = await chart.locator('svg').evaluate(e => ({ vw: e.viewBox.baseVal.width, cw: e.getBoundingClientRect().width }));
|
||||
for (const s of sizes) expect(s.fs * (vb.cw / vb.vw)).toBeGreaterThanOrEqual(10);
|
||||
expect(Math.abs(vb.vw - vb.cw)).toBeLessThan(2);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user