@tomny-dev/uzi 0.7.1 → 0.8.0-pr.85.286.1.ccec3e4

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 (51) hide show
  1. package/README.md +8 -0
  2. package/dist/components/ai-chat/AIChat.d.ts +164 -0
  3. package/dist/components/ai-chat/AIChat.d.ts.map +1 -0
  4. package/dist/components/ai-chat/AIChat.test.d.ts +2 -0
  5. package/dist/components/ai-chat/AIChat.test.d.ts.map +1 -0
  6. package/dist/components/ai-chat/AIChatDock.d.ts +33 -0
  7. package/dist/components/ai-chat/AIChatDock.d.ts.map +1 -0
  8. package/dist/components/floating-action-stack/FloatingActionStack.d.ts +19 -0
  9. package/dist/components/floating-action-stack/FloatingActionStack.d.ts.map +1 -0
  10. package/dist/components/floating-action-stack/FloatingActionStack.test.d.ts +2 -0
  11. package/dist/components/floating-action-stack/FloatingActionStack.test.d.ts.map +1 -0
  12. package/dist/components/multi-select/MultiSelect.d.ts +3 -0
  13. package/dist/components/multi-select/MultiSelect.d.ts.map +1 -1
  14. package/dist/index.cjs +9 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -1
  18. package/dist/index26.js +6 -2
  19. package/dist/index26.js.map +1 -1
  20. package/dist/index27.cjs +7 -1
  21. package/dist/index27.cjs.map +1 -1
  22. package/dist/index27.js +40 -10
  23. package/dist/index27.js.map +1 -1
  24. package/dist/index28.cjs +40 -10
  25. package/dist/index28.cjs.map +1 -1
  26. package/dist/index75.js +91 -0
  27. package/dist/index75.js.map +1 -0
  28. package/dist/index76.cjs +133 -0
  29. package/dist/index76.cjs.map +1 -0
  30. package/dist/index76.js +667 -0
  31. package/dist/index76.js.map +1 -0
  32. package/dist/index77.cjs +669 -0
  33. package/dist/index77.cjs.map +1 -0
  34. package/dist/index77.js +35 -0
  35. package/dist/index77.js.map +1 -0
  36. package/dist/index78.cjs +49 -0
  37. package/dist/index78.cjs.map +1 -0
  38. package/dist/index78.js +231 -0
  39. package/dist/index78.js.map +1 -0
  40. package/dist/index79.cjs +231 -0
  41. package/dist/index79.cjs.map +1 -0
  42. package/dist/index79.js +19 -0
  43. package/dist/index79.js.map +1 -0
  44. package/dist/index80.cjs +25 -0
  45. package/dist/index80.cjs.map +1 -0
  46. package/dist/index80.js +36 -0
  47. package/dist/index80.js.map +1 -0
  48. package/dist/index81.cjs +37 -0
  49. package/dist/index81.cjs.map +1 -0
  50. package/dist/style.css +655 -0
  51. package/package.json +3 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index77.cjs","names":[],"sources":["../src/components/ai-chat/AIChat.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport type { CSSProperties, ComponentType, FormEvent, KeyboardEvent, ReactNode } from 'react';\n\nimport { AuiConfig, AuiProvider, ThreadPrimitive } from '@assistant-ui/react';\nimport { AISDKChat, AssistantChatTransport, useAISDKChat } from '@assistant-ui/ai-sdk';\n\nimport styles from './ai-chat.module.css';\n\ntype AIChatTransportOptions = NonNullable<ConstructorParameters<typeof AssistantChatTransport>[0]>;\n\nexport interface AIChatProps {\n /** AI SDK UI-message stream endpoint. */\n readonly api: string;\n /** Opaque scope identifier sent to the API. Server must authorize it. */\n readonly scope?: string;\n /** Human-readable display text; never used as an access-control identifier. */\n readonly scopeLabel?: string;\n /** Fetch adapter for authentication, request instrumentation and local demos. */\n readonly fetch?: typeof globalThis.fetch;\n readonly headers?: Record<string, string>;\n readonly credentials?: RequestCredentials;\n /** Customize serialized requests for APIs with a strict request schema. */\n readonly prepareSendMessagesRequest?: AIChatTransportOptions['prepareSendMessagesRequest'];\n /** Change this when the authenticated identity changes to reset the transcript. */\n readonly sessionKey?: string | number;\n /** Maximum number of user turns (default: 8). */\n readonly messageLimit?: number;\n readonly inputPlaceholder?: string;\n /** Accessible name for the message textarea. */\n readonly inputAriaLabel?: string;\n readonly inputMaxLength?: number;\n readonly components?: AIChatComponents;\n /** Opt-in files, restricted to images/text accepted by the application API. */\n readonly attachments?: AIChatAttachmentOptions;\n /** Optional persistence; implementations must scope storage by user AND resource. */\n readonly history?: AIChatHistoryAdapter;\n /** Native SDK continuation predicate for tool approvals. */\n readonly sendAutomaticallyWhen?: NonNullable<Parameters<typeof AISDKChat>[0]>['sendAutomaticallyWhen'];\n readonly onClose?: () => void;\n readonly className?: string;\n readonly style?: CSSProperties;\n}\n\n/**\n * Framework-independent presentation contract. The parent owns the runtime,\n * message identities, approvals, uploads, history and security boundaries.\n * Uzi does not serialize, persist or reinterpret any messages supplied here.\n */\nexport type AIChatMessage = AIChatMessageRendererProps['message'];\n\nexport interface AIChatAttachmentOptions {\n readonly accept: string;\n readonly maxFiles?: number;\n readonly maxBytesPerFile?: number;\n}\n\nexport interface AIChatSelectedAttachment {\n readonly name: string;\n readonly size: number;\n /** Local thumbnail URL for supported image files. */\n readonly previewUrl?: string;\n}\n\nexport interface AIChatHistoryAdapter {\n /** Load only validated UI messages from the authenticated principal's own storage. */\n readonly load: () => Promise<readonly AIChatMessage[]> | readonly AIChatMessage[];\n /** Save after complete turns; the consumer decides what metadata is safe. */\n readonly save: (messages: readonly AIChatMessage[]) => Promise<void> | void;\n readonly clear: () => Promise<void> | void;\n /** Observability for failed history operations; never include message payloads in logs. */\n readonly onError?: (operation: 'load' | 'save' | 'clear', error: unknown) => void;\n}\n\nexport type AIChatToolApprovalHandler = (id: string, approved: boolean) => Promise<void> | void;\n\nexport interface AIChatViewProps {\n readonly messages: readonly AIChatMessage[];\n readonly status?: string;\n readonly error?: Error | null;\n readonly draft?: string;\n readonly onDraftChange?: (draft: string) => void;\n readonly onSend?: () => void;\n readonly onStop?: () => void;\n readonly onRetry?: () => void;\n readonly onNewChat?: () => void;\n /** No limit by default: specialized apps decide their own session policy. */\n readonly messageLimit?: number;\n readonly inputPlaceholder?: string;\n readonly inputAriaLabel?: string;\n readonly inputMaxLength?: number;\n readonly components?: AIChatComponents;\n readonly attachments?: readonly AIChatSelectedAttachment[];\n readonly onSelectAttachments?: (files: FileList) => void;\n readonly onRemoveAttachment?: (index: number) => void;\n readonly attachmentAccept?: string;\n readonly attachmentError?: string | null;\n /** Only invoked from explicit user action on an approval-requested tool part. */\n readonly onToolApproval?: AIChatToolApprovalHandler;\n /** Custom message toolbar controls, e.g. a conversation selector. */\n readonly toolbarActions?: ReactNode;\n /** Context labels or other app-owned notices above the transcript. */\n readonly header?: ReactNode;\n /** Upload actions, attachment previews or context chips before the input. */\n readonly composerLeading?: ReactNode;\n /** Additional buttons beside the Send/Stop control. */\n readonly composerTrailing?: ReactNode;\n /** Completely replace the composer while retaining shared transcript chrome. */\n readonly composer?: ReactNode;\n readonly emptyState?: ReactNode;\n readonly className?: string;\n readonly style?: CSSProperties;\n}\n\nexport interface AIChatComponents {\n readonly MessageRenderer?: ComponentType<AIChatMessageRendererProps>;\n readonly ReasoningRenderer?: ComponentType<AIChatReasoningRendererProps>;\n readonly SourceRenderer?: ComponentType<AIChatSourceRendererProps>;\n readonly DataRenderer?: ComponentType<AIChatDataRendererProps>;\n readonly ToolRenderer?: ComponentType<AIChatToolRendererProps>;\n readonly ErrorRenderer?: ComponentType<AIChatErrorRendererProps>;\n}\n\nexport interface AIChatMessageRendererProps {\n readonly message: {\n readonly id: string;\n readonly role: 'user' | 'assistant';\n readonly parts: readonly unknown[];\n readonly metadata?: Record<string, unknown>;\n };\n readonly renderMessagePart: (part: Record<string, unknown>, index: number) => ReactNode;\n readonly onToolApproval?: AIChatToolApprovalHandler;\n}\n\nexport interface AIChatReasoningRendererProps {\n readonly children: string;\n readonly title?: string;\n}\n\nexport interface AIChatSource {\n readonly id: string;\n readonly title?: string;\n readonly url?: string;\n}\n\nexport interface AIChatSourceRendererProps {\n readonly sources: readonly AIChatSource[];\n readonly renderSource: (source: AIChatSource, index: number) => ReactNode;\n}\n\nexport interface AIChatDataRendererProps {\n readonly data: readonly Record<string, unknown>[];\n readonly renderDataItem: (item: Record<string, unknown>, index: number) => ReactNode;\n}\n\nexport interface AIChatToolRendererProps {\n readonly part: Record<string, unknown>;\n readonly name: string;\n readonly approvalId?: string;\n readonly onApprove?: () => void;\n readonly onDeny?: () => void;\n readonly approvalPending: boolean;\n}\n\nexport interface AIChatErrorRendererProps {\n readonly error: Error;\n readonly onRetry: () => void;\n readonly onClear: () => void;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction safeHttpUrl(value: unknown): string | undefined {\n const url = asString(value);\n return url && /^https?:\\/\\//i.test(url) ? url : undefined;\n}\n\n/** Reusable role-aware message surface for domain renderers. */\nexport interface AIChatMessageBubbleProps {\n readonly role: 'user' | 'assistant';\n readonly label?: string;\n readonly children: ReactNode;\n readonly className?: string;\n}\n\nexport function AIChatMessageBubble({ role, label, children, className }: AIChatMessageBubbleProps) {\n return (\n <div className={[styles.messageBubble, role === 'user' ? styles.userMessage : styles.assistantMessage, className]\n .filter(Boolean).join(' ')} data-role={role}>\n {label && <span className={styles.messageLabel}>{label}</span>}\n {children}\n </div>\n );\n}\n\nfunction DefaultMessageRenderer({ message, renderMessagePart }: AIChatMessageRendererProps) {\n return (\n <AIChatMessageBubble role={message.role}>\n {message.parts.map((part, index) => (\n <div key={isRecord(part) && typeof part.id === 'string' ? part.id : index}>\n {isRecord(part) ? renderMessagePart(part, index) : null}\n </div>\n ))}\n </AIChatMessageBubble>\n );\n}\n\nfunction DefaultReasoningRenderer({ children, title = 'Reasoning' }: AIChatReasoningRendererProps) {\n return (\n <details className={styles.reasoning}>\n <summary className={styles.reasoningSummary}>{title}</summary>\n <p className={styles.reasoningText}>{children}</p>\n </details>\n );\n}\n\nfunction DefaultSourceRenderer({ sources, renderSource }: AIChatSourceRendererProps) {\n return (\n <div className={styles.sources}>\n <span className={styles.sourcesLabel}>Sources</span>\n <ul>\n {sources.map((source, index) => (\n <li key={source.id}>{renderSource(source, index)}</li>\n ))}\n </ul>\n </div>\n );\n}\n\nfunction DefaultDataRenderer({ data, renderDataItem }: AIChatDataRendererProps) {\n return (\n <div className={styles.data}>\n {data.map((item, index) => (\n <div key={index}>{renderDataItem(item, index)}</div>\n ))}\n </div>\n );\n}\n\nfunction DefaultErrorRenderer({ error, onRetry, onClear }: AIChatErrorRendererProps) {\n return (\n <div className={styles.error} role=\"alert\">\n <p>{error.message}</p>\n <div className={styles.errorActions}>\n <button type=\"button\" onClick={onRetry}>Retry response</button>\n <button type=\"button\" onClick={onClear}>Start new chat</button>\n </div>\n </div>\n );\n}\n\nfunction renderPart(\n part: Record<string, unknown>,\n index: number,\n components: AIChatComponents | undefined,\n onToolApproval?: AIChatToolApprovalHandler,\n pendingApprovalId?: string | null,\n): ReactNode {\n const type = asString(part.type) ?? '';\n if (type === 'step-start') return null;\n if (type === 'text') {\n return <span className={styles.textPart}>{asString(part.text) ?? ''}</span>;\n }\n if (type === 'reasoning') {\n const ReasoningRenderer = components?.ReasoningRenderer ?? DefaultReasoningRenderer;\n return <ReasoningRenderer>{asString(part.text) ?? ''}</ReasoningRenderer>;\n }\n if (type === 'source-url' || type === 'source-document') {\n const SourceRenderer = components?.SourceRenderer ?? DefaultSourceRenderer;\n const source: AIChatSource = {\n id: asString(part.sourceId) ?? asString(part.id) ?? `source-${index}`,\n title: asString(part.title) ?? asString(part.filename),\n url: safeHttpUrl(part.url),\n };\n return (\n <SourceRenderer\n sources={[source]}\n renderSource={(item) => item.url ? (\n <a href={item.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {item.title ?? item.url}\n </a>\n ) : <span>{item.title ?? item.id}</span>}\n />\n );\n }\n if (type.startsWith('data-')) {\n const DataRenderer = components?.DataRenderer ?? DefaultDataRenderer;\n return (\n <DataRenderer\n data={[part]}\n renderDataItem={(item) => (\n <pre className={styles.structuredData}>{JSON.stringify(item.data ?? item, null, 2)}</pre>\n )}\n />\n );\n }\n if (type.startsWith('tool-') || type === 'dynamic-tool') {\n const name = asString(part.toolName) ?? type.replace(/^tool-/, '');\n const approval = isRecord(part.approval) ? asString(part.approval.id) : undefined;\n const approvalId = part.state === 'approval-requested' ? approval : undefined;\n const controlsDisabled = pendingApprovalId !== null && pendingApprovalId !== undefined;\n const ToolRenderer = components?.ToolRenderer;\n const props: AIChatToolRendererProps = {\n part, name, approvalId,\n approvalPending: controlsDisabled,\n ...(approvalId && onToolApproval ? {\n onApprove: () => { if (!controlsDisabled) void onToolApproval(approvalId, true); },\n onDeny: () => { if (!controlsDisabled) void onToolApproval(approvalId, false); },\n } : {}),\n };\n if (ToolRenderer) return <ToolRenderer {...props} />;\n return (\n <div className={styles.toolPart}>\n <details>\n <summary>{name}</summary>\n <pre className={styles.structuredData}>\n {JSON.stringify(part.output ?? part.input ?? part, null, 2)}\n </pre>\n </details>\n {approvalId && (\n <div className={styles.approvalActions} aria-label={`Approval required for ${name}`}>\n {props.onApprove && props.onDeny ? (\n <>\n <button type=\"button\" disabled={controlsDisabled} onClick={props.onApprove}>Approve</button>\n <button type=\"button\" disabled={controlsDisabled} onClick={props.onDeny}>Deny</button>\n </>\n ) : <span>Approval required in application</span>}\n </div>\n )}\n </div>\n );\n }\n if (type === 'file' || type === 'image') {\n const url = safeHttpUrl(part.url);\n return url ? (\n <a href={url} target=\"_blank\" rel=\"noopener noreferrer\">\n {asString(part.filename) ?? 'View attachment'}\n </a>\n ) : <span>Attachment</span>;\n }\n // Preserve unsupported part types as inspectable data instead of silently dropping them.\n return <pre className={styles.structuredData}>{JSON.stringify(part, null, 2)}</pre>;\n}\n\nconst DEFAULT_MESSAGE_LIMIT = 8;\n\nexport function AIChat({\n api,\n scope,\n scopeLabel,\n fetch: requestFetch,\n headers,\n credentials,\n prepareSendMessagesRequest,\n sessionKey,\n messageLimit = DEFAULT_MESSAGE_LIMIT,\n inputPlaceholder = 'Ask a question...',\n inputAriaLabel = 'Message',\n inputMaxLength = 2000,\n components,\n attachments,\n history,\n sendAutomaticallyWhen,\n onClose,\n className,\n style,\n}: AIChatProps) {\n // Remounting the provider creates a fresh AI SDK chat id, not merely an empty transcript.\n const [session, setSession] = useState(0);\n const transport = useMemo(\n () => new AssistantChatTransport({\n api,\n // The transport merges this with the AI SDK messages, id, model context and tools.\n // The endpoint, not the presentation component, must enforce the scope.\n ...(scope ? { body: { scope } } : {}),\n ...(requestFetch ? { fetch: requestFetch } : {}),\n ...(headers ? { headers } : {}),\n ...(credentials ? { credentials } : {}),\n ...(prepareSendMessagesRequest ? { prepareSendMessagesRequest } : {}),\n }),\n [api, scope, requestFetch, headers, credentials, prepareSendMessagesRequest],\n );\n const config = useMemo(\n () => AuiConfig({ threads: AISDKChat({ transport, ...(sendAutomaticallyWhen ? { sendAutomaticallyWhen } : {}) }) }),\n [transport, session, sendAutomaticallyWhen],\n );\n\n return (\n <div className={[styles.container, className].filter(Boolean).join(' ')} style={style}>\n {scopeLabel && (\n <div className={styles.scope}>\n <span className={styles.scopeLabel}>Current scope</span>\n <strong>{scopeLabel}</strong>\n </div>\n )}\n {onClose && (\n <button className={styles.closeBtn} type=\"button\" onClick={onClose} aria-label=\"Close chat\">\n &#x2715;\n </button>\n )}\n <AuiProvider key={JSON.stringify([api, scope, sessionKey, session])} config={config}>\n <ThreadPrimitive.Root className={styles.thread}>\n <ChatViewport\n messageLimit={Number.isFinite(messageLimit) ? Math.max(1, Math.floor(messageLimit)) : DEFAULT_MESSAGE_LIMIT}\n inputPlaceholder={inputPlaceholder}\n inputAriaLabel={inputAriaLabel}\n inputMaxLength={inputMaxLength}\n components={components}\n attachmentOptions={attachments}\n history={history}\n onNewChat={() => setSession((previous) => previous + 1)}\n />\n </ThreadPrimitive.Root>\n </AuiProvider>\n </div>\n );\n}\n\ninterface ChatViewportProps {\n readonly messageLimit: number;\n readonly inputPlaceholder: string;\n readonly inputAriaLabel: string;\n readonly inputMaxLength: number;\n readonly components?: AIChatComponents;\n readonly attachmentOptions?: AIChatAttachmentOptions;\n readonly history?: AIChatHistoryAdapter;\n readonly onNewChat: () => void;\n}\n\nfunction ChatViewport({\n messageLimit,\n inputPlaceholder,\n inputAriaLabel,\n inputMaxLength,\n components,\n attachmentOptions,\n history,\n onNewChat,\n}: ChatViewportProps) {\n const chat = useAISDKChat();\n const [draft, setDraft] = useState('');\n const [selectedFiles, setSelectedFiles] = useState<File[]>([]);\n const [attachmentError, setAttachmentError] = useState<string | null>(null);\n const [attachmentPreviews, setAttachmentPreviews] = useState<string[]>([]);\n const [hydrated, setHydrated] = useState(!history);\n const expectedRestoredId = useRef<string | null>(null);\n const saveQueue = useRef<Promise<void>>(Promise.resolve());\n const [approvalBusy, setApprovalBusy] = useState(false);\n useEffect(() => {\n const urls = selectedFiles.map((file) => file.type.startsWith('image/') && typeof URL.createObjectURL === 'function' ? URL.createObjectURL(file) : '');\n setAttachmentPreviews(urls);\n return () => urls.forEach((url) => { if (url && typeof URL.revokeObjectURL === 'function') URL.revokeObjectURL(url); });\n }, [selectedFiles]);\n const messages = chat?.messages ?? [];\n const status = chat?.status ?? 'ready';\n const isBusy = status === 'submitted' || status === 'streaming';\n const historyFull = messages.filter((message) => message.role === 'user').length >= messageLimit;\n const reportHistoryError = (operation: 'load' | 'save' | 'clear', error: unknown) => {\n if (history?.onError) history.onError(operation, error);\n else console.warn(`[Uzi AIChat] History ${operation} failed`);\n };\n // Persist only complete turns. Do not save an empty runtime over stored history\n // during hydration or while a response is still streaming.\n useEffect(() => {\n if (!history) return;\n let active = true;\n void Promise.resolve().then(() => history.load()).then((restored) => {\n if (!active) return;\n if (Array.isArray(restored)) {\n const valid = restored.filter((message) =>\n typeof message?.id === 'string' &&\n (message.role === 'user' || message.role === 'assistant') &&\n Array.isArray(message.parts));\n expectedRestoredId.current = valid.length ? valid[0].id : null;\n if (valid.length) chat?.setMessages(valid as Parameters<NonNullable<typeof chat>['setMessages']>[0]);\n }\n setHydrated(true);\n }).catch((error: unknown) => {\n if (active) {\n reportHistoryError('load', error);\n setHydrated(true);\n }\n });\n return () => { active = false; };\n }, [chat?.setMessages, history]);\n\n useEffect(() => {\n if (!history || !hydrated || isBusy) return;\n const snapshot = messages.filter((message) => message.role === 'user' || message.role === 'assistant').map((message) => ({\n id: message.id, role: message.role as 'user' | 'assistant', parts: message.parts,\n ...(isRecord(message.metadata) ? { metadata: message.metadata } : {}),\n }));\n // Guard against the transient empty SDK state between history.load and\n // setMessages propagation; otherwise an old transcript could be erased.\n if (expectedRestoredId.current) {\n if (!snapshot.some((message) => message.id === expectedRestoredId.current)) return;\n expectedRestoredId.current = null;\n }\n // Serialize writes. New Chat waits for in-flight writes before clearing.\n saveQueue.current = saveQueue.current.then(\n () => history.save(snapshot),\n () => history.save(snapshot),\n ).then(\n () => {},\n (error: unknown) => { reportHistoryError('save', error); },\n );\n }, [history, hydrated, isBusy, messages]);\n\n const handleSelectedFiles = (files: FileList) => {\n if (!attachmentOptions) return;\n const selected = Array.from(files);\n if (selected.length + selectedFiles.length > (attachmentOptions.maxFiles ?? 3)) {\n setAttachmentError('Too many attachments.');\n return;\n }\n if (selected.some((file) => file.size > (attachmentOptions.maxBytesPerFile ?? 5 * 1024 * 1024))) {\n setAttachmentError('An attachment exceeds the size limit.');\n return;\n }\n const allowedTypes = attachmentOptions.accept.split(',').map((item) => item.trim());\n if (selected.some((file) => !allowedTypes.some((allow) => (\n allow === file.type || (allow.endsWith('/*') && file.type.startsWith(allow.slice(0, -1)))\n )))) {\n setAttachmentError('Unsupported attachment type.');\n return;\n }\n setAttachmentError(null);\n setSelectedFiles((existing) => [...existing, ...selected]);\n };\n\n const handleSubmit = () => {\n const text = draft.trim();\n if (!chat || (!text && selectedFiles.length === 0) || isBusy || historyFull || chat.error || !hydrated || approvalBusy) return;\n const files = selectedFiles.length ? new DataTransfer() : null;\n selectedFiles.forEach((file) => files?.items.add(file));\n // AI SDK serializes FileList to FileUIParts and sends them through the\n // app-owned transport. The server MUST validate types and sizes again.\n // Clear the composer promptly; restore the draft and files if submission\n // fails, so users do not lose attachments or typed content.\n const previousDraft = draft;\n const previousFiles = selectedFiles;\n setDraft('');\n setSelectedFiles([]);\n setAttachmentError(null);\n void Promise.resolve(chat.sendMessage({ text, ...(files ? { files: files.files } : {}) }))\n .catch(() => {\n setDraft((current) => current || previousDraft);\n setSelectedFiles((current) => current.length ? current : previousFiles);\n });\n };\n\n const handleNewChat = () => {\n void (async () => {\n await chat?.stop();\n if (history) {\n try {\n await saveQueue.current;\n await history.clear();\n } catch (error) {\n reportHistoryError('clear', error);\n return;\n }\n }\n onNewChat();\n })();\n };\n\n const handleRetry = () => {\n if (!chat || isBusy) return;\n chat.clearError();\n void chat.regenerate();\n };\n\n return (\n <AIChatView\n messages={messages.filter((message) => message.role === 'user' || message.role === 'assistant').map((message) => ({\n id: message.id,\n role: message.role as 'user' | 'assistant',\n parts: message.parts,\n ...(isRecord(message.metadata) ? { metadata: message.metadata } : {}),\n }))}\n status={status}\n error={chat?.error}\n draft={draft}\n onDraftChange={setDraft}\n onSend={handleSubmit}\n onStop={() => { void chat?.stop(); }}\n onRetry={handleRetry}\n onNewChat={handleNewChat}\n messageLimit={messageLimit}\n attachments={selectedFiles.map((file, index) => ({ name: file.name, size: file.size, ...(attachmentPreviews[index] ? { previewUrl: attachmentPreviews[index] } : {}) }))}\n onSelectAttachments={attachmentOptions ? handleSelectedFiles : undefined}\n onRemoveAttachment={attachmentOptions ? (index) => setSelectedFiles((files) => files.filter((_, i) => i !== index)) : undefined}\n attachmentAccept={attachmentOptions?.accept}\n attachmentError={attachmentError}\n onToolApproval={chat ? async (id, approved) => {\n if (approvalBusy) return;\n setApprovalBusy(true);\n try { await chat.addToolApprovalResponse({ id, approved, ...(!approved ? { reason: 'User denied approval' } : {}) }); }\n finally { setApprovalBusy(false); }\n } : undefined}\n inputPlaceholder={inputPlaceholder}\n inputAriaLabel={inputAriaLabel}\n inputMaxLength={inputMaxLength}\n components={components}\n />\n );\n}\n\n/**\n * Controlled chat presentation for apps with their own AI SDK/assistant-ui\n * runtime. It does not create providers, transports or chat state. Callbacks\n * are the only way messages/actions leave the view.\n */\nexport function AIChatView({\n messages,\n status = 'ready',\n error,\n draft = '',\n onDraftChange,\n onSend,\n onStop,\n onRetry,\n onNewChat,\n messageLimit,\n inputPlaceholder = 'Ask a question...',\n inputAriaLabel = 'Message',\n inputMaxLength = 2000,\n components,\n attachments,\n onSelectAttachments,\n onRemoveAttachment,\n attachmentAccept,\n attachmentError,\n onToolApproval,\n toolbarActions,\n header,\n composerLeading,\n composerTrailing,\n composer,\n emptyState,\n className,\n style,\n}: AIChatViewProps) {\n const viewportRef = useRef<HTMLDivElement>(null);\n const pinnedToBottom = useRef(true);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const approvalRef = useRef<string | null>(null);\n const [pendingApprovalId, setPendingApprovalId] = useState<string | null>(null);\n const [approvalError, setApprovalError] = useState<string | null>(null);\n const respondToApproval: AIChatToolApprovalHandler = async (id, approved) => {\n if (!onToolApproval || approvalRef.current) return;\n approvalRef.current = id;\n setPendingApprovalId(id);\n setApprovalError(null);\n try { await onToolApproval(id, approved); }\n catch { setApprovalError('Could not submit approval. Please try again.'); }\n finally { approvalRef.current = null; setPendingApprovalId(null); }\n };\n const isBusy = status === 'submitted' || status === 'streaming';\n const limit = messageLimit != null && Number.isFinite(messageLimit)\n ? Math.max(1, Math.floor(messageLimit)) : null;\n const historyFull = limit !== null && messages.filter((message) => message.role === 'user').length >= limit;\n const MessageRenderer = components?.MessageRenderer ?? DefaultMessageRenderer;\n const ErrorRenderer = components?.ErrorRenderer ?? DefaultErrorRenderer;\n\n // Follow streaming text only if the user is already near the bottom.\n useEffect(() => {\n const viewport = viewportRef.current;\n if (viewport && pinnedToBottom.current) viewport.scrollTop = viewport.scrollHeight;\n });\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n if (!isBusy && !historyFull && !error && (draft.trim() || (attachments?.length ?? 0) > 0)) onSend?.();\n }\n };\n\n const handleFormSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!isBusy && !historyFull && !error && (draft.trim() || (attachments?.length ?? 0) > 0)) onSend?.();\n };\n\n return (\n <div className={[styles.thread, className].filter(Boolean).join(' ')} style={style}>\n {header}\n <div\n ref={viewportRef}\n className={styles.viewport}\n onScroll={(event) => {\n const viewport = event.currentTarget;\n pinnedToBottom.current = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight < 80;\n }}\n >\n {(messages.length > 0 || toolbarActions) && (onNewChat || toolbarActions) && (\n <div className={styles.chatToolbar}>\n {toolbarActions}\n {onNewChat && messages.length > 0 && (\n <button type=\"button\" onClick={onNewChat}>New chat</button>\n )}\n </div>\n )}\n <div className={styles.messageList} role=\"log\" aria-label=\"Chat history\" aria-live=\"polite\">\n {messages.length === 0 && (\n <div className={styles.emptyState}>\n {emptyState ?? <p>Start a conversation</p>}\n </div>\n )}\n {messages.map((message) => (\n <MessageRenderer\n key={message.id}\n message={message}\n renderMessagePart={(part, index) => renderPart(part, index, components, onToolApproval ? respondToApproval : undefined, pendingApprovalId)}\n onToolApproval={onToolApproval ? respondToApproval : undefined}\n />\n ))}\n {isBusy && <p className={styles.status}>Thinking...</p>}\n </div>\n </div>\n <div className={styles.footer}>\n {approvalError && <p role=\"alert\" className={styles.attachmentError}>{approvalError}</p>}\n {attachmentError && <p role=\"alert\" className={styles.attachmentError}>{attachmentError}</p>}\n {error && (\n onRetry && onNewChat ? (\n <ErrorRenderer error={error} onRetry={onRetry} onClear={onNewChat} />\n ) : (\n <div className={styles.error} role=\"alert\">\n <p>{error.message}</p>\n {(onRetry || onNewChat) && (\n <div className={styles.errorActions}>\n {onRetry && <button type=\"button\" onClick={onRetry}>Retry response</button>}\n {onNewChat && <button type=\"button\" onClick={onNewChat}>Start new chat</button>}\n </div>\n )}\n </div>\n )\n )}\n {historyFull && !error && !isBusy && onNewChat && (\n <div className={styles.historyLimit}>\n <span>This chat has reached its {limit}-question limit.</span>\n <button type=\"button\" onClick={onNewChat}>Start new chat</button>\n </div>\n )}\n {(!historyFull || isBusy) && !error && (\n composer !== undefined ? composer : (\n <form className={styles.composerForm} onSubmit={handleFormSubmit}>\n {composerLeading && <div className={styles.composerExtras}>{composerLeading}</div>}\n {onSelectAttachments && (\n <div className={styles.composerExtras}>\n <input\n ref={fileInputRef}\n className={styles.visuallyHidden}\n type=\"file\"\n aria-label=\"Add attachments\"\n accept={attachmentAccept}\n multiple\n disabled={isBusy}\n onChange={(event) => {\n if (event.target.files) onSelectAttachments(event.target.files);\n event.target.value = '';\n }}\n />\n {attachments && attachments.length > 0 && (\n <ul className={styles.attachmentList}>\n {attachments.map((attachment, index) => (\n <li key={index}>\n {attachment.previewUrl ? <img className={styles.attachmentThumbnail} src={attachment.previewUrl} alt={attachment.name} /> : <span className={styles.attachmentFilename}>{attachment.name}</span>}\n {onRemoveAttachment && (\n <button type=\"button\" onClick={() => onRemoveAttachment(index)}\n aria-label={`Remove ${attachment.name}`} title={`Remove ${attachment.name}`}>×</button>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n )}\n <div className={styles.composerSurface}>\n <div className={styles.composerRow}>\n {onSelectAttachments && (\n <button type=\"button\" className={styles.composerIconButton}\n aria-label=\"Attach file\" title=\"Attach file\"\n disabled={isBusy} onClick={() => fileInputRef.current?.click()}>\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"m21 11-8.5 8.5a6 6 0 0 1-8.5-8.5l9-9a4 4 0 0 1 5.7 5.7l-9 9a2 2 0 0 1-2.8-2.8l8.5-8.5\" />\n </svg>\n </button>\n )}\n <textarea\n className={styles.input}\n aria-label={inputAriaLabel}\n rows={1}\n value={draft}\n onChange={(event) => onDraftChange?.(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={inputPlaceholder}\n maxLength={inputMaxLength}\n disabled={isBusy || !onSend || !onDraftChange}\n readOnly={!onDraftChange}\n />\n <div className={styles.composerActions}>\n {composerTrailing}\n {isBusy ? (\n onStop && <button type=\"button\" className={styles.composerIconButton} onClick={onStop}\n aria-label=\"Stop response\" title=\"Stop response\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" rx=\"2\" /></svg>\n </button>\n ) : (\n <button type=\"submit\" className={styles.sendButton} aria-label=\"Send message\"\n title=\"Send message\" disabled={!onSend || (!draft.trim() && !attachments?.length)}>\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 19V5m-7 7 7-7 7 7\" /></svg>\n </button>\n )}\n </div>\n </div>\n </div>\n </form>\n )\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ //#region src/components/ai-chat/ai-chat-dock.module.css
2
+ var dock = "uzi-ai-chat-dock-module__dock__hHrvH";
3
+ var launcher = "uzi-ai-chat-dock-module__launcher__4Q71E";
4
+ var iconButton = "uzi-ai-chat-dock-module__iconButton__lHEHr";
5
+ var launcherText = "uzi-ai-chat-dock-module__launcherText__eb6OK";
6
+ var panel = "uzi-ai-chat-dock-module__panel__4ig-u";
7
+ var header = "uzi-ai-chat-dock-module__header__-g1KW";
8
+ var identity = "uzi-ai-chat-dock-module__identity__qIsdQ";
9
+ var avatar = "uzi-ai-chat-dock-module__avatar__OTEjI";
10
+ var titles = "uzi-ai-chat-dock-module__titles__cEiMf";
11
+ var title = "uzi-ai-chat-dock-module__title__Boxdi";
12
+ var description = "uzi-ai-chat-dock-module__description__afqW-";
13
+ var actions = "uzi-ai-chat-dock-module__actions__rGPJb";
14
+ var content = "uzi-ai-chat-dock-module__content__t-hes";
15
+ var backdrop = "uzi-ai-chat-dock-module__backdrop__Z-yVb";
16
+ var ai_chat_dock_module_default = {
17
+ dock,
18
+ launcher,
19
+ iconButton,
20
+ launcherText,
21
+ panel,
22
+ header,
23
+ identity,
24
+ avatar,
25
+ titles,
26
+ title,
27
+ description,
28
+ actions,
29
+ content,
30
+ backdrop
31
+ };
32
+ //#endregion
33
+ export { actions, avatar, backdrop, content, ai_chat_dock_module_default as default, description, dock, header, iconButton, identity, launcher, launcherText, panel, title, titles };
34
+
35
+ //# sourceMappingURL=index77.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index77.js","names":[],"sources":["../src/components/ai-chat/ai-chat-dock.module.css"],"sourcesContent":[".dock {\n position: relative;\n z-index: 100;\n}\n\n.launcher {\n position: fixed;\n right: max(1.25rem, env(safe-area-inset-right));\n bottom: calc(1.25rem + env(safe-area-inset-bottom));\n z-index: 100;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.6rem;\n min-height: 3.5rem;\n padding: 0.7rem 1.1rem;\n border: 1px solid var(--border);\n border-radius: 999px;\n background: var(--primary);\n color: var(--primary-foreground);\n box-shadow: 0 6px 24px color-mix(in srgb, var(--foreground) 20%, transparent);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.launcher:hover { filter: brightness(0.95); }\n.launcher:focus-visible, .iconButton:focus-visible {\n outline: 2px solid var(--ring, var(--primary));\n outline-offset: 2px;\n}\n.launcherText { white-space: nowrap; }\n.panel {\n position: fixed;\n z-index: 101;\n box-sizing: border-box;\n right: max(1rem, env(safe-area-inset-right));\n bottom: calc(1rem + env(safe-area-inset-bottom));\n width: min(26.25rem, calc(100vw - 2rem));\n height: min(42rem, calc(100dvh - 2rem - env(safe-area-inset-bottom)));\n min-height: 0;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--border);\n border-radius: 1rem;\n background: var(--background);\n color: var(--foreground);\n box-shadow: 0 12px 44px color-mix(in srgb, var(--foreground) 22%, transparent);\n overflow: hidden;\n}\n.panel[hidden] { display: none !important; }\n.panel[data-expanded='true'] {\n width: min(72rem, calc(100vw - 2rem));\n height: calc(100dvh - 2rem - env(safe-area-inset-bottom));\n}\n.header {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: space-between;\n min-height: 3.75rem;\n padding: 0.6rem 0.85rem;\n gap: 0.75rem;\n border-bottom: 1px solid var(--border);\n background: var(--background);\n}\n.identity { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }\n.avatar {\n display: grid;\n flex: none;\n place-items: center;\n width: 2.25rem;\n height: 2.25rem;\n border-radius: 0.7rem;\n background: color-mix(in srgb, var(--primary) 12%, var(--background));\n color: var(--primary);\n}\n.titles { display: flex; min-width: 0; flex-direction: column; gap: 0.08rem; }\n.title { font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.description { font-size: 0.72rem; color: var(--muted-foreground); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.actions { display: flex; flex: none; align-items: center; gap: 0.25rem; }\n.iconButton {\n display: grid;\n place-items: center;\n height: 2rem;\n width: 2rem;\n background: transparent;\n border: 0;\n border-radius: 0.5rem;\n color: var(--muted-foreground);\n cursor: pointer;\n}\n.iconButton:hover { background: var(--muted); color: var(--foreground); }\n.content { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden; }\n.content > * { flex: 1; min-height: 0; min-width: 0; }\n.backdrop { display: none; }\n\n@media (max-width: 640px) {\n .launcher {\n right: max(1rem, env(safe-area-inset-right));\n bottom: calc(1rem + env(safe-area-inset-bottom));\n width: 3.5rem;\n height: 3.5rem;\n min-width: 3.5rem;\n padding: 0;\n }\n .actions .iconButton:first-child { display: none; }\n .launcherText { display: none; }\n .backdrop { display: block; position: fixed; z-index: 100; inset: 0; background: color-mix(in srgb, var(--foreground) 35%, transparent); }\n .panel, .panel[data-expanded='true'] {\n inset: 0;\n width: 100vw;\n height: 100dvh;\n border: 0;\n border-radius: 0;\n }\n .header { padding-top: max(0.6rem, env(safe-area-inset-top)); }\n .content { padding-bottom: env(safe-area-inset-bottom); }\n}\n"],"mappings":""}
@@ -0,0 +1,49 @@
1
+ //#region src/components/ai-chat/ai-chat-dock.module.css
2
+ var dock = "uzi-ai-chat-dock-module__dock__hHrvH";
3
+ var launcher = "uzi-ai-chat-dock-module__launcher__4Q71E";
4
+ var iconButton = "uzi-ai-chat-dock-module__iconButton__lHEHr";
5
+ var launcherText = "uzi-ai-chat-dock-module__launcherText__eb6OK";
6
+ var panel = "uzi-ai-chat-dock-module__panel__4ig-u";
7
+ var header = "uzi-ai-chat-dock-module__header__-g1KW";
8
+ var identity = "uzi-ai-chat-dock-module__identity__qIsdQ";
9
+ var avatar = "uzi-ai-chat-dock-module__avatar__OTEjI";
10
+ var titles = "uzi-ai-chat-dock-module__titles__cEiMf";
11
+ var title = "uzi-ai-chat-dock-module__title__Boxdi";
12
+ var description = "uzi-ai-chat-dock-module__description__afqW-";
13
+ var actions = "uzi-ai-chat-dock-module__actions__rGPJb";
14
+ var content = "uzi-ai-chat-dock-module__content__t-hes";
15
+ var backdrop = "uzi-ai-chat-dock-module__backdrop__Z-yVb";
16
+ var ai_chat_dock_module_default = {
17
+ dock,
18
+ launcher,
19
+ iconButton,
20
+ launcherText,
21
+ panel,
22
+ header,
23
+ identity,
24
+ avatar,
25
+ titles,
26
+ title,
27
+ description,
28
+ actions,
29
+ content,
30
+ backdrop
31
+ };
32
+ //#endregion
33
+ exports.actions = actions;
34
+ exports.avatar = avatar;
35
+ exports.backdrop = backdrop;
36
+ exports.content = content;
37
+ exports.default = ai_chat_dock_module_default;
38
+ exports.description = description;
39
+ exports.dock = dock;
40
+ exports.header = header;
41
+ exports.iconButton = iconButton;
42
+ exports.identity = identity;
43
+ exports.launcher = launcher;
44
+ exports.launcherText = launcherText;
45
+ exports.panel = panel;
46
+ exports.title = title;
47
+ exports.titles = titles;
48
+
49
+ //# sourceMappingURL=index78.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index78.cjs","names":[],"sources":["../src/components/ai-chat/ai-chat-dock.module.css"],"sourcesContent":[".dock {\n position: relative;\n z-index: 100;\n}\n\n.launcher {\n position: fixed;\n right: max(1.25rem, env(safe-area-inset-right));\n bottom: calc(1.25rem + env(safe-area-inset-bottom));\n z-index: 100;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.6rem;\n min-height: 3.5rem;\n padding: 0.7rem 1.1rem;\n border: 1px solid var(--border);\n border-radius: 999px;\n background: var(--primary);\n color: var(--primary-foreground);\n box-shadow: 0 6px 24px color-mix(in srgb, var(--foreground) 20%, transparent);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.launcher:hover { filter: brightness(0.95); }\n.launcher:focus-visible, .iconButton:focus-visible {\n outline: 2px solid var(--ring, var(--primary));\n outline-offset: 2px;\n}\n.launcherText { white-space: nowrap; }\n.panel {\n position: fixed;\n z-index: 101;\n box-sizing: border-box;\n right: max(1rem, env(safe-area-inset-right));\n bottom: calc(1rem + env(safe-area-inset-bottom));\n width: min(26.25rem, calc(100vw - 2rem));\n height: min(42rem, calc(100dvh - 2rem - env(safe-area-inset-bottom)));\n min-height: 0;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--border);\n border-radius: 1rem;\n background: var(--background);\n color: var(--foreground);\n box-shadow: 0 12px 44px color-mix(in srgb, var(--foreground) 22%, transparent);\n overflow: hidden;\n}\n.panel[hidden] { display: none !important; }\n.panel[data-expanded='true'] {\n width: min(72rem, calc(100vw - 2rem));\n height: calc(100dvh - 2rem - env(safe-area-inset-bottom));\n}\n.header {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: space-between;\n min-height: 3.75rem;\n padding: 0.6rem 0.85rem;\n gap: 0.75rem;\n border-bottom: 1px solid var(--border);\n background: var(--background);\n}\n.identity { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }\n.avatar {\n display: grid;\n flex: none;\n place-items: center;\n width: 2.25rem;\n height: 2.25rem;\n border-radius: 0.7rem;\n background: color-mix(in srgb, var(--primary) 12%, var(--background));\n color: var(--primary);\n}\n.titles { display: flex; min-width: 0; flex-direction: column; gap: 0.08rem; }\n.title { font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.description { font-size: 0.72rem; color: var(--muted-foreground); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.actions { display: flex; flex: none; align-items: center; gap: 0.25rem; }\n.iconButton {\n display: grid;\n place-items: center;\n height: 2rem;\n width: 2rem;\n background: transparent;\n border: 0;\n border-radius: 0.5rem;\n color: var(--muted-foreground);\n cursor: pointer;\n}\n.iconButton:hover { background: var(--muted); color: var(--foreground); }\n.content { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden; }\n.content > * { flex: 1; min-height: 0; min-width: 0; }\n.backdrop { display: none; }\n\n@media (max-width: 640px) {\n .launcher {\n right: max(1rem, env(safe-area-inset-right));\n bottom: calc(1rem + env(safe-area-inset-bottom));\n width: 3.5rem;\n height: 3.5rem;\n min-width: 3.5rem;\n padding: 0;\n }\n .actions .iconButton:first-child { display: none; }\n .launcherText { display: none; }\n .backdrop { display: block; position: fixed; z-index: 100; inset: 0; background: color-mix(in srgb, var(--foreground) 35%, transparent); }\n .panel, .panel[data-expanded='true'] {\n inset: 0;\n width: 100vw;\n height: 100dvh;\n border: 0;\n border-radius: 0;\n }\n .header { padding-top: max(0.6rem, env(safe-area-inset-top)); }\n .content { padding-bottom: env(safe-area-inset-bottom); }\n}\n"],"mappings":""}
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ import ai_chat_dock_module_default from "./index77.js";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/ai-chat/AIChatDock.tsx
6
+ /**
7
+ * Shared chat navigation chrome. Children remain mounted while closed to retain
8
+ * the consumer's active AI SDK conversation, pending approvals and attachments.
9
+ * The dock never creates a second chat runtime or reads application messages.
10
+ * Desktop is deliberately non-modal: Tab can move back into the page.
11
+ * On mobile the full-screen modal sheet traps sequential keyboard focus.
12
+ */
13
+ function AIChatDock({ title, description, launcherLabel, showLauncher = true, panelId: controlledPanelId, externalLauncherId, children, open, defaultOpen = false, onOpenChange, expanded, defaultExpanded = false, onExpandedChange, className }) {
14
+ const autoId = useId();
15
+ const panelId = controlledPanelId ?? `uzi-ai-dock-${autoId}`;
16
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
17
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
18
+ const isOpen = open ?? internalOpen;
19
+ const isExpanded = expanded ?? internalExpanded;
20
+ const [mobile, setMobile] = useState(false);
21
+ const launcherRef = useRef(null);
22
+ const panelRef = useRef(null);
23
+ const openerRef = useRef(null);
24
+ const wasOpen = useRef(false);
25
+ const changeOpen = (next) => {
26
+ if (open === void 0) setInternalOpen(next);
27
+ onOpenChange?.(next);
28
+ };
29
+ const changeExpanded = (next) => {
30
+ if (expanded === void 0) setInternalExpanded(next);
31
+ onExpandedChange?.(next);
32
+ if (next && !isOpen) changeOpen(true);
33
+ };
34
+ useEffect(() => {
35
+ if (typeof window.matchMedia !== "function") return;
36
+ const media = window.matchMedia("(max-width: 640px)");
37
+ const update = () => setMobile(media.matches);
38
+ update();
39
+ media.addEventListener("change", update);
40
+ return () => media.removeEventListener("change", update);
41
+ }, []);
42
+ useEffect(() => {
43
+ if (isOpen && !wasOpen.current) {
44
+ openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
45
+ panelRef.current?.focus();
46
+ } else if (!isOpen && wasOpen.current) {
47
+ const previous = openerRef.current;
48
+ if (previous?.isConnected && previous !== panelRef.current) previous.focus();
49
+ else if (externalLauncherId) document.getElementById(externalLauncherId)?.focus();
50
+ else launcherRef.current?.focus();
51
+ }
52
+ wasOpen.current = isOpen;
53
+ }, [isOpen, externalLauncherId]);
54
+ useEffect(() => {
55
+ if (!isOpen || !mobile) return;
56
+ const previous = document.body.style.overflow;
57
+ document.body.style.overflow = "hidden";
58
+ return () => {
59
+ document.body.style.overflow = previous;
60
+ };
61
+ }, [isOpen, mobile]);
62
+ useEffect(() => {
63
+ if (!isOpen) return;
64
+ const keydown = (event) => {
65
+ if (event.key === "Escape") {
66
+ event.preventDefault();
67
+ changeOpen(false);
68
+ return;
69
+ }
70
+ if (event.key !== "Tab" || !mobile) return;
71
+ const focusable = Array.from(panelRef.current?.querySelectorAll("a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])") ?? []).filter((item) => item.tabIndex >= 0 && item.getClientRects().length > 0);
72
+ if (!focusable.length) {
73
+ event.preventDefault();
74
+ panelRef.current?.focus();
75
+ return;
76
+ }
77
+ const first = focusable[0], last = focusable[focusable.length - 1];
78
+ if (event.shiftKey && (document.activeElement === first || !panelRef.current?.contains(document.activeElement))) {
79
+ event.preventDefault();
80
+ last?.focus();
81
+ } else if (!event.shiftKey && (document.activeElement === last || !panelRef.current?.contains(document.activeElement))) {
82
+ event.preventDefault();
83
+ first?.focus();
84
+ }
85
+ };
86
+ document.addEventListener("keydown", keydown);
87
+ return () => document.removeEventListener("keydown", keydown);
88
+ }, [
89
+ isOpen,
90
+ mobile,
91
+ onOpenChange,
92
+ open
93
+ ]);
94
+ return /* @__PURE__ */ jsxs("div", {
95
+ className: [ai_chat_dock_module_default.dock, className].filter(Boolean).join(" "),
96
+ children: [
97
+ showLauncher && !isOpen && /* @__PURE__ */ jsxs("button", {
98
+ ref: launcherRef,
99
+ type: "button",
100
+ className: ai_chat_dock_module_default.launcher,
101
+ "aria-controls": panelId,
102
+ "aria-expanded": false,
103
+ "aria-label": launcherLabel ?? `Open ${title}`,
104
+ onClick: () => changeOpen(true),
105
+ children: [/* @__PURE__ */ jsx("svg", {
106
+ "aria-hidden": "true",
107
+ width: "23",
108
+ height: "23",
109
+ viewBox: "0 0 24 24",
110
+ fill: "none",
111
+ stroke: "currentColor",
112
+ strokeWidth: "1.8",
113
+ strokeLinecap: "round",
114
+ strokeLinejoin: "round",
115
+ children: /* @__PURE__ */ jsx("path", { d: "M21 11.5a8.5 8.5 0 0 1-8.5 8.5 9.1 9.1 0 0 1-4-.9L3 21l1.9-5.5a9.1 9.1 0 0 1-.9-4 8.5 8.5 0 0 1 8.5-8.5h.5A8.5 8.5 0 0 1 21 11.5Z" })
116
+ }), /* @__PURE__ */ jsx("span", {
117
+ className: ai_chat_dock_module_default.launcherText,
118
+ children: title
119
+ })]
120
+ }),
121
+ isOpen && mobile && /* @__PURE__ */ jsx("div", {
122
+ className: ai_chat_dock_module_default.backdrop,
123
+ "aria-hidden": "true",
124
+ onClick: () => changeOpen(false)
125
+ }),
126
+ /* @__PURE__ */ jsxs("section", {
127
+ ref: panelRef,
128
+ id: panelId,
129
+ role: "dialog",
130
+ "aria-modal": mobile && isOpen,
131
+ "aria-label": title,
132
+ tabIndex: -1,
133
+ className: ai_chat_dock_module_default.panel,
134
+ "data-expanded": isExpanded,
135
+ hidden: !isOpen,
136
+ children: [/* @__PURE__ */ jsxs("header", {
137
+ className: ai_chat_dock_module_default.header,
138
+ children: [/* @__PURE__ */ jsxs("div", {
139
+ className: ai_chat_dock_module_default.identity,
140
+ children: [/* @__PURE__ */ jsx("div", {
141
+ className: ai_chat_dock_module_default.avatar,
142
+ "aria-hidden": "true",
143
+ children: /* @__PURE__ */ jsxs("svg", {
144
+ width: "18",
145
+ height: "18",
146
+ viewBox: "0 0 24 24",
147
+ fill: "none",
148
+ stroke: "currentColor",
149
+ strokeWidth: "1.8",
150
+ strokeLinecap: "round",
151
+ strokeLinejoin: "round",
152
+ children: [/* @__PURE__ */ jsx("rect", {
153
+ x: "4",
154
+ y: "6",
155
+ width: "16",
156
+ height: "14",
157
+ rx: "4"
158
+ }), /* @__PURE__ */ jsx("path", { d: "M12 3v3M9 13h.01M15 13h.01M9 17h6" })]
159
+ })
160
+ }), /* @__PURE__ */ jsxs("div", {
161
+ className: ai_chat_dock_module_default.titles,
162
+ children: [/* @__PURE__ */ jsx("strong", {
163
+ className: ai_chat_dock_module_default.title,
164
+ children: title
165
+ }), description && /* @__PURE__ */ jsx("span", {
166
+ className: ai_chat_dock_module_default.description,
167
+ children: description
168
+ })]
169
+ })]
170
+ }), /* @__PURE__ */ jsxs("div", {
171
+ className: ai_chat_dock_module_default.actions,
172
+ children: [/* @__PURE__ */ jsx("button", {
173
+ type: "button",
174
+ className: ai_chat_dock_module_default.iconButton,
175
+ "aria-label": isExpanded ? `Collapse ${title}` : `Expand ${title}`,
176
+ title: isExpanded ? "Collapse" : "Expand",
177
+ onClick: () => changeExpanded(!isExpanded),
178
+ children: isExpanded ? /* @__PURE__ */ jsx("svg", {
179
+ width: "17",
180
+ height: "17",
181
+ viewBox: "0 0 24 24",
182
+ fill: "none",
183
+ stroke: "currentColor",
184
+ strokeWidth: "2",
185
+ strokeLinecap: "round",
186
+ strokeLinejoin: "round",
187
+ "aria-hidden": "true",
188
+ children: /* @__PURE__ */ jsx("path", { d: "M9 3v6H3M15 21v-6h6M3 9l6-6M21 15l-6 6" })
189
+ }) : /* @__PURE__ */ jsx("svg", {
190
+ width: "17",
191
+ height: "17",
192
+ viewBox: "0 0 24 24",
193
+ fill: "none",
194
+ stroke: "currentColor",
195
+ strokeWidth: "2",
196
+ strokeLinecap: "round",
197
+ strokeLinejoin: "round",
198
+ "aria-hidden": "true",
199
+ children: /* @__PURE__ */ jsx("path", { d: "M8 3H3v5M16 21h5v-5M3 3l7 7M21 21l-7-7" })
200
+ })
201
+ }), /* @__PURE__ */ jsx("button", {
202
+ type: "button",
203
+ className: ai_chat_dock_module_default.iconButton,
204
+ "aria-label": `Close ${title}`,
205
+ title: "Close",
206
+ onClick: () => changeOpen(false),
207
+ children: /* @__PURE__ */ jsx("svg", {
208
+ width: "18",
209
+ height: "18",
210
+ viewBox: "0 0 24 24",
211
+ fill: "none",
212
+ stroke: "currentColor",
213
+ strokeWidth: "2",
214
+ strokeLinecap: "round",
215
+ "aria-hidden": "true",
216
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
217
+ })
218
+ })]
219
+ })]
220
+ }), /* @__PURE__ */ jsx("div", {
221
+ className: ai_chat_dock_module_default.content,
222
+ children
223
+ })]
224
+ })
225
+ ]
226
+ });
227
+ }
228
+ //#endregion
229
+ export { AIChatDock };
230
+
231
+ //# sourceMappingURL=index78.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index78.js","names":[],"sources":["../src/components/ai-chat/AIChatDock.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from 'react';\n\nimport styles from './ai-chat-dock.module.css';\n\nexport interface AIChatDockProps {\n /** Visible assistant name, e.g. \"Betty\" or \"Ask Tultr\". */\n readonly title: string;\n readonly description?: string;\n /** Accessible name for the floating action button. Defaults to \"Open {title}\". */\n readonly launcherLabel?: string;\n /** Turn off the built-in FAB when a parent FloatingActionStack owns the launcher. */\n readonly showLauncher?: boolean;\n /** Optional stable panel ID for an external button's aria-controls. */\n readonly panelId?: string;\n /** ID of the external launch button to restore keyboard focus after close. */\n readonly externalLauncherId?: string;\n readonly children: ReactNode;\n /** Controlled visibility; when omitted, the dock manages its own state. */\n readonly open?: boolean;\n readonly defaultOpen?: boolean;\n readonly onOpenChange?: (open: boolean) => void;\n /** Controlled expansion; apps may synchronize this with a dedicated route. */\n readonly expanded?: boolean;\n readonly defaultExpanded?: boolean;\n readonly onExpandedChange?: (expanded: boolean) => void;\n readonly className?: string;\n}\n\n/**\n * Shared chat navigation chrome. Children remain mounted while closed to retain\n * the consumer's active AI SDK conversation, pending approvals and attachments.\n * The dock never creates a second chat runtime or reads application messages.\n * Desktop is deliberately non-modal: Tab can move back into the page.\n * On mobile the full-screen modal sheet traps sequential keyboard focus.\n */\nexport function AIChatDock({\n title,\n description,\n launcherLabel,\n showLauncher = true,\n panelId: controlledPanelId,\n externalLauncherId,\n children,\n open,\n defaultOpen = false,\n onOpenChange,\n expanded,\n defaultExpanded = false,\n onExpandedChange,\n className,\n}: AIChatDockProps) {\n const autoId = useId();\n const panelId = controlledPanelId ?? `uzi-ai-dock-${autoId}`;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n const isOpen = open ?? internalOpen;\n const isExpanded = expanded ?? internalExpanded;\n const [mobile, setMobile] = useState(false);\n const launcherRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLElement>(null);\n const openerRef = useRef<HTMLElement | null>(null);\n const wasOpen = useRef(false);\n\n const changeOpen = (next: boolean) => {\n if (open === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n };\n\n const changeExpanded = (next: boolean) => {\n if (expanded === undefined) setInternalExpanded(next);\n onExpandedChange?.(next);\n if (next && !isOpen) changeOpen(true);\n };\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return;\n const media = window.matchMedia('(max-width: 640px)');\n const update = () => setMobile(media.matches);\n update();\n media.addEventListener('change', update);\n return () => media.removeEventListener('change', update);\n }, []);\n\n useEffect(() => {\n if (isOpen && !wasOpen.current) {\n openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n panelRef.current?.focus();\n } else if (!isOpen && wasOpen.current) {\n const previous = openerRef.current;\n if (previous?.isConnected && previous !== panelRef.current) previous.focus();\n else if (externalLauncherId) document.getElementById(externalLauncherId)?.focus();\n else launcherRef.current?.focus();\n }\n wasOpen.current = isOpen;\n }, [isOpen, externalLauncherId]);\n\n useEffect(() => {\n if (!isOpen || !mobile) return;\n const previous = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => { document.body.style.overflow = previous; };\n }, [isOpen, mobile]);\n\n useEffect(() => {\n if (!isOpen) return;\n const keydown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n changeOpen(false);\n return;\n }\n // On desktop the panel is non-modal; only the mobile sheet traps focus.\n if (event.key !== 'Tab' || !mobile) return;\n const focusable = Array.from(panelRef.current?.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ) ?? []).filter((item) => item.tabIndex >= 0 && item.getClientRects().length > 0);\n if (!focusable.length) { event.preventDefault(); panelRef.current?.focus(); return; }\n const first = focusable[0], last = focusable[focusable.length - 1];\n if (event.shiftKey && (document.activeElement === first || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n last?.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n first?.focus();\n }\n };\n document.addEventListener('keydown', keydown);\n return () => document.removeEventListener('keydown', keydown);\n }, [isOpen, mobile, onOpenChange, open]);\n\n return (\n <div className={[styles.dock, className].filter(Boolean).join(' ')}>\n {showLauncher && !isOpen && (\n <button\n ref={launcherRef}\n type=\"button\"\n className={styles.launcher}\n aria-controls={panelId}\n aria-expanded={false}\n aria-label={launcherLabel ?? `Open ${title}`}\n onClick={() => changeOpen(true)}\n >\n <svg aria-hidden=\"true\" width=\"23\" height=\"23\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M21 11.5a8.5 8.5 0 0 1-8.5 8.5 9.1 9.1 0 0 1-4-.9L3 21l1.9-5.5a9.1 9.1 0 0 1-.9-4 8.5 8.5 0 0 1 8.5-8.5h.5A8.5 8.5 0 0 1 21 11.5Z\" />\n </svg>\n <span className={styles.launcherText}>{title}</span>\n </button>\n )}\n {isOpen && mobile && <div className={styles.backdrop} aria-hidden=\"true\" onClick={() => changeOpen(false)} />}\n <section\n ref={panelRef}\n id={panelId}\n role=\"dialog\"\n aria-modal={mobile && isOpen}\n aria-label={title}\n tabIndex={-1}\n className={styles.panel}\n data-expanded={isExpanded}\n hidden={!isOpen}\n >\n <header className={styles.header}>\n <div className={styles.identity}>\n <div className={styles.avatar} aria-hidden=\"true\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"4\" y=\"6\" width=\"16\" height=\"14\" rx=\"4\" />\n <path d=\"M12 3v3M9 13h.01M15 13h.01M9 17h6\" />\n </svg>\n </div>\n <div className={styles.titles}>\n <strong className={styles.title}>{title}</strong>\n {description && <span className={styles.description}>{description}</span>}\n </div>\n </div>\n <div className={styles.actions}>\n <button type=\"button\" className={styles.iconButton}\n aria-label={isExpanded ? `Collapse ${title}` : `Expand ${title}`}\n title={isExpanded ? 'Collapse' : 'Expand'}\n onClick={() => changeExpanded(!isExpanded)}>\n {isExpanded ? (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M9 3v6H3M15 21v-6h6M3 9l6-6M21 15l-6 6\" /></svg>\n ) : (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 3H3v5M16 21h5v-5M3 3l7 7M21 21l-7-7\" /></svg>\n )}\n </button>\n <button type=\"button\" className={styles.iconButton}\n aria-label={`Close ${title}`} title=\"Close\"\n onClick={() => changeOpen(false)}>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18M6 6l12 12\" /></svg>\n </button>\n </div>\n </header>\n <div className={styles.content}>{children}</div>\n </section>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAqCA,SAAgB,WAAW,EACzB,OACA,aACA,eACA,eAAe,MACf,SAAS,mBACT,oBACA,UACA,MACA,cAAc,OACd,cACA,UACA,kBAAkB,OAClB,kBACA,aACkB;CAClB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,qBAAqB,eAAe;CACpD,MAAM,CAAC,cAAc,mBAAmB,SAAS,WAAW;CAC5D,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,eAAe;CACxE,MAAM,SAAS,QAAQ;CACvB,MAAM,aAAa,YAAY;CAC/B,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,cAAc,OAA0B,IAAI;CAClD,MAAM,WAAW,OAAoB,IAAI;CACzC,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,UAAU,OAAO,KAAK;CAE5B,MAAM,cAAc,SAAkB;EACpC,IAAI,SAAS,KAAA,GAAW,gBAAgB,IAAI;EAC5C,eAAe,IAAI;CACrB;CAEA,MAAM,kBAAkB,SAAkB;EACxC,IAAI,aAAa,KAAA,GAAW,oBAAoB,IAAI;EACpD,mBAAmB,IAAI;EACvB,IAAI,QAAQ,CAAC,QAAQ,WAAW,IAAI;CACtC;CAEA,gBAAgB;EACd,IAAI,OAAO,OAAO,eAAe,YAAY;EAC7C,MAAM,QAAQ,OAAO,WAAW,oBAAoB;EACpD,MAAM,eAAe,UAAU,MAAM,OAAO;EAC5C,OAAO;EACP,MAAM,iBAAiB,UAAU,MAAM;EACvC,aAAa,MAAM,oBAAoB,UAAU,MAAM;CACzD,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,IAAI,UAAU,CAAC,QAAQ,SAAS;GAC9B,UAAU,UAAU,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;GAC7F,SAAS,SAAS,MAAM;EAC1B,OAAO,IAAI,CAAC,UAAU,QAAQ,SAAS;GACrC,MAAM,WAAW,UAAU;GAC3B,IAAI,UAAU,eAAe,aAAa,SAAS,SAAS,SAAS,MAAM;QACtE,IAAI,oBAAoB,SAAS,eAAe,kBAAkB,CAAC,EAAE,MAAM;QAC3E,YAAY,SAAS,MAAM;EAClC;EACA,QAAQ,UAAU;CACpB,GAAG,CAAC,QAAQ,kBAAkB,CAAC;CAE/B,gBAAgB;EACd,IAAI,CAAC,UAAU,CAAC,QAAQ;EACxB,MAAM,WAAW,SAAS,KAAK,MAAM;EACrC,SAAS,KAAK,MAAM,WAAW;EAC/B,aAAa;GAAE,SAAS,KAAK,MAAM,WAAW;EAAU;CAC1D,GAAG,CAAC,QAAQ,MAAM,CAAC;CAEnB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,WAAW,UAAyB;GACxC,IAAI,MAAM,QAAQ,UAAU;IAC1B,MAAM,eAAe;IACrB,WAAW,KAAK;IAChB;GACF;GAEA,IAAI,MAAM,QAAQ,SAAS,CAAC,QAAQ;GACpC,MAAM,YAAY,MAAM,KAAK,SAAS,SAAS,iBAC7C,6IACF,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,SAAS,KAAK,YAAY,KAAK,KAAK,eAAe,CAAC,CAAC,SAAS,CAAC;GAChF,IAAI,CAAC,UAAU,QAAQ;IAAE,MAAM,eAAe;IAAG,SAAS,SAAS,MAAM;IAAG;GAAQ;GACpF,MAAM,QAAQ,UAAU,IAAI,OAAO,UAAU,UAAU,SAAS;GAChE,IAAI,MAAM,aAAa,SAAS,kBAAkB,SAAS,CAAC,SAAS,SAAS,SAAS,SAAS,aAAa,IAAI;IAC/G,MAAM,eAAe;IACrB,MAAM,MAAM;GACd,OAAO,IAAI,CAAC,MAAM,aAAa,SAAS,kBAAkB,QAAQ,CAAC,SAAS,SAAS,SAAS,SAAS,aAAa,IAAI;IACtH,MAAM,eAAe;IACrB,OAAO,MAAM;GACf;EACF;EACA,SAAS,iBAAiB,WAAW,OAAO;EAC5C,aAAa,SAAS,oBAAoB,WAAW,OAAO;CAC9D,GAAG;EAAC;EAAQ;EAAQ;EAAc;CAAI,CAAC;CAEvC,OACE,qBAAC,OAAD;EAAK,WAAW,CAAC,4BAAO,MAAM,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;YAAjE;GACG,gBAAgB,CAAC,UAChB,qBAAC,UAAD;IACE,KAAK;IACL,MAAK;IACL,WAAW,4BAAO;IAClB,iBAAe;IACf,iBAAe;IACf,cAAY,iBAAiB,QAAQ;IACrC,eAAe,WAAW,IAAI;cAPhC,CASE,oBAAC,OAAD;KAAK,eAAY;KAAO,OAAM;KAAK,QAAO;KAAK,SAAQ;KAAY,MAAK;KACtE,QAAO;KAAe,aAAY;KAAM,eAAc;KAAQ,gBAAe;eAC7E,oBAAC,QAAD,EAAM,GAAE,oIAAqI,CAAA;IAC1I,CAAA,GACL,oBAAC,QAAD;KAAM,WAAW,4BAAO;eAAe;IAAY,CAAA,CAC7C;;GAET,UAAU,UAAU,oBAAC,OAAD;IAAK,WAAW,4BAAO;IAAU,eAAY;IAAO,eAAe,WAAW,KAAK;GAAI,CAAA;GAC5G,qBAAC,WAAD;IACE,KAAK;IACL,IAAI;IACJ,MAAK;IACL,cAAY,UAAU;IACtB,cAAY;IACZ,UAAU;IACV,WAAW,4BAAO;IAClB,iBAAe;IACf,QAAQ,CAAC;cATX,CAWE,qBAAC,UAAD;KAAQ,WAAW,4BAAO;eAA1B,CACE,qBAAC,OAAD;MAAK,WAAW,4BAAO;gBAAvB,CACE,oBAAC,OAAD;OAAK,WAAW,4BAAO;OAAQ,eAAY;iBACzC,qBAAC,OAAD;QAAK,OAAM;QAAK,QAAO;QAAK,SAAQ;QAAY,MAAK;QAAO,QAAO;QACjE,aAAY;QAAM,eAAc;QAAQ,gBAAe;kBADzD,CAEE,oBAAC,QAAD;SAAM,GAAE;SAAI,GAAE;SAAI,OAAM;SAAK,QAAO;SAAK,IAAG;QAAK,CAAA,GACjD,oBAAC,QAAD,EAAM,GAAE,oCAAqC,CAAA,CAC1C;;MACF,CAAA,GACL,qBAAC,OAAD;OAAK,WAAW,4BAAO;iBAAvB,CACE,oBAAC,UAAD;QAAQ,WAAW,4BAAO;kBAAQ;OAAc,CAAA,GAC/C,eAAe,oBAAC,QAAD;QAAM,WAAW,4BAAO;kBAAc;OAAkB,CAAA,CACrE;QACF;SACL,qBAAC,OAAD;MAAK,WAAW,4BAAO;gBAAvB,CACE,oBAAC,UAAD;OAAQ,MAAK;OAAS,WAAW,4BAAO;OACtC,cAAY,aAAa,YAAY,UAAU,UAAU;OACzD,OAAO,aAAa,aAAa;OACjC,eAAe,eAAe,CAAC,UAAU;iBACxC,aACC,oBAAC,OAAD;QAAK,OAAM;QAAK,QAAO;QAAK,SAAQ;QAAY,MAAK;QAAO,QAAO;QAAe,aAAY;QAC5F,eAAc;QAAQ,gBAAe;QAAQ,eAAY;kBAAO,oBAAC,QAAD,EAAM,GAAE,yCAA0C,CAAA;OAAM,CAAA,IAE1H,oBAAC,OAAD;QAAK,OAAM;QAAK,QAAO;QAAK,SAAQ;QAAY,MAAK;QAAO,QAAO;QAAe,aAAY;QAC5F,eAAc;QAAQ,gBAAe;QAAQ,eAAY;kBAAO,oBAAC,QAAD,EAAM,GAAE,yCAA0C,CAAA;OAAM,CAAA;MAEtH,CAAA,GACR,oBAAC,UAAD;OAAQ,MAAK;OAAS,WAAW,4BAAO;OACtC,cAAY,SAAS;OAAS,OAAM;OACpC,eAAe,WAAW,KAAK;iBAC/B,oBAAC,OAAD;QAAK,OAAM;QAAK,QAAO;QAAK,SAAQ;QAAY,MAAK;QAAO,QAAO;QACjE,aAAY;QAAI,eAAc;QAAQ,eAAY;kBAAO,oBAAC,QAAD,EAAM,GAAE,uBAAwB,CAAA;OAAM,CAAA;MAC3F,CAAA,CACL;OACC;QACR,oBAAC,OAAD;KAAK,WAAW,4BAAO;KAAU;IAAc,CAAA,CACxC;;EACN;;AAET"}