@djangocfg/ui-tools 2.1.384 → 2.1.387

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +25 -11
  2. package/dist/ChatRoot-4KM2JMGA.mjs +6 -0
  3. package/dist/{ChatRoot-JVR3M3H2.mjs.map → ChatRoot-4KM2JMGA.mjs.map} +1 -1
  4. package/dist/ChatRoot-OILWMMZ6.cjs +15 -0
  5. package/dist/{ChatRoot-LXIUBOXF.cjs.map → ChatRoot-OILWMMZ6.cjs.map} +1 -1
  6. package/dist/DictationField-AS2F33WI.cjs +13 -0
  7. package/dist/{DictationField-U25MEYAL.mjs.map → DictationField-AS2F33WI.cjs.map} +1 -1
  8. package/dist/DictationField-WPONUCYE.mjs +4 -0
  9. package/dist/{DictationField-XWR5VOID.cjs.map → DictationField-WPONUCYE.mjs.map} +1 -1
  10. package/dist/MapContainer-AKIPABJK.mjs +4 -0
  11. package/dist/MapContainer-AKIPABJK.mjs.map +1 -0
  12. package/dist/MapContainer-STVDMC36.cjs +17 -0
  13. package/dist/MapContainer-STVDMC36.cjs.map +1 -0
  14. package/dist/{MapContainer-76YL2JXL.cjs → chunk-5D2OCOPQ.cjs} +3 -2
  15. package/dist/chunk-5D2OCOPQ.cjs.map +1 -0
  16. package/dist/{MapContainer-7HXBI3OH.mjs → chunk-7CWGZPO3.mjs} +3 -3
  17. package/dist/chunk-7CWGZPO3.mjs.map +1 -0
  18. package/dist/{chunk-4PFW7MIJ.cjs → chunk-ADEN3UA4.cjs} +60 -5
  19. package/dist/chunk-ADEN3UA4.cjs.map +1 -0
  20. package/dist/chunk-BVESQTBM.mjs +1439 -0
  21. package/dist/chunk-BVESQTBM.mjs.map +1 -0
  22. package/dist/{chunk-PEKBT75W.mjs → chunk-DMX7W4XZ.mjs} +53 -1387
  23. package/dist/chunk-DMX7W4XZ.mjs.map +1 -0
  24. package/dist/chunk-HNIMIIFR.mjs +1361 -0
  25. package/dist/chunk-HNIMIIFR.mjs.map +1 -0
  26. package/dist/chunk-L25HA3TM.cjs +1478 -0
  27. package/dist/chunk-L25HA3TM.cjs.map +1 -0
  28. package/dist/{chunk-HPK3EWBF.cjs → chunk-TBSHZO5R.cjs} +50 -1409
  29. package/dist/chunk-TBSHZO5R.cjs.map +1 -0
  30. package/dist/chunk-TSNRU3UO.cjs +1387 -0
  31. package/dist/chunk-TSNRU3UO.cjs.map +1 -0
  32. package/dist/{chunk-C2YN6WEO.mjs → chunk-UNCS5V5F.mjs} +61 -7
  33. package/dist/chunk-UNCS5V5F.mjs.map +1 -0
  34. package/dist/index.cjs +1236 -1768
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +780 -780
  37. package/dist/index.d.ts +780 -780
  38. package/dist/index.mjs +853 -1423
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/launcher-5WYPDPEP.mjs +7 -0
  41. package/dist/launcher-5WYPDPEP.mjs.map +1 -0
  42. package/dist/launcher-FCI3LTDY.css +7 -0
  43. package/dist/launcher-FCI3LTDY.css.map +1 -0
  44. package/dist/launcher-QAOG2NUI.cjs +60 -0
  45. package/dist/launcher-QAOG2NUI.cjs.map +1 -0
  46. package/package.json +23 -18
  47. package/src/tools/AudioPlayer/lazy.tsx +100 -0
  48. package/src/tools/Chat/README.md +85 -1
  49. package/src/tools/Chat/context/ChatProvider.tsx +42 -0
  50. package/src/tools/Chat/lazy.tsx +213 -1
  51. package/src/tools/CodeEditor/lazy.tsx +70 -0
  52. package/src/tools/Map/lazy.tsx +38 -1
  53. package/src/tools/MarkdownEditor/lazy.tsx +42 -0
  54. package/src/tools/SpeechRecognition/README.md +48 -0
  55. package/src/tools/SpeechRecognition/core/index.ts +6 -1
  56. package/src/tools/SpeechRecognition/core/logger.ts +107 -1
  57. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +15 -4
  58. package/src/tools/SpeechRecognition/index.ts +9 -0
  59. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +37 -2
  60. package/dist/ChatRoot-JVR3M3H2.mjs +0 -5
  61. package/dist/ChatRoot-LXIUBOXF.cjs +0 -14
  62. package/dist/DictationField-U25MEYAL.mjs +0 -4
  63. package/dist/DictationField-XWR5VOID.cjs +0 -13
  64. package/dist/MapContainer-76YL2JXL.cjs.map +0 -1
  65. package/dist/MapContainer-7HXBI3OH.mjs.map +0 -1
  66. package/dist/chunk-4PFW7MIJ.cjs.map +0 -1
  67. package/dist/chunk-C2YN6WEO.mjs.map +0 -1
  68. package/dist/chunk-HPK3EWBF.cjs.map +0 -1
  69. package/dist/chunk-PEKBT75W.mjs.map +0 -1
package/dist/index.d.ts CHANGED
@@ -2249,815 +2249,813 @@ interface ChatRootProps {
2249
2249
  }
2250
2250
  declare function ChatRoot(props: ChatRootProps): react_jsx_runtime.JSX.Element;
2251
2251
 
