@open-mercato/core 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1-develop.3306.1.9ad9ff2526

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 (122) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +14 -0
  3. package/dist/modules/auth/backend/roles/[id]/edit/page.js +2 -1
  4. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -5
  6. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  7. package/dist/modules/catalog/ai-agents.js +47 -5
  8. package/dist/modules/catalog/ai-agents.js.map +2 -2
  9. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -5
  10. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  11. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +4 -3
  12. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -9
  14. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +2 -9
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  17. package/dist/modules/customers/ai-agents.js +174 -1
  18. package/dist/modules/customers/ai-agents.js.map +2 -2
  19. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +196 -0
  20. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +7 -0
  21. package/dist/modules/customers/ai-tools.js +3 -1
  22. package/dist/modules/customers/ai-tools.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/companies/page.js +11 -1
  24. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  25. package/dist/modules/customers/backend/customers/deals/page.js +11 -1
  26. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  27. package/dist/modules/customers/backend/customers/people/page.js +11 -1
  28. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  29. package/dist/modules/customers/commands/companies.js +37 -15
  30. package/dist/modules/customers/commands/companies.js.map +2 -2
  31. package/dist/modules/customers/components/detail/AiActionChips.js +2 -2
  32. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  33. package/dist/modules/customers/components/detail/ChangelogTab.js +3 -9
  34. package/dist/modules/customers/components/detail/ChangelogTab.js.map +2 -2
  35. package/dist/modules/customers/components/detail/changelogValues.js +35 -0
  36. package/dist/modules/customers/components/detail/changelogValues.js.map +7 -0
  37. package/dist/modules/customers/components/detail/customFieldUtils.js +1 -1
  38. package/dist/modules/customers/components/detail/customFieldUtils.js.map +1 -1
  39. package/dist/modules/customers/components/formConfig.js +3 -2
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +4 -9
  42. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  43. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +302 -0
  44. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +7 -0
  45. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js +17 -0
  46. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js.map +7 -0
  47. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +2 -2
  48. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +2 -2
  49. package/dist/modules/customers/widgets/injection-table.js +2 -2
  50. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  51. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -5
  52. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  53. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +2 -5
  54. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  55. package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -5
  56. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  57. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +2 -25
  58. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  59. package/dist/modules/sales/components/channels/ChannelOfferForm.js +2 -43
  60. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  61. package/dist/modules/sales/components/documents/LineItemDialog.js +162 -42
  62. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  63. package/dist/modules/sales/components/documents/customFieldHelpers.js +4 -31
  64. package/dist/modules/sales/components/documents/customFieldHelpers.js.map +2 -2
  65. package/package.json +15 -6
  66. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +2 -1
  67. package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -5
  68. package/src/modules/business_rules/migrations/.snapshot-open-mercato.json +746 -394
  69. package/src/modules/catalog/ai-agents.ts +62 -4
  70. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +2 -5
  71. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
  72. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -12
  73. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -10
  74. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +2519 -1307
  75. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +828 -834
  76. package/src/modules/customers/AGENTS.md +6 -1
  77. package/src/modules/customers/ai-agents.ts +213 -6
  78. package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +286 -0
  79. package/src/modules/customers/ai-tools.ts +2 -0
  80. package/src/modules/customers/backend/customers/companies/page.tsx +10 -0
  81. package/src/modules/customers/backend/customers/deals/page.tsx +10 -0
  82. package/src/modules/customers/backend/customers/people/page.tsx +10 -0
  83. package/src/modules/customers/commands/companies.ts +38 -14
  84. package/src/modules/customers/components/detail/AiActionChips.tsx +2 -2
  85. package/src/modules/customers/components/detail/ChangelogTab.tsx +3 -10
  86. package/src/modules/customers/components/detail/changelogValues.ts +42 -0
  87. package/src/modules/customers/components/detail/customFieldUtils.ts +1 -1
  88. package/src/modules/customers/components/formConfig.tsx +3 -2
  89. package/src/modules/customers/i18n/de.json +7 -0
  90. package/src/modules/customers/i18n/en.json +7 -0
  91. package/src/modules/customers/i18n/es.json +7 -0
  92. package/src/modules/customers/i18n/pl.json +7 -0
  93. package/src/modules/customers/migrations/.snapshot-open-mercato.json +2313 -2336
  94. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -6
  95. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +358 -0
  96. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.ts +34 -0
  97. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +2 -2
  98. package/src/modules/customers/widgets/injection-table.ts +12 -9
  99. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +554 -210
  100. package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +146 -146
  101. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +2 -5
  102. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +2 -5
  103. package/src/modules/directory/migrations/.snapshot-open-mercato.json +273 -140
  104. package/src/modules/feature_toggles/migrations/.snapshot-open-mercato.json +242 -137
  105. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +498 -195
  106. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +158 -157
  107. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +274 -129
  108. package/src/modules/planner/migrations/.snapshot-open-mercato.json +327 -157
  109. package/src/modules/progress/AGENTS.md +72 -0
  110. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -5
  111. package/src/modules/resources/migrations/.snapshot-open-mercato.json +920 -470
  112. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +2 -27
  113. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +2 -49
  114. package/src/modules/sales/components/documents/LineItemDialog.tsx +214 -78
  115. package/src/modules/sales/components/documents/customFieldHelpers.ts +4 -43
  116. package/src/modules/sales/i18n/de.json +3 -0
  117. package/src/modules/sales/i18n/en.json +3 -0
  118. package/src/modules/sales/i18n/es.json +3 -0
  119. package/src/modules/sales/i18n/pl.json +3 -0
  120. package/src/modules/sales/migrations/.snapshot-open-mercato.json +4740 -4778
  121. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +340 -81
  122. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1439 -729
