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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -1
  3. package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
  4. package/dist/ai/AiAssistantLauncher.js +596 -0
  5. package/dist/ai/AiAssistantLauncher.js.map +7 -0
  6. package/dist/ai/AiChat.js +1092 -0
  7. package/dist/ai/AiChat.js.map +7 -0
  8. package/dist/ai/AiChatSessions.js +297 -0
  9. package/dist/ai/AiChatSessions.js.map +7 -0
  10. package/dist/ai/AiDock.js +347 -0
  11. package/dist/ai/AiDock.js.map +7 -0
  12. package/dist/ai/AiMessageContent.js +369 -0
  13. package/dist/ai/AiMessageContent.js.map +7 -0
  14. package/dist/ai/ChatPaneTabs.js +251 -0
  15. package/dist/ai/ChatPaneTabs.js.map +7 -0
  16. package/dist/ai/index.js +115 -0
  17. package/dist/ai/index.js.map +7 -0
  18. package/dist/ai/parts/ConfirmationCard.js +211 -0
  19. package/dist/ai/parts/ConfirmationCard.js.map +7 -0
  20. package/dist/ai/parts/FieldDiffCard.js +119 -0
  21. package/dist/ai/parts/FieldDiffCard.js.map +7 -0
  22. package/dist/ai/parts/MutationPreviewCard.js +224 -0
  23. package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
  24. package/dist/ai/parts/MutationResultCard.js +240 -0
  25. package/dist/ai/parts/MutationResultCard.js.map +7 -0
  26. package/dist/ai/parts/approval-cards-map.js +15 -0
  27. package/dist/ai/parts/approval-cards-map.js.map +7 -0
  28. package/dist/ai/parts/index.js +24 -0
  29. package/dist/ai/parts/index.js.map +7 -0
  30. package/dist/ai/parts/pending-action-api.js +60 -0
  31. package/dist/ai/parts/pending-action-api.js.map +7 -0
  32. package/dist/ai/parts/types.js +1 -0
  33. package/dist/ai/parts/types.js.map +7 -0
  34. package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
  35. package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
  36. package/dist/ai/records/ActivityCard.js +83 -0
  37. package/dist/ai/records/ActivityCard.js.map +7 -0
  38. package/dist/ai/records/CompanyCard.js +81 -0
  39. package/dist/ai/records/CompanyCard.js.map +7 -0
  40. package/dist/ai/records/DealCard.js +76 -0
  41. package/dist/ai/records/DealCard.js.map +7 -0
  42. package/dist/ai/records/PersonCard.js +68 -0
  43. package/dist/ai/records/PersonCard.js.map +7 -0
  44. package/dist/ai/records/ProductCard.js +68 -0
  45. package/dist/ai/records/ProductCard.js.map +7 -0
  46. package/dist/ai/records/RecordCard.js +29 -0
  47. package/dist/ai/records/RecordCard.js.map +7 -0
  48. package/dist/ai/records/RecordCardShell.js +103 -0
  49. package/dist/ai/records/RecordCardShell.js.map +7 -0
  50. package/dist/ai/records/index.js +31 -0
  51. package/dist/ai/records/index.js.map +7 -0
  52. package/dist/ai/records/registry.js +51 -0
  53. package/dist/ai/records/registry.js.map +7 -0
  54. package/dist/ai/records/types.js +1 -0
  55. package/dist/ai/records/types.js.map +7 -0
  56. package/dist/ai/ui-part-registry.js +112 -0
  57. package/dist/ai/ui-part-registry.js.map +7 -0
  58. package/dist/ai/ui-part-slots.js +14 -0
  59. package/dist/ai/ui-part-slots.js.map +7 -0
  60. package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
  61. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
  62. package/dist/ai/upload-adapter.js +256 -0
  63. package/dist/ai/upload-adapter.js.map +7 -0
  64. package/dist/ai/useAiChat.js +549 -0
  65. package/dist/ai/useAiChat.js.map +7 -0
  66. package/dist/ai/useAiChatUpload.js +127 -0
  67. package/dist/ai/useAiChatUpload.js.map +7 -0
  68. package/dist/ai/useAiShortcuts.js +43 -0
  69. package/dist/ai/useAiShortcuts.js.map +7 -0
  70. package/dist/backend/AppShell.js +8 -4
  71. package/dist/backend/AppShell.js.map +2 -2
  72. package/dist/backend/BackendChromeProvider.js +2 -0
  73. package/dist/backend/BackendChromeProvider.js.map +2 -2
  74. package/dist/backend/DataTable.js +19 -2
  75. package/dist/backend/DataTable.js.map +2 -2
  76. package/dist/backend/FilterBar.js +19 -15
  77. package/dist/backend/FilterBar.js.map +2 -2
  78. package/dist/backend/dashboard/DashboardScreen.js +31 -3
  79. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  80. package/dist/backend/injection/spotIds.js +6 -0
  81. package/dist/backend/injection/spotIds.js.map +2 -2
  82. package/dist/backend/notifications/useNotificationEffect.js +38 -2
  83. package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +2 -2
  86. package/jest.config.cjs +7 -1
  87. package/jest.markdown-mock.tsx +7 -0
  88. package/package.json +10 -4
  89. package/src/ai/AiAssistantLauncher.tsx +805 -0
  90. package/src/ai/AiChat.tsx +1483 -0
  91. package/src/ai/AiChatSessions.tsx +429 -0
  92. package/src/ai/AiDock.tsx +505 -0
  93. package/src/ai/AiMessageContent.tsx +515 -0
  94. package/src/ai/ChatPaneTabs.tsx +310 -0
  95. package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
  96. package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
  97. package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
  98. package/src/ai/__tests__/AiChat.test.tsx +257 -0
  99. package/src/ai/__tests__/AiDock.test.tsx +124 -0
  100. package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
  101. package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
  102. package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
  103. package/src/ai/__tests__/upload-adapter.test.ts +213 -0
  104. package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
  105. package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
  106. package/src/ai/index.ts +125 -0
  107. package/src/ai/parts/ConfirmationCard.tsx +310 -0
  108. package/src/ai/parts/FieldDiffCard.tsx +173 -0
  109. package/src/ai/parts/MutationPreviewCard.tsx +302 -0
  110. package/src/ai/parts/MutationResultCard.tsx +360 -0
  111. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
  112. package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
  113. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
  114. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
  115. package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
  116. package/src/ai/parts/approval-cards-map.ts +24 -0
  117. package/src/ai/parts/index.ts +27 -0
  118. package/src/ai/parts/pending-action-api.ts +123 -0
  119. package/src/ai/parts/types.ts +84 -0
  120. package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
  121. package/src/ai/records/ActivityCard.tsx +102 -0
  122. package/src/ai/records/CompanyCard.tsx +89 -0
  123. package/src/ai/records/DealCard.tsx +85 -0
  124. package/src/ai/records/PersonCard.tsx +77 -0
  125. package/src/ai/records/ProductCard.tsx +83 -0
  126. package/src/ai/records/RecordCard.tsx +37 -0
  127. package/src/ai/records/RecordCardShell.tsx +169 -0
  128. package/src/ai/records/index.ts +30 -0
  129. package/src/ai/records/registry.tsx +80 -0
  130. package/src/ai/records/types.ts +90 -0
  131. package/src/ai/ui-part-registry.ts +233 -0
  132. package/src/ai/ui-part-slots.ts +32 -0
  133. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
  134. package/src/ai/upload-adapter.ts +421 -0
  135. package/src/ai/useAiChat.ts +865 -0
  136. package/src/ai/useAiChatUpload.ts +180 -0
  137. package/src/ai/useAiShortcuts.ts +79 -0
  138. package/src/backend/AppShell.tsx +12 -5
  139. package/src/backend/BackendChromeProvider.tsx +2 -0
  140. package/src/backend/DataTable.tsx +20 -1
  141. package/src/backend/FilterBar.tsx +26 -13
  142. package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
  143. package/src/backend/dashboard/DashboardScreen.tsx +38 -3
  144. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
  145. package/src/backend/injection/spotIds.ts +6 -0
  146. package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
  147. package/src/backend/notifications/useNotificationEffect.ts +47 -2
  148. package/src/index.ts +1 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/records/RecordCardShell.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ExternalLink } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Tag, type TagVariant } from '../../primitives/tag'\n\nexport interface RecordCardShellProps {\n kindLabel: string\n kindIcon: React.ReactNode\n title: string\n subtitle?: React.ReactNode\n status?: { label: string; variant: TagVariant } | null\n href?: string\n id?: string\n leading?: React.ReactNode\n children?: React.ReactNode\n className?: string\n dataKind?: string\n}\n\nexport function RecordCardShell({\n kindLabel,\n kindIcon,\n title,\n subtitle,\n status,\n href,\n id,\n leading,\n children,\n className,\n dataKind,\n}: RecordCardShellProps) {\n const t = useT()\n return (\n <div\n data-ai-record-card={dataKind ?? kindLabel.toLowerCase()}\n data-record-id={id}\n className={cn(\n 'group/record relative my-2 flex flex-col gap-3 rounded-lg border border-border bg-card p-3 text-card-foreground shadow-sm transition-colors',\n href ? 'hover:border-primary/40 hover:bg-accent/40' : '',\n className,\n )}\n >\n <div className=\"flex items-start gap-3\">\n {leading ? (\n <div className=\"shrink-0\">{leading}</div>\n ) : (\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary\" aria-hidden>\n {kindIcon}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {kindLabel}\n </span>\n {status ? (\n <Tag variant={status.variant} dot>\n {status.label}\n </Tag>\n ) : null}\n </div>\n <div className=\"mt-0.5 flex items-start gap-2\">\n <h4 className=\"min-w-0 flex-1 truncate text-sm font-semibold leading-snug text-foreground\">\n {href ? (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"outline-none hover:underline focus-visible:underline\"\n >\n {title}\n </a>\n ) : (\n title\n )}\n </h4>\n {href ? (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"shrink-0 rounded-md p-1 text-muted-foreground hover:text-foreground\"\n aria-label={t('ai_assistant.chat.records.openRecord', 'Open record')}\n >\n <ExternalLink className=\"size-3.5\" aria-hidden />\n </a>\n ) : null}\n </div>\n {subtitle ? (\n <div className=\"mt-0.5 truncate text-xs text-muted-foreground\">{subtitle}</div>\n ) : null}\n </div>\n </div>\n {children ? <div className=\"text-xs text-foreground\">{children}</div> : null}\n </div>\n )\n}\n\nexport interface KeyValueListItem {\n label: string\n value: React.ReactNode\n}\n\nexport function KeyValueList({ items }: { items: KeyValueListItem[] }) {\n if (items.length === 0) return null\n return (\n <dl className=\"grid grid-cols-[minmax(80px,auto)_1fr] gap-x-3 gap-y-1\">\n {items.map((item, idx) => (\n <React.Fragment key={`${item.label}-${idx}`}>\n <dt className=\"truncate text-[11px] uppercase tracking-wide text-muted-foreground\">\n {item.label}\n </dt>\n <dd className=\"min-w-0 truncate text-foreground\">{item.value}</dd>\n </React.Fragment>\n ))}\n </dl>\n )\n}\n\nexport function TagRow({ tags }: { tags: string[] }) {\n if (tags.length === 0) return null\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.map((tag, idx) => (\n <Tag key={`${tag}-${idx}`} variant=\"brand\">\n {tag}\n </Tag>\n ))}\n </div>\n )\n}\n\nexport function statusToTagVariant(status: string | null | undefined): TagVariant {\n if (!status) return 'neutral'\n const s = String(status).toLowerCase().trim()\n if (\n s === 'won' ||\n s === 'win' ||\n s === 'active' ||\n s === 'completed' ||\n s === 'done' ||\n s === 'paid' ||\n s === 'success' ||\n s === 'closed_won' ||\n s === 'closed-won'\n )\n return 'success'\n if (\n s === 'lost' ||\n s === 'failed' ||\n s === 'cancelled' ||\n s === 'canceled' ||\n s === 'overdue' ||\n s === 'closed_lost' ||\n s === 'closed-lost'\n )\n return 'error'\n if (s === 'pending' || s === 'in_progress' || s === 'in progress' || s === 'open' || s === 'qualified')\n return 'info'\n if (s === 'at_risk' || s === 'at risk' || s === 'review' || s === 'follow_up')\n return 'warning'\n if (s === 'draft' || s === 'archived' || s === 'inactive')\n return 'neutral'\n return 'info'\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ import { RecordCard } from "./RecordCard.js";
2
+ import {
3
+ registerRecordCardUiParts,
4
+ RECORD_CARD_COMPONENT_IDS
5
+ } from "./registry.js";
6
+ import { DealCard } from "./DealCard.js";
7
+ import { PersonCard } from "./PersonCard.js";
8
+ import { CompanyCard } from "./CompanyCard.js";
9
+ import { ProductCard } from "./ProductCard.js";
10
+ import { ActivityCard } from "./ActivityCard.js";
11
+ import {
12
+ RecordCardShell,
13
+ KeyValueList,
14
+ TagRow,
15
+ statusToTagVariant
16
+ } from "./RecordCardShell.js";
17
+ export {
18
+ ActivityCard,
19
+ CompanyCard,
20
+ DealCard,
21
+ KeyValueList,
22
+ PersonCard,
23
+ ProductCard,
24
+ RECORD_CARD_COMPONENT_IDS,
25
+ RecordCard,
26
+ RecordCardShell,
27
+ TagRow,
28
+ registerRecordCardUiParts,
29
+ statusToTagVariant
30
+ };
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/records/index.ts"],
4
+ "sourcesContent": ["export { RecordCard, type RecordCardProps } from './RecordCard'\nexport {\n registerRecordCardUiParts,\n RECORD_CARD_COMPONENT_IDS,\n type RecordCardComponentId,\n} from './registry'\nexport { DealCard, type DealCardProps } from './DealCard'\nexport { PersonCard, type PersonCardProps } from './PersonCard'\nexport { CompanyCard, type CompanyCardProps } from './CompanyCard'\nexport { ProductCard, type ProductCardProps } from './ProductCard'\nexport { ActivityCard, type ActivityCardProps } from './ActivityCard'\nexport {\n RecordCardShell,\n KeyValueList,\n TagRow,\n statusToTagVariant,\n type RecordCardShellProps,\n type KeyValueListItem,\n} from './RecordCardShell'\nexport type {\n RecordCardKind,\n RecordCardPayload,\n RecordCardBaseProps,\n DealRecordPayload,\n PersonRecordPayload,\n CompanyRecordPayload,\n ProductRecordPayload,\n ActivityRecordPayload,\n StatusToTag,\n} from './types'\n"],
5
+ "mappings": "AAAA,SAAS,kBAAwC;AACjD;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,gBAAoC;AAC7C,SAAS,kBAAwC;AACjD,SAAS,mBAA0C;AACnD,SAAS,mBAA0C;AACnD,SAAS,oBAA4C;AACrD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;",
6
+ "names": []
7
+ }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import {
4
+ defaultAiUiPartRegistry
5
+ } from "../ui-part-registry.js";
6
+ import { ActivityCard } from "./ActivityCard.js";
7
+ import { CompanyCard } from "./CompanyCard.js";
8
+ import { DealCard } from "./DealCard.js";
9
+ import { PersonCard } from "./PersonCard.js";
10
+ import { ProductCard } from "./ProductCard.js";
11
+ const RECORD_CARD_COMPONENT_IDS = [
12
+ "open-mercato.deal-card",
13
+ "open-mercato.person-card",
14
+ "open-mercato.company-card",
15
+ "open-mercato.product-card",
16
+ "open-mercato.activity-card"
17
+ ];
18
+ function DealCardPart({ payload }) {
19
+ return /* @__PURE__ */ jsx(DealCard, { ...payload ?? {} });
20
+ }
21
+ function PersonCardPart({ payload }) {
22
+ return /* @__PURE__ */ jsx(PersonCard, { ...payload ?? {} });
23
+ }
24
+ function CompanyCardPart({ payload }) {
25
+ return /* @__PURE__ */ jsx(CompanyCard, { ...payload ?? {} });
26
+ }
27
+ function ProductCardPart({ payload }) {
28
+ return /* @__PURE__ */ jsx(ProductCard, { ...payload ?? {} });
29
+ }
30
+ function ActivityCardPart({ payload }) {
31
+ return /* @__PURE__ */ jsx(ActivityCard, { ...payload ?? {} });
32
+ }
33
+ let registered = false;
34
+ function registerRecordCardUiParts() {
35
+ if (registered) return;
36
+ registered = true;
37
+ defaultAiUiPartRegistry.register("open-mercato.deal-card", DealCardPart);
38
+ defaultAiUiPartRegistry.register("open-mercato.person-card", PersonCardPart);
39
+ defaultAiUiPartRegistry.register("open-mercato.company-card", CompanyCardPart);
40
+ defaultAiUiPartRegistry.register("open-mercato.product-card", ProductCardPart);
41
+ defaultAiUiPartRegistry.register(
42
+ "open-mercato.activity-card",
43
+ ActivityCardPart
44
+ );
45
+ }
46
+ registerRecordCardUiParts();
47
+ export {
48
+ RECORD_CARD_COMPONENT_IDS,
49
+ registerRecordCardUiParts
50
+ };
51
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/records/registry.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n defaultAiUiPartRegistry,\n type AiUiPartProps,\n} from '../ui-part-registry'\nimport { ActivityCard } from './ActivityCard'\nimport { CompanyCard } from './CompanyCard'\nimport { DealCard } from './DealCard'\nimport { PersonCard } from './PersonCard'\nimport { ProductCard } from './ProductCard'\nimport type {\n ActivityRecordPayload,\n CompanyRecordPayload,\n DealRecordPayload,\n PersonRecordPayload,\n ProductRecordPayload,\n} from './types'\n\n/**\n * Stable component ids the AI runtime can emit through the typed UI-parts\n * protocol. Each id renders the same record-card component used by the\n * inline fenced-block renderer, so a host can opt into either path without\n * duplicating UI code.\n */\nexport const RECORD_CARD_COMPONENT_IDS = [\n 'open-mercato.deal-card',\n 'open-mercato.person-card',\n 'open-mercato.company-card',\n 'open-mercato.product-card',\n 'open-mercato.activity-card',\n] as const\n\nexport type RecordCardComponentId = (typeof RECORD_CARD_COMPONENT_IDS)[number]\n\nfunction DealCardPart({ payload }: AiUiPartProps) {\n return <DealCard {...((payload ?? {}) as DealRecordPayload)} />\n}\n\nfunction PersonCardPart({ payload }: AiUiPartProps) {\n return <PersonCard {...((payload ?? {}) as PersonRecordPayload)} />\n}\n\nfunction CompanyCardPart({ payload }: AiUiPartProps) {\n return <CompanyCard {...((payload ?? {}) as CompanyRecordPayload)} />\n}\n\nfunction ProductCardPart({ payload }: AiUiPartProps) {\n return <ProductCard {...((payload ?? {}) as ProductRecordPayload)} />\n}\n\nfunction ActivityCardPart({ payload }: AiUiPartProps) {\n return <ActivityCard {...((payload ?? {}) as ActivityRecordPayload)} />\n}\n\nlet registered = false\n\n/**\n * Register the built-in record-card components on the module-global UI-part\n * registry. Idempotent; safe to call from app bootstrap or from a top-level\n * `<AiChat>` host. Hosts that need a scoped registry can re-register the\n * same ids on their own {@link createAiUiPartRegistry} instance.\n */\nexport function registerRecordCardUiParts(): void {\n if (registered) return\n registered = true\n defaultAiUiPartRegistry.register('open-mercato.deal-card', DealCardPart)\n defaultAiUiPartRegistry.register('open-mercato.person-card', PersonCardPart)\n defaultAiUiPartRegistry.register('open-mercato.company-card', CompanyCardPart)\n defaultAiUiPartRegistry.register('open-mercato.product-card', ProductCardPart)\n defaultAiUiPartRegistry.register(\n 'open-mercato.activity-card',\n ActivityCardPart,\n )\n}\n\n// Auto-register at module load so consumers that import from\n// `@open-mercato/ui/ai` get the cards wired up without manual bootstrap.\nregisterRecordCardUiParts()\n"],
5
+ "mappings": ";AAqCS;AAlCT;AAAA,EACE;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAerB,MAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIA,SAAS,aAAa,EAAE,QAAQ,GAAkB;AAChD,SAAO,oBAAC,YAAU,GAAK,WAAW,CAAC,GAA0B;AAC/D;AAEA,SAAS,eAAe,EAAE,QAAQ,GAAkB;AAClD,SAAO,oBAAC,cAAY,GAAK,WAAW,CAAC,GAA4B;AACnE;AAEA,SAAS,gBAAgB,EAAE,QAAQ,GAAkB;AACnD,SAAO,oBAAC,eAAa,GAAK,WAAW,CAAC,GAA6B;AACrE;AAEA,SAAS,gBAAgB,EAAE,QAAQ,GAAkB;AACnD,SAAO,oBAAC,eAAa,GAAK,WAAW,CAAC,GAA6B;AACrE;AAEA,SAAS,iBAAiB,EAAE,QAAQ,GAAkB;AACpD,SAAO,oBAAC,gBAAc,GAAK,WAAW,CAAC,GAA8B;AACvE;AAEA,IAAI,aAAa;AAQV,SAAS,4BAAkC;AAChD,MAAI,WAAY;AAChB,eAAa;AACb,0BAAwB,SAAS,0BAA0B,YAAY;AACvE,0BAAwB,SAAS,4BAA4B,cAAc;AAC3E,0BAAwB,SAAS,6BAA6B,eAAe;AAC7E,0BAAwB,SAAS,6BAA6B,eAAe;AAC7E,0BAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AACF;AAIA,0BAA0B;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import {
3
+ RESERVED_AI_UI_PART_IDS,
4
+ isReservedAiUiPartId
5
+ } from "./ui-part-slots.js";
6
+ import { PendingPhase3Placeholder } from "./ui-parts/pending-phase3-placeholder.js";
7
+ import { AI_MUTATION_APPROVAL_CARDS } from "./parts/approval-cards-map.js";
8
+ function seedReservedSlots(store, options = { seedLive: false }) {
9
+ for (const reservedId of RESERVED_AI_UI_PART_IDS) {
10
+ if (store.has(reservedId)) continue;
11
+ if (options.seedLive && AI_MUTATION_APPROVAL_CARDS[reservedId]) {
12
+ store.set(
13
+ reservedId,
14
+ AI_MUTATION_APPROVAL_CARDS[reservedId]
15
+ );
16
+ continue;
17
+ }
18
+ store.set(
19
+ reservedId,
20
+ PendingPhase3Placeholder
21
+ );
22
+ }
23
+ }
24
+ function createAiUiPartRegistry(options) {
25
+ const seedReserved = options?.seedReservedPlaceholders !== false;
26
+ const seedLive = options?.seedLiveApprovalCards === true;
27
+ const store = /* @__PURE__ */ new Map();
28
+ if (seedReserved) {
29
+ seedReservedSlots(store, { seedLive });
30
+ }
31
+ const registry = {
32
+ register(componentId, component) {
33
+ if (!componentId) {
34
+ throw new Error("registerAiUiPart requires a non-empty componentId");
35
+ }
36
+ store.set(componentId, component);
37
+ },
38
+ unregister(componentId) {
39
+ store.delete(componentId);
40
+ },
41
+ resolve(componentId) {
42
+ const found = store.get(componentId);
43
+ if (!found) return null;
44
+ return found;
45
+ },
46
+ has(componentId) {
47
+ return store.has(componentId);
48
+ },
49
+ list() {
50
+ const entries = [];
51
+ for (const componentId of store.keys()) {
52
+ entries.push({
53
+ componentId,
54
+ reserved: isReservedAiUiPartId(componentId)
55
+ });
56
+ }
57
+ return entries;
58
+ },
59
+ clear() {
60
+ store.clear();
61
+ if (seedReserved) {
62
+ seedReservedSlots(store, { seedLive });
63
+ }
64
+ }
65
+ };
66
+ return registry;
67
+ }
68
+ const REGISTRY_GLOBAL_KEY = "__openMercatoAiUiPartRegistry";
69
+ function getDefaultRegistry() {
70
+ const scope = globalThis;
71
+ if (!scope[REGISTRY_GLOBAL_KEY]) {
72
+ scope[REGISTRY_GLOBAL_KEY] = createAiUiPartRegistry({ seedLiveApprovalCards: true });
73
+ }
74
+ return scope[REGISTRY_GLOBAL_KEY];
75
+ }
76
+ const defaultAiUiPartRegistry = new Proxy(
77
+ {},
78
+ {
79
+ get(_target, prop) {
80
+ const impl = getDefaultRegistry();
81
+ const value = impl[prop];
82
+ return typeof value === "function" ? value.bind(impl) : value;
83
+ }
84
+ }
85
+ );
86
+ function registerAiUiPart(componentId, component) {
87
+ defaultAiUiPartRegistry.register(componentId, component);
88
+ }
89
+ function resolveAiUiPart(componentId) {
90
+ return defaultAiUiPartRegistry.resolve(componentId);
91
+ }
92
+ function unregisterAiUiPart(componentId) {
93
+ defaultAiUiPartRegistry.unregister(componentId);
94
+ }
95
+ function resetAiUiPartRegistryForTests() {
96
+ defaultAiUiPartRegistry.clear();
97
+ }
98
+ function listAiUiParts() {
99
+ return defaultAiUiPartRegistry.list();
100
+ }
101
+ export {
102
+ RESERVED_AI_UI_PART_IDS,
103
+ createAiUiPartRegistry,
104
+ defaultAiUiPartRegistry,
105
+ isReservedAiUiPartId,
106
+ listAiUiParts,
107
+ registerAiUiPart,
108
+ resetAiUiPartRegistryForTests,
109
+ resolveAiUiPart,
110
+ unregisterAiUiPart
111
+ };
112
+ //# sourceMappingURL=ui-part-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/ui-part-registry.ts"],
4
+ "sourcesContent": ["\"use client\"\n\nimport type { ComponentType } from 'react'\nimport {\n RESERVED_AI_UI_PART_IDS,\n isReservedAiUiPartId,\n type ReservedAiUiPartId,\n} from './ui-part-slots'\nimport { PendingPhase3Placeholder } from './ui-parts/pending-phase3-placeholder'\nimport { AI_MUTATION_APPROVAL_CARDS } from './parts/approval-cards-map'\n\nexport { RESERVED_AI_UI_PART_IDS, isReservedAiUiPartId }\nexport type { ReservedAiUiPartId }\n\nexport type AiUiPartComponentId = ReservedAiUiPartId | (string & {})\n\nexport type AiUiPartProps = {\n /** Stable component id emitted by the server-side UI-part producer. */\n componentId: AiUiPartComponentId\n /** Arbitrary payload the server attached to this UI part. */\n payload?: unknown\n /** Optional pending-action id for mutation-approval cards (Phase 3). */\n pendingActionId?: string\n}\n\nexport type AiUiPartComponent<P = AiUiPartProps> = ComponentType<P>\n\nexport interface AiUiPartRegistryEntry {\n componentId: string\n reserved: boolean\n}\n\n/**\n * A UI-part registry instance. Consumers typically use\n * {@link defaultAiUiPartRegistry} for global registrations; tests and embedded\n * pages that need isolation can construct a dedicated registry via\n * {@link createAiUiPartRegistry} and pass it to `<AiChat registry={...} />`.\n */\nexport interface AiUiPartRegistry {\n register<P = AiUiPartProps>(\n componentId: AiUiPartComponentId,\n component: AiUiPartComponent<P>,\n ): void\n unregister(componentId: AiUiPartComponentId): void\n resolve<P = AiUiPartProps>(\n componentId: AiUiPartComponentId,\n ): AiUiPartComponent<P> | null\n has(componentId: AiUiPartComponentId): boolean\n list(): AiUiPartRegistryEntry[]\n clear(): void\n}\n\ntype RegistryStore = Map<string, AiUiPartComponent<AiUiPartProps>>\n\nexport interface CreateAiUiPartRegistryOptions {\n /**\n * When true (default) the registry is pre-seeded with the shared\n * `PendingPhase3Placeholder` for every id in {@link RESERVED_AI_UI_PART_IDS}.\n * Pass `false` to get an empty registry \u2014 useful for tests that want to\n * assert the registry is genuinely empty.\n */\n seedReservedPlaceholders?: boolean\n /**\n * When true, the registry is pre-seeded with the live Phase 3 mutation\n * approval cards from `@open-mercato/ui/ai/parts` instead of the humane\n * pending placeholder. The app-wide {@link defaultAiUiPartRegistry}\n * opts in, so end users see the real cards without any bootstrap wiring;\n * scoped registries created via {@link createAiUiPartRegistry} keep the\n * placeholder by default so unit tests and embedded playground mounts\n * stay deterministic and isolated.\n */\n seedLiveApprovalCards?: boolean\n}\n\nfunction seedReservedSlots(\n store: RegistryStore,\n options: { seedLive: boolean } = { seedLive: false },\n): void {\n for (const reservedId of RESERVED_AI_UI_PART_IDS) {\n if (store.has(reservedId)) continue\n if (options.seedLive && AI_MUTATION_APPROVAL_CARDS[reservedId]) {\n store.set(\n reservedId,\n AI_MUTATION_APPROVAL_CARDS[reservedId] as AiUiPartComponent<AiUiPartProps>,\n )\n continue\n }\n store.set(\n reservedId,\n PendingPhase3Placeholder as AiUiPartComponent<AiUiPartProps>,\n )\n }\n}\n\n/**\n * Create a fresh UI-part registry. By default the four Phase 3 reserved\n * slot ids are pre-seeded with {@link PendingPhase3Placeholder} so consumers\n * that forget to register the real cards see a humane \"Phase 3 pending\"\n * state instead of the neutral debug chip.\n */\nexport function createAiUiPartRegistry(\n options?: CreateAiUiPartRegistryOptions,\n): AiUiPartRegistry {\n const seedReserved = options?.seedReservedPlaceholders !== false\n const seedLive = options?.seedLiveApprovalCards === true\n const store: RegistryStore = new Map()\n if (seedReserved) {\n seedReservedSlots(store, { seedLive })\n }\n\n const registry: AiUiPartRegistry = {\n register(componentId, component) {\n if (!componentId) {\n throw new Error('registerAiUiPart requires a non-empty componentId')\n }\n store.set(componentId, component as AiUiPartComponent<AiUiPartProps>)\n },\n unregister(componentId) {\n store.delete(componentId)\n },\n resolve<P = AiUiPartProps>(componentId: AiUiPartComponentId) {\n const found = store.get(componentId)\n if (!found) return null\n return found as unknown as AiUiPartComponent<P>\n },\n has(componentId) {\n return store.has(componentId)\n },\n list() {\n const entries: AiUiPartRegistryEntry[] = []\n for (const componentId of store.keys()) {\n entries.push({\n componentId,\n reserved: isReservedAiUiPartId(componentId),\n })\n }\n return entries\n },\n clear() {\n store.clear()\n if (seedReserved) {\n seedReservedSlots(store, { seedLive })\n }\n },\n }\n return registry\n}\n\nconst REGISTRY_GLOBAL_KEY = '__openMercatoAiUiPartRegistry'\n\nfunction getDefaultRegistry(): AiUiPartRegistry {\n const scope = globalThis as typeof globalThis & {\n [REGISTRY_GLOBAL_KEY]?: AiUiPartRegistry\n }\n if (!scope[REGISTRY_GLOBAL_KEY]) {\n // Default registry seeds the LIVE mutation-approval cards (Step 5.10).\n // Scoped registries created via `createAiUiPartRegistry()` still default\n // to the humane placeholder so playground embeds + unit tests stay\n // deterministic and isolated.\n scope[REGISTRY_GLOBAL_KEY] = createAiUiPartRegistry({ seedLiveApprovalCards: true })\n }\n return scope[REGISTRY_GLOBAL_KEY]\n}\n\n/**\n * The app-wide default UI-part registry. Seeded with Phase 3 placeholders on\n * first access. Tests that need a clean registry should instantiate their own\n * via {@link createAiUiPartRegistry} and pass it to `<AiChat registry={...}>`.\n */\nexport const defaultAiUiPartRegistry: AiUiPartRegistry = new Proxy(\n {} as AiUiPartRegistry,\n {\n get(_target, prop: keyof AiUiPartRegistry) {\n const impl = getDefaultRegistry()\n const value = impl[prop]\n return typeof value === 'function' ? value.bind(impl) : value\n },\n },\n)\n\n/**\n * Register a UI-part component on the module-global default registry. Legacy\n * Step 4.1 API \u2014 preserved verbatim so existing callers keep working. Prefer\n * {@link defaultAiUiPartRegistry} directly (or a scoped registry) in new code.\n *\n * Idempotent: re-registering overwrites the previous entry so hot reload and\n * Phase 3 card replacement (which overwrites the seeded placeholder) stay\n * deterministic.\n */\nexport function registerAiUiPart<P = AiUiPartProps>(\n componentId: AiUiPartComponentId,\n component: AiUiPartComponent<P>,\n): void {\n defaultAiUiPartRegistry.register(componentId, component)\n}\n\n/**\n * Resolve a registered UI-part component on the module-global default\n * registry. Returns `null` when no component has been registered \u2014 callers\n * MUST handle the null case gracefully (the canonical consumer, `<AiChat>`,\n * renders a neutral placeholder chip + logs a `console.warn`).\n */\nexport function resolveAiUiPart<P = AiUiPartProps>(\n componentId: AiUiPartComponentId,\n): AiUiPartComponent<P> | null {\n return defaultAiUiPartRegistry.resolve<P>(componentId)\n}\n\n/**\n * Remove a UI-part component registration from the module-global default\n * registry. Primarily useful for tests to keep a clean slate between runs.\n */\nexport function unregisterAiUiPart(componentId: AiUiPartComponentId): void {\n defaultAiUiPartRegistry.unregister(componentId)\n}\n\n/**\n * Clear every registration on the module-global default registry and re-seed\n * the Phase 3 reserved placeholders. Test-only helper; production code must\n * never invoke this.\n */\nexport function resetAiUiPartRegistryForTests(): void {\n defaultAiUiPartRegistry.clear()\n}\n\n/**\n * Snapshot every registration on the module-global default registry. Used by\n * debugging UIs (Step 4.6) to enumerate what's registered without mutating\n * state.\n */\nexport function listAiUiParts(): AiUiPartRegistryEntry[] {\n return defaultAiUiPartRegistry.list()\n}\n"],
5
+ "mappings": ";AAGA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,gCAAgC;AACzC,SAAS,kCAAkC;AAiE3C,SAAS,kBACP,OACA,UAAiC,EAAE,UAAU,MAAM,GAC7C;AACN,aAAW,cAAc,yBAAyB;AAChD,QAAI,MAAM,IAAI,UAAU,EAAG;AAC3B,QAAI,QAAQ,YAAY,2BAA2B,UAAU,GAAG;AAC9D,YAAM;AAAA,QACJ;AAAA,QACA,2BAA2B,UAAU;AAAA,MACvC;AACA;AAAA,IACF;AACA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAQO,SAAS,uBACd,SACkB;AAClB,QAAM,eAAe,SAAS,6BAA6B;AAC3D,QAAM,WAAW,SAAS,0BAA0B;AACpD,QAAM,QAAuB,oBAAI,IAAI;AACrC,MAAI,cAAc;AAChB,sBAAkB,OAAO,EAAE,SAAS,CAAC;AAAA,EACvC;AAEA,QAAM,WAA6B;AAAA,IACjC,SAAS,aAAa,WAAW;AAC/B,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI,MAAM,mDAAmD;AAAA,MACrE;AACA,YAAM,IAAI,aAAa,SAA6C;AAAA,IACtE;AAAA,IACA,WAAW,aAAa;AACtB,YAAM,OAAO,WAAW;AAAA,IAC1B;AAAA,IACA,QAA2B,aAAkC;AAC3D,YAAM,QAAQ,MAAM,IAAI,WAAW;AACnC,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO;AAAA,IACT;AAAA,IACA,IAAI,aAAa;AACf,aAAO,MAAM,IAAI,WAAW;AAAA,IAC9B;AAAA,IACA,OAAO;AACL,YAAM,UAAmC,CAAC;AAC1C,iBAAW,eAAe,MAAM,KAAK,GAAG;AACtC,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA,UAAU,qBAAqB,WAAW;AAAA,QAC5C,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AACN,YAAM,MAAM;AACZ,UAAI,cAAc;AAChB,0BAAkB,OAAO,EAAE,SAAS,CAAC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,sBAAsB;AAE5B,SAAS,qBAAuC;AAC9C,QAAM,QAAQ;AAGd,MAAI,CAAC,MAAM,mBAAmB,GAAG;AAK/B,UAAM,mBAAmB,IAAI,uBAAuB,EAAE,uBAAuB,KAAK,CAAC;AAAA,EACrF;AACA,SAAO,MAAM,mBAAmB;AAClC;AAOO,MAAM,0BAA4C,IAAI;AAAA,EAC3D,CAAC;AAAA,EACD;AAAA,IACE,IAAI,SAAS,MAA8B;AACzC,YAAM,OAAO,mBAAmB;AAChC,YAAM,QAAQ,KAAK,IAAI;AACvB,aAAO,OAAO,UAAU,aAAa,MAAM,KAAK,IAAI,IAAI;AAAA,IAC1D;AAAA,EACF;AACF;AAWO,SAAS,iBACd,aACA,WACM;AACN,0BAAwB,SAAS,aAAa,SAAS;AACzD;AAQO,SAAS,gBACd,aAC6B;AAC7B,SAAO,wBAAwB,QAAW,WAAW;AACvD;AAMO,SAAS,mBAAmB,aAAwC;AACzE,0BAAwB,WAAW,WAAW;AAChD;AAOO,SAAS,gCAAsC;AACpD,0BAAwB,MAAM;AAChC;AAOO,SAAS,gBAAyC;AACvD,SAAO,wBAAwB,KAAK;AACtC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ const RESERVED_AI_UI_PART_IDS = Object.freeze([
2
+ "mutation-preview-card",
3
+ "field-diff-card",
4
+ "confirmation-card",
5
+ "mutation-result-card"
6
+ ]);
7
+ function isReservedAiUiPartId(componentId) {
8
+ return RESERVED_AI_UI_PART_IDS.includes(componentId);
9
+ }
10
+ export {
11
+ RESERVED_AI_UI_PART_IDS,
12
+ isReservedAiUiPartId
13
+ };
14
+ //# sourceMappingURL=ui-part-slots.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/ui-part-slots.ts"],
4
+ "sourcesContent": ["/**\n * Reserved UI-part component ids for Phase 3 approval cards.\n *\n * These ids match the server-emitted UI parts the mutation-approval runtime\n * will produce in Phase 3 Steps 5.6 and 5.10. They are listed here so the\n * registry contract can be validated at compile time and the default registry\n * ships with the same slot names the runtime will later emit.\n *\n * Hard rule: this tuple is FROZEN. Adding new reserved ids is additive, but\n * renaming or removing any entry is a breaking change per the backward-\n * compatibility contract (see `BACKWARD_COMPATIBILITY.md` \u00A76 Widget injection\n * spot IDs).\n */\nexport const RESERVED_AI_UI_PART_IDS = Object.freeze([\n 'mutation-preview-card',\n 'field-diff-card',\n 'confirmation-card',\n 'mutation-result-card',\n] as const)\n\nexport type ReservedAiUiPartId = (typeof RESERVED_AI_UI_PART_IDS)[number]\n\n/**\n * Returns true when the given componentId is one of the Phase 3 reserved\n * slot identifiers. Used by the registry to flag seeded placeholders and by\n * debugging UIs (Step 4.6) to render reserved slots distinctly.\n */\nexport function isReservedAiUiPartId(\n componentId: string,\n): componentId is ReservedAiUiPartId {\n return (RESERVED_AI_UI_PART_IDS as readonly string[]).includes(componentId)\n}\n"],
5
+ "mappings": "AAaO,MAAM,0BAA0B,OAAO,OAAO;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAU;AASH,SAAS,qBACd,aACmC;AACnC,SAAQ,wBAA8C,SAAS,WAAW;AAC5E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Info } from "lucide-react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { Alert, AlertDescription, AlertTitle } from "../../primitives/alert.js";
6
+ function PendingPhase3Placeholder({ componentId }) {
7
+ const t = useT();
8
+ return /* @__PURE__ */ jsxs(
9
+ Alert,
10
+ {
11
+ variant: "info",
12
+ "data-ai-ui-part-pending-phase3": componentId,
13
+ children: [
14
+ /* @__PURE__ */ jsx(Info, { className: "size-4", "aria-hidden": true }),
15
+ /* @__PURE__ */ jsx(AlertTitle, { children: t(
16
+ "ai_assistant.chat.pending_phase3.title",
17
+ "Mutation approval card pending"
18
+ ) }),
19
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
20
+ /* @__PURE__ */ jsx("span", { children: t(
21
+ "ai_assistant.chat.pending_phase3.body",
22
+ "This interactive card will land in Phase 3 of the unified AI framework."
23
+ ) }),
24
+ /* @__PURE__ */ jsx("span", { className: "ml-1 font-mono text-xs", children: componentId })
25
+ ] })
26
+ ]
27
+ }
28
+ );
29
+ }
30
+ var pending_phase3_placeholder_default = PendingPhase3Placeholder;
31
+ export {
32
+ PendingPhase3Placeholder,
33
+ pending_phase3_placeholder_default as default
34
+ };
35
+ //# sourceMappingURL=pending-phase3-placeholder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/ui-parts/pending-phase3-placeholder.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, AlertTitle } from '../../primitives/alert'\nimport type { AiUiPartProps } from '../ui-part-registry'\n\n/**\n * Default placeholder for the four Phase 3 reserved slot ids.\n *\n * Consumers that forget to register the real mutation-approval cards still\n * see a humane \"Phase 3 pending\" state instead of the neutral debug chip the\n * `<AiChat>` fallback uses for genuinely unknown component ids. Uses the\n * shared DS `Alert` primitive with `variant=\"info\"` \u2014 no hardcoded colors.\n *\n * When Step 5.10 lands the real cards, app bootstrappers will call\n * `registerAiUiPart('mutation-preview-card', MutationPreviewCard)` (etc.)\n * which overwrites this placeholder. The unit tests in\n * `__tests__/ui-part-registry.test.ts` pin that replacement behavior so the\n * Phase 3 hand-off stays deterministic.\n */\nexport function PendingPhase3Placeholder({ componentId }: AiUiPartProps) {\n const t = useT()\n return (\n <Alert\n variant=\"info\"\n data-ai-ui-part-pending-phase3={componentId}\n >\n <Info className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.pending_phase3.title',\n 'Mutation approval card pending',\n )}\n </AlertTitle>\n <AlertDescription>\n <span>\n {t(\n 'ai_assistant.chat.pending_phase3.body',\n 'This interactive card will land in Phase 3 of the unified AI framework.',\n )}\n </span>\n <span className=\"ml-1 font-mono text-xs\">{componentId}</span>\n </AlertDescription>\n </Alert>\n )\n}\n\nexport default PendingPhase3Placeholder\n"],
5
+ "mappings": ";AA6BM,cAOA,YAPA;AA1BN,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,OAAO,kBAAkB,kBAAkB;AAiB7C,SAAS,yBAAyB,EAAE,YAAY,GAAkB;AACvE,QAAM,IAAI,KAAK;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,kCAAgC;AAAA,MAEhC;AAAA,4BAAC,QAAK,WAAU,UAAS,eAAW,MAAC;AAAA,QACrC,oBAAC,cACE;AAAA,UACC;AAAA,UACA;AAAA,QACF,GACF;AAAA,QACA,qBAAC,oBACC;AAAA,8BAAC,UACE;AAAA,YACC;AAAA,YACA;AAAA,UACF,GACF;AAAA,UACA,oBAAC,UAAK,WAAU,0BAA0B,uBAAY;AAAA,WACxD;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,qCAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,256 @@
1
+ const DEFAULT_ATTACHMENTS_ENDPOINT = "/api/attachments";
2
+ const DEFAULT_AI_CHAT_ENTITY_ID = "ai-chat-draft";
3
+ const DEFAULT_CONCURRENCY = 3;
4
+ const DEFAULT_PER_FILE_TIMEOUT_MS = 6e4;
5
+ function mintRecordId() {
6
+ const cryptoApi = globalThis.crypto;
7
+ if (cryptoApi && typeof cryptoApi.randomUUID === "function") {
8
+ return cryptoApi.randomUUID();
9
+ }
10
+ const random = Math.random().toString(36).slice(2, 10);
11
+ const time = Date.now().toString(36);
12
+ return `ai-chat-${time}-${random}`;
13
+ }
14
+ function resolveFetchImpl(explicit) {
15
+ if (explicit) return explicit;
16
+ const fallback = globalThis.fetch;
17
+ if (!fallback) {
18
+ throw new Error("No fetch implementation available for uploadAttachmentsForChat");
19
+ }
20
+ return fallback.bind(globalThis);
21
+ }
22
+ function normalizeServerErrorMessage(raw) {
23
+ if (raw && typeof raw === "object") {
24
+ const err = raw.error;
25
+ if (typeof err === "string" && err.trim()) return err;
26
+ const msg = raw.message;
27
+ if (typeof msg === "string" && msg.trim()) return msg;
28
+ }
29
+ return "";
30
+ }
31
+ function mapStatusToReason(status, message) {
32
+ if (status === 413) return "size_exceeded";
33
+ if (status === 403 || status === 415) return "mime_rejected";
34
+ if (status === 400) {
35
+ const lower = message.toLowerCase();
36
+ if (lower.includes("file type") || lower.includes("active content")) {
37
+ return "mime_rejected";
38
+ }
39
+ if (lower.includes("size") || lower.includes("quota")) {
40
+ return "size_exceeded";
41
+ }
42
+ }
43
+ return "server";
44
+ }
45
+ function parseServerItem(payload, fallbackFile, inputIndex) {
46
+ if (!payload || typeof payload !== "object") return null;
47
+ const item = payload.item;
48
+ if (!item || typeof item !== "object") return null;
49
+ const id = item.id;
50
+ if (typeof id !== "string" || !id.trim()) return null;
51
+ const fileName = typeof item.fileName === "string" ? item.fileName : fallbackFile.name;
52
+ const fileSize = item.fileSize;
53
+ const size = typeof fileSize === "number" && Number.isFinite(fileSize) ? fileSize : fallbackFile.size;
54
+ const mimeTypeCandidate = item;
55
+ const mediaType = typeof mimeTypeCandidate.mimeType === "string" && mimeTypeCandidate.mimeType.trim() ? mimeTypeCandidate.mimeType : typeof mimeTypeCandidate.mediaType === "string" && mimeTypeCandidate.mediaType.trim() ? mimeTypeCandidate.mediaType : fallbackFile.type || "application/octet-stream";
56
+ return {
57
+ attachmentId: id,
58
+ fileName,
59
+ originalFileName: fallbackFile.name,
60
+ inputIndex,
61
+ mediaType,
62
+ size
63
+ };
64
+ }
65
+ async function uploadSingleFile(args) {
66
+ const {
67
+ file,
68
+ fileIndex,
69
+ endpoint,
70
+ entityType,
71
+ recordId,
72
+ partitionCode,
73
+ fetchImpl,
74
+ signal,
75
+ perFileTimeoutMs,
76
+ onProgress
77
+ } = args;
78
+ const buildFailure = (reason, message) => ({
79
+ fileName: file.name,
80
+ originalFileName: file.name,
81
+ inputIndex: fileIndex,
82
+ reason,
83
+ message
84
+ });
85
+ if (signal.aborted) {
86
+ return {
87
+ ok: false,
88
+ failure: buildFailure("aborted", "Upload aborted before starting.")
89
+ };
90
+ }
91
+ const form = new FormData();
92
+ form.append("entityId", entityType);
93
+ form.append("recordId", recordId);
94
+ form.append("file", file);
95
+ if (partitionCode && partitionCode.trim().length > 0) {
96
+ form.append("partitionCode", partitionCode.trim());
97
+ }
98
+ const localController = new AbortController();
99
+ const onParentAbort = () => localController.abort();
100
+ signal.addEventListener("abort", onParentAbort, { once: true });
101
+ let timeoutHandle = null;
102
+ let timedOut = false;
103
+ if (perFileTimeoutMs > 0) {
104
+ timeoutHandle = setTimeout(() => {
105
+ timedOut = true;
106
+ localController.abort();
107
+ }, perFileTimeoutMs);
108
+ }
109
+ const clearTimers = () => {
110
+ if (timeoutHandle !== null) {
111
+ clearTimeout(timeoutHandle);
112
+ timeoutHandle = null;
113
+ }
114
+ signal.removeEventListener("abort", onParentAbort);
115
+ };
116
+ let response;
117
+ try {
118
+ response = await fetchImpl(endpoint, {
119
+ method: "POST",
120
+ body: form,
121
+ signal: localController.signal
122
+ });
123
+ } catch (networkError) {
124
+ clearTimers();
125
+ if (timedOut) {
126
+ return {
127
+ ok: false,
128
+ failure: buildFailure(
129
+ "aborted",
130
+ `Upload timed out after ${Math.round(perFileTimeoutMs / 1e3)}s. The server did not respond \u2014 try again, or attach the file to a record first and reference it in the chat.`
131
+ )
132
+ };
133
+ }
134
+ const aborted = signal.aborted || localController.signal.aborted || networkError?.name === "AbortError";
135
+ if (aborted) {
136
+ return { ok: false, failure: buildFailure("aborted", "Upload aborted.") };
137
+ }
138
+ const message = networkError instanceof Error ? networkError.message : "Network request failed.";
139
+ return { ok: false, failure: buildFailure("network", message) };
140
+ }
141
+ clearTimers();
142
+ let payload = null;
143
+ try {
144
+ const text = await response.text();
145
+ if (text && text.trim()) {
146
+ payload = JSON.parse(text);
147
+ }
148
+ } catch {
149
+ payload = null;
150
+ }
151
+ if (!response.ok) {
152
+ const rawMessage = normalizeServerErrorMessage(payload);
153
+ const fallbackMessage = rawMessage || `Upload failed (${response.status}).`;
154
+ const reason = mapStatusToReason(response.status, rawMessage);
155
+ return { ok: false, failure: buildFailure(reason, fallbackMessage) };
156
+ }
157
+ const item = parseServerItem(payload, file, fileIndex);
158
+ if (!item) {
159
+ return {
160
+ ok: false,
161
+ failure: buildFailure(
162
+ "server",
163
+ "Attachment API returned an unexpected response shape."
164
+ )
165
+ };
166
+ }
167
+ if (onProgress) {
168
+ try {
169
+ onProgress(fileIndex, { loaded: item.size, total: item.size });
170
+ } catch {
171
+ }
172
+ }
173
+ return { ok: true, item };
174
+ }
175
+ async function uploadAttachmentsForChat(files, options = {}) {
176
+ const items = [];
177
+ const failed = [];
178
+ if (!Array.isArray(files) || files.length === 0) {
179
+ return { items, failed };
180
+ }
181
+ const fetchImpl = resolveFetchImpl(options.fetchImpl);
182
+ const endpoint = options.endpoint?.trim() || DEFAULT_ATTACHMENTS_ENDPOINT;
183
+ const entityType = options.entityType?.trim() || DEFAULT_AI_CHAT_ENTITY_ID;
184
+ const recordId = options.recordId?.trim() || mintRecordId();
185
+ const rawConcurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
186
+ const concurrency = Math.max(
187
+ 1,
188
+ Math.min(files.length, Math.floor(rawConcurrency) || DEFAULT_CONCURRENCY)
189
+ );
190
+ const signal = options.signal ?? new AbortController().signal;
191
+ const perFileTimeoutMs = (() => {
192
+ const raw = options.perFileTimeoutMs;
193
+ if (raw === 0) return 0;
194
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return raw;
195
+ return DEFAULT_PER_FILE_TIMEOUT_MS;
196
+ })();
197
+ const outcomes = new Array(files.length).fill(null);
198
+ let nextIndex = 0;
199
+ const worker = async () => {
200
+ while (true) {
201
+ if (signal.aborted) return;
202
+ const currentIndex = nextIndex;
203
+ if (currentIndex >= files.length) return;
204
+ nextIndex = currentIndex + 1;
205
+ const file = files[currentIndex];
206
+ outcomes[currentIndex] = await uploadSingleFile({
207
+ file,
208
+ fileIndex: currentIndex,
209
+ endpoint,
210
+ entityType,
211
+ recordId,
212
+ partitionCode: options.partitionCode,
213
+ fetchImpl,
214
+ signal,
215
+ perFileTimeoutMs,
216
+ onProgress: options.onProgress
217
+ });
218
+ }
219
+ };
220
+ const workerCount = Math.min(concurrency, files.length);
221
+ const workers = Array.from({ length: workerCount }, () => worker());
222
+ await Promise.all(workers);
223
+ for (let index = 0; index < files.length; index += 1) {
224
+ const outcome = outcomes[index];
225
+ if (outcome && outcome.ok) {
226
+ items.push(outcome.item);
227
+ continue;
228
+ }
229
+ if (outcome && !outcome.ok) {
230
+ failed.push(outcome.failure);
231
+ continue;
232
+ }
233
+ const file = files[index];
234
+ const fallbackName = file?.name ?? `file-${index}`;
235
+ failed.push({
236
+ fileName: fallbackName,
237
+ originalFileName: fallbackName,
238
+ inputIndex: index,
239
+ reason: "aborted",
240
+ message: "Upload aborted before starting."
241
+ });
242
+ }
243
+ return { items, failed };
244
+ }
245
+ const __testables = {
246
+ DEFAULT_ATTACHMENTS_ENDPOINT,
247
+ DEFAULT_AI_CHAT_ENTITY_ID,
248
+ DEFAULT_CONCURRENCY,
249
+ DEFAULT_PER_FILE_TIMEOUT_MS,
250
+ mapStatusToReason
251
+ };
252
+ export {
253
+ __testables,
254
+ uploadAttachmentsForChat
255
+ };
256
+ //# sourceMappingURL=upload-adapter.js.map