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) ·.spspacer · 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 pxIconButtonlike its neighbours (MenuButtonwith thetriggerprop), notMenuButton iconOnly(24 px)..aichat-bodywrap›.aichat-body.aichat-thread[role="log"][aria-live="off"]— scrolls;.aichat-downis the floating scroll-to-bottom.- Thread items:
.aichat-user(the question, right) ·.aichat-msg(the answer, flat) with.aichat-mdmarkdown,.aichat-tablefor tabular results ·.aichat-tools(collapsible trace:.aichat-tools-ttoggle,.aichat-tooldone,.aichat-runin flight) ·.aichat-acts(copy, 👍, 👎, retry) ·.aichat-typingwhile thinking. .aichat-composerpinned at the bottom: the mention input,.aichat-cfootwith.aichat-slashand.aichat-send.- Empty thread:
.aichat-emptywith 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-fupsstretches; text on ONE line,.aichat-fup-tellipsis 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 isButton 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 DSsparkles. - 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.
Related
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.