/** * Card above the composer while a Session's worktree is created, while a message * waits after a failed or stopped attempt, and when its worktree is gone or * being restored. */ import { useState, type ReactNode } from 'react' import { Button, IconBranchOutlineRegular, IconCheckOutlineRegular, IconChevronRightOutlineRegular, IconCloseOutlineRegular, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives' import type { HostObservable, InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' import { assertNever } from '@deepseek-ai/dsh-util-values' import type { ForgeMissingWorktree, ForgePanelWorktree, ForgeSessionWorktree, ForgeWorktreeRestoration } from '../types.ts' import type { ForgePanelKey, NS } from './locales.ts' import type { PanelSnapshot } from './source.ts' import css from './WorktreeProgress.module.css' /** Business face of the card. */ export interface WorktreeProgressInjected { /** Panel snapshot of the Session; the renderer binds it as `usePanel`. */ readonly hooks: { readonly panel: HostObservable } /** Stop the running attempt a message waits for; the message keeps waiting. */ readonly stop: () => void /** Make another attempt at the worktree a message waits for. */ readonly retry: () => void /** Withdraw the worktree request, so the waiting message goes on without a worktree. */ readonly withdraw: () => void /** * Check out the removed worktree again. * @returns the failure text, or undefined once it is restored. */ readonly restore: () => Promise /** Move the Session back to the source checkout of its removed worktree. */ readonly leave: () => void } /** Full props of the card. */ export type WorktreeProgressProps = InjectFace & PropsLocale type Translate = WorktreeProgressProps['t'] type StepState = 'done' | 'running' | 'waiting' | 'failed' type Phase = 'prepare' | 'checkout' type StepList = readonly { readonly phase: Phase; readonly label: ForgePanelKey }[] const CREATION_STEPS: StepList = [ { phase: 'prepare', label: 'creation.prepare' }, { phase: 'checkout', label: 'creation.checkout' }, ] const RESTORATION_STEPS: StepList = [ { phase: 'prepare', label: 'restoration.prepare' }, { phase: 'checkout', label: 'creation.checkout' }, ] function stepState(current: 'prepare' | 'checkout', failed: boolean, phase: 'prepare' | 'checkout'): StepState { if (phase === current) return failed ? 'failed' : 'running' return phase === 'prepare' ? 'done' : 'waiting' } function Steps({ steps, phase, failed, t }: { readonly steps: StepList readonly phase: Phase readonly failed: boolean readonly t: Translate }): ReactNode { return (
    {steps.map(step => (
  • {t(step.label)}
  • ))}
) } function StepIcon({ state }: { readonly state: StepState }): ReactNode { switch (state) { case 'done': return case 'running': return case 'failed': return case 'waiting': return