AI assistant

The right-hand panel the assistant lives in: a thread of questions and answers, the tool trace behind each answer, and the composer. React in apps/web/src/components/assistant/ (AssistantPanel.tsx, AssistantChat.tsx, tool-trace.tsx).

The CSS lives in two sheets and both are required: components/assistant/assistant.css carries the shell (.aichat-panel, .aichat-head, .aichat-hbtn, .aichat-composer, .aichat-typing), ds/ai-chat.css carries the thread content (.aichat-user, .aichat-msg, .aichat-tools, .aichat-acts, .aichat-table, .aichat-empty).

The rule the thread is built on: the question is the only framed thing. The user's message is a quiet block on the right; the answer is flat markdown with no bubble, because a frame around an answer fights the content inside it — since 17 Oct 2026 (proto/bublina-hlas-ajda@1740bc21d): .aichat-user has --line-100, the chat-bubble shape (--radius-xl, sharp --radius-xs bottom-right corner, padding --sp-3 --sp-5, max 80 % / phone 85 %) with the time BELOW it (.aichat-row.user + .aichat-meta); .aichat-msg has no background, no border and no radius, and under it sits one row of icons (copy, thumb up, thumb down, regenerate) plus the time (.aichat-time), no name. Question and answer text are 15/24 like .cht-msg-t.

When to use

  • The assistant, on any screen. It squeezes the page, it does not cover it — the panel is a column, not an overlay.

When not to use

  • A confirmation of something the user just did — that is toast.
  • A conversation between people — see discussion, which is a proposal and not built.
  • A form. Anything that needs fields belongs in a dialog the assistant opens, not in the thread.

Anatomy

  • section.aichat-panel[aria-label] filling its host (width/height: 100%, column flex).
  • header.aichat-head — .aichat-mark (the sparkle, the assistant's only identity) · .aichat-title (the thread name, truncated) · .sp spacer · ghost icon buttons: new thread (the frequent action), open on the workspace (maximize), kebab (dots-vertical) and close. The kebab is the ONE menu for the rare actions (owner 19 Oct 2026, ACRM-548): Thread history › (drill-in submenu of threads) · What the assistant did · Model · <current> (radio submenu of the models the company allows; same setting and same save as Settings → My assistant) · Anonymous mode (switch) · separator · Settings (direct link, no in-panel settings view). The workspace head has the same kebab without Anonymous mode. The kebab trigger is a quiet default 30 px IconButton like its neighbours (MenuButton with the trigger prop), not MenuButton iconOnly (24 px).
  • .aichat-bodywrap › .aichat-body.aichat-thread[role="log"][aria-live="off"] — scrolls; .aichat-down is the floating scroll-to-bottom.
  • Thread items: .aichat-user (the question, right) · .aichat-msg (the answer, flat) with .aichat-md markdown, .aichat-table for tabular results · .aichat-tools (collapsible trace: .aichat-tools-t toggle, .aichat-tool done, .aichat-run in flight) · .aichat-acts (copy, 👍, 👎, retry) · .aichat-typing while thinking.
  • .aichat-composer pinned at the bottom: the mention input, .aichat-cfoot with .aichat-slash and .aichat-send.
  • Empty thread: .aichat-empty with a title, one sub-line and up to three starters. No mark (owner 19 Oct 2026, ACRM-552). Each starter (.aichat-fup) is a full-width row (.aichat-fups stretches; text on ONE line, .aichat-fup-t ellipsis only as a safety net).
  • Anonymous mode shows a quiet .acrm-pill „Anonymní“ + one sentence under the head; the mode switch itself is in the kebab. The locked page offers Continue anonymously as a secondary button next to the primary Unlock.
  • The composer's / button is Button compact + icon (24 × 24).

Measured at the app width: panel 398 × 518, head 49px, composer 90px, body scrolls between them.

Best practices

Do

  • Keep the tool trace collapsed once a run is done and open while it is running — the user wants progress live and a summary afterwards.
  • Let the answer be markdown. Tables, lists and bold come from the content, not from bespoke classes.
  • Put every AI thing under sparkles-2 (the AI icon convention), never the plain DS sparkles.
  • Squeeze the page when the panel opens.
  • On phones the overlay panel (.as-ai-overlay) is never wider than the screen (max-width: 100vw); it covers the page instead of squeezing it (AD 24 Oct 2026, a2a FA254).

Don't

  • Wrap the answer in a bubble.
  • Add a second identity mark, a status badge or a model name to the head.
  • Put actions other than copy / feedback / retry on an answer.
  • Load only one of the two sheets — the shell without the content sheet, or the reverse, gives a panel that is half-styled.

Content

  • The thread title is the thread's own name, truncated with an ellipsis; the full name is not repeated elsewhere.
  • The empty state is the one place the panel explains itself: what it can see, in one line.
  • Tool labels say what was done, with a result count where there is one.
  • drawer-slot — the surface the panel occupies.
  • entity-ref (variant mini) — how records are referenced inside answers.
  • toast, notifications — the other two ways the app speaks.
  • discussion — the unbuilt proposal for a human thread.
  • Stories: components--ai-assistant--default, --variants, --sizes, --states, --with-icon-and-kbd, --composition.

Known drift

thumb-up, thumb-down and player-stop are not in the DS icon set, and Icon falls back to info-circle for an unknown name without warning. Today both feedback buttons therefore draw the same glyph, and the stop button draws an info circle. sparkles-2 and robot have the same gap and are patched at runtime by apps/web/src/lib/ai-icons.ts; the three above are not. Adding them is an Art Director call — the canonical home is the DS icon set, and the app must not write to the DS directly.

Source: docs/ai-assistant/ai-assistant.usage.md · GitLab