@open-mercato/ui 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -0
  3. package/dist/ai/AiAssistantLauncher.js +8 -8
  4. package/dist/ai/AiAssistantLauncher.js.map +2 -2
  5. package/dist/ai/AiChat.js +92 -93
  6. package/dist/ai/AiChat.js.map +2 -2
  7. package/dist/ai/AiIcon.js +17 -0
  8. package/dist/ai/AiIcon.js.map +7 -0
  9. package/dist/ai/LoopDisabledBanner.js +28 -0
  10. package/dist/ai/LoopDisabledBanner.js.map +7 -0
  11. package/dist/ai/LoopTracePanel.js +123 -0
  12. package/dist/ai/LoopTracePanel.js.map +7 -0
  13. package/dist/ai/ModelPicker.js.map +1 -1
  14. package/dist/ai/index.js +8 -0
  15. package/dist/ai/index.js.map +2 -2
  16. package/dist/ai/parts/ConfirmationCard.js +1 -5
  17. package/dist/ai/parts/ConfirmationCard.js.map +2 -2
  18. package/dist/ai/parts/FieldDiffCard.js +8 -15
  19. package/dist/ai/parts/FieldDiffCard.js.map +2 -2
  20. package/dist/ai/parts/MutationResultCard.js +1 -4
  21. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  22. package/dist/ai/ui-parts/pending-phase3-placeholder.js +0 -2
  23. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +2 -2
  24. package/dist/ai/useAiChat.js +18 -4
  25. package/dist/ai/useAiChat.js.map +2 -2
  26. package/dist/backend/CrudForm.js +24 -2
  27. package/dist/backend/CrudForm.js.map +2 -2
  28. package/dist/backend/DataTable.js +2 -2
  29. package/dist/backend/DataTable.js.map +2 -2
  30. package/dist/backend/confirm-dialog/ConfirmDialog.js +102 -96
  31. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  32. package/dist/backend/detail/CustomDataSection.js +22 -3
  33. package/dist/backend/detail/CustomDataSection.js.map +2 -2
  34. package/dist/backend/filters/AdvancedFilterBuilder.js +2 -2
  35. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  36. package/dist/backend/injection/ComponentOverrideProvider.js +1 -1
  37. package/dist/backend/injection/ComponentOverrideProvider.js.map +2 -2
  38. package/dist/backend/inputs/ComboboxInput.js +25 -7
  39. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  40. package/dist/backend/progress/useProgressPoll.js +58 -3
  41. package/dist/backend/progress/useProgressPoll.js.map +2 -2
  42. package/dist/backend/progress/useProgressSse.js +15 -2
  43. package/dist/backend/progress/useProgressSse.js.map +2 -2
  44. package/dist/backend/utils/bulkDelete.js +76 -0
  45. package/dist/backend/utils/bulkDelete.js.map +2 -2
  46. package/package.json +11 -6
  47. package/src/ai/AiAssistantLauncher.tsx +8 -8
  48. package/src/ai/AiChat.tsx +44 -38
  49. package/src/ai/AiIcon.tsx +12 -0
  50. package/src/ai/LoopDisabledBanner.tsx +38 -0
  51. package/src/ai/LoopTracePanel.tsx +184 -0
  52. package/src/ai/ModelPicker.tsx +1 -1
  53. package/src/ai/__tests__/AiChat.test.tsx +174 -5
  54. package/src/ai/index.ts +8 -0
  55. package/src/ai/parts/ConfirmationCard.tsx +1 -5
  56. package/src/ai/parts/FieldDiffCard.tsx +0 -3
  57. package/src/ai/parts/MutationResultCard.tsx +1 -4
  58. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +5 -0
  59. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +0 -2
  60. package/src/ai/useAiChat.ts +35 -5
  61. package/src/backend/CrudForm.tsx +30 -3
  62. package/src/backend/DataTable.tsx +2 -2
  63. package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +72 -0
  64. package/src/backend/__tests__/CustomDataSection.test.tsx +76 -0
  65. package/src/backend/__tests__/DataTable.render.test.tsx +36 -0
  66. package/src/backend/__tests__/component-replacement.test.tsx +44 -0
  67. package/src/backend/confirm-dialog/ConfirmDialog.tsx +101 -91
  68. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +19 -0
  69. package/src/backend/detail/CustomDataSection.tsx +26 -3
  70. package/src/backend/filters/AdvancedFilterBuilder.tsx +6 -6
  71. package/src/backend/injection/ComponentOverrideProvider.tsx +2 -1
  72. package/src/backend/inputs/ComboboxInput.tsx +29 -7
  73. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +33 -2
  74. package/src/backend/progress/useProgressPoll.ts +68 -3
  75. package/src/backend/progress/useProgressSse.ts +16 -2
  76. package/src/backend/utils/__tests__/bulkDelete.test.ts +42 -0
  77. package/src/backend/utils/bulkDelete.ts +93 -0
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ChevronDown, ChevronRight, Wrench } from "lucide-react";
5
+ function formatMs(ms) {
6
+ if (ms < 1e3) return `${ms}ms`;
7
+ return `${(ms / 1e3).toFixed(1)}s`;
8
+ }
9
+ function LoopStepRow({ step }) {
10
+ const [expanded, setExpanded] = React.useState(false);
11
+ const hasTools = step.toolCalls.length > 0;
12
+ return /* @__PURE__ */ jsxs("li", { className: "border-b border-border last:border-0", children: [
13
+ /* @__PURE__ */ jsxs(
14
+ "button",
15
+ {
16
+ type: "button",
17
+ className: "flex w-full items-center gap-2 px-2 py-1.5 text-xs hover:bg-muted/40",
18
+ onClick: () => setExpanded((prev) => !prev),
19
+ "aria-expanded": expanded,
20
+ "aria-label": `Step ${step.stepIndex}`,
21
+ children: [
22
+ expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3 shrink-0 text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground", "aria-hidden": true }),
23
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-muted-foreground", children: [
24
+ "Step ",
25
+ step.stepIndex
26
+ ] }),
27
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground/60", children: step.modelId }),
28
+ hasTools ? /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1 text-muted-foreground", children: [
29
+ /* @__PURE__ */ jsx(Wrench, { className: "size-3", "aria-hidden": true }),
30
+ step.toolCalls.length
31
+ ] }) : null,
32
+ /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-muted-foreground/60", children: step.finishReason }),
33
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-muted-foreground/60", children: [
34
+ step.usage.inputTokens,
35
+ "\u2191 ",
36
+ step.usage.outputTokens,
37
+ "\u2193"
38
+ ] })
39
+ ]
40
+ }
41
+ ),
42
+ expanded ? /* @__PURE__ */ jsxs("div", { className: "bg-muted/20 px-4 pb-2 pt-1 text-xs", children: [
43
+ step.textDelta ? /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
44
+ /* @__PURE__ */ jsx("div", { className: "mb-0.5 font-semibold text-muted-foreground", children: "Text" }),
45
+ /* @__PURE__ */ jsx("pre", { className: "max-h-32 overflow-auto whitespace-pre-wrap rounded bg-background p-1.5 font-mono", children: step.textDelta })
46
+ ] }) : null,
47
+ hasTools ? /* @__PURE__ */ jsxs("div", { children: [
48
+ /* @__PURE__ */ jsx("div", { className: "mb-0.5 font-semibold text-muted-foreground", children: "Tool calls" }),
49
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: step.toolCalls.map((tc, index) => /* @__PURE__ */ jsxs(
50
+ "li",
51
+ {
52
+ className: "rounded border border-border bg-background p-1.5",
53
+ "data-loop-trace-tool-call": tc.toolName,
54
+ children: [
55
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
56
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: tc.toolName }),
57
+ tc.durationMs ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60", children: formatMs(tc.durationMs) }) : null,
58
+ tc.error ? /* @__PURE__ */ jsxs("span", { className: "text-status-error-fg", children: [
59
+ "error: ",
60
+ tc.error.code
61
+ ] }) : null
62
+ ] }),
63
+ tc.args !== void 0 ? /* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-24 overflow-auto whitespace-pre-wrap font-mono text-xs text-muted-foreground", children: JSON.stringify(tc.args, null, 2) }) : null
64
+ ]
65
+ },
66
+ index
67
+ )) })
68
+ ] }) : null
69
+ ] }) : null
70
+ ] });
71
+ }
72
+ function LoopTracePanel({ trace, className }) {
73
+ const [open, setOpen] = React.useState(false);
74
+ return /* @__PURE__ */ jsxs(
75
+ "section",
76
+ {
77
+ className: `rounded-md border border-border bg-background text-xs${className ? ` ${className}` : ""}`,
78
+ "data-ai-loop-trace-panel": trace.agentId,
79
+ children: [
80
+ /* @__PURE__ */ jsxs(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ className: "flex w-full items-center gap-2 px-3 py-2 hover:bg-muted/40",
85
+ onClick: () => setOpen((prev) => !prev),
86
+ "aria-expanded": open,
87
+ "aria-controls": "loop-trace-steps",
88
+ children: [
89
+ open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3 shrink-0 text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground", "aria-hidden": true }),
90
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-muted-foreground", children: "Loop trace" }),
91
+ /* @__PURE__ */ jsxs("span", { className: "ml-1 font-mono text-muted-foreground/60", children: [
92
+ trace.steps.length,
93
+ " step",
94
+ trace.steps.length !== 1 ? "s" : ""
95
+ ] }),
96
+ /* @__PURE__ */ jsx(
97
+ "span",
98
+ {
99
+ className: "ml-auto font-mono text-muted-foreground/60",
100
+ "data-loop-trace-stop-reason": trace.stopReason,
101
+ children: trace.stopReason
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground/60", children: formatMs(trace.totalDurationMs) }),
105
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-muted-foreground/60", children: [
106
+ trace.totalUsage.inputTokens + trace.totalUsage.outputTokens,
107
+ " tok"
108
+ ] })
109
+ ]
110
+ }
111
+ ),
112
+ open ? /* @__PURE__ */ jsxs("ul", { id: "loop-trace-steps", className: "border-t border-border", role: "list", children: [
113
+ trace.steps.map((step) => /* @__PURE__ */ jsx(LoopStepRow, { step }, step.stepIndex)),
114
+ trace.steps.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-muted-foreground", children: "No steps recorded." }) : null
115
+ ] }) : null
116
+ ]
117
+ }
118
+ );
119
+ }
120
+ export {
121
+ LoopTracePanel
122
+ };
123
+ //# sourceMappingURL=LoopTracePanel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/LoopTracePanel.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ChevronDown, ChevronRight, Wrench } from 'lucide-react'\n\nexport interface LoopTracePanelStepRecord {\n stepIndex: number\n modelId: string\n toolCalls: Array<{\n toolName: string\n args: unknown\n result?: unknown\n error?: { code: string; message: string }\n repairAttempted: boolean\n durationMs: number\n }>\n textDelta: string\n usage: { inputTokens: number; outputTokens: number }\n finishReason: 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'error'\n}\n\nexport interface LoopTracePanelTrace {\n agentId: string\n turnId: string\n steps: LoopTracePanelStepRecord[]\n stopReason:\n | 'step-count'\n | 'has-tool-call'\n | 'custom-stop'\n | 'budget-tokens'\n | 'budget-tool-calls'\n | 'budget-wall-clock'\n | 'tenant-disabled'\n | 'finish-reason'\n | 'abort'\n totalDurationMs: number\n totalUsage: { inputTokens: number; outputTokens: number }\n}\n\ntype LoopStepRecord = LoopTracePanelStepRecord\ntype LoopTrace = LoopTracePanelTrace\n\nfunction formatMs(ms: number): string {\n if (ms < 1000) return `${ms}ms`\n return `${(ms / 1000).toFixed(1)}s`\n}\n\nfunction LoopStepRow({ step }: { step: LoopStepRecord }) {\n const [expanded, setExpanded] = React.useState(false)\n const hasTools = step.toolCalls.length > 0\n\n return (\n <li className=\"border-b border-border last:border-0\">\n <button\n type=\"button\"\n className=\"flex w-full items-center gap-2 px-2 py-1.5 text-xs hover:bg-muted/40\"\n onClick={() => setExpanded((prev) => !prev)}\n aria-expanded={expanded}\n aria-label={`Step ${step.stepIndex}`}\n >\n {expanded ? (\n <ChevronDown className=\"size-3 shrink-0 text-muted-foreground\" aria-hidden />\n ) : (\n <ChevronRight className=\"size-3 shrink-0 text-muted-foreground\" aria-hidden />\n )}\n <span className=\"font-medium text-muted-foreground\">Step {step.stepIndex}</span>\n <span className=\"font-mono text-muted-foreground/60\">{step.modelId}</span>\n {hasTools ? (\n <span className=\"ml-auto flex items-center gap-1 text-muted-foreground\">\n <Wrench className=\"size-3\" aria-hidden />\n {step.toolCalls.length}\n </span>\n ) : null}\n <span className=\"ml-auto font-mono text-muted-foreground/60\">{step.finishReason}</span>\n <span className=\"font-mono text-muted-foreground/60\">\n {step.usage.inputTokens}\u2191 {step.usage.outputTokens}\u2193\n </span>\n </button>\n\n {expanded ? (\n <div className=\"bg-muted/20 px-4 pb-2 pt-1 text-xs\">\n {step.textDelta ? (\n <div className=\"mb-2\">\n <div className=\"mb-0.5 font-semibold text-muted-foreground\">Text</div>\n <pre className=\"max-h-32 overflow-auto whitespace-pre-wrap rounded bg-background p-1.5 font-mono\">\n {step.textDelta}\n </pre>\n </div>\n ) : null}\n {hasTools ? (\n <div>\n <div className=\"mb-0.5 font-semibold text-muted-foreground\">Tool calls</div>\n <ul className=\"flex flex-col gap-1\">\n {step.toolCalls.map((tc, index) => (\n <li\n key={index}\n className=\"rounded border border-border bg-background p-1.5\"\n data-loop-trace-tool-call={tc.toolName}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-mono font-semibold\">{tc.toolName}</span>\n {tc.durationMs ? (\n <span className=\"text-muted-foreground/60\">{formatMs(tc.durationMs)}</span>\n ) : null}\n {tc.error ? (\n <span className=\"text-status-error-fg\">error: {tc.error.code}</span>\n ) : null}\n </div>\n {tc.args !== undefined ? (\n <pre className=\"mt-1 max-h-24 overflow-auto whitespace-pre-wrap font-mono text-xs text-muted-foreground\">\n {JSON.stringify(tc.args, null, 2)}\n </pre>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n </div>\n ) : null}\n </li>\n )\n}\n\nexport interface LoopTracePanelProps {\n trace: LoopTracePanelTrace\n className?: string\n}\n\n/**\n * Collapsible playground / debug panel that renders a `LoopTrace` per turn.\n * Collapsed by default; each step row is independently expandable.\n *\n * Phase 4 of spec `2026-04-28-ai-agents-agentic-loop-controls`.\n */\nexport function LoopTracePanel({ trace, className }: LoopTracePanelProps) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <section\n className={`rounded-md border border-border bg-background text-xs${className ? ` ${className}` : ''}`}\n data-ai-loop-trace-panel={trace.agentId}\n >\n <button\n type=\"button\"\n className=\"flex w-full items-center gap-2 px-3 py-2 hover:bg-muted/40\"\n onClick={() => setOpen((prev) => !prev)}\n aria-expanded={open}\n aria-controls=\"loop-trace-steps\"\n >\n {open ? (\n <ChevronDown className=\"size-3 shrink-0 text-muted-foreground\" aria-hidden />\n ) : (\n <ChevronRight className=\"size-3 shrink-0 text-muted-foreground\" aria-hidden />\n )}\n <span className=\"font-semibold text-muted-foreground\">Loop trace</span>\n <span className=\"ml-1 font-mono text-muted-foreground/60\">\n {trace.steps.length} step{trace.steps.length !== 1 ? 's' : ''}\n </span>\n <span\n className=\"ml-auto font-mono text-muted-foreground/60\"\n data-loop-trace-stop-reason={trace.stopReason}\n >\n {trace.stopReason}\n </span>\n <span className=\"font-mono text-muted-foreground/60\">{formatMs(trace.totalDurationMs)}</span>\n <span className=\"font-mono text-muted-foreground/60\">\n {trace.totalUsage.inputTokens + trace.totalUsage.outputTokens} tok\n </span>\n </button>\n\n {open ? (\n <ul id=\"loop-trace-steps\" className=\"border-t border-border\" role=\"list\">\n {trace.steps.map((step) => (\n <LoopStepRow key={step.stepIndex} step={step} />\n ))}\n {trace.steps.length === 0 ? (\n <li className=\"px-3 py-2 text-muted-foreground\">No steps recorded.</li>\n ) : null}\n </ul>\n ) : null}\n </section>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/ModelPicker.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ChevronDown, Cpu } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '../primitives/button'\n\nexport interface ModelPickerProviderModel {\n id: string\n name: string\n contextWindow?: number | null\n tags?: string[]\n isDefault: boolean\n}\n\nexport interface ModelPickerProvider {\n id: string\n name: string\n isDefault: boolean\n models: ModelPickerProviderModel[]\n}\n\nexport interface ModelPickerValue {\n providerId: string\n modelId: string\n baseURL?: string | null\n}\n\nexport interface ModelPickerProps {\n agentId: string\n value: ModelPickerValue | null\n onChange: (value: ModelPickerValue | null) => void\n availableProviders: ModelPickerProvider[]\n disabled?: boolean\n compact?: boolean\n defaultLabel?: string | null\n className?: string\n}\n\n/**\n * Stateless dropdown for selecting a provider + model override in the chat UI.\n *\n * - Renders provider name + model id + \"(default)\" badge for the agent's resolved default.\n * - Keyboard-accessible: trigger opens a floating list, arrow keys navigate, Enter selects,\n * Escape closes.\n * - Does NOT expose a baseURL input (R6 mitigation: only provider+model selection from the\n * curated catalog is exposed to end users).\n * - localStorage persistence keyed by agentId is handled by the consumer (AiChat / 4b.2).\n * - When allowRuntimeModelOverride is false the consumer should not render this component\n * at all; this component itself does no gating.\n */\nexport function ModelPicker({\n agentId,\n value,\n onChange,\n availableProviders,\n disabled,\n compact = false,\n defaultLabel,\n className,\n}: ModelPickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const triggerRef = React.useRef<HTMLButtonElement>(null)\n\n const selectedLabel = React.useMemo(() => {\n if (!value) {\n return defaultLabel\n ? t('ai_assistant.modelPicker.defaultWithModelLabel', 'Default: {{model}}').replace('{{model}}', defaultLabel)\n : t('ai_assistant.modelPicker.defaultLabel', 'Model: Default')\n }\n const provider = availableProviders.find((p) => p.id === value.providerId)\n const model = provider?.models.find((m) => m.id === value.modelId)\n const modelLabel = model?.name ?? value.modelId\n const providerLabel = provider?.name ?? value.providerId\n return `${providerLabel} / ${modelLabel}`\n }, [value, availableProviders, defaultLabel, t])\n\n const handleToggle = React.useCallback(() => {\n if (disabled) return\n setOpen((prev) => !prev)\n }, [disabled])\n\n const handleSelect = React.useCallback(\n (providerId: string, modelId: string) => {\n onChange({ providerId, modelId })\n setOpen(false)\n triggerRef.current?.focus()\n },\n [onChange],\n )\n\n const handleClearDefault = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n triggerRef.current?.focus()\n }, [onChange])\n\n React.useEffect(() => {\n if (!open) return\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setOpen(false)\n triggerRef.current?.focus()\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [open])\n\n if (availableProviders.length === 0) return null\n\n return (\n <div ref={containerRef} className={cn('relative', className)} data-ai-model-picker={agentId}>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled}\n onClick={handleToggle}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={t('ai_assistant.modelPicker.triggerAriaLabel', 'Select AI model')}\n title={selectedLabel}\n data-ai-model-picker-trigger\n className={cn(\n 'font-normal',\n compact ? 'w-8 gap-0 px-0' : 'gap-1.5 px-2.5',\n )}\n >\n <Cpu className=\"size-3.5 shrink-0\" aria-hidden />\n {!compact ? (\n <span className=\"max-w-36 truncate text-xs\">{selectedLabel}</span>\n ) : null}\n <ChevronDown\n className={cn(\n 'size-3 shrink-0 transition-transform',\n compact && 'hidden',\n open && 'rotate-180',\n )}\n aria-hidden\n />\n </Button>\n\n {open ? (\n <div\n role=\"listbox\"\n aria-label={t('ai_assistant.modelPicker.listAriaLabel', 'Available models')}\n className={cn(\n 'absolute bottom-full left-0 z-50 mb-1 max-h-72 min-w-56 overflow-y-auto',\n 'rounded-md border border-border bg-popover shadow-md',\n )}\n data-ai-model-picker-dropdown\n >\n <div\n role=\"option\"\n aria-selected={value === null}\n tabIndex={0}\n className={cn(\n 'cursor-pointer px-3 py-2 text-xs hover:bg-accent hover:text-accent-foreground',\n 'flex items-center gap-2',\n value === null && 'bg-accent/50',\n )}\n onClick={handleClearDefault}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleClearDefault()\n }\n }}\n data-ai-model-picker-default-option\n >\n <span className=\"flex-1\">\n {defaultLabel\n ? t('ai_assistant.modelPicker.useDefaultWithModel', 'Use agent default: {{model}}').replace('{{model}}', defaultLabel)\n : t('ai_assistant.modelPicker.useDefault', 'Use agent default')}\n </span>\n {value === null ? (\n <span className=\"rounded bg-primary/10 px-1 py-0.5 text-overline font-medium text-primary\">\n {t('ai_assistant.modelPicker.activeBadge', 'active')}\n </span>\n ) : null}\n </div>\n {availableProviders.map((provider) => (\n <div key={provider.id} data-ai-model-picker-provider={provider.id}>\n <div className=\"px-3 py-1.5 text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {provider.name}\n </div>\n {provider.models.map((model) => {\n const isSelected =\n value?.providerId === provider.id && value?.modelId === model.id\n return (\n <div\n key={model.id}\n role=\"option\"\n aria-selected={isSelected}\n tabIndex={0}\n className={cn(\n 'cursor-pointer px-3 py-2 text-xs hover:bg-accent hover:text-accent-foreground',\n 'flex items-center gap-2 pl-5',\n isSelected && 'bg-accent/50',\n )}\n onClick={() => handleSelect(provider.id, model.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(provider.id, model.id)\n }\n }}\n data-ai-model-picker-model={`${provider.id}:${model.id}`}\n >\n <span className=\"flex-1 truncate\">{model.name}</span>\n {model.isDefault ? (\n <span className=\"shrink-0 rounded bg-muted px-1 py-0.5 text-overline text-muted-foreground\">\n {t('ai_assistant.modelPicker.defaultBadge', 'default')}\n </span>\n ) : null}\n {isSelected ? (\n <span className=\"shrink-0 rounded bg-primary/10 px-1 py-0.5 text-overline font-medium text-primary\">\n {t('ai_assistant.modelPicker.activeBadge', 'active')}\n </span>\n ) : null}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default ModelPicker\n"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ChevronDown, Cpu } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '../primitives/button'\n\nexport interface ModelPickerProviderModel {\n id: string\n name: string\n contextWindow?: number | null\n tags?: string[]\n isDefault: boolean\n}\n\nexport interface ModelPickerProvider {\n id: string\n name: string\n isDefault: boolean\n models: ModelPickerProviderModel[]\n}\n\nexport interface ModelPickerValue {\n providerId: string\n modelId: string\n baseURL?: string | null\n}\n\nexport interface ModelPickerProps {\n agentId: string\n value: ModelPickerValue | null\n onChange: (value: ModelPickerValue | null) => void\n availableProviders: ModelPickerProvider[]\n disabled?: boolean\n compact?: boolean\n defaultLabel?: string | null\n className?: string\n}\n\n/**\n * Stateless dropdown for selecting a provider + model override in the chat UI.\n *\n * - Renders provider name + model id + \"(default)\" badge for the agent's resolved default.\n * - Keyboard-accessible: trigger opens a floating list, arrow keys navigate, Enter selects,\n * Escape closes.\n * - Does NOT expose a baseURL input (R6 mitigation: only provider+model selection from the\n * curated catalog is exposed to end users).\n * - localStorage persistence keyed by agentId is handled by the consumer (AiChat / 4b.2).\n * - When allowRuntimeOverride is false the consumer should not render this component\n * at all; this component itself does no gating.\n */\nexport function ModelPicker({\n agentId,\n value,\n onChange,\n availableProviders,\n disabled,\n compact = false,\n defaultLabel,\n className,\n}: ModelPickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const triggerRef = React.useRef<HTMLButtonElement>(null)\n\n const selectedLabel = React.useMemo(() => {\n if (!value) {\n return defaultLabel\n ? t('ai_assistant.modelPicker.defaultWithModelLabel', 'Default: {{model}}').replace('{{model}}', defaultLabel)\n : t('ai_assistant.modelPicker.defaultLabel', 'Model: Default')\n }\n const provider = availableProviders.find((p) => p.id === value.providerId)\n const model = provider?.models.find((m) => m.id === value.modelId)\n const modelLabel = model?.name ?? value.modelId\n const providerLabel = provider?.name ?? value.providerId\n return `${providerLabel} / ${modelLabel}`\n }, [value, availableProviders, defaultLabel, t])\n\n const handleToggle = React.useCallback(() => {\n if (disabled) return\n setOpen((prev) => !prev)\n }, [disabled])\n\n const handleSelect = React.useCallback(\n (providerId: string, modelId: string) => {\n onChange({ providerId, modelId })\n setOpen(false)\n triggerRef.current?.focus()\n },\n [onChange],\n )\n\n const handleClearDefault = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n triggerRef.current?.focus()\n }, [onChange])\n\n React.useEffect(() => {\n if (!open) return\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setOpen(false)\n triggerRef.current?.focus()\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const handleClickOutside = (event: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\n setOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [open])\n\n if (availableProviders.length === 0) return null\n\n return (\n <div ref={containerRef} className={cn('relative', className)} data-ai-model-picker={agentId}>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled}\n onClick={handleToggle}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={t('ai_assistant.modelPicker.triggerAriaLabel', 'Select AI model')}\n title={selectedLabel}\n data-ai-model-picker-trigger\n className={cn(\n 'font-normal',\n compact ? 'w-8 gap-0 px-0' : 'gap-1.5 px-2.5',\n )}\n >\n <Cpu className=\"size-3.5 shrink-0\" aria-hidden />\n {!compact ? (\n <span className=\"max-w-36 truncate text-xs\">{selectedLabel}</span>\n ) : null}\n <ChevronDown\n className={cn(\n 'size-3 shrink-0 transition-transform',\n compact && 'hidden',\n open && 'rotate-180',\n )}\n aria-hidden\n />\n </Button>\n\n {open ? (\n <div\n role=\"listbox\"\n aria-label={t('ai_assistant.modelPicker.listAriaLabel', 'Available models')}\n className={cn(\n 'absolute bottom-full left-0 z-50 mb-1 max-h-72 min-w-56 overflow-y-auto',\n 'rounded-md border border-border bg-popover shadow-md',\n )}\n data-ai-model-picker-dropdown\n >\n <div\n role=\"option\"\n aria-selected={value === null}\n tabIndex={0}\n className={cn(\n 'cursor-pointer px-3 py-2 text-xs hover:bg-accent hover:text-accent-foreground',\n 'flex items-center gap-2',\n value === null && 'bg-accent/50',\n )}\n onClick={handleClearDefault}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleClearDefault()\n }\n }}\n data-ai-model-picker-default-option\n >\n <span className=\"flex-1\">\n {defaultLabel\n ? t('ai_assistant.modelPicker.useDefaultWithModel', 'Use agent default: {{model}}').replace('{{model}}', defaultLabel)\n : t('ai_assistant.modelPicker.useDefault', 'Use agent default')}\n </span>\n {value === null ? (\n <span className=\"rounded bg-primary/10 px-1 py-0.5 text-overline font-medium text-primary\">\n {t('ai_assistant.modelPicker.activeBadge', 'active')}\n </span>\n ) : null}\n </div>\n {availableProviders.map((provider) => (\n <div key={provider.id} data-ai-model-picker-provider={provider.id}>\n <div className=\"px-3 py-1.5 text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {provider.name}\n </div>\n {provider.models.map((model) => {\n const isSelected =\n value?.providerId === provider.id && value?.modelId === model.id\n return (\n <div\n key={model.id}\n role=\"option\"\n aria-selected={isSelected}\n tabIndex={0}\n className={cn(\n 'cursor-pointer px-3 py-2 text-xs hover:bg-accent hover:text-accent-foreground',\n 'flex items-center gap-2 pl-5',\n isSelected && 'bg-accent/50',\n )}\n onClick={() => handleSelect(provider.id, model.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleSelect(provider.id, model.id)\n }\n }}\n data-ai-model-picker-model={`${provider.id}:${model.id}`}\n >\n <span className=\"flex-1 truncate\">{model.name}</span>\n {model.isDefault ? (\n <span className=\"shrink-0 rounded bg-muted px-1 py-0.5 text-overline text-muted-foreground\">\n {t('ai_assistant.modelPicker.defaultBadge', 'default')}\n </span>\n ) : null}\n {isSelected ? (\n <span className=\"shrink-0 rounded bg-primary/10 px-1 py-0.5 text-overline font-medium text-primary\">\n {t('ai_assistant.modelPicker.activeBadge', 'active')}\n </span>\n ) : null}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default ModelPicker\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/ai/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AiChat } from "./AiChat.js";
2
+ import { AiIcon } from "./AiIcon.js";
2
3
  import {
3
4
  AiAssistantLauncher,
4
5
  AI_ASSISTANT_LAUNCHER_OPEN_EVENT
@@ -66,6 +67,10 @@ import {
66
67
  import {
67
68
  useAiChatUpload
68
69
  } from "./useAiChatUpload.js";
70
+ import { LoopDisabledBanner } from "./LoopDisabledBanner.js";
71
+ import {
72
+ LoopTracePanel
73
+ } from "./LoopTracePanel.js";
69
74
  export {
70
75
  AI_ASSISTANT_LAUNCHER_OPEN_EVENT,
71
76
  AI_MUTATION_APPROVAL_CARDS,
@@ -74,6 +79,7 @@ export {
74
79
  AiChat,
75
80
  AiChatSessionsProvider,
76
81
  AiDockProvider,
82
+ AiIcon,
77
83
  AiMessageContent,
78
84
  ChatPaneTabs,
79
85
  CompanyCard,
@@ -81,6 +87,8 @@ export {
81
87
  DealCard,
82
88
  FieldDiffCard,
83
89
  KeyValueList,
90
+ LoopDisabledBanner,
91
+ LoopTracePanel,
84
92
  MutationPreviewCard,
85
93
  MutationResultCard,
86
94
  PendingPhase3Placeholder,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/index.ts"],
4
- "sourcesContent": ["export { AiChat, type AiChatProps, type AiChatDebugTool, type AiChatDebugPromptSection } from './AiChat'\nexport {\n AiAssistantLauncher,\n AI_ASSISTANT_LAUNCHER_OPEN_EVENT,\n type AiAssistantLauncherAgent,\n type AiAssistantLauncherProps,\n} from './AiAssistantLauncher'\nexport {\n AiDockProvider,\n useAiDock,\n type AiDockedAssistant,\n} from './AiDock'\nexport {\n AiChatSessionsProvider,\n useAiChatSessions,\n defaultSessionLabel,\n type AiChatSession,\n} from './AiChatSessions'\nexport { ChatPaneTabs } from './ChatPaneTabs'\nexport {\n useAiChat,\n type AiChatMessage,\n type AiChatToolCallSnapshot,\n type AiChatMessageFile,\n type AiChatErrorEnvelope,\n type UseAiChatInput,\n type UseAiChatResult,\n} from './useAiChat'\nexport {\n useAiShortcuts,\n type UseAiShortcutsOptions,\n type UseAiShortcutsResult,\n} from './useAiShortcuts'\nexport {\n registerAiUiPart,\n resolveAiUiPart,\n unregisterAiUiPart,\n resetAiUiPartRegistryForTests,\n listAiUiParts,\n createAiUiPartRegistry,\n defaultAiUiPartRegistry,\n RESERVED_AI_UI_PART_IDS,\n isReservedAiUiPartId,\n type AiUiPartComponent,\n type AiUiPartComponentId,\n type AiUiPartProps,\n type AiUiPartRegistry,\n type AiUiPartRegistryEntry,\n type CreateAiUiPartRegistryOptions,\n type ReservedAiUiPartId,\n} from './ui-part-registry'\nexport { PendingPhase3Placeholder } from './ui-parts/pending-phase3-placeholder'\nexport {\n MutationPreviewCard,\n FieldDiffCard,\n ConfirmationCard,\n MutationResultCard,\n AI_MUTATION_APPROVAL_CARDS,\n useAiPendingActionPolling,\n confirmPendingAction,\n cancelPendingAction,\n type UseAiPendingActionPollingOptions,\n type UseAiPendingActionPollingResult,\n type PendingActionMutationOk,\n type PendingActionMutationError,\n type PendingActionMutationResult,\n type AiPendingActionCardAction,\n type AiPendingActionCardStatus,\n type AiPendingActionCardFieldDiff,\n type AiPendingActionCardRecordDiff,\n type AiPendingActionCardFailedRecord,\n type AiPendingActionCardExecutionResult,\n} from './parts'\nexport {\n uploadAttachmentsForChat,\n type UploadAttachmentsForChatOptions,\n type UploadAttachmentsForChatResult,\n type UploadedAttachment,\n type UploadFailure,\n type UploadFailureReason,\n} from './upload-adapter'\nexport {\n RecordCard,\n DealCard,\n PersonCard,\n CompanyCard,\n ProductCard,\n ActivityCard,\n RecordCardShell,\n KeyValueList,\n TagRow,\n statusToTagVariant,\n type RecordCardProps,\n type RecordCardShellProps,\n type KeyValueListItem,\n type DealCardProps,\n type PersonCardProps,\n type CompanyCardProps,\n type ProductCardProps,\n type ActivityCardProps,\n type RecordCardKind,\n type RecordCardPayload,\n type RecordCardBaseProps,\n type DealRecordPayload,\n type PersonRecordPayload,\n type CompanyRecordPayload,\n type ProductRecordPayload,\n type ActivityRecordPayload,\n registerRecordCardUiParts,\n RECORD_CARD_COMPONENT_IDS,\n type RecordCardComponentId,\n} from './records'\nexport {\n AiMessageContent,\n parseAiContentSegments,\n RECORD_CARD_FENCE_INFO_PREFIX,\n type AiMessageContentSegment,\n type AiMessageContentProps,\n} from './AiMessageContent'\nexport {\n useAiChatUpload,\n type UseAiChatUploadOptions,\n type UseAiChatUploadState,\n type AiChatUploadFileState,\n type AiChatUploadFileStatus,\n} from './useAiChatUpload'\n"],
5
- "mappings": "AAAA,SAAS,cAAqF;AAC9F;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAOK;AACP;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAYK;AACP;AAAA,EACE;AAAA,OAMK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAiBA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAKK;",
4
+ "sourcesContent": ["export { AiChat, type AiChatProps, type AiChatDebugTool, type AiChatDebugPromptSection } from './AiChat'\nexport { AiIcon } from './AiIcon'\nexport {\n AiAssistantLauncher,\n AI_ASSISTANT_LAUNCHER_OPEN_EVENT,\n type AiAssistantLauncherAgent,\n type AiAssistantLauncherProps,\n} from './AiAssistantLauncher'\nexport {\n AiDockProvider,\n useAiDock,\n type AiDockedAssistant,\n} from './AiDock'\nexport {\n AiChatSessionsProvider,\n useAiChatSessions,\n defaultSessionLabel,\n type AiChatSession,\n} from './AiChatSessions'\nexport { ChatPaneTabs } from './ChatPaneTabs'\nexport {\n useAiChat,\n type AiChatMessage,\n type AiChatToolCallSnapshot,\n type AiChatMessageFile,\n type AiChatErrorEnvelope,\n type UseAiChatInput,\n type UseAiChatResult,\n} from './useAiChat'\nexport {\n useAiShortcuts,\n type UseAiShortcutsOptions,\n type UseAiShortcutsResult,\n} from './useAiShortcuts'\nexport {\n registerAiUiPart,\n resolveAiUiPart,\n unregisterAiUiPart,\n resetAiUiPartRegistryForTests,\n listAiUiParts,\n createAiUiPartRegistry,\n defaultAiUiPartRegistry,\n RESERVED_AI_UI_PART_IDS,\n isReservedAiUiPartId,\n type AiUiPartComponent,\n type AiUiPartComponentId,\n type AiUiPartProps,\n type AiUiPartRegistry,\n type AiUiPartRegistryEntry,\n type CreateAiUiPartRegistryOptions,\n type ReservedAiUiPartId,\n} from './ui-part-registry'\nexport { PendingPhase3Placeholder } from './ui-parts/pending-phase3-placeholder'\nexport {\n MutationPreviewCard,\n FieldDiffCard,\n ConfirmationCard,\n MutationResultCard,\n AI_MUTATION_APPROVAL_CARDS,\n useAiPendingActionPolling,\n confirmPendingAction,\n cancelPendingAction,\n type UseAiPendingActionPollingOptions,\n type UseAiPendingActionPollingResult,\n type PendingActionMutationOk,\n type PendingActionMutationError,\n type PendingActionMutationResult,\n type AiPendingActionCardAction,\n type AiPendingActionCardStatus,\n type AiPendingActionCardFieldDiff,\n type AiPendingActionCardRecordDiff,\n type AiPendingActionCardFailedRecord,\n type AiPendingActionCardExecutionResult,\n} from './parts'\nexport {\n uploadAttachmentsForChat,\n type UploadAttachmentsForChatOptions,\n type UploadAttachmentsForChatResult,\n type UploadedAttachment,\n type UploadFailure,\n type UploadFailureReason,\n} from './upload-adapter'\nexport {\n RecordCard,\n DealCard,\n PersonCard,\n CompanyCard,\n ProductCard,\n ActivityCard,\n RecordCardShell,\n KeyValueList,\n TagRow,\n statusToTagVariant,\n type RecordCardProps,\n type RecordCardShellProps,\n type KeyValueListItem,\n type DealCardProps,\n type PersonCardProps,\n type CompanyCardProps,\n type ProductCardProps,\n type ActivityCardProps,\n type RecordCardKind,\n type RecordCardPayload,\n type RecordCardBaseProps,\n type DealRecordPayload,\n type PersonRecordPayload,\n type CompanyRecordPayload,\n type ProductRecordPayload,\n type ActivityRecordPayload,\n registerRecordCardUiParts,\n RECORD_CARD_COMPONENT_IDS,\n type RecordCardComponentId,\n} from './records'\nexport {\n AiMessageContent,\n parseAiContentSegments,\n RECORD_CARD_FENCE_INFO_PREFIX,\n type AiMessageContentSegment,\n type AiMessageContentProps,\n} from './AiMessageContent'\nexport {\n useAiChatUpload,\n type UseAiChatUploadOptions,\n type UseAiChatUploadState,\n type AiChatUploadFileState,\n type AiChatUploadFileStatus,\n} from './useAiChatUpload'\nexport { LoopDisabledBanner } from './LoopDisabledBanner'\nexport {\n LoopTracePanel,\n type LoopTracePanelProps,\n type LoopTracePanelTrace,\n type LoopTracePanelStepRecord,\n} from './LoopTracePanel'\n"],
5
+ "mappings": "AAAA,SAAS,cAAqF;AAC9F,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAOK;AACP;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAYK;AACP;AAAA,EACE;AAAA,OAMK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAiBA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAKK;AACP,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,OAIK;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { AlertTriangle, Loader2 } from "lucide-react";
4
+ import { Loader2 } from "lucide-react";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { Alert, AlertDescription, AlertTitle } from "../../primitives/alert.js";
7
7
  import { Button } from "../../primitives/button.js";
@@ -129,7 +129,6 @@ function ConfirmationErrorAlert({
129
129
  className: "mt-3",
130
130
  "data-ai-confirmation-error": "stale_version",
131
131
  children: [
132
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
133
132
  /* @__PURE__ */ jsx(AlertTitle, { children: t(
134
133
  "ai_assistant.chat.mutation_cards.confirmation.staleVersionTitle",
135
134
  "Re-propose required"
@@ -160,7 +159,6 @@ function ConfirmationErrorAlert({
160
159
  className: "mt-3",
161
160
  "data-ai-confirmation-error": "schema_drift",
162
161
  children: [
163
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
164
162
  /* @__PURE__ */ jsx(AlertTitle, { children: t(
165
163
  "ai_assistant.chat.mutation_cards.confirmation.schemaDriftTitle",
166
164
  "Schema changed"
@@ -181,7 +179,6 @@ function ConfirmationErrorAlert({
181
179
  className: "mt-3",
182
180
  "data-ai-confirmation-error": "invalid_status",
183
181
  children: [
184
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
185
182
  /* @__PURE__ */ jsx(AlertTitle, { children: t(
186
183
  "ai_assistant.chat.mutation_cards.confirmation.invalidStatusTitle",
187
184
  "Action already resolved"
@@ -195,7 +192,6 @@ function ConfirmationErrorAlert({
195
192
  );
196
193
  }
197
194
  return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "mt-3", "data-ai-confirmation-error": code, children: [
198
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
199
195
  /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.chat.mutation_cards.confirmation.errorTitle", "Confirm failed") }),
200
196
  /* @__PURE__ */ jsxs(AlertDescription, { children: [
201
197
  /* @__PURE__ */ jsx("span", { className: "mr-2 font-mono text-xs", children: code }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/ai/parts/ConfirmationCard.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertTriangle, Loader2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '../../primitives/alert'\nimport { Button } from '../../primitives/button'\nimport { Spinner } from '../../primitives/spinner'\nimport { useAiShortcuts } from '../useAiShortcuts'\nimport type { AiUiPartProps } from '../ui-part-registry'\nimport { cancelPendingAction } from './pending-action-api'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport { MutationResultCard } from './MutationResultCard'\nimport type { AiPendingActionCardAction } from './types'\n\n/**\n * Confirmation / in-flight card rendered after the user clicks `Confirm` on\n * the preview card. Shows a spinner + the side-effects summary while the\n * server runs the re-check contract and executes the wrapped tool.\n *\n * The user can race the confirm by clicking Cancel \u2014 but only while the\n * server has not yet flipped the row to `executing`. The polling hook\n * drives the disable logic.\n *\n * Surfaces structured error envelopes from the confirm route: 412\n * `stale_version` (records changed since preview) and 412 `schema_drift`\n * (tool input schema changed) render targeted alerts with the specific\n * recovery copy. Keyboard: `Escape` triggers Cancel; `Cmd/Ctrl+Enter` is\n * intentionally inert (the user already confirmed).\n *\n * Terminal states flip this card into a {@link MutationResultCard} render\n * so the chat transcript does not need two separate cards for confirmed\n * vs pending.\n */\nexport interface ConfirmationCardPayload {\n sideEffectsSummary?: string | null\n pendingAction?: AiPendingActionCardAction\n confirmError?: {\n status: number\n code?: string\n message: string\n extra?: Record<string, unknown>\n }\n}\n\nexport interface ConfirmationCardProps extends AiUiPartProps {\n /** Optional override for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Endpoint override (tests). */\n endpoint?: string\n /** Optional cancel handler override (tests). */\n onCancel?: () => Promise<void> | void\n}\n\nexport function ConfirmationCard(props: ConfirmationCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as ConfirmationCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action, status, refresh } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId,\n })\n\n const effectiveAction = action ?? injected ?? null\n const effectiveStatus = effectiveAction?.status ?? status\n\n const [isCancelling, setIsCancelling] = React.useState(false)\n const [localError, setLocalError] = React.useState<{\n code?: string\n message: string\n extra?: Record<string, unknown>\n } | null>(\n payload.confirmError\n ? { code: payload.confirmError.code, message: payload.confirmError.message, extra: payload.confirmError.extra }\n : null,\n )\n\n const canCancel =\n !isCancelling &&\n (effectiveStatus === 'pending' || effectiveStatus === 'confirmed' || effectiveStatus == null)\n\n const handleCancel = React.useCallback(async () => {\n if (!canCancel) return\n if (props.onCancel) {\n await props.onCancel()\n return\n }\n if (!pendingActionId) return\n setIsCancelling(true)\n try {\n const result = await cancelPendingAction(pendingActionId, {\n endpoint: props.endpoint,\n })\n if (!result.ok) {\n setLocalError({\n code: result.error.code,\n message: result.error.message,\n extra: result.error.extra,\n })\n }\n await refresh()\n } finally {\n setIsCancelling(false)\n }\n }, [canCancel, pendingActionId, props, refresh])\n\n const { handleKeyDown } = useAiShortcuts({\n onCancel: () => {\n void handleCancel()\n },\n enabled: canCancel,\n })\n\n // Terminal states \u2014 hand off to the result card renderer. Also short-\n // circuit when the dispatcher has already populated an\n // `executionResult.error`: the row may not have transitioned out of\n // `executing` in the latest polling snapshot, but the handler error is\n // authoritative, and leaving the spinner up while a known error is\n // available is exactly the \"stalled at processing\" symptom the user\n // reported. Surface the error card immediately.\n const executionError = effectiveAction?.executionResult?.error\n if (\n effectiveStatus === 'confirmed' ||\n effectiveStatus === 'failed' ||\n effectiveStatus === 'cancelled' ||\n effectiveStatus === 'expired' ||\n executionError\n ) {\n return (\n <MutationResultCard\n componentId=\"mutation-result-card\"\n pendingActionId={pendingActionId}\n initialAction={effectiveAction ?? undefined}\n endpoint={props.endpoint}\n />\n )\n }\n\n const summary =\n effectiveAction?.sideEffectsSummary ??\n payload.sideEffectsSummary ??\n t(\n 'ai_assistant.chat.mutation_cards.confirmation.defaultSummary',\n 'Applying the requested changes...',\n )\n\n return (\n <section\n className=\"rounded-md border border-border bg-muted/30 p-4 text-sm outline-none\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n data-ai-confirmation-card\n data-ai-confirmation-status={effectiveStatus ?? 'pending'}\n aria-busy\n >\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"mt-0.5\" />\n <div className=\"flex-1\">\n <h4 className=\"text-sm font-semibold\">\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.title',\n 'Applying action...',\n )}\n </h4>\n <p className=\"mt-1 text-sm text-muted-foreground\">{summary}</p>\n </div>\n </div>\n\n {localError ? (\n <ConfirmationErrorAlert error={localError} />\n ) : null}\n\n <div className=\"mt-3 flex items-center justify-end gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void handleCancel()\n }}\n disabled={!canCancel}\n data-ai-confirmation-cancel\n >\n {isCancelling ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : null}\n <span>\n {t('ai_assistant.chat.mutation_cards.confirmation.cancel', 'Cancel')}\n </span>\n </Button>\n </div>\n </section>\n )\n}\n\nfunction ConfirmationErrorAlert({\n error,\n}: {\n error: { code?: string; message: string; extra?: Record<string, unknown> }\n}) {\n const t = useT()\n const code = error.code ?? 'unknown'\n\n if (code === 'stale_version') {\n const failedRecords = Array.isArray(error.extra?.failedRecords)\n ? (error.extra?.failedRecords as Array<{ recordId?: string }>)\n : []\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"stale_version\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionTitle',\n 'Re-propose required',\n )}\n </AlertTitle>\n <AlertDescription>\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionBody',\n 'One or more records changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </p>\n {failedRecords.length > 0 ? (\n <ul className=\"mt-2 list-disc pl-5 text-xs\">\n {failedRecords.map((record, idx) => (\n <li\n key={`${record.recordId ?? idx}`}\n data-ai-confirmation-stale-record={record.recordId ?? ''}\n >\n <span className=\"font-mono\">{record.recordId ?? '\u2014'}</span>\n </li>\n ))}\n </ul>\n ) : null}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'schema_drift') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"schema_drift\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftTitle',\n 'Schema changed',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftBody',\n 'The tool signature changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'invalid_status') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"invalid_status\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusTitle',\n 'Action already resolved',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusBody',\n 'This action has already been confirmed, cancelled, or executed.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <Alert variant=\"destructive\" className=\"mt-3\" data-ai-confirmation-error={code}>\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.confirmation.errorTitle', 'Confirm failed')}\n </AlertTitle>\n <AlertDescription>\n <span className=\"mr-2 font-mono text-xs\">{code}</span>\n <span>{error.message}</span>\n </AlertDescription>\n </Alert>\n )\n}\n\nexport default ConfirmationCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '../../primitives/alert'\nimport { Button } from '../../primitives/button'\nimport { Spinner } from '../../primitives/spinner'\nimport { useAiShortcuts } from '../useAiShortcuts'\nimport type { AiUiPartProps } from '../ui-part-registry'\nimport { cancelPendingAction } from './pending-action-api'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport { MutationResultCard } from './MutationResultCard'\nimport type { AiPendingActionCardAction } from './types'\n\n/**\n * Confirmation / in-flight card rendered after the user clicks `Confirm` on\n * the preview card. Shows a spinner + the side-effects summary while the\n * server runs the re-check contract and executes the wrapped tool.\n *\n * The user can race the confirm by clicking Cancel \u2014 but only while the\n * server has not yet flipped the row to `executing`. The polling hook\n * drives the disable logic.\n *\n * Surfaces structured error envelopes from the confirm route: 412\n * `stale_version` (records changed since preview) and 412 `schema_drift`\n * (tool input schema changed) render targeted alerts with the specific\n * recovery copy. Keyboard: `Escape` triggers Cancel; `Cmd/Ctrl+Enter` is\n * intentionally inert (the user already confirmed).\n *\n * Terminal states flip this card into a {@link MutationResultCard} render\n * so the chat transcript does not need two separate cards for confirmed\n * vs pending.\n */\nexport interface ConfirmationCardPayload {\n sideEffectsSummary?: string | null\n pendingAction?: AiPendingActionCardAction\n confirmError?: {\n status: number\n code?: string\n message: string\n extra?: Record<string, unknown>\n }\n}\n\nexport interface ConfirmationCardProps extends AiUiPartProps {\n /** Optional override for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Endpoint override (tests). */\n endpoint?: string\n /** Optional cancel handler override (tests). */\n onCancel?: () => Promise<void> | void\n}\n\nexport function ConfirmationCard(props: ConfirmationCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as ConfirmationCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action, status, refresh } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId,\n })\n\n const effectiveAction = action ?? injected ?? null\n const effectiveStatus = effectiveAction?.status ?? status\n\n const [isCancelling, setIsCancelling] = React.useState(false)\n const [localError, setLocalError] = React.useState<{\n code?: string\n message: string\n extra?: Record<string, unknown>\n } | null>(\n payload.confirmError\n ? { code: payload.confirmError.code, message: payload.confirmError.message, extra: payload.confirmError.extra }\n : null,\n )\n\n const canCancel =\n !isCancelling &&\n (effectiveStatus === 'pending' || effectiveStatus === 'confirmed' || effectiveStatus == null)\n\n const handleCancel = React.useCallback(async () => {\n if (!canCancel) return\n if (props.onCancel) {\n await props.onCancel()\n return\n }\n if (!pendingActionId) return\n setIsCancelling(true)\n try {\n const result = await cancelPendingAction(pendingActionId, {\n endpoint: props.endpoint,\n })\n if (!result.ok) {\n setLocalError({\n code: result.error.code,\n message: result.error.message,\n extra: result.error.extra,\n })\n }\n await refresh()\n } finally {\n setIsCancelling(false)\n }\n }, [canCancel, pendingActionId, props, refresh])\n\n const { handleKeyDown } = useAiShortcuts({\n onCancel: () => {\n void handleCancel()\n },\n enabled: canCancel,\n })\n\n // Terminal states \u2014 hand off to the result card renderer. Also short-\n // circuit when the dispatcher has already populated an\n // `executionResult.error`: the row may not have transitioned out of\n // `executing` in the latest polling snapshot, but the handler error is\n // authoritative, and leaving the spinner up while a known error is\n // available is exactly the \"stalled at processing\" symptom the user\n // reported. Surface the error card immediately.\n const executionError = effectiveAction?.executionResult?.error\n if (\n effectiveStatus === 'confirmed' ||\n effectiveStatus === 'failed' ||\n effectiveStatus === 'cancelled' ||\n effectiveStatus === 'expired' ||\n executionError\n ) {\n return (\n <MutationResultCard\n componentId=\"mutation-result-card\"\n pendingActionId={pendingActionId}\n initialAction={effectiveAction ?? undefined}\n endpoint={props.endpoint}\n />\n )\n }\n\n const summary =\n effectiveAction?.sideEffectsSummary ??\n payload.sideEffectsSummary ??\n t(\n 'ai_assistant.chat.mutation_cards.confirmation.defaultSummary',\n 'Applying the requested changes...',\n )\n\n return (\n <section\n className=\"rounded-md border border-border bg-muted/30 p-4 text-sm outline-none\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n data-ai-confirmation-card\n data-ai-confirmation-status={effectiveStatus ?? 'pending'}\n aria-busy\n >\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"mt-0.5\" />\n <div className=\"flex-1\">\n <h4 className=\"text-sm font-semibold\">\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.title',\n 'Applying action...',\n )}\n </h4>\n <p className=\"mt-1 text-sm text-muted-foreground\">{summary}</p>\n </div>\n </div>\n\n {localError ? (\n <ConfirmationErrorAlert error={localError} />\n ) : null}\n\n <div className=\"mt-3 flex items-center justify-end gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void handleCancel()\n }}\n disabled={!canCancel}\n data-ai-confirmation-cancel\n >\n {isCancelling ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : null}\n <span>\n {t('ai_assistant.chat.mutation_cards.confirmation.cancel', 'Cancel')}\n </span>\n </Button>\n </div>\n </section>\n )\n}\n\nfunction ConfirmationErrorAlert({\n error,\n}: {\n error: { code?: string; message: string; extra?: Record<string, unknown> }\n}) {\n const t = useT()\n const code = error.code ?? 'unknown'\n\n if (code === 'stale_version') {\n const failedRecords = Array.isArray(error.extra?.failedRecords)\n ? (error.extra?.failedRecords as Array<{ recordId?: string }>)\n : []\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"stale_version\"\n >\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionTitle',\n 'Re-propose required',\n )}\n </AlertTitle>\n <AlertDescription>\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionBody',\n 'One or more records changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </p>\n {failedRecords.length > 0 ? (\n <ul className=\"mt-2 list-disc pl-5 text-xs\">\n {failedRecords.map((record, idx) => (\n <li\n key={`${record.recordId ?? idx}`}\n data-ai-confirmation-stale-record={record.recordId ?? ''}\n >\n <span className=\"font-mono\">{record.recordId ?? '\u2014'}</span>\n </li>\n ))}\n </ul>\n ) : null}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'schema_drift') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"schema_drift\"\n >\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftTitle',\n 'Schema changed',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftBody',\n 'The tool signature changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'invalid_status') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"invalid_status\"\n >\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusTitle',\n 'Action already resolved',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusBody',\n 'This action has already been confirmed, cancelled, or executed.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <Alert variant=\"destructive\" className=\"mt-3\" data-ai-confirmation-error={code}>\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.confirmation.errorTitle', 'Confirm failed')}\n </AlertTitle>\n <AlertDescription>\n <span className=\"mr-2 font-mono text-xs\">{code}</span>\n <span>{error.message}</span>\n </AlertDescription>\n </Alert>\n )\n}\n\nexport default ConfirmationCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { Info } from "lucide-react";
4
3
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
4
  import { Alert, AlertDescription } from "../../primitives/alert.js";
6
5
  function formatValue(value) {
@@ -80,26 +79,20 @@ function FieldDiffCard({ fieldDiff, records }) {
80
79
  beforeHeader,
81
80
  afterHeader
82
81
  }
83
- ) : /* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
84
- /* @__PURE__ */ jsx(Info, { className: "size-4", "aria-hidden": true }),
85
- /* @__PURE__ */ jsx(AlertDescription, { children: t(
86
- "ai_assistant.chat.mutation_cards.diff.empty",
87
- "No field changes for this record."
88
- ) })
89
- ] })
82
+ ) : /* @__PURE__ */ jsx(Alert, { variant: "info", children: /* @__PURE__ */ jsx(AlertDescription, { children: t(
83
+ "ai_assistant.chat.mutation_cards.diff.empty",
84
+ "No field changes for this record."
85
+ ) }) })
90
86
  ]
91
87
  },
92
88
  record.recordId
93
89
  )) });
94
90
  }
95
91
  if (flat.length === 0) {
96
- return /* @__PURE__ */ jsxs(Alert, { variant: "info", "data-ai-field-diff-mode": "empty", children: [
97
- /* @__PURE__ */ jsx(Info, { className: "size-4", "aria-hidden": true }),
98
- /* @__PURE__ */ jsx(AlertDescription, { children: t(
99
- "ai_assistant.chat.mutation_cards.diff.empty",
100
- "No field changes for this record."
101
- ) })
102
- ] });
92
+ return /* @__PURE__ */ jsx(Alert, { variant: "info", "data-ai-field-diff-mode": "empty", children: /* @__PURE__ */ jsx(AlertDescription, { children: t(
93
+ "ai_assistant.chat.mutation_cards.diff.empty",
94
+ "No field changes for this record."
95
+ ) }) });
103
96
  }
104
97
  return /* @__PURE__ */ jsx("div", { "data-ai-field-diff-mode": "flat", children: /* @__PURE__ */ jsx(
105
98
  DiffTable,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/ai/parts/FieldDiffCard.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Info } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription } from '../../primitives/alert'\nimport type {\n AiPendingActionCardFieldDiff,\n AiPendingActionCardRecordDiff,\n} from './types'\n\n/**\n * Presentational card rendering a `fieldDiff` list in compact three-column\n * form (field | before | after) with DS-compliant semantic-token colors.\n * Accepts either a flat `fieldDiff[]` (single-record preview) or grouped\n * `records[]` (batch preview) \u2014 when both are supplied, `records` wins,\n * matching the server-side `AiPendingAction` contract (spec \u00A78 rule 2).\n */\nexport interface FieldDiffCardProps {\n fieldDiff?: AiPendingActionCardFieldDiff[] | null\n records?: AiPendingActionCardRecordDiff[] | null\n /** Optional forwarded componentId for the registry renderer. */\n componentId?: string\n}\n\nfunction formatValue(value: unknown): string {\n if (value == null) return ''\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction DiffRow({ entry }: { entry: AiPendingActionCardFieldDiff }) {\n const before = formatValue(entry.before)\n const after = formatValue(entry.after)\n return (\n <tr className=\"border-b border-border last:border-b-0\" data-ai-field-diff-row>\n <td className=\"py-1.5 pr-4 text-xs font-mono text-muted-foreground align-top\">\n {entry.field}\n </td>\n <td\n className=\"py-1.5 pr-4 text-sm align-top text-status-warning-text\"\n data-ai-field-diff-before\n >\n <span className=\"line-through break-all\">{before || '\u2014'}</span>\n </td>\n <td\n className=\"py-1.5 text-sm align-top text-status-success-text\"\n data-ai-field-diff-after\n >\n <span className=\"font-medium break-all\">{after || '\u2014'}</span>\n </td>\n </tr>\n )\n}\n\nfunction DiffTable({ rows, fieldHeader, beforeHeader, afterHeader }: {\n rows: AiPendingActionCardFieldDiff[]\n fieldHeader: string\n beforeHeader: string\n afterHeader: string\n}) {\n return (\n <table className=\"w-full\" data-ai-field-diff-table>\n <thead>\n <tr className=\"border-b border-border\">\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {fieldHeader}\n </th>\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {beforeHeader}\n </th>\n <th className=\"py-1 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {afterHeader}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.map((entry, idx) => (\n <DiffRow key={`${entry.field}-${idx}`} entry={entry} />\n ))}\n </tbody>\n </table>\n )\n}\n\nexport function FieldDiffCard({ fieldDiff, records }: FieldDiffCardProps) {\n const t = useT()\n const fieldHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.fieldHeader',\n 'Field',\n )\n const beforeHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.beforeHeader',\n 'Before',\n )\n const afterHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.afterHeader',\n 'After',\n )\n\n const batch = Array.isArray(records) && records.length > 0 ? records : null\n const flat = Array.isArray(fieldDiff) ? fieldDiff : []\n\n if (batch) {\n return (\n <div className=\"flex flex-col gap-3\" data-ai-field-diff-mode=\"batch\">\n {batch.map((record) => (\n <section\n key={record.recordId}\n className=\"rounded-md border border-border bg-background p-3\"\n data-ai-field-diff-record={record.recordId}\n >\n <header className=\"mb-2 flex items-baseline justify-between gap-2\">\n <h4 className=\"text-sm font-semibold\">{record.label}</h4>\n <span className=\"text-xs font-mono text-muted-foreground\">\n {record.entityType}\n </span>\n </header>\n {record.fieldDiff.length > 0 ? (\n <DiffTable\n rows={record.fieldDiff}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n ) : (\n <Alert variant=\"info\">\n <Info className=\"size-4\" aria-hidden />\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )}\n </section>\n ))}\n </div>\n )\n }\n\n if (flat.length === 0) {\n return (\n <Alert variant=\"info\" data-ai-field-diff-mode=\"empty\">\n <Info className=\"size-4\" aria-hidden />\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div data-ai-field-diff-mode=\"flat\">\n <DiffTable\n rows={flat}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n </div>\n )\n}\n\nexport default FieldDiffCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription } from '../../primitives/alert'\nimport type {\n AiPendingActionCardFieldDiff,\n AiPendingActionCardRecordDiff,\n} from './types'\n\n/**\n * Presentational card rendering a `fieldDiff` list in compact three-column\n * form (field | before | after) with DS-compliant semantic-token colors.\n * Accepts either a flat `fieldDiff[]` (single-record preview) or grouped\n * `records[]` (batch preview) \u2014 when both are supplied, `records` wins,\n * matching the server-side `AiPendingAction` contract (spec \u00A78 rule 2).\n */\nexport interface FieldDiffCardProps {\n fieldDiff?: AiPendingActionCardFieldDiff[] | null\n records?: AiPendingActionCardRecordDiff[] | null\n /** Optional forwarded componentId for the registry renderer. */\n componentId?: string\n}\n\nfunction formatValue(value: unknown): string {\n if (value == null) return ''\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction DiffRow({ entry }: { entry: AiPendingActionCardFieldDiff }) {\n const before = formatValue(entry.before)\n const after = formatValue(entry.after)\n return (\n <tr className=\"border-b border-border last:border-b-0\" data-ai-field-diff-row>\n <td className=\"py-1.5 pr-4 text-xs font-mono text-muted-foreground align-top\">\n {entry.field}\n </td>\n <td\n className=\"py-1.5 pr-4 text-sm align-top text-status-warning-text\"\n data-ai-field-diff-before\n >\n <span className=\"line-through break-all\">{before || '\u2014'}</span>\n </td>\n <td\n className=\"py-1.5 text-sm align-top text-status-success-text\"\n data-ai-field-diff-after\n >\n <span className=\"font-medium break-all\">{after || '\u2014'}</span>\n </td>\n </tr>\n )\n}\n\nfunction DiffTable({ rows, fieldHeader, beforeHeader, afterHeader }: {\n rows: AiPendingActionCardFieldDiff[]\n fieldHeader: string\n beforeHeader: string\n afterHeader: string\n}) {\n return (\n <table className=\"w-full\" data-ai-field-diff-table>\n <thead>\n <tr className=\"border-b border-border\">\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {fieldHeader}\n </th>\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {beforeHeader}\n </th>\n <th className=\"py-1 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {afterHeader}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.map((entry, idx) => (\n <DiffRow key={`${entry.field}-${idx}`} entry={entry} />\n ))}\n </tbody>\n </table>\n )\n}\n\nexport function FieldDiffCard({ fieldDiff, records }: FieldDiffCardProps) {\n const t = useT()\n const fieldHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.fieldHeader',\n 'Field',\n )\n const beforeHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.beforeHeader',\n 'Before',\n )\n const afterHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.afterHeader',\n 'After',\n )\n\n const batch = Array.isArray(records) && records.length > 0 ? records : null\n const flat = Array.isArray(fieldDiff) ? fieldDiff : []\n\n if (batch) {\n return (\n <div className=\"flex flex-col gap-3\" data-ai-field-diff-mode=\"batch\">\n {batch.map((record) => (\n <section\n key={record.recordId}\n className=\"rounded-md border border-border bg-background p-3\"\n data-ai-field-diff-record={record.recordId}\n >\n <header className=\"mb-2 flex items-baseline justify-between gap-2\">\n <h4 className=\"text-sm font-semibold\">{record.label}</h4>\n <span className=\"text-xs font-mono text-muted-foreground\">\n {record.entityType}\n </span>\n </header>\n {record.fieldDiff.length > 0 ? (\n <DiffTable\n rows={record.fieldDiff}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n ) : (\n <Alert variant=\"info\">\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )}\n </section>\n ))}\n </div>\n )\n }\n\n if (flat.length === 0) {\n return (\n <Alert variant=\"info\" data-ai-field-diff-mode=\"empty\">\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div data-ai-field-diff-mode=\"flat\">\n <DiffTable\n rows={flat}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n </div>\n )\n}\n\nexport default FieldDiffCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { AlertTriangle, CheckCircle2, Wand2, XCircle } from "lucide-react";
3
+ import { Wand2 } from "lucide-react";
4
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
5
  import { Alert, AlertTitle } from "../../primitives/alert.js";
6
6
  import { Button } from "../../primitives/button.js";
@@ -35,7 +35,6 @@ function MutationResultCard(props) {
35
35
  }
36
36
  if (isSuccessStatus(status) && failedRecords && failedRecords.length > 0) {
37
37
  return /* @__PURE__ */ jsxs(Alert, { variant: "warning", "data-ai-mutation-result": "partial", children: [
38
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
39
38
  /* @__PURE__ */ jsx(AlertTitle, { children: t(
40
39
  "ai_assistant.chat.mutation_cards.result.partialTitle",
41
40
  "Action applied with failures"
@@ -66,7 +65,6 @@ function MutationResultCard(props) {
66
65
  const recordId = result?.recordId ?? action.targetRecordId ?? null;
67
66
  const href = payload.recordHref ?? null;
68
67
  return /* @__PURE__ */ jsxs(Alert, { variant: "success", "data-ai-mutation-result": "success", children: [
69
- /* @__PURE__ */ jsx(CheckCircle2, { className: "size-4", "aria-hidden": true }),
70
68
  /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.chat.mutation_cards.result.successTitle", "Action applied") }),
71
69
  /* @__PURE__ */ jsxs("div", { className: "text-sm leading-relaxed", children: [
72
70
  /* @__PURE__ */ jsx("p", { children: result?.commandName ? t(
@@ -184,7 +182,6 @@ function MutationResultCard(props) {
184
182
  };
185
183
  const visibleIssues = Array.isArray(errorDetails?.issues) ? errorDetails.issues.filter((entry) => entry && (entry.message || entry.path)) : [];
186
184
  return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", "data-ai-mutation-result": "failure", children: [
187
- /* @__PURE__ */ jsx(XCircle, { className: "size-4", "aria-hidden": true }),
188
185
  /* @__PURE__ */ jsx(AlertTitle, { children: t(
189
186
  "ai_assistant.chat.mutation_cards.result.failureTitle",
190
187
  "Action failed"