feat(portal-shell): chatbot widget — SSE-bridged AI assistant with full a11y uplift
Closes the AI relay chantier opened by ADR-0024 by landing the SPA side: a floating-launcher + dialog-panel chatbot that consumes the BFF's POST /api/ai/chat SSE endpoint shipped in #196. Stargate's POC widget was rebuilt from scratch rather than ported — its drag UX, missing aria-live, and minimal screen-reader contract did not meet ADR-0016's WCAG 2.2 AA + targeted AAA bar. Key UX + a11y decisions: - No drag. Stargate's mouse-only floating-bubble drag had no keyboard equivalent; replaced with a fixed bottom-right launcher + windowed/fullscreen toggle. - role=dialog + aria-modal=false (non-modal) so the page stays operable behind the panel; focus returns to the launcher on close via a viewChild + effect pair. - role=log + aria-live=polite + aria-relevant=additions on the message list, so screen readers announce incoming tokens without re-reading the full history. Typing-dots animation is decorative (aria-hidden) and gated on prefers-reduced-motion. - Every interactive element honours the 44 × 44 touch-target minimum (launcher, header buttons, send/stop, citation chips). - Stop button visible during streaming, wired to AbortController → ChatClient → upstream LLM cancel. - Citations rendered as inline footnote chips; clicking opens a side-panel sibling component with source + score + snippet. - All strings tagged with $localize per ADR-0019; FR + EN catalogue entries added. - 5 new icons in the shared registry: maximize-2, message-circle, minimize-2, send, square, x. Architecture: - ChatbotApiService — native fetch (HttpClient buffers streaming) + ReadableStream parser + manual CSRF cookie read + AbortSignal. - SSE parser — pure function with CRLF tolerance, partial-frame reassembly, and well-formed JSON / passthrough decoding. - ChatbotService — signal-based state (view / messages / streaming / selected citation) with stop() / send() actions; ephemeral conversation per session reload. - ChatbotHost component — host of the widget; ChatbotCitationPanel extracted as a sibling component to stay under anyComponentStyle budget (raised from 5/6 to 6/8 KB to match portal-admin's posture). - Lazy-loaded via @defer (on idle) in app.html so the chatbot's ~27 KB lazy chunk does not weigh on the initial bundle. Tests: 27 new specs covering the SSE parser (8), API service (5), state service (10), and host component (12 — launcher / dialog / empty state / messages / log live region / input / citation panel).
This commit is contained in:
@@ -20,15 +20,21 @@ import {
|
||||
type LucideIconData,
|
||||
LucideAngularModule,
|
||||
Mail,
|
||||
Maximize2,
|
||||
MessageCircle,
|
||||
Minimize2,
|
||||
Monitor,
|
||||
Moon,
|
||||
Search,
|
||||
Send,
|
||||
Settings,
|
||||
Square,
|
||||
Store,
|
||||
Sun,
|
||||
User,
|
||||
UsersRound,
|
||||
Wrench,
|
||||
X,
|
||||
} from 'lucide-angular';
|
||||
|
||||
/**
|
||||
@@ -71,15 +77,21 @@ const ICON_REGISTRY = {
|
||||
'log-in': LogIn,
|
||||
'log-out': LogOut,
|
||||
mail: Mail,
|
||||
'maximize-2': Maximize2,
|
||||
'message-circle': MessageCircle,
|
||||
'minimize-2': Minimize2,
|
||||
monitor: Monitor,
|
||||
moon: Moon,
|
||||
search: Search,
|
||||
send: Send,
|
||||
settings: Settings,
|
||||
square: Square,
|
||||
store: Store,
|
||||
sun: Sun,
|
||||
user: User,
|
||||
'users-round': UsersRound,
|
||||
wrench: Wrench,
|
||||
x: X,
|
||||
} satisfies Record<string, LucideIconData>;
|
||||
|
||||
export type IconName = keyof typeof ICON_REGISTRY;
|
||||
|
||||
Reference in New Issue
Block a user