2252
- declare const LazyChat: react.ComponentType<ChatRootProps>;
2253
-
2254
- /**
2255
- * Chat defaults and constants.
2256
- */
2257
- declare const STORAGE_KEYS: {
2258
- readonly mode: "djc-chat-mode";
2259
- readonly sidebarWidth: "djc-chat-sidebar-width";
2260
- readonly composerHistory: "djc-chat-composer-history";
2261
- };
2262
- declare const CSS_VARS: {
2263
- readonly reserve: "--djc-chat-reserve";
2264
- };
2265
- declare const DEFAULT_Z_INDEX = 9000;
2266
- declare const LIMITS: {
2267
- /** Max characters per single message. */
2268
- readonly messageMaxLength: 8000;
2269
- /** Max attachments per message. */
2270
- readonly attachmentsMax: 10;
2271
- /** Composer history slots. */
2272
- readonly composerHistorySize: 50;
2273
- /** Coalesce stream tokens within this window before dispatching. */
2274
- readonly streamCoalesceMs: 16;
2275
- /** Default history page size. */
2276
- readonly pageSize: 50;
2277
- /** Virtualize list when >= this many messages (host-controlled threshold). */
2278
- readonly virtualizeThreshold: 50;
2279
- /** SSE idle timeout. */
2280
- readonly sseIdleMs: 45000;
2281
- };
2282
- declare const DEFAULT_SIDEBAR: {
2283
- readonly width: 420;
2284
- readonly min: 320;
2285
- readonly max: 720;
2286
- };
2287
- declare const HOTKEYS: {
2288
- readonly send: "mod+enter";
2289
- readonly cancel: "esc";
2290
- readonly newChat: "mod+shift+n";
2291
- readonly toggleOpen: "mod+/";
2292
- readonly focusComposer: "mod+l";
2293
- };
2294
- declare const CHAT_EVENT_NAME = "djc:chat:send";
2295
- interface ChatEventDetail {
2296
- content: string;
2297
- sessionId?: string;
2298
- attachments?: unknown[];
2299
- metadata?: Record<string, unknown>;
2252
+ type ChatFABPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
2253
+ type ChatFABVariant = 'simple' | 'animated' | 'glass';
2254
+ type ChatFABSize = 'sm' | 'md' | 'lg' | 'responsive';
2255
+ interface ChatFABProps {
2256
+ /** Click handler — typically toggles a `ChatDock`. */
2257
+ onClick: () => void;
2258
+ /** Accessible label. */
2259
+ ariaLabel?: string;
2260
+ /** Icon inside the FAB. Defaults to a bot glyph. */
2261
+ icon?: ReactNode;
2262
+ /** Visual style. @default 'simple' */
2263
+ variant?: ChatFABVariant;
2264
+ /** Button size. @default 'md' */
2265
+ size?: ChatFABSize;
2266
+ /** Fixed-screen position. @default 'bottom-right' */
2267
+ position?: ChatFABPosition;
2268
+ /** Pixel offset from screen edges. @default 24 */
2269
+ offset?: number;
2270
+ /** z-index for the button. @default 9999 */
2271
+ zIndex?: number;
2272
+ /** Show a small attention dot (unread / new). */
2273
+ pulse?: boolean;
2274
+ /**
2275
+ * Numeric badge unread count. Numbers > 9 render as "9+".
2276
+ * Overrides `pulse` when both set.
2277
+ */
2278
+ badge?: number;
2279
+ /** Hover tooltip text. Shows next to the FAB on hover/focus. */
2280
+ tooltip?: string;
2281
+ /**
2282
+ * Render in-place (no fixed positioning) so the FAB sits inline in the
2283
+ * normal document flow. Useful for stories, screenshots, and previews
2284
+ * inside a contained playground panel. @default false
2285
+ */
2286
+ inline?: boolean;
2287
+ /** Override classes on the button itself. */
2288
+ className?: string;
2289
+ /** Extra style on the button (caller-controlled overrides). */
2290
+ style?: CSSProperties;
2300
2291
  }
2301
-
2302
2292
  /**
2303
- * ID generation. Uses crypto.randomUUID when available with a fallback
2304
- * for older environments (and SSR bundles that may not have crypto).
2293
+ * Floating action button for opening a chat dock. Pure presentation owns
2294
+ * no state. Wire it to whatever toggles your dock open.
2295
+ *
2296
+ * For the common "FAB + dock + hotkey" composition, use `<ChatLauncher>` —
2297
+ * this primitive is only useful when you need custom triggering.
2305
2298
  */
2306
- declare function createId(prefix?: string): string;
2299
+ declare function ChatFAB({ onClick, ariaLabel, icon, variant, size, position, offset, zIndex, pulse, badge, tooltip, inline, className, style, }: ChatFABProps): react_jsx_runtime.JSX.Element;
2307
2300
 
2308
- /**
2309
- * Token coalescer. Buffers stream tokens within a small time window before
2310
- * dispatching a single aggregated chunk. Prevents 60+ re-renders per second
2311
- * on fast streams.
2312
- */
2313
- interface TokenBuffer {
2314
- /** Append a delta. Returns immediately. */
2315
- push(delta: string): void;
2316
- /** Force flush and resolve any pending timer. */
2317
- flush(): void;
2318
- /** Stop accepting tokens; flush whatever is buffered. */
2319
- close(): void;
2301
+ type ChatDockMode = 'popover' | 'side';
2302
+ type ChatDockSide = 'left' | 'right';
2303
+ interface ChatDockProps {
2304
+ /** Controlled open state. */
2305
+ open: boolean;
2306
+ /** Called when the user clicks the close button. */
2307
+ onClose: () => void;
2308
+ /** Dock contents (typically a `<Chat>` component). */
2309
+ children: ReactNode;
2310
+ /**
2311
+ * Visual mode.
2312
+ * - `popover` (default): floating card anchored to a corner, fixed size, FAB-style.
2313
+ * - `side`: docked panel pinned to the left/right edge, full viewport height.
2314
+ */
2315
+ mode?: ChatDockMode;
2316
+ /** Side for `mode='side'`. @default 'right' */
2317
+ side?: ChatDockSide;
2318
+ /** Header title text. */
2319
+ title?: ReactNode;
2320
+ /** Header icon. Defaults to a bot glyph. */
2321
+ icon?: ReactNode;
2322
+ /**
2323
+ * Header actions slot (right side, before the close button).
2324
+ * Use `ChatHeaderActionButton` to keep visual consistency.
2325
+ */
2326
+ headerActions?: ReactNode;
2327
+ /** Hide the header entirely (you render your own inside `children`). */
2328
+ hideHeader?: boolean;
2329
+ /** ARIA label for the close button. @default 'Close' */
2330
+ closeLabel?: string;
2331
+ /** Dock width in px. Clamped to viewport. @default 480 (popover) / 420 (side) */
2332
+ width?: number;
2333
+ /** Dock height in px. Only used in `popover` mode. @default 720 */
2334
+ height?: number;
2335
+ /** Which screen corner to dock to in `popover` mode. @default 'bottom-right' */
2336
+ position?: ChatFABPosition;
2337
+ /** Offset from screen edges in px (popover only). @default 24 / 96 */
2338
+ offset?: {
2339
+ horizontal?: number;
2340
+ vertical?: number;
2341
+ };
2342
+ /** Transition duration in ms — should match CSS animation. @default 200 */
2343
+ exitDurationMs?: number;
2344
+ /** z-index. @default 10000 */
2345
+ zIndex?: number;
2346
+ /** Accessible dialog label. */
2347
+ ariaLabel?: string;
2348
+ /** Extra classes on the dock container. */
2349
+ className?: string;
2350
+ /**
2351
+ * Take over the full viewport on mobile (< 768px). Applies to both modes.
2352
+ * @default true
2353
+ */
2354
+ mobileFullscreen?: boolean;
2355
+ /**
2356
+ * Render in-place (not in `document.body` via a portal). Useful for stories,
2357
+ * screenshots, or wrapping the dock inside a custom container. @default false
2358
+ */
2359
+ disablePortal?: boolean;
2360
+ /**
2361
+ * Drop fixed positioning entirely — the dock renders as a normal flow
2362
+ * element sized by `width`/`height`. Combine with `disablePortal` for
2363
+ * stories/previews where the dock should sit inside the panel instead
2364
+ * of attaching to the viewport. @default false
2365
+ */
2366
+ inline?: boolean;
2367
+ /**
2368
+ * In `mode='side'`, reserve space on the document body so page content
2369
+ * isn't covered by the dock. Sets `padding-{side}` on `<body>` while
2370
+ * the dock is open and exposes the width via the `--chat-dock-reserve`
2371
+ * CSS variable for custom layouts. @default true (when mode='side')
2372
+ */
2373
+ reserveBodySpace?: boolean;
2320
2374
  }
2321
- declare function createTokenBuffer(onFlush: (delta: string) => void, windowMs?: 16): TokenBuffer;
2322
-
2323
- declare function resolvePersona(message: Pick<ChatMessage, 'role' | 'sender'>, user?: ChatUserContext, assistant?: ChatAssistantContext): ChatPersona;
2324
- /** Compute initials for an avatar fallback. */
2325
- declare function deriveInitials(persona: ChatPersona, role?: string): string;
2326
-
2327
2375
  /**
2328
- * HTTP + SSE transport. Default implementation for web hosts.
2376
+ * Fixed-position chat surface. Two modes:
2329
2377
  *
2330
- * Backend contract (see @dev/@refactoring7-chat/06-integration.md):
2331
- * POST /sessions → SessionInfo (JSON)
2332
- * GET /sessions/:id/history?cursor= → HistoryPage (JSON)
2333
- * POST /sessions/:id/messages → SSE stream of ChatStreamEvent
2334
- * POST /sessions/:id/messages/buffered ChatMessage (JSON, fallback)
2335
- * DELETE /sessions/:id → 204
2378
+ * - `popover` floating card anchored to a corner. Companion to `<ChatFAB>`.
2379
+ * - `side` — full-height panel pinned to the left/right edge. App-shell style.
2380
+ *
2381
+ * Renders only when `open` is true (plus the leave-transition tail). Uses
2382
+ * `useChatPresence` for the four-phase mount/animate/unmount cycle.
2336
2383
  */
2384
+ declare function ChatDock({ open, onClose, children, mode, side, title, icon, headerActions, hideHeader, closeLabel, width, height, position, offset, exitDurationMs, zIndex, ariaLabel, className, mobileFullscreen, disablePortal, inline, reserveBodySpace, }: ChatDockProps): react_jsx_runtime.JSX.Element;
2337
2385
 
2338
- interface HttpTransportConfig {
2339
- /** Base URL without trailing slash, e.g. '/api/chat' or 'https://api.example.com/v1/chat'. */
2340
- baseUrl: string;
2341
- /** Optional slug appended/forwarded as project identifier. */
2342
- slug?: string;
2343
- /** Returns headers applied to every request — e.g. Authorization. */
2344
- getAuthHeader?: () => Record<string, string> | Promise<Record<string, string>>;
2345
- /** Default fetch timeout (per non-streaming request). */
2346
- timeoutMs?: number;
2347
- /** Override fetch implementation (useful for tests or custom retry layers). */
2348
- fetchImpl?: typeof fetch;
2386
+ interface ChatHeaderProps {
2387
+ /** Window title text. */
2388
+ title?: ReactNode;
2389
+ /** Icon next to the title. Defaults to a bot glyph. */
2390
+ icon?: ReactNode;
2391
+ /**
2392
+ * Action slot appears to the right of the title, before the close button.
2393
+ * Use for reset / settings / minimize / etc. Compose with `ChatHeaderActionButton`.
2394
+ */
2395
+ actions?: ReactNode;
2396
+ /** Show the close (×) button. @default true */
2397
+ showClose?: boolean;
2398
+ /** Close click handler. */
2399
+ onClose?: () => void;
2400
+ /** ARIA label for the close button. @default 'Close' */
2401
+ closeLabel?: string;
2402
+ /** Replace the close button entirely (rare — most hosts want the default). */
2403
+ closeSlot?: ReactNode;
2404
+ /** Extra classes on the `<header>` element. */
2405
+ className?: string;
2349
2406
  }
2350
- declare function createHttpTransport(config: HttpTransportConfig): ChatTransport;
2351
-
2352
2407
  /**
2353
- * In-memory chat transport for stories and tests. Replays scripted replies.
2408
+ * Standalone chat header title + icon + action slot + close button.
2409
+ *
2410
+ * Used by `<ChatDock>` automatically, but can be rendered standalone when
2411
+ * the host owns the chat container (e.g. embedded inline in a page).
2354
2412
  */
2413
+ declare function ChatHeader({ title, icon, actions, showClose, onClose, closeLabel, closeSlot, className, }: ChatHeaderProps): react_jsx_runtime.JSX.Element;
2355
2414
 
2356
- interface MockTransportOptions {
2357
- /** Each entry is the assistant's reply for one user turn. Strings are split
2358
- * into chunks; arrays are taken as the exact event sequence (after a
2359
- * prepended `message_start` and before a synthetic `message_end`). */
2360
- replies?: Array<string | ChatStreamEvent[]>;
2361
- latencyMs?: number;
2362
- /** Initial history returned by `createSession`. */
2363
- initialMessages?: ChatMessage[];
2364
- shouldFail?: (attempt: number) => boolean;
2415
+ interface ChatHeaderActionButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
2416
+ /** Icon (required). */
2417
+ icon: ReactNode;
2418
+ /** Accessible label + native tooltip. */
2419
+ ariaLabel: string;
2420
+ /** Optional unread / status badge — small number on top-right. */
2421
+ badge?: number;
2422
+ /** Mark as destructive — uses destructive hover tokens. */
2423
+ destructive?: boolean;
2424
+ /** Optional visual loading state (e.g. while reset is in flight). */
2425
+ loading?: boolean;
2365
2426
  }
2366
- declare function createMockTransport(opts?: MockTransportOptions): ChatTransport;
2367
-
2368
2427
  /**
2369
- * Server-Sent Events parser as an AsyncGenerator.
2428
+ * Compact icon button for the chat header actions slot.
2370
2429
  *
2371
- * Yields parsed events from a `Response` body. Handles the split-read case
2372
- * where `event:` and `data:` arrive in separate TCP packets. Skips malformed
2373
- * JSON gracefully. Honors AbortSignal (caller passes one to fetch).
2430
+ * Standard chrome: 28×28 ghost button, hover bg-accent, focus ring, optional
2431
+ * destructive variant, optional numeric badge for unread / pending.
2432
+ *
2433
+ * @example
2434
+ * ```tsx
2435
+ * <ChatHeader
2436
+ * title="Assistant"
2437
+ * onClose={close}
2438
+ * actions={
2439
+ * <>
2440
+ * <ChatHeaderActionButton
2441
+ * icon={<RotateCcw className="h-3.5 w-3.5" />}
2442
+ * ariaLabel="Clear context"
2443
+ * onClick={handleReset}
2444
+ * loading={isResetting}
2445
+ * />
2446
+ * <ChatHeaderActionButton
2447
+ * icon={<Settings className="h-3.5 w-3.5" />}
2448
+ * ariaLabel="Settings"
2449
+ * onClick={openSettings}
2450
+ * />
2451
+ * </>
2452
+ * }
2453
+ * />
2454
+ * ```
2374
2455
  */
2456
+ declare const ChatHeaderActionButton: react.ForwardRefExoticComponent<ChatHeaderActionButtonProps & react.RefAttributes<HTMLButtonElement>>;
2375
2457
 
2376
- interface RawEvent {
2377
- event?: string;
2378
- data?: string;
2379
- }
2380
- interface ParseSSEOptions {
2381
- signal?: AbortSignal;
2382
- /** Map a raw SSE event to zero, one, or many `ChatStreamEvent`s.
2383
- * Default: parse `data` as JSON and assume the JSON shape already
2384
- * matches `ChatStreamEvent`. */
2385
- map?: (raw: RawEvent) => ChatStreamEvent | ChatStreamEvent[] | null;
2386
- idleTimeoutMs?: number;
2458
+ interface ChatHeaderModeToggleProps {
2459
+ /** Current dock mode. */
2460
+ mode: ChatDockMode;
2461
+ /** Toggle handler. Wire to `useChatDockPrefs().toggleMode`. */
2462
+ onToggle: () => void;
2463
+ /** Override aria/tooltip label for popover→side. */
2464
+ expandLabel?: string;
2465
+ /** Override aria/tooltip label for side→popover. */
2466
+ collapseLabel?: string;
2467
+ /**
2468
+ * Always render — useful for stories. By default the toggle hides itself on
2469
+ * viewports below `lg` (1024px) since side mode falls back to popover there.
2470
+ */
2471
+ forceVisible?: boolean;
2387
2472
  }
2388
- declare function parseSSE(response: Response, options?: ParseSSEOptions): AsyncGenerator<ChatStreamEvent, void, void>;
2389
-
2390
2473
  /**
2391
- * Transport surface re-export. Lives in core so transport implementations
2392
- * never need to reach into the public types module.
2474
+ * "Dock to side" / "back to popover" toggle.
2475
+ *
2476
+ * Side mode is desktop-only — on viewports below `lg` (1024px) `<ChatDock>`
2477
+ * silently falls back to popover anyway, so the toggle has nothing to do
2478
+ * and auto-hides. Pass `forceVisible` to override (e.g. in stories).
2393
2479
  */
2480
+ declare function ChatHeaderModeToggle({ mode, onToggle, expandLabel, collapseLabel, forceVisible, }: ChatHeaderModeToggleProps): react_jsx_runtime.JSX.Element;
2394
2481
 
2395
- declare class TransportError extends Error {
2396
- code: string;
2397
- constructor(message: string, code?: string);
2482
+ interface ChatHeaderAudioToggleProps {
2483
+ /** Current muted state. */
2484
+ muted: boolean;
2485
+ /** Toggle handler. Wire to `useChatAudio().setMuted` or `toggleMute`. */
2486
+ onToggle: () => void;
2487
+ /** Override tooltip label for muted → unmuted. */
2488
+ unmuteLabel?: string;
2489
+ /** Override tooltip label for unmuted → muted. */
2490
+ muteLabel?: string;
2398
2491
  }
2399
-
2400
2492
  /**
2401
- * Pydantic-AI SSE event mapper.
2402
- *
2403
- * Translates the event shape emitted by pydantic-AI–style Django backends
2404
- * (text_delta / tool_call / tool_result / done / error / approval_required)
2405
- * into the canonical `ChatStreamEvent` stream consumed by the Chat reducer.
2493
+ * Mute / unmute notification sounds. Drop into a `<ChatHeader>` actions
2494
+ * slot or into `<ChatDock headerActions>`.
2406
2495
  *
2407
- * Backends that don't expose a stable `tool_call_id` are supported via a
2408
- * per-stream FIFO queue keyed by tool name. The earlier "Map<name, toolId>"
2409
- * approach lost the first toolId when a tool was invoked twice in one turn
2410
- * (e.g. `list_tasks` for search and again for confirmation) — using a queue
2411
- * keeps each call/result pair correctly matched.
2496
+ * @example
2497
+ * ```tsx
2498
+ * const audio = useChatAudio({ sounds: {...} });
2499
+ * <ChatHeaderAudioToggle muted={audio.muted} onToggle={() => audio.setMuted(!audio.muted)} />
2500
+ * ```
2412
2501
  */
2502
+ declare function ChatHeaderAudioToggle({ muted, onToggle, unmuteLabel, muteLabel, }: ChatHeaderAudioToggleProps): react_jsx_runtime.JSX.Element;
2413
2503
 
2414
- interface PydanticAIEvent {
2415
- type: 'text_delta' | 'tool_call' | 'tool_result' | 'done' | 'error' | 'approval_required';
2416
- delta?: string;
2417
- tool?: string;
2418
- args?: unknown;
2419
- result?: unknown;
2504
+ interface ChatHeaderResetButtonProps {
2420
2505
  /**
2421
- * Structured frontend payload present on `tool_result` when the tool has
2422
- * a `result_schema`. The LLM sees only `result` (compact text); the
2423
- * frontend uses `data` to render rich UI (e.g. vehicle cards).
2506
+ * Backend reset call. Should resolve to `true` on success.
2507
+ * Plugged into `useChatReset` for in-flight state.
2424
2508
  */
2425
- data?: unknown;
2426
- total_tokens?: number;
2427
- error?: string;
2428
- tool_call_id?: string;
2429
- session_id?: string;
2430
- }
2431
- /** Per-stream FIFO queue keyed by tool name. Created via `createToolIdQueue`. */
2432
- interface ToolIdQueue {
2433
- /** Allocate a new toolId for a `tool_call` event and enqueue it under `name`. */
2434
- push(name: string): string;
2435
- /** Pop the oldest toolId for `name` (or return an orphan marker if none). */
2436
- shift(name: string): string;
2437
- /** Reset all queues (e.g. on stream close). */
2438
- clear(): void;
2509
+ onReset: () => Promise<boolean>;
2510
+ /** Called after a successful reset (e.g. clear local messages, refetch). */
2511
+ onSuccess?: () => void;
2512
+ /** Called when reset fails (returned `false` or threw). */
2513
+ onError?: (error?: unknown) => void;
2514
+ /**
2515
+ * Show a `window.dialog.confirm` before calling `onReset`. @default true
2516
+ *
2517
+ * Requires the host to mount `<DialogProvider>` from `@djangocfg/ui-core`
2518
+ * it installs the `window.dialog` API used here.
2519
+ */
2520
+ confirm?: boolean;
2521
+ /** Confirm dialog title. */
2522
+ confirmTitle?: string;
2523
+ /** Confirm dialog message. */
2524
+ confirmMessage?: string;
2525
+ /** Override tooltip / aria label. */
2526
+ ariaLabel?: string;
2439
2527
  }
2440
- declare function createToolIdQueue(): ToolIdQueue;
2441
- /**
2442
- * Translate a single pydantic-AI event into zero or more `ChatStreamEvent`s.
2443
- * Pass a `ToolIdQueue` shared across the lifetime of one stream so that
2444
- * `tool_call` / `tool_result` pairs match correctly.
2445
- */
2446
- declare function mapPydanticAIEvent(ev: PydanticAIEvent, toolIds: ToolIdQueue): Generator<ChatStreamEvent>;
2447
2528
  /**
2448
- * Convenience factory: returns a `ParseSSEOptions['map']` callback that
2449
- * decodes raw SSE frames as `PydanticAIEvent` JSON and yields zero or
2450
- * more `ChatStreamEvent`s through `mapPydanticAIEvent`.
2529
+ * Standard chat-reset action: prompts the user via `window.dialog.confirm`,
2530
+ * then runs the backend reset call through `useChatReset` so the button
2531
+ * spins while it's in flight.
2451
2532
  *
2452
- * Allocates an internal `ToolIdQueue` — call this once per stream.
2533
+ * @example
2534
+ * ```tsx
2535
+ * <ChatHeaderResetButton
2536
+ * onReset={api.clearChat}
2537
+ * onSuccess={() => chat.clearMessages()}
2538
+ * />
2539
+ * ```
2453
2540
  */
2454
- declare function createPydanticAISSEMap(): NonNullable<ParseSSEOptions['map']>;
2541
+ declare function ChatHeaderResetButton({ onReset, onSuccess, onError, confirm, confirmTitle, confirmMessage, ariaLabel, }: ChatHeaderResetButtonProps): react_jsx_runtime.JSX.Element;
2455
2542
 
2543
+ interface ChatHeaderLanguageButtonProps {
2544
+ /** Override aria-label. Default "Speech language". */
2545
+ ariaLabel?: string;
2546
+ /**
2547
+ * Subset of BCP-47 tags to offer. Default: every entry from the
2548
+ * Web Speech catalogue (~66 tags incl. regional variants). Pass a
2549
+ * tighter list when your backend STT only supports a subset.
2550
+ */
2551
+ allowedTags?: string[];
2552
+ /** Hide the globe-fallback icon when no flag resolves. */
2553
+ hideFallbackIcon?: boolean;
2554
+ className?: string;
2555
+ }
2456
2556
  /**
2457
- * High-level transport factory for pydantic-AI–style backends.
2458
- *
2459
- * Composes:
2460
- * - `parseSSE` for spec-compliant SSE framing (multi-line `data:`, comments, idle timeout).
2461
- * - `createPydanticAISSEMap` for normalizing pydantic-AI events into `ChatStreamEvent`.
2557
+ * Compact flag-button language picker for the chat header. Built on
2558
+ * top of the ui-core `<Combobox>` — searchable autocomplete with
2559
+ * flags, ~66 BCP-47 tags from the official Chrome Web Speech demo, and
2560
+ * a custom 28×28 trigger via `renderTrigger`.
2462
2561
  *
2463
- * Use when your backend speaks the canonical pydantic-AI stream shape
2464
- * (`text_delta` / `tool_call` / `tool_result` / `done` / `error`).
2465
- * URL building, auth headers, history loading, and optional session
2466
- * bootstrapping are caller responsibilities — pass them in.
2562
+ * The selection persists into `useSpeechPrefs` (zustand+localStorage)
2563
+ * so `useSpeechRecognition` picks it up as the second-priority resolver
2564
+ * value (above i18n locale, below an explicit `language` prop).
2467
2565
  */
2566
+ declare function ChatHeaderLanguageButton({ ariaLabel, allowedTags, hideFallbackIcon, className, }: ChatHeaderLanguageButtonProps): react.ReactElement;
2468
2567
 
2469
- interface PydanticAIChatTransportOpts {
2470
- /**
2471
- * Build the SSE stream URL for a user message turn.
2472
- * @example (sessionId, message) => `${base}/stream?session_id=${sessionId}&message=${encodeURIComponent(message)}`
2473
- */
2474
- buildStreamUrl: (sessionId: string, message: string) => string | URL;
2475
- /** Optional history loader. If omitted, `loadHistory` returns an empty page. */
2476
- loadHistory?: (sessionId: string, cursor?: string | null) => Promise<HistoryPage>;
2477
- /**
2478
- * Optional session bootstrap. Called from `createSession`. Useful for
2479
- * backends that need a `POST /sessions` round-trip or want to pre-seed
2480
- * history.
2481
- */
2482
- bootstrapSession?: (opts?: CreateSessionOptions) => Promise<SessionInfo>;
2483
- /** Optional session teardown. */
2484
- closeSession?: (sessionId: string) => Promise<void>;
2568
+ interface ChatGreetingProps {
2569
+ /** Controlled visibility — usually `!chatOpen && !userDismissed`. */
2570
+ open: boolean;
2571
+ /** Greeting text. Pass a string for the default bubble, or any ReactNode. */
2572
+ children: ReactNode;
2573
+ /** Click handler typically opens the chat. Bubble is clickable when set. */
2574
+ onClick?: () => void;
2575
+ /** Close (×) button handler typically marks the greeting as dismissed. */
2576
+ onDismiss?: () => void;
2577
+ /** Anchor relative to a FAB on the same side. @default 'bottom-right' */
2578
+ position?: ChatFABPosition;
2485
2579
  /**
2486
- * Optional non-streaming send (for hosts that need a buffered fallback,
2487
- * e.g. when the user disables streaming). Defaults to throwing — most
2488
- * hosts only use streaming.
2580
+ * Horizontal pixel offset matching the FAB's `offset` prop, so the greeting
2581
+ * lines up under the FAB. @default 24
2489
2582
  */
2490
- send?: (sessionId: string, content: string, options?: SendOptions) => Promise<ChatMessage>;
2491
- /** Request headers (Authorization, content-type, etc.). */
2492
- buildHeaders?: () => HeadersInit | Promise<HeadersInit>;
2493
- /** Override fetch (tests, retry layers). */
2494
- fetchImpl?: typeof fetch;
2583
+ fabOffset?: number;
2495
2584
  /**
2496
- * HTTP method for the stream request. Defaults to `'POST'` with
2497
- * `{ content, attachments, metadata }` JSON body. Set to `'GET'` if
2498
- * your backend embeds the message in the URL via `buildStreamUrl`.
2585
+ * Vertical pixel offset above/below the FAB centerline. @default 96
2586
+ * (room for an `md` FAB plus a small gap).
2499
2587
  */
2500
- streamMethod?: 'GET' | 'POST';
2501
- /** Idle timeout for the SSE connection, in ms. Forwarded to `parseSSE`. */
2502
- idleTimeoutMs?: number;
2588
+ fabClearance?: number;
2589
+ /** Delay before the greeting appears, in ms. @default 1500 */
2590
+ delayMs?: number;
2591
+ /** z-index. @default 9998 (just below the default FAB at 9999). */
2592
+ zIndex?: number;
2593
+ /** Override classes on the bubble. */
2594
+ className?: string;
2595
+ /** Override styles on the bubble. */
2596
+ style?: CSSProperties;
2597
+ /** Optional sender avatar / icon shown on the left. */
2598
+ avatar?: ReactNode;
2599
+ /** Optional sender label rendered above the text. */
2600
+ senderName?: string;
2601
+ /** ARIA label for the dismiss button. @default 'Dismiss' */
2602
+ dismissLabel?: string;
2503
2603
  /**
2504
- * Side-channel for events that don't translate to `ChatStreamEvent`
2505
- * (e.g. `approval_required` — surfaces interactive prompts outside the
2506
- * normal message stream). Called synchronously while parsing the SSE
2507
- * frame; mutate caller-owned state, don't `await` long work here.
2604
+ * Render in-place (no fixed positioning). Useful for stories and inline previews.
2605
+ * @default false
2508
2606
  */
2509
- onPydanticEvent?: (event: PydanticAIEvent) => void;
2607
+ inline?: boolean;
2510
2608
  }
2511
- declare function createPydanticAIChatTransport(opts: PydanticAIChatTransportOpts): ChatTransport;
2609
+ /**
2610
+ * Greeting bubble shown next to a `ChatFAB` to invite the user to start a
2611
+ * conversation (LiveChat / Intercom-style proactive prompt).
2612
+ *
2613
+ * Renders fixed-position, anchored to the same corner as the FAB. Owns its
2614
+ * own delayed-mount + presence animation. Hide on chat open and/or after
2615
+ * user dismissal.
2616
+ *
2617
+ * @example
2618
+ * ```tsx
2619
+ * const [open, setOpen] = useState(false);
2620
+ * const [dismissed, setDismissed] = useState(false);
2621
+ *
2622
+ * <ChatLauncher
2623
+ * open={open}
2624
+ * onOpenChange={setOpen}
2625
+ * fab={{ variant: 'animated' }}
2626
+ * dock={{ title: 'Support' }}
2627
+ * >
2628
+ * <SupportChat />
2629
+ * </ChatLauncher>
2630
+ *
2631
+ * <ChatGreeting
2632
+ * open={!open && !dismissed}
2633
+ * onClick={() => setOpen(true)}
2634
+ * onDismiss={() => setDismissed(true)}
2635
+ * senderName="Anna from Support"
2636
+ * delayMs={2000}
2637
+ * >
2638
+ * Hi! 👋 Got a question? I'm here to help.
2639
+ * </ChatGreeting>
2640
+ * ```
2641
+ */
2642
+ declare function ChatGreeting({ open, children, onClick, onDismiss, position, fabOffset, fabClearance, delayMs, zIndex, className, style, avatar, senderName, dismissLabel, inline, }: ChatGreetingProps): react_jsx_runtime.JSX.Element;
2512
2643
 
2513
- type ChatFABPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
2514
- type ChatFABVariant = 'simple' | 'animated' | 'glass';
2515
- type ChatFABSize = 'sm' | 'md' | 'lg' | 'responsive';
2516
- interface ChatFABProps {
2517
- /** Click handler — typically toggles a `ChatDock`. */
2518
- onClick: () => void;
2519
- /** Accessible label. */
2520
- ariaLabel?: string;
2521
- /** Icon inside the FAB. Defaults to a bot glyph. */
2522
- icon?: ReactNode;
2523
- /** Visual style. @default 'simple' */
2524
- variant?: ChatFABVariant;
2525
- /** Button size. @default 'md' */
2526
- size?: ChatFABSize;
2527
- /** Fixed-screen position. @default 'bottom-right' */
2644
+ interface ChatUnreadPreviewProps {
2645
+ /** Controlled usually `!dockOpen && !!message`. */
2646
+ open: boolean;
2647
+ /** Inbound message to preview. `null` hides the bubble. */
2648
+ message: ChatMessage | null;
2649
+ /** Tap open chat + mark read. */
2650
+ onClick?: () => void;
2651
+ /** × → mark read without opening. */
2652
+ onDismiss?: () => void;
2653
+ /** Anchor corner — match the FAB so the bubble sits above it. @default 'bottom-right' */
2528
2654
  position?: ChatFABPosition;
2529
- /** Pixel offset from screen edges. @default 24 */
2530
- offset?: number;
2531
- /** z-index for the button. @default 9999 */
2655
+ /** Horizontal offset from screen edge, matches the FAB. @default 24 */
2656
+ fabOffset?: number;
2657
+ /** Vertical clearance above/below the FAB. @default 96 */
2658
+ fabClearance?: number;
2659
+ /** Lines of body text before ellipsis. @default 2 */
2660
+ truncate?: number;
2661
+ /** z-index. @default 9998 */
2532
2662
  zIndex?: number;
2533
- /** Show a small attention dot (unread / new). */
2534
- pulse?: boolean;
2535
- /**
2536
- * Numeric badge — unread count. Numbers > 9 render as "9+".
2537
- * Overrides `pulse` when both set.
2538
- */
2539
- badge?: number;
2540
- /** Hover tooltip text. Shows next to the FAB on hover/focus. */
2541
- tooltip?: string;
2542
- /**
2543
- * Render in-place (no fixed positioning) so the FAB sits inline in the
2544
- * normal document flow. Useful for stories, screenshots, and previews
2545
- * inside a contained playground panel. @default false
2546
- */
2663
+ /** Render in-place (stories / previews). @default false */
2547
2664
  inline?: boolean;
2548
- /** Override classes on the button itself. */
2665
+ /** Override classes on the bubble. */
2549
2666
  className?: string;
2550
- /** Extra style on the button (caller-controlled overrides). */
2667
+ /** Override styles on the bubble. */
2551
2668
  style?: CSSProperties;
2669
+ /** ARIA label for the dismiss button. @default 'Mark as read' */
2670
+ dismissLabel?: string;
2671
+ /** Override the avatar — defaults to derived from `message.sender`. */
2672
+ avatar?: ReactNode;
2673
+ /** Override the sender label — defaults to `message.sender?.name`. */
2674
+ senderName?: string;
2552
2675
  }
2553
2676
  /**
2554
- * Floating action button for opening a chat dock. Pure presentation — owns
2555
- * no state. Wire it to whatever toggles your dock open.
2677
+ * Push-notification bubble next to the chat FAB.
2556
2678
  *
2557
- * For the common "FAB + dock + hotkey" composition, use `<ChatLauncher>`
2558
- * this primitive is only useful when you need custom triggering.
2679
+ * Shows the last inbound message while the chat is closed
2680
+ * Intercom / LiveChat-style. Tap open chat (host wires `onClick`).
2681
+ * Dismiss × → keep chat closed but stop nagging.
2682
+ *
2683
+ * Pair with `useChatUnread()` (inside `<ChatProvider>`) for state.
2559
2684
  */
2560
- declare function ChatFAB({ onClick, ariaLabel, icon, variant, size, position, offset, zIndex, pulse, badge, tooltip, inline, className, style, }: ChatFABProps): react_jsx_runtime.JSX.Element;
2685
+ declare function ChatUnreadPreview({ open, message, onClick, onDismiss, position, fabOffset, fabClearance, truncate, zIndex, inline, className, style, dismissLabel, avatar, senderName, }: ChatUnreadPreviewProps): react_jsx_runtime.JSX.Element;
2561
2686
 
2562
- type ChatDockMode = 'popover' | 'side';
2563
- type ChatDockSide = 'left' | 'right';
2564
- interface ChatDockProps {
2565
- /** Controlled open state. */
2566
- open: boolean;
2567
- /** Called when the user clicks the close button. */
2568
- onClose: () => void;
2569
- /** Dock contents (typically a `<Chat>` component). */
2687
+ interface ChatLauncherHotkey {
2688
+ /** Key (case-sensitive single char or named like 'Escape'). */
2689
+ key: string;
2690
+ /** Require Cmd (mac) or Ctrl (other). */
2691
+ meta?: boolean;
2692
+ /** Require Shift. */
2693
+ shift?: boolean;
2694
+ /** Require Alt. */
2695
+ alt?: boolean;
2696
+ }
2697
+ interface ChatLauncherGreeting extends Omit<ChatGreetingProps, 'open' | 'onClick' | 'onDismiss' | 'position' | 'fabOffset' | 'children'> {
2698
+ /** Greeting body — string for the default style, or any ReactNode. */
2699
+ content: ReactNode;
2700
+ /** Persistence key for "user dismissed this greeting" in `localStorage`. Pass `null` to disable persistence. @default null */
2701
+ dismissStorageKey?: string | null;
2702
+ /** Hide the greeting once the user opens the chat. @default true */
2703
+ hideOnOpen?: boolean;
2704
+ }
2705
+ interface ChatLauncherProps {
2706
+ /** Dock contents — typically a `<Chat>` instance. */
2570
2707
  children: ReactNode;
2708
+ /** FAB customization (icon, position, label, pulse, badge, tooltip, variant, size). */
2709
+ fab?: Omit<ChatFABProps, 'onClick'>;
2710
+ /** Dock customization (size, title, position, transition, mobileFullscreen). */
2711
+ dock?: Omit<ChatDockProps, 'open' | 'onClose' | 'children'>;
2571
2712
  /**
2572
- * Visual mode.
2573
- * - `popover` (default): floating card anchored to a corner, fixed size, FAB-style.
2574
- * - `side`: docked panel pinned to the left/right edge, full viewport height.
2713
+ * Proactive greeting bubble shown next to the FAB before the user opens the chat.
2714
+ * Set to a string or full config object. Omit to disable.
2575
2715
  */
2576
- mode?: ChatDockMode;
2577
- /** Side for `mode='side'`. @default 'right' */
2578
- side?: ChatDockSide;
2579
- /** Header title text. */
2580
- title?: ReactNode;
2581
- /** Header icon. Defaults to a bot glyph. */
2582
- icon?: ReactNode;
2716
+ greeting?: string | ChatLauncherGreeting;
2717
+ /** Open/close via a keyboard shortcut. */
2718
+ hotkey?: ChatLauncherHotkey;
2719
+ /** Initial open state for uncontrolled mode. @default false */
2720
+ defaultOpen?: boolean;
2721
+ /** Controlled open state (pair with `onOpenChange`). */
2722
+ open?: boolean;
2723
+ /** Controlled open state setter. */
2724
+ onOpenChange?: (open: boolean) => void;
2583
2725
  /**
2584
- * Header actions slot (right side, before the close button).
2585
- * Use `ChatHeaderActionButton` to keep visual consistency.
2726
+ * Focus the composer textarea when the dock opens. Saves a click for
2727
+ * every "FAB start typing" interaction. @default true
2586
2728
  */
2587
- headerActions?: ReactNode;
2588
- /** Hide the header entirely (you render your own inside `children`). */
2589
- hideHeader?: boolean;
2590
- /** ARIA label for the close button. @default 'Close' */
2591
- closeLabel?: string;
2592
- /** Dock width in px. Clamped to viewport. @default 480 (popover) / 420 (side) */
2593
- width?: number;
2594
- /** Dock height in px. Only used in `popover` mode. @default 720 */
2595
- height?: number;
2596
- /** Which screen corner to dock to in `popover` mode. @default 'bottom-right' */
2597
- position?: ChatFABPosition;
2598
- /** Offset from screen edges in px (popover only). @default 24 / 96 */
2599
- offset?: {
2600
- horizontal?: number;
2601
- vertical?: number;
2602
- };
2603
- /** Transition duration in ms — should match CSS animation. @default 200 */
2604
- exitDurationMs?: number;
2605
- /** z-index. @default 10000 */
2606
- zIndex?: number;
2607
- /** Accessible dialog label. */
2608
- ariaLabel?: string;
2609
- /** Extra classes on the dock container. */
2610
- className?: string;
2729
+ autoFocusComposerOnOpen?: boolean;
2611
2730
  /**
2612
- * Take over the full viewport on mobile (< 768px). Applies to both modes.
2613
- * @default true
2731
+ * Close the dock on `Escape`. Mirrors standard popover / drawer UX.
2732
+ * Set to `false` to disable (e.g. if you want Escape to do something
2733
+ * else inside the chat). @default true
2614
2734
  */
2615
- mobileFullscreen?: boolean;
2735
+ closeOnEscape?: boolean;
2616
2736
  /**
2617
- * Render in-place (not in `document.body` via a portal). Useful for stories,
2618
- * screenshots, or wrapping the dock inside a custom container. @default false
2737
+ * Last inbound message (admin reply / system notice / agent push) the
2738
+ * user hasn't seen yet. Drives the `<ChatUnreadPreview>` bubble next
2739
+ * to the FAB and (by default) the FAB badge.
2740
+ *
2741
+ * Source it from `useChatUnread()` inside your `<ChatProvider>`.
2619
2742
  */
2620
- disablePortal?: boolean;
2743
+ unreadMessage?: ChatMessage | null;
2621
2744
  /**
2622
- * Drop fixed positioning entirely the dock renders as a normal flow
2623
- * element sized by `width`/`height`. Combine with `disablePortal` for
2624
- * stories/previews where the dock should sit inside the panel instead
2625
- * of attaching to the viewport. @default false
2745
+ * Called when the user opens the chat via FAB/preview/hotkey or
2746
+ * dismisses the preview with ×. Wire to `useChatUnread().markRead`.
2626
2747
  */
2627
- inline?: boolean;
2748
+ onMarkRead?: () => void;
2628
2749
  /**
2629
- * In `mode='side'`, reserve space on the document body so page content
2630
- * isn't covered by the dock. Sets `padding-{side}` on `<body>` while
2631
- * the dock is open and exposes the width via the `--chat-dock-reserve`
2632
- * CSS variable for custom layouts. @default true (when mode='side')
2750
+ * Customize the unread bubble (`truncate`, `dismissLabel`, …).
2751
+ * `open`/`message`/`onClick`/`onDismiss`/`position`/`fabOffset` are
2752
+ * wired automatically.
2633
2753
  */
2634
- reserveBodySpace?: boolean;
2635
- }
2636
- /**
2637
- * Fixed-position chat surface. Two modes:
2638
- *
2639
- * - `popover` — floating card anchored to a corner. Companion to `<ChatFAB>`.
2640
- * - `side` — full-height panel pinned to the left/right edge. App-shell style.
2641
- *
2642
- * Renders only when `open` is true (plus the leave-transition tail). Uses
2643
- * `useChatPresence` for the four-phase mount/animate/unmount cycle.
2644
- */
2645
- declare function ChatDock({ open, onClose, children, mode, side, title, icon, headerActions, hideHeader, closeLabel, width, height, position, offset, exitDurationMs, zIndex, ariaLabel, className, mobileFullscreen, disablePortal, inline, reserveBodySpace, }: ChatDockProps): react_jsx_runtime.JSX.Element;
2646
-
2647
- interface ChatHeaderProps {
2648
- /** Window title text. */
2649
- title?: ReactNode;
2650
- /** Icon next to the title. Defaults to a bot glyph. */
2651
- icon?: ReactNode;
2754
+ unreadPreview?: Omit<ChatUnreadPreviewProps, 'open' | 'message' | 'onClick' | 'onDismiss' | 'position' | 'fabOffset'>;
2652
2755
  /**
2653
- * Action slot appears to the right of the title, before the close button.
2654
- * Use for reset / settings / minimize / etc. Compose with `ChatHeaderActionButton`.
2756
+ * Auto-inject a mute / unmute button into the header. Pass the
2757
+ * `useChatAudio()` (or any compatible `{ muted, toggleMute }`)
2758
+ * instance — the launcher renders `<ChatHeaderAudioToggle>` in the
2759
+ * header's actions slot when audio is actually configured (not silent).
2760
+ *
2761
+ * Hosts that manage their own header can ignore this prop and render
2762
+ * `<ChatHeaderAudioToggle>` directly.
2655
2763
  */
2656
- actions?: ReactNode;
2657
- /** Show the close (×) button. @default true */
2658
- showClose?: boolean;
2659
- /** Close click handler. */
2660
- onClose?: () => void;
2661
- /** ARIA label for the close button. @default 'Close' */
2662
- closeLabel?: string;
2663
- /** Replace the close button entirely (rare — most hosts want the default). */
2664
- closeSlot?: ReactNode;
2665
- /** Extra classes on the `<header>` element. */
2666
- className?: string;
2764
+ audio?: {
2765
+ muted: boolean;
2766
+ toggleMute: () => void;
2767
+ isSilent?: boolean;
2768
+ } | null;
2769
+ /**
2770
+ * Suppress the auto-injected audio toggle even when `audio` is passed.
2771
+ * @default false
2772
+ */
2773
+ hideAudioToggle?: boolean;
2667
2774
  }
2668
2775
  /**
2669
- * Standalone chat header title + icon + action slot + close button.
2776
+ * Floating chat launcher = FAB + Dock + presence + optional greeting + hotkey.
2670
2777
  *
2671
- * Used by `<ChatDock>` automatically, but can be rendered standalone when
2672
- * the host owns the chat container (e.g. embedded inline in a page).
2778
+ * 99% of hosts use this directly. For non-FAB triggers (e.g. an inline
2779
+ * link in the page) compose `<ChatDock>` with your own button.
2673
2780
  */
2674
- declare function ChatHeader({ title, icon, actions, showClose, onClose, closeLabel, closeSlot, className, }: ChatHeaderProps): react_jsx_runtime.JSX.Element;
2781
+ declare function ChatLauncher({ children, fab, dock, greeting, hotkey, defaultOpen, open: controlledOpen, onOpenChange, autoFocusComposerOnOpen, closeOnEscape, unreadMessage, onMarkRead, unreadPreview, audio, hideAudioToggle, }: ChatLauncherProps): react_jsx_runtime.JSX.Element;
2675
2782
 
2676
- interface ChatHeaderActionButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
2677
- /** Icon (required). */
2678
- icon: ReactNode;
2679
- /** Accessible label + native tooltip. */
2680
- ariaLabel: string;
2681
- /** Optional unread / status badge — small number on top-right. */
2682
- badge?: number;
2683
- /** Mark as destructive — uses destructive hover tokens. */
2684
- destructive?: boolean;
2685
- /** Optional visual loading state (e.g. while reset is in flight). */
2686
- loading?: boolean;
2687
- }
2783
+ type ChatPresencePhase = 'hidden' | 'entering' | 'visible' | 'leaving';
2688
2784
  /**
2689
- * Compact icon button for the chat header actions slot.
2785
+ * Presence state machine for floating popovers.
2690
2786
  *
2691
- * Standard chrome: 28×28 ghost button, hover bg-accent, focus ring, optional
2692
- * destructive variant, optional numeric badge for unread / pending.
2787
+ * Drives a four-phase lifecycle so enter/leave CSS transitions actually fire:
2693
2788
  *
2694
- * @example
2695
- * ```tsx
2696
- * <ChatHeader
2697
- * title="Assistant"
2698
- * onClose={close}
2699
- * actions={
2700
- * <>
2701
- * <ChatHeaderActionButton
2702
- * icon={<RotateCcw className="h-3.5 w-3.5" />}
2703
- * ariaLabel="Clear context"
2704
- * onClick={handleReset}
2705
- * loading={isResetting}
2706
- * />
2707
- * <ChatHeaderActionButton
2708
- * icon={<Settings className="h-3.5 w-3.5" />}
2709
- * ariaLabel="Settings"
2710
- * onClick={openSettings}
2711
- * />
2712
- * </>
2713
- * }
2714
- * />
2715
- * ```
2789
+ * hidden → entering (mount, transition class starts) → visible
2790
+ * visible → leaving (transition runs) → hidden (unmount)
2791
+ *
2792
+ * Mounting in `entering` and ticking to `visible` on the next paint is what
2793
+ * lets transition classes animate. Without it the element appears already
2794
+ * at its final state and CSS transitions never observe a change.
2795
+ *
2796
+ * @param open - controlled open state
2797
+ * @param exitDurationMs - how long the leave transition runs; should match CSS
2716
2798
  */
2717
- declare const ChatHeaderActionButton: react.ForwardRefExoticComponent<ChatHeaderActionButtonProps & react.RefAttributes<HTMLButtonElement>>;
2799
+ declare function useChatPresence(open: boolean, exitDurationMs?: number): ChatPresencePhase;
2718
2800
 
2719
- interface ChatHeaderModeToggleProps {
2720
- /** Current dock mode. */
2721
- mode: ChatDockMode;
2722
- /** Toggle handler. Wire to `useChatDockPrefs().toggleMode`. */
2723
- onToggle: () => void;
2724
- /** Override aria/tooltip label for popover→side. */
2725
- expandLabel?: string;
2726
- /** Override aria/tooltip label for side→popover. */
2727
- collapseLabel?: string;
2728
- /**
2729
- * Always render — useful for stories. By default the toggle hides itself on
2730
- * viewports below `lg` (1024px) since side mode falls back to popover there.
2731
- */
2732
- forceVisible?: boolean;
2801
+ /**
2802
+ * Chat defaults and constants.
2803
+ */
2804
+ declare const STORAGE_KEYS: {
2805
+ readonly mode: "djc-chat-mode";
2806
+ readonly sidebarWidth: "djc-chat-sidebar-width";
2807
+ readonly composerHistory: "djc-chat-composer-history";
2808
+ };
2809
+ declare const CSS_VARS: {
2810
+ readonly reserve: "--djc-chat-reserve";
2811
+ };
2812
+ declare const DEFAULT_Z_INDEX = 9000;
2813
+ declare const LIMITS: {
2814
+ /** Max characters per single message. */
2815
+ readonly messageMaxLength: 8000;
2816
+ /** Max attachments per message. */
2817
+ readonly attachmentsMax: 10;
2818
+ /** Composer history slots. */
2819
+ readonly composerHistorySize: 50;
2820
+ /** Coalesce stream tokens within this window before dispatching. */
2821
+ readonly streamCoalesceMs: 16;
2822
+ /** Default history page size. */
2823
+ readonly pageSize: 50;
2824
+ /** Virtualize list when >= this many messages (host-controlled threshold). */
2825
+ readonly virtualizeThreshold: 50;
2826
+ /** SSE idle timeout. */
2827
+ readonly sseIdleMs: 45000;
2828
+ };
2829
+ declare const DEFAULT_SIDEBAR: {
2830
+ readonly width: 420;
2831
+ readonly min: 320;
2832
+ readonly max: 720;
2833
+ };
2834
+ declare const HOTKEYS: {
2835
+ readonly send: "mod+enter";
2836
+ readonly cancel: "esc";
2837
+ readonly newChat: "mod+shift+n";
2838
+ readonly toggleOpen: "mod+/";
2839
+ readonly focusComposer: "mod+l";
2840
+ };
2841
+ declare const CHAT_EVENT_NAME = "djc:chat:send";
2842
+ interface ChatEventDetail {
2843
+ content: string;
2844
+ sessionId?: string;
2845
+ attachments?: unknown[];
2846
+ metadata?: Record<string, unknown>;
2733
2847
  }
2848
+
2734
2849
  /**
2735
- * "Dock to side" / "back to popover" toggle.
2736
- *
2737
- * Side mode is desktop-only — on viewports below `lg` (1024px) `<ChatDock>`
2738
- * silently falls back to popover anyway, so the toggle has nothing to do
2739
- * and auto-hides. Pass `forceVisible` to override (e.g. in stories).
2850
+ * ID generation. Uses crypto.randomUUID when available with a fallback
2851
+ * for older environments (and SSR bundles that may not have crypto).
2740
2852
  */
2741
- declare function ChatHeaderModeToggle({ mode, onToggle, expandLabel, collapseLabel, forceVisible, }: ChatHeaderModeToggleProps): react_jsx_runtime.JSX.Element;
2853
+ declare function createId(prefix?: string): string;
2742
2854
 
2743
- interface ChatHeaderAudioToggleProps {
2744
- /** Current muted state. */
2745
- muted: boolean;
2746
- /** Toggle handler. Wire to `useChatAudio().setMuted` or `toggleMute`. */
2747
- onToggle: () => void;
2748
- /** Override tooltip label for muted → unmuted. */
2749
- unmuteLabel?: string;
2750
- /** Override tooltip label for unmuted → muted. */
2751
- muteLabel?: string;
2855
+ /**
2856
+ * Token coalescer. Buffers stream tokens within a small time window before
2857
+ * dispatching a single aggregated chunk. Prevents 60+ re-renders per second
2858
+ * on fast streams.
2859
+ */
2860
+ interface TokenBuffer {
2861
+ /** Append a delta. Returns immediately. */
2862
+ push(delta: string): void;
2863
+ /** Force flush and resolve any pending timer. */
2864
+ flush(): void;
2865
+ /** Stop accepting tokens; flush whatever is buffered. */
2866
+ close(): void;
2752
2867
  }
2868
+ declare function createTokenBuffer(onFlush: (delta: string) => void, windowMs?: 16): TokenBuffer;
2869
+
2870
+ declare function resolvePersona(message: Pick<ChatMessage, 'role' | 'sender'>, user?: ChatUserContext, assistant?: ChatAssistantContext): ChatPersona;
2871
+ /** Compute initials for an avatar fallback. */
2872
+ declare function deriveInitials(persona: ChatPersona, role?: string): string;
2873
+
2753
2874
  /**
2754
- * Mute / unmute notification sounds. Drop into a `<ChatHeader>` actions
2755
- * slot or into `<ChatDock headerActions>`.
2875
+ * HTTP + SSE transport. Default implementation for web hosts.
2756
2876
  *
2757
- * @example
2758
- * ```tsx
2759
- * const audio = useChatAudio({ sounds: {...} });
2760
- * <ChatHeaderAudioToggle muted={audio.muted} onToggle={() => audio.setMuted(!audio.muted)} />
2761
- * ```
2877
+ * Backend contract (see @dev/@refactoring7-chat/06-integration.md):
2878
+ * POST /sessions → SessionInfo (JSON)
2879
+ * GET /sessions/:id/history?cursor= HistoryPage (JSON)
2880
+ * POST /sessions/:id/messages → SSE stream of ChatStreamEvent
2881
+ * POST /sessions/:id/messages/buffered → ChatMessage (JSON, fallback)
2882
+ * DELETE /sessions/:id → 204
2762
2883
  */
2763
- declare function ChatHeaderAudioToggle({ muted, onToggle, unmuteLabel, muteLabel, }: ChatHeaderAudioToggleProps): react_jsx_runtime.JSX.Element;
2764
2884
 
2765
- interface ChatHeaderResetButtonProps {
2766
- /**
2767
- * Backend reset call. Should resolve to `true` on success.
2768
- * Plugged into `useChatReset` for in-flight state.
2769
- */
2770
- onReset: () => Promise<boolean>;
2771
- /** Called after a successful reset (e.g. clear local messages, refetch). */
2772
- onSuccess?: () => void;
2773
- /** Called when reset fails (returned `false` or threw). */
2774
- onError?: (error?: unknown) => void;
2775
- /**
2776
- * Show a `window.dialog.confirm` before calling `onReset`. @default true
2777
- *
2778
- * Requires the host to mount `<DialogProvider>` from `@djangocfg/ui-core`
2779
- * — it installs the `window.dialog` API used here.
2780
- */
2781
- confirm?: boolean;
2782
- /** Confirm dialog title. */
2783
- confirmTitle?: string;
2784
- /** Confirm dialog message. */
2785
- confirmMessage?: string;
2786
- /** Override tooltip / aria label. */
2787
- ariaLabel?: string;
2885
+ interface HttpTransportConfig {
2886
+ /** Base URL without trailing slash, e.g. '/api/chat' or 'https://api.example.com/v1/chat'. */
2887
+ baseUrl: string;
2888
+ /** Optional slug appended/forwarded as project identifier. */
2889
+ slug?: string;
2890
+ /** Returns headers applied to every request — e.g. Authorization. */
2891
+ getAuthHeader?: () => Record<string, string> | Promise<Record<string, string>>;
2892
+ /** Default fetch timeout (per non-streaming request). */
2893
+ timeoutMs?: number;
2894
+ /** Override fetch implementation (useful for tests or custom retry layers). */
2895
+ fetchImpl?: typeof fetch;
2788
2896
  }
2897
+ declare function createHttpTransport(config: HttpTransportConfig): ChatTransport;
2898
+
2789
2899
  /**
2790
- * Standard chat-reset action: prompts the user via `window.dialog.confirm`,
2791
- * then runs the backend reset call through `useChatReset` so the button
2792
- * spins while it's in flight.
2900
+ * In-memory chat transport for stories and tests. Replays scripted replies.
2901
+ */
2902
+
2903
+ interface MockTransportOptions {
2904
+ /** Each entry is the assistant's reply for one user turn. Strings are split
2905
+ * into chunks; arrays are taken as the exact event sequence (after a
2906
+ * prepended `message_start` and before a synthetic `message_end`). */
2907
+ replies?: Array<string | ChatStreamEvent[]>;
2908
+ latencyMs?: number;
2909
+ /** Initial history returned by `createSession`. */
2910
+ initialMessages?: ChatMessage[];
2911
+ shouldFail?: (attempt: number) => boolean;
2912
+ }
2913
+ declare function createMockTransport(opts?: MockTransportOptions): ChatTransport;
2914
+
2915
+ /**
2916
+ * Server-Sent Events parser as an AsyncGenerator.
2793
2917
  *
2794
- * @example
2795
- * ```tsx
2796
- * <ChatHeaderResetButton
2797
- * onReset={api.clearChat}
2798
- * onSuccess={() => chat.clearMessages()}
2799
- * />
2800
- * ```
2918
+ * Yields parsed events from a `Response` body. Handles the split-read case
2919
+ * where `event:` and `data:` arrive in separate TCP packets. Skips malformed
2920
+ * JSON gracefully. Honors AbortSignal (caller passes one to fetch).
2921
+ */
2922
+
2923
+ interface RawEvent {
2924
+ event?: string;
2925
+ data?: string;
2926
+ }
2927
+ interface ParseSSEOptions {
2928
+ signal?: AbortSignal;
2929
+ /** Map a raw SSE event to zero, one, or many `ChatStreamEvent`s.
2930
+ * Default: parse `data` as JSON and assume the JSON shape already
2931
+ * matches `ChatStreamEvent`. */
2932
+ map?: (raw: RawEvent) => ChatStreamEvent | ChatStreamEvent[] | null;
2933
+ idleTimeoutMs?: number;
2934
+ }
2935
+ declare function parseSSE(response: Response, options?: ParseSSEOptions): AsyncGenerator<ChatStreamEvent, void, void>;
2936
+
2937
+ /**
2938
+ * Transport surface re-export. Lives in core so transport implementations
2939
+ * never need to reach into the public types module.
2801
2940
  */
2802
- declare function ChatHeaderResetButton({ onReset, onSuccess, onError, confirm, confirmTitle, confirmMessage, ariaLabel, }: ChatHeaderResetButtonProps): react_jsx_runtime.JSX.Element;
2803
2941
 
2804
- interface ChatHeaderLanguageButtonProps {
2805
- /** Override aria-label. Default "Speech language". */
2806
- ariaLabel?: string;
2807
- /**
2808
- * Subset of BCP-47 tags to offer. Default: every entry from the
2809
- * Web Speech catalogue (~66 tags incl. regional variants). Pass a
2810
- * tighter list when your backend STT only supports a subset.
2811
- */
2812
- allowedTags?: string[];
2813
- /** Hide the globe-fallback icon when no flag resolves. */
2814
- hideFallbackIcon?: boolean;
2815
- className?: string;
2942
+ declare class TransportError extends Error {
2943
+ code: string;
2944
+ constructor(message: string, code?: string);
2816
2945
  }
2946
+
2817
2947
  /**
2818
- * Compact flag-button language picker for the chat header. Built on
2819
- * top of the ui-core `<Combobox>` — searchable autocomplete with
2820
- * flags, ~66 BCP-47 tags from the official Chrome Web Speech demo, and
2821
- * a custom 28×28 trigger via `renderTrigger`.
2948
+ * Pydantic-AI SSE event mapper.
2822
2949
  *
2823
- * The selection persists into `useSpeechPrefs` (zustand+localStorage)
2824
- * so `useSpeechRecognition` picks it up as the second-priority resolver
2825
- * value (above i18n locale, below an explicit `language` prop).
2950
+ * Translates the event shape emitted by pydantic-AI–style Django backends
2951
+ * (text_delta / tool_call / tool_result / done / error / approval_required)
2952
+ * into the canonical `ChatStreamEvent` stream consumed by the Chat reducer.
2953
+ *
2954
+ * Backends that don't expose a stable `tool_call_id` are supported via a
2955
+ * per-stream FIFO queue keyed by tool name. The earlier "Map<name, toolId>"
2956
+ * approach lost the first toolId when a tool was invoked twice in one turn
2957
+ * (e.g. `list_tasks` for search and again for confirmation) — using a queue
2958
+ * keeps each call/result pair correctly matched.
2826
2959
  */
2827
- declare function ChatHeaderLanguageButton({ ariaLabel, allowedTags, hideFallbackIcon, className, }: ChatHeaderLanguageButtonProps): react.ReactElement;
2828
2960
 
2829
- interface ChatGreetingProps {
2830
- /** Controlled visibility usually `!chatOpen && !userDismissed`. */
2831
- open: boolean;
2832
- /** Greeting text. Pass a string for the default bubble, or any ReactNode. */
2833
- children: ReactNode;
2834
- /** Click handler — typically opens the chat. Bubble is clickable when set. */
2835
- onClick?: () => void;
2836
- /** Close (×) button handler — typically marks the greeting as dismissed. */
2837
- onDismiss?: () => void;
2838
- /** Anchor relative to a FAB on the same side. @default 'bottom-right' */
2839
- position?: ChatFABPosition;
2840
- /**
2841
- * Horizontal pixel offset matching the FAB's `offset` prop, so the greeting
2842
- * lines up under the FAB. @default 24
2843
- */
2844
- fabOffset?: number;
2845
- /**
2846
- * Vertical pixel offset above/below the FAB centerline. @default 96
2847
- * (room for an `md` FAB plus a small gap).
2848
- */
2849
- fabClearance?: number;
2850
- /** Delay before the greeting appears, in ms. @default 1500 */
2851
- delayMs?: number;
2852
- /** z-index. @default 9998 (just below the default FAB at 9999). */
2853
- zIndex?: number;
2854
- /** Override classes on the bubble. */
2855
- className?: string;
2856
- /** Override styles on the bubble. */
2857
- style?: CSSProperties;
2858
- /** Optional sender avatar / icon shown on the left. */
2859
- avatar?: ReactNode;
2860
- /** Optional sender label rendered above the text. */
2861
- senderName?: string;
2862
- /** ARIA label for the dismiss button. @default 'Dismiss' */
2863
- dismissLabel?: string;
2961
+ interface PydanticAIEvent {
2962
+ type: 'text_delta' | 'tool_call' | 'tool_result' | 'done' | 'error' | 'approval_required';
2963
+ delta?: string;
2964
+ tool?: string;
2965
+ args?: unknown;
2966
+ result?: unknown;
2864
2967
  /**
2865
- * Render in-place (no fixed positioning). Useful for stories and inline previews.
2866
- * @default false
2968
+ * Structured frontend payload present on `tool_result` when the tool has
2969
+ * a `result_schema`. The LLM sees only `result` (compact text); the
2970
+ * frontend uses `data` to render rich UI (e.g. vehicle cards).
2867
2971
  */
2868
- inline?: boolean;
2972
+ data?: unknown;
2973
+ total_tokens?: number;
2974
+ error?: string;
2975
+ tool_call_id?: string;
2976
+ session_id?: string;
2977
+ }
2978
+ /** Per-stream FIFO queue keyed by tool name. Created via `createToolIdQueue`. */
2979
+ interface ToolIdQueue {
2980
+ /** Allocate a new toolId for a `tool_call` event and enqueue it under `name`. */
2981
+ push(name: string): string;
2982
+ /** Pop the oldest toolId for `name` (or return an orphan marker if none). */
2983
+ shift(name: string): string;
2984
+ /** Reset all queues (e.g. on stream close). */
2985
+ clear(): void;
2869
2986
  }
2987
+ declare function createToolIdQueue(): ToolIdQueue;
2870
2988
  /**
2871
- * Greeting bubble shown next to a `ChatFAB` to invite the user to start a
2872
- * conversation (LiveChat / Intercom-style proactive prompt).
2873
- *
2874
- * Renders fixed-position, anchored to the same corner as the FAB. Owns its
2875
- * own delayed-mount + presence animation. Hide on chat open and/or after
2876
- * user dismissal.
2877
- *
2878
- * @example
2879
- * ```tsx
2880
- * const [open, setOpen] = useState(false);
2881
- * const [dismissed, setDismissed] = useState(false);
2882
- *
2883
- * <ChatLauncher
2884
- * open={open}
2885
- * onOpenChange={setOpen}
2886
- * fab={{ variant: 'animated' }}
2887
- * dock={{ title: 'Support' }}
2888
- * >
2889
- * <SupportChat />
2890
- * </ChatLauncher>
2989
+ * Translate a single pydantic-AI event into zero or more `ChatStreamEvent`s.
2990
+ * Pass a `ToolIdQueue` shared across the lifetime of one stream so that
2991
+ * `tool_call` / `tool_result` pairs match correctly.
2992
+ */
2993
+ declare function mapPydanticAIEvent(ev: PydanticAIEvent, toolIds: ToolIdQueue): Generator<ChatStreamEvent>;
2994
+ /**
2995
+ * Convenience factory: returns a `ParseSSEOptions['map']` callback that
2996
+ * decodes raw SSE frames as `PydanticAIEvent` JSON and yields zero or
2997
+ * more `ChatStreamEvent`s through `mapPydanticAIEvent`.
2891
2998
  *
2892
- * <ChatGreeting
2893
- * open={!open && !dismissed}
2894
- * onClick={() => setOpen(true)}
2895
- * onDismiss={() => setDismissed(true)}
2896
- * senderName="Anna from Support"
2897
- * delayMs={2000}
2898
- * >
2899
- * Hi! 👋 Got a question? I'm here to help.
2900
- * </ChatGreeting>
2901
- * ```
2999
+ * Allocates an internal `ToolIdQueue` — call this once per stream.
2902
3000
  */
2903
- declare function ChatGreeting({ open, children, onClick, onDismiss, position, fabOffset, fabClearance, delayMs, zIndex, className, style, avatar, senderName, dismissLabel, inline, }: ChatGreetingProps): react_jsx_runtime.JSX.Element;
3001
+ declare function createPydanticAISSEMap(): NonNullable<ParseSSEOptions['map']>;
2904
3002
 
2905
- interface ChatUnreadPreviewProps {
2906
- /** Controlled — usually `!dockOpen && !!message`. */
2907
- open: boolean;
2908
- /** Inbound message to preview. `null` hides the bubble. */
2909
- message: ChatMessage | null;
2910
- /** Tap → open chat + mark read. */
2911
- onClick?: () => void;
2912
- /** × → mark read without opening. */
2913
- onDismiss?: () => void;
2914
- /** Anchor corner — match the FAB so the bubble sits above it. @default 'bottom-right' */
2915
- position?: ChatFABPosition;
2916
- /** Horizontal offset from screen edge, matches the FAB. @default 24 */
2917
- fabOffset?: number;
2918
- /** Vertical clearance above/below the FAB. @default 96 */
2919
- fabClearance?: number;
2920
- /** Lines of body text before ellipsis. @default 2 */
2921
- truncate?: number;
2922
- /** z-index. @default 9998 */
2923
- zIndex?: number;
2924
- /** Render in-place (stories / previews). @default false */
2925
- inline?: boolean;
2926
- /** Override classes on the bubble. */
2927
- className?: string;
2928
- /** Override styles on the bubble. */
2929
- style?: CSSProperties;
2930
- /** ARIA label for the dismiss button. @default 'Mark as read' */
2931
- dismissLabel?: string;
2932
- /** Override the avatar — defaults to derived from `message.sender`. */
2933
- avatar?: ReactNode;
2934
- /** Override the sender label — defaults to `message.sender?.name`. */
2935
- senderName?: string;
2936
- }
2937
3003
  /**
2938
- * Push-notification bubble next to the chat FAB.
3004
+ * High-level transport factory for pydantic-AI–style backends.
2939
3005
  *
2940
- * Shows the last inbound message while the chat is closed —
2941
- * Intercom / LiveChat-style. Tap open chat (host wires `onClick`).
2942
- * Dismiss × keep chat closed but stop nagging.
3006
+ * Composes:
3007
+ * - `parseSSE` for spec-compliant SSE framing (multi-line `data:`, comments, idle timeout).
3008
+ * - `createPydanticAISSEMap` for normalizing pydantic-AI events into `ChatStreamEvent`.
2943
3009
  *
2944
- * Pair with `useChatUnread()` (inside `<ChatProvider>`) for state.
3010
+ * Use when your backend speaks the canonical pydantic-AI stream shape
3011
+ * (`text_delta` / `tool_call` / `tool_result` / `done` / `error`).
3012
+ * URL building, auth headers, history loading, and optional session
3013
+ * bootstrapping are caller responsibilities — pass them in.
2945
3014
  */
2946
- declare function ChatUnreadPreview({ open, message, onClick, onDismiss, position, fabOffset, fabClearance, truncate, zIndex, inline, className, style, dismissLabel, avatar, senderName, }: ChatUnreadPreviewProps): react_jsx_runtime.JSX.Element;
2947
3015
 
2948
- interface ChatLauncherHotkey {
2949
- /** Key (case-sensitive single char or named like 'Escape'). */
2950
- key: string;
2951
- /** Require Cmd (mac) or Ctrl (other). */
2952
- meta?: boolean;
2953
- /** Require Shift. */
2954
- shift?: boolean;
2955
- /** Require Alt. */
2956
- alt?: boolean;
2957
- }
2958
- interface ChatLauncherGreeting extends Omit<ChatGreetingProps, 'open' | 'onClick' | 'onDismiss' | 'position' | 'fabOffset' | 'children'> {
2959
- /** Greeting body — string for the default style, or any ReactNode. */
2960
- content: ReactNode;
2961
- /** Persistence key for "user dismissed this greeting" in `localStorage`. Pass `null` to disable persistence. @default null */
2962
- dismissStorageKey?: string | null;
2963
- /** Hide the greeting once the user opens the chat. @default true */
2964
- hideOnOpen?: boolean;
2965
- }
2966
- interface ChatLauncherProps {
2967
- /** Dock contents — typically a `<Chat>` instance. */
2968
- children: ReactNode;
2969
- /** FAB customization (icon, position, label, pulse, badge, tooltip, variant, size). */
2970
- fab?: Omit<ChatFABProps, 'onClick'>;
2971
- /** Dock customization (size, title, position, transition, mobileFullscreen). */
2972
- dock?: Omit<ChatDockProps, 'open' | 'onClose' | 'children'>;
2973
- /**
2974
- * Proactive greeting bubble shown next to the FAB before the user opens the chat.
2975
- * Set to a string or full config object. Omit to disable.
2976
- */
2977
- greeting?: string | ChatLauncherGreeting;
2978
- /** Open/close via a keyboard shortcut. */
2979
- hotkey?: ChatLauncherHotkey;
2980
- /** Initial open state for uncontrolled mode. @default false */
2981
- defaultOpen?: boolean;
2982
- /** Controlled open state (pair with `onOpenChange`). */
2983
- open?: boolean;
2984
- /** Controlled open state setter. */
2985
- onOpenChange?: (open: boolean) => void;
2986
- /**
2987
- * Focus the composer textarea when the dock opens. Saves a click for
2988
- * every "FAB → start typing" interaction. @default true
2989
- */
2990
- autoFocusComposerOnOpen?: boolean;
2991
- /**
2992
- * Close the dock on `Escape`. Mirrors standard popover / drawer UX.
2993
- * Set to `false` to disable (e.g. if you want Escape to do something
2994
- * else inside the chat). @default true
2995
- */
2996
- closeOnEscape?: boolean;
3016
+ interface PydanticAIChatTransportOpts {
2997
3017
  /**
2998
- * Last inbound message (admin reply / system notice / agent push) the
2999
- * user hasn't seen yet. Drives the `<ChatUnreadPreview>` bubble next
3000
- * to the FAB and (by default) the FAB badge.
3001
- *
3002
- * Source it from `useChatUnread()` inside your `<ChatProvider>`.
3018
+ * Build the SSE stream URL for a user message turn.
3019
+ * @example (sessionId, message) => `${base}/stream?session_id=${sessionId}&message=${encodeURIComponent(message)}`
3003
3020
  */
3004
- unreadMessage?: ChatMessage | null;
3021
+ buildStreamUrl: (sessionId: string, message: string) => string | URL;
3022
+ /** Optional history loader. If omitted, `loadHistory` returns an empty page. */
3023
+ loadHistory?: (sessionId: string, cursor?: string | null) => Promise<HistoryPage>;
3005
3024
  /**
3006
- * Called when the user opens the chat via FAB/preview/hotkey or
3007
- * dismisses the preview with ×. Wire to `useChatUnread().markRead`.
3025
+ * Optional session bootstrap. Called from `createSession`. Useful for
3026
+ * backends that need a `POST /sessions` round-trip or want to pre-seed
3027
+ * history.
3008
3028
  */
3009
- onMarkRead?: () => void;
3029
+ bootstrapSession?: (opts?: CreateSessionOptions) => Promise<SessionInfo>;
3030
+ /** Optional session teardown. */
3031
+ closeSession?: (sessionId: string) => Promise<void>;
3010
3032
  /**
3011
- * Customize the unread bubble (`truncate`, `dismissLabel`, …).
3012
- * `open`/`message`/`onClick`/`onDismiss`/`position`/`fabOffset` are
3013
- * wired automatically.
3033
+ * Optional non-streaming send (for hosts that need a buffered fallback,
3034
+ * e.g. when the user disables streaming). Defaults to throwing — most
3035
+ * hosts only use streaming.
3014
3036
  */
3015
- unreadPreview?: Omit<ChatUnreadPreviewProps, 'open' | 'message' | 'onClick' | 'onDismiss' | 'position' | 'fabOffset'>;
3037
+ send?: (sessionId: string, content: string, options?: SendOptions) => Promise<ChatMessage>;
3038
+ /** Request headers (Authorization, content-type, etc.). */
3039
+ buildHeaders?: () => HeadersInit | Promise<HeadersInit>;
3040
+ /** Override fetch (tests, retry layers). */
3041
+ fetchImpl?: typeof fetch;
3016
3042
  /**
3017
- * Auto-inject a mute / unmute button into the header. Pass the
3018
- * `useChatAudio()` (or any compatible `{ muted, toggleMute }`)
3019
- * instance the launcher renders `<ChatHeaderAudioToggle>` in the
3020
- * header's actions slot when audio is actually configured (not silent).
3021
- *
3022
- * Hosts that manage their own header can ignore this prop and render
3023
- * `<ChatHeaderAudioToggle>` directly.
3043
+ * HTTP method for the stream request. Defaults to `'POST'` with
3044
+ * `{ content, attachments, metadata }` JSON body. Set to `'GET'` if
3045
+ * your backend embeds the message in the URL via `buildStreamUrl`.
3024
3046
  */
3025
- audio?: {
3026
- muted: boolean;
3027
- toggleMute: () => void;
3028
- isSilent?: boolean;
3029
- } | null;
3047
+ streamMethod?: 'GET' | 'POST';
3048
+ /** Idle timeout for the SSE connection, in ms. Forwarded to `parseSSE`. */
3049
+ idleTimeoutMs?: number;
3030
3050
  /**
3031
- * Suppress the auto-injected audio toggle even when `audio` is passed.
3032
- * @default false
3051
+ * Side-channel for events that don't translate to `ChatStreamEvent`
3052
+ * (e.g. `approval_required` — surfaces interactive prompts outside the
3053
+ * normal message stream). Called synchronously while parsing the SSE
3054
+ * frame; mutate caller-owned state, don't `await` long work here.
3033
3055
  */
3034
- hideAudioToggle?: boolean;
3056
+ onPydanticEvent?: (event: PydanticAIEvent) => void;
3035
3057
  }
3036
- /**
3037
- * Floating chat launcher = FAB + Dock + presence + optional greeting + hotkey.
3038
- *
3039
- * 99% of hosts use this directly. For non-FAB triggers (e.g. an inline
3040
- * link in the page) compose `<ChatDock>` with your own button.
3041
- */
3042
- declare function ChatLauncher({ children, fab, dock, greeting, hotkey, defaultOpen, open: controlledOpen, onOpenChange, autoFocusComposerOnOpen, closeOnEscape, unreadMessage, onMarkRead, unreadPreview, audio, hideAudioToggle, }: ChatLauncherProps): react_jsx_runtime.JSX.Element;
3043
-
3044
- type ChatPresencePhase = 'hidden' | 'entering' | 'visible' | 'leaving';
3045
- /**
3046
- * Presence state machine for floating popovers.
3047
- *
3048
- * Drives a four-phase lifecycle so enter/leave CSS transitions actually fire:
3049
- *
3050
- * hidden → entering (mount, transition class starts) → visible
3051
- * visible → leaving (transition runs) → hidden (unmount)
3052
- *
3053
- * Mounting in `entering` and ticking to `visible` on the next paint is what
3054
- * lets transition classes animate. Without it the element appears already
3055
- * at its final state and CSS transitions never observe a change.
3056
- *
3057
- * @param open - controlled open state
3058
- * @param exitDurationMs - how long the leave transition runs; should match CSS
3059
- */
3060
- declare function useChatPresence(open: boolean, exitDurationMs?: number): ChatPresencePhase;
3058
+ declare function createPydanticAIChatTransport(opts: PydanticAIChatTransportOpts): ChatTransport;
3061
3059
 
3062
3060
  /**
3063
3061
  * @deprecated Plan64. As of ui-tools 2.1.369, `<MessageList>` is
@@ -3419,6 +3417,101 @@ declare function isGeoJSONFeatureCollection(v: unknown): v is {
3419
3417
  };
3420
3418
  declare function isStringValue(v: unknown): v is string;
3421
3419
 
3420
+ /**
3421
+ * Chat dev logger.
3422
+ *
3423
+ * A thin namespaced wrapper over `consola` that no-ops in production unless
3424
+ * the host app explicitly opts in via `<ChatRoot debug />`. The default
3425
+ * detection uses `isDev` from `@djangocfg/ui-core/lib/env` (NODE_ENV).
3426
+ *
3427
+ * Why a dedicated module: chat is async and event-heavy (bootstrap, transport,
3428
+ * SSE chunks, tool calls, regenerate, …). Inline `console.log`s rot fast and
3429
+ * leak into prod. A single `getChatLogger()` call gives every layer the same
3430
+ * namespaced sub-logger and keeps zero-cost gating in one place.
3431
+ *
3432
+ * Sub-loggers:
3433
+ * bootstrap — initial session bootstrap (createSession / loadHistory)
3434
+ * transport — outbound transport calls + responses
3435
+ * stream — SSE chunk / tool / message_end events
3436
+ * lifecycle — sendMessage, regenerate, newSession, edits
3437
+ * tools — tool_call_start / _delta / _end specifics
3438
+ * error — caught errors (always emitted as `error` level)
3439
+ */
3440
+
3441
+ type ChatLogScope = 'bootstrap' | 'transport' | 'stream' | 'lifecycle' | 'tools' | 'error';
3442
+ interface ChatLogger {
3443
+ bootstrap: ConsolaInstance;
3444
+ transport: ConsolaInstance;
3445
+ stream: ConsolaInstance;
3446
+ lifecycle: ConsolaInstance;
3447
+ tools: ConsolaInstance;
3448
+ error: ConsolaInstance;
3449
+ /** True when this logger is actually emitting (host opted in or NODE_ENV=development). */
3450
+ enabled: boolean;
3451
+ }
3452
+ /**
3453
+ * Get the chat logger.
3454
+ * @param debug Explicit override from the host. `undefined` falls back to `isDev`.
3455
+ */
3456
+ declare function getChatLogger(debug?: boolean): ChatLogger;
3457
+
3458
+ /**
3459
+ * sanitizeDraft — minimal pre-submit cleanup for chat-composer drafts.
3460
+ *
3461
+ * Mirrors the conservative behaviour ChatGPT / Claude / Telegram
3462
+ * actually ship: clean the obvious junk the user didn't intend to
3463
+ * send, touch nothing that *could* be intentional.
3464
+ *
3465
+ * **What we DO touch:**
3466
+ *
3467
+ * 1. Trim leading/trailing whitespace (spaces, tabs, newlines,
3468
+ * NBSP). The user typing `\n\n hello \n` meant `hello`.
3469
+ * 2. Normalise line endings — `\r\n` / `\r` → `\n`. Pasted Windows
3470
+ * / old-mac text gets the same internal shape, so the LLM
3471
+ * tokeniser and markdown renderer see one canonical form.
3472
+ * 3. Strip zero-width / invisible characters that web-paste
3473
+ * smuggles in: ZWSP (U+200B), ZWNJ (U+200C), ZWJ (U+200D),
3474
+ * BOM / ZWNBSP (U+FEFF). They're invisible, break LLM
3475
+ * tokenisation, and the user never meant to type them.
3476
+ *
3477
+ * **What we DO NOT touch (and why):**
3478
+ *
3479
+ * - **Internal whitespace runs** (3+ spaces, tabs, blank lines).
3480
+ * Code indentation depends on these. ChatGPT preserves them as
3481
+ * typed — " if (x):\n return" stays four-space-indented.
3482
+ * Collapsing them is the path to subtly broken code snippets.
3483
+ *
3484
+ * - **Bidi override marks** (U+200E LRM, U+200F RLM, U+202A..U+202E).
3485
+ * Legitimately used in Arabic / Hebrew / mixed-direction text.
3486
+ * Stripping silently breaks RTL users. If a specific deployment
3487
+ * wants to block them as a security measure, do it at that layer
3488
+ * with explicit user-visible feedback.
3489
+ *
3490
+ * - **Tabs vs spaces** beyond rule 1. Could be either code or
3491
+ * prose; without parsing markdown we can't tell.
3492
+ *
3493
+ * - **Emoji, mentions, URLs, code spans** — passthrough text.
3494
+ *
3495
+ * The function is intentionally tiny — every rule earns its keep
3496
+ * with a concrete "user pasted X from Y, got nonsense" story.
3497
+ *
3498
+ * **Idempotent**: `sanitizeDraft(sanitizeDraft(x)) === sanitizeDraft(x)`.
3499
+ */
3500
+ declare function sanitizeDraft(input: string): string;
3501
+ /**
3502
+ * Convenience predicate: true when the draft is non-empty AFTER
3503
+ * sanitation. Use to gate Send buttons / Enter submits so an empty
3504
+ * or whitespace-only draft never produces a real message.
3505
+ *
3506
+ * Cheaper than sanitizeDraft(input).length > 0 only marginally —
3507
+ * we still allocate the cleaned string. Kept as a named helper for
3508
+ * call-site clarity.
3509
+ */
3510
+ declare function isSubmittableDraft(input: string): boolean;
3511
+
3512
+ /** Walk the conversation and collect image attachments in chronological order. */
3513
+ declare function collectImageAttachments(messages: ChatMessage[]): ChatAttachment[];
3514
+
3422
3515
  /**
3423
3516
  * Chat color tokens — single source of truth.
3424
3517
  *
@@ -3533,100 +3626,7 @@ interface ChatDestructiveStyles {
3533
3626
  */
3534
3627
  declare function useChatDestructiveStyles(): ChatDestructiveStyles;
3535
3628
 
3536
- /** Walk the conversation and collect image attachments in chronological order. */
3537
- declare function collectImageAttachments(messages: ChatMessage[]): ChatAttachment[];
3538
-
3539
- /**
3540
- * sanitizeDraft — minimal pre-submit cleanup for chat-composer drafts.
3541
- *
3542
- * Mirrors the conservative behaviour ChatGPT / Claude / Telegram
3543
- * actually ship: clean the obvious junk the user didn't intend to
3544
- * send, touch nothing that *could* be intentional.
3545
- *
3546
- * **What we DO touch:**
3547
- *
3548
- * 1. Trim leading/trailing whitespace (spaces, tabs, newlines,
3549
- * NBSP). The user typing `\n\n hello \n` meant `hello`.
3550
- * 2. Normalise line endings — `\r\n` / `\r` → `\n`. Pasted Windows
3551
- * / old-mac text gets the same internal shape, so the LLM
3552
- * tokeniser and markdown renderer see one canonical form.
3553
- * 3. Strip zero-width / invisible characters that web-paste
3554
- * smuggles in: ZWSP (U+200B), ZWNJ (U+200C), ZWJ (U+200D),
3555
- * BOM / ZWNBSP (U+FEFF). They're invisible, break LLM
3556
- * tokenisation, and the user never meant to type them.
3557
- *
3558
- * **What we DO NOT touch (and why):**
3559
- *
3560
- * - **Internal whitespace runs** (3+ spaces, tabs, blank lines).
3561
- * Code indentation depends on these. ChatGPT preserves them as
3562
- * typed — " if (x):\n return" stays four-space-indented.
3563
- * Collapsing them is the path to subtly broken code snippets.
3564
- *
3565
- * - **Bidi override marks** (U+200E LRM, U+200F RLM, U+202A..U+202E).
3566
- * Legitimately used in Arabic / Hebrew / mixed-direction text.
3567
- * Stripping silently breaks RTL users. If a specific deployment
3568
- * wants to block them as a security measure, do it at that layer
3569
- * with explicit user-visible feedback.
3570
- *
3571
- * - **Tabs vs spaces** beyond rule 1. Could be either code or
3572
- * prose; without parsing markdown we can't tell.
3573
- *
3574
- * - **Emoji, mentions, URLs, code spans** — passthrough text.
3575
- *
3576
- * The function is intentionally tiny — every rule earns its keep
3577
- * with a concrete "user pasted X from Y, got nonsense" story.
3578
- *
3579
- * **Idempotent**: `sanitizeDraft(sanitizeDraft(x)) === sanitizeDraft(x)`.
3580
- */
3581
- declare function sanitizeDraft(input: string): string;
3582
- /**
3583
- * Convenience predicate: true when the draft is non-empty AFTER
3584
- * sanitation. Use to gate Send buttons / Enter submits so an empty
3585
- * or whitespace-only draft never produces a real message.
3586
- *
3587
- * Cheaper than sanitizeDraft(input).length > 0 only marginally —
3588
- * we still allocate the cleaned string. Kept as a named helper for
3589
- * call-site clarity.
3590
- */
3591
- declare function isSubmittableDraft(input: string): boolean;
3592
-
3593
- /**
3594
- * Chat dev logger.
3595
- *
3596
- * A thin namespaced wrapper over `consola` that no-ops in production unless
3597
- * the host app explicitly opts in via `<ChatRoot debug />`. The default
3598
- * detection uses `isDev` from `@djangocfg/ui-core/lib/env` (NODE_ENV).
3599
- *
3600
- * Why a dedicated module: chat is async and event-heavy (bootstrap, transport,
3601
- * SSE chunks, tool calls, regenerate, …). Inline `console.log`s rot fast and
3602
- * leak into prod. A single `getChatLogger()` call gives every layer the same
3603
- * namespaced sub-logger and keeps zero-cost gating in one place.
3604
- *
3605
- * Sub-loggers:
3606
- * bootstrap — initial session bootstrap (createSession / loadHistory)
3607
- * transport — outbound transport calls + responses
3608
- * stream — SSE chunk / tool / message_end events
3609
- * lifecycle — sendMessage, regenerate, newSession, edits
3610
- * tools — tool_call_start / _delta / _end specifics
3611
- * error — caught errors (always emitted as `error` level)
3612
- */
3613
-
3614
- type ChatLogScope = 'bootstrap' | 'transport' | 'stream' | 'lifecycle' | 'tools' | 'error';
3615
- interface ChatLogger {
3616
- bootstrap: ConsolaInstance;
3617
- transport: ConsolaInstance;
3618
- stream: ConsolaInstance;
3619
- lifecycle: ConsolaInstance;
3620
- tools: ConsolaInstance;
3621
- error: ConsolaInstance;
3622
- /** True when this logger is actually emitting (host opted in or NODE_ENV=development). */
3623
- enabled: boolean;
3624
- }
3625
- /**
3626
- * Get the chat logger.
3627
- * @param debug Explicit override from the host. `undefined` falls back to `isDev`.
3628
- */
3629
- declare function getChatLogger(debug?: boolean): ChatLogger;
3629
+ declare const LazyChat: react.ComponentType<ChatRootProps>;
3630
3630
 
3631
3631
  interface MessageListProps {
3632
3632
  messages?: ChatMessage[];