Components

Thinking steps

An assistant’s reasoning and tool calls as a trace that opens while it runs and says how long it took.

variant
connector
scheme
direction

Done #

  1. Planned the answer done 0.9s
  2. Searched the web done 2.3s
    Found four sources on tide tables.
  3. Read the sources done 1.1s
export const ThinkingStepsStyle = forSlots(ThinkingStepsSlots)(Recipes.ThinkingSteps({}))

export const THOUGHT: ReadonlyArray<ThinkingStep> = [
  { id: 'plan', title: 'Planned the answer', state: 'done', duration: 900 },
  {
    id: 'search',
    title: 'Searched the web',
    state: 'done',
    duration: 2300,
    detail: 'Found four sources on tide tables.',
  },
  { id: 'read', title: 'Read the sources', state: 'done', duration: 1100 },
]

// Shut once done, saying how long it took; a press opens it. `open` is the
// Model's: none follows the work, and the press sends what the reader asked.
export const thinkingSteps = <Message>(
  style: NamedStyle<typeof ThinkingStepsSlots>,
  open: Option.Option<boolean>,
  onToggle: (open: boolean) => Message,
  h: HtmlBuilder<Message>,
): Html =>
  h.div(
    [h.Style({ inlineSize: 'min(28rem, 100%)' })],
    [ThinkingSteps.view({ id: 'preview-thought', steps: THOUGHT, open, onToggle, style }, h)],
  )

Running #

  1. Searched the web done 2.3s
  2. Fetching tides.example failed 0.4s
  3. Reading 3 sources running
  4. Write the answer pending
export const THINKING: ReadonlyArray<ThinkingStep> = [
  { id: 'search', title: 'Searched the web', state: 'done', duration: 2300 },
  { id: 'fetch', title: 'Fetching tides.example', state: 'failed', duration: 400 },
  { id: 'read', title: 'Reading 3 sources', state: 'running' },
  { id: 'write', title: 'Write the answer', state: 'pending' },
]

// Open while it runs, its summary the running step's title, shimmering.
export const thinkingStepsRunning = <Message>(
  style: NamedStyle<typeof ThinkingStepsSlots>,
  open: Option.Option<boolean>,
  onToggle: (open: boolean) => Message,
  h: HtmlBuilder<Message>,
): Html =>
  h.div(
    [h.Style({ inlineSize: 'min(28rem, 100%)' })],
    [ThinkingSteps.view({ id: 'preview-thinking', steps: THINKING, open, onToggle, style }, h)],
  )

Tool calls #

  1. → { "city": "Lisbon" }
    ← { "high": 24, "sky": "clear" }
  2. → { "day": "Saturday" }
    ← 2 events
    1. Work calendar done 0.5s
    2. Home calendar done 0.7s
export const ToolStepsStyle = forSlots(ThinkingStepsSlots)(
  Recipes.ThinkingSteps({ variant: 'card' }),
)

const toolCall = <Message>(h: HtmlBuilder<Message>, input: string, output: string): Html =>
  h.pre(
    [h.Style({ margin: '0', whiteSpace: 'pre-wrap', fontSize: '0.8125rem' })],
    [`→ ${input}`, h.br([]), `← ${output}`],
  )

/** Built per builder: a tool call's detail is markup. */
export const toolSteps = <Message>(h: HtmlBuilder<Message>): ReadonlyArray<ThinkingStep> => [
  {
    id: 'weather',
    title: 'get_weather',
    state: 'done',
    duration: 640,
    detail: toolCall(h, '{ "city": "Lisbon" }', '{ "high": 24, "sky": "clear" }'),
  },
  {
    id: 'calendar',
    title: 'list_events',
    state: 'done',
    duration: 1200,
    detail: toolCall(h, '{ "day": "Saturday" }', '2 events'),
    steps: [
      { id: 'calendar-work', title: 'Work calendar', state: 'done', duration: 500 },
      { id: 'calendar-home', title: 'Home calendar', state: 'done', duration: 700 },
    ],
  },
]

// Each tool call's input and output under its own row, which a press shows
// and hides (`Accordion.toggled(openSteps, id, { multiple: true })`).
export const thinkingStepsTools = <Message>(
  style: NamedStyle<typeof ThinkingStepsSlots>,
  open: Option.Option<boolean>,
  onToggle: (open: boolean) => Message,
  openSteps: ReadonlyArray<string>,
  onToggleStep: (id: string) => Message,
  h: HtmlBuilder<Message>,
): Html =>
  h.div(
    [h.Style({ inlineSize: 'min(28rem, 100%)' })],
    [
      ThinkingSteps.view(
        {
          id: 'preview-tools',
          steps: toolSteps(h),
          open,
          onToggle,
          summary: 'Used 2 tools',
          openSteps,
          onToggleStep,
          style,
        },
        h,
      ),
    ],
  )

Usage

Each example above names the function that drew it, over the theme module Create a theme writes. The preview passes it the style picked above instead of its default.

The recipe ↗This code ↗