@open-mercato/ui 0.6.8-develop.6897.1.586171ad21 → 0.6.8-develop.6899.1.433952fd93

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.
package/dist/ai/AiChat.js CHANGED
@@ -90,11 +90,13 @@ function useAgentModels(agent) {
90
90
  const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false);
91
91
  const [defaultLabel, setDefaultLabel] = React.useState(null);
92
92
  const [status, setStatus] = React.useState("loading");
93
+ const [degraded, setDegraded] = React.useState(false);
93
94
  React.useEffect(() => {
94
95
  let cancelled = false;
95
96
  const modelsUrl = `/api/ai_assistant/ai/agents/${encodeURIComponent(agent)}/models`;
96
97
  setStatus("loading");
97
98
  setDefaultLabel(null);
99
+ setDegraded(false);
98
100
  apiCall(modelsUrl).then((result) => {
99
101
  if (cancelled) return;
100
102
  if (!result.ok || !result.result) {
@@ -105,6 +107,13 @@ function useAgentModels(agent) {
105
107
  const effectiveAllowRuntimeOverride = result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false;
106
108
  setAllowRuntimeOverride(effectiveAllowRuntimeOverride);
107
109
  setProviders(result.result.providers);
110
+ if (result.result.degraded === true) {
111
+ setDegraded(true);
112
+ logger.warn("AI agent models response is degraded", {
113
+ agent,
114
+ degradedReason: result.result.degradedReason ?? null
115
+ });
116
+ }
108
117
  setDefaultLabel(
109
118
  result.result.defaultProviderName && result.result.defaultModelName ? `${result.result.defaultProviderName} / ${result.result.defaultModelName}` : result.result.defaultProviderId && result.result.defaultModelId ? `${result.result.defaultProviderId} / ${result.result.defaultModelId}` : null
110
119
  );
@@ -123,7 +132,8 @@ function useAgentModels(agent) {
123
132
  allowRuntimeOverride,
124
133
  allowRuntimeModelOverride: allowRuntimeOverride,
125
134
  defaultLabel,
126
- status
135
+ status,
136
+ degraded
127
137
  };
128
138
  }
129
139
  function isModelPickerValueAvailable(value, providers) {
@@ -877,7 +887,8 @@ function AiChat({
877
887
  allowRuntimeOverride,
878
888
  allowRuntimeModelOverride,
879
889
  defaultLabel: modelDefaultLabel,
880
- status: modelProvidersStatus
890
+ status: modelProvidersStatus,
891
+ degraded: modelProvidersDegraded
881
892
  } = useAgentModels(agent);
882
893
  const [modelPickerValue, setModelPickerValue] = React.useState(
883
894
  () => readModelPickerValue(agent)
@@ -901,6 +912,7 @@ function AiChat({
901
912
  }, [agent]);
902
913
  React.useEffect(() => {
903
914
  if (modelProvidersStatus !== "ready") return;
915
+ if (modelProvidersDegraded) return;
904
916
  if (!allowRuntimeModelOverride || modelProviders.length === 0) {
905
917
  if (modelPickerValue !== null) {
906
918
  setModelPickerValue(null);
@@ -917,6 +929,7 @@ function AiChat({
917
929
  allowRuntimeModelOverride,
918
930
  modelPickerValue,
919
931
  modelProviders,
932
+ modelProvidersDegraded,
920
933
  modelProvidersStatus
921
934
  ]);
922
935
  const handleModelPickerChange = React.useCallback(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/AiChat.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Activity,\n AlertTriangle,\n BadgeQuestionMark,\n Bot,\n ChartNoAxesCombined,\n Check,\n ChevronDown,\n ChevronRight,\n Circle,\n CircleAlert,\n CircleCheck,\n CircleDashed,\n Copy,\n Database,\n Download,\n FileQuestion,\n GitBranch,\n Handshake,\n Lightbulb,\n ListChecks,\n Loader2,\n Maximize2,\n Paperclip,\n Plus,\n Search,\n Send,\n Square,\n TrendingDown,\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 {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog'\nimport { IconButton } from '../primitives/icon-button'\nimport { Label } from '../primitives/label'\nimport { Textarea } from '../primitives/textarea'\nimport { apiCall } from '../backend/utils/apiCall'\nimport { parseAiContentSegments } from './AiMessageContent'\nimport {\n ModelPicker,\n type ModelPickerProvider,\n type ModelPickerValue,\n} from './ModelPicker'\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 AiAgentTaskSnapshot,\n type AiChatMessage,\n type AiChatMessageFile,\n type AiChatMessageUiPart,\n type AiChatToolCallSnapshot,\n} from './useAiChat'\nimport { useAiChatUpload } from './useAiChatUpload'\nimport { useAiShortcuts } from './useAiShortcuts'\nimport { LoopTracePanel, type LoopTracePanelTrace } from './LoopTracePanel'\nimport { AiIcon } from './AiIcon'\nimport { readModelPickerValue, writeModelPickerValue } from './modelPickerStorage'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'AiChat' })\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\nconst COMPACT_FOOTER_MAX_WIDTH = 640\n\ninterface ModelsApiResponse {\n agentId: string\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride?: boolean\n defaultProviderId: string | null\n defaultModelId: string | null\n defaultProviderName?: string | null\n defaultModelName?: string | null\n providers: ModelPickerProvider[]\n}\n\ntype AgentModelsStatus = 'loading' | 'ready' | 'failed'\n\nfunction readErrorStatus(error: unknown): number | undefined {\n if (typeof error !== 'object' || error === null) return undefined\n const { status } = error as { status?: unknown }\n return typeof status === 'number' ? status : undefined\n}\n\nfunction logModelsFailure(status: number | undefined, details: Record<string, unknown>): void {\n const message = 'Failed to load AI agent models'\n if (status === 401 || status === 403) {\n logger.warn(message, details)\n return\n }\n logger.error(message, details)\n}\n\nfunction useAgentModels(agent: string): {\n providers: ModelPickerProvider[]\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride: boolean\n defaultLabel: string | null\n status: AgentModelsStatus\n} {\n const [providers, setProviders] = React.useState<ModelPickerProvider[]>([])\n const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false)\n const [defaultLabel, setDefaultLabel] = React.useState<string | null>(null)\n const [status, setStatus] = React.useState<AgentModelsStatus>('loading')\n\n React.useEffect(() => {\n let cancelled = false\n const modelsUrl = `/api/ai_assistant/ai/agents/${encodeURIComponent(agent)}/models`\n setStatus('loading')\n setDefaultLabel(null)\n apiCall<ModelsApiResponse>(modelsUrl)\n .then((result) => {\n if (cancelled) return\n if (!result.ok || !result.result) {\n logModelsFailure(result.status, { agent, status: result.status })\n setStatus('failed')\n return\n }\n const effectiveAllowRuntimeOverride =\n result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false\n setAllowRuntimeOverride(effectiveAllowRuntimeOverride)\n setProviders(result.result.providers)\n setDefaultLabel(\n result.result.defaultProviderName && result.result.defaultModelName\n ? `${result.result.defaultProviderName} / ${result.result.defaultModelName}`\n : result.result.defaultProviderId && result.result.defaultModelId\n ? `${result.result.defaultProviderId} / ${result.result.defaultModelId}`\n : null,\n )\n setStatus('ready')\n })\n .catch((err) => {\n if (cancelled) return\n logModelsFailure(readErrorStatus(err), { agent, err })\n setStatus('failed')\n })\n return () => {\n cancelled = true\n }\n }, [agent])\n\n return {\n providers,\n allowRuntimeOverride,\n allowRuntimeModelOverride: allowRuntimeOverride,\n defaultLabel,\n status,\n }\n}\n\nfunction isModelPickerValueAvailable(\n value: ModelPickerValue,\n providers: ModelPickerProvider[],\n): boolean {\n return providers.some(\n (provider) =>\n provider.id === value.providerId &&\n provider.models.some((model) => model.id === value.modelId),\n )\n}\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 /**\n * Optional loop trace from the last completed turn. When present and\n * `debug` is truthy, renders a `LoopTracePanel` inside the debug panel.\n * Populated by the host from the dispatcher SSE `loop-finish` event\n * (wired in l4.4). Ignored when `debug` is falsy.\n */\n loopTrace?: LoopTracePanelTrace\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 /** Initial compact composer state used before the footer has been measured. */\n defaultCompactFooter?: boolean\n /** Optional extra action nodes rendered in the conversation header bar. */\n headerActions?: React.ReactNode\n /** Called once on mount (and on reset) with the effective conversation id. */\n onConversationIdChange?: (conversationId: string) => void\n /**\n * Called when the server returns 404 for the active `conversationId`,\n * e.g. when the id was carried over from a previous tenant/org scope\n * and no longer exists. The host (typically the AI dock) should remove\n * the corresponding session entry so the stale tab disappears instead\n * of staying empty.\n */\n onConversationNotFound?: () => void\n}\n\ninterface ServerEmittedUiPartRef {\n componentId: AiUiPartComponentId\n payload?: unknown\n pendingActionId?: string\n}\n\nfunction mapErrorCodeToStatus(\n code: string | undefined,\n): 'error' | 'warning' {\n if (!code) return 'error'\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 // Content-safety rejections are soft: the user can rephrase and retry.\n 'moderation_blocked',\n 'moderation_unavailable',\n ])\n return warningCodes.has(code) ? 'warning' : 'error'\n}\n\n/**\n * Maps known error codes to a translated, user-safe message. For content-safety\n * codes the raw server message is intentionally generic (no category oracle);\n * the UI shows the friendly localized copy instead of the internal text.\n */\nfunction resolveChatErrorMessage(\n code: string | undefined,\n fallback: string,\n translate: (key: string, fallbackText?: string) => string,\n): string {\n switch (code) {\n case 'moderation_blocked':\n return translate(\n 'ai_assistant.errors.moderationBlocked',\n 'Your message was blocked by the content safety filter. Please rephrase and try again.',\n )\n case 'moderation_unavailable':\n return translate(\n 'ai_assistant.errors.moderationUnavailable',\n 'The content safety check is temporarily unavailable. Please try again in a moment.',\n )\n default:\n return fallback\n }\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 titleWords(value: string): string {\n return value.replace(/\\b[a-z]/g, (char) => char.toUpperCase())\n}\n\nfunction formatToolCaption(call: AiChatToolCallSnapshot): string {\n const rawCaption = call.caption?.trim()\n if (!rawCaption) return call.toolName\n const caption = titleWords(rawCaption.replace(/\\s*\u00B7\\s*/g, ' - '))\n return `${caption} (${call.toolName})`\n}\n\n/**\n * Visible agent task plan rendered above raw tool-call rows. Phase 1\n * implementation of spec `.ai/specs/implemented/2026-05-13-ai-chat-visible-task-plan.md`:\n * compact, fixed-height rows with icon + label + status badge so the\n * panel does not jump while streaming. Source of truth is the\n * client-local `taskPlan` array on the assistant message.\n */\nfunction AiChatTaskPlan({ tasks }: { tasks: AiAgentTaskSnapshot[] }) {\n const t = useT()\n if (!tasks || tasks.length === 0) return null\n return (\n <div\n className=\"rounded-md border border-border bg-muted/30\"\n data-ai-chat-task-plan=\"\"\n data-ai-chat-task-plan-count={tasks.length}\n >\n <div className=\"flex items-center gap-2 border-b border-border/60 px-2 py-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n <ListChecks className=\"size-3.5 text-muted-foreground\" aria-hidden />\n <span>{t('ai_assistant.chat.taskPlanTitle', 'Plan')}</span>\n <span className=\"ml-auto font-mono\">{tasks.length}</span>\n </div>\n <ul className=\"divide-y divide-border/60\" data-ai-chat-task-plan-list=\"\">\n {tasks.map((task) => (\n <AiChatTaskRow key={task.id} task={task} />\n ))}\n </ul>\n </div>\n )\n}\n\nfunction AiChatTaskRow({ task }: { task: AiAgentTaskSnapshot }) {\n const t = useT()\n const statusLabel =\n task.state === 'running'\n ? t('ai_assistant.chat.taskRunning', 'running\u2026')\n : task.state === 'done'\n ? t('ai_assistant.chat.taskDone', 'done')\n : task.state === 'failed'\n ? t('ai_assistant.chat.taskFailed', 'failed')\n : task.state === 'skipped'\n ? t('ai_assistant.chat.taskSkipped', 'skipped')\n : t('ai_assistant.chat.taskPending', 'pending')\n const Icon =\n task.state === 'running'\n ? Loader2\n : task.state === 'done'\n ? CircleCheck\n : task.state === 'failed'\n ? CircleAlert\n : task.state === 'skipped'\n ? CircleDashed\n : Circle\n const iconClassName = cn(\n 'size-3.5 shrink-0',\n task.state === 'running' && 'animate-spin text-muted-foreground',\n task.state === 'done' && 'text-status-success-icon',\n task.state === 'failed' && 'text-destructive',\n task.state === 'skipped' && 'text-muted-foreground',\n task.state === 'pending' && 'text-muted-foreground',\n )\n const statusBadgeClassName = cn(\n 'ml-auto text-[10px] uppercase tracking-wide',\n task.state === 'done' && 'text-status-success-text',\n task.state === 'failed' && 'text-destructive',\n task.state !== 'done' && task.state !== 'failed' && 'text-muted-foreground',\n )\n return (\n <li\n className=\"flex h-7 items-center gap-2 px-2 text-xs\"\n data-ai-chat-task-id={task.id}\n data-ai-chat-task-state={task.state}\n data-ai-chat-task-source={task.source}\n data-ai-chat-task-tool-call-id={task.toolCallId}\n >\n <Icon className={iconClassName} aria-hidden />\n <span className=\"truncate\" title={task.detail ?? task.label}>\n {task.label}\n </span>\n <span className={statusBadgeClassName}>{statusLabel}</span>\n </li>\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\n className=\"rounded-md border border-border bg-muted/30\"\n data-ai-chat-tool-calls=\"\"\n data-ai-chat-tool-call-count={toolCalls.length}\n >\n <div className=\"flex items-center gap-2 border-b border-border/60 px-2 py-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n <Wrench className=\"size-3.5 text-muted-foreground\" aria-hidden />\n <span>{t('ai_assistant.chat.agentTasksTitle', 'Tool calls')}</span>\n <span className=\"ml-auto font-mono\">{toolCalls.length}</span>\n </div>\n <ul className=\"divide-y divide-border/60\">\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 <li\n key={call.id}\n className={cn(\n isError ? 'bg-destructive/5' : '',\n )}\n data-ai-chat-tool-call={call.toolName}\n data-ai-chat-tool-state={call.state}\n >\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n type=\"button\"\n onClick={() => setOpenId(isOpen ? null : call.id)}\n className=\"w-full justify-start rounded-none px-2 text-left 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=\"truncate\" title={formatToolCaption(call)}>\n {formatToolCaption(call)}\n </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 </li>\n )\n })}\n </ul>\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 getAttachmentImageUrl(file: AiChatMessageFile): string | null {\n if (file.id) return `/api/attachments/image/${encodeURIComponent(file.id)}`\n return file.previewUrl ?? null\n}\n\nfunction getAttachmentDownloadUrl(file: AiChatMessageFile): string | null {\n if (file.id) return `/api/attachments/file/${encodeURIComponent(file.id)}?download=1`\n return file.previewUrl ?? null\n}\n\nfunction MessageFileAttachment({ file }: { file: AiChatMessageFile }) {\n const t = useT()\n const [zoomOpen, setZoomOpen] = React.useState(false)\n const imageUrl = file.previewUrl ? getAttachmentImageUrl(file) : null\n const downloadUrl = getAttachmentDownloadUrl(file)\n const downloadLabel = t('ai_assistant.chat.downloadFile', 'Download {name}').replace(\n '{name}',\n file.name,\n )\n const zoomLabel = t('ai_assistant.chat.zoomImage', 'Open {name} preview').replace(\n '{name}',\n file.name,\n )\n\n if (!file.previewUrl) {\n return (\n <span className=\"inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs\">\n <Paperclip className=\"size-3 shrink-0\" aria-hidden />\n <span className=\"max-w-[180px] truncate\">{file.name}</span>\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"xs\"\n className=\"-mr-1 size-5 shrink-0\"\n >\n <a href={downloadUrl} download={file.name} aria-label={downloadLabel}>\n <Download className=\"size-3\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </span>\n )\n }\n\n return (\n <>\n <div className=\"group/file relative max-w-full\">\n <button\n type=\"button\"\n className=\"block max-w-full rounded-md border border-border bg-muted/30 focus-visible:outline-none focus-visible:shadow-focus\"\n onClick={() => setZoomOpen(true)}\n aria-label={zoomLabel}\n data-ai-chat-image-preview=\"\"\n >\n <img\n src={file.previewUrl}\n alt={file.name}\n className=\"max-h-32 max-w-[200px] rounded-md object-cover\"\n />\n <span className=\"pointer-events-none absolute right-1 top-1 inline-flex size-6 items-center justify-center rounded-md bg-background/90 text-muted-foreground opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100\">\n <Maximize2 className=\"size-3.5\" aria-hidden />\n </span>\n </button>\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"white\"\n size=\"xs\"\n className=\"absolute bottom-1 right-1 opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100\"\n >\n <a\n href={downloadUrl}\n download={file.name}\n aria-label={downloadLabel}\n onClick={(event) => event.stopPropagation()}\n data-ai-chat-file-download=\"\"\n >\n <Download className=\"size-3.5\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </div>\n\n <Dialog open={zoomOpen} onOpenChange={setZoomOpen}>\n <DialogContent className=\"gap-3 p-4 sm:max-w-[min(96vw,960px)]\">\n <DialogHeader className=\"min-w-0 pr-16\">\n <DialogTitle className=\"truncate text-sm\">{file.name}</DialogTitle>\n <DialogDescription className=\"sr-only\">\n {t('ai_assistant.chat.imagePreviewDialogDescription', 'Image preview')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"relative flex min-h-0 items-center justify-center overflow-auto rounded-md bg-muted/30 p-2\">\n <img\n src={imageUrl ?? file.previewUrl}\n alt={file.name}\n className=\"max-h-[75vh] max-w-full object-contain\"\n />\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"white\"\n size=\"sm\"\n className=\"absolute bottom-3 right-3 shadow-xs\"\n >\n <a\n href={downloadUrl}\n download={file.name}\n aria-label={downloadLabel}\n data-ai-chat-file-download=\"\"\n >\n <Download className=\"size-4\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nfunction MessageRow({\n message,\n registry,\n onMutationRequested,\n isOwner,\n}: {\n message: AiChatMessage\n registry?: AiUiPartRegistry\n onMutationRequested?: (pendingActionId: string) => void\n /** Whether the current viewer owns the conversation. Used to label foreign user messages. */\n isOwner?: boolean | null\n}) {\n const t = useT()\n const isAssistant = message.role === 'assistant'\n const isOtherUsersMessage = !isAssistant && isOwner === false\n const label = isAssistant\n ? t('ai_assistant.chat.assistantRoleLabel', 'Assistant')\n : isOtherUsersMessage\n ? t('ai_assistant.chat.ownerRoleLabel', 'Owner')\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 const visibleTaskPlan =\n isAssistant && message.taskPlan\n ? message.taskPlan.filter((task) => task.source === 'agent')\n : []\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 <MessageFileAttachment key={`${file.id ?? file.name}:${i}`} file={file} />\n ))}\n </div>\n ) : null}\n {isAssistant && message.reasoning ? (\n <ReasoningPanel\n text={message.reasoning}\n streaming={message.reasoningStreaming === true}\n />\n ) : null}\n {visibleTaskPlan.length > 0 ? (\n <AiChatTaskPlan tasks={visibleTaskPlan} />\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 logger.warn('No component registered for UI part', { componentId: part.componentId })\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 getSuggestionIcon(suggestion: AiChatSuggestion): {\n icon: React.ReactNode\n name: string\n} {\n if (suggestion.icon) return { icon: suggestion.icon, name: 'custom' }\n const text = `${suggestion.label} ${suggestion.prompt}`.toLowerCase()\n const iconClassName = 'size-4 text-foreground'\n if (/\\b(analy[sz]e|analysis|health|score|summary|summari[sz]e)\\b/.test(text)) {\n return { icon: <ChartNoAxesCombined className={iconClassName} />, name: 'analysis' }\n }\n if (/\\b(at[-\\s]?risk|risk|stalled|stale|overdue|danger)\\b/.test(text)) {\n return { icon: <AlertTriangle className={iconClassName} />, name: 'risk' }\n }\n if (/\\bpipeline|stage|funnel\\b/.test(text)) {\n return { icon: <GitBranch className={iconClassName} />, name: 'pipeline' }\n }\n if (/\\bdeal|opportunit(?:y|ies)\\b/.test(text)) {\n return { icon: <Handshake className={iconClassName} />, name: 'deal' }\n }\n if (/\\bdata|access|record|records|schema\\b/.test(text)) {\n return { icon: <Database className={iconClassName} />, name: 'data' }\n }\n if (/\\bwhat can you help|help me|capabilit(?:y|ies)\\b/.test(text)) {\n return { icon: <BadgeQuestionMark className={iconClassName} />, name: 'capabilities' }\n }\n if (/\\bsuggest|try|idea|ideas|recommend\\b/.test(text)) {\n return { icon: <Lightbulb className={iconClassName} />, name: 'ideas' }\n }\n if (/\\bhow do i|how to|use this|instructions?|guide\\b/.test(text)) {\n return { icon: <FileQuestion className={iconClassName} />, name: 'guide' }\n }\n if (/\\bfind|search|show|list|lookup\\b/.test(text)) {\n return { icon: <Search className={iconClassName} />, name: 'search' }\n }\n if (/\\bactivity|activities|timeline|recent\\b/.test(text)) {\n return { icon: <Activity className={iconClassName} />, name: 'activity' }\n }\n if (/\\bdown|drop|declin|loss|lost\\b/.test(text)) {\n return { icon: <TrendingDown className={iconClassName} />, name: 'trend-down' }\n }\n return { icon: <AiIcon className={iconClassName} />, name: 'ai' }\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 const suggestionIcon = getSuggestionIcon(suggestion)\n return (\n <Button\n variant=\"outline\"\n key={index}\n type=\"button\"\n className=\"h-auto w-full justify-start whitespace-normal rounded-lg bg-card px-3 py-2.5 text-left text-sm\"\n onClick={() => onSuggestionClick(suggestion.prompt)}\n data-ai-chat-suggestion={index}\n data-ai-chat-suggestion-icon={suggestionIcon.name}\n >\n <span className=\"shrink-0 text-muted-foreground\" aria-hidden>\n {suggestionIcon.icon}\n </span>\n <span>{suggestion.label}</span>\n </Button>\n )\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 loopTrace,\n conversationId,\n suggestions,\n contextItems,\n welcomeTitle,\n welcomeDescription,\n defaultCompactFooter = false,\n headerActions,\n onConversationIdChange,\n onConversationNotFound,\n}: AiChatProps) {\n const t = useT()\n const onConversationIdChangeRef = React.useRef(onConversationIdChange)\n React.useLayoutEffect(() => {\n onConversationIdChangeRef.current = onConversationIdChange\n })\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 local message for immediate previews; once the transcript\n * is loaded from server storage the attachment id is used to rebuild a\n * same-origin thumbnail URL.\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 const footerRef = React.useRef<HTMLDivElement | null>(null)\n const [isCompactFooter, setIsCompactFooter] = React.useState(defaultCompactFooter)\n\n React.useEffect(() => {\n const element = footerRef.current\n if (!element || typeof ResizeObserver === 'undefined') return\n\n const updateCompactState = (width: number) => {\n setIsCompactFooter(width < COMPACT_FOOTER_MAX_WIDTH)\n }\n\n updateCompactState(element.getBoundingClientRect().width)\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n updateCompactState(entry.contentRect.width)\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\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 {\n providers: modelProviders,\n allowRuntimeOverride,\n allowRuntimeModelOverride,\n defaultLabel: modelDefaultLabel,\n status: modelProvidersStatus,\n } = useAgentModels(agent)\n\n const [modelPickerValue, setModelPickerValue] = React.useState<ModelPickerValue | null>(() =>\n readModelPickerValue(agent),\n )\n\n const effectiveModelPickerValue = React.useMemo(() => {\n if (modelProvidersStatus !== 'ready' || !allowRuntimeOverride || modelProviders.length === 0) {\n return null\n }\n if (modelPickerValue && isModelPickerValueAvailable(modelPickerValue, modelProviders)) {\n return modelPickerValue\n }\n return null\n }, [\n allowRuntimeOverride,\n modelPickerValue,\n modelProviders,\n modelProvidersStatus,\n ])\n\n React.useEffect(() => {\n setModelPickerValue(readModelPickerValue(agent))\n }, [agent])\n\n React.useEffect(() => {\n if (modelProvidersStatus !== 'ready') return\n if (!allowRuntimeModelOverride || modelProviders.length === 0) {\n if (modelPickerValue !== null) {\n setModelPickerValue(null)\n writeModelPickerValue(agent, null)\n }\n return\n }\n if (modelPickerValue && !isModelPickerValueAvailable(modelPickerValue, modelProviders)) {\n setModelPickerValue(null)\n writeModelPickerValue(agent, null)\n }\n }, [\n agent,\n allowRuntimeModelOverride,\n modelPickerValue,\n modelProviders,\n modelProvidersStatus,\n ])\n\n const handleModelPickerChange = React.useCallback(\n (value: ModelPickerValue | null) => {\n setModelPickerValue(value)\n writeModelPickerValue(agent, value)\n },\n [agent],\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 providerOverride: effectiveModelPickerValue?.providerId ?? null,\n modelOverride: effectiveModelPickerValue?.modelId ?? null,\n onConversationNotFound,\n })\n\n const isStreaming = chat.status === 'streaming'\n const isSubmitting = chat.status === 'submitting'\n const isBusy = isStreaming || isSubmitting\n\n React.useEffect(() => {\n onConversationIdChangeRef.current?.(chat.conversationId)\n }, [chat.conversationId])\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 // Once visible text/reasoning exists and no tool is pending, hide the\n // placeholder even if the network stream stays open for a tail event. That\n // avoids showing \"Thinking...\" under an already usable answer.\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 lastSnapshotRef = React.useRef<string>('')\n const [, setStreamTick] = React.useState(0)\n\n React.useEffect(() => {\n if (assistantStreamSnapshot !== lastSnapshotRef.current) {\n lastSnapshotRef.current = assistantStreamSnapshot\n setStreamTick((value) => value + 1)\n }\n }, [assistantStreamSnapshot])\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 ))\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 id: entry.attachmentId,\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 errorStatus = mapErrorCodeToStatus(chat.error?.code)\n\n return (\n <section\n className={cn(\n 'flex h-full min-h-[320px] min-w-0 flex-col gap-3 overflow-hidden 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 min-w-0 items-center justify-between gap-2 border-b border-border pb-2\">\n <div className=\"flex min-w-0 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 {headerActions ?? null}\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=\"min-w-0 flex-1 space-y-2 overflow-y-auto overflow-x-hidden 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 isOwner={chat.isOwner}\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 status={errorStatus} 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 {resolveChatErrorMessage(chat.error.code, chat.error.message, t)}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {chat.isOwner === false ? (\n <div\n className=\"flex items-center justify-center rounded-md border border-border bg-muted/30 px-3 py-3 text-xs text-muted-foreground\"\n data-ai-chat-read-only-notice=\"\"\n >\n {t('ai_assistant.chat.readOnlyNotice', 'This is a shared conversation. You can read but not reply.')}\n </div>\n ) : null}\n <form\n className={cn('flex min-w-0 flex-col gap-2', chat.isOwner === false && 'hidden')}\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=\"min-w-0 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\n ref={footerRef}\n className=\"flex min-w-0 items-center justify-between gap-2\"\n data-ai-chat-footer=\"\"\n data-ai-chat-footer-compact={isCompactFooter ? 'true' : 'false'}\n >\n <div className=\"flex min-w-0 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 {allowRuntimeOverride && modelProviders.length > 0 ? (\n <ModelPicker\n agentId={agent}\n value={effectiveModelPickerValue}\n onChange={handleModelPickerChange}\n availableProviders={modelProviders}\n disabled={isBusy}\n compact={isCompactFooter}\n defaultLabel={modelDefaultLabel}\n className=\"shrink-0\"\n />\n ) : null}\n <p className={cn('text-xs text-muted-foreground', isCompactFooter && 'hidden')}>\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 shrink-0 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 className={cn(isCompactFooter && 'w-8 px-0')}\n >\n <Send className=\"size-4\" aria-hidden />\n {!isCompactFooter ? (\n <span>{t('ai_assistant.chat.send', 'Send message')}</span>\n ) : null}\n </Button>\n </div>\n </div>\n </form>\n\n {debug ? (\n <AiChatDebugPanel\n tools={debugTools}\n promptSections={debugPromptSections}\n loopTrace={loopTrace ?? chat.lastLoopTrace ?? undefined}\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 loopTrace?: LoopTracePanelTrace\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 loopTrace,\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 {loopTrace ? (\n <LoopTracePanel trace={loopTrace} />\n ) : null}\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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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Activity,\n AlertTriangle,\n BadgeQuestionMark,\n Bot,\n ChartNoAxesCombined,\n Check,\n ChevronDown,\n ChevronRight,\n Circle,\n CircleAlert,\n CircleCheck,\n CircleDashed,\n Copy,\n Database,\n Download,\n FileQuestion,\n GitBranch,\n Handshake,\n Lightbulb,\n ListChecks,\n Loader2,\n Maximize2,\n Paperclip,\n Plus,\n Search,\n Send,\n Square,\n TrendingDown,\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 {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog'\nimport { IconButton } from '../primitives/icon-button'\nimport { Label } from '../primitives/label'\nimport { Textarea } from '../primitives/textarea'\nimport { apiCall } from '../backend/utils/apiCall'\nimport { parseAiContentSegments } from './AiMessageContent'\nimport {\n ModelPicker,\n type ModelPickerProvider,\n type ModelPickerValue,\n} from './ModelPicker'\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 AiAgentTaskSnapshot,\n type AiChatMessage,\n type AiChatMessageFile,\n type AiChatMessageUiPart,\n type AiChatToolCallSnapshot,\n} from './useAiChat'\nimport { useAiChatUpload } from './useAiChatUpload'\nimport { useAiShortcuts } from './useAiShortcuts'\nimport { LoopTracePanel, type LoopTracePanelTrace } from './LoopTracePanel'\nimport { AiIcon } from './AiIcon'\nimport { readModelPickerValue, writeModelPickerValue } from './modelPickerStorage'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'AiChat' })\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\nconst COMPACT_FOOTER_MAX_WIDTH = 640\n\ninterface ModelsApiResponse {\n agentId: string\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride?: boolean\n defaultProviderId: string | null\n defaultModelId: string | null\n defaultProviderName?: string | null\n defaultModelName?: string | null\n providers: ModelPickerProvider[]\n degraded?: boolean\n degradedReason?: string | null\n}\n\ntype AgentModelsStatus = 'loading' | 'ready' | 'failed'\n\nfunction readErrorStatus(error: unknown): number | undefined {\n if (typeof error !== 'object' || error === null) return undefined\n const { status } = error as { status?: unknown }\n return typeof status === 'number' ? status : undefined\n}\n\nfunction logModelsFailure(status: number | undefined, details: Record<string, unknown>): void {\n const message = 'Failed to load AI agent models'\n if (status === 401 || status === 403) {\n logger.warn(message, details)\n return\n }\n logger.error(message, details)\n}\n\nfunction useAgentModels(agent: string): {\n providers: ModelPickerProvider[]\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride: boolean\n defaultLabel: string | null\n status: AgentModelsStatus\n degraded: boolean\n} {\n const [providers, setProviders] = React.useState<ModelPickerProvider[]>([])\n const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false)\n const [defaultLabel, setDefaultLabel] = React.useState<string | null>(null)\n const [status, setStatus] = React.useState<AgentModelsStatus>('loading')\n const [degraded, setDegraded] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n const modelsUrl = `/api/ai_assistant/ai/agents/${encodeURIComponent(agent)}/models`\n setStatus('loading')\n setDefaultLabel(null)\n setDegraded(false)\n apiCall<ModelsApiResponse>(modelsUrl)\n .then((result) => {\n if (cancelled) return\n if (!result.ok || !result.result) {\n logModelsFailure(result.status, { agent, status: result.status })\n setStatus('failed')\n return\n }\n const effectiveAllowRuntimeOverride =\n result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false\n setAllowRuntimeOverride(effectiveAllowRuntimeOverride)\n setProviders(result.result.providers)\n if (result.result.degraded === true) {\n setDegraded(true)\n logger.warn('AI agent models response is degraded', {\n agent,\n degradedReason: result.result.degradedReason ?? null,\n })\n }\n setDefaultLabel(\n result.result.defaultProviderName && result.result.defaultModelName\n ? `${result.result.defaultProviderName} / ${result.result.defaultModelName}`\n : result.result.defaultProviderId && result.result.defaultModelId\n ? `${result.result.defaultProviderId} / ${result.result.defaultModelId}`\n : null,\n )\n setStatus('ready')\n })\n .catch((err) => {\n if (cancelled) return\n logModelsFailure(readErrorStatus(err), { agent, err })\n setStatus('failed')\n })\n return () => {\n cancelled = true\n }\n }, [agent])\n\n return {\n providers,\n allowRuntimeOverride,\n allowRuntimeModelOverride: allowRuntimeOverride,\n defaultLabel,\n status,\n degraded,\n }\n}\n\nfunction isModelPickerValueAvailable(\n value: ModelPickerValue,\n providers: ModelPickerProvider[],\n): boolean {\n return providers.some(\n (provider) =>\n provider.id === value.providerId &&\n provider.models.some((model) => model.id === value.modelId),\n )\n}\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 /**\n * Optional loop trace from the last completed turn. When present and\n * `debug` is truthy, renders a `LoopTracePanel` inside the debug panel.\n * Populated by the host from the dispatcher SSE `loop-finish` event\n * (wired in l4.4). Ignored when `debug` is falsy.\n */\n loopTrace?: LoopTracePanelTrace\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 /** Initial compact composer state used before the footer has been measured. */\n defaultCompactFooter?: boolean\n /** Optional extra action nodes rendered in the conversation header bar. */\n headerActions?: React.ReactNode\n /** Called once on mount (and on reset) with the effective conversation id. */\n onConversationIdChange?: (conversationId: string) => void\n /**\n * Called when the server returns 404 for the active `conversationId`,\n * e.g. when the id was carried over from a previous tenant/org scope\n * and no longer exists. The host (typically the AI dock) should remove\n * the corresponding session entry so the stale tab disappears instead\n * of staying empty.\n */\n onConversationNotFound?: () => void\n}\n\ninterface ServerEmittedUiPartRef {\n componentId: AiUiPartComponentId\n payload?: unknown\n pendingActionId?: string\n}\n\nfunction mapErrorCodeToStatus(\n code: string | undefined,\n): 'error' | 'warning' {\n if (!code) return 'error'\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 // Content-safety rejections are soft: the user can rephrase and retry.\n 'moderation_blocked',\n 'moderation_unavailable',\n ])\n return warningCodes.has(code) ? 'warning' : 'error'\n}\n\n/**\n * Maps known error codes to a translated, user-safe message. For content-safety\n * codes the raw server message is intentionally generic (no category oracle);\n * the UI shows the friendly localized copy instead of the internal text.\n */\nfunction resolveChatErrorMessage(\n code: string | undefined,\n fallback: string,\n translate: (key: string, fallbackText?: string) => string,\n): string {\n switch (code) {\n case 'moderation_blocked':\n return translate(\n 'ai_assistant.errors.moderationBlocked',\n 'Your message was blocked by the content safety filter. Please rephrase and try again.',\n )\n case 'moderation_unavailable':\n return translate(\n 'ai_assistant.errors.moderationUnavailable',\n 'The content safety check is temporarily unavailable. Please try again in a moment.',\n )\n default:\n return fallback\n }\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 titleWords(value: string): string {\n return value.replace(/\\b[a-z]/g, (char) => char.toUpperCase())\n}\n\nfunction formatToolCaption(call: AiChatToolCallSnapshot): string {\n const rawCaption = call.caption?.trim()\n if (!rawCaption) return call.toolName\n const caption = titleWords(rawCaption.replace(/\\s*\u00B7\\s*/g, ' - '))\n return `${caption} (${call.toolName})`\n}\n\n/**\n * Visible agent task plan rendered above raw tool-call rows. Phase 1\n * implementation of spec `.ai/specs/implemented/2026-05-13-ai-chat-visible-task-plan.md`:\n * compact, fixed-height rows with icon + label + status badge so the\n * panel does not jump while streaming. Source of truth is the\n * client-local `taskPlan` array on the assistant message.\n */\nfunction AiChatTaskPlan({ tasks }: { tasks: AiAgentTaskSnapshot[] }) {\n const t = useT()\n if (!tasks || tasks.length === 0) return null\n return (\n <div\n className=\"rounded-md border border-border bg-muted/30\"\n data-ai-chat-task-plan=\"\"\n data-ai-chat-task-plan-count={tasks.length}\n >\n <div className=\"flex items-center gap-2 border-b border-border/60 px-2 py-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n <ListChecks className=\"size-3.5 text-muted-foreground\" aria-hidden />\n <span>{t('ai_assistant.chat.taskPlanTitle', 'Plan')}</span>\n <span className=\"ml-auto font-mono\">{tasks.length}</span>\n </div>\n <ul className=\"divide-y divide-border/60\" data-ai-chat-task-plan-list=\"\">\n {tasks.map((task) => (\n <AiChatTaskRow key={task.id} task={task} />\n ))}\n </ul>\n </div>\n )\n}\n\nfunction AiChatTaskRow({ task }: { task: AiAgentTaskSnapshot }) {\n const t = useT()\n const statusLabel =\n task.state === 'running'\n ? t('ai_assistant.chat.taskRunning', 'running\u2026')\n : task.state === 'done'\n ? t('ai_assistant.chat.taskDone', 'done')\n : task.state === 'failed'\n ? t('ai_assistant.chat.taskFailed', 'failed')\n : task.state === 'skipped'\n ? t('ai_assistant.chat.taskSkipped', 'skipped')\n : t('ai_assistant.chat.taskPending', 'pending')\n const Icon =\n task.state === 'running'\n ? Loader2\n : task.state === 'done'\n ? CircleCheck\n : task.state === 'failed'\n ? CircleAlert\n : task.state === 'skipped'\n ? CircleDashed\n : Circle\n const iconClassName = cn(\n 'size-3.5 shrink-0',\n task.state === 'running' && 'animate-spin text-muted-foreground',\n task.state === 'done' && 'text-status-success-icon',\n task.state === 'failed' && 'text-destructive',\n task.state === 'skipped' && 'text-muted-foreground',\n task.state === 'pending' && 'text-muted-foreground',\n )\n const statusBadgeClassName = cn(\n 'ml-auto text-[10px] uppercase tracking-wide',\n task.state === 'done' && 'text-status-success-text',\n task.state === 'failed' && 'text-destructive',\n task.state !== 'done' && task.state !== 'failed' && 'text-muted-foreground',\n )\n return (\n <li\n className=\"flex h-7 items-center gap-2 px-2 text-xs\"\n data-ai-chat-task-id={task.id}\n data-ai-chat-task-state={task.state}\n data-ai-chat-task-source={task.source}\n data-ai-chat-task-tool-call-id={task.toolCallId}\n >\n <Icon className={iconClassName} aria-hidden />\n <span className=\"truncate\" title={task.detail ?? task.label}>\n {task.label}\n </span>\n <span className={statusBadgeClassName}>{statusLabel}</span>\n </li>\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\n className=\"rounded-md border border-border bg-muted/30\"\n data-ai-chat-tool-calls=\"\"\n data-ai-chat-tool-call-count={toolCalls.length}\n >\n <div className=\"flex items-center gap-2 border-b border-border/60 px-2 py-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">\n <Wrench className=\"size-3.5 text-muted-foreground\" aria-hidden />\n <span>{t('ai_assistant.chat.agentTasksTitle', 'Tool calls')}</span>\n <span className=\"ml-auto font-mono\">{toolCalls.length}</span>\n </div>\n <ul className=\"divide-y divide-border/60\">\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 <li\n key={call.id}\n className={cn(\n isError ? 'bg-destructive/5' : '',\n )}\n data-ai-chat-tool-call={call.toolName}\n data-ai-chat-tool-state={call.state}\n >\n <Button\n variant=\"ghost\"\n size=\"2xs\"\n type=\"button\"\n onClick={() => setOpenId(isOpen ? null : call.id)}\n className=\"w-full justify-start rounded-none px-2 text-left 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=\"truncate\" title={formatToolCaption(call)}>\n {formatToolCaption(call)}\n </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 </li>\n )\n })}\n </ul>\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 getAttachmentImageUrl(file: AiChatMessageFile): string | null {\n if (file.id) return `/api/attachments/image/${encodeURIComponent(file.id)}`\n return file.previewUrl ?? null\n}\n\nfunction getAttachmentDownloadUrl(file: AiChatMessageFile): string | null {\n if (file.id) return `/api/attachments/file/${encodeURIComponent(file.id)}?download=1`\n return file.previewUrl ?? null\n}\n\nfunction MessageFileAttachment({ file }: { file: AiChatMessageFile }) {\n const t = useT()\n const [zoomOpen, setZoomOpen] = React.useState(false)\n const imageUrl = file.previewUrl ? getAttachmentImageUrl(file) : null\n const downloadUrl = getAttachmentDownloadUrl(file)\n const downloadLabel = t('ai_assistant.chat.downloadFile', 'Download {name}').replace(\n '{name}',\n file.name,\n )\n const zoomLabel = t('ai_assistant.chat.zoomImage', 'Open {name} preview').replace(\n '{name}',\n file.name,\n )\n\n if (!file.previewUrl) {\n return (\n <span className=\"inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs\">\n <Paperclip className=\"size-3 shrink-0\" aria-hidden />\n <span className=\"max-w-[180px] truncate\">{file.name}</span>\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"xs\"\n className=\"-mr-1 size-5 shrink-0\"\n >\n <a href={downloadUrl} download={file.name} aria-label={downloadLabel}>\n <Download className=\"size-3\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </span>\n )\n }\n\n return (\n <>\n <div className=\"group/file relative max-w-full\">\n <button\n type=\"button\"\n className=\"block max-w-full rounded-md border border-border bg-muted/30 focus-visible:outline-none focus-visible:shadow-focus\"\n onClick={() => setZoomOpen(true)}\n aria-label={zoomLabel}\n data-ai-chat-image-preview=\"\"\n >\n <img\n src={file.previewUrl}\n alt={file.name}\n className=\"max-h-32 max-w-[200px] rounded-md object-cover\"\n />\n <span className=\"pointer-events-none absolute right-1 top-1 inline-flex size-6 items-center justify-center rounded-md bg-background/90 text-muted-foreground opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100\">\n <Maximize2 className=\"size-3.5\" aria-hidden />\n </span>\n </button>\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"white\"\n size=\"xs\"\n className=\"absolute bottom-1 right-1 opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100\"\n >\n <a\n href={downloadUrl}\n download={file.name}\n aria-label={downloadLabel}\n onClick={(event) => event.stopPropagation()}\n data-ai-chat-file-download=\"\"\n >\n <Download className=\"size-3.5\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </div>\n\n <Dialog open={zoomOpen} onOpenChange={setZoomOpen}>\n <DialogContent className=\"gap-3 p-4 sm:max-w-[min(96vw,960px)]\">\n <DialogHeader className=\"min-w-0 pr-16\">\n <DialogTitle className=\"truncate text-sm\">{file.name}</DialogTitle>\n <DialogDescription className=\"sr-only\">\n {t('ai_assistant.chat.imagePreviewDialogDescription', 'Image preview')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"relative flex min-h-0 items-center justify-center overflow-auto rounded-md bg-muted/30 p-2\">\n <img\n src={imageUrl ?? file.previewUrl}\n alt={file.name}\n className=\"max-h-[75vh] max-w-full object-contain\"\n />\n {downloadUrl ? (\n <IconButton\n asChild\n variant=\"white\"\n size=\"sm\"\n className=\"absolute bottom-3 right-3 shadow-xs\"\n >\n <a\n href={downloadUrl}\n download={file.name}\n aria-label={downloadLabel}\n data-ai-chat-file-download=\"\"\n >\n <Download className=\"size-4\" aria-hidden />\n </a>\n </IconButton>\n ) : null}\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nfunction MessageRow({\n message,\n registry,\n onMutationRequested,\n isOwner,\n}: {\n message: AiChatMessage\n registry?: AiUiPartRegistry\n onMutationRequested?: (pendingActionId: string) => void\n /** Whether the current viewer owns the conversation. Used to label foreign user messages. */\n isOwner?: boolean | null\n}) {\n const t = useT()\n const isAssistant = message.role === 'assistant'\n const isOtherUsersMessage = !isAssistant && isOwner === false\n const label = isAssistant\n ? t('ai_assistant.chat.assistantRoleLabel', 'Assistant')\n : isOtherUsersMessage\n ? t('ai_assistant.chat.ownerRoleLabel', 'Owner')\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 const visibleTaskPlan =\n isAssistant && message.taskPlan\n ? message.taskPlan.filter((task) => task.source === 'agent')\n : []\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 <MessageFileAttachment key={`${file.id ?? file.name}:${i}`} file={file} />\n ))}\n </div>\n ) : null}\n {isAssistant && message.reasoning ? (\n <ReasoningPanel\n text={message.reasoning}\n streaming={message.reasoningStreaming === true}\n />\n ) : null}\n {visibleTaskPlan.length > 0 ? (\n <AiChatTaskPlan tasks={visibleTaskPlan} />\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 logger.warn('No component registered for UI part', { componentId: part.componentId })\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 getSuggestionIcon(suggestion: AiChatSuggestion): {\n icon: React.ReactNode\n name: string\n} {\n if (suggestion.icon) return { icon: suggestion.icon, name: 'custom' }\n const text = `${suggestion.label} ${suggestion.prompt}`.toLowerCase()\n const iconClassName = 'size-4 text-foreground'\n if (/\\b(analy[sz]e|analysis|health|score|summary|summari[sz]e)\\b/.test(text)) {\n return { icon: <ChartNoAxesCombined className={iconClassName} />, name: 'analysis' }\n }\n if (/\\b(at[-\\s]?risk|risk|stalled|stale|overdue|danger)\\b/.test(text)) {\n return { icon: <AlertTriangle className={iconClassName} />, name: 'risk' }\n }\n if (/\\bpipeline|stage|funnel\\b/.test(text)) {\n return { icon: <GitBranch className={iconClassName} />, name: 'pipeline' }\n }\n if (/\\bdeal|opportunit(?:y|ies)\\b/.test(text)) {\n return { icon: <Handshake className={iconClassName} />, name: 'deal' }\n }\n if (/\\bdata|access|record|records|schema\\b/.test(text)) {\n return { icon: <Database className={iconClassName} />, name: 'data' }\n }\n if (/\\bwhat can you help|help me|capabilit(?:y|ies)\\b/.test(text)) {\n return { icon: <BadgeQuestionMark className={iconClassName} />, name: 'capabilities' }\n }\n if (/\\bsuggest|try|idea|ideas|recommend\\b/.test(text)) {\n return { icon: <Lightbulb className={iconClassName} />, name: 'ideas' }\n }\n if (/\\bhow do i|how to|use this|instructions?|guide\\b/.test(text)) {\n return { icon: <FileQuestion className={iconClassName} />, name: 'guide' }\n }\n if (/\\bfind|search|show|list|lookup\\b/.test(text)) {\n return { icon: <Search className={iconClassName} />, name: 'search' }\n }\n if (/\\bactivity|activities|timeline|recent\\b/.test(text)) {\n return { icon: <Activity className={iconClassName} />, name: 'activity' }\n }\n if (/\\bdown|drop|declin|loss|lost\\b/.test(text)) {\n return { icon: <TrendingDown className={iconClassName} />, name: 'trend-down' }\n }\n return { icon: <AiIcon className={iconClassName} />, name: 'ai' }\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 const suggestionIcon = getSuggestionIcon(suggestion)\n return (\n <Button\n variant=\"outline\"\n key={index}\n type=\"button\"\n className=\"h-auto w-full justify-start whitespace-normal rounded-lg bg-card px-3 py-2.5 text-left text-sm\"\n onClick={() => onSuggestionClick(suggestion.prompt)}\n data-ai-chat-suggestion={index}\n data-ai-chat-suggestion-icon={suggestionIcon.name}\n >\n <span className=\"shrink-0 text-muted-foreground\" aria-hidden>\n {suggestionIcon.icon}\n </span>\n <span>{suggestion.label}</span>\n </Button>\n )\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 loopTrace,\n conversationId,\n suggestions,\n contextItems,\n welcomeTitle,\n welcomeDescription,\n defaultCompactFooter = false,\n headerActions,\n onConversationIdChange,\n onConversationNotFound,\n}: AiChatProps) {\n const t = useT()\n const onConversationIdChangeRef = React.useRef(onConversationIdChange)\n React.useLayoutEffect(() => {\n onConversationIdChangeRef.current = onConversationIdChange\n })\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 local message for immediate previews; once the transcript\n * is loaded from server storage the attachment id is used to rebuild a\n * same-origin thumbnail URL.\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 const footerRef = React.useRef<HTMLDivElement | null>(null)\n const [isCompactFooter, setIsCompactFooter] = React.useState(defaultCompactFooter)\n\n React.useEffect(() => {\n const element = footerRef.current\n if (!element || typeof ResizeObserver === 'undefined') return\n\n const updateCompactState = (width: number) => {\n setIsCompactFooter(width < COMPACT_FOOTER_MAX_WIDTH)\n }\n\n updateCompactState(element.getBoundingClientRect().width)\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n updateCompactState(entry.contentRect.width)\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\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 {\n providers: modelProviders,\n allowRuntimeOverride,\n allowRuntimeModelOverride,\n defaultLabel: modelDefaultLabel,\n status: modelProvidersStatus,\n degraded: modelProvidersDegraded,\n } = useAgentModels(agent)\n\n const [modelPickerValue, setModelPickerValue] = React.useState<ModelPickerValue | null>(() =>\n readModelPickerValue(agent),\n )\n\n const effectiveModelPickerValue = React.useMemo(() => {\n if (modelProvidersStatus !== 'ready' || !allowRuntimeOverride || modelProviders.length === 0) {\n return null\n }\n if (modelPickerValue && isModelPickerValueAvailable(modelPickerValue, modelProviders)) {\n return modelPickerValue\n }\n return null\n }, [\n allowRuntimeOverride,\n modelPickerValue,\n modelProviders,\n modelProvidersStatus,\n ])\n\n React.useEffect(() => {\n setModelPickerValue(readModelPickerValue(agent))\n }, [agent])\n\n React.useEffect(() => {\n if (modelProvidersStatus !== 'ready') return\n // A degraded response lost part of its tenant-scoped narrowing, so it is not an\n // authoritative view of what this agent offers \u2014 dropping the user's stored\n // selection against it would lose a still-valid choice.\n if (modelProvidersDegraded) return\n if (!allowRuntimeModelOverride || modelProviders.length === 0) {\n if (modelPickerValue !== null) {\n setModelPickerValue(null)\n writeModelPickerValue(agent, null)\n }\n return\n }\n if (modelPickerValue && !isModelPickerValueAvailable(modelPickerValue, modelProviders)) {\n setModelPickerValue(null)\n writeModelPickerValue(agent, null)\n }\n }, [\n agent,\n allowRuntimeModelOverride,\n modelPickerValue,\n modelProviders,\n modelProvidersDegraded,\n modelProvidersStatus,\n ])\n\n const handleModelPickerChange = React.useCallback(\n (value: ModelPickerValue | null) => {\n setModelPickerValue(value)\n writeModelPickerValue(agent, value)\n },\n [agent],\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 providerOverride: effectiveModelPickerValue?.providerId ?? null,\n modelOverride: effectiveModelPickerValue?.modelId ?? null,\n onConversationNotFound,\n })\n\n const isStreaming = chat.status === 'streaming'\n const isSubmitting = chat.status === 'submitting'\n const isBusy = isStreaming || isSubmitting\n\n React.useEffect(() => {\n onConversationIdChangeRef.current?.(chat.conversationId)\n }, [chat.conversationId])\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 // Once visible text/reasoning exists and no tool is pending, hide the\n // placeholder even if the network stream stays open for a tail event. That\n // avoids showing \"Thinking...\" under an already usable answer.\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 lastSnapshotRef = React.useRef<string>('')\n const [, setStreamTick] = React.useState(0)\n\n React.useEffect(() => {\n if (assistantStreamSnapshot !== lastSnapshotRef.current) {\n lastSnapshotRef.current = assistantStreamSnapshot\n setStreamTick((value) => value + 1)\n }\n }, [assistantStreamSnapshot])\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 ))\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 id: entry.attachmentId,\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 errorStatus = mapErrorCodeToStatus(chat.error?.code)\n\n return (\n <section\n className={cn(\n 'flex h-full min-h-[320px] min-w-0 flex-col gap-3 overflow-hidden 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 min-w-0 items-center justify-between gap-2 border-b border-border pb-2\">\n <div className=\"flex min-w-0 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 {headerActions ?? null}\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=\"min-w-0 flex-1 space-y-2 overflow-y-auto overflow-x-hidden 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 isOwner={chat.isOwner}\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 status={errorStatus} 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 {resolveChatErrorMessage(chat.error.code, chat.error.message, t)}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {chat.isOwner === false ? (\n <div\n className=\"flex items-center justify-center rounded-md border border-border bg-muted/30 px-3 py-3 text-xs text-muted-foreground\"\n data-ai-chat-read-only-notice=\"\"\n >\n {t('ai_assistant.chat.readOnlyNotice', 'This is a shared conversation. You can read but not reply.')}\n </div>\n ) : null}\n <form\n className={cn('flex min-w-0 flex-col gap-2', chat.isOwner === false && 'hidden')}\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=\"min-w-0 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\n ref={footerRef}\n className=\"flex min-w-0 items-center justify-between gap-2\"\n data-ai-chat-footer=\"\"\n data-ai-chat-footer-compact={isCompactFooter ? 'true' : 'false'}\n >\n <div className=\"flex min-w-0 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 {allowRuntimeOverride && modelProviders.length > 0 ? (\n <ModelPicker\n agentId={agent}\n value={effectiveModelPickerValue}\n onChange={handleModelPickerChange}\n availableProviders={modelProviders}\n disabled={isBusy}\n compact={isCompactFooter}\n defaultLabel={modelDefaultLabel}\n className=\"shrink-0\"\n />\n ) : null}\n <p className={cn('text-xs text-muted-foreground', isCompactFooter && 'hidden')}>\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 shrink-0 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 className={cn(isCompactFooter && 'w-8 px-0')}\n >\n <Send className=\"size-4\" aria-hidden />\n {!isCompactFooter ? (\n <span>{t('ai_assistant.chat.send', 'Send message')}</span>\n ) : null}\n </Button>\n </div>\n </div>\n </form>\n\n {debug ? (\n <AiChatDebugPanel\n tools={debugTools}\n promptSections={debugPromptSections}\n loopTrace={loopTrace ?? chat.lastLoopTrace ?? undefined}\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 loopTrace?: LoopTracePanelTrace\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 loopTrace,\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 {loopTrace ? (\n <LoopTracePanel trace={loopTrace} />\n ) : null}\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
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.8-develop.6897.1.586171ad21",
3
+ "version": "0.6.8-develop.6899.1.433952fd93",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,14 +155,14 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.8-develop.6897.1.586171ad21",
158
+ "@open-mercato/shared": "0.6.8-develop.6899.1.433952fd93",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
164
  "@figma/code-connect": "^1.3.4",
165
- "@open-mercato/shared": "0.6.8-develop.6897.1.586171ad21",
165
+ "@open-mercato/shared": "0.6.8-develop.6899.1.433952fd93",
166
166
  "@testing-library/dom": "^10.4.1",
167
167
  "@testing-library/jest-dom": "^7.0.0",
168
168
  "@testing-library/react": "^16.3.1",
package/src/ai/AiChat.tsx CHANGED
@@ -96,6 +96,8 @@ interface ModelsApiResponse {
96
96
  defaultProviderName?: string | null
97
97
  defaultModelName?: string | null
98
98
  providers: ModelPickerProvider[]
99
+ degraded?: boolean
100
+ degradedReason?: string | null
99
101
  }
100
102
 
101
103
  type AgentModelsStatus = 'loading' | 'ready' | 'failed'
@@ -121,17 +123,20 @@ function useAgentModels(agent: string): {
121
123
  allowRuntimeModelOverride: boolean
122
124
  defaultLabel: string | null
123
125
  status: AgentModelsStatus
126
+ degraded: boolean
124
127
  } {
125
128
  const [providers, setProviders] = React.useState<ModelPickerProvider[]>([])
126
129
  const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false)
127
130
  const [defaultLabel, setDefaultLabel] = React.useState<string | null>(null)
128
131
  const [status, setStatus] = React.useState<AgentModelsStatus>('loading')
132
+ const [degraded, setDegraded] = React.useState(false)
129
133
 
130
134
  React.useEffect(() => {
131
135
  let cancelled = false
132
136
  const modelsUrl = `/api/ai_assistant/ai/agents/${encodeURIComponent(agent)}/models`
133
137
  setStatus('loading')
134
138
  setDefaultLabel(null)
139
+ setDegraded(false)
135
140
  apiCall<ModelsApiResponse>(modelsUrl)
136
141
  .then((result) => {
137
142
  if (cancelled) return
@@ -144,6 +149,13 @@ function useAgentModels(agent: string): {
144
149
  result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false
145
150
  setAllowRuntimeOverride(effectiveAllowRuntimeOverride)
146
151
  setProviders(result.result.providers)
152
+ if (result.result.degraded === true) {
153
+ setDegraded(true)
154
+ logger.warn('AI agent models response is degraded', {
155
+ agent,
156
+ degradedReason: result.result.degradedReason ?? null,
157
+ })
158
+ }
147
159
  setDefaultLabel(
148
160
  result.result.defaultProviderName && result.result.defaultModelName
149
161
  ? `${result.result.defaultProviderName} / ${result.result.defaultModelName}`
@@ -169,6 +181,7 @@ function useAgentModels(agent: string): {
169
181
  allowRuntimeModelOverride: allowRuntimeOverride,
170
182
  defaultLabel,
171
183
  status,
184
+ degraded,
172
185
  }
173
186
  }
174
187
 
@@ -1243,6 +1256,7 @@ export function AiChat({
1243
1256
  allowRuntimeModelOverride,
1244
1257
  defaultLabel: modelDefaultLabel,
1245
1258
  status: modelProvidersStatus,
1259
+ degraded: modelProvidersDegraded,
1246
1260
  } = useAgentModels(agent)
1247
1261
 
1248
1262
  const [modelPickerValue, setModelPickerValue] = React.useState<ModelPickerValue | null>(() =>
@@ -1270,6 +1284,10 @@ export function AiChat({
1270
1284
 
1271
1285
  React.useEffect(() => {
1272
1286
  if (modelProvidersStatus !== 'ready') return
1287
+ // A degraded response lost part of its tenant-scoped narrowing, so it is not an
1288
+ // authoritative view of what this agent offers — dropping the user's stored
1289
+ // selection against it would lose a still-valid choice.
1290
+ if (modelProvidersDegraded) return
1273
1291
  if (!allowRuntimeModelOverride || modelProviders.length === 0) {
1274
1292
  if (modelPickerValue !== null) {
1275
1293
  setModelPickerValue(null)
@@ -1286,6 +1304,7 @@ export function AiChat({
1286
1304
  allowRuntimeModelOverride,
1287
1305
  modelPickerValue,
1288
1306
  modelProviders,
1307
+ modelProvidersDegraded,
1289
1308
  modelProvidersStatus,
1290
1309
  ])
1291
1310
 
@@ -403,6 +403,50 @@ describe('<AiChat>', () => {
403
403
  })
404
404
  })
405
405
 
406
+ it('keeps a stored model picker value when the models response is degraded', async () => {
407
+ const apiCallMock = apiCall as unknown as jest.Mock
408
+ apiCallMock.mockResolvedValueOnce({
409
+ ok: true,
410
+ status: 200,
411
+ result: {
412
+ agentId: 'customers.account_assistant',
413
+ allowRuntimeModelOverride: true,
414
+ defaultProviderId: 'openai',
415
+ defaultModelId: 'gpt-5-mini',
416
+ degraded: true,
417
+ degradedReason: 'tenant_allowlist_unavailable',
418
+ providers: [
419
+ {
420
+ id: 'openai',
421
+ name: 'OpenAI',
422
+ isDefault: true,
423
+ models: [
424
+ {
425
+ id: 'gpt-5-mini',
426
+ name: 'GPT-5 Mini',
427
+ isDefault: true,
428
+ },
429
+ ],
430
+ },
431
+ ],
432
+ },
433
+ })
434
+ const storedValue = JSON.stringify({ providerId: 'openai', modelId: 'gpt-4o' })
435
+ window.localStorage.setItem('om-ai-model-picker:customers.account_assistant', storedValue)
436
+
437
+ renderWithProviders(<AiChat agent="customers.account_assistant" />, { dict })
438
+
439
+ await waitFor(() => {
440
+ expect(screen.getByRole('button', { name: 'Select AI model' })).toHaveAttribute(
441
+ 'title',
442
+ 'Default: openai / gpt-5-mini',
443
+ )
444
+ })
445
+ expect(window.localStorage.getItem('om-ai-model-picker:customers.account_assistant')).toBe(
446
+ storedValue,
447
+ )
448
+ })
449
+
406
450
  it('does not send provider or model overrides while the model picker is on Default', async () => {
407
451
  const apiCallMock = apiCall as unknown as jest.Mock
408
452
  apiCallMock.mockResolvedValueOnce({