@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,83 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Package, Tag as TagIcon } from 'lucide-react'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import { KeyValueList, RecordCardShell, TagRow, statusToTagVariant } from './RecordCardShell'
7
+ import type { ProductRecordPayload } from './types'
8
+
9
+ function formatPrice(price: string | number | null | undefined, currency?: string | null): string | null {
10
+ if (price === null || price === undefined || price === '') return null
11
+ const value = typeof price === 'number' ? price : Number(price)
12
+ if (!Number.isFinite(value)) {
13
+ return typeof price === 'string' ? price : null
14
+ }
15
+ const code = currency && currency.length === 3 ? currency.toUpperCase() : undefined
16
+ try {
17
+ if (code) {
18
+ return new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).format(value)
19
+ }
20
+ } catch {
21
+ // fall through
22
+ }
23
+ const formatted = new Intl.NumberFormat().format(value)
24
+ return code ? `${formatted} ${code}` : formatted
25
+ }
26
+
27
+ export interface ProductCardProps extends ProductRecordPayload {}
28
+
29
+ export function ProductCard(props: ProductCardProps) {
30
+ const t = useT()
31
+ const status = props.status
32
+ ? { label: props.status, variant: statusToTagVariant(props.status) }
33
+ : null
34
+ const price = formatPrice(props.price, props.currency)
35
+
36
+ const leading = props.imageUrl ? (
37
+ <div className="relative size-12 overflow-hidden rounded-md border border-border bg-muted">
38
+ <img
39
+ src={props.imageUrl}
40
+ alt={props.name}
41
+ className="size-full object-cover"
42
+ loading="lazy"
43
+ />
44
+ </div>
45
+ ) : (
46
+ <div className="flex size-12 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground" aria-hidden>
47
+ <Package className="size-5" />
48
+ </div>
49
+ )
50
+
51
+ const items = [
52
+ props.sku ? { label: t('ai_assistant.chat.records.fields.sku', 'SKU'), value: <span className="font-mono text-[11px]">{props.sku}</span> } : null,
53
+ price ? { label: t('ai_assistant.chat.records.fields.price', 'Price'), value: <span className="font-medium">{price}</span> } : null,
54
+ props.category ? { label: t('ai_assistant.chat.records.fields.category', 'Category'), value: props.category } : null,
55
+ ].filter(Boolean) as { label: string; value: React.ReactNode }[]
56
+
57
+ return (
58
+ <RecordCardShell
59
+ kindLabel={t('ai_assistant.chat.records.kinds.product', 'Product')}
60
+ kindIcon={<Package className="size-4" aria-hidden />}
61
+ leading={leading}
62
+ title={props.name}
63
+ subtitle={[props.sku, price].filter(Boolean).join(' • ') || undefined}
64
+ status={status}
65
+ href={props.href}
66
+ id={props.id}
67
+ className={props.className}
68
+ dataKind="product"
69
+ >
70
+ <div className="space-y-2">
71
+ <KeyValueList items={items} />
72
+ {props.description ? (
73
+ <p className="line-clamp-2 text-muted-foreground">{props.description}</p>
74
+ ) : null}
75
+ {props.tags && props.tags.length > 0 ? <TagRow tags={props.tags} /> : null}
76
+ </div>
77
+ </RecordCardShell>
78
+ )
79
+ }
80
+
81
+ export default ProductCard
82
+
83
+ export { Package, TagIcon }
@@ -0,0 +1,37 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { ActivityCard } from './ActivityCard'
5
+ import { CompanyCard } from './CompanyCard'
6
+ import { DealCard } from './DealCard'
7
+ import { PersonCard } from './PersonCard'
8
+ import { ProductCard } from './ProductCard'
9
+ import type { RecordCardPayload } from './types'
10
+
11
+ export interface RecordCardProps {
12
+ data: RecordCardPayload
13
+ }
14
+
15
+ /**
16
+ * Renders an Open Mercato record card based on the `kind` discriminator.
17
+ * Used by the AI chat transcript to upgrade fenced ```open-mercato:<kind>```
18
+ * blocks into rich, interactive widgets.
19
+ */
20
+ export function RecordCard({ data }: RecordCardProps) {
21
+ switch (data.kind) {
22
+ case 'deal':
23
+ return <DealCard {...data} />
24
+ case 'person':
25
+ return <PersonCard {...data} />
26
+ case 'company':
27
+ return <CompanyCard {...data} />
28
+ case 'product':
29
+ return <ProductCard {...data} />
30
+ case 'activity':
31
+ return <ActivityCard {...data} />
32
+ default:
33
+ return null
34
+ }
35
+ }
36
+
37
+ export default RecordCard
@@ -0,0 +1,169 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { ExternalLink } from 'lucide-react'
5
+ import { cn } from '@open-mercato/shared/lib/utils'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import { Tag, type TagVariant } from '../../primitives/tag'
8
+
9
+ export interface RecordCardShellProps {
10
+ kindLabel: string
11
+ kindIcon: React.ReactNode
12
+ title: string
13
+ subtitle?: React.ReactNode
14
+ status?: { label: string; variant: TagVariant } | null
15
+ href?: string
16
+ id?: string
17
+ leading?: React.ReactNode
18
+ children?: React.ReactNode
19
+ className?: string
20
+ dataKind?: string
21
+ }
22
+
23
+ export function RecordCardShell({
24
+ kindLabel,
25
+ kindIcon,
26
+ title,
27
+ subtitle,
28
+ status,
29
+ href,
30
+ id,
31
+ leading,
32
+ children,
33
+ className,
34
+ dataKind,
35
+ }: RecordCardShellProps) {
36
+ const t = useT()
37
+ return (
38
+ <div
39
+ data-ai-record-card={dataKind ?? kindLabel.toLowerCase()}
40
+ data-record-id={id}
41
+ className={cn(
42
+ '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',
43
+ href ? 'hover:border-primary/40 hover:bg-accent/40' : '',
44
+ className,
45
+ )}
46
+ >
47
+ <div className="flex items-start gap-3">
48
+ {leading ? (
49
+ <div className="shrink-0">{leading}</div>
50
+ ) : (
51
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary" aria-hidden>
52
+ {kindIcon}
53
+ </div>
54
+ )}
55
+ <div className="min-w-0 flex-1">
56
+ <div className="flex flex-wrap items-center gap-2">
57
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
58
+ {kindLabel}
59
+ </span>
60
+ {status ? (
61
+ <Tag variant={status.variant} dot>
62
+ {status.label}
63
+ </Tag>
64
+ ) : null}
65
+ </div>
66
+ <div className="mt-0.5 flex items-start gap-2">
67
+ <h4 className="min-w-0 flex-1 truncate text-sm font-semibold leading-snug text-foreground">
68
+ {href ? (
69
+ <a
70
+ href={href}
71
+ target="_blank"
72
+ rel="noopener noreferrer"
73
+ className="outline-none hover:underline focus-visible:underline"
74
+ >
75
+ {title}
76
+ </a>
77
+ ) : (
78
+ title
79
+ )}
80
+ </h4>
81
+ {href ? (
82
+ <a
83
+ href={href}
84
+ target="_blank"
85
+ rel="noopener noreferrer"
86
+ className="shrink-0 rounded-md p-1 text-muted-foreground hover:text-foreground"
87
+ aria-label={t('ai_assistant.chat.records.openRecord', 'Open record')}
88
+ >
89
+ <ExternalLink className="size-3.5" aria-hidden />
90
+ </a>
91
+ ) : null}
92
+ </div>
93
+ {subtitle ? (
94
+ <div className="mt-0.5 truncate text-xs text-muted-foreground">{subtitle}</div>
95
+ ) : null}
96
+ </div>
97
+ </div>
98
+ {children ? <div className="text-xs text-foreground">{children}</div> : null}
99
+ </div>
100
+ )
101
+ }
102
+
103
+ export interface KeyValueListItem {
104
+ label: string
105
+ value: React.ReactNode
106
+ }
107
+
108
+ export function KeyValueList({ items }: { items: KeyValueListItem[] }) {
109
+ if (items.length === 0) return null
110
+ return (
111
+ <dl className="grid grid-cols-[minmax(80px,auto)_1fr] gap-x-3 gap-y-1">
112
+ {items.map((item, idx) => (
113
+ <React.Fragment key={`${item.label}-${idx}`}>
114
+ <dt className="truncate text-[11px] uppercase tracking-wide text-muted-foreground">
115
+ {item.label}
116
+ </dt>
117
+ <dd className="min-w-0 truncate text-foreground">{item.value}</dd>
118
+ </React.Fragment>
119
+ ))}
120
+ </dl>
121
+ )
122
+ }
123
+
124
+ export function TagRow({ tags }: { tags: string[] }) {
125
+ if (tags.length === 0) return null
126
+ return (
127
+ <div className="flex flex-wrap gap-1">
128
+ {tags.map((tag, idx) => (
129
+ <Tag key={`${tag}-${idx}`} variant="brand">
130
+ {tag}
131
+ </Tag>
132
+ ))}
133
+ </div>
134
+ )
135
+ }
136
+
137
+ export function statusToTagVariant(status: string | null | undefined): TagVariant {
138
+ if (!status) return 'neutral'
139
+ const s = String(status).toLowerCase().trim()
140
+ if (
141
+ s === 'won' ||
142
+ s === 'win' ||
143
+ s === 'active' ||
144
+ s === 'completed' ||
145
+ s === 'done' ||
146
+ s === 'paid' ||
147
+ s === 'success' ||
148
+ s === 'closed_won' ||
149
+ s === 'closed-won'
150
+ )
151
+ return 'success'
152
+ if (
153
+ s === 'lost' ||
154
+ s === 'failed' ||
155
+ s === 'cancelled' ||
156
+ s === 'canceled' ||
157
+ s === 'overdue' ||
158
+ s === 'closed_lost' ||
159
+ s === 'closed-lost'
160
+ )
161
+ return 'error'
162
+ if (s === 'pending' || s === 'in_progress' || s === 'in progress' || s === 'open' || s === 'qualified')
163
+ return 'info'
164
+ if (s === 'at_risk' || s === 'at risk' || s === 'review' || s === 'follow_up')
165
+ return 'warning'
166
+ if (s === 'draft' || s === 'archived' || s === 'inactive')
167
+ return 'neutral'
168
+ return 'info'
169
+ }
@@ -0,0 +1,30 @@
1
+ export { RecordCard, type RecordCardProps } from './RecordCard'
2
+ export {
3
+ registerRecordCardUiParts,
4
+ RECORD_CARD_COMPONENT_IDS,
5
+ type RecordCardComponentId,
6
+ } from './registry'
7
+ export { DealCard, type DealCardProps } from './DealCard'
8
+ export { PersonCard, type PersonCardProps } from './PersonCard'
9
+ export { CompanyCard, type CompanyCardProps } from './CompanyCard'
10
+ export { ProductCard, type ProductCardProps } from './ProductCard'
11
+ export { ActivityCard, type ActivityCardProps } from './ActivityCard'
12
+ export {
13
+ RecordCardShell,
14
+ KeyValueList,
15
+ TagRow,
16
+ statusToTagVariant,
17
+ type RecordCardShellProps,
18
+ type KeyValueListItem,
19
+ } from './RecordCardShell'
20
+ export type {
21
+ RecordCardKind,
22
+ RecordCardPayload,
23
+ RecordCardBaseProps,
24
+ DealRecordPayload,
25
+ PersonRecordPayload,
26
+ CompanyRecordPayload,
27
+ ProductRecordPayload,
28
+ ActivityRecordPayload,
29
+ StatusToTag,
30
+ } from './types'
@@ -0,0 +1,80 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ defaultAiUiPartRegistry,
6
+ type AiUiPartProps,
7
+ } from '../ui-part-registry'
8
+ import { ActivityCard } from './ActivityCard'
9
+ import { CompanyCard } from './CompanyCard'
10
+ import { DealCard } from './DealCard'
11
+ import { PersonCard } from './PersonCard'
12
+ import { ProductCard } from './ProductCard'
13
+ import type {
14
+ ActivityRecordPayload,
15
+ CompanyRecordPayload,
16
+ DealRecordPayload,
17
+ PersonRecordPayload,
18
+ ProductRecordPayload,
19
+ } from './types'
20
+
21
+ /**
22
+ * Stable component ids the AI runtime can emit through the typed UI-parts
23
+ * protocol. Each id renders the same record-card component used by the
24
+ * inline fenced-block renderer, so a host can opt into either path without
25
+ * duplicating UI code.
26
+ */
27
+ export const RECORD_CARD_COMPONENT_IDS = [
28
+ 'open-mercato.deal-card',
29
+ 'open-mercato.person-card',
30
+ 'open-mercato.company-card',
31
+ 'open-mercato.product-card',
32
+ 'open-mercato.activity-card',
33
+ ] as const
34
+
35
+ export type RecordCardComponentId = (typeof RECORD_CARD_COMPONENT_IDS)[number]
36
+
37
+ function DealCardPart({ payload }: AiUiPartProps) {
38
+ return <DealCard {...((payload ?? {}) as DealRecordPayload)} />
39
+ }
40
+
41
+ function PersonCardPart({ payload }: AiUiPartProps) {
42
+ return <PersonCard {...((payload ?? {}) as PersonRecordPayload)} />
43
+ }
44
+
45
+ function CompanyCardPart({ payload }: AiUiPartProps) {
46
+ return <CompanyCard {...((payload ?? {}) as CompanyRecordPayload)} />
47
+ }
48
+
49
+ function ProductCardPart({ payload }: AiUiPartProps) {
50
+ return <ProductCard {...((payload ?? {}) as ProductRecordPayload)} />
51
+ }
52
+
53
+ function ActivityCardPart({ payload }: AiUiPartProps) {
54
+ return <ActivityCard {...((payload ?? {}) as ActivityRecordPayload)} />
55
+ }
56
+
57
+ let registered = false
58
+
59
+ /**
60
+ * Register the built-in record-card components on the module-global UI-part
61
+ * registry. Idempotent; safe to call from app bootstrap or from a top-level
62
+ * `<AiChat>` host. Hosts that need a scoped registry can re-register the
63
+ * same ids on their own {@link createAiUiPartRegistry} instance.
64
+ */
65
+ export function registerRecordCardUiParts(): void {
66
+ if (registered) return
67
+ registered = true
68
+ defaultAiUiPartRegistry.register('open-mercato.deal-card', DealCardPart)
69
+ defaultAiUiPartRegistry.register('open-mercato.person-card', PersonCardPart)
70
+ defaultAiUiPartRegistry.register('open-mercato.company-card', CompanyCardPart)
71
+ defaultAiUiPartRegistry.register('open-mercato.product-card', ProductCardPart)
72
+ defaultAiUiPartRegistry.register(
73
+ 'open-mercato.activity-card',
74
+ ActivityCardPart,
75
+ )
76
+ }
77
+
78
+ // Auto-register at module load so consumers that import from
79
+ // `@open-mercato/ui/ai` get the cards wired up without manual bootstrap.
80
+ registerRecordCardUiParts()
@@ -0,0 +1,90 @@
1
+ import type { TagVariant } from '../../primitives/tag'
2
+
3
+ export type RecordCardKind =
4
+ | 'deal'
5
+ | 'person'
6
+ | 'company'
7
+ | 'product'
8
+ | 'activity'
9
+
10
+ export interface RecordCardBaseProps {
11
+ id?: string
12
+ href?: string
13
+ className?: string
14
+ }
15
+
16
+ export interface DealRecordPayload extends RecordCardBaseProps {
17
+ title: string
18
+ status?: string | null
19
+ stage?: string | null
20
+ amount?: string | number | null
21
+ currency?: string | null
22
+ closeDate?: string | null
23
+ ownerName?: string | null
24
+ personName?: string | null
25
+ companyName?: string | null
26
+ description?: string | null
27
+ tags?: string[] | null
28
+ }
29
+
30
+ export interface PersonRecordPayload extends RecordCardBaseProps {
31
+ name: string
32
+ title?: string | null
33
+ email?: string | null
34
+ phone?: string | null
35
+ companyName?: string | null
36
+ ownerName?: string | null
37
+ status?: string | null
38
+ tags?: string[] | null
39
+ avatarUrl?: string | null
40
+ }
41
+
42
+ export interface CompanyRecordPayload extends RecordCardBaseProps {
43
+ name: string
44
+ industry?: string | null
45
+ website?: string | null
46
+ email?: string | null
47
+ phone?: string | null
48
+ city?: string | null
49
+ country?: string | null
50
+ ownerName?: string | null
51
+ status?: string | null
52
+ tags?: string[] | null
53
+ logoUrl?: string | null
54
+ }
55
+
56
+ export interface ProductRecordPayload extends RecordCardBaseProps {
57
+ name: string
58
+ sku?: string | null
59
+ price?: string | number | null
60
+ currency?: string | null
61
+ status?: string | null
62
+ category?: string | null
63
+ description?: string | null
64
+ imageUrl?: string | null
65
+ tags?: string[] | null
66
+ }
67
+
68
+ export interface ActivityRecordPayload extends RecordCardBaseProps {
69
+ title: string
70
+ type?: string | null
71
+ status?: string | null
72
+ dueDate?: string | null
73
+ completedAt?: string | null
74
+ ownerName?: string | null
75
+ relatedTo?: string | null
76
+ description?: string | null
77
+ tags?: string[] | null
78
+ }
79
+
80
+ export type RecordCardPayload =
81
+ | ({ kind: 'deal' } & DealRecordPayload)
82
+ | ({ kind: 'person' } & PersonRecordPayload)
83
+ | ({ kind: 'company' } & CompanyRecordPayload)
84
+ | ({ kind: 'product' } & ProductRecordPayload)
85
+ | ({ kind: 'activity' } & ActivityRecordPayload)
86
+
87
+ export interface StatusToTag {
88
+ variant: TagVariant
89
+ label: string
90
+ }