@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +2 -1
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -5
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/ai-agents.js +47 -5
- package/dist/modules/catalog/ai-agents.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -5
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +4 -3
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -9
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +2 -9
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/customers/ai-agents.js +174 -1
- package/dist/modules/customers/ai-agents.js.map +2 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +196 -0
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +3 -1
- package/dist/modules/customers/ai-tools.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +11 -1
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +11 -1
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +11 -1
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +37 -15
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/ChangelogTab.js +3 -9
- package/dist/modules/customers/components/detail/ChangelogTab.js.map +2 -2
- package/dist/modules/customers/components/detail/changelogValues.js +35 -0
- package/dist/modules/customers/components/detail/changelogValues.js.map +7 -0
- package/dist/modules/customers/components/detail/customFieldUtils.js +1 -1
- package/dist/modules/customers/components/detail/customFieldUtils.js.map +1 -1
- package/dist/modules/customers/components/formConfig.js +3 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +4 -9
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +302 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +2 -2
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -5
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +2 -5
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -5
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +2 -25
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +2 -43
- package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +162 -42
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/customFieldHelpers.js +4 -31
- package/dist/modules/sales/components/documents/customFieldHelpers.js.map +2 -2
- package/package.json +15 -6
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +2 -1
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -5
- package/src/modules/business_rules/migrations/.snapshot-open-mercato.json +746 -394
- package/src/modules/catalog/ai-agents.ts +62 -4
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +2 -5
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -12
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -10
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +2519 -1307
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +828 -834
- package/src/modules/customers/AGENTS.md +6 -1
- package/src/modules/customers/ai-agents.ts +213 -6
- package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +286 -0
- package/src/modules/customers/ai-tools.ts +2 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +10 -0
- package/src/modules/customers/backend/customers/deals/page.tsx +10 -0
- package/src/modules/customers/backend/customers/people/page.tsx +10 -0
- package/src/modules/customers/commands/companies.ts +38 -14
- package/src/modules/customers/components/detail/AiActionChips.tsx +2 -2
- package/src/modules/customers/components/detail/ChangelogTab.tsx +3 -10
- package/src/modules/customers/components/detail/changelogValues.ts +42 -0
- package/src/modules/customers/components/detail/customFieldUtils.ts +1 -1
- package/src/modules/customers/components/formConfig.tsx +3 -2
- package/src/modules/customers/i18n/de.json +7 -0
- package/src/modules/customers/i18n/en.json +7 -0
- package/src/modules/customers/i18n/es.json +7 -0
- package/src/modules/customers/i18n/pl.json +7 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +2313 -2336
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -6
- package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +358 -0
- package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.ts +34 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +2 -2
- package/src/modules/customers/widgets/injection-table.ts +12 -9
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +554 -210
- package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +146 -146
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +2 -5
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +2 -5
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +273 -140
- package/src/modules/feature_toggles/migrations/.snapshot-open-mercato.json +242 -137
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +498 -195
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +158 -157
- package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +274 -129
- package/src/modules/planner/migrations/.snapshot-open-mercato.json +327 -157
- package/src/modules/progress/AGENTS.md +72 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -5
- package/src/modules/resources/migrations/.snapshot-open-mercato.json +920 -470
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +2 -27
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +2 -49
- package/src/modules/sales/components/documents/LineItemDialog.tsx +214 -78
- package/src/modules/sales/components/documents/customFieldHelpers.ts +4 -43
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/pl.json +3 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +4740 -4778
- package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +340 -81
- 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,
|
|
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: <
|
|
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
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
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
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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-
|
|
36
|
-
priority:
|
|
38
|
+
widgetId: 'customers.injection.ai-deal-analyzer-trigger',
|
|
39
|
+
priority: 90,
|
|
37
40
|
},
|
|
38
41
|
],
|
|
39
42
|
'detail:customers.deal:header': [
|