@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +2 -1
- package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
- package/dist/ai/AiAssistantLauncher.js +596 -0
- package/dist/ai/AiAssistantLauncher.js.map +7 -0
- package/dist/ai/AiChat.js +1092 -0
- package/dist/ai/AiChat.js.map +7 -0
- package/dist/ai/AiChatSessions.js +297 -0
- package/dist/ai/AiChatSessions.js.map +7 -0
- package/dist/ai/AiDock.js +347 -0
- package/dist/ai/AiDock.js.map +7 -0
- package/dist/ai/AiMessageContent.js +369 -0
- package/dist/ai/AiMessageContent.js.map +7 -0
- package/dist/ai/ChatPaneTabs.js +251 -0
- package/dist/ai/ChatPaneTabs.js.map +7 -0
- package/dist/ai/index.js +115 -0
- package/dist/ai/index.js.map +7 -0
- package/dist/ai/parts/ConfirmationCard.js +211 -0
- package/dist/ai/parts/ConfirmationCard.js.map +7 -0
- package/dist/ai/parts/FieldDiffCard.js +119 -0
- package/dist/ai/parts/FieldDiffCard.js.map +7 -0
- package/dist/ai/parts/MutationPreviewCard.js +224 -0
- package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
- package/dist/ai/parts/MutationResultCard.js +240 -0
- package/dist/ai/parts/MutationResultCard.js.map +7 -0
- package/dist/ai/parts/approval-cards-map.js +15 -0
- package/dist/ai/parts/approval-cards-map.js.map +7 -0
- package/dist/ai/parts/index.js +24 -0
- package/dist/ai/parts/index.js.map +7 -0
- package/dist/ai/parts/pending-action-api.js +60 -0
- package/dist/ai/parts/pending-action-api.js.map +7 -0
- package/dist/ai/parts/types.js +1 -0
- package/dist/ai/parts/types.js.map +7 -0
- package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
- package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
- package/dist/ai/records/ActivityCard.js +83 -0
- package/dist/ai/records/ActivityCard.js.map +7 -0
- package/dist/ai/records/CompanyCard.js +81 -0
- package/dist/ai/records/CompanyCard.js.map +7 -0
- package/dist/ai/records/DealCard.js +76 -0
- package/dist/ai/records/DealCard.js.map +7 -0
- package/dist/ai/records/PersonCard.js +68 -0
- package/dist/ai/records/PersonCard.js.map +7 -0
- package/dist/ai/records/ProductCard.js +68 -0
- package/dist/ai/records/ProductCard.js.map +7 -0
- package/dist/ai/records/RecordCard.js +29 -0
- package/dist/ai/records/RecordCard.js.map +7 -0
- package/dist/ai/records/RecordCardShell.js +103 -0
- package/dist/ai/records/RecordCardShell.js.map +7 -0
- package/dist/ai/records/index.js +31 -0
- package/dist/ai/records/index.js.map +7 -0
- package/dist/ai/records/registry.js +51 -0
- package/dist/ai/records/registry.js.map +7 -0
- package/dist/ai/records/types.js +1 -0
- package/dist/ai/records/types.js.map +7 -0
- package/dist/ai/ui-part-registry.js +112 -0
- package/dist/ai/ui-part-registry.js.map +7 -0
- package/dist/ai/ui-part-slots.js +14 -0
- package/dist/ai/ui-part-slots.js.map +7 -0
- package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
- package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
- package/dist/ai/upload-adapter.js +256 -0
- package/dist/ai/upload-adapter.js.map +7 -0
- package/dist/ai/useAiChat.js +549 -0
- package/dist/ai/useAiChat.js.map +7 -0
- package/dist/ai/useAiChatUpload.js +127 -0
- package/dist/ai/useAiChatUpload.js.map +7 -0
- package/dist/ai/useAiShortcuts.js +43 -0
- package/dist/ai/useAiShortcuts.js.map +7 -0
- package/dist/backend/AppShell.js +8 -4
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/BackendChromeProvider.js +2 -0
- package/dist/backend/BackendChromeProvider.js.map +2 -2
- package/dist/backend/DataTable.js +19 -2
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterBar.js +19 -15
- package/dist/backend/FilterBar.js.map +2 -2
- package/dist/backend/dashboard/DashboardScreen.js +31 -3
- package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
- package/dist/backend/injection/spotIds.js +6 -0
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/notifications/useNotificationEffect.js +38 -2
- package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +2 -2
- package/jest.config.cjs +7 -1
- package/jest.markdown-mock.tsx +7 -0
- package/package.json +10 -4
- package/src/ai/AiAssistantLauncher.tsx +805 -0
- package/src/ai/AiChat.tsx +1483 -0
- package/src/ai/AiChatSessions.tsx +429 -0
- package/src/ai/AiDock.tsx +505 -0
- package/src/ai/AiMessageContent.tsx +515 -0
- package/src/ai/ChatPaneTabs.tsx +310 -0
- package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
- package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
- package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
- package/src/ai/__tests__/AiChat.test.tsx +257 -0
- package/src/ai/__tests__/AiDock.test.tsx +124 -0
- package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
- package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
- package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
- package/src/ai/__tests__/upload-adapter.test.ts +213 -0
- package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
- package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
- package/src/ai/index.ts +125 -0
- package/src/ai/parts/ConfirmationCard.tsx +310 -0
- package/src/ai/parts/FieldDiffCard.tsx +173 -0
- package/src/ai/parts/MutationPreviewCard.tsx +302 -0
- package/src/ai/parts/MutationResultCard.tsx +360 -0
- package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
- package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
- package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
- package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
- package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
- package/src/ai/parts/approval-cards-map.ts +24 -0
- package/src/ai/parts/index.ts +27 -0
- package/src/ai/parts/pending-action-api.ts +123 -0
- package/src/ai/parts/types.ts +84 -0
- package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
- package/src/ai/records/ActivityCard.tsx +102 -0
- package/src/ai/records/CompanyCard.tsx +89 -0
- package/src/ai/records/DealCard.tsx +85 -0
- package/src/ai/records/PersonCard.tsx +77 -0
- package/src/ai/records/ProductCard.tsx +83 -0
- package/src/ai/records/RecordCard.tsx +37 -0
- package/src/ai/records/RecordCardShell.tsx +169 -0
- package/src/ai/records/index.ts +30 -0
- package/src/ai/records/registry.tsx +80 -0
- package/src/ai/records/types.ts +90 -0
- package/src/ai/ui-part-registry.ts +233 -0
- package/src/ai/ui-part-slots.ts +32 -0
- package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
- package/src/ai/upload-adapter.ts +421 -0
- package/src/ai/useAiChat.ts +865 -0
- package/src/ai/useAiChatUpload.ts +180 -0
- package/src/ai/useAiShortcuts.ts +79 -0
- package/src/backend/AppShell.tsx +12 -5
- package/src/backend/BackendChromeProvider.tsx +2 -0
- package/src/backend/DataTable.tsx +20 -1
- package/src/backend/FilterBar.tsx +26 -13
- package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
- package/src/backend/dashboard/DashboardScreen.tsx +38 -3
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
- package/src/backend/injection/spotIds.ts +6 -0
- package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
- package/src/backend/notifications/useNotificationEffect.ts +47 -2
- 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
|
+
}
|