@@ -17,8 +17,9 @@
17
17
  */
18
18
 
19
19
  import * as React from 'react'
20
- import { Building2, ChevronDown, Handshake, PanelRightOpen, Search, Sparkles, Users } from 'lucide-react'
20
+ import { Building2, ChevronDown, Handshake, PanelRightOpen, Search, Users } from 'lucide-react'
21
21
  import { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'
22
+ import { AiIcon } from '@open-mercato/ui/ai/AiIcon'
22
23
  import { useAiDock } from '@open-mercato/ui/ai/AiDock'
23
24
  import { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'
24
25
  import { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'
@@ -431,7 +432,7 @@ function useCustomerAgents(): CustomerAgentDescriptor[] {
431
432
  'customers.ai_assistant.agents.account.description',
432
433
  'Explore people, companies, deals, and activities.',
433
434
  ),
434
- icon: <Sparkles className="size-4" />,
435
+ icon: <AiIcon className="size-4" />,
435
436
  },
436
437
  ],
437
438
  [t],
@@ -538,10 +539,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
538
539
  agents.length > 1 && 'rounded-r-none border-r-0',
539
540
  )}
540
541
  >
541
- <Sparkles
542
- className="size-4 text-brand-violet"
543
- aria-hidden
544
- />
542
+ <AiIcon className="size-4" />
545
543
  <span>{labelText}</span>
