mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
feat(web): reveal generation steps progressively as the agent reaches them
- Only render steps the agent has actually reached (drop pending pills) with a slide/fade entrance, so the card visibly evolves 1->2->3 instead of always showing the same fully-populated row. - Keep the "understand" step in progress during requesting/starting so a fresh run opens with a single step rather than a pre-filled set. - Stop surfacing status detail (e.g. the model slug from `requesting`) as the live activity line; only genuine thinking/output text is shown. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0eaf1c37d9
commit
716000e200
4 changed files with 46 additions and 6 deletions
|
|
@ -153,6 +153,20 @@
|
|||
background: var(--bg-panel);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
/* Steps are revealed one at a time as the agent reaches them, so each
|
||||
pill slides + fades in on mount to make the progression visible. */
|
||||
animation: stepReveal 280ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
@keyframes stepReveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px) scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.step[data-status='running'] {
|
||||
|
|
@ -207,7 +221,8 @@
|
|||
.mark[data-active='true'],
|
||||
.progress[data-active='true']::after,
|
||||
.stepDot[data-running='true'],
|
||||
.lead[data-live='true'] {
|
||||
.lead[data-live='true'],
|
||||
.step {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,9 @@ export function GenerationPreviewStage({ model, onRetry }: Props) {
|
|||
<span style={{ width: `${model.progressPercent}%` }} />
|
||||
</div>
|
||||
<ol className={styles.steps}>
|
||||
{model.steps.map((step) => (
|
||||
{model.steps
|
||||
.filter((step) => step.status !== 'pending')
|
||||
.map((step) => (
|
||||
<li key={step.id} className={styles.step} data-status={step.status}>
|
||||
<span className={styles.stepIcon} aria-hidden>
|
||||
{step.status === 'succeeded' ? (
|
||||
|
|
|
|||
|
|
@ -172,7 +172,12 @@ export function derivePrototypeGenerationSteps(input: {
|
|||
let understand: GenerationStepStatus = 'running';
|
||||
if (input.failed && !hasText && !hasToolUse) {
|
||||
understand = 'failed';
|
||||
} else if (hasText || hasStatus(['thinking', 'streaming', 'requesting', 'starting']) || hasToolUse) {
|
||||
} else if (hasText || hasStatus(['thinking', 'streaming']) || hasToolUse) {
|
||||
// `requesting`/`starting` only mean the request left the client — the
|
||||
// model hasn't produced anything yet, so we keep "understand" in
|
||||
// progress until real thinking/output/tool activity arrives. This lets
|
||||
// the UI reveal the steps one at a time instead of jumping straight to
|
||||
// a fully populated row.
|
||||
understand = 'succeeded';
|
||||
}
|
||||
|
||||
|
|
@ -241,9 +246,9 @@ function latestActivityLabel(events: AgentEvent[]): string | null {
|
|||
if (event.kind === 'text' && event.text.trim() && !QUESTION_FORM_RE.test(event.text)) {
|
||||
return truncateActivity(event.text);
|
||||
}
|
||||
if (event.kind === 'status' && event.detail?.trim()) {
|
||||
return truncateActivity(event.detail);
|
||||
}
|
||||
// Intentionally skip `status` details: their payload is often an
|
||||
// internal identifier (e.g. the model slug from a `requesting` event)
|
||||
// rather than human-readable progress, so surfacing it reads as noise.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,24 @@ describe('generation preview helpers', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('keeps the understand step in progress while the request is still pending', () => {
|
||||
// `requesting` only means the request left the client; nothing should
|
||||
// advance past the first step until real model activity arrives, so the
|
||||
// UI can reveal steps one at a time.
|
||||
expect(
|
||||
derivePrototypeGenerationSteps({
|
||||
events: [{ kind: 'status', label: 'requesting', detail: 'claude-opus-4-7' }],
|
||||
hasArtifactHtml: false,
|
||||
hasPreviewSurface: false,
|
||||
failed: false,
|
||||
}),
|
||||
).toEqual([
|
||||
{ id: 'understand', status: 'running' },
|
||||
{ id: 'generate', status: 'pending' },
|
||||
{ id: 'prepare', status: 'pending' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('builds preview state for an active assistant run without an open preview tab', () => {
|
||||
const assistant: ChatMessage = {
|
||||
id: 'a1',
|
||||
|
|
|
|||
Loading…
Reference in a new issue