UNPKG

@kitn.ai/chat

Version:

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

246 lines (200 loc) 8.9 kB
import { Meta } from '@storybook/addon-docs/blocks'; <Meta title="Docs/Frameworks/Angular" /> # Angular Angular needs **no wrappers** — its property binding (`[prop]="value"`) assigns the DOM **property** directly, so arrays and objects pass through unstringified. Register the elements once, allow the custom tags with `CUSTOM_ELEMENTS_SCHEMA`, and handle events with `(event)="handler($event)"`. There are **two ways to build with the kit**, and you can mix them: 1. **`<kc-chat>`** — the batteries-included shell: a whole chat experience in one tag. Fastest start. 2. **Compose the individual elements** (`<kc-conversations>`, `<kc-markdown>`, `<kc-artifact>`, …) into your own layout when you want full control. Both are shown below. ## Install & setup ```bash npm i @kitn.ai/chat ``` Register the custom elements once as a static side-effect import in `main.ts`, **before** `bootstrapApplication(...)`. Then add `CUSTOM_ELEMENTS_SCHEMA` to each standalone component that uses `<kc-*>` tags: ```ts // main.ts — import BEFORE bootstrapApplication so elements are defined first import '@kitn.ai/chat/elements'; import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; bootstrapApplication(AppComponent, appConfig); ``` ```ts // app.component.ts import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html', schemas: [CUSTOM_ELEMENTS_SCHEMA], // required: allows <kc-*> tags in the template }) export class AppComponent { /* … */ } ``` - **`@kitn.ai/chat/elements`** — the side-effect import that registers every `<kc-*>` element globally. Import it once, in `main.ts`. - **`CUSTOM_ELEMENTS_SCHEMA`** — tells the Angular compiler to accept unknown element names (the `<kc-*>` tags). Add it to every standalone component whose template uses them. - No CSS to import: each element is styled inside its own Shadow DOM. Only pull in `@kitn.ai/chat/theme.css` if you want to override design tokens (see **Theming**). ## Quick start — the all-in-one shell `<kc-chat>` is **transport-agnostic**: give it a `messages` array, handle the `submit` event, and stream your model's reply back into state. You own the request; the component owns the UI. ```ts // app.component.ts import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; type Message = { id: string; role: 'user' | 'assistant'; content: string }; @Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html', schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class AppComponent { messages: Message[] = [ { id: '1', role: 'assistant', content: 'Hello! How can I help?' }, ]; async onSubmit(e: Event) { const { value } = (e as CustomEvent<{ value: string }>).detail; // Reassign a NEW array so Angular's change detection picks it up. const history = [...this.messages, { id: crypto.randomUUID(), role: 'user' as const, content: value }]; this.messages = history; const aid = crypto.randomUUID(); this.messages = [...history, { id: aid, role: 'assistant' as const, content: '' }]; let answer = ''; for await (const token of streamFromYourAPI(history)) { answer += token; // Reassign a new array — and a new object for the message being streamed. this.messages = this.messages.map((m) => m.id === aid ? { ...m, content: answer } : m ); } } } ``` ```html <!-- app.component.html --> <!-- The elements are display:block and fill their container — lay them out with flex and let the element grow with flex:1 rather than hard-coding a height. --> <div style="display: flex; flex-direction: column; height: 100dvh"> <kc-chat [messages]="messages" (kc-submit)="onSubmit($event)" style="flex: 1; min-height: 0" ></kc-chat> </div> ``` ## Go further — compose the pieces `<kc-chat>` is one option, not the only one. You can assemble your own layout from individual elements. Here is a multi-conversation shell — a `<kc-conversations>` sidebar next to the `<kc-chat>` thread: ```ts // workspace.component.ts import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @Component({ selector: 'app-workspace', standalone: true, templateUrl: './workspace.component.html', schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class WorkspaceComponent { conversations = myConversations; activeId = this.conversations[0]?.id; messages = loadMessages(this.activeId); onConversationSelect(e: Event) { this.activeId = (e as CustomEvent<{ id: string }>).detail.id; this.messages = loadMessages(this.activeId); } onNewChat() { startNewConversation(); } onSubmit(e: Event) { const { value } = (e as CustomEvent<{ value: string }>).detail; sendMessage(value); } } ``` ```html <!-- workspace.component.html --> <!-- Lay panels out with flex: the sidebar is fixed-width, the thread takes the rest with flex:1. The elements fill whatever box you give them. --> <div style="display: flex; height: 100dvh"> <kc-conversations [conversations]="conversations" [activeId]="activeId" (kc-conversation-select)="onConversationSelect($event)" (kc-new-chat)="onNewChat()" style="width: 300px; flex-shrink: 0" ></kc-conversations> <kc-chat [messages]="messages" (kc-submit)="onSubmit($event)" style="flex: 1; min-width: 0" ></kc-chat> </div> ``` ### Make the panels resizable Want a draggable divider between the sidebar and the thread? Wrap the panels in `<kc-resizable>` with one `<kc-resizable-item>` each — the handles are inserted for you (up to 3 panels). Each item takes a `size` (px or `%`) plus optional `min`/`max`; listen for `(change)` (`$event.detail.sizes`) to persist the layout. ```html <div style="display: flex; flex-direction: column; height: 100dvh"> <kc-resizable orientation="horizontal" (kc-change)="onResize($event)" style="flex: 1; min-height: 0" > <kc-resizable-item size="25%" min="200px"> <kc-conversations [conversations]="conversations" [activeId]="activeId" (kc-conversation-select)="onConversationSelect($event)" ></kc-conversations> </kc-resizable-item> <kc-resizable-item> <kc-chat [messages]="messages" (kc-submit)="onSubmit($event)"></kc-chat> </kc-resizable-item> </kc-resizable> </div> ``` ```ts onResize(e: Event) { const { sizes } = (e as CustomEvent<{ sizes: string[] }>).detail; // persist sizes to localStorage, a service, etc. console.log('panel sizes:', sizes); } ``` You can also drop **standalone display elements** anywhere in your own UI — `<kc-markdown>`, `<kc-code-block>`, `<kc-artifact>`, `<kc-reasoning>`, `<kc-tool>` — to render rich AI content without adopting the whole chat. Each fills its container and is controlled via `[prop]` bindings and `(event)` listeners. > **See it all assembled:** **[Examples → Full Chat App](?path=/story/examples-full-chat-app--default)** > wires a sidebar, threaded markdown, reasoning, a tool call, a model switcher, a context meter, and > a rich prompt input into one screen — a working reference to crib from. > **Find every element:** browse the **Components** section in the sidebar. Each element's **API** > tab lists its props, events, and copy-paste usage for Angular (and every other framework). ## Props & events The rule for all elements: **rich data goes in as properties, interactions come out as events.** Angular's `[prop]="…"` binding writes to the element's DOM property (not an HTML attribute), so arrays and objects pass through unstringified — no adapter or wrapper needed. `(event)="handler($event)"` listens for a DOM CustomEvent. `$event` is the `CustomEvent` itself; read the payload from `$event.detail`: | Element | Property binding | Event | `$event.detail` | |---|---|---|---| | `kc-chat` | `[messages]="messages"` | `(submit)` | `{ value: string }` | | `kc-conversations` | `[conversations]="…"` | `(conversationselect)` | `{ id: string }` | | `kc-conversations` | `[groups]="…"` | `(newchat)` | — | | `kc-conversations` | — | `(togglesidebar)` | — | | `kc-resizable` | `orientation="horizontal"` | `(change)` | `{ sizes: string[] }` | | `kc-resizable-item` | `size="25%"` `min="200px"` `max="…"` | — | — | > **Scalar attributes** like `orientation="horizontal"` or `theme="dark"` can be plain HTML > attributes (no brackets). Use `[prop]="…"` only when passing non-string values (arrays, objects, > booleans) or Angular expressions. > **`CUSTOM_ELEMENTS_SCHEMA` is required** in every standalone component (or NgModule) whose > template contains `<kc-*>` tags. Without it, the Angular compiler rejects unknown element names at > build time.