546
544
  {hasSelection ? (
547
545
  <span
@@ -0,0 +1,358 @@
1
+ "use client"
2
+
3
+ // See /framework/ai-assistant/agents → "Deal Analyzer demo" for the
4
+ // pageContext contract (selectedRowIds → recordId comma-list) and the
5
+ // loop primitives this widget surfaces.
6
+
7
+ import * as React from 'react'
8
+ import { Handshake, PanelRightOpen, TrendingDown } from 'lucide-react'
9
+ import { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'
10
+ import { AiIcon } from '@open-mercato/ui/ai/AiIcon'
11
+ import { useAiDock } from '@open-mercato/ui/ai/AiDock'
12
+ import { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'
13
+ import { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'
14
+ import { Button } from '@open-mercato/ui/primitives/button'
15
+ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
16
+ import {
17
+ Dialog,
18
+ DialogContent,
19
+ DialogDescription,
20
+ DialogHeader,
21
+ DialogTitle,
22
+ } from '@open-mercato/ui/primitives/dialog'
23
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
24
+ import { cn } from '@open-mercato/shared/lib/utils'
25
+
26
+ export const DEAL_ANALYZER_AGENT_ID = 'customers.deal_analyzer'
27
+
28
+ export interface DealAnalyzerPageContext {
29
+ view: 'customers.deals.list'
30
+ recordType: null
31
+ recordId: string | null
32
+ extra: {
33
+ selectedCount: number
34
+ totalMatching: number
35
+ }
36
+ }
37
+
38
+ interface HostInjectionContext {
39
+ tableId?: string | null
40
+ selectedRowIds?: string[]
41
+ selectedCount?: number
42
+ total?: number
43
+ totalMatching?: number
44
+ rowCount?: number
45
+ }
46
+
47
+ function readNumber(value: unknown): number {
48
+ if (typeof value === 'number' && Number.isFinite(value)) return value
49
+ if (typeof value === 'string') {
50
+ const parsed = Number.parseInt(value, 10)
51
+ if (Number.isFinite(parsed)) return parsed
52
+ }
53
+ return 0
54
+ }
55
+
56
+ function readString(value: unknown): string {
57
+ return typeof value === 'string' ? value : ''
58
+ }
59
+
60
+ function buildDealAnalyzerPageContext(context: HostInjectionContext | undefined): DealAnalyzerPageContext {
61
+ const selectedIdsRaw = Array.isArray(context?.selectedRowIds) ? context?.selectedRowIds ?? [] : []
62
+ const selectedIds = selectedIdsRaw.map(readString).filter((id) => id.length > 0)
63
+ const selectedCount = selectedIds.length > 0
64
+ ? selectedIds.length
65
+ : readNumber(context?.selectedCount)
66
+ const totalMatching = readNumber(context?.totalMatching ?? context?.total ?? context?.rowCount)
67
+ const recordId = selectedIds.length > 0 ? selectedIds.join(',') : null
68
+ return {
69
+ view: 'customers.deals.list',
70
+ recordType: null,
71
+ recordId,
72
+ extra: {
73
+ selectedCount,
74
+ totalMatching,
75
+ },
76
+ }
77
+ }
78
+
79
+ function useDealAnalyzerSuggestions(
80
+ hasSelection: boolean,
81
+ selectedCount: number,
82
+ ): AiChatSuggestion[] {
83
+ const t = useT()
84
+ return React.useMemo(() => {
85
+ if (hasSelection) {
86
+ return [
87
+ {
88
+ label: t(
89
+ 'customers.deal_analyzer.suggestions.analyzeSelected',
90
+ 'Analyze selected deals',
91
+ ),
92
+ prompt: `Analyze my ${selectedCount} selected deals and surface any that are stalled`,
93
+ icon: <TrendingDown className="size-4" />,
94
+ },
95
+ {
96
+ label: t(
97
+ 'customers.deal_analyzer.suggestions.proposeStageMove',
98
+ 'Propose stage moves for selected deals',
99
+ ),
100
+ prompt: `Analyze my ${selectedCount} selected deals and propose stage moves for stalled high-value ones`,
101
+ icon: <Handshake className="size-4" />,
102
+ },
103
+ ]
104
+ }
105
+ return [
106
+ {
107
+ label: t(
108
+ 'customers.deal_analyzer.suggestions.analyzeStalledDeals',
109
+ 'Analyze stalled deals',
110
+ ),
111
+ prompt: 'Analyze stalled deals from the last 30 days and propose a stage move for the highest value one',
112
+ icon: <TrendingDown className="size-4" />,
113
+ },
114
+ {
115
+ label: t(
116
+ 'customers.deal_analyzer.suggestions.showAtRiskPipeline',
117
+ 'Show at-risk pipeline',
118
+ ),
119
+ prompt: 'Show me deals with no activity in the last 14 days worth more than $5,000',
120
+ icon: <Handshake className="size-4" />,
121
+ },
122
+ {
123
+ label: t(
124
+ 'customers.deal_analyzer.suggestions.overviewByStage',
125
+ 'Deal health overview',
126
+ ),
127
+ prompt: 'Give me a health overview of all open deals ranked by activity recency',
128
+ icon: <AiIcon className="size-4" />,
129
+ },
130
+ ]
131
+ }, [hasSelection, selectedCount, t])
132
+ }
133
+
134
+ function useDealAnalyzerContextItems(pageContext: DealAnalyzerPageContext): AiChatContextItem[] {
135
+ const t = useT()
136
+ return React.useMemo(() => {
137
+ const items: AiChatContextItem[] = []
138
+ const { selectedCount, totalMatching } = pageContext.extra
139
+ if (selectedCount > 0) {
140
+ items.push({
141
+ label: t(
142
+ 'customers.deal_analyzer.context.selectedDeals',
143
+ '{count} deals selected',
144
+ ).replace('{count}', String(selectedCount)),
145
+ })
146
+ } else if (totalMatching > 0) {
147
+ items.push({
148
+ label: t(
149
+ 'customers.deal_analyzer.context.dealsInView',
150
+ '{count} deals in view',
151
+ ).replace('{count}', String(totalMatching)),
152
+ })
153
+ }
154
+ return items
155
+ }, [pageContext, t])
156
+ }
157
+
158
+ interface DealAnalyzerTriggerProps {
159
+ context?: HostInjectionContext
160
+ }
161
+
162
+ export default function DealAnalyzerTriggerWidget({ context }: DealAnalyzerTriggerProps) {
163
+ const t = useT()
164
+ const dock = useAiDock()
165
+ const [open, setOpen] = React.useState(false)
166
+ const pageContext = React.useMemo(() => buildDealAnalyzerPageContext(context), [context])
167
+
168
+ const selectedCount = pageContext.extra.selectedCount
169
+ const hasSelection = selectedCount > 0
170
+ const suggestions = useDealAnalyzerSuggestions(hasSelection, selectedCount)
171
+ const contextItems = useDealAnalyzerContextItems(pageContext)
172
+
173
+ const handleTriggerClick = React.useCallback(() => {
174
+ if (dock.state.assistant?.agent === DEAL_ANALYZER_AGENT_ID) {
175
+ dock.dock(dock.state.assistant)
176
+ setOpen(false)
177
+ return
178
+ }
179
+ setOpen(true)
180
+ }, [dock])
181
+
182
+ const handleDock = React.useCallback(() => {
183
+ dock.dock({
184
+ agent: DEAL_ANALYZER_AGENT_ID,
185
+ label: t('customers.deal_analyzer.sheet.title', 'Deal Analyzer'),
186
+ description: t('customers.deal_analyzer.dock.subtitle', 'Deals'),
187
+ pageContext: pageContext as unknown as Record<string, unknown>,
188
+ placeholder: t(
189
+ 'customers.deal_analyzer.sheet.composerPlaceholder',
190
+ 'Analyze stalled deals, propose stage moves...',
191
+ ),
192
+ suggestions,
193
+ contextItems,
194
+ welcomeTitle: t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer'),
195
+ welcomeDescription: hasSelection
196
+ ? t(
197
+ 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',
198
+ 'Ready to analyze your {count} selected deals:',
199
+ ).replace('{count}', String(selectedCount))
200
+ : t(
201
+ 'customers.deal_analyzer.sheet.welcomeDescriptionAll',
202
+ 'Analyze deal pipeline health and propose stage moves:',
203
+ ),
204
+ })
205
+ setOpen(false)
206
+ }, [contextItems, dock, hasSelection, pageContext, selectedCount, suggestions, t])
207
+
208
+ const triggerLabel = t(
209
+ 'customers.deal_analyzer.trigger.ariaLabel',
210
+ 'Open Deal Analyzer AI agent',
211
+ )
212
+ const labelText = t('customers.ai_assistant.trigger.label', 'AI')
213
+
214
+ return (
215
+ <>
216
+ <Button
217
+ type="button"
218
+ variant="outline"
219
+ onClick={handleTriggerClick}
220
+ data-ai-deal-analyzer-trigger=""
221
+ aria-label={triggerLabel}
222
+ title={triggerLabel}
223
+ className={cn('relative', 'hover:bg-brand-violet/10')}
224
+ >
225
+ <AiIcon className="size-4" />
226
+ <span>{labelText}</span>
227
+ {hasSelection ? (
228
+ <span
229
+ className="absolute -top-1 -right-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium leading-none text-primary-foreground"
230
+ data-ai-deal-analyzer-selected-count={selectedCount}
231
+ >
232
+ {selectedCount}
233
+ </span>
234
+ ) : null}
235
+ </Button>
236
+ <Dialog open={open} onOpenChange={setOpen}>
237
+ <DialogContent
238
+ className={cn(
239
+ 'top-0 left-0 right-0 bottom-0 translate-x-0 translate-y-0 max-w-none w-screen h-svh max-h-svh rounded-none',
240
+ 'sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',
241
+ 'sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen',
242
+ 'flex flex-col gap-3 p-4',
243
+ )}
244
+ data-ai-deal-analyzer-sheet=""
245
+ >
246
+ <DialogHeader>
247
+ <div className="flex items-center gap-3 pr-8">
248
+ <IconButton
249
+ type="button"
250
+ variant="ghost"
251
+ size="sm"
252
+ aria-label={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}
253
+ title={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}
254
+ onClick={handleDock}
255
+ data-ai-deal-analyzer-dock=""
256
+ className="hidden lg:inline-flex shrink-0"
257
+ >
258
+ <PanelRightOpen className="size-4" aria-hidden />
259
+ </IconButton>
260
+ <DialogTitle className="flex-1 min-w-0 truncate">
261
+ {t('customers.deal_analyzer.sheet.title', 'Deal Analyzer')}
262
+ </DialogTitle>
263
+ {hasSelection ? (
264
+ <span
265
+ className="shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
266
+ data-ai-deal-analyzer-selection-pill=""
267
+ data-ai-deal-analyzer-selected-count={selectedCount}
268
+ >
269
+ {t(
270
+ 'customers.deal_analyzer.sheet.selectionPill',
271
+ 'Acting on {count} deals',
272
+ ).replace('{count}', String(selectedCount))}
273
+ </span>
274
+ ) : null}
275
+ </div>
276
+ <DialogDescription>
277
+ {hasSelection
278
+ ? t(
279
+ 'customers.deal_analyzer.sheet.descriptionWithSelection',
280
+ 'Analyzing {count} selected deals for pipeline health and stage move proposals.',
281
+ ).replace('{count}', String(selectedCount))
282
+ : t(
283
+ 'customers.deal_analyzer.sheet.description',
284
+ 'Multi-step deal health analyzer. Surfaces stalled deals and proposes stage transitions for approval.',
285
+ )}
286
+ </DialogDescription>
287
+ </DialogHeader>
288
+ <DealAnalyzerChatBody
289
+ pageContext={pageContext}
290
+ suggestions={suggestions}
291
+ contextItems={contextItems}
292
+ hasSelection={hasSelection}
293
+ selectedCount={selectedCount}
294
+ />
295
+ </DialogContent>
296
+ </Dialog>
297
+ </>
298
+ )
299
+ }
300
+
301
+ interface DealAnalyzerChatBodyProps {
302
+ pageContext: DealAnalyzerPageContext
303
+ suggestions: AiChatSuggestion[]
304
+ contextItems: AiChatContextItem[]
305
+ hasSelection: boolean
306
+ selectedCount: number
307
+ }
308
+
309
+ function DealAnalyzerChatBody({
310
+ pageContext,
311
+ suggestions,
312
+ contextItems,
313
+ hasSelection,
314
+ selectedCount,
315
+ }: DealAnalyzerChatBodyProps) {
316
+ const t = useT()
317
+ const sessions = useAiChatSessions()
318
+ const session = sessions.getActiveSession(DEAL_ANALYZER_AGENT_ID)
319
+
320
+ React.useEffect(() => {
321
+ if (!session) sessions.ensureSession(DEAL_ANALYZER_AGENT_ID)
322
+ }, [session, sessions])
323
+
324
+ return (
325
+ <>
326
+ <ChatPaneTabs agentId={DEAL_ANALYZER_AGENT_ID} className="border-b" />
327
+ <div className="min-h-0 flex-1" data-ai-deal-analyzer-chat-container="">
328
+ {session ? (
329
+ <AiChat
330
+ key={session.id}
331
+ agent={DEAL_ANALYZER_AGENT_ID}
332
+ conversationId={session.conversationId}
333
+ pageContext={pageContext as unknown as Record<string, unknown>}
334
+ className="h-full"
335
+ placeholder={t(
336
+ 'customers.deal_analyzer.sheet.composerPlaceholder',
337
+ 'Analyze stalled deals, propose stage moves...',
338
+ )}
339
+ suggestions={suggestions}
340
+ contextItems={contextItems}
341
+ welcomeTitle={t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer')}
342
+ welcomeDescription={
343
+ hasSelection
344
+ ? t(
345
+ 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',
346
+ 'Ready to analyze your {count} selected deals:',
347
+ ).replace('{count}', String(selectedCount))
348
+ : t(
349
+ 'customers.deal_analyzer.sheet.welcomeDescriptionAll',
350
+ 'Analyze deal pipeline health and propose stage moves:',
351
+ )
352
+ }
353
+ />
354
+ ) : null}
355
+ </div>
356
+ </>
357
+ )
358
+ }
@@ -0,0 +1,34 @@
1
+ import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'
2
+ import DealAnalyzerTriggerWidget from './widget.client'
3
+
4
+ /**
5
+ * Step d4 — Deal Analyzer AiChat injection widget.
6
+ *
7
+ * Drops the `ai-deal-analyzer-trigger` widget into the Deals list DataTable
8
+ * `:search-trailing` injection slot, rendering adjacent to the search input.
9
+ *
10
+ * The trigger opens a right-side sheet embedding
11
+ * `<AiChat agent="customers.deal_analyzer" pageContext={...} />`.
12
+ *
13
+ * `pageContext` follows spec §10.1 shape:
14
+ * { view: 'customers.deals.list',
15
+ * recordType: null,
16
+ * recordId: string, // "" or comma-separated selected deal UUIDs
17
+ * extra: { selectedCount, totalMatching } }
18
+ *
19
+ * Feature-gated behind `customers.deals.view` + `ai_assistant.view`.
20
+ */
21
+ const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
22
+ metadata: {
23
+ id: 'customers.injection.ai-deal-analyzer-trigger',
24
+ title: 'Deal Analyzer Trigger',
25
+ description:
26
+ 'Renders an AI button in the deals list search-trailing slot that opens a sheet embedding the deal analyzer agent.',
27
+ features: ['customers.deals.view', 'ai_assistant.view'],
28
+ priority: 90,
29
+ enabled: true,
30
+ },
31
+ Widget: DealAnalyzerTriggerWidget,
32
+ }
33
+
34
+ export default widget
@@ -24,7 +24,7 @@
24
24
  */
25
25
 
26
26
  import * as React from 'react'
27
- import { Sparkles } from 'lucide-react'
27
+ import { AiIcon } from '@open-mercato/ui/ai/AiIcon'
28
28
  import { AiChat } from '@open-mercato/ui/ai/AiChat'
29
29
  import { Button } from '@open-mercato/ui/primitives/button'
30
30
  import {
@@ -136,7 +136,7 @@ export default function AiDealDetailTriggerWidget({ context, data }: AiDealDetai
136
136
  'Open AI assistant for this deal',
137
137
  )}
138
138
  >
139
- <Sparkles className="size-4" aria-hidden />
139
+ <AiIcon className="size-4" />
140
140
  <span>{t('customers.ai_assistant.dealDetail.trigger.label', 'Ask AI')}</span>
141
141
  </Button>
142
142
  <Dialog open={open} onOpenChange={setOpen}>
@@ -1,21 +1,24 @@
1
1
  import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
2
2
 
3
3
  /**
4
- * Step 4.10 / Step 5.15 — customers module injection table.
4
+ * Step 4.10 / Step 5.15 / Step d4 — customers module injection table.
5
5
  *
6
- * - Step 4.10 drops the `ai-assistant-trigger` widget on the People-list
6
+ * - Step 4.10 drops the `ai-assistant-trigger` widget on the People and Companies
7
7
  * `DataTable` `:search-trailing` slot, which renders adjacent to the
8
8
  * list search input. The previous mount point was the `:header` slot
9
9
  * (separate row); the round icon-only trigger now lives next to the
10
10
  * search box for a tighter, single-row toolbar.
11
11
  * - Step 5.15 (Phase 3 WS-D) adds the `ai-deal-detail-trigger` widget on
12
12
  * the Deal detail page header spot (`detail:customers.deal:header`).
13
+ * - Step d4 adds the `ai-deal-analyzer-trigger` widget on the Deals list
14
+ * `DataTable` `:search-trailing` slot. Embeds `<AiChat>` for the
15
+ * `customers.deal_analyzer` agent with selected deal IDs as page context.
13
16
  *
14
- * Both widgets embed `<AiChat agent="customers.account_assistant" …>`
15
- * with a selection- or record-aware `pageContext`. The page files
16
- * themselves only register the shared `<InjectionSpot>` mount point —
17
- * the trigger, sheet, and chat surface live entirely in the injection
18
- * widgets so third-party modules can copy the pattern unchanged.
17
+ * Widgets embed `<AiChat agent="…" …>` with a selection- or record-aware
18
+ * `pageContext`. The page files themselves only register the shared
19
+ * `<InjectionSpot>` mount point — the trigger, sheet, and chat surface
20
+ * live entirely in the injection widgets so third-party modules can copy
21
+ * the pattern unchanged.
19
22
  */
20
23
  export const injectionTable: ModuleInjectionTable = {
21
24
  'data-table:customers.people.list:search-trailing': [
@@ -32,8 +35,8 @@ export const injectionTable: ModuleInjectionTable = {
32
35
  ],
33
36
  'data-table:customers.deals.list:search-trailing': [
34
37
  {
35
- widgetId: 'customers.injection.ai-assistant-trigger',
36
- priority: 100,
38
+ widgetId: 'customers.injection.ai-deal-analyzer-trigger',
39
+ priority: 90,
37
40
  },
38
41
  ],
39
42
  'detail:customers.deal:header': [