mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
feat(web): drop elapsed timer and duplicate estimate from generation preview
The "usually 2–5 minutes" estimate showed twice (lead footnote + meta row) and the elapsed counter added little signal, so remove both: delete the meta row, stop falling back to the estimate footnote in the generating lead (render the lead only when live narration exists), and drop the now unused elapsed timer/util. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4ca4df0cab
commit
876c0de2e6
4 changed files with 3 additions and 53 deletions
|
|
@ -257,20 +257,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-faint);
|
|
||||||
}
|
|
||||||
|
|
||||||
.metaDivider {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry {
|
.retry {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useT } from '../i18n';
|
import { useT } from '../i18n';
|
||||||
import type { GenerationPreviewModel } from '../runtime/generation-preview';
|
import type { GenerationPreviewModel } from '../runtime/generation-preview';
|
||||||
import { formatGenerationElapsed } from '../runtime/generation-preview';
|
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import styles from './GenerationPreviewStage.module.css';
|
import styles from './GenerationPreviewStage.module.css';
|
||||||
|
|
||||||
|
|
@ -12,19 +10,9 @@ type Props = {
|
||||||
|
|
||||||
export function GenerationPreviewStage({ model, onRetry }: Props) {
|
export function GenerationPreviewStage({ model, onRetry }: Props) {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [now, setNow] = useState(() => Date.now());
|
|
||||||
|
|
||||||
const generating = model.phase === 'generating';
|
const generating = model.phase === 'generating';
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!generating) return undefined;
|
|
||||||
const id = window.setInterval(() => setNow(Date.now()), 1000);
|
|
||||||
return () => window.clearInterval(id);
|
|
||||||
}, [generating, model.startedAt]);
|
|
||||||
|
|
||||||
const elapsedSec = Math.max(0, Math.round((now - model.startedAt) / 1000));
|
|
||||||
const elapsedLabel = formatGenerationElapsed(elapsedSec);
|
|
||||||
|
|
||||||
const stepLabels: Record<GenerationPreviewModel['steps'][number]['id'], string> = {
|
const stepLabels: Record<GenerationPreviewModel['steps'][number]['id'], string> = {
|
||||||
understand: t('generationPreview.stepUnderstand'),
|
understand: t('generationPreview.stepUnderstand'),
|
||||||
generate: t('generationPreview.stepGenerate'),
|
generate: t('generationPreview.stepGenerate'),
|
||||||
|
|
@ -47,7 +35,7 @@ export function GenerationPreviewStage({ model, onRetry }: Props) {
|
||||||
? t('generationPreview.stoppedLead')
|
? t('generationPreview.stoppedLead')
|
||||||
: model.phase === 'awaiting-input'
|
: model.phase === 'awaiting-input'
|
||||||
? t('generationPreview.awaitingLead')
|
? t('generationPreview.awaitingLead')
|
||||||
: model.activityLabel || t('generationPreview.footnote');
|
: model.activityLabel;
|
||||||
|
|
||||||
const markIcon =
|
const markIcon =
|
||||||
model.phase === 'failed' ? 'close' : model.phase === 'stopped' ? 'stop' : 'sparkles';
|
model.phase === 'failed' ? 'close' : model.phase === 'stopped' ? 'stop' : 'sparkles';
|
||||||
|
|
@ -69,11 +57,11 @@ export function GenerationPreviewStage({ model, onRetry }: Props) {
|
||||||
<Icon name={markIcon} size={24} />
|
<Icon name={markIcon} size={24} />
|
||||||
</div>
|
</div>
|
||||||
<h1 className={styles.title}>{title}</h1>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
{showSubstatus ? null : (
|
{!showSubstatus && lead ? (
|
||||||
<p className={styles.lead} data-live={generating && Boolean(model.activityLabel)}>
|
<p className={styles.lead} data-live={generating && Boolean(model.activityLabel)}>
|
||||||
{lead}
|
{lead}
|
||||||
</p>
|
</p>
|
||||||
)}
|
) : null}
|
||||||
<div
|
<div
|
||||||
className={styles.progress}
|
className={styles.progress}
|
||||||
data-active={generating}
|
data-active={generating}
|
||||||
|
|
@ -118,17 +106,6 @@ export function GenerationPreviewStage({ model, onRetry }: Props) {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{generating ? (
|
|
||||||
<div className={styles.meta}>
|
|
||||||
<span data-testid="generation-preview-elapsed">
|
|
||||||
{t('generationPreview.elapsed', { elapsed: elapsedLabel })}
|
|
||||||
</span>
|
|
||||||
<span className={styles.metaDivider} aria-hidden>
|
|
||||||
·
|
|
||||||
</span>
|
|
||||||
<span>{t('generationPreview.estimate')}</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{model.phase === 'failed' && onRetry ? (
|
{model.phase === 'failed' && onRetry ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -249,14 +249,6 @@ export function generationPreviewProgress(steps: GenerationPreviewStep[]): numbe
|
||||||
return Math.max(8, Math.min(steps.some((step) => step.status === 'failed') ? 72 : 92, Math.round(score * 100)));
|
return Math.max(8, Math.min(steps.some((step) => step.status === 'failed') ? 72 : 92, Math.round(score * 100)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatGenerationElapsed(seconds: number): string {
|
|
||||||
const safe = Math.max(0, Math.floor(seconds));
|
|
||||||
if (safe < 60) return `${safe}s`;
|
|
||||||
const minutes = Math.floor(safe / 60);
|
|
||||||
const remainder = safe % 60;
|
|
||||||
return remainder > 0 ? `${minutes}m ${remainder}s` : `${minutes}m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActiveRunStatus(status: ChatMessage['runStatus']): boolean {
|
function isActiveRunStatus(status: ChatMessage['runStatus']): boolean {
|
||||||
return status === 'queued' || status === 'running';
|
return status === 'queued' || status === 'running';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
buildGenerationPreviewState,
|
buildGenerationPreviewState,
|
||||||
derivePrototypeGenerationSteps,
|
derivePrototypeGenerationSteps,
|
||||||
formatGenerationElapsed,
|
|
||||||
workspaceHasPreviewSurface,
|
workspaceHasPreviewSurface,
|
||||||
} from '../../src/runtime/generation-preview';
|
} from '../../src/runtime/generation-preview';
|
||||||
import type { AgentEvent, ChatMessage } from '../../src/types';
|
import type { AgentEvent, ChatMessage } from '../../src/types';
|
||||||
|
|
@ -309,8 +308,4 @@ describe('generation preview helpers', () => {
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats elapsed durations for the meta row', () => {
|
|
||||||
expect(formatGenerationElapsed(42)).toBe('42s');
|
|
||||||
expect(formatGenerationElapsed(125)).toBe('2m 5s');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue