@open-mercato/ui 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (148) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -1
  3. package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
  4. package/dist/ai/AiAssistantLauncher.js +596 -0
  5. package/dist/ai/AiAssistantLauncher.js.map +7 -0
  6. package/dist/ai/AiChat.js +1092 -0
  7. package/dist/ai/AiChat.js.map +7 -0
  8. package/dist/ai/AiChatSessions.js +297 -0
  9. package/dist/ai/AiChatSessions.js.map +7 -0
  10. package/dist/ai/AiDock.js +347 -0
  11. package/dist/ai/AiDock.js.map +7 -0
  12. package/dist/ai/AiMessageContent.js +369 -0
  13. package/dist/ai/AiMessageContent.js.map +7 -0
  14. package/dist/ai/ChatPaneTabs.js +251 -0
  15. package/dist/ai/ChatPaneTabs.js.map +7 -0
  16. package/dist/ai/index.js +115 -0
  17. package/dist/ai/index.js.map +7 -0
  18. package/dist/ai/parts/ConfirmationCard.js +211 -0
  19. package/dist/ai/parts/ConfirmationCard.js.map +7 -0
  20. package/dist/ai/parts/FieldDiffCard.js +119 -0
  21. package/dist/ai/parts/FieldDiffCard.js.map +7 -0
  22. package/dist/ai/parts/MutationPreviewCard.js +224 -0
  23. package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
  24. package/dist/ai/parts/MutationResultCard.js +240 -0
  25. package/dist/ai/parts/MutationResultCard.js.map +7 -0
  26. package/dist/ai/parts/approval-cards-map.js +15 -0
  27. package/dist/ai/parts/approval-cards-map.js.map +7 -0
  28. package/dist/ai/parts/index.js +24 -0
  29. package/dist/ai/parts/index.js.map +7 -0
  30. package/dist/ai/parts/pending-action-api.js +60 -0
  31. package/dist/ai/parts/pending-action-api.js.map +7 -0
  32. package/dist/ai/parts/types.js +1 -0
  33. package/dist/ai/parts/types.js.map +7 -0
  34. package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
  35. package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
  36. package/dist/ai/records/ActivityCard.js +83 -0
  37. package/dist/ai/records/ActivityCard.js.map +7 -0
  38. package/dist/ai/records/CompanyCard.js +81 -0
  39. package/dist/ai/records/CompanyCard.js.map +7 -0
  40. package/dist/ai/records/DealCard.js +76 -0
  41. package/dist/ai/records/DealCard.js.map +7 -0
  42. package/dist/ai/records/PersonCard.js +68 -0
  43. package/dist/ai/records/PersonCard.js.map +7 -0
  44. package/dist/ai/records/ProductCard.js +68 -0
  45. package/dist/ai/records/ProductCard.js.map +7 -0
  46. package/dist/ai/records/RecordCard.js +29 -0
  47. package/dist/ai/records/RecordCard.js.map +7 -0
  48. package/dist/ai/records/RecordCardShell.js +103 -0
  49. package/dist/ai/records/RecordCardShell.js.map +7 -0
  50. package/dist/ai/records/index.js +31 -0
  51. package/dist/ai/records/index.js.map +7 -0
  52. package/dist/ai/records/registry.js +51 -0
  53. package/dist/ai/records/registry.js.map +7 -0
  54. package/dist/ai/records/types.js +1 -0
  55. package/dist/ai/records/types.js.map +7 -0
  56. package/dist/ai/ui-part-registry.js +112 -0
  57. package/dist/ai/ui-part-registry.js.map +7 -0
  58. package/dist/ai/ui-part-slots.js +14 -0
  59. package/dist/ai/ui-part-slots.js.map +7 -0
  60. package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
  61. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
  62. package/dist/ai/upload-adapter.js +256 -0
  63. package/dist/ai/upload-adapter.js.map +7 -0
  64. package/dist/ai/useAiChat.js +549 -0
  65. package/dist/ai/useAiChat.js.map +7 -0
  66. package/dist/ai/useAiChatUpload.js +127 -0
  67. package/dist/ai/useAiChatUpload.js.map +7 -0
  68. package/dist/ai/useAiShortcuts.js +43 -0
  69. package/dist/ai/useAiShortcuts.js.map +7 -0
  70. package/dist/backend/AppShell.js +8 -4
  71. package/dist/backend/AppShell.js.map +2 -2
  72. package/dist/backend/BackendChromeProvider.js +2 -0
  73. package/dist/backend/BackendChromeProvider.js.map +2 -2
  74. package/dist/backend/DataTable.js +19 -2
  75. package/dist/backend/DataTable.js.map +2 -2
  76. package/dist/backend/FilterBar.js +19 -15
  77. package/dist/backend/FilterBar.js.map +2 -2
  78. package/dist/backend/dashboard/DashboardScreen.js +31 -3
  79. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  80. package/dist/backend/injection/spotIds.js +6 -0
  81. package/dist/backend/injection/spotIds.js.map +2 -2
  82. package/dist/backend/notifications/useNotificationEffect.js +38 -2
  83. package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +2 -2
  86. package/jest.config.cjs +7 -1
  87. package/jest.markdown-mock.tsx +7 -0
  88. package/package.json +10 -4
  89. package/src/ai/AiAssistantLauncher.tsx +805 -0
  90. package/src/ai/AiChat.tsx +1483 -0
  91. package/src/ai/AiChatSessions.tsx +429 -0
  92. package/src/ai/AiDock.tsx +505 -0
  93. package/src/ai/AiMessageContent.tsx +515 -0
  94. package/src/ai/ChatPaneTabs.tsx +310 -0
  95. package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
  96. package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
  97. package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
  98. package/src/ai/__tests__/AiChat.test.tsx +257 -0
  99. package/src/ai/__tests__/AiDock.test.tsx +124 -0
  100. package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
  101. package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
  102. package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
  103. package/src/ai/__tests__/upload-adapter.test.ts +213 -0
  104. package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
  105. package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
  106. package/src/ai/index.ts +125 -0
  107. package/src/ai/parts/ConfirmationCard.tsx +310 -0
  108. package/src/ai/parts/FieldDiffCard.tsx +173 -0
  109. package/src/ai/parts/MutationPreviewCard.tsx +302 -0
  110. package/src/ai/parts/MutationResultCard.tsx +360 -0
  111. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
  112. package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
  113. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
  114. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
  115. package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
  116. package/src/ai/parts/approval-cards-map.ts +24 -0
  117. package/src/ai/parts/index.ts +27 -0
  118. package/src/ai/parts/pending-action-api.ts +123 -0
  119. package/src/ai/parts/types.ts +84 -0
  120. package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
  121. package/src/ai/records/ActivityCard.tsx +102 -0
  122. package/src/ai/records/CompanyCard.tsx +89 -0
  123. package/src/ai/records/DealCard.tsx +85 -0
  124. package/src/ai/records/PersonCard.tsx +77 -0
  125. package/src/ai/records/ProductCard.tsx +83 -0
  126. package/src/ai/records/RecordCard.tsx +37 -0
  127. package/src/ai/records/RecordCardShell.tsx +169 -0
  128. package/src/ai/records/index.ts +30 -0
  129. package/src/ai/records/registry.tsx +80 -0
  130. package/src/ai/records/types.ts +90 -0
  131. package/src/ai/ui-part-registry.ts +233 -0
  132. package/src/ai/ui-part-slots.ts +32 -0
  133. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
  134. package/src/ai/upload-adapter.ts +421 -0
  135. package/src/ai/useAiChat.ts +865 -0
  136. package/src/ai/useAiChatUpload.ts +180 -0
  137. package/src/ai/useAiShortcuts.ts +79 -0
  138. package/src/backend/AppShell.tsx +12 -5
  139. package/src/backend/BackendChromeProvider.tsx +2 -0
  140. package/src/backend/DataTable.tsx +20 -1
  141. package/src/backend/FilterBar.tsx +26 -13
  142. package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
  143. package/src/backend/dashboard/DashboardScreen.tsx +38 -3
  144. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
  145. package/src/backend/injection/spotIds.ts +6 -0
  146. package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
  147. package/src/backend/notifications/useNotificationEffect.ts +47 -2
  148. package/src/index.ts +1 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/AiChat.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Bot,\n Check,\n ChevronDown,\n ChevronRight,\n Copy,\n Lightbulb,\n Loader2,\n Paperclip,\n Plus,\n Send,\n Square,\n User,\n Wrench,\n X,\n} from 'lucide-react'\nimport ReactMarkdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Alert, AlertDescription, AlertTitle } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Label } from '../primitives/label'\nimport { Textarea } from '../primitives/textarea'\nimport { parseAiContentSegments } from './AiMessageContent'\nimport { RecordCard } from './records/RecordCard'\nimport {\n defaultAiUiPartRegistry,\n isReservedAiUiPartId,\n type AiUiPartComponentId,\n type AiUiPartRegistry,\n} from './ui-part-registry'\nimport {\n useAiChat,\n type AiChatMessage,\n type AiChatMessageFile,\n type AiChatMessageUiPart,\n type AiChatToolCallSnapshot,\n} from './useAiChat'\nimport { useAiChatUpload } from './useAiChatUpload'\nimport { useAiShortcuts } from './useAiShortcuts'\n\n// Cap inline previews so we do not blow past localStorage quota (~5MB on most\n// browsers). Images larger than this still upload + send to the LLM as inline\n// base64 server-side; only the in-chat preview is dropped on reload.\nconst PREVIEW_DATA_URL_MAX_BYTES = 2 * 1024 * 1024\n\nasync function readFileAsDataUrl(file: File): Promise<string | undefined> {\n if (!file.type.startsWith('image/')) return undefined\n if (file.size > PREVIEW_DATA_URL_MAX_BYTES) return undefined\n return new Promise<string | undefined>((resolve) => {\n const reader = new FileReader()\n reader.onload = () =>\n resolve(typeof reader.result === 'string' ? reader.result : undefined)\n reader.onerror = () => resolve(undefined)\n try {\n reader.readAsDataURL(file)\n } catch {\n resolve(undefined)\n }\n })\n}\n\n/**\n * Optional resolved-tool snapshot the host can feed into the debug panel.\n * Step 4.6 wires this from the `GET /api/ai_assistant/ai/agents` response\n * (`tools[]`). Step 5.3+ will replace the manual wiring with a streamed\n * `debug` part once the dispatcher emits one.\n */\nexport interface AiChatDebugTool {\n name: string\n displayName?: string\n isMutation?: boolean\n requiredFeatures?: string[]\n}\n\n/**\n * Resolved prompt-section snapshot for the debug panel. Until Phase 3 Step\n * 5.3 lands structured `PromptTemplate.sections`, hosts synthesise this\n * from the agent's `systemPrompt` + additive overrides.\n */\nexport interface AiChatDebugPromptSection {\n id: string\n source?: 'default' | 'override' | 'placeholder'\n text?: string\n}\n\n/** Quick-action suggestion shown in the welcome state. */\nexport interface AiChatSuggestion {\n label: string\n prompt: string\n icon?: React.ReactNode\n}\n\n/** Context item displayed as a chip/pill in the chat header. */\nexport interface AiChatContextItem {\n label: string\n detail?: string\n}\n\nexport interface AiChatProps {\n agent: string\n apiPath?: string\n pageContext?: Record<string, unknown>\n attachmentIds?: string[]\n initialMessages?: Array<{ role: 'user' | 'assistant'; content: string }>\n debug?: boolean\n className?: string\n placeholder?: string\n onMutationRequested?: (pendingActionId: string) => void\n onError?: (err: { code?: string; message: string }) => void\n /**\n * Optional stable conversation id. Forwarded verbatim to\n * `POST /api/ai_assistant/ai/chat` request bodies and reused across\n * turns so the Step 5.6 `prepareMutation` idempotency hash stays\n * stable across retries within the same chat. When omitted, the hook\n * mints a fresh random id once on mount \u2014 remounting the component\n * resets the conversation.\n */\n conversationId?: string\n /**\n * Optional UI-part registry. Defaults to the module-global\n * {@link defaultAiUiPartRegistry}. Pass a scoped registry from\n * {@link createAiUiPartRegistry} when embedding multiple `<AiChat>`\n * instances that should not share registrations (playground, tests).\n */\n registry?: AiUiPartRegistry\n /**\n * Optional list of server-emitted UI parts to render inside the chat\n * transcript. The registry resolves each part via `componentId`. Phase 3\n * will populate this from the streamed dispatcher response; Phase 2 WS-A\n * leaves the wiring exposed so hosts can preview the registry path.\n */\n uiParts?: Array<{\n componentId: AiUiPartComponentId\n payload?: unknown\n pendingActionId?: string\n }>\n /**\n * Optional resolved-tool map for the debug panel. Ignored when\n * `debug` is falsy.\n */\n debugTools?: AiChatDebugTool[]\n /**\n * Optional resolved prompt sections for the debug panel. Ignored when\n * `debug` is falsy.\n */\n debugPromptSections?: AiChatDebugPromptSection[]\n /** Suggested prompts shown in the empty / welcome state. */\n suggestions?: AiChatSuggestion[]\n /** Context items shown as pills above the transcript (e.g. selected products). */\n contextItems?: AiChatContextItem[]\n /** Welcome heading shown when there are no messages yet. */\n welcomeTitle?: string\n /** Welcome description shown below the heading. */\n welcomeDescription?: string\n}\n\ninterface ServerEmittedUiPartRef {\n componentId: AiUiPartComponentId\n payload?: unknown\n pendingActionId?: string\n}\n\nfunction mapErrorCodeToVariant(\n code: string | undefined,\n): 'destructive' | 'warning' {\n if (!code) return 'destructive'\n // Policy denies that describe a filtered tool or attachment surface a\n // warning alert; caller can still continue. Hard denials (agent_unknown,\n // agent_features_denied, unauthenticated, execution_mode_not_supported,\n // mutation_blocked_by_*, validation_error) surface destructive alerts.\n const warningCodes = new Set<string>([\n 'tool_not_whitelisted',\n 'tool_features_denied',\n 'attachment_type_not_accepted',\n ])\n return warningCodes.has(code) ? 'warning' : 'destructive'\n}\n\nconst MARKDOWN_TYPOGRAPHY_CLASS = cn(\n '[&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0',\n '[&_ul]:my-2 [&_ol]:my-2 [&_ul]:ml-4 [&_ol]:ml-4 [&_ul]:list-disc [&_ol]:list-decimal',\n '[&_li]:my-0.5',\n '[&_h1]:mt-3 [&_h1]:mb-2 [&_h1]:text-base [&_h1]:font-semibold',\n '[&_h2]:mt-3 [&_h2]:mb-2 [&_h2]:text-sm [&_h2]:font-semibold',\n '[&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-semibold',\n '[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2',\n '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs',\n '[&_pre]:my-2 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted [&_pre]:p-3',\n '[&_pre_code]:bg-transparent [&_pre_code]:p-0',\n '[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground',\n '[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse [&_table]:text-xs',\n '[&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-medium',\n '[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1',\n)\n\nconst MARKDOWN_COMPONENTS = {\n a: ({ node, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { node?: unknown }) => (\n <a {...props} target=\"_blank\" rel=\"noreferrer\" />\n ),\n}\n\nfunction MarkdownChunk({ text }: { text: string }) {\n if (!text.trim()) return null\n return (\n <div className={cn('text-sm', MARKDOWN_TYPOGRAPHY_CLASS)}>\n <ReactMarkdown remarkPlugins={[remarkGfm]} components={MARKDOWN_COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n )\n}\n\nfunction MessageContent({ content, isAssistant }: { content: string; isAssistant: boolean }) {\n if (!isAssistant) {\n return <div className=\"whitespace-pre-wrap text-sm\">{content}</div>\n }\n if (!content) {\n return null\n }\n const segments = parseAiContentSegments(content)\n if (segments.length === 0) {\n return null\n }\n return (\n <div className=\"space-y-1\" data-ai-message-content=\"\">\n {segments.map((segment, index) => {\n if (segment.kind === 'record-card') {\n return <RecordCard key={`card-${index}`} data={segment.payload} />\n }\n if (segment.kind === 'invalid-card') {\n return (\n <pre\n key={`raw-${index}`}\n className=\"my-2 max-h-60 overflow-auto rounded-md border border-dashed border-border bg-muted p-2 text-xs\"\n data-ai-record-card-invalid={segment.info}\n >\n {segment.raw}\n </pre>\n )\n }\n return <MarkdownChunk key={`md-${index}`} text={segment.text} />\n })}\n </div>\n )\n}\n\nfunction safeStringify(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value, null, 2)\n } catch {\n return String(value)\n }\n}\n\nfunction ToolCallList({ toolCalls }: { toolCalls: AiChatToolCallSnapshot[] }) {\n const t = useT()\n const [openId, setOpenId] = React.useState<string | null>(null)\n if (!toolCalls || toolCalls.length === 0) return null\n return (\n <div className=\"space-y-1\" data-ai-chat-tool-calls=\"\">\n {toolCalls.map((call) => {\n const isOpen = openId === call.id\n const isError = call.state === 'error'\n const isPending = call.state === 'pending'\n const isComplete = call.state === 'complete'\n const statusLabel = isError\n ? t('ai_assistant.chat.toolError', 'failed')\n : isPending\n ? t('ai_assistant.chat.toolRunning', 'running\u2026')\n : t('ai_assistant.chat.toolDone', 'done')\n return (\n <div\n key={call.id}\n className={cn(\n 'rounded-md border border-border bg-muted/30',\n isError ? 'border-destructive/40 bg-destructive/5' : '',\n )}\n data-ai-chat-tool-call={call.toolName}\n data-ai-chat-tool-state={call.state}\n >\n <button\n type=\"button\"\n onClick={() => setOpenId(isOpen ? null : call.id)}\n className=\"flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs font-medium hover:bg-muted/60\"\n >\n {isOpen ? (\n <ChevronDown className=\"size-3.5 text-muted-foreground\" aria-hidden />\n ) : (\n <ChevronRight className=\"size-3.5 text-muted-foreground\" aria-hidden />\n )}\n {isPending ? (\n <Loader2 className=\"size-3.5 animate-spin text-muted-foreground\" aria-hidden />\n ) : (\n <Wrench\n className={cn(\n 'size-3.5',\n isError ? 'text-destructive' : 'text-muted-foreground',\n )}\n aria-hidden\n />\n )}\n <span className=\"font-mono\">{call.toolName}</span>\n <span\n className={cn(\n 'ml-auto text-[10px] uppercase tracking-wide',\n isError\n ? 'text-destructive'\n : isComplete\n ? 'text-status-success-text'\n : 'text-muted-foreground',\n )}\n >\n {statusLabel}\n </span>\n </button>\n {isOpen ? (\n <div className=\"space-y-1 border-t border-border/60 px-2 py-1.5 text-xs\">\n {call.input !== undefined ? (\n <div>\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n {t('ai_assistant.chat.toolInput', 'Input')}\n </div>\n <pre className=\"mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]\">\n {safeStringify(call.input)}\n </pre>\n </div>\n ) : null}\n {call.output !== undefined && !isError ? (\n <div>\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n {t('ai_assistant.chat.toolOutput', 'Output')}\n </div>\n <pre className=\"mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]\">\n {safeStringify(call.output)}\n </pre>\n </div>\n ) : null}\n {call.errorMessage ? (\n <div className=\"text-destructive\">{call.errorMessage}</div>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )\n}\n\nfunction ReasoningPanel({ text, streaming }: { text: string; streaming: boolean }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n if (!text) return null\n return (\n <div\n className=\"rounded-md border border-border bg-muted/30\"\n data-ai-chat-reasoning={streaming ? 'streaming' : 'complete'}\n >\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n className=\"flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs font-medium hover:bg-muted/60\"\n >\n {open ? (\n <ChevronDown className=\"size-3.5 text-muted-foreground\" aria-hidden />\n ) : (\n <ChevronRight className=\"size-3.5 text-muted-foreground\" aria-hidden />\n )}\n <Lightbulb className=\"size-3.5 text-muted-foreground\" aria-hidden />\n <span>{t('ai_assistant.chat.reasoning', 'Reasoning')}</span>\n {streaming ? (\n <Loader2\n className=\"ml-1 size-3 animate-spin text-muted-foreground\"\n aria-hidden\n />\n ) : null}\n </button>\n {open ? (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap border-t border-border/60 px-2 py-1.5 text-xs text-muted-foreground\">\n {text}\n </pre>\n ) : null}\n </div>\n )\n}\n\nfunction MessageRow({\n message,\n registry,\n onMutationRequested,\n}: {\n message: AiChatMessage\n registry?: AiUiPartRegistry\n onMutationRequested?: (pendingActionId: string) => void\n}) {\n const t = useT()\n const isAssistant = message.role === 'assistant'\n const label = isAssistant\n ? t('ai_assistant.chat.assistantRoleLabel', 'Assistant')\n : t('ai_assistant.chat.userRoleLabel', 'You')\n const Icon = isAssistant ? Bot : User\n const [copied, setCopied] = React.useState(false)\n const copyTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n React.useEffect(() => {\n return () => {\n if (copyTimerRef.current) clearTimeout(copyTimerRef.current)\n }\n }, [])\n\n const handleCopy = React.useCallback(async () => {\n const text = message.content\n if (!text) return\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text)\n } else {\n const textarea = document.createElement('textarea')\n textarea.value = text\n textarea.setAttribute('readonly', '')\n textarea.style.position = 'absolute'\n textarea.style.left = '-9999px'\n document.body.appendChild(textarea)\n textarea.select()\n document.execCommand('copy')\n document.body.removeChild(textarea)\n }\n setCopied(true)\n if (copyTimerRef.current) clearTimeout(copyTimerRef.current)\n copyTimerRef.current = setTimeout(() => setCopied(false), 1500)\n } catch {\n // Clipboard API blocked (no permission, http context) \u2014 silently fail.\n }\n }, [message.content])\n\n return (\n <div\n className={cn(\n 'group/message flex gap-3 px-3 py-2',\n isAssistant ? 'bg-muted/40 rounded-md' : '',\n )}\n data-role={message.role}\n >\n <div\n className={cn(\n 'flex size-6 shrink-0 items-center justify-center rounded-full',\n isAssistant\n ? 'bg-primary/10 text-primary'\n : 'bg-secondary text-secondary-foreground',\n )}\n aria-hidden\n >\n <Icon className=\"size-4\" />\n </div>\n <div className=\"flex-1 space-y-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-xs font-medium text-muted-foreground\">{label}</div>\n {message.content ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopy}\n aria-label={\n copied\n ? t('ai_assistant.chat.copied', 'Copied')\n : t('ai_assistant.chat.copyMessage', 'Copy message')\n }\n data-ai-chat-copy-button=\"\"\n className=\"opacity-0 transition-opacity group-hover/message:opacity-100 focus-visible:opacity-100\"\n >\n {copied ? (\n <Check className=\"size-3.5 text-status-success-icon\" aria-hidden />\n ) : (\n <Copy className=\"size-3.5\" aria-hidden />\n )}\n </IconButton>\n ) : null}\n </div>\n {message.files && message.files.length > 0 ? (\n <div className=\"flex flex-wrap gap-2 py-1\">\n {message.files.map((file, i) =>\n file.previewUrl ? (\n <img\n key={i}\n src={file.previewUrl}\n alt={file.name}\n className=\"max-h-32 max-w-[200px] rounded-md border border-border object-cover\"\n />\n ) : (\n <span\n key={i}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs\"\n >\n <Paperclip className=\"size-3\" aria-hidden />\n {file.name}\n </span>\n ),\n )}\n </div>\n ) : null}\n {isAssistant && message.reasoning ? (\n <ReasoningPanel\n text={message.reasoning}\n streaming={message.reasoningStreaming === true}\n />\n ) : null}\n {isAssistant && message.toolCalls && message.toolCalls.length > 0 ? (\n <ToolCallList toolCalls={message.toolCalls} />\n ) : null}\n <MessageContent content={message.content} isAssistant={isAssistant} />\n {isAssistant && registry && message.uiParts && message.uiParts.length > 0 ? (\n <MessageUiParts\n parts={message.uiParts}\n registry={registry}\n onMutationRequested={onMutationRequested}\n />\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction MessageUiParts({\n parts,\n registry,\n onMutationRequested,\n}: {\n parts: AiChatMessageUiPart[]\n registry: AiUiPartRegistry\n onMutationRequested?: (pendingActionId: string) => void\n}) {\n return (\n <div className=\"mt-2 flex flex-col gap-2\" data-ai-message-ui-parts=\"\">\n {parts.map((part) => (\n <AiUiPartRenderer\n key={part.key}\n part={{\n componentId: part.componentId as AiUiPartComponentId,\n payload: part.payload,\n pendingActionId: part.pendingActionId,\n }}\n registry={registry}\n onMutationRequested={onMutationRequested}\n />\n ))}\n </div>\n )\n}\n\nfunction UnknownUiPartPlaceholder({ componentId }: { componentId: AiUiPartComponentId }) {\n const t = useT()\n return (\n <div\n className=\"inline-flex items-center gap-2 rounded-full border border-dashed border-border bg-muted px-3 py-1 text-xs text-muted-foreground\"\n data-ai-ui-part-placeholder={componentId}\n >\n <span>\n {t('ai_assistant.chat.uiPartPending', 'Pending UI part:')} {componentId}\n </span>\n </div>\n )\n}\n\nfunction AiUiPartRenderer({\n part,\n registry,\n onMutationRequested,\n}: {\n part: ServerEmittedUiPartRef\n registry: AiUiPartRegistry\n onMutationRequested?: (pendingActionId: string) => void\n}) {\n const Component = registry.resolve(part.componentId)\n const isReserved = isReservedAiUiPartId(part.componentId)\n React.useEffect(() => {\n if (Component) return\n if (isReserved) return\n try {\n // eslint-disable-next-line no-console\n console.warn(\n `[AiChat] No component registered for UI part \"${part.componentId}\".`,\n )\n } catch {\n // noop\n }\n }, [Component, part.componentId, isReserved])\n React.useEffect(() => {\n if (part.pendingActionId) {\n onMutationRequested?.(part.pendingActionId)\n }\n }, [part.pendingActionId, onMutationRequested])\n if (!Component) {\n return <UnknownUiPartPlaceholder componentId={part.componentId} />\n }\n return (\n <Component\n componentId={part.componentId}\n payload={part.payload}\n pendingActionId={part.pendingActionId}\n />\n )\n}\n\nfunction WelcomeState({\n title,\n description,\n suggestions,\n onSuggestionClick,\n}: {\n title?: string\n description?: string\n suggestions?: AiChatSuggestion[]\n onSuggestionClick: (prompt: string) => void\n}) {\n const t = useT()\n const heading = title ?? t('ai_assistant.chat.welcomeTitle', 'How can I help?')\n const desc =\n description ??\n t(\n 'ai_assistant.chat.welcomeDescription',\n 'Ask me anything about your data. Here are some things I can do:',\n )\n return (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-4 px-4 py-8\">\n <div\n className=\"flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary\"\n aria-hidden\n >\n <Bot className=\"size-6\" />\n </div>\n <div className=\"space-y-1 text-center\">\n <h3 className=\"text-sm font-semibold\">{heading}</h3>\n <p className=\"text-xs text-muted-foreground\">{desc}</p>\n </div>\n {suggestions && suggestions.length > 0 ? (\n <div className=\"flex w-full max-w-md flex-col gap-2\" data-ai-chat-suggestions=\"\">\n {suggestions.map((suggestion, index) => (\n <button\n key={index}\n type=\"button\"\n className=\"flex items-center gap-2 rounded-lg border border-border bg-card px-3 py-2.5 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground\"\n onClick={() => onSuggestionClick(suggestion.prompt)}\n data-ai-chat-suggestion={index}\n >\n {suggestion.icon ? (\n <span className=\"shrink-0 text-muted-foreground\" aria-hidden>\n {suggestion.icon}\n </span>\n ) : null}\n <span>{suggestion.label}</span>\n </button>\n ))}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction ContextItemsPill({ items }: { items: AiChatContextItem[] }) {\n if (items.length === 0) return null\n return (\n <div\n className=\"flex flex-wrap gap-1.5 border-b border-border px-3 py-2\"\n data-ai-chat-context-items=\"\"\n >\n {items.map((item, index) => (\n <span\n key={index}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground\"\n data-ai-chat-context-item={index}\n title={item.detail}\n >\n {item.label}\n </span>\n ))}\n </div>\n )\n}\n\n/**\n * Embeddable AI chat component. Binds to the dispatcher route\n * `POST /api/ai_assistant/ai/chat?agent=<module>.<agent>` via\n * {@link createAiAgentTransport}. Phase 2 WS-A deliverable (Step 4.1).\n *\n * - Keyboard: `Enter` submits; `Shift+Enter` inserts a newline; `Escape`\n * aborts streaming (or blurs the composer when idle).\n * - Error envelopes from the dispatcher surface as `Alert` + `onError`.\n * - UI parts render via the client-side registry; unknown parts render a\n * neutral placeholder chip so mutation-card slots reserved for Phase 3\n * never throw before their implementations land.\n */\nexport function AiChat({\n agent,\n apiPath,\n pageContext,\n attachmentIds,\n initialMessages,\n debug,\n className,\n placeholder,\n onMutationRequested,\n onError,\n registry,\n uiParts: uiPartsProp,\n debugTools,\n debugPromptSections,\n conversationId,\n suggestions,\n contextItems,\n welcomeTitle,\n welcomeDescription,\n}: AiChatProps) {\n const t = useT()\n const textareaRef = React.useRef<HTMLTextAreaElement | null>(null)\n const transcriptRef = React.useRef<HTMLDivElement | null>(null)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n const [input, setInput] = React.useState('')\n type PendingAttachment = {\n file: File\n attachmentId?: string\n /**\n * Base64 data URL of the image (capped by PREVIEW_DATA_URL_MAX_BYTES).\n * Stored on the message so the preview survives a reload \u2014 durable\n * server URLs were intentionally avoided because the LLM provider can\n * never reach a localhost dev URL anyway, and HTTP fetches add latency\n * the chat doesn't need.\n */\n previewDataUrl?: string\n error?: string\n }\n const [pendingFiles, setPendingFiles] = React.useState<PendingAttachment[]>([])\n const upload = useAiChatUpload()\n const isUploading = upload.busy\n\n const uploadedAttachmentIds = React.useMemo(\n () =>\n pendingFiles\n .map((entry) => entry.attachmentId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0),\n [pendingFiles],\n )\n\n const allAttachmentIds = React.useMemo(\n () => [...(attachmentIds ?? []), ...uploadedAttachmentIds],\n [attachmentIds, uploadedAttachmentIds],\n )\n\n const chat = useAiChat({\n agent,\n apiPath,\n pageContext,\n attachmentIds: allAttachmentIds.length > 0 ? allAttachmentIds : undefined,\n debug,\n initialMessages,\n onError,\n conversationId,\n })\n\n const isStreaming = chat.status === 'streaming'\n const isSubmitting = chat.status === 'submitting'\n const isBusy = isStreaming || isSubmitting\n\n // Surface a \"Thinking...\" placeholder so the chat does not look frozen.\n // Visible whenever ANY of the following is true while a turn is in flight:\n // (a) we're still in the submit phase before the first stream chunk\n // (b) streaming, but no content / reasoning / tool calls have arrived yet\n // (c) streaming, and at least one tool call is still in `pending` state\n // (the model is waiting on a tool result \u2014 the previous version\n // treated `toolCalls.length > 0` as \"has content\" and hid the\n // indicator the moment the first tool started, even though the\n // model had not produced any user-visible output yet)\n // (d) streaming, and the last visible event was a finished tool call\n // \u2014 the model is reasoning about the result before emitting more\n // text or kicking off the next tool\n // (e) streaming, but no delta has landed in the last ~300 ms (idle gap)\n const lastAssistant = React.useMemo(() => {\n for (let index = chat.messages.length - 1; index >= 0; index -= 1) {\n const candidate = chat.messages[index]\n if (candidate?.role === 'assistant') return candidate\n }\n return null\n }, [chat.messages])\n\n const trimmedContent = lastAssistant?.content?.trim() ?? ''\n const hasReasoning = !!(lastAssistant?.reasoning && lastAssistant.reasoning.length > 0)\n const toolCalls = lastAssistant?.toolCalls ?? []\n const hasPendingToolCall = toolCalls.some((call) => call.state === 'pending')\n const hasCompletedToolCall = toolCalls.some(\n (call) => call.state === 'complete' || call.state === 'error',\n )\n const hasAnyVisibleSignal = !!(\n trimmedContent || hasReasoning || toolCalls.length > 0\n )\n\n const assistantStreamSnapshot = React.useMemo(() => {\n if (!lastAssistant) return ''\n const toolSig = toolCalls\n .map((call) => `${call.id}:${call.state}:${call.output != null ? 1 : 0}`)\n .join('|')\n return [\n lastAssistant.id,\n lastAssistant.content?.length ?? 0,\n lastAssistant.reasoning?.length ?? 0,\n lastAssistant.reasoningStreaming ? 1 : 0,\n toolSig,\n ].join('#')\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [lastAssistant])\n\n const lastStreamUpdateRef = React.useRef<number>(Date.now())\n const lastSnapshotRef = React.useRef<string>('')\n const [, setStreamTick] = React.useState(0)\n\n React.useEffect(() => {\n if (assistantStreamSnapshot !== lastSnapshotRef.current) {\n lastSnapshotRef.current = assistantStreamSnapshot\n lastStreamUpdateRef.current = Date.now()\n setStreamTick((value) => value + 1)\n }\n }, [assistantStreamSnapshot])\n\n React.useEffect(() => {\n if (!isStreaming && !isSubmitting) return\n const interval = window.setInterval(() => {\n setStreamTick((value) => value + 1)\n }, 200)\n return () => window.clearInterval(interval)\n }, [isStreaming, isSubmitting])\n\n const idleDuringStream =\n isStreaming && Date.now() - lastStreamUpdateRef.current >= 300\n\n const showThinkingIndicator =\n isSubmitting ||\n (isStreaming &&\n (\n !hasAnyVisibleSignal ||\n hasPendingToolCall ||\n // Tool just returned and the model hasn't started speaking yet.\n (hasCompletedToolCall && !trimmedContent) ||\n idleDuringStream\n ))\n\n const activeRegistry = registry ?? defaultAiUiPartRegistry\n\n // Reserved UI parts. Phase 3 will populate this from the streamed response;\n // for Phase 2 WS-A it stays empty unless the host surfaces test/debug parts\n // via the optional `uiParts` prop so the registry resolution path can be\n // exercised without waiting for the runtime emitter.\n const uiParts: ServerEmittedUiPartRef[] = React.useMemo(\n () => (uiPartsProp ?? []).map((part) => ({\n componentId: part.componentId,\n payload: part.payload,\n pendingActionId: part.pendingActionId,\n })),\n [uiPartsProp],\n )\n\n const hasUploadingFiles = React.useMemo(\n () => pendingFiles.some((entry) => !entry.attachmentId && !entry.error),\n [pendingFiles],\n )\n\n const handleSendMessage = React.useCallback(\n (text: string) => {\n if (!text.trim() || isBusy) return\n // Block send while any attachment is still uploading. Without this guard\n // the message would ship with an empty attachmentIds list (the chip is\n // visible but the server hasn't returned an id yet), the model would\n // never see the file, and `setPendingFiles([])` below would erase the\n // chip \u2014 so the upload finishes into the void. Surface the wait via the\n // disabled Send button + composer hint instead.\n if (hasUploadingFiles || isUploading) return\n const filesToAttach = pendingFiles.map((entry): AiChatMessageFile => {\n const isImage = entry.file.type.startsWith('image/')\n const fallback = isImage ? URL.createObjectURL(entry.file) : undefined\n return {\n name: entry.file.name,\n type: entry.file.type,\n previewUrl: isImage ? (entry.previewDataUrl ?? fallback) : undefined,\n }\n })\n setInput('')\n setPendingFiles([])\n void chat.sendMessage(text, filesToAttach.length > 0 ? filesToAttach : undefined)\n },\n [chat, hasUploadingFiles, isBusy, isUploading, pendingFiles],\n )\n\n const handleSubmit = React.useCallback(() => {\n handleSendMessage(input)\n }, [handleSendMessage, input])\n\n // Listen for \"Fix with AI\" requests dispatched by the failure variant\n // of `MutationResultCard`. Any rendered failure card can fire a custom\n // DOM event with the prompt \u2014 this side-steps having to thread a\n // sendMessage callback through the UI-part registry while keeping the\n // chat the single owner of message creation. Idempotency is handled\n // server-side: `prepareMutation` only dedupes against active `pending`\n // rows, so a retry after a terminal failure always produces a fresh\n // pending action.\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const handler = (event: Event) => {\n const detail = (event as CustomEvent<{ message?: string }>).detail\n const message = detail?.message\n if (typeof message !== 'string' || message.trim().length === 0) return\n handleSendMessage(message)\n }\n window.addEventListener('om-ai-chat-fix-request', handler as EventListener)\n return () => {\n window.removeEventListener('om-ai-chat-fix-request', handler as EventListener)\n }\n }, [handleSendMessage])\n\n const cancelOrBlur = React.useCallback(() => {\n if (isBusy) {\n chat.cancel()\n return\n }\n textareaRef.current?.blur()\n }, [chat, isBusy])\n\n const handleFileSelect = React.useCallback(\n async (event: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? [])\n if (files.length === 0) return\n const queued: PendingAttachment[] = files.map((file) => ({ file }))\n setPendingFiles((prev) => [...prev, ...queued])\n const [previewResults, result] = await Promise.all([\n Promise.all(files.map((file) => readFileAsDataUrl(file))),\n upload.upload(files),\n ])\n // Pair upload outcomes back to chips by input INDEX, not by filename.\n // The server may sanitize the uploaded name (whitespace, unicode,\n // dangerous characters), and two files in the same batch can share a\n // name \u2014 both cases broke the previous Map-by-fileName matching and\n // left the chip stuck on the spinner forever.\n const idByIndex = new Map<number, string>()\n for (const item of result.items) {\n if (typeof item.inputIndex === 'number') idByIndex.set(item.inputIndex, item.attachmentId)\n }\n const errorByIndex = new Map<number, string>()\n for (const failure of result.failed) {\n if (typeof failure.inputIndex === 'number') errorByIndex.set(failure.inputIndex, failure.message)\n }\n setPendingFiles((prev) => {\n const next = prev.slice()\n const baseIndex = next.length - files.length\n for (let offset = 0; offset < files.length; offset += 1) {\n const index = baseIndex + offset\n if (index < 0) continue\n const entry = next[index]\n if (!entry) continue\n const dataUrl = previewResults[offset]\n const patch: PendingAttachment = {\n ...entry,\n previewDataUrl: dataUrl ?? entry.previewDataUrl,\n }\n if (!patch.attachmentId) {\n const id = idByIndex.get(offset)\n if (id) {\n patch.attachmentId = id\n patch.error = undefined\n } else {\n // Defensive fallback: if neither success nor failure carried an\n // index for this slot (older transports, partial outcome), the\n // chip would otherwise stay on the spinner. Mark it as a\n // generic error so the user can remove it and retry instead of\n // staring at a dead spinner that also blocks the Send button.\n const explicitError = errorByIndex.get(offset)\n patch.error =\n explicitError ??\n 'Upload finished without a server response. Remove the file and try again.'\n }\n }\n next[index] = patch\n }\n return next\n })\n if (fileInputRef.current) fileInputRef.current.value = ''\n },\n [upload],\n )\n\n const removePendingFile = React.useCallback((index: number) => {\n setPendingFiles((prev) => prev.filter((_, i) => i !== index))\n }, [])\n\n const { handleKeyDown } = useAiShortcuts({\n onSubmit: handleSubmit,\n onCancel: cancelOrBlur,\n })\n\n React.useEffect(() => {\n textareaRef.current?.focus()\n }, [])\n\n // Sticky-bottom autoscroll: only re-pin to the bottom when the user is\n // already there (or within a small tolerance). If they have scrolled up to\n // read an earlier part of a long response, every streaming delta would\n // otherwise yank them back to the tail and make the message look truncated.\n // Tolerance is generous enough to absorb sub-pixel rounding, but tight\n // enough that an intentional scroll-up keeps the user where they want.\n const stickToBottomRef = React.useRef(true)\n const SCROLL_STICK_TOLERANCE_PX = 64\n\n React.useEffect(() => {\n const node = transcriptRef.current\n if (!node) return\n const handleScroll = () => {\n const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight\n stickToBottomRef.current = distanceFromBottom <= SCROLL_STICK_TOLERANCE_PX\n }\n node.addEventListener('scroll', handleScroll, { passive: true })\n return () => node.removeEventListener('scroll', handleScroll)\n }, [])\n\n React.useEffect(() => {\n const node = transcriptRef.current\n if (!node) return\n if (!stickToBottomRef.current) return\n node.scrollTop = node.scrollHeight\n }, [chat.messages])\n\n // Mark the body so floating UI surfaces (e.g. the demo feedback FAB) can\n // hide themselves while the chat is open and would otherwise overlay the\n // composer's send button. CSS lives in `apps/mercato/src/app/globals.css`\n // alongside the column-chooser precedent.\n React.useEffect(() => {\n if (typeof document === 'undefined') return\n const previous = document.body.getAttribute('data-ai-chat-open')\n document.body.setAttribute('data-ai-chat-open', 'true')\n return () => {\n if (previous === null) {\n document.body.removeAttribute('data-ai-chat-open')\n } else {\n document.body.setAttribute('data-ai-chat-open', previous)\n }\n }\n }, [])\n\n const handleNewConversation = React.useCallback(() => {\n chat.reset()\n setInput('')\n setPendingFiles([])\n upload.reset()\n setTimeout(() => textareaRef.current?.focus(), 0)\n }, [chat, upload])\n\n const resolvedPlaceholder =\n placeholder ?? t('ai_assistant.chat.composerPlaceholder', 'Message the AI agent...')\n\n const errorVariant = mapErrorCodeToVariant(chat.error?.code)\n\n return (\n <section\n className={cn(\n 'flex h-full min-h-[320px] flex-col gap-3 rounded-lg border border-border bg-background p-3',\n className,\n )}\n aria-label={t('ai_assistant.chat.regionLabel', 'AI chat')}\n data-ai-chat-agent={agent}\n data-ai-chat-conversation-id={chat.conversationId}\n >\n <div className=\"flex items-center justify-between gap-2 border-b border-border pb-2\">\n <div className=\"flex flex-1 items-center gap-2 text-xs text-muted-foreground\">\n {contextItems && contextItems.length > 0 ? (\n <ContextItemsPill items={contextItems} />\n ) : (\n <span className=\"font-mono opacity-70\" aria-hidden>\n {chat.conversationId.slice(0, 8)}\n </span>\n )}\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleNewConversation}\n disabled={isBusy}\n aria-label={t('ai_assistant.chat.newConversation', 'Start new conversation')}\n title={t('ai_assistant.chat.newConversation', 'Start new conversation')}\n data-ai-chat-new-conversation=\"\"\n >\n <Plus className=\"size-4\" aria-hidden />\n </IconButton>\n </div>\n <div\n ref={transcriptRef}\n role=\"log\"\n aria-live=\"polite\"\n aria-label={t('ai_assistant.chat.transcriptLabel', 'Chat transcript')}\n className=\"flex-1 space-y-2 overflow-y-auto pr-1\"\n >\n {chat.messages.length === 0 ? (\n <WelcomeState\n title={welcomeTitle}\n description={welcomeDescription}\n suggestions={suggestions}\n onSuggestionClick={handleSendMessage}\n />\n ) : (\n chat.messages.map((message) => (\n <MessageRow\n key={message.id}\n message={message}\n registry={activeRegistry}\n onMutationRequested={onMutationRequested}\n />\n ))\n )}\n {uiParts.map((part, index) => (\n <AiUiPartRenderer\n key={`${part.componentId}-${index}`}\n part={part}\n registry={activeRegistry}\n onMutationRequested={onMutationRequested}\n />\n ))}\n {showThinkingIndicator ? (\n <div\n className=\"flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground\"\n data-ai-chat-state=\"thinking\"\n >\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n <span>{t('ai_assistant.chat.thinking', 'Thinking...')}</span>\n </div>\n ) : null}\n </div>\n\n {chat.error ? (\n <Alert variant={errorVariant} data-ai-chat-error={chat.error.code ?? 'unknown'}>\n <AlertTitle>\n {t('ai_assistant.chat.errorTitle', 'Agent dispatch failed')}\n </AlertTitle>\n <AlertDescription>\n {chat.error.code ? (\n <span className=\"mr-2 font-mono text-xs\">{chat.error.code}</span>\n ) : null}\n {chat.error.message}\n </AlertDescription>\n </Alert>\n ) : null}\n\n <form\n className=\"flex flex-col gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n handleSubmit()\n }}\n >\n <Label\n htmlFor=\"ai-chat-composer\"\n className=\"sr-only\"\n >\n {t('ai_assistant.chat.composerLabel', 'Message composer')}\n </Label>\n {pendingFiles.length > 0 ? (\n <div className=\"flex flex-wrap gap-1.5 rounded-md border border-border bg-muted/30 px-2 py-1.5\" data-ai-chat-attachments=\"\">\n {pendingFiles.map((entry, index) => (\n <span\n key={index}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-background px-2 py-0.5 text-xs\"\n title={entry.error ? entry.error : undefined}\n data-ai-chat-attachment-state={\n entry.error ? 'error' : entry.attachmentId ? 'ready' : 'uploading'\n }\n >\n <Paperclip className=\"size-3 text-muted-foreground\" aria-hidden />\n <span className=\"max-w-[120px] truncate\">{entry.file.name}</span>\n {!entry.attachmentId && !entry.error ? (\n <Loader2 className=\"size-3 animate-spin text-muted-foreground\" aria-hidden />\n ) : null}\n <button\n type=\"button\"\n className=\"ml-0.5 rounded-full p-0.5 hover:bg-muted\"\n onClick={() => removePendingFile(index)}\n aria-label={t('ai_assistant.chat.removeFile', 'Remove file')}\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n {isUploading ? <Loader2 className=\"size-3 animate-spin text-muted-foreground\" aria-hidden /> : null}\n </div>\n ) : null}\n <Textarea\n id=\"ai-chat-composer\"\n ref={textareaRef}\n value={input}\n placeholder={resolvedPlaceholder}\n onChange={(event) => setInput(event.target.value)}\n onKeyDown={handleKeyDown}\n rows={3}\n aria-label={t('ai_assistant.chat.composerLabel', 'Message composer')}\n className=\"resize-none\"\n />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\"image/*,.pdf,.doc,.docx,.txt,.csv\"\n className=\"hidden\"\n onChange={handleFileSelect}\n data-ai-chat-file-input=\"\"\n />\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isBusy || isUploading}\n aria-label={t('ai_assistant.chat.attachFile', 'Attach file')}\n >\n <Paperclip className=\"size-4\" aria-hidden />\n </IconButton>\n <p className=\"text-xs text-muted-foreground\">\n {hasUploadingFiles || isUploading\n ? t(\n 'ai_assistant.chat.uploadingHint',\n 'Uploading attachments\u2026 Send is disabled until they finish.',\n )\n : t(\n 'ai_assistant.chat.shortcutHint',\n 'Press Enter to send, Shift+Enter for new line.',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {isStreaming ? (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => chat.cancel()}\n aria-label={t('ai_assistant.chat.cancel', 'Cancel streaming response')}\n >\n <Square className=\"size-4\" aria-hidden />\n </IconButton>\n ) : null}\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={\n isBusy ||\n isUploading ||\n hasUploadingFiles ||\n input.trim().length === 0\n }\n aria-label={\n hasUploadingFiles || isUploading\n ? t('ai_assistant.chat.sendWaitingForUpload', 'Waiting for upload to finish\u2026')\n : t('ai_assistant.chat.send', 'Send message')\n }\n title={\n hasUploadingFiles || isUploading\n ? t('ai_assistant.chat.sendWaitingForUpload', 'Waiting for upload to finish\u2026')\n : undefined\n }\n >\n <Send className=\"size-4\" aria-hidden />\n <span>{t('ai_assistant.chat.send', 'Send message')}</span>\n </Button>\n </div>\n </div>\n </form>\n\n {debug ? (\n <AiChatDebugPanel\n tools={debugTools}\n promptSections={debugPromptSections}\n lastRequestDebug={chat.lastRequestDebug}\n lastResponseDebug={chat.lastResponseDebug}\n status={chat.status}\n errorCode={chat.error?.code}\n />\n ) : null}\n </section>\n )\n}\n\ninterface DebugPanelProps {\n tools?: AiChatDebugTool[]\n promptSections?: AiChatDebugPromptSection[]\n lastRequestDebug: { url: string; body: unknown } | null\n lastResponseDebug: { status: number; text: string } | null\n status: 'idle' | 'submitting' | 'streaming'\n errorCode?: string\n}\n\nfunction AiChatDebugPanel({\n tools,\n promptSections,\n lastRequestDebug,\n lastResponseDebug,\n status,\n errorCode,\n}: DebugPanelProps) {\n const t = useT()\n return (\n <div\n className=\"flex flex-col gap-2 rounded-md border border-border bg-muted/60 p-2 text-xs\"\n data-ai-chat-debug=\"true\"\n >\n <div className=\"font-semibold\">\n {t('ai_assistant.chat.debug.panelTitle', 'Debug panel')}\n </div>\n\n <details className=\"rounded border border-border bg-background\" data-ai-chat-debug-section=\"tools\" open>\n <summary className=\"cursor-pointer px-2 py-1 font-semibold\">\n {t('ai_assistant.chat.debug.toolsSection', 'Resolved tools')}\n {tools ? (\n <span className=\"ml-2 font-mono text-muted-foreground\">({tools.length})</span>\n ) : null}\n </summary>\n <div className=\"px-2 pb-2\">\n {tools && tools.length > 0 ? (\n <ul className=\"flex flex-col gap-1\" data-ai-chat-debug-tools>\n {tools.map((tool) => (\n <li\n key={tool.name}\n className=\"flex flex-col rounded border border-border bg-muted/40 px-2 py-1\"\n data-ai-chat-debug-tool={tool.name}\n >\n <span className=\"font-mono\">{tool.name}</span>\n {tool.displayName ? (\n <span className=\"text-muted-foreground\">{tool.displayName}</span>\n ) : null}\n <span className=\"mt-1 flex flex-wrap gap-2 text-muted-foreground\">\n <span>\n {tool.isMutation\n ? t('ai_assistant.chat.debug.toolMutation', 'mutation')\n : t('ai_assistant.chat.debug.toolRead', 'read')}\n </span>\n {tool.requiredFeatures && tool.requiredFeatures.length > 0 ? (\n <span className=\"font-mono\">\n [{tool.requiredFeatures.join(', ')}]\n </span>\n ) : (\n <span>\n {t('ai_assistant.chat.debug.toolNoFeatures', 'no required features')}\n </span>\n )}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.chat.debug.toolsEmpty',\n 'No tools resolved for this agent yet.',\n )}\n </p>\n )}\n </div>\n </details>\n\n <details\n className=\"rounded border border-border bg-background\"\n data-ai-chat-debug-section=\"promptSections\"\n >\n <summary className=\"cursor-pointer px-2 py-1 font-semibold\">\n {t('ai_assistant.chat.debug.promptSection', 'Prompt sections')}\n {promptSections ? (\n <span className=\"ml-2 font-mono text-muted-foreground\">({promptSections.length})</span>\n ) : null}\n </summary>\n <div className=\"px-2 pb-2\">\n {promptSections && promptSections.length > 0 ? (\n <ul className=\"flex flex-col gap-1\" data-ai-chat-debug-prompt-sections>\n {promptSections.map((section) => (\n <li\n key={section.id}\n className=\"rounded border border-border bg-muted/40 px-2 py-1\"\n data-ai-chat-debug-prompt-section-id={section.id}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-mono\">{section.id}</span>\n <span className=\"text-muted-foreground\">\n {section.source === 'override'\n ? t('ai_assistant.chat.debug.promptOverride', 'override')\n : section.source === 'placeholder'\n ? t('ai_assistant.chat.debug.promptPlaceholder', 'placeholder')\n : t('ai_assistant.chat.debug.promptDefault', 'default')}\n </span>\n </div>\n {section.text ? (\n <pre className=\"mt-1 max-h-24 overflow-auto whitespace-pre-wrap font-mono text-muted-foreground\">\n {section.text}\n </pre>\n ) : null}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.chat.debug.promptEmpty',\n 'No prompt sections resolved for this agent.',\n )}\n </p>\n )}\n </div>\n </details>\n\n <details\n className=\"rounded border border-border bg-background\"\n data-ai-chat-debug-section=\"lastRequest\"\n >\n <summary className=\"cursor-pointer px-2 py-1 font-semibold\">\n {t('ai_assistant.chat.debug.lastRequestSection', 'Last request')}\n </summary>\n <div className=\"px-2 pb-2\">\n {lastRequestDebug ? (\n <pre\n className=\"max-h-40 overflow-auto whitespace-pre-wrap font-mono\"\n data-ai-chat-debug-last-request\n >\n {JSON.stringify(lastRequestDebug, null, 2)}\n </pre>\n ) : (\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.chat.debug.lastRequestEmpty',\n 'No request has been sent yet.',\n )}\n </p>\n )}\n </div>\n </details>\n\n <details\n className=\"rounded border border-border bg-background\"\n data-ai-chat-debug-section=\"lastResponse\"\n >\n <summary className=\"cursor-pointer px-2 py-1 font-semibold\">\n {t('ai_assistant.chat.debug.lastResponseSection', 'Last response')}\n </summary>\n <div className=\"px-2 pb-2\">\n {lastResponseDebug ? (\n <pre\n className=\"max-h-40 overflow-auto whitespace-pre-wrap font-mono\"\n data-ai-chat-debug-last-response\n >\n {JSON.stringify(\n { status: lastResponseDebug.status, text: lastResponseDebug.text, errorCode },\n null,\n 2,\n )}\n </pre>\n ) : (\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.chat.debug.lastResponseEmpty',\n 'No response received yet.',\n )}\n </p>\n )}\n </div>\n </details>\n\n <div className=\"text-muted-foreground\" data-ai-chat-debug-status={status}>\n {t('ai_assistant.chat.debug.statusLabel', 'Status:')}{' '}\n <span className=\"font-mono\">{status}</span>\n </div>\n </div>\n )\n}\n\nexport default AiChat\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,297 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ const STORAGE_KEY = "om-ai-chat-sessions-v1";
5
+ const HISTORY_LIMIT = 50;
6
+ const AiChatSessionsContext = React.createContext(null);
7
+ function makeId() {
8
+ const g = globalThis;
9
+ if (g.crypto && typeof g.crypto.randomUUID === "function") {
10
+ try {
11
+ return g.crypto.randomUUID();
12
+ } catch {
13
+ }
14
+ }
15
+ const rand = () => Math.random().toString(16).slice(2, 10);
16
+ return `${Date.now().toString(16)}-${rand()}-${rand()}`;
17
+ }
18
+ function readPersisted() {
19
+ if (typeof window === "undefined") return { sessions: [], activeByAgent: {} };
20
+ try {
21
+ const raw = window.localStorage.getItem(STORAGE_KEY);
22
+ if (!raw) return { sessions: [], activeByAgent: {} };
23
+ const parsed = JSON.parse(raw);
24
+ const sessions = Array.isArray(parsed?.sessions) ? parsed.sessions.filter((entry) => {
25
+ if (!entry || typeof entry !== "object") return false;
26
+ const value = entry;
27
+ return typeof value.id === "string" && typeof value.agentId === "string" && typeof value.conversationId === "string" && typeof value.createdAt === "number" && typeof value.lastUsedAt === "number" && (value.status === "open" || value.status === "closed");
28
+ }).map((entry) => {
29
+ const value = entry;
30
+ const candidate = value.name;
31
+ return {
32
+ ...entry,
33
+ name: typeof candidate === "string" ? candidate : void 0
34
+ };
35
+ }) : [];
36
+ const activeByAgent = parsed?.activeByAgent && typeof parsed.activeByAgent === "object" ? Object.fromEntries(
37
+ Object.entries(parsed.activeByAgent).filter(
38
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
39
+ )
40
+ ) : {};
41
+ return { sessions, activeByAgent };
42
+ } catch {
43
+ return { sessions: [], activeByAgent: {} };
44
+ }
45
+ }
46
+ function writePersisted(state) {
47
+ if (typeof window === "undefined") return;
48
+ try {
49
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
50
+ } catch {
51
+ }
52
+ }
53
+ function AiChatSessionsProvider({ children }) {
54
+ const [state, setState] = React.useState(() => readPersisted());
55
+ React.useEffect(() => {
56
+ writePersisted(state);
57
+ }, [state]);
58
+ const update = React.useCallback(
59
+ (mutator) => {
60
+ setState((prev) => mutator(prev));
61
+ },
62
+ []
63
+ );
64
+ const getOpenSessions = React.useCallback(
65
+ (agentId) => state.sessions.filter((s) => s.agentId === agentId && s.status === "open").sort((a, b) => a.createdAt - b.createdAt),
66
+ [state.sessions]
67
+ );
68
+ const getClosedSessions = React.useCallback(
69
+ (agentId, limit = HISTORY_LIMIT) => state.sessions.filter((s) => s.agentId === agentId && s.status === "closed").sort((a, b) => b.lastUsedAt - a.lastUsedAt).slice(0, limit),
70
+ [state.sessions]
71
+ );
72
+ const getActiveSession = React.useCallback(
73
+ (agentId) => {
74
+ const id = state.activeByAgent[agentId];
75
+ if (!id) return null;
76
+ const session = state.sessions.find((s) => s.id === id);
77
+ if (!session || session.status !== "open") return null;
78
+ return session;
79
+ },
80
+ [state.activeByAgent, state.sessions]
81
+ );
82
+ const createSession = React.useCallback((agentId) => {
83
+ const session = {
84
+ id: makeId(),
85
+ agentId,
86
+ conversationId: makeId(),
87
+ createdAt: Date.now(),
88
+ lastUsedAt: Date.now(),
89
+ status: "open"
90
+ };
91
+ update((prev) => ({
92
+ sessions: [...prev.sessions, session],
93
+ activeByAgent: { ...prev.activeByAgent, [agentId]: session.id }
94
+ }));
95
+ return session;
96
+ }, [update]);
97
+ const closeSession = React.useCallback(
98
+ (sessionId) => {
99
+ update((prev) => {
100
+ const target = prev.sessions.find((s) => s.id === sessionId);
101
+ if (!target) return prev;
102
+ const sessions = prev.sessions.map(
103
+ (s) => s.id === sessionId ? { ...s, status: "closed", lastUsedAt: Date.now() } : s
104
+ );
105
+ const activeByAgent = { ...prev.activeByAgent };
106
+ if (activeByAgent[target.agentId] === sessionId) {
107
+ const fallback = sessions.filter((s) => s.agentId === target.agentId && s.status === "open").sort((a, b) => b.lastUsedAt - a.lastUsedAt)[0];
108
+ if (fallback) {
109
+ activeByAgent[target.agentId] = fallback.id;
110
+ } else {
111
+ delete activeByAgent[target.agentId];
112
+ }
113
+ }
114
+ return { sessions, activeByAgent };
115
+ });
116
+ },
117
+ [update]
118
+ );
119
+ const reopenSession = React.useCallback(
120
+ (sessionId) => {
121
+ update((prev) => {
122
+ const target = prev.sessions.find((s) => s.id === sessionId);
123
+ if (!target) return prev;
124
+ const sessions = prev.sessions.map(
125
+ (s) => s.id === sessionId ? { ...s, status: "open", lastUsedAt: Date.now() } : s
126
+ );
127
+ return {
128
+ sessions,
129
+ activeByAgent: { ...prev.activeByAgent, [target.agentId]: sessionId }
130
+ };
131
+ });
132
+ },
133
+ [update]
134
+ );
135
+ const setActiveSession = React.useCallback(
136
+ (sessionId) => {
137
+ update((prev) => {
138
+ const target = prev.sessions.find((s) => s.id === sessionId);
139
+ if (!target || target.status !== "open") return prev;
140
+ const sessions = prev.sessions.map(
141
+ (s) => s.id === sessionId ? { ...s, lastUsedAt: Date.now() } : s
142
+ );
143
+ return {
144
+ sessions,
145
+ activeByAgent: { ...prev.activeByAgent, [target.agentId]: sessionId }
146
+ };
147
+ });
148
+ },
149
+ [update]
150
+ );
151
+ const renameSession = React.useCallback(
152
+ (sessionId, name) => {
153
+ const trimmed = name.trim();
154
+ update((prev) => ({
155
+ ...prev,
156
+ sessions: prev.sessions.map(
157
+ (s) => s.id === sessionId ? { ...s, name: trimmed.length > 0 ? trimmed : void 0 } : s
158
+ )
159
+ }));
160
+ },
161
+ [update]
162
+ );
163
+ const touchSession = React.useCallback(
164
+ (sessionId) => {
165
+ update((prev) => ({
166
+ ...prev,
167
+ sessions: prev.sessions.map(
168
+ (s) => s.id === sessionId ? { ...s, lastUsedAt: Date.now() } : s
169
+ )
170
+ }));
171
+ },
172
+ [update]
173
+ );
174
+ const ensureSession = React.useCallback(
175
+ (agentId) => {
176
+ let resolved = null;
177
+ let mintedSession = null;
178
+ setState((prev) => {
179
+ const activeId = prev.activeByAgent[agentId];
180
+ if (activeId) {
181
+ const active = prev.sessions.find((s) => s.id === activeId);
182
+ if (active && active.status === "open") {
183
+ resolved = active;
184
+ return prev;
185
+ }
186
+ }
187
+ const anyOpen = prev.sessions.filter((s) => s.agentId === agentId && s.status === "open").sort((a, b) => b.lastUsedAt - a.lastUsedAt)[0];
188
+ if (anyOpen) {
189
+ resolved = anyOpen;
190
+ return {
191
+ ...prev,
192
+ activeByAgent: { ...prev.activeByAgent, [agentId]: anyOpen.id }
193
+ };
194
+ }
195
+ if (!mintedSession) {
196
+ mintedSession = {
197
+ id: makeId(),
198
+ agentId,
199
+ conversationId: makeId(),
200
+ createdAt: Date.now(),
201
+ lastUsedAt: Date.now(),
202
+ status: "open"
203
+ };
204
+ }
205
+ resolved = mintedSession;
206
+ return {
207
+ sessions: [...prev.sessions, mintedSession],
208
+ activeByAgent: { ...prev.activeByAgent, [agentId]: mintedSession.id }
209
+ };
210
+ });
211
+ return resolved;
212
+ },
213
+ []
214
+ );
215
+ const api = React.useMemo(
216
+ () => ({
217
+ state,
218
+ getOpenSessions,
219
+ getClosedSessions,
220
+ getActiveSession,
221
+ createSession,
222
+ closeSession,
223
+ reopenSession,
224
+ setActiveSession,
225
+ renameSession,
226
+ touchSession,
227
+ ensureSession
228
+ }),
229
+ [
230
+ state,
231
+ getOpenSessions,
232
+ getClosedSessions,
233
+ getActiveSession,
234
+ createSession,
235
+ closeSession,
236
+ reopenSession,
237
+ setActiveSession,
238
+ renameSession,
239
+ touchSession,
240
+ ensureSession
241
+ ]
242
+ );
243
+ return /* @__PURE__ */ jsx(AiChatSessionsContext.Provider, { value: api, children });
244
+ }
245
+ function useAiChatSessions() {
246
+ const ctx = React.useContext(AiChatSessionsContext);
247
+ if (ctx) return ctx;
248
+ return {
249
+ state: { sessions: [], activeByAgent: {} },
250
+ getOpenSessions: () => [],
251
+ getClosedSessions: () => [],
252
+ getActiveSession: () => null,
253
+ createSession: (agentId) => ({
254
+ id: "noop",
255
+ agentId,
256
+ conversationId: "noop",
257
+ createdAt: Date.now(),
258
+ lastUsedAt: Date.now(),
259
+ status: "open"
260
+ }),
261
+ closeSession: () => {
262
+ },
263
+ reopenSession: () => {
264
+ },
265
+ setActiveSession: () => {
266
+ },
267
+ renameSession: () => {
268
+ },
269
+ touchSession: () => {
270
+ },
271
+ ensureSession: (agentId) => ({
272
+ id: "noop",
273
+ agentId,
274
+ conversationId: "noop",
275
+ createdAt: Date.now(),
276
+ lastUsedAt: Date.now(),
277
+ status: "open"
278
+ })
279
+ };
280
+ }
281
+ function defaultSessionLabel(session) {
282
+ if (session.name && session.name.trim().length > 0) return session.name.trim();
283
+ const date = new Date(session.createdAt);
284
+ if (Number.isNaN(date.getTime())) return "Session";
285
+ return date.toLocaleString(void 0, {
286
+ month: "short",
287
+ day: "numeric",
288
+ hour: "2-digit",
289
+ minute: "2-digit"
290
+ });
291
+ }
292
+ export {
293
+ AiChatSessionsProvider,
294
+ defaultSessionLabel,
295
+ useAiChatSessions
296
+ };
297
+ //# sourceMappingURL=AiChatSessions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/AiChatSessions.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\n/**\n * Multi-tab AI chat sessions.\n *\n * Each agent can have several concurrent conversation threads (sessions).\n * The provider stores their *metadata* \u2014 id, conversationId, optional\n * user-given name, timestamps, status \u2014 in localStorage, so closing and\n * re-opening the chat pane (or refreshing the page) restores the tabs and\n * their history. The actual messages live in `useAiChat`'s per-conversation\n * persistence slot keyed by `(agent, conversationId)`, which is why every\n * session gets its own UUID even when it shares an agent with the next tab.\n *\n * Sessions are partitioned into:\n * - `open` \u2192 currently shown in the tab strip\n * - `closed` \u2192 hidden but kept for the history dropdown\n *\n * Closing a tab moves it to `closed`. Re-opening from history flips it back\n * to `open` and surfaces it as the active tab. Renaming is a single\n * `name` field; falling back to the formatted creation date when the user\n * never named the session keeps the picker scannable.\n */\n\nimport * as React from 'react'\n\nconst STORAGE_KEY = 'om-ai-chat-sessions-v1'\nconst HISTORY_LIMIT = 50\n\nexport type AiChatSessionStatus = 'open' | 'closed'\n\nexport interface AiChatSession {\n id: string\n agentId: string\n conversationId: string\n name?: string\n createdAt: number\n lastUsedAt: number\n status: AiChatSessionStatus\n}\n\ninterface AiChatSessionsState {\n sessions: AiChatSession[]\n activeByAgent: Record<string, string>\n}\n\ninterface AiChatSessionsApi {\n state: AiChatSessionsState\n /** Returns open sessions for `agentId`, ordered by creation. */\n getOpenSessions: (agentId: string) => AiChatSession[]\n /** Returns closed sessions (history) for `agentId`, newest first. */\n getClosedSessions: (agentId: string, limit?: number) => AiChatSession[]\n /** Returns the active session for `agentId`, or null if none. */\n getActiveSession: (agentId: string) => AiChatSession | null\n /** Creates and activates a fresh open session for `agentId`. */\n createSession: (agentId: string) => AiChatSession\n /** Closes a session (moves to history). If it was active, picks another. */\n closeSession: (sessionId: string) => void\n /** Re-opens a closed session and activates it. */\n reopenSession: (sessionId: string) => void\n /** Activates an open session. */\n setActiveSession: (sessionId: string) => void\n /** Renames a session \u2014 empty / whitespace clears the custom name. */\n renameSession: (sessionId: string, name: string) => void\n /** Bumps `lastUsedAt` on a session (call when activity happens). */\n touchSession: (sessionId: string) => void\n /** Ensures `agentId` has at least one open session and returns its id. */\n ensureSession: (agentId: string) => AiChatSession\n}\n\nconst AiChatSessionsContext = React.createContext<AiChatSessionsApi | null>(null)\n\nfunction makeId(): string {\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto && typeof g.crypto.randomUUID === 'function') {\n try {\n return g.crypto.randomUUID()\n } catch {\n /* fall through */\n }\n }\n const rand = () => Math.random().toString(16).slice(2, 10)\n return `${Date.now().toString(16)}-${rand()}-${rand()}`\n}\n\nfunction readPersisted(): AiChatSessionsState {\n if (typeof window === 'undefined') return { sessions: [], activeByAgent: {} }\n try {\n const raw = window.localStorage.getItem(STORAGE_KEY)\n if (!raw) return { sessions: [], activeByAgent: {} }\n const parsed = JSON.parse(raw) as Partial<AiChatSessionsState> | null\n const sessions = Array.isArray(parsed?.sessions)\n ? (parsed!.sessions as unknown[])\n .filter((entry): entry is AiChatSession => {\n if (!entry || typeof entry !== 'object') return false\n const value = entry as Record<string, unknown>\n return (\n typeof value.id === 'string' &&\n typeof value.agentId === 'string' &&\n typeof value.conversationId === 'string' &&\n typeof value.createdAt === 'number' &&\n typeof value.lastUsedAt === 'number' &&\n (value.status === 'open' || value.status === 'closed')\n )\n })\n .map((entry) => {\n const value = entry as unknown as Record<string, unknown>\n const candidate = value.name\n return {\n ...entry,\n name: typeof candidate === 'string' ? candidate : undefined,\n }\n })\n : []\n const activeByAgent =\n parsed?.activeByAgent && typeof parsed.activeByAgent === 'object'\n ? Object.fromEntries(\n Object.entries(parsed.activeByAgent as Record<string, unknown>).filter(\n (entry): entry is [string, string] =>\n typeof entry[0] === 'string' && typeof entry[1] === 'string',\n ),\n )\n : {}\n return { sessions, activeByAgent }\n } catch {\n return { sessions: [], activeByAgent: {} }\n }\n}\n\nfunction writePersisted(state: AiChatSessionsState): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n } catch {\n /* quota / privacy mode \u2014 drop silently */\n }\n}\n\nexport function AiChatSessionsProvider({ children }: { children: React.ReactNode }) {\n // Hydrate synchronously via a lazy initializer. The previous \"empty\n // state + post-mount load effect\" pattern had a window where the\n // persistence effect ran with the empty closure value (because the\n // hydrate effect's queued setState had not committed yet) and clobbered\n // localStorage with `[]` before the loaded state's re-render wrote it\n // back. The lazy initializer puts the loaded state into the very first\n // render, so the persistence effect always sees the real data.\n // `readPersisted` already short-circuits to an empty object on the\n // server (no `window`), so SSR stays consistent with the bare-bones\n // shell \u2014 actual session-dependent UI only renders after a user\n // interaction opens a chat surface.\n const [state, setState] = React.useState<AiChatSessionsState>(() => readPersisted())\n\n React.useEffect(() => {\n writePersisted(state)\n }, [state])\n\n const update = React.useCallback(\n (mutator: (prev: AiChatSessionsState) => AiChatSessionsState) => {\n setState((prev) => mutator(prev))\n },\n [],\n )\n\n const getOpenSessions = React.useCallback(\n (agentId: string) =>\n state.sessions\n .filter((s) => s.agentId === agentId && s.status === 'open')\n .sort((a, b) => a.createdAt - b.createdAt),\n [state.sessions],\n )\n\n const getClosedSessions = React.useCallback(\n (agentId: string, limit = HISTORY_LIMIT) =>\n state.sessions\n .filter((s) => s.agentId === agentId && s.status === 'closed')\n .sort((a, b) => b.lastUsedAt - a.lastUsedAt)\n .slice(0, limit),\n [state.sessions],\n )\n\n const getActiveSession = React.useCallback(\n (agentId: string) => {\n const id = state.activeByAgent[agentId]\n if (!id) return null\n const session = state.sessions.find((s) => s.id === id)\n if (!session || session.status !== 'open') return null\n return session\n },\n [state.activeByAgent, state.sessions],\n )\n\n const createSession = React.useCallback((agentId: string): AiChatSession => {\n const session: AiChatSession = {\n id: makeId(),\n agentId,\n conversationId: makeId(),\n createdAt: Date.now(),\n lastUsedAt: Date.now(),\n status: 'open',\n }\n update((prev) => ({\n sessions: [...prev.sessions, session],\n activeByAgent: { ...prev.activeByAgent, [agentId]: session.id },\n }))\n return session\n }, [update])\n\n const closeSession = React.useCallback(\n (sessionId: string) => {\n update((prev) => {\n const target = prev.sessions.find((s) => s.id === sessionId)\n if (!target) return prev\n const sessions = prev.sessions.map((s) =>\n s.id === sessionId ? { ...s, status: 'closed' as const, lastUsedAt: Date.now() } : s,\n )\n const activeByAgent = { ...prev.activeByAgent }\n if (activeByAgent[target.agentId] === sessionId) {\n // Pick the next open session for this agent (most-recent first).\n const fallback = sessions\n .filter((s) => s.agentId === target.agentId && s.status === 'open')\n .sort((a, b) => b.lastUsedAt - a.lastUsedAt)[0]\n if (fallback) {\n activeByAgent[target.agentId] = fallback.id\n } else {\n delete activeByAgent[target.agentId]\n }\n }\n return { sessions, activeByAgent }\n })\n },\n [update],\n )\n\n const reopenSession = React.useCallback(\n (sessionId: string) => {\n update((prev) => {\n const target = prev.sessions.find((s) => s.id === sessionId)\n if (!target) return prev\n const sessions = prev.sessions.map((s) =>\n s.id === sessionId ? { ...s, status: 'open' as const, lastUsedAt: Date.now() } : s,\n )\n return {\n sessions,\n activeByAgent: { ...prev.activeByAgent, [target.agentId]: sessionId },\n }\n })\n },\n [update],\n )\n\n const setActiveSession = React.useCallback(\n (sessionId: string) => {\n update((prev) => {\n const target = prev.sessions.find((s) => s.id === sessionId)\n if (!target || target.status !== 'open') return prev\n const sessions = prev.sessions.map((s) =>\n s.id === sessionId ? { ...s, lastUsedAt: Date.now() } : s,\n )\n return {\n sessions,\n activeByAgent: { ...prev.activeByAgent, [target.agentId]: sessionId },\n }\n })\n },\n [update],\n )\n\n const renameSession = React.useCallback(\n (sessionId: string, name: string) => {\n const trimmed = name.trim()\n update((prev) => ({\n ...prev,\n sessions: prev.sessions.map((s) =>\n s.id === sessionId\n ? { ...s, name: trimmed.length > 0 ? trimmed : undefined }\n : s,\n ),\n }))\n },\n [update],\n )\n\n const touchSession = React.useCallback(\n (sessionId: string) => {\n update((prev) => ({\n ...prev,\n sessions: prev.sessions.map((s) =>\n s.id === sessionId ? { ...s, lastUsedAt: Date.now() } : s,\n ),\n }))\n },\n [update],\n )\n\n const ensureSession = React.useCallback(\n (agentId: string): AiChatSession => {\n // Everything happens inside a single functional setState so we always\n // see the latest pending state (not a stale closure). React Strict\n // Mode double-invokes both effects AND setState updaters in dev,\n // which previously caused the auto-bootstrap path to mint two\n // sessions on first chat-pane open. The `mintedSession` closure\n // cache makes the updater itself idempotent across double-invokes\n // (one fresh id per `ensureSession` call, regardless of how many\n // times React replays the updater for purity testing); the\n // functional `prev` lookup handles the case where two queued\n // ensureSession calls resolve back-to-back \u2014 the second one sees\n // the first's pending append and short-circuits.\n let resolved: AiChatSession | null = null\n let mintedSession: AiChatSession | null = null\n setState((prev) => {\n const activeId = prev.activeByAgent[agentId]\n if (activeId) {\n const active = prev.sessions.find((s) => s.id === activeId)\n if (active && active.status === 'open') {\n resolved = active\n return prev\n }\n }\n const anyOpen = prev.sessions\n .filter((s) => s.agentId === agentId && s.status === 'open')\n .sort((a, b) => b.lastUsedAt - a.lastUsedAt)[0]\n if (anyOpen) {\n resolved = anyOpen\n return {\n ...prev,\n activeByAgent: { ...prev.activeByAgent, [agentId]: anyOpen.id },\n }\n }\n if (!mintedSession) {\n mintedSession = {\n id: makeId(),\n agentId,\n conversationId: makeId(),\n createdAt: Date.now(),\n lastUsedAt: Date.now(),\n status: 'open',\n }\n }\n resolved = mintedSession\n return {\n sessions: [...prev.sessions, mintedSession],\n activeByAgent: { ...prev.activeByAgent, [agentId]: mintedSession.id },\n }\n })\n // `setState` returns synchronously after invoking the updater (twice\n // in Strict Mode dev), so `resolved` is guaranteed to be set here.\n return resolved as unknown as AiChatSession\n },\n [],\n )\n\n const api = React.useMemo<AiChatSessionsApi>(\n () => ({\n state,\n getOpenSessions,\n getClosedSessions,\n getActiveSession,\n createSession,\n closeSession,\n reopenSession,\n setActiveSession,\n renameSession,\n touchSession,\n ensureSession,\n }),\n [\n state,\n getOpenSessions,\n getClosedSessions,\n getActiveSession,\n createSession,\n closeSession,\n reopenSession,\n setActiveSession,\n renameSession,\n touchSession,\n ensureSession,\n ],\n )\n\n return <AiChatSessionsContext.Provider value={api}>{children}</AiChatSessionsContext.Provider>\n}\n\nexport function useAiChatSessions(): AiChatSessionsApi {\n const ctx = React.useContext(AiChatSessionsContext)\n if (ctx) return ctx\n // Fallback no-op API \u2014 keeps consumers safe when the provider is absent\n // (legacy code paths, isolated unit tests). Every method is a no-op so a\n // chat surface without the provider behaves like a single anonymous\n // session (the behavior shipped before the multi-tab work).\n return {\n state: { sessions: [], activeByAgent: {} },\n getOpenSessions: () => [],\n getClosedSessions: () => [],\n getActiveSession: () => null,\n createSession: (agentId) => ({\n id: 'noop',\n agentId,\n conversationId: 'noop',\n createdAt: Date.now(),\n lastUsedAt: Date.now(),\n status: 'open',\n }),\n closeSession: () => {},\n reopenSession: () => {},\n setActiveSession: () => {},\n renameSession: () => {},\n touchSession: () => {},\n ensureSession: (agentId) => ({\n id: 'noop',\n agentId,\n conversationId: 'noop',\n createdAt: Date.now(),\n lastUsedAt: Date.now(),\n status: 'open',\n }),\n }\n}\n\nexport function defaultSessionLabel(session: AiChatSession): string {\n if (session.name && session.name.trim().length > 0) return session.name.trim()\n const date = new Date(session.createdAt)\n if (Number.isNaN(date.getTime())) return 'Session'\n return date.toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n })\n}\n"],
5
+ "mappings": ";AA2XS;AApWT,YAAY,WAAW;AAEvB,MAAM,cAAc;AACpB,MAAM,gBAAgB;AA2CtB,MAAM,wBAAwB,MAAM,cAAwC,IAAI;AAEhF,SAAS,SAAiB;AACxB,QAAM,IAAI;AACV,MAAI,EAAE,UAAU,OAAO,EAAE,OAAO,eAAe,YAAY;AACzD,QAAI;AACF,aAAO,EAAE,OAAO,WAAW;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,OAAO,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE;AACzD,SAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;AACvD;AAEA,SAAS,gBAAqC;AAC5C,MAAI,OAAO,WAAW,YAAa,QAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAC5E,MAAI;AACF,UAAM,MAAM,OAAO,aAAa,QAAQ,WAAW;AACnD,QAAI,CAAC,IAAK,QAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AACnD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAM,WAAW,MAAM,QAAQ,QAAQ,QAAQ,IAC1C,OAAQ,SACN,OAAO,CAAC,UAAkC;AACzC,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,YAAM,QAAQ;AACd,aACE,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,YAAY,YACzB,OAAO,MAAM,mBAAmB,YAChC,OAAO,MAAM,cAAc,YAC3B,OAAO,MAAM,eAAe,aAC3B,MAAM,WAAW,UAAU,MAAM,WAAW;AAAA,IAEjD,CAAC,EACA,IAAI,CAAC,UAAU;AACd,YAAM,QAAQ;AACd,YAAM,YAAY,MAAM;AACxB,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM,OAAO,cAAc,WAAW,YAAY;AAAA,MACpD;AAAA,IACF,CAAC,IACH,CAAC;AACL,UAAM,gBACJ,QAAQ,iBAAiB,OAAO,OAAO,kBAAkB,WACrD,OAAO;AAAA,MACL,OAAO,QAAQ,OAAO,aAAwC,EAAE;AAAA,QAC9D,CAAC,UACC,OAAO,MAAM,CAAC,MAAM,YAAY,OAAO,MAAM,CAAC,MAAM;AAAA,MACxD;AAAA,IACF,IACA,CAAC;AACP,WAAO,EAAE,UAAU,cAAc;AAAA,EACnC,QAAQ;AACN,WAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,EAC3C;AACF;AAEA,SAAS,eAAe,OAAkC;AACxD,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,aAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,CAAC;AAAA,EAChE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,uBAAuB,EAAE,SAAS,GAAkC;AAYlF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA8B,MAAM,cAAc,CAAC;AAEnF,QAAM,UAAU,MAAM;AACpB,mBAAe,KAAK;AAAA,EACtB,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,YAAgE;AAC/D,eAAS,CAAC,SAAS,QAAQ,IAAI,CAAC;AAAA,IAClC;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,kBAAkB,MAAM;AAAA,IAC5B,CAAC,YACC,MAAM,SACH,OAAO,CAAC,MAAM,EAAE,YAAY,WAAW,EAAE,WAAW,MAAM,EAC1D,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,IAC7C,CAAC,MAAM,QAAQ;AAAA,EACjB;AAEA,QAAM,oBAAoB,MAAM;AAAA,IAC9B,CAAC,SAAiB,QAAQ,kBACxB,MAAM,SACH,OAAO,CAAC,MAAM,EAAE,YAAY,WAAW,EAAE,WAAW,QAAQ,EAC5D,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAC1C,MAAM,GAAG,KAAK;AAAA,IACnB,CAAC,MAAM,QAAQ;AAAA,EACjB;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,YAAoB;AACnB,YAAM,KAAK,MAAM,cAAc,OAAO;AACtC,UAAI,CAAC,GAAI,QAAO;AAChB,YAAM,UAAU,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACtD,UAAI,CAAC,WAAW,QAAQ,WAAW,OAAQ,QAAO;AAClD,aAAO;AAAA,IACT;AAAA,IACA,CAAC,MAAM,eAAe,MAAM,QAAQ;AAAA,EACtC;AAEA,QAAM,gBAAgB,MAAM,YAAY,CAAC,YAAmC;AAC1E,UAAM,UAAyB;AAAA,MAC7B,IAAI,OAAO;AAAA,MACX;AAAA,MACA,gBAAgB,OAAO;AAAA,MACvB,WAAW,KAAK,IAAI;AAAA,MACpB,YAAY,KAAK,IAAI;AAAA,MACrB,QAAQ;AAAA,IACV;AACA,WAAO,CAAC,UAAU;AAAA,MAChB,UAAU,CAAC,GAAG,KAAK,UAAU,OAAO;AAAA,MACpC,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,OAAO,GAAG,QAAQ,GAAG;AAAA,IAChE,EAAE;AACF,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,cAAsB;AACrB,aAAO,CAAC,SAAS;AACf,cAAM,SAAS,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS;AAC3D,YAAI,CAAC,OAAQ,QAAO;AACpB,cAAM,WAAW,KAAK,SAAS;AAAA,UAAI,CAAC,MAClC,EAAE,OAAO,YAAY,EAAE,GAAG,GAAG,QAAQ,UAAmB,YAAY,KAAK,IAAI,EAAE,IAAI;AAAA,QACrF;AACA,cAAM,gBAAgB,EAAE,GAAG,KAAK,cAAc;AAC9C,YAAI,cAAc,OAAO,OAAO,MAAM,WAAW;AAE/C,gBAAM,WAAW,SACd,OAAO,CAAC,MAAM,EAAE,YAAY,OAAO,WAAW,EAAE,WAAW,MAAM,EACjE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AAChD,cAAI,UAAU;AACZ,0BAAc,OAAO,OAAO,IAAI,SAAS;AAAA,UAC3C,OAAO;AACL,mBAAO,cAAc,OAAO,OAAO;AAAA,UACrC;AAAA,QACF;AACA,eAAO,EAAE,UAAU,cAAc;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,cAAsB;AACrB,aAAO,CAAC,SAAS;AACf,cAAM,SAAS,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS;AAC3D,YAAI,CAAC,OAAQ,QAAO;AACpB,cAAM,WAAW,KAAK,SAAS;AAAA,UAAI,CAAC,MAClC,EAAE,OAAO,YAAY,EAAE,GAAG,GAAG,QAAQ,QAAiB,YAAY,KAAK,IAAI,EAAE,IAAI;AAAA,QACnF;AACA,eAAO;AAAA,UACL;AAAA,UACA,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,OAAO,OAAO,GAAG,UAAU;AAAA,QACtE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,cAAsB;AACrB,aAAO,CAAC,SAAS;AACf,cAAM,SAAS,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS;AAC3D,YAAI,CAAC,UAAU,OAAO,WAAW,OAAQ,QAAO;AAChD,cAAM,WAAW,KAAK,SAAS;AAAA,UAAI,CAAC,MAClC,EAAE,OAAO,YAAY,EAAE,GAAG,GAAG,YAAY,KAAK,IAAI,EAAE,IAAI;AAAA,QAC1D;AACA,eAAO;AAAA,UACL;AAAA,UACA,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,OAAO,OAAO,GAAG,UAAU;AAAA,QACtE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,WAAmB,SAAiB;AACnC,YAAM,UAAU,KAAK,KAAK;AAC1B,aAAO,CAAC,UAAU;AAAA,QAChB,GAAG;AAAA,QACH,UAAU,KAAK,SAAS;AAAA,UAAI,CAAC,MAC3B,EAAE,OAAO,YACL,EAAE,GAAG,GAAG,MAAM,QAAQ,SAAS,IAAI,UAAU,OAAU,IACvD;AAAA,QACN;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,cAAsB;AACrB,aAAO,CAAC,UAAU;AAAA,QAChB,GAAG;AAAA,QACH,UAAU,KAAK,SAAS;AAAA,UAAI,CAAC,MAC3B,EAAE,OAAO,YAAY,EAAE,GAAG,GAAG,YAAY,KAAK,IAAI,EAAE,IAAI;AAAA,QAC1D;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,YAAmC;AAYlC,UAAI,WAAiC;AACrC,UAAI,gBAAsC;AAC1C,eAAS,CAAC,SAAS;AACjB,cAAM,WAAW,KAAK,cAAc,OAAO;AAC3C,YAAI,UAAU;AACZ,gBAAM,SAAS,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AAC1D,cAAI,UAAU,OAAO,WAAW,QAAQ;AACtC,uBAAW;AACX,mBAAO;AAAA,UACT;AAAA,QACF;AACA,cAAM,UAAU,KAAK,SAClB,OAAO,CAAC,MAAM,EAAE,YAAY,WAAW,EAAE,WAAW,MAAM,EAC1D,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AAChD,YAAI,SAAS;AACX,qBAAW;AACX,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,OAAO,GAAG,QAAQ,GAAG;AAAA,UAChE;AAAA,QACF;AACA,YAAI,CAAC,eAAe;AAClB,0BAAgB;AAAA,YACd,IAAI,OAAO;AAAA,YACX;AAAA,YACA,gBAAgB,OAAO;AAAA,YACvB,WAAW,KAAK,IAAI;AAAA,YACpB,YAAY,KAAK,IAAI;AAAA,YACrB,QAAQ;AAAA,UACV;AAAA,QACF;AACA,mBAAW;AACX,eAAO;AAAA,UACL,UAAU,CAAC,GAAG,KAAK,UAAU,aAAa;AAAA,UAC1C,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,OAAO,GAAG,cAAc,GAAG;AAAA,QACtE;AAAA,MACF,CAAC;AAGD,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,MAAM,MAAM;AAAA,IAChB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAO,KAAM,UAAS;AAC/D;AAEO,SAAS,oBAAuC;AACrD,QAAM,MAAM,MAAM,WAAW,qBAAqB;AAClD,MAAI,IAAK,QAAO;AAKhB,SAAO;AAAA,IACL,OAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,IACzC,iBAAiB,MAAM,CAAC;AAAA,IACxB,mBAAmB,MAAM,CAAC;AAAA,IAC1B,kBAAkB,MAAM;AAAA,IACxB,eAAe,CAAC,aAAa;AAAA,MAC3B,IAAI;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,KAAK,IAAI;AAAA,MACpB,YAAY,KAAK,IAAI;AAAA,MACrB,QAAQ;AAAA,IACV;AAAA,IACA,cAAc,MAAM;AAAA,IAAC;AAAA,IACrB,eAAe,MAAM;AAAA,IAAC;AAAA,IACtB,kBAAkB,MAAM;AAAA,IAAC;AAAA,IACzB,eAAe,MAAM;AAAA,IAAC;AAAA,IACtB,cAAc,MAAM;AAAA,IAAC;AAAA,IACrB,eAAe,CAAC,aAAa;AAAA,MAC3B,IAAI;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW,KAAK,IAAI;AAAA,MACpB,YAAY,KAAK,IAAI;AAAA,MACrB,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,SAAgC;AAClE,MAAI,QAAQ,QAAQ,QAAQ,KAAK,KAAK,EAAE,SAAS,EAAG,QAAO,QAAQ,KAAK,KAAK;AAC7E,QAAM,OAAO,IAAI,KAAK,QAAQ,SAAS;AACvC,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO,KAAK,eAAe,QAAW;AAAA,IACpC,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;",
6
+ "names": []
7
+ }