@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3278.1.a4d26475e4
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 +2 -2
- package/dist/generated/entities/domain_mapping/index.js +31 -0
- package/dist/generated/entities/domain_mapping/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +16 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +18 -17
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +2 -1
- package/dist/modules/customer_accounts/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-check.js +70 -0
- package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +14 -3
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
- package/dist/modules/customer_accounts/api/signup.js +32 -5
- package/dist/modules/customer_accounts/api/signup.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/data/enrichers.js +78 -12
- package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
- package/dist/modules/customer_accounts/data/entities.js +85 -2
- package/dist/modules/customer_accounts/data/entities.js.map +2 -2
- package/dist/modules/customer_accounts/data/guards.js +130 -0
- package/dist/modules/customer_accounts/data/guards.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +19 -0
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/di.js +23 -1
- package/dist/modules/customer_accounts/di.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +9 -2
- package/dist/modules/customer_accounts/events.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
- package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
- package/dist/modules/customer_accounts/lib/hostname.js +55 -0
- package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +76 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +76 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +48 -1
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
- package/dist/modules/customers/api/companies/route.js +4 -9
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +4 -9
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +4 -9
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +0 -12
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +361 -284
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +378 -473
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +368 -286
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +6 -0
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +16 -2
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +1 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +31 -8
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/generated/entities/domain_mapping/index.ts +14 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +16 -0
- package/package.json +5 -5
- package/src/helpers/integration/salesUi.ts +27 -23
- package/src/modules/customer_accounts/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
- package/src/modules/customer_accounts/api/domain-check.ts +83 -0
- package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
- package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
- package/src/modules/customer_accounts/api/login.ts +14 -3
- package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
- package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
- package/src/modules/customer_accounts/api/signup.ts +36 -5
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
- package/src/modules/customer_accounts/data/enrichers.ts +95 -12
- package/src/modules/customer_accounts/data/entities.ts +80 -1
- package/src/modules/customer_accounts/data/guards.ts +155 -0
- package/src/modules/customer_accounts/data/validators.ts +26 -0
- package/src/modules/customer_accounts/di.ts +35 -1
- package/src/modules/customer_accounts/events.ts +8 -1
- package/src/modules/customer_accounts/i18n/de.json +78 -0
- package/src/modules/customer_accounts/i18n/en.json +78 -0
- package/src/modules/customer_accounts/i18n/es.json +78 -0
- package/src/modules/customer_accounts/i18n/pl.json +78 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
- package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
- package/src/modules/customer_accounts/lib/hostname.ts +76 -0
- package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
- package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
- package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
- package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
- package/src/modules/customer_accounts/notifications.client.ts +76 -0
- package/src/modules/customer_accounts/notifications.ts +76 -0
- package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
- package/src/modules/customer_accounts/setup.ts +81 -1
- package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
- package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
- package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
- package/src/modules/customers/api/companies/route.ts +4 -9
- package/src/modules/customers/api/deals/route.ts +4 -9
- package/src/modules/customers/api/people/route.ts +4 -9
- package/src/modules/customers/api/utils.ts +0 -14
- package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
- package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
- package/src/modules/customers/backend/customers/people/page.tsx +331 -240
- package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
- package/src/modules/customers/i18n/de.json +31 -0
- package/src/modules/customers/i18n/en.json +31 -0
- package/src/modules/customers/i18n/es.json +31 -0
- package/src/modules/customers/i18n/pl.json +31 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
- package/src/modules/customers/widgets/injection-table.ts +6 -0
- package/src/modules/perspectives/services/perspectiveService.ts +30 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -0
- package/src/modules/query_index/lib/engine.ts +46 -8
|
@@ -37,7 +37,10 @@ import { cn } from '@open-mercato/shared/lib/utils'
|
|
|
37
37
|
|
|
38
38
|
export const CUSTOMERS_AI_INJECT_AGENT_ID = 'customers.account_assistant'
|
|
39
39
|
|
|
40
|
-
export type CustomersAiInjectView =
|
|
40
|
+
export type CustomersAiInjectView =
|
|
41
|
+
| 'customers.people.list'
|
|
42
|
+
| 'customers.companies.list'
|
|
43
|
+
| 'customers.deals.list'
|
|
41
44
|
|
|
42
45
|
export interface CustomersAiInjectPageContext {
|
|
43
46
|
view: CustomersAiInjectView
|
|
@@ -83,8 +86,9 @@ function readNumber(value: unknown): number {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function resolveView(tableId: string | null | undefined): CustomersAiInjectView {
|
|
86
|
-
if (typeof tableId === 'string'
|
|
87
|
-
return 'customers.
|
|
89
|
+
if (typeof tableId === 'string') {
|
|
90
|
+
if (tableId.includes('deals')) return 'customers.deals.list'
|
|
91
|
+
if (tableId.includes('companies')) return 'customers.companies.list'
|
|
88
92
|
}
|
|
89
93
|
return 'customers.people.list'
|
|
90
94
|
}
|
|
@@ -115,6 +119,78 @@ function useCustomerSuggestions(
|
|
|
115
119
|
): AiChatSuggestion[] {
|
|
116
120
|
const t = useT()
|
|
117
121
|
return React.useMemo(() => {
|
|
122
|
+
if (view === 'customers.deals.list') {
|
|
123
|
+
if (hasSelection) {
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
label: t(
|
|
127
|
+
'customers.ai_assistant.suggestions.summarizeSelectedDeals',
|
|
128
|
+
'Summarize selected deals',
|
|
129
|
+
),
|
|
130
|
+
prompt: `Give me a summary of my ${selectedCount} selected deals — stage, value, and close date`,
|
|
131
|
+
icon: <Handshake className="size-4" />,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: t(
|
|
135
|
+
'customers.ai_assistant.suggestions.atRiskSelectedDeals',
|
|
136
|
+
'Selected deals at risk',
|
|
137
|
+
),
|
|
138
|
+
prompt: `Of my ${selectedCount} selected deals, which look at risk of slipping their close date?`,
|
|
139
|
+
icon: <Handshake className="size-4" />,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: t(
|
|
143
|
+
'customers.ai_assistant.suggestions.companiesForSelectedDeals',
|
|
144
|
+
'Companies on selected deals',
|
|
145
|
+
),
|
|
146
|
+
prompt: `List the companies associated with my ${selectedCount} selected deals`,
|
|
147
|
+
icon: <Building2 className="size-4" />,
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
label: t(
|
|
154
|
+
'customers.ai_assistant.suggestions.dealsClosingThisMonth',
|
|
155
|
+
'Deals closing this month',
|
|
156
|
+
),
|
|
157
|
+
prompt: 'Which deals are closing this month?',
|
|
158
|
+
icon: <Handshake className="size-4" />,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: t(
|
|
162
|
+
'customers.ai_assistant.suggestions.dealsAtRisk',
|
|
163
|
+
'Deals at risk of slipping',
|
|
164
|
+
),
|
|
165
|
+
prompt: 'Show me deals at risk of slipping their close date',
|
|
166
|
+
icon: <Handshake className="size-4" />,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: t(
|
|
170
|
+
'customers.ai_assistant.suggestions.totalOpenDealValue',
|
|
171
|
+
'Total open deal value',
|
|
172
|
+
),
|
|
173
|
+
prompt: "What's the total value of open deals across the pipeline?",
|
|
174
|
+
icon: <Handshake className="size-4" />,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: t(
|
|
178
|
+
'customers.ai_assistant.suggestions.topDealsByValue',
|
|
179
|
+
'Top deals by value',
|
|
180
|
+
),
|
|
181
|
+
prompt: 'Show me the top 5 deals by value',
|
|
182
|
+
icon: <Handshake className="size-4" />,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
label: t(
|
|
186
|
+
'customers.ai_assistant.suggestions.searchDeals',
|
|
187
|
+
'Search for a deal',
|
|
188
|
+
),
|
|
189
|
+
prompt: 'Search for deals by title, company, or stage',
|
|
190
|
+
icon: <Search className="size-4" />,
|
|
191
|
+
},
|
|
192
|
+
]
|
|
193
|
+
}
|
|
118
194
|
if (view === 'customers.companies.list') {
|
|
119
195
|
if (hasSelection) {
|
|
120
196
|
return [
|
|
@@ -228,24 +304,115 @@ function useCustomerContextItems(pageContext: CustomersAiInjectPageContext): AiC
|
|
|
228
304
|
return React.useMemo(() => {
|
|
229
305
|
const items: AiChatContextItem[] = []
|
|
230
306
|
const { selectedCount, totalMatching } = pageContext.extra
|
|
231
|
-
const
|
|
307
|
+
const view = pageContext.view
|
|
308
|
+
let selectedKey = 'customers.ai_assistant.context.selectedPeople'
|
|
309
|
+
let selectedFallback = '{count} contacts selected'
|
|
310
|
+
let matchingKey = 'customers.ai_assistant.context.matchingPeople'
|
|
311
|
+
let matchingFallback = '{count} contacts in view'
|
|
312
|
+
if (view === 'customers.companies.list') {
|
|
313
|
+
selectedKey = 'customers.ai_assistant.context.selectedCompanies'
|
|
314
|
+
selectedFallback = '{count} companies selected'
|
|
315
|
+
matchingKey = 'customers.ai_assistant.context.matchingCompanies'
|
|
316
|
+
matchingFallback = '{count} companies in view'
|
|
317
|
+
} else if (view === 'customers.deals.list') {
|
|
318
|
+
selectedKey = 'customers.ai_assistant.context.selectedDeals'
|
|
319
|
+
selectedFallback = '{count} deals selected'
|
|
320
|
+
matchingKey = 'customers.ai_assistant.context.matchingDeals'
|
|
321
|
+
matchingFallback = '{count} deals in view'
|
|
322
|
+
}
|
|
232
323
|
if (selectedCount > 0) {
|
|
233
|
-
|
|
234
|
-
? 'customers.ai_assistant.context.selectedCompanies'
|
|
235
|
-
: 'customers.ai_assistant.context.selectedPeople'
|
|
236
|
-
const fallback = isCompanies ? '{count} companies selected' : '{count} contacts selected'
|
|
237
|
-
items.push({ label: t(key, fallback).replace('{count}', String(selectedCount)) })
|
|
324
|
+
items.push({ label: t(selectedKey, selectedFallback).replace('{count}', String(selectedCount)) })
|
|
238
325
|
} else if (totalMatching > 0) {
|
|
239
|
-
|
|
240
|
-
? 'customers.ai_assistant.context.matchingCompanies'
|
|
241
|
-
: 'customers.ai_assistant.context.matchingPeople'
|
|
242
|
-
const fallback = isCompanies ? '{count} companies in view' : '{count} contacts in view'
|
|
243
|
-
items.push({ label: t(key, fallback).replace('{count}', String(totalMatching)) })
|
|
326
|
+
items.push({ label: t(matchingKey, matchingFallback).replace('{count}', String(totalMatching)) })
|
|
244
327
|
}
|
|
245
328
|
return items
|
|
246
329
|
}, [pageContext, t])
|
|
247
330
|
}
|
|
248
331
|
|
|
332
|
+
interface ViewCopy {
|
|
333
|
+
welcomeDescriptionAll: string
|
|
334
|
+
welcomeDescriptionSelection: string
|
|
335
|
+
descriptionWithSelection: string
|
|
336
|
+
triggerAriaLabel: string
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function useViewCopy(view: CustomersAiInjectView, selectedCount: number): ViewCopy {
|
|
340
|
+
const t = useT()
|
|
341
|
+
return React.useMemo(() => {
|
|
342
|
+
const replaceCount = (s: string) => s.replace('{count}', String(selectedCount))
|
|
343
|
+
if (view === 'customers.deals.list') {
|
|
344
|
+
return {
|
|
345
|
+
welcomeDescriptionAll: t(
|
|
346
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAllDeals',
|
|
347
|
+
'Ask about deals, pipeline, or close dates:',
|
|
348
|
+
),
|
|
349
|
+
welcomeDescriptionSelection: replaceCount(
|
|
350
|
+
t(
|
|
351
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelectionDeals',
|
|
352
|
+
'Ready to explore your {count} selected deals:',
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
descriptionWithSelection: replaceCount(
|
|
356
|
+
t(
|
|
357
|
+
'customers.ai_assistant.sheet.descriptionWithSelectionDeals',
|
|
358
|
+
'Working with {count} selected deals. Ask about their stage, value, and close dates.',
|
|
359
|
+
),
|
|
360
|
+
),
|
|
361
|
+
triggerAriaLabel: t(
|
|
362
|
+
'customers.ai_assistant.trigger.ariaLabelDeals',
|
|
363
|
+
'Open AI assistant for deals',
|
|
364
|
+
),
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (view === 'customers.companies.list') {
|
|
368
|
+
return {
|
|
369
|
+
welcomeDescriptionAll: t(
|
|
370
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAllCompanies',
|
|
371
|
+
'Ask me anything about your companies, deals, and activities:',
|
|
372
|
+
),
|
|
373
|
+
welcomeDescriptionSelection: replaceCount(
|
|
374
|
+
t(
|
|
375
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelectionCompanies',
|
|
376
|
+
'Ready to explore your {count} selected companies:',
|
|
377
|
+
),
|
|
378
|
+
),
|
|
379
|
+
descriptionWithSelection: replaceCount(
|
|
380
|
+
t(
|
|
381
|
+
'customers.ai_assistant.sheet.descriptionWithSelectionCompanies',
|
|
382
|
+
'Working with {count} selected companies. Ask about their deals, contacts, and activities.',
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
triggerAriaLabel: t(
|
|
386
|
+
'customers.ai_assistant.trigger.ariaLabelCompanies',
|
|
387
|
+
'Open AI assistant for companies',
|
|
388
|
+
),
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
welcomeDescriptionAll: t(
|
|
393
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
394
|
+
'Ask me anything about your customers, companies, and deals:',
|
|
395
|
+
),
|
|
396
|
+
welcomeDescriptionSelection: replaceCount(
|
|
397
|
+
t(
|
|
398
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelection',
|
|
399
|
+
'Ready to explore your {count} selected contacts:',
|
|
400
|
+
),
|
|
401
|
+
),
|
|
402
|
+
descriptionWithSelection: replaceCount(
|
|
403
|
+
t(
|
|
404
|
+
'customers.ai_assistant.sheet.descriptionWithSelection',
|
|
405
|
+
'Working with {count} selected contacts. Ask about their details, deals, companies, and activities.',
|
|
406
|
+
),
|
|
407
|
+
),
|
|
408
|
+
triggerAriaLabel: t(
|
|
409
|
+
'customers.ai_assistant.trigger.ariaLabel',
|
|
410
|
+
'Open AI assistant for people',
|
|
411
|
+
),
|
|
412
|
+
}
|
|
413
|
+
}, [view, selectedCount, t])
|
|
414
|
+
}
|
|
415
|
+
|
|
249
416
|
interface CustomerAgentDescriptor {
|
|
250
417
|
id: string
|
|
251
418
|
label: string
|
|
@@ -285,6 +452,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
285
452
|
const hasSelection = selectedCount > 0
|
|
286
453
|
const suggestions = useCustomerSuggestions(pageContext.view, hasSelection, selectedCount)
|
|
287
454
|
const contextItems = useCustomerContextItems(pageContext)
|
|
455
|
+
const viewCopy = useViewCopy(pageContext.view, selectedCount)
|
|
288
456
|
|
|
289
457
|
const openAgent = React.useCallback((agentId: string) => {
|
|
290
458
|
setActiveAgent(agentId)
|
|
@@ -330,14 +498,8 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
330
498
|
contextItems,
|
|
331
499
|
welcomeTitle: t('customers.ai_assistant.sheet.welcomeTitle', 'CRM Assistant'),
|
|
332
500
|
welcomeDescription: hasSelection
|
|
333
|
-
?
|
|
334
|
-
|
|
335
|
-
'Ready to explore your {count} selected contacts:',
|
|
336
|
-
).replace('{count}', String(selectedCount))
|
|
337
|
-
: t(
|
|
338
|
-
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
339
|
-
'Ask me anything about your customers, companies, and deals:',
|
|
340
|
-
),
|
|
501
|
+
? viewCopy.welcomeDescriptionSelection
|
|
502
|
+
: viewCopy.welcomeDescriptionAll,
|
|
341
503
|
})
|
|
342
504
|
setOpen(false)
|
|
343
505
|
}, [
|
|
@@ -347,15 +509,12 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
347
509
|
dock,
|
|
348
510
|
hasSelection,
|
|
349
511
|
pageContext,
|
|
350
|
-
selectedCount,
|
|
351
512
|
suggestions,
|
|
352
513
|
t,
|
|
514
|
+
viewCopy,
|
|
353
515
|
])
|
|
354
516
|
|
|
355
|
-
const triggerLabel =
|
|
356
|
-
'customers.ai_assistant.trigger.ariaLabel',
|
|
357
|
-
'Open AI assistant for people',
|
|
358
|
-
)
|
|
517
|
+
const triggerLabel = viewCopy.triggerAriaLabel
|
|
359
518
|
|
|
360
519
|
const labelText = t('customers.ai_assistant.trigger.label', 'AI')
|
|
361
520
|
const moreAgentsLabel = t(
|
|
@@ -375,10 +534,14 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
375
534
|
title={triggerLabel}
|
|
376
535
|
className={cn(
|
|
377
536
|
'relative',
|
|
537
|
+
'hover:bg-brand-violet/10',
|
|
378
538
|
agents.length > 1 && 'rounded-r-none border-r-0',
|
|
379
539
|
)}
|
|
380
540
|
>
|
|
381
|
-
<Sparkles
|
|
541
|
+
<Sparkles
|
|
542
|
+
className="size-4 text-brand-violet"
|
|
543
|
+
aria-hidden
|
|
544
|
+
/>
|
|
382
545
|
<span>{labelText}</span>
|
|
383
546
|
{hasSelection ? (
|
|
384
547
|
<span
|
|
@@ -487,10 +650,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
487
650
|
</div>
|
|
488
651
|
<DialogDescription>
|
|
489
652
|
{hasSelection
|
|
490
|
-
?
|
|
491
|
-
'customers.ai_assistant.sheet.descriptionWithSelection',
|
|
492
|
-
'Working with {count} selected contacts. Ask about their details, deals, companies, and activities.',
|
|
493
|
-
).replace('{count}', String(selectedCount))
|
|
653
|
+
? viewCopy.descriptionWithSelection
|
|
494
654
|
: t(
|
|
495
655
|
'customers.ai_assistant.sheet.description',
|
|
496
656
|
'Your CRM assistant. Ask about people, companies, deals, and activities.',
|
|
@@ -504,6 +664,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
504
664
|
contextItems={contextItems}
|
|
505
665
|
hasSelection={hasSelection}
|
|
506
666
|
selectedCount={selectedCount}
|
|
667
|
+
viewCopy={viewCopy}
|
|
507
668
|
/>
|
|
508
669
|
</DialogContent>
|
|
509
670
|
</Dialog>
|
|
@@ -518,6 +679,7 @@ interface CustomersChatBodyProps {
|
|
|
518
679
|
contextItems: AiChatContextItem[]
|
|
519
680
|
hasSelection: boolean
|
|
520
681
|
selectedCount: number
|
|
682
|
+
viewCopy: ViewCopy
|
|
521
683
|
}
|
|
522
684
|
|
|
523
685
|
function CustomersChatBody({
|
|
@@ -527,6 +689,7 @@ function CustomersChatBody({
|
|
|
527
689
|
contextItems,
|
|
528
690
|
hasSelection,
|
|
529
691
|
selectedCount,
|
|
692
|
+
viewCopy,
|
|
530
693
|
}: CustomersChatBodyProps) {
|
|
531
694
|
const t = useT()
|
|
532
695
|
const sessions = useAiChatSessions()
|
|
@@ -563,14 +726,8 @@ function CustomersChatBody({
|
|
|
563
726
|
welcomeTitle={t('customers.ai_assistant.sheet.welcomeTitle', 'CRM Assistant')}
|
|
564
727
|
welcomeDescription={
|
|
565
728
|
hasSelection
|
|
566
|
-
?
|
|
567
|
-
|
|
568
|
-
'Ready to explore your {count} selected contacts:',
|
|
569
|
-
).replace('{count}', String(selectedCount))
|
|
570
|
-
: t(
|
|
571
|
-
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
572
|
-
'Ask me anything about your customers, companies, and deals:',
|
|
573
|
-
)
|
|
729
|
+
? viewCopy.welcomeDescriptionSelection
|
|
730
|
+
: viewCopy.welcomeDescriptionAll
|
|
574
731
|
}
|
|
575
732
|
/>
|
|
576
733
|
) : null}
|
|
@@ -5,28 +5,34 @@ import AiAssistantTriggerWidget from './widget.client'
|
|
|
5
5
|
* Step 4.10 — Backend AiChat injection example.
|
|
6
6
|
*
|
|
7
7
|
* Demonstrates how a third-party module can drop `<AiChat>` onto a page
|
|
8
|
-
* it does NOT own (the customers People
|
|
9
|
-
* injection system. Targets
|
|
10
|
-
*
|
|
8
|
+
* it does NOT own (the customers People / Companies / Deals lists) via
|
|
9
|
+
* the existing widget injection system. Targets DataTable header /
|
|
10
|
+
* search-trailing spots owned by the `DataTable` primitive in
|
|
11
|
+
* `packages/ui`.
|
|
11
12
|
*
|
|
12
13
|
* The trigger button opens a right-side sheet embedding
|
|
13
14
|
* `<AiChat agent="customers.account_assistant" pageContext={...} />`.
|
|
14
15
|
* `pageContext` follows the spec §10.1 shape:
|
|
15
16
|
*
|
|
16
|
-
* { view: 'customers.people.list',
|
|
17
|
+
* { view: 'customers.people.list' | 'customers.companies.list' | 'customers.deals.list',
|
|
17
18
|
* recordType: null,
|
|
18
19
|
* recordId: string, // "" or comma-separated UUIDs
|
|
19
20
|
* extra: { selectedCount, totalMatching } }
|
|
20
21
|
*
|
|
21
|
-
* Feature-gated behind `
|
|
22
|
+
* Feature-gated behind `ai_assistant.view` only — the host CRM page
|
|
23
|
+
* already enforces its own `customers.people.view`/`companies.view`/
|
|
24
|
+
* `deals.view` guard, so the widget gate just needs to ensure the user
|
|
25
|
+
* has AI assistant access. (Earlier versions also listed
|
|
26
|
+
* `customers.people.view`, which is AND-evaluated by the registry and
|
|
27
|
+
* hid the trigger from companies-only or deals-only viewers.)
|
|
22
28
|
*/
|
|
23
29
|
const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
|
|
24
30
|
metadata: {
|
|
25
31
|
id: 'customers.injection.ai-assistant-trigger',
|
|
26
32
|
title: 'Customers AI Assistant Trigger',
|
|
27
33
|
description:
|
|
28
|
-
'Renders an "Ask AI" button in the people list
|
|
29
|
-
features: ['
|
|
34
|
+
'Renders an "Ask AI" button in the people / companies / deals list headers that opens a sheet embedding the customers account assistant.',
|
|
35
|
+
features: ['ai_assistant.view'],
|
|
30
36
|
requiredModules: ['ai_assistant'],
|
|
31
37
|
priority: 100,
|
|
32
38
|
enabled: true,
|
|
@@ -30,6 +30,12 @@ export const injectionTable: ModuleInjectionTable = {
|
|
|
30
30
|
priority: 100,
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
+
'data-table:customers.deals.list:search-trailing': [
|
|
34
|
+
{
|
|
35
|
+
widgetId: 'customers.injection.ai-assistant-trigger',
|
|
36
|
+
priority: 100,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
33
39
|
'detail:customers.deal:header': [
|
|
34
40
|
{
|
|
35
41
|
widgetId: 'customers.injection.ai-deal-detail-trigger',
|
|
@@ -95,26 +95,54 @@ function isPerspectivesState(value: unknown): value is PerspectivesState {
|
|
|
95
95
|
return true
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Defensive migration for legacy filter state shapes captured before the
|
|
100
|
+
* advanced-filter tree (SPEC-048). Existing perspectives only store either
|
|
101
|
+
* advanced-filter URL params (tree shape with `v:2` or a `root` key) or
|
|
102
|
+
* undefined — this helper is a safety net for legacy `FilterValues`-shaped
|
|
103
|
+
* records (flat key/value records of column filters) that could only appear
|
|
104
|
+
* if old saved-view JSON were imported.
|
|
105
|
+
*
|
|
106
|
+
* - Tree-shaped state (`v:2` or `root` key) is passed through unchanged.
|
|
107
|
+
* - Undefined / null filters are passed through unchanged.
|
|
108
|
+
* - Legacy `FilterValues`-shaped records are dropped (set to `undefined`)
|
|
109
|
+
* because there is no reliable mapping back to the new operator model;
|
|
110
|
+
* the user sees an empty tree and can recreate.
|
|
111
|
+
*/
|
|
112
|
+
export function maybeMigrateLegacyFilterValues(settings: PerspectiveSettings): PerspectiveSettings {
|
|
113
|
+
const filters = settings.filters
|
|
114
|
+
if (!filters || typeof filters !== 'object') return settings
|
|
115
|
+
const record = filters as Record<string, unknown>
|
|
116
|
+
if ('v' in record && record.v === 2) return settings
|
|
117
|
+
if ('root' in record) return settings
|
|
118
|
+
if (typeof console !== 'undefined') {
|
|
119
|
+
console.warn('[perspectives] Dropping legacy filterValues shape; please re-create the perspective with the new filter UI.')
|
|
120
|
+
}
|
|
121
|
+
return { ...settings, filters: undefined }
|
|
122
|
+
}
|
|
123
|
+
|
|
98
124
|
function toResolvedPerspective(entity: Perspective): ResolvedPerspective {
|
|
125
|
+
const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)
|
|
99
126
|
return {
|
|
100
127
|
id: entity.id,
|
|
101
128
|
name: entity.name,
|
|
102
129
|
tableId: entity.tableId,
|
|
103
130
|
isDefault: !!entity.isDefault,
|
|
104
|
-
settings
|
|
131
|
+
settings,
|
|
105
132
|
createdAt: entity.createdAt.toISOString(),
|
|
106
133
|
updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,
|
|
107
134
|
}
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
function toResolvedRolePerspective(entity: RolePerspective): ResolvedRolePerspective {
|
|
138
|
+
const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)
|
|
111
139
|
return {
|
|
112
140
|
id: entity.id,
|
|
113
141
|
roleId: entity.roleId,
|
|
114
142
|
tableId: entity.tableId,
|
|
115
143
|
name: entity.name,
|
|
116
144
|
isDefault: !!entity.isDefault,
|
|
117
|
-
settings
|
|
145
|
+
settings,
|
|
118
146
|
tenantId: nullish(entity.tenantId),
|
|
119
147
|
organizationId: nullish(entity.organizationId),
|
|
120
148
|
createdAt: entity.createdAt.toISOString(),
|
|
@@ -39,6 +39,7 @@ export default function PortalLoginPage({ params }: Props) {
|
|
|
39
39
|
const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {
|
|
40
40
|
method: 'POST',
|
|
41
41
|
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
credentials: 'include',
|
|
42
43
|
body: JSON.stringify({ email, password, tenantId: tenant.tenantId }),
|
|
43
44
|
})
|
|
44
45
|
|
|
@@ -625,14 +625,19 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
625
625
|
existing.push(filter)
|
|
626
626
|
groups.set(filter.orGroup, existing)
|
|
627
627
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
628
|
+
const groupList = Array.from(groups.values()).filter((g) => g.length > 0)
|
|
629
|
+
if (groupList.length === 0) return q
|
|
630
|
+
// Combine all groups in a single WHERE: disjuncts are OR'd together; within
|
|
631
|
+
// each disjunct, fields are AND'd. Building this as separate `.where()` calls
|
|
632
|
+
// would AND the disjuncts (wrong semantics).
|
|
633
|
+
return q.where((eb: any) => eb.or(
|
|
634
|
+
groupList.map((groupFilters) => {
|
|
635
|
+
const parts = groupFilters.map((filter) =>
|
|
636
|
+
this.buildBaseFilterExpression(eb, filter, resolveBaseColumn, qualify, entity, searchRuntime),
|
|
637
|
+
)
|
|
638
|
+
return parts.length === 1 ? parts[0] : eb.and(parts)
|
|
639
|
+
}),
|
|
640
|
+
))
|
|
636
641
|
}
|
|
637
642
|
|
|
638
643
|
const applyAliasScopes = async (target: AnyBuilder, aliasName: string): Promise<AnyBuilder> => {
|
|
@@ -1341,6 +1346,39 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1341
1346
|
// Doc-based filter via `ei` alias — returned as EXISTS where possible
|
|
1342
1347
|
return this.buildIndexDocFilterExpression(eb, 'ei', entity, fieldName, filter.op, filter.value, 'b.id', searchRuntime)
|
|
1343
1348
|
}
|
|
1349
|
+
// For like/ilike with active search-tokens, route through hashed-token EXISTS subquery
|
|
1350
|
+
// so encrypted-at-rest columns can still be searched.
|
|
1351
|
+
if (
|
|
1352
|
+
(filter.op === 'like' || filter.op === 'ilike') &&
|
|
1353
|
+
searchRuntime?.enabled &&
|
|
1354
|
+
typeof filter.value === 'string'
|
|
1355
|
+
) {
|
|
1356
|
+
const tokens = tokenizeText(String(filter.value), searchRuntime.config)
|
|
1357
|
+
if (tokens.hashes.length) {
|
|
1358
|
+
const sources: SearchTokenSource[] = (searchRuntime.searchSources && searchRuntime.searchSources.length
|
|
1359
|
+
? searchRuntime.searchSources
|
|
1360
|
+
: [{ entity: String(entity), recordIdColumn: 'b.id' }]
|
|
1361
|
+
).filter((src) => src.recordIdColumn && src.entity)
|
|
1362
|
+
if (sources.length) {
|
|
1363
|
+
return eb.or(
|
|
1364
|
+
sources.map((src) =>
|
|
1365
|
+
eb.exists(this.buildSearchTokensSub(eb, {
|
|
1366
|
+
entity: src.entity,
|
|
1367
|
+
field: fieldName,
|
|
1368
|
+
hashes: tokens.hashes,
|
|
1369
|
+
recordIdColumn: src.recordIdColumn,
|
|
1370
|
+
tenantId: searchRuntime.tenantId ?? null,
|
|
1371
|
+
organizationScope: searchRuntime.organizationScope ?? null,
|
|
1372
|
+
})),
|
|
1373
|
+
),
|
|
1374
|
+
)
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
// Tokenizer produced no hashes (e.g. value too short). Match the regular-base-filter
|
|
1378
|
+
// path's behavior of skipping the predicate (no filter), which is preferable to
|
|
1379
|
+
// silently turning into a plain `ilike` against an encrypted column.
|
|
1380
|
+
return sql<boolean>`true`
|
|
1381
|
+
}
|
|
1344
1382
|
return this.buildColumnFilterExpression(eb, qualify(baseField), filter.op, filter.value)
|
|
1345
1383
|
}
|
|
1346
1384
|
|