# @gravity-ui/aikit β€” full documentation This file concatenates the project README and all docs/*.md files for LLM agents that prefer one-shot context over following links. Sections are delimited by lines like: --- file: --- --- file: README.md --- # AIKit · [![npm package](https://img.shields.io/npm/v/@gravity-ui/aikit?logo=npm)](https://www.npmjs.com/package/@gravity-ui/aikit) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/aikit/.github/workflows/ci.yml?branch=main&label=CI&logo=github)](https://github.com/gravity-ui/aikit/actions/workflows/ci.yml?query=branch:main) [![storybook](https://img.shields.io/badge/Storybook-deployed-ff4685?logo=storybook)](https://preview.gravity-ui.com/aikit/?path=/docs/pages-chatcontainer--docs) UI component library for AI chats built with Atomic Design principles. ![Cover image](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/aikit_cover.png) ![Example image](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/aikit_example.png) ## Resources ### ![Globe Logo Light](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/globe_light.svg#gh-light-mode-only) ![Globe Logo Dark](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/globe_dark.svg#gh-dark-mode-only) [Website](https://gravity-ui.com/libraries/aikit) ### ![Storybook Logo Light](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/storybook_light.svg#gh-light-mode-only) ![Storybook Logo Dark](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/storybook_dark.svg#gh-dark-mode-only) [Storybook](https://preview.gravity-ui.com/aikit/) ### ![Community Logo Light](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/telegram_light.svg#gh-light-mode-only) ![Community Logo Dark](https://raw.githubusercontent.com/gravity-ui/aikit/main/docs/assets/telegram_dark.svg#gh-dark-mode-only) [Community](https://t.me/gravity_ui) ## Description **@gravity-ui/aikit** is a flexible and extensible React component library for building AI chats of any complexity. The library provides a set of ready-made components that can be used as-is or customized to fit your needs. ### Key Features - 🎨 **Atomic Design** β€” clear component hierarchy from atoms to pages - πŸ”§ **SDK Agnostic** β€” independent of specific AI SDKs - 🎭 **Two-Level Approach** β€” ready-made components + hooks for customization - 🎨 **CSS Variables** β€” easy theming without component overrides - πŸ“¦ **TypeScript** β€” full type safety out of the box - πŸ”Œ **Extensible** β€” custom message type registration system ## Project Structure ``` src/ β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ atoms/ # Basic indivisible UI elements β”‚ β”œβ”€β”€ molecules/ # Simple groups of atoms β”‚ β”œβ”€β”€ organisms/ # Complex components with logic β”‚ β”œβ”€β”€ templates/ # Complete layouts β”‚ └── pages/ # Full integrations with data β”œβ”€β”€ hooks/ # General purpose hooks β”œβ”€β”€ types/ # TypeScript types β”œβ”€β”€ utils/ # Utilities └── themes/ # CSS themes and variables ``` ## Installation ```bash npm install @gravity-ui/aikit ``` ## Quick Start ```typescript import { ChatContainer } from '@gravity-ui/aikit'; import type { ChatType, TChatMessage } from '@gravity-ui/aikit'; function App() { const [messages, setMessages] = useState([]); const [chats, setChats] = useState([]); const [activeChat, setActiveChat] = useState(null); return ( { // Your sending logic console.log('Message:', data.content); }} onSelectChat={setActiveChat} onCreateChat={() => { // Create new chat }} onDeleteChat={(chat) => { // Delete chat }} /> ); } ``` ## Architecture The library is built on **Atomic Design** principles: ### πŸ”Ή Atoms Basic indivisible UI elements without business logic: - `ActionButton` β€” button with integrated tooltip - `Alert` β€” alert messages with variants - `ChatDate` β€” date formatting with relative dates - `ContextIndicator` β€” token context usage indicator - `ContextItem` β€” context label with remove action - `DiffStat` β€” code change statistics display - `Disclaimer` β€” disclaimer text component - `InlineCitation` β€” text citations - `Loader` β€” loading indicator - `MarkdownRenderer` β€” Yandex Flavored Markdown renderer - `MessageBalloon` β€” message wrapper - `Shimmer` β€” loading animation effect - `SubmitButton` β€” submit button with states - `ToolIndicator` β€” tool execution status indicator ### πŸ”Έ Molecules Simple combinations of atoms: - `BaseMessage` β€” base wrapper for all message types - `ButtonGroup` β€” button group with orientation support - `InputContext` β€” context management - `PromptInputBody` β€” textarea with auto-growing - `PromptInputFooter` β€” footer with action icons and submit button - `PromptInputHeader` β€” header with context items and indicator - `PromptInputPanel` β€” panel container for custom content - `Suggestions` β€” clickable suggestion buttons - `Tabs` β€” navigation tabs with delete functionality - `ToolFooter` β€” tool message footer with actions - `ToolHeader` β€” tool message header with icon and actions ### πŸ”Ά Organisms Complex components with internal logic: - `AssistantMessage` β€” AI assistant message - `Header` β€” chat header - `MessageList` β€” message list - `PromptInput` β€” message input field - `ThinkingMessage` β€” AI thinking process - `ToolMessage` β€” tool execution - `UserMessage` β€” user message ### πŸ“„ Templates Complete layouts: - `ChatContent` β€” main chat content - `EmptyContainer` β€” empty state - `History` β€” chat history ### πŸ“± Pages Full integrations: - `ChatContainer` β€” fully assembled chat ## Documentation - [Quick Start Guide](./docs/GETTING_STARTED.md) - [Architecture](./docs/ARCHITECTURE.md) - [Project Structure](./docs/PROJECT_STRUCTURE.md) - [Testing Guide](./docs/TESTING.md) - [Playwright Guide](./playwright/README.md) ## Testing The project uses Playwright Component Testing for visual regression testing. ### Run tests **Important**: All tests must be run via Docker to ensure consistent screenshots across different environments. ```bash # Run all component tests in Docker (recommended) npm run playwright:docker # Update screenshot baselines in Docker npm run playwright:docker:update # Run specific test by grep pattern in Docker npm run playwright:docker -- --grep "@ComponentName" # Clear Docker cache if needed npm run playwright:docker:clear-cache ``` ### Local testing (Linux only) If you're on Linux, you can run tests locally: ```bash # Install Playwright browsers (run once) npm run playwright:install # Run all component tests npm run playwright # Update screenshot baselines npm run playwright:update ``` For detailed testing documentation, see [Playwright Guide](./playwright/README.md). ## Development Development and contribution instructions are available in [CONTRIBUTING.md](./CONTRIBUTING.md). ## License MIT ## For AI agents A React component library for building AI chat interfaces, organized by Atomic Design (atoms β†’ molecules β†’ organisms β†’ templates β†’ pages) and SDK-agnostic β€” reach for it to assemble a chat UI (message lists, prompt input, tool calls, attachments) instead of composing those primitives out of `@gravity-ui/uikit` by hand. ### When to use - Building an AI/LLM chat UI (assistant/user/tool messages, prompt input with suggestions, attachment uploads, thinking states). - Wanting ready-made chat layouts (`ChatContainer`, `MessageList`, `PromptInput`) plus hooks to customize behavior. - Embedding into the Gravity UI ecosystem with shared theming via CSS variables. ### When not to use - For general-purpose UI primitives (buttons, inputs, modals), use [`@gravity-ui/uikit`](https://gravity-ui.com/uikit) directly β€” AIKit builds on top of it for chat-specific needs. - To render rich markdown in messages, AIKit's `MarkdownRenderer` wraps [`@gravity-ui/markdown-editor`](https://github.com/gravity-ui/markdown-editor); for standalone markdown rendering use that package directly. - For a single chat bubble without chat orchestration, a uikit `MarkdownRenderer`/text block is lighter than the full AIKit message pipeline. ### Common pitfalls - **Hallucinating an AI SDK import** β€” AIKit is SDK-agnostic; it provides components/hooks, not an LLM client. Bring your own data source and feed messages via props. - **Reaching for `` / ``** β€” the page-level export is `ChatContainer` (and `AIStudioChat`); there is no component literally named `Chat`. - **Skipping message-type registration for custom types** β€” custom message kinds must be registered in the message type system, or they render as unknown. - **Editing base components instead of using hooks** β€” the two-level design expects you to customize via hooks/composition; overriding internals directly breaks upgrades. ## Documentation for AI agents Agent-readable documentation for the installed version is located in `node_modules/@gravity-ui/aikit/build/docs/INDEX.md`. --- file: docs/GETTING_STARTED.md --- # Quick Start This guide walks you through installing `@gravity-ui/aikit` and rendering your first chat. ## Installation ```bash npm install @gravity-ui/aikit ``` You also need to install the peer dependencies (most likely already in your app if you use Gravity UI): ```bash npm install @gravity-ui/uikit @gravity-ui/icons @gravity-ui/i18n @diplodoc/transform highlight.js react react-dom ``` See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) if `Module not found` errors appear after install. ## Theme CSS AIKit ships compiled CSS that you must import once at the application root: ```typescript import '@gravity-ui/aikit/themes/common'; import '@gravity-ui/aikit/themes/light'; // or '/dark' ``` The component tree must be rendered inside Gravity UI's `` (from `@gravity-ui/uikit`) so that `data-theme="light"` / `data-theme="dark"` is set on the root. ## 1. Simple Chat Out of the Box The fastest path is the `ChatContainer` page component: ```tsx import {useState} from 'react'; import {ChatContainer} from '@gravity-ui/aikit'; import type {ChatType, TChatMessage, TSubmitData} from '@gravity-ui/aikit'; function App() { const [chats, setChats] = useState([]); const [activeChat, setActiveChat] = useState(null); const [messages, setMessages] = useState([]); const handleSendMessage = async (data: TSubmitData) => { // Append the user message immediately setMessages((prev) => [...prev, {role: 'user', content: data.content}]); // Call your backend, then append the assistant message const response = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({message: data.content}), }); const assistant = await response.json(); setMessages((prev) => [...prev, {role: 'assistant', content: assistant.content}]); }; return ( { /* create chat */ }} onDeleteChat={(chat) => { /* delete chat */ }} /> ); } ``` ## 2. Composing from Organisms For more control, compose `Header`, `MessageList`, and `PromptInput` directly: ```tsx import {Header, MessageList, PromptInput} from '@gravity-ui/aikit'; function CustomChat() { return (
{ /* ... */ }} />
); } ``` Each organism has its own README in `src/components/organisms//README.md` with the full props table. ## 3. Hooks AIKit exports a small set of hooks for advanced composition (date formatting, smart scrolling, tool-message state, file-upload store, etc.). See [HOOKS.md](./HOOKS.md) for the catalog and signatures. ## Working with Message Types Messages are typed by `role` (`'user'` | `'assistant'` | `'system'`). Assistant content supports multi-part rendering β€” a string, a single `TMessageContent` object, or an array of parts. ### Built-in content types | Type | Description | | ---------- | --------------------------------- | | `text` | Plain text or markdown | | `thinking` | AI thinking process (collapsible) | | `tool` | Tool execution with status | ```tsx import type {TAssistantMessage} from '@gravity-ui/aikit'; const message: TAssistantMessage = { role: 'assistant', timestamp: new Date().toISOString(), content: [ {type: 'text', data: {text: 'Let me think…'}}, {type: 'thinking', data: {content: 'Analyzing the request', status: 'thinking'}}, {type: 'text', data: {text: 'Here is the answer.'}}, ], }; ``` ### Custom message content types Register a custom renderer with `MessageRendererRegistry`: ```tsx import { createMessageRendererRegistry, registerMessageRenderer, type MessageRendererRegistry, } from '@gravity-ui/aikit'; type ChartMessageContent = { type: 'chart'; data: {points: number[]}; }; const renderers: MessageRendererRegistry = createMessageRendererRegistry(); registerMessageRenderer(renderers, 'chart', { render: ({content}) => , }); ; ``` See [src/components/organisms/MessageList/README.md](../src/components/organisms/MessageList/README.md) for the full registry API. ## Theming Theme switching is done via Gravity UI's `` (sets `data-theme` on the root). AIKit ships per-theme CSS files; import them at the application root. CSS variables follow the `--g-aikit-*` convention and are documented in [THEMING.md](./THEMING.md). ## Streaming Responses Stream tokens by mutating an in-flight assistant message with `status: 'streaming'` on `MessageList`: ```tsx const handleSendMessage = async (data: TSubmitData) => { setMessages((prev) => [...prev, {role: 'user', content: data.content}]); const response = await fetch('/api/chat/stream', { method: 'POST', body: JSON.stringify({message: data.content}), }); const reader = response.body!.getReader(); let accumulated = ''; // Insert a placeholder assistant message setMessages((prev) => [...prev, {role: 'assistant', content: ''}]); while (true) { const {done, value} = await reader.read(); if (done) break; accumulated += new TextDecoder().decode(value); setMessages((prev) => { const next = [...prev]; next[next.length - 1] = {role: 'assistant', content: accumulated}; return next; }); } }; ``` For an end-to-end working example with OpenAI on the server side, see [EXAMPLES.md](./EXAMPLES.md). ## Server-Side: OpenAI Adapter A server-side helper (`@gravity-ui/aikit/server/openai`) wraps the OpenAI Responses API: ```typescript const service = new OpenAIService({ /* config */ }); const stream = await service.createResponseStream({input: 'Hello world'}); stream.start(); stream.onEventChunk(console.log); // typed event chunks stream.onBufferChunk(console.log); // raw chunks (e.g. to forward over HTTP) stream.abort(); // cancel ``` `openai` and `semver` are listed as `optionalDependencies` β€” install them only if you use the server adapter. ## Next Steps - Component catalog: [COMPONENTS.md](./COMPONENTS.md) - Theming and CSS variables: [THEMING.md](./THEMING.md) - Hooks reference: [HOOKS.md](./HOOKS.md) - Real-world examples: [EXAMPLES.md](./EXAMPLES.md) - Architecture deep dive: [ARCHITECTURE.md](./ARCHITECTURE.md) - AI agent integration in your project: [AI_AGENTS.md](./AI_AGENTS.md) --- file: docs/ARCHITECTURE.md --- # AIKit Library Architecture ## Atomic Design Principles The library is built on **Atomic Design** principles, which ensure: - Clear component hierarchy - Element reusability - Easy maintenance - Flexible customization ## Component Levels ### 1. Atoms Minimal indivisible UI elements without business logic. **Characteristics:** - Do not contain business logic - Stateless - Unaware of usage context - Accept data only through props **Examples:** `ActionButton`, `Alert`, `Loader`, `Shimmer`, `MarkdownRenderer` ### 2. Molecules Simple groups of atoms forming basic UI blocks. **Characteristics:** - Combine multiple atoms - Minimal internal logic - Reusable blocks **Examples:** `ButtonGroup`, `Tabs`, `Suggestions`, `BaseMessage`, `PromptInputBody` ### 3. Organisms Complex self-sufficient components with internal logic. **Characteristics:** - Contain business logic - Have internal state - Can be used independently **Examples:** `Header`, `MessageList`, `PromptInput`, `UserMessage`, `AssistantMessage`, `ToolMessage`, `ThinkingMessage`, `FileUploadDialog` ### 4. Templates Complete layouts combining organisms. **Characteristics:** - Define page structure - Coordinate organism interactions - Work with abstract data **Examples:** `History`, `EmptyContainer`, `ChatContent` ### 5. Pages Full integrations with specific data. **Characteristics:** - Highest level of abstraction - Manage data and state - Connect all components together **Examples:** `ChatContainer`, `AIStudioChat` ### 6. Server Code that runs exclusively on the server side and is responsible for interacting with external APIs, primarily neural network services. **Characteristics:** - Runs only on the server - Contains no UI and is not involved in component rendering - Serves as a middleware layer between the application and external services (such as neural network APIs) **Examples:** `OpenAIService` (`@gravity-ui/aikit/server/openai`) ## Two-Level Approach The library offers two ways to integrate a chat: ### 1. Ready Page Component `ChatContainer` (or `AIStudioChat`) β€” a fully assembled chat with header, history, message list, and prompt input: ```tsx import {ChatContainer} from '@gravity-ui/aikit'; ; ``` ### 2. Compose from Organisms Drop down to individual organisms when you need a custom layout: ```tsx import {Header, MessageList, PromptInput} from '@gravity-ui/aikit'; function CustomChat() { return (
); } ``` Composition-level hooks (`useToolMessage`, `useSmartScroll`, `useFileUploadStore`, etc.) are documented in [HOOKS.md](./HOOKS.md). ## State Management ### Data Handling Principle - The library is **state-agnostic**: it does not own messages, chats, or upload state - All data flows through props - The library invokes callbacks (`onSendMessage`, `onSelectChat`, `onCreateChat`, `onDeleteChat`, …) and lets the host application manage requests, persistence, and errors ### Data Flow Example ```tsx const [messages, setMessages] = useState([]); const [status, setStatus] = useState('ready'); { setStatus('streaming'); const reply = await fetchAssistantReply(data.content); setMessages((prev) => [...prev, reply]); setStatus('ready'); }} />; ``` ## Type System ### Message Types ```typescript export type TMessageRole = 'user' | 'assistant' | 'system'; export type TUserMessage = { role: 'user'; content: string; timestamp?: string; format?: 'plain' | 'markdown'; images?: string[]; fileAttachments?: FileAttachment[]; }; export type TAssistantMessage = { role: 'assistant'; content: string | TMessageContent | TMessageContent[]; timestamp?: string; userRating?: 'like' | 'dislike'; }; export type TChatMessage = TUserMessage | TAssistantMessage; ``` Assistant content can be a plain string, a single typed part (`'text'` | `'thinking'` | `'tool'`), or an array of parts. Custom part types extend via the `TCustomContent` generic. ### Chat Types ```typescript export type ChatType = { id: string; name: string; createTime: string | null; lastMessage?: string; metadata?: Record; }; export type ChatStatus = 'submitted' | 'streaming' | 'streaming_loading' | 'ready' | 'error'; ``` Full type catalog: `src/types/{messages,chat,tool,common}.ts`. ## Extensibility ### Custom Message Content Renderers Register custom content types via `MessageRendererRegistry`: ```tsx import { createMessageRendererRegistry, registerMessageRenderer, type MessageRendererRegistry, } from '@gravity-ui/aikit'; type ChartMessageContent = { type: 'chart'; data: {points: number[]}; }; const renderers: MessageRendererRegistry = createMessageRendererRegistry(); registerMessageRenderer(renderers, 'chart', { render: ({content}) => , }); ; ``` See [src/components/organisms/MessageList/README.md](../src/components/organisms/MessageList/README.md) for the full registry API and validators. ### Adapters `src/adapters/` houses pre-built integrations (e.g. `openai`). They are imported through the main package entry: `import {…} from '@gravity-ui/aikit'`. ## Theming ### CSS Variables All styles are controlled through CSS variables in the `--g-aikit-*` namespace, with fallbacks to Gravity UI's `--g-color-*` system: ```css .g-root { --g-aikit-color-bg-primary: var(--g-aikit-bg-primary, var(--g-color-base-float)); } [data-theme='dark'] { --g-aikit-bg-message-user: #0066cc; } ``` ### Applying a Theme Wrap the application in Gravity UI's `` and import the matching theme CSS: ```tsx import {ThemeProvider} from '@gravity-ui/uikit'; import '@gravity-ui/aikit/themes/common'; import '@gravity-ui/aikit/themes/dark'; ; ``` Full CSS variable reference: [THEMING.md](./THEMING.md). ## Performance ### Optimizations - `React.memo` on heavy renderers - `react-window` virtualization for long message lists - Lazy hooks (`useSmartScroll`, `useScrollPreservation`) to avoid layout thrash - Markdown transform cache (`useMarkdownTransform`) ## Accessibility ### ARIA Attributes All interactive elements expose: - `aria-label` for screen readers - `role` for semantics - `aria-live` for streaming/loading regions ### Keyboard Navigation - `Enter` β€” send message - `Shift+Enter` β€” newline - `Escape` β€” cancel / close - `Tab` β€” navigation ## Testing Component tests run under Playwright Component Testing β€” see [TESTING.md](./TESTING.md). For developer guidelines specific to writing components, stories, and tests inside this repo, see [guidelines/](./guidelines/). --- file: docs/COMPONENTS.md --- # Components 49 components organized by Atomic Design level. Each component lives in `src/components///` and ships with its own README and Storybook stories. Each component also has a dedicated subpath export for tree-shaking β€” `import {ComponentName} from '@gravity-ui/aikit/ComponentName'` β€” except `AIStudioChat` (use the main entry). ## Atoms (16) | Component | Description | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [ActionButton](../src/components/atoms/ActionButton/README.md) | Button with integrated tooltip (Button + ActionTooltip from Gravity UI). | | [Alert](../src/components/atoms/Alert/README.md) | Alert message with type indicator and optional button. | | [ChatDate](../src/components/atoms/ChatDate/README.md) | Formatted date with time and locale support. | | [ContextIndicator](../src/components/atoms/ContextIndicator/README.md) | Circular progress indicator for context usage (0–100%). | | [ContextItem](../src/components/atoms/ContextItem/README.md) | Label for rendering a context chip. | | [DiffStat](../src/components/atoms/DiffStat/README.md) | Diff statistics β€” added/deleted line counts. | | [Disclaimer](../src/components/atoms/Disclaimer/README.md) | Informational or warning message block. | | [FileIcon](../src/components/atoms/FileIcon/README.md) | Icon for a file based on MIME type or extension. | | InlineCitation | Citation link rendered inline in markdown. | | [IntersectionContainer](../src/components/atoms/IntersectionContainer/README.md) | Intersection Observer wrapper (used by `MessageList` for infinite scroll). | | [Loader](../src/components/atoms/Loader/README.md) | Loading-state indicator. | | [MarkdownRenderer](../src/components/atoms/MarkdownRenderer/README.md) | Yandex Flavored Markdown (YFM) β†’ HTML renderer. | | [MessageBalloon](../src/components/atoms/MessageBalloon/README.md) | Visual wrapper for a user message. | | [Shimmer](../src/components/atoms/Shimmer/README.md) | Shimmer animation overlay for loading state. | | [SubmitButton](../src/components/atoms/SubmitButton/README.md) | Submit button with send/cancel state switching. | | [ToolIndicator](../src/components/atoms/ToolIndicator/README.md) | Tool-execution status icon. | ## Molecules (19) | Component | Description | | ---------------------------------------------------------------------------- | ----------------------------------------------------------- | | [ActionPopup](../src/components/molecules/ActionPopup/README.md) | Anchored popup container for action-button content. | | [BaseMessage](../src/components/molecules/BaseMessage/README.md) | Base wrapper with support for action buttons. | | [ButtonGroup](../src/components/molecules/ButtonGroup/README.md) | Wrapper for a button group with orientation support. | | [FeedbackForm](../src/components/molecules/FeedbackForm/README.md) | Reusable feedback form with reason selection and comment. | | [FileDropZone](../src/components/molecules/FileDropZone/README.md) | Drag-and-drop area with hidden file input (native HTML5). | | [FileItem](../src/components/molecules/FileItem/README.md) | File row: icon, name, size, status, remove button. | | [InputContext](../src/components/molecules/InputContext/README.md) | React context for prompt-input attachments and chips. | | [PromptInputBody](../src/components/molecules/PromptInputBody/README.md) | Textarea body for prompt input with auto-grow. | | [PromptInputFooter](../src/components/molecules/PromptInputFooter/README.md) | Footer with action icons and submit button. | | [PromptInputHeader](../src/components/molecules/PromptInputHeader/README.md) | Header with context items / context indicator. | | [PromptInputPanel](../src/components/molecules/PromptInputPanel/README.md) | Panel container for custom prompt content. | | [RatingBlock](../src/components/molecules/RatingBlock/README.md) | Rating block with title and star rating. | | [StarRating](../src/components/molecules/StarRating/README.md) | 1–5 star rating component. | | [Suggestions](../src/components/molecules/Suggestions/README.md) | Clickable suggestion buttons in grid or list layout. | | [Tabs](../src/components/molecules/Tabs/README.md) | Section tabs with optional delete (built on uikit `Label`). | | [ToolFooter](../src/components/molecules/ToolFooter/README.md) | Tool-message footer with action buttons and status. | | [ToolHeader](../src/components/molecules/ToolHeader/README.md) | Tool-message header with icon, name, actions, indicators. | | [ToolStatus](../src/components/molecules/ToolStatus/README.md) | Tool-status indicator with localized text. | ## Organisms (9) | Component | Description | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [AssistantMessage](../src/components/organisms/AssistantMessage/README.md) | Assistant message with multi-part content and custom renderers. | | [AttachmentPicker](../src/components/organisms/AttachmentPicker/README.md) | Paperclip button that opens the file upload dialog. | | [FileUploadDialog](../src/components/organisms/FileUploadDialog/README.md) | Dialog with drag-and-drop and queued/uploaded files list. | | [Header](../src/components/organisms/Header/README.md) | Chat header with navigation and actions. | | [MessageList](../src/components/organisms/MessageList/README.md) | Message list with custom renderer registry support. | | [PromptInput](../src/components/organisms/PromptInput/README.md) | Flexible chat input β€” simple/full views, panels, suggestions, attachments. | | [ThinkingMessage](../src/components/organisms/ThinkingMessage/README.md) | AI thinking process with collapsible content and status. | | [ToolMessage](../src/components/organisms/ToolMessage/README.md) | Tool message with automatic expand/collapse and status behavior. | | [UserMessage](../src/components/organisms/UserMessage/README.md) | User message renderer. | ## Templates (3) | Component | Description | | ---------------------------------------------------------------------- | --------------------------------------------------------------- | | [ChatContent](../src/components/templates/ChatContent/README.md) | Chat content container β€” empty state vs message list switching. | | [EmptyContainer](../src/components/templates/EmptyContainer/README.md) | Welcome screen with image, title, description, suggestions. | | [History](../src/components/templates/History/README.md) | Chat history popup β€” list, search, grouping, actions. | ## Pages (2) | Component | Description | | ---------------------------------------------------------------- | ------------------------------------------------------------------------- | | [AIStudioChat](../src/components/pages/AIStudioChat/README.md) | Ready-to-use chat with built-in OpenAI streaming β€” needs only an API URL. | | [ChatContainer](../src/components/pages/ChatContainer/README.md) | Fully assembled chat β€” integrates Header, ChatContent, and History. | --- file: docs/THEMING.md --- # Theming AIKit uses CSS variables for theming, in the `--g-aikit-*` namespace. Values fall back to Gravity UI's `--g-color-*` system so AIKit picks up your existing uikit theme automatically. ## CSS Setup Always import `common.css`, plus the matching theme variant: ```typescript import '@gravity-ui/aikit/themes/common'; import '@gravity-ui/aikit/themes/light'; // or '/dark' ``` Switching between themes is done via `@gravity-ui/uikit`'s ``, which writes `data-theme="light"` or `data-theme="dark"` onto a root element: ```tsx import {ThemeProvider} from '@gravity-ui/uikit'; ; ``` `variables.css` is **deprecated** β€” keep importing `common.css` only. ## Common Variables (`common.css`) Defined under `.g-root`, applied regardless of theme. ### Colors | Variable | Default | Description | | -------------------------------------- | ------------------------------------------------------ | ----------------------------------- | | `--g-aikit-color-bg-primary` | `var(--g-aikit-bg-primary, var(--g-color-base-float))` | Primary background | | `--g-aikit-color-bg-secondary` | `#f5f5f5` | Secondary background | | `--g-aikit-color-bg-message-user` | `#0077ff` | User message bubble background | | `--g-aikit-color-bg-message-assistant` | `#f0f0f0` | Assistant message bubble background | ### Layout | Variable | Default | Description | | --------------------------------- | ------- | ------------------- | | `--g-aikit-layout-base-padding-m` | `12px` | Base medium padding | ### Disclaimer / Suggestions / Header | Variable | Default | Description | | ---------------------------------- | ---------------------------------- | ----------------------------- | | `--g-aikit-disclaimer-gap` | `10px` | Gap inside `Disclaimer` | | `--g-aikit-suggestions-box-shadow` | `0 3px 10px rgba(198,172,255,.52)` | Shadow on `Suggestions` items | | `--g-aikit-header-background` | `none` | Header background | ### Context Indicator | Variable | Default | Description | | ------------------------------- | ------- | ------------------ | | `--g-aikit-ci-color-progress-1` | green | Low-usage segment | | `--g-aikit-ci-color-progress-2` | orange | Mid-usage segment | | `--g-aikit-ci-color-progress-3` | red | High-usage segment | ### Shimmer | Variable | Default | Description | | --------------------------------- | ----------------- | ---------------------- | | `--g-aikit-shimmer-color-from` | `rgba(0,0,0,.35)` | Shimmer gradient start | | `--g-aikit-shimmer-color-to` | `rgba(0,0,0,.85)` | Shimmer gradient end | | `--g-aikit-shimmer-duration` | `2.5s` | Animation duration | | `--g-aikit-shimmer-gradient-size` | `200%` | Gradient size | ### History | Variable | Default | Description | | ------------------------------- | ------- | ------------------------ | | `--g-aikit-history-width` | `360px` | History popup width | | `--g-aikit-history-max-height` | `605px` | History popup max height | | `--g-aikit-history-item-height` | `24px` | Row height | ### Prompt Input | Variable | Default | Description | | ----------------------------------------- | ------- | ------------------------------ | | `--g-aikit-prompt-input-panel-max-height` | `500px` | Max height of expandable panel | ### Empty Container | Variable | Default | Description | | --------------------------------------- | -------------------------------- | -------------------------- | | `--g-aikit-empty-container-background` | `var(--g-color-base-background)` | Empty-state background | | `--g-aikit-empty-container-content-gap` | `48px` | Gap between content blocks | | `--g-aikit-empty-container-padding` | `48px 32px` | Empty-state padding | | `--g-aikit-empty-container-welcome-gap` | `var(--g-spacing-6)` | Gap between hero and text | Mobile mode uses a parallel set of tokens, applied by the component itself: | Variable | Default | Description | | ---------------------------------------------- | --------------------------- | ----------------------------------------- | | `--g-aikit-empty-container-mobile-content-gap` | `32px` | Gap between content blocks in mobile mode | | `--g-aikit-empty-container-mobile-padding` | `80px var(--g-spacing-4) 0` | Empty-state padding in mobile mode | | `--g-aikit-empty-container-mobile-welcome-gap` | `18px` | Gap between hero and text in mobile mode | ### Chat Content / Container | Variable | Default | Description | | --------------------------------------------------------- | -------------------------------- | ---------------------------------------- | | `--g-aikit-chat-content-background` | `var(--g-color-base-background)` | Content area background | | `--g-aikit-chat-content-padding` | derived | Content area padding | | `--g-aikit-chat-container-background` | `var(--g-color-base-background)` | Container background | | `--g-aikit-chat-container-mobile-font-size` | `16px` | Body text size in mobile mode | | `--g-aikit-chat-container-mobile-line-height` | `24px` | Body line height in mobile mode | | `--g-aikit-chat-container-mobile-padding` | `var(--g-spacing-4)` | Base layout padding in mobile mode | | `--g-aikit-chat-container-mobile-empty-container-padding` | `80px 0 0` | Empty-state padding inside a mobile chat | | `--g-aikit-chat-container-header-background` | `var(--g-color-base-background)` | Header band background | | `--g-aikit-chat-container-content-background` | `var(--g-color-base-background)` | Content band background | | `--g-aikit-chat-container-footer-background` | `var(--g-color-base-background)` | Footer band background | | `--g-aikit-chat-container-content-empty-background` | `var(--g-color-base-background)` | Empty-state content background | | `--g-aikit-chat-container-content-chat-background` | `var(--g-color-base-background)` | Active-chat content background | | `--g-aikit-chat-container-footer-empty-background` | `var(--g-color-base-background)` | Empty-state footer background | | `--g-aikit-chat-container-footer-chat-background` | `var(--g-color-base-background)` | Active-chat footer background | ## Light Theme Overrides (`light.css`) Applied under `[data-theme='light']`: | Variable | Value | | ---------------------------------- | ----------------------------- | | `--g-aikit-bg-secondary` | `#f5f5f5` | | `--g-aikit-bg-message-user` | `#0077ff` | | `--g-aikit-bg-message-assistant` | `#f0f0f0` | | `--g-aikit-text-primary` | `#000000` | | `--g-aikit-text-secondary` | `#666666` | | `--g-aikit-text-message-user` | `#ffffff` | | `--g-aikit-text-message-assistant` | `#000000` | | `--g-aikit-border-color` | `#e0e0e0` | | `--g-aikit-accent-color` | `#0077ff` | | `--g-aikit-line-brand` | `var(--g-aikit-accent-color)` | ## Dark Theme Overrides (`dark.css`) Applied under `[data-theme='dark']`: | Variable | Value | | ---------------------------------- | ----------------------------- | | `--g-aikit-bg-secondary` | `#2a2a2a` | | `--g-aikit-bg-message-user` | `#0066cc` | | `--g-aikit-bg-message-assistant` | `#2a2a2a` | | `--g-aikit-text-primary` | `#ffffff` | | `--g-aikit-text-secondary` | `#999999` | | `--g-aikit-text-message-user` | `#ffffff` | | `--g-aikit-text-message-assistant` | `#ffffff` | | `--g-aikit-border-color` | `#404040` | | `--g-aikit-accent-color` | `#0077ff` | | `--g-aikit-line-brand` | `var(--g-aikit-accent-color)` | | `--g-aikit-shimmer-color-from` | `rgba(255,255,255,.35)` | | `--g-aikit-shimmer-color-to` | `rgba(255,255,255,.85)` | ## Overriding Variables Override at any selector level. Per-component scope is preferred: ```css .my-chat .g-root { --g-aikit-color-bg-message-user: #6c5ce7; --g-aikit-color-bg-message-assistant: #2d3436; } ``` For one-off overrides inline: ```tsx ``` ## Fallback Chain Each `--g-aikit-color-*` variable falls back to a `--g-aikit-*` "raw" variable, then to a `--g-color-*` variable from Gravity UI: ``` --g-aikit-color-bg-primary β†’ var(--g-aikit-bg-primary, var(--g-color-base-float)) ``` This means: if you already set `--g-color-base-float` via `@gravity-ui/uikit`'s theming, AIKit picks it up automatically. --- file: docs/HOOKS.md --- # Hooks AIKit exports 8 public hooks. All are re-exported from the package root and from the `@gravity-ui/aikit/hooks` subpath. | Hook | Purpose | | ----------------------------------------------------------- | ------------------------------------------------------------------------------ | | [`useDateFormatter`](#usedateformatter) | Format chat timestamps with locale and relative-date logic | | [`useToolMessage`](#usetoolmessage) | Tool-message state machine (expand/collapse based on status) | | [`useSmartScroll`](#usesmartscroll) | Auto-scroll to bottom while respecting user scroll-up | | [`useScrollPreservation`](#usescrollpreservation) | Preserve scroll position when items are prepended | | [`useAutoCollapseOnSuccess`](#useautocollapseonsuccess) | Collapse a section when its async operation succeeds | | [`useAutoCollapseOnCancelled`](#useautocollapseoncancelled) | Collapse a section when its async operation is cancelled | | [`useFileUploadStore`](#usefileuploadstore) | Standalone file-upload store with progress/error states | | [`useToolset`](#usetoolset) | Wire a toolset into the chat: renderer registry + history merge for tool calls | ## `useDateFormatter` Formats a single date with locale support and relative-date thresholding. ```typescript function useDateFormatter(options: UseDateFormatterOptions): UseDateFormatterResult; interface UseDateFormatterOptions { date: string | Date | number; format?: string; // dayjs format token; defaults to library config } interface UseDateFormatterResult { formattedDate: string; // 'Today' | 'Yesterday' | 'Mar 12, 2026' formattedTime: string; // '15:42' fullDate: string; // combined date + time dateObject: Dayjs | null; // parsed dayjs instance isValid: boolean; diffDays: number | null; // signed days from now (negative = past) } ``` Powered by `dayjs`. Used inside `ChatDate`, `History`, and message components. ## `useToolMessage` Computes display state for a tool message (expanded/collapsed, status icon, etc.). ```typescript function useToolMessage(options: ToolMessageProps): ToolMessageState; ``` `ToolMessageProps` is defined in `src/types/tool.ts` and includes the tool's status (`'loading' | 'success' | 'error' | 'cancelled'`), title, args, result, and a `defaultExpanded` flag. The hook handles auto-collapse on success and cancellation (internally uses [`useAutoCollapseOnSuccess`](#useautocollapseonsuccess) and [`useAutoCollapseOnCancelled`](#useautocollapseoncancelled)). ## `useSmartScroll` Keeps the scroll container pinned to the bottom while messages stream in, but yields to the user as soon as they scroll up. Used by `MessageList`. ```typescript function useSmartScroll(options?: { threshold?: number; // px from bottom to be "at bottom" β€” default 50 enabled?: boolean; }): { ref: React.RefObject; isAtBottom: boolean; scrollToBottom: () => void; }; ``` ## `useScrollPreservation` Preserves a container's visual scroll position when items are prepended (typical for infinite-scroll-up message history). ```typescript function useScrollPreservation( deps: React.DependencyList, ): React.RefObject; ``` Attach the returned ref to the scrollable container. When `deps` change (e.g. messages array grows at the top), the hook captures pre-update scroll metrics and restores them post-update. ## `useAutoCollapseOnSuccess` Collapses a section automatically after its status transitions into a "success" state. ```typescript function useAutoCollapseOnSuccess(options: { status: string; successStatuses: string[]; // statuses that should trigger collapse isExpanded: boolean; setIsExpanded: (v: boolean) => void; delay?: number; // ms; default 0 }): void; ``` ## `useAutoCollapseOnCancelled` Same as [`useAutoCollapseOnSuccess`](#useautocollapseonsuccess) but triggered on a "cancelled" status. ```typescript function useAutoCollapseOnCancelled(options: { status: string; cancelledStatuses: string[]; isExpanded: boolean; setIsExpanded: (v: boolean) => void; delay?: number; }): void; ``` ## `useFileUploadStore` A standalone, headless store for file uploads. Tracks queued / uploading / uploaded / errored entries with progress, supports cancel and remove. Used by `FileUploadDialog` and `AttachmentPicker`, but you can use it independently to build your own upload UI. ```typescript function useFileUploadStore( options: UseFileUploadStoreOptions, ): UseFileUploadStoreReturn; type FileUploadEntry = { file: File; status: 'queued' | 'uploading' | 'uploaded' | 'error' | 'cancelled'; progress: number; error?: unknown; meta?: Meta; }; type UseFileUploadStoreReturn = { files: FileUploadEntry[]; add: (files: File[]) => void; remove: (id: string) => void; cancel: (id: string) => void; clear: () => void; // …additional helpers }; ``` See `src/hooks/useFileUploadStore.ts` for the complete type signature. ## `useToolset` Wires a `Toolset` into the chat: returns a `MessageRendererRegistry` whose `tool` dispatcher renders your tool components, and a `handleToolResult` callback that merges results into history via `applyToolResult`. It does not run model continuation side effects; pair it with `useToolResultContinuation` when you need to re-send the updated conversation after a tool completes. ```typescript function useToolset( options: UseToolsetOptions, ): UseToolsetReturn; type UseToolsetOptions = { toolset: Toolset; setMessages: Dispatch[]>>; registry?: MessageRendererRegistry; }; type UseToolsetReturn = { messageRendererRegistry: MessageRendererRegistry; handleToolResult: (event: ToolsetResultEvent) => void; }; ``` ## `useToolResultContinuation` Observes `messages` and calls `onSettled` once when a tool part moves from a pending status (`loading`, `waitingConfirmation`, `waitingSubmission`) to a terminal status (`success`, `error`, `cancelled`). Tools that are already terminal in the first observed snapshot do not fire, so replay/restore is silent by default. ```typescript function useToolResultContinuation( options: UseToolResultContinuationOptions, ): void; type UseToolResultContinuationOptions = { messages: TChatMessage[]; onSettled: (event: ToolSettledEvent) => void; }; type ToolSettledEvent = { toolCallId: string; toolName: string; status: 'success' | 'error' | 'cancelled'; messages: TChatMessage[]; }; ``` See [GENUI.md](./GENUI.md) for an end-to-end example with a live model adapter. --- file: docs/I18N.md --- # Internationalization (i18n) AIKit localizes user-facing strings on a **per-component** basis using `@gravity-ui/i18n`. There is no global i18n provider β€” each component bundles its own keyset. Out-of-the-box languages: **English (`en`)** and **Russian (`ru`)**. ## How It Works Each localized component contains an `i18n/` directory: ``` ComponentName/ β”œβ”€β”€ i18n/ β”‚ β”œβ”€β”€ index.ts # registers the keyset with @gravity-ui/i18n β”‚ β”œβ”€β”€ en.json β”‚ └── ru.json ``` The keyset is registered under a namespace derived from the component's name. At runtime, `@gravity-ui/i18n` resolves strings against the active language (set via `configure({lang})`). ## Setting the Language ```typescript import {configure} from '@gravity-ui/i18n'; configure({lang: 'ru'}); // or 'en' ``` Call this once at application bootstrap, before rendering AIKit components. ## Overriding Strings Every component that has user-visible text also exposes a `texts` prop (or per-feature prop like `headerProps.texts`) for per-instance overrides. `ChatContainer` aggregates these via a single `texts` prop on the page level. The resolution order is: 1. `texts.` on the component (or on `ChatContainer`) 2. Custom keyset registered with `@gravity-ui/i18n` (if you override the bundled one) 3. AIKit's bundled `i18n/.json` defaults ## Adding a Custom Language Re-register the bundled keysets with your translations: ```typescript import {addKeysets} from '@gravity-ui/i18n'; import esTabs from './my-translations/Tabs.es.json'; addKeysets('es', { 'aikit.Tabs': esTabs, // …repeat for each component you localize }); configure({lang: 'es'}); ``` Namespace prefixes match the component name (e.g. `aikit.Header`, `aikit.MessageList`). Inspect `src/components/.../i18n/index.ts` for the exact key. ## Localized Components 22 components ship with built-in translations: ### Atoms - `ChatDate` - `SubmitButton` ### Molecules - `ActionPopup`, `BaseMessage`, `FeedbackForm`, `FileDropZone`, `FileItem`, `InputContext`, - `PromptInputFooter`, `StarRating`, `Tabs`, `ToolStatus` ### Organisms - `AttachmentPicker`, `FileUploadDialog`, `Header`, `MessageList`, `ThinkingMessage`, `ToolMessage` ### Templates - `EmptyContainer`, `History` ### Pages - `AIStudioChat`, `ChatContainer` Components not listed above either have no visible text or take all text through props. ## See Also - `@gravity-ui/i18n` documentation: - `ChatContainer`'s aggregated `texts` prop: [src/components/pages/ChatContainer/types.ts](../src/components/pages/ChatContainer/types.ts) (`ChatContainerTexts` interface) --- file: docs/EXAMPLES.md --- # Examples Practical patterns for common AIKit integrations. ## 1. Streaming Assistant Responses Append a placeholder assistant message and mutate it as tokens arrive. ```tsx import {useState} from 'react'; import {ChatContainer} from '@gravity-ui/aikit'; import type {ChatStatus, TChatMessage, TSubmitData} from '@gravity-ui/aikit'; function StreamingChat() { const [messages, setMessages] = useState([]); const [status, setStatus] = useState('ready'); const handleSend = async (data: TSubmitData) => { setMessages((prev) => [ ...prev, {role: 'user', content: data.content, timestamp: new Date().toISOString()}, {role: 'assistant', content: '', timestamp: new Date().toISOString()}, ]); setStatus('streaming'); const response = await fetch('/api/chat/stream', { method: 'POST', body: JSON.stringify({message: data.content}), }); const reader = response.body!.getReader(); const decoder = new TextDecoder(); let accumulated = ''; while (true) { const {done, value} = await reader.read(); if (done) break; accumulated += decoder.decode(value, {stream: true}); setMessages((prev) => { const next = [...prev]; next[next.length - 1] = {...next[next.length - 1], content: accumulated}; return next; }); } setStatus('ready'); }; return ( {}} onCreateChat={() => {}} onDeleteChat={() => {}} /> ); } ``` Server-side: AIKit ships an OpenAI streaming wrapper β€” see [Β§4](#4-server-side-openai-via-server-openai). ## 2. File Upload with `AttachmentPicker` + `useFileUploadStore` `AttachmentPicker` is a paperclip button that renders `FileUploadDialog` internally β€” you never mount the dialog yourself. Neither component owns upload state: `useFileUploadStore` holds the queue and you hand its data to the picker through `fileDialogProps`. The hook takes an `upload` callback (one file in, metadata out) and returns `entries`, `addFiles`, `removeFile`, `reset`, `uploadedMetas`, and `isLoading`: ```tsx import {AttachmentPicker, useFileUploadStore} from '@gravity-ui/aikit'; type UploadMeta = {id: string; name: string; mimeType?: string}; function useAttachmentPicker() { const {entries, addFiles, removeFile, reset, uploadedMetas} = useFileUploadStore({ // Called once per file; return whatever metadata your backend responds with. upload: async (file) => { const form = new FormData(); form.append('file', file); const res = await fetch('/api/upload', {method: 'POST', body: form}); return (await res.json()) as UploadMeta; }, }); const attachmentPicker = ( ({ id: entry.id, name: entry.file.name, size: entry.file.size, mimeType: entry.file.type || undefined, status: (() => { if (entry.status === 'uploading') return 'loading'; if (entry.status === 'done') return 'success'; if (entry.status === 'error') return 'error'; return undefined; })(), onRemove: () => removeFile(entry.id), })), }} /> ); return {attachmentPicker, entries, removeFile, reset, uploadedMetas}; } ``` Mount the picker in the prompt input footer and mirror the queue as removable chips in the header: ```tsx function ChatWithAttachments() { const {attachmentPicker, entries, removeFile, reset, uploadedMetas} = useAttachmentPicker(); const handleSendMessage = async (data: TSubmitData) => { // `uploadedMetas` holds the backend metadata of successfully uploaded files. const fileAttachments = uploadedMetas.map((meta) => ({ id: meta.id, name: meta.name, mimeType: meta.mimeType, })); reset(); await sendToBackend({content: data.content, fileAttachments}); }; return ( ({ id: entry.id, content: entry.file.name, onRemove: () => removeFile(entry.id), })), }, footerProps: {attachmentContent: attachmentPicker}, }} /> ); } ``` `entries` (not `files`) lists every queued / uploading / done / errored entry, each carrying its `status`, `id`, and original `File`; `uploadedMetas` narrows that to the metadata of entries that finished uploading. A runnable version of this wiring is in [`WithAttachmentInput`](../src/components/pages/ChatContainer/__stories__/parts/attachments.tsx), and `InputContextProvider` packages the same logic behind a context β€” see [src/components/molecules/InputContext/InputContextProvider.tsx](../src/components/molecules/InputContext/InputContextProvider.tsx). ## 3. Custom Message Content Renderer Add a custom assistant content part (e.g. interactive chart) via `MessageRendererRegistry`: ```tsx import { createMessageRendererRegistry, registerMessageRenderer, MessageList, type MessageRendererRegistry, type TMessageContent, } from '@gravity-ui/aikit'; type ChartContent = TMessageContent<'chart', {points: number[]; label?: string}>; const renderers: MessageRendererRegistry = createMessageRendererRegistry(); registerMessageRenderer(renderers, 'chart', { render: ({content}) => , }); function ChatView({messages}: {messages: TChatMessage[]}) { return ; } ``` A produced assistant message may now mix text and custom parts: ```typescript const msg: TAssistantMessage = { role: 'assistant', content: [ {type: 'text', data: {text: 'Here is the trend:'}}, {type: 'chart', data: {points: [1, 4, 9, 16, 25], label: 'Squares'}}, ], }; ``` ## 4. Server-Side OpenAI via `server/openai` A Node-only wrapper for OpenAI's Responses API. Install the optional `openai` dependency: ```bash npm install openai ``` ```typescript import {OpenAIService} from '@gravity-ui/aikit/server/openai'; const service = new OpenAIService({ apiKey: process.env.OPENAI_API_KEY!, // Other OpenAI client options }); // Streaming response app.post('/api/chat/stream', async (req, res) => { const stream = await service.createResponseStream({input: req.body.message}); stream.onBufferChunk((chunk) => res.write(chunk)); stream.onEventChunk((event) => { if (event.type === 'response.completed') res.end(); }); stream.start(); req.on('close', () => stream.abort()); }); // Conversation title summarization app.post('/api/chat/title', async (req, res) => { const title = await service.summarizeConversationTitle({ conversation: req.body.conversationId, byLastItems: 5, // or `byFirstItems` }); res.json({title}); }); ``` The `optionalDependencies` field in `package.json` lists `openai` and `semver` β€” install them only on the server side. ## 5. Drop-in `AIStudioChat` If you have an AI Studio compatible endpoint, `AIStudioChat` wraps `ChatContainer` and handles streaming/state internally: ```tsx import {AIStudioChat} from '@gravity-ui/aikit'; ; ``` See `src/components/pages/AIStudioChat/README.md` for the full prop list. ## 6. Custom Composition: Header + MessageList + PromptInput When `ChatContainer` is too opinionated, build your own layout from organisms: ```tsx import {Header, MessageList, PromptInput} from '@gravity-ui/aikit'; function MyChat() { return (
); } ``` Each organism exposes its full prop surface through its own README; combine them however you need. --- file: docs/TROUBLESHOOTING.md --- # Troubleshooting Common issues when integrating `@gravity-ui/aikit`. ## Peer Dependencies AIKit declares the following peer dependencies. Your app must install them: ```bash npm install \ @gravity-ui/uikit@^7.25 \ @gravity-ui/icons@^2.16 \ @gravity-ui/i18n@^1.8 \ @diplodoc/transform@^4.63 \ highlight.js@^11.11 \ react@^18 react-dom@^18 ``` If you see `Module not found: @gravity-ui/uikit` (or similar) after installing AIKit, you're missing one of the peers above. ## Themes / Dark Mode Not Applying Three things must all be in place: 1. **Theme CSS imported once at app root:** ```typescript import '@gravity-ui/aikit/themes/common'; import '@gravity-ui/aikit/themes/dark'; ``` 2. **`` wrapping AIKit components** (from `@gravity-ui/uikit`). It writes `data-theme="dark"` onto the root element. 3. **`.g-root` class on a parent**, which is added automatically by ``. If you bypass ``, you must set `class="g-root"` manually. ```tsx import {ThemeProvider} from '@gravity-ui/uikit'; ; ``` ## Markdown Not Rendering `MarkdownRenderer` requires `@diplodoc/transform` and `highlight.js` (both are peer deps). If they're missing, markdown content falls back to plain text without errors. Re-install peers if so. Code blocks specifically need `highlight.js` styles. If syntax highlighting is missing, also import a highlight.js theme: ```typescript import 'highlight.js/styles/github.css'; // or your preferred theme ``` ## Server-Side Code Won't Build `@gravity-ui/aikit/server/openai` depends on the optional `openai` and `semver` packages, which are **not installed by default**: ```bash npm install openai semver ``` The server code is shipped in a separate target (`build/server/`); make sure your bundler treats this subpath as Node-only. ## Portal-Mounted Dialogs Missing from Component Screenshots If you're writing Playwright Component Tests for a component that opens `FileUploadDialog` (or any portal-mounted dialog), `expectScreenshot()` without options won't capture the dialog. Use full-page mode: ```tsx await expectScreenshot({component: page, fullPage: true}); ``` See [TESTING.md](./TESTING.md) and the [Playwright dialog testing notes](./guidelines/testing.md). ## `usePromptBox` / `PromptBox` Imports Failing These were renamed. Use the current API: | Old | New | | -------------- | ----------------------------------------------------------------------------------------- | | `PromptBox` | `PromptInput` | | `usePromptBox` | no direct equivalent; compose `PromptInput` or build with `PromptInputBody/Header/Footer` | ## `import {…} from 'aikit'` Fails The package name is `@gravity-ui/aikit`, not `aikit`. Use: ```typescript import {ChatContainer} from '@gravity-ui/aikit'; ``` ## TypeScript: `Cannot find module '@gravity-ui/aikit/X'` Subpath imports (`@gravity-ui/aikit/Header`, `/themes/dark`, etc.) require either `moduleResolution: "Bundler"` or `"NodeNext"` in your `tsconfig.json`. If you're on the legacy `"Node"` resolution, switch to one of the above or use the main entry only. ## Bundle Size The main entry (`@gravity-ui/aikit`) re-exports everything. For smaller bundles, import individual components via their subpaths: ```typescript // Tree-shakable import {Header} from '@gravity-ui/aikit/Header'; // Pulls in the whole library (still tree-shakes with a modern bundler, but slower) import {Header} from '@gravity-ui/aikit'; ``` ## Where to Report Issues --- file: docs/AI_AGENTS.md --- # Using AIKit with AI Agents (Claude Code / Cursor) When you install `@gravity-ui/aikit` in a downstream project, you can teach Claude Code and Cursor about it so they write correct code without you spelling out the API every time. After install, two files are available locally: - `node_modules/@gravity-ui/aikit/llms.txt` β€” concise index (component catalog + key links) - `node_modules/@gravity-ui/aikit/llms-full.txt` β€” full documentation concatenated The patterns below point your agent at those files. ## Cursor: drop-in rule Create `.cursor/rules/aikit.mdc` in your project: ``` --- description: Gravity UI AIKit (@gravity-ui/aikit) component library globs: src/**/*.{ts,tsx,jsx,js} alwaysApply: false --- The project uses @gravity-ui/aikit for AI chat components. Reference: - `node_modules/@gravity-ui/aikit/llms.txt` β€” catalog of components, hooks, peer deps, theming - `node_modules/@gravity-ui/aikit/llms-full.txt` β€” full docs (read on demand) Conventions when using @gravity-ui/aikit: - Prefer the page-level `ChatContainer` for quick integration; drop to organisms (`PromptInput`, `MessageList`, `Header`) for custom layouts. - Custom assistant content parts go through `MessageRendererRegistry` via `createMessageRendererRegistry` + `registerMessageRenderer`. There is no `messageTypeRegistry` prop. - Theme CSS must be imported at the app root: `@gravity-ui/aikit/themes/common` + one of `/light` or `/dark`. Render inside `` from `@gravity-ui/uikit`. - Subpath imports (`@gravity-ui/aikit/Header`, etc.) are tree-shakable and preferred over the barrel entry for production code. - Peer deps required: @gravity-ui/uikit, @gravity-ui/icons, @gravity-ui/i18n, @diplodoc/transform, highlight.js, react>=18. ``` This rule auto-attaches when you edit anything under `src/`. The agent reads `llms.txt` on first use and consults `llms-full.txt` for deeper questions. ## Claude Code: drop-in skill Create `.claude/skills/aikit/SKILL.md` in your project: ```markdown --- name: aikit description: Gravity UI AIKit (@gravity-ui/aikit) React component library for building AI chats. Use when the user works with @gravity-ui/aikit components, hooks, types, theming, or examples. --- Read `node_modules/@gravity-ui/aikit/llms.txt` for the component catalog and `node_modules/@gravity-ui/aikit/llms-full.txt` for full documentation. Key conventions: - `ChatContainer` is the quickest integration; drop to organisms (`PromptInput`, `MessageList`, `Header`) for custom layouts. - Custom message content parts: `createMessageRendererRegistry` + `registerMessageRenderer(reg, 'type', {render: …})`; pass via `messageRendererRegistry` prop on `MessageList`. - Theme CSS at app root: `@gravity-ui/aikit/themes/common` plus `/light` or `/dark`. Wrap tree in `` from `@gravity-ui/uikit`. - Subpath imports (`@gravity-ui/aikit/Header`, etc.) for tree-shaking. - Peer deps required: @gravity-ui/uikit, @gravity-ui/icons, @gravity-ui/i18n, @diplodoc/transform, highlight.js, react>=18. ``` Claude Code surfaces the skill in its registry; it will load it when your prompts touch AIKit. ## Without npm install (raw GitHub access) If the agent can fetch URLs and you don't have the package installed locally yet, point at the raw files on GitHub: ``` https://raw.githubusercontent.com/gravity-ui/aikit/main/llms.txt https://raw.githubusercontent.com/gravity-ui/aikit/main/llms-full.txt ``` ## Verifying the setup After dropping in the snippet: - **Cursor**: open any `src/**/*.tsx` file; in the chat panel the rule `aikit` should appear in the attached-rules list. Ask "what's the right way to render a streaming assistant message with @gravity-ui/aikit?" β€” the agent should cite the streaming example from `llms-full.txt`. - **Claude Code**: start a new session and ask "give me a minimal `ChatContainer` usage example." The agent should invoke the `aikit` skill and reproduce the code from the docs. If neither happens, double-check that the rule/skill file is in the right location and that `node_modules/@gravity-ui/aikit/llms.txt` actually exists (run `cat node_modules/@gravity-ui/aikit/llms.txt | head` to verify). --- file: docs/PROJECT_STRUCTURE.md --- # AIKit Project Structure This document describes the layout of the `@gravity-ui/aikit` source tree. ## File Tree ``` aikit/ β”œβ”€β”€ .storybook/ # Storybook configuration β”œβ”€β”€ .claude/ # Claude Code skills for contributors β”œβ”€β”€ .cursor/ # Cursor rules for contributors β”œβ”€β”€ docs/ # Documentation (this directory) β”‚ β”œβ”€β”€ README.md β”‚ β”œβ”€β”€ GETTING_STARTED.md β”‚ β”œβ”€β”€ ARCHITECTURE.md β”‚ β”œβ”€β”€ PROJECT_STRUCTURE.md β”‚ β”œβ”€β”€ COMPONENTS.md β”‚ β”œβ”€β”€ THEMING.md β”‚ β”œβ”€β”€ HOOKS.md β”‚ β”œβ”€β”€ I18N.md β”‚ β”œβ”€β”€ EXAMPLES.md β”‚ β”œβ”€β”€ TROUBLESHOOTING.md β”‚ β”œβ”€β”€ AI_AGENTS.md β”‚ β”œβ”€β”€ TESTING.md β”‚ β”œβ”€β”€ PLAYWRIGHT.md β”‚ β”œβ”€β”€ assets/ # Logos and cover image β”‚ └── guidelines/ # Internal contributor guidelines (storybook, testing, readme, code-style) β”‚ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ adapters/ # SDK adapters (OpenAI) β”‚ β”‚ └── openai/ β”‚ β”‚ β”‚ β”œβ”€β”€ components/ β”‚ β”‚ β”œβ”€β”€ atoms/ # 16 atoms β”‚ β”‚ β”‚ β”œβ”€β”€ ActionButton/ ChatDate/ ContextItem/ β”‚ β”‚ β”‚ β”œβ”€β”€ Alert/ ContextIndicator/ DiffStat/ β”‚ β”‚ β”‚ β”œβ”€β”€ Disclaimer/ FileIcon/ InlineCitation/ β”‚ β”‚ β”‚ β”œβ”€β”€ IntersectionContainer/ Loader/ MarkdownRenderer/ β”‚ β”‚ β”‚ β”œβ”€β”€ MessageBalloon/ Shimmer/ SubmitButton/ β”‚ β”‚ β”‚ └── ToolIndicator/ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ molecules/ # 19 molecules β”‚ β”‚ β”‚ β”œβ”€β”€ ActionPopup/ BaseMessage/ ButtonGroup/ β”‚ β”‚ β”‚ β”œβ”€β”€ FeedbackForm/ FileDropZone/ FileItem/ β”‚ β”‚ β”‚ β”œβ”€β”€ InputContext/ PromptInputBody/ PromptInputFooter/ β”‚ β”‚ β”‚ β”œβ”€β”€ PromptInputHeader/ PromptInputPanel/ RatingBlock/ β”‚ β”‚ β”‚ β”œβ”€β”€ StarRating/ Suggestions/ Tabs/ β”‚ β”‚ β”‚ β”œβ”€β”€ ToolFooter/ ToolHeader/ ToolStatus/ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ organisms/ # 9 organisms β”‚ β”‚ β”‚ β”œβ”€β”€ AssistantMessage/ AttachmentPicker/ β”‚ β”‚ β”‚ β”œβ”€β”€ FileUploadDialog/ Header/ MessageList/ β”‚ β”‚ β”‚ β”œβ”€β”€ PromptInput/ ThinkingMessage/ β”‚ β”‚ β”‚ β”œβ”€β”€ ToolMessage/ UserMessage/ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ templates/ # 3 templates β”‚ β”‚ β”‚ β”œβ”€β”€ ChatContent/ EmptyContainer/ History/ β”‚ β”‚ β”‚ β”‚ β”‚ └── pages/ # 2 pages β”‚ β”‚ β”œβ”€β”€ AIStudioChat/ ChatContainer/ β”‚ β”‚ β”‚ β”œβ”€β”€ hooks/ # 7 public hooks (see docs/HOOKS.md) β”‚ β”‚ β”œβ”€β”€ useDateFormatter/ β”‚ β”‚ β”œβ”€β”€ useToolMessage.tsx β”‚ β”‚ β”œβ”€β”€ useSmartScroll.tsx β”‚ β”‚ β”œβ”€β”€ useScrollPreservation.ts β”‚ β”‚ β”œβ”€β”€ useAutoCollapseOnSuccess.ts β”‚ β”‚ β”œβ”€β”€ useAutoCollapseOnCancelled.ts β”‚ β”‚ └── useFileUploadStore.ts β”‚ β”‚ β”‚ β”œβ”€β”€ types/ # TypeScript type definitions β”‚ β”‚ β”œβ”€β”€ messages.ts # TUserMessage, TAssistantMessage, TChatMessage, content types β”‚ β”‚ β”œβ”€β”€ chat.ts # ChatType, ChatStatus, list items β”‚ β”‚ β”œβ”€β”€ tool.ts # ToolMessageProps, statuses β”‚ β”‚ └── common.ts # ActionConfig, SuggestionsItem, shared types β”‚ β”‚ β”‚ β”œβ”€β”€ utils/ # Public utility modules β”‚ β”‚ β”œβ”€β”€ chatUtils.ts β”‚ β”‚ β”œβ”€β”€ messageUtils.ts β”‚ β”‚ β”œβ”€β”€ validation.ts β”‚ β”‚ β”œβ”€β”€ messageTypeRegistry.ts # MessageRendererRegistry + helpers β”‚ β”‚ β”œβ”€β”€ clipboardUtils.ts β”‚ β”‚ β”œβ”€β”€ actionUtils.ts β”‚ β”‚ └── cn.ts # bem-react classname wrapper β”‚ β”‚ β”‚ β”œβ”€β”€ themes/ # Compiled CSS themes β”‚ β”‚ β”œβ”€β”€ common.css # Base CSS variables (always import) β”‚ β”‚ β”œβ”€β”€ light.css # [data-theme='light'] overrides β”‚ β”‚ β”œβ”€β”€ dark.css # [data-theme='dark'] overrides β”‚ β”‚ └── variables.css # Deprecated β€” use common.css β”‚ β”‚ β”‚ β”œβ”€β”€ server/ # Server-only code (Node.js) β”‚ β”‚ └── openai/ # OpenAIService β€” Responses API wrapper, streaming, summarization β”‚ β”‚ β”‚ └── index.ts # Main barrel export β”‚ β”œβ”€β”€ playwright/ # Docker-backed Playwright runner β”œβ”€β”€ test-utils/ # Shared test helpers β”œβ”€β”€ build-utils/ # Build scripts (gulp tasks) β”œβ”€β”€ build/ # Compiled output (esm/, cjs/, types) β”œβ”€β”€ llms.txt # LLM agent index (Mantine-style) β”œβ”€β”€ llms-full.txt # Concatenated docs for LLM context β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json β”œβ”€β”€ playwright-ct.config.ts β”œβ”€β”€ gulpfile.js β”œβ”€β”€ README.md / README-ru.md / CLAUDE.md / CONTRIBUTING.md └── .gitignore / .eslintrc / .prettierrc / … ``` ## Source Code: `src/` ### `src/components/` Components organized by Atomic Design level. Each component lives in its own directory and follows the structure: ``` ComponentName/ β”œβ”€β”€ ComponentName.tsx # Implementation β”œβ”€β”€ ComponentName.scss # Styles (optional) β”œβ”€β”€ types.ts # Component types (optional) β”œβ”€β”€ README.md # Public API documentation β”œβ”€β”€ i18n/ # Localization (optional) β”‚ β”œβ”€β”€ index.ts β”‚ β”œβ”€β”€ en.json β”‚ └── ru.json β”œβ”€β”€ __stories__/ β”‚ β”œβ”€β”€ ComponentName.stories.tsx β”‚ └── Docs.mdx β”œβ”€β”€ __tests__/ β”‚ β”œβ”€β”€ ComponentName.visual.spec.tsx β”‚ β”œβ”€β”€ helpersPlaywright.tsx β”‚ └── __snapshots__/ └── index.ts # Barrel export ``` Full component catalog: [COMPONENTS.md](./COMPONENTS.md). ### `src/hooks/` Public hooks exported through both the root entry (`from '@gravity-ui/aikit'`) and the subpath `from '@gravity-ui/aikit/hooks'`. Internal helpers like `useMarkdownTransform` and `useRemend` live in `src/hooks/` but are not re-exported. Full hooks reference: [HOOKS.md](./HOOKS.md). ### `src/types/` Type definitions split by concern: messages, chats, tool messages, common (action configs, suggestions). All re-exported from the root via `src/types/index.ts` and accessible as `import type {…} from '@gravity-ui/aikit'` or `from '@gravity-ui/aikit/types'`. ### `src/utils/` Each utility is exposed both via the root barrel and a dedicated subpath: `@gravity-ui/aikit/utils/chatUtils`, `/messageUtils`, `/validation`, `/messageTypeRegistry`, `/clipboardUtils`. ### `src/themes/` CSS theming files. Import via subpaths: ```typescript import '@gravity-ui/aikit/themes/common'; import '@gravity-ui/aikit/themes/light'; // or '/dark' ``` `variables.css` is deprecated β€” keep `common.css` only. ### `src/server/openai/` Server-side only code, builds into a separate CommonJS/ESM target via `tsconfig.{esm,cjs}.json` and is exposed as the subpath `@gravity-ui/aikit/server/openai`. Pulls in `openai` and `semver` from `optionalDependencies`. ## Naming Conventions - Component folders & files: `PascalCase` (e.g. `PromptInput/PromptInput.tsx`) - Hooks: `useThing.ts` (or `.tsx` when JSX is present) - Types files: `camelCase.ts` - Test files: `.visual.spec.tsx` (Playwright), `.unit.test.ts` (Jest) - Story files: `.stories.tsx` ## Export Hierarchy ``` ComponentName/index.ts β†’ src/components//index.ts β†’ src/components/index.ts (implicit via src/index.ts re-exports each level) β†’ src/index.ts ``` Each component additionally has a dedicated subpath in `package.json#exports` (`@gravity-ui/aikit/ComponentName`) for tree-shaken imports. ## Useful Links - [Quick Start](./GETTING_STARTED.md) - [Architecture](./ARCHITECTURE.md) - [Component Catalog](./COMPONENTS.md) - [Theming](./THEMING.md) - [Hooks](./HOOKS.md) - [Examples](./EXAMPLES.md) - [Troubleshooting](./TROUBLESHOOTING.md) - [AI Agent Integration](./AI_AGENTS.md) - [Testing Guide](./TESTING.md) - [Contributor Guidelines](./guidelines/)