@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3043.1a796c3920
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 +13 -1
- package/dist/helpers/integration/api.js +29 -16
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/auth.js +11 -6
- package/dist/helpers/integration/auth.js.map +3 -3
- package/dist/modules/auth/commands/roles.js +9 -12
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/catalog/ai-agents-context.js +147 -0
- package/dist/modules/catalog/ai-agents-context.js.map +7 -0
- package/dist/modules/catalog/ai-agents.js +383 -0
- package/dist/modules/catalog/ai-agents.js.map +7 -0
- package/dist/modules/catalog/ai-tools/_shared.js +318 -0
- package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
- package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/types.js +10 -0
- package/dist/modules/catalog/ai-tools/types.js.map +7 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools.js +28 -0
- package/dist/modules/catalog/ai-tools.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
- package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
- package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
- package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/events.js +7 -4
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +13 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/ai-agents-context.js +96 -0
- package/dist/modules/customers/ai-agents-context.js.map +7 -0
- package/dist/modules/customers/ai-agents.js +244 -0
- package/dist/modules/customers/ai-agents.js.map +7 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
- package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
- package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/people-pack.js +261 -0
- package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
- package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/types.js +10 -0
- package/dist/modules/customers/ai-tools/types.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +20 -0
- package/dist/modules/customers/ai-tools.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection-table.js +26 -0
- package/dist/modules/customers/widgets/injection-table.js.map +7 -0
- package/dist/modules/inbox_ops/ai-tools.js +4 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/notifications/setup.js +13 -0
- package/dist/modules/notifications/setup.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.setup.ts +18 -0
- package/package.json +5 -3
- package/src/helpers/integration/api.ts +38 -16
- package/src/helpers/integration/auth.ts +13 -6
- package/src/modules/auth/commands/roles.ts +10 -12
- package/src/modules/catalog/AGENTS.md +11 -0
- package/src/modules/catalog/ai-agents-context.ts +239 -0
- package/src/modules/catalog/ai-agents.ts +525 -0
- package/src/modules/catalog/ai-tools/_shared.ts +487 -0
- package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
- package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
- package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
- package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
- package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
- package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
- package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
- package/src/modules/catalog/ai-tools/types.ts +81 -0
- package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
- package/src/modules/catalog/ai-tools.ts +78 -0
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
- package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
- package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
- package/src/modules/catalog/events.ts +7 -4
- package/src/modules/catalog/i18n/de.json +17 -0
- package/src/modules/catalog/i18n/en.json +17 -0
- package/src/modules/catalog/i18n/es.json +17 -0
- package/src/modules/catalog/i18n/pl.json +17 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/catalog/widgets/injection-table.ts +12 -0
- package/src/modules/customer_accounts/i18n/de.json +5 -0
- package/src/modules/customer_accounts/i18n/en.json +5 -0
- package/src/modules/customer_accounts/i18n/es.json +5 -0
- package/src/modules/customer_accounts/i18n/pl.json +5 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
- package/src/modules/customers/AGENTS.md +13 -0
- package/src/modules/customers/ai-agents-context.ts +150 -0
- package/src/modules/customers/ai-agents.ts +355 -0
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
- package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
- package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
- package/src/modules/customers/ai-tools/people-pack.ts +369 -0
- package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
- package/src/modules/customers/ai-tools/types.ts +76 -0
- package/src/modules/customers/ai-tools.ts +34 -0
- package/src/modules/customers/i18n/de.json +25 -0
- package/src/modules/customers/i18n/en.json +25 -0
- package/src/modules/customers/i18n/es.json +25 -0
- package/src/modules/customers/i18n/pl.json +25 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
- package/src/modules/customers/widgets/injection-table.ts +41 -0
- package/src/modules/inbox_ops/ai-tools.ts +4 -0
- package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
- package/src/modules/notifications/setup.ts +11 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-root AI tool contribution for the catalog module
|
|
3
|
+
* (Phase 1 WS-C, Steps 3.10 + 3.11 + 3.12 — read-only Phase 1 coverage;
|
|
4
|
+
* Phase 3 WS-C, Step 5.14 — D18 mutation pack under the pending-action
|
|
5
|
+
* approval contract).
|
|
6
|
+
*
|
|
7
|
+
* The generator walks every module for a top-level `ai-tools.ts` and takes
|
|
8
|
+
* the default/`aiTools` export as the contribution. This file aggregates the
|
|
9
|
+
* eight catalog packs (products, categories, variants, prices + offers,
|
|
10
|
+
* media + tags, product configuration, D18 merchandising, D18 authoring)
|
|
11
|
+
* so they all flow through the existing `ai-tools.generated.ts` pipeline
|
|
12
|
+
* without any generator changes.
|
|
13
|
+
*
|
|
14
|
+
* Step 5.14 (D18) adds the four mutation tools in `mutation-pack.ts`:
|
|
15
|
+
* - `catalog.update_product`
|
|
16
|
+
* - `catalog.bulk_update_products`
|
|
17
|
+
* - `catalog.apply_attribute_extraction`
|
|
18
|
+
* - `catalog.update_product_media_descriptions`
|
|
19
|
+
*
|
|
20
|
+
* These route through the Step 5.6 `prepareMutation` wrapper and the
|
|
21
|
+
* Step 5.8 confirm route; all other tools remain read-only and enforce
|
|
22
|
+
* tenant + organization scoping via the existing encryption helpers. The Step 3.12 authoring tools are
|
|
23
|
+
* structured-output helpers: they NEVER write and never open a second model
|
|
24
|
+
* call from inside the handler — the surrounding agent turn performs
|
|
25
|
+
* structured output against the handler's `outputSchemaDescriptor`.
|
|
26
|
+
*
|
|
27
|
+
* Step 3.11 (D18) adds the seven spec-named read tools in
|
|
28
|
+
* `merchandising-pack.ts`:
|
|
29
|
+
* - `catalog.search_products`
|
|
30
|
+
* - `catalog.get_product_bundle`
|
|
31
|
+
* - `catalog.list_selected_products`
|
|
32
|
+
* - `catalog.get_product_media`
|
|
33
|
+
* - `catalog.get_attribute_schema`
|
|
34
|
+
* - `catalog.get_category_brief`
|
|
35
|
+
* - `catalog.list_price_kinds`
|
|
36
|
+
*
|
|
37
|
+
* Step 3.12 (D18) adds the five spec-named AI-authoring tools in
|
|
38
|
+
* `authoring-pack.ts`:
|
|
39
|
+
* - `catalog.draft_description_from_attributes`
|
|
40
|
+
* - `catalog.extract_attributes_from_description`
|
|
41
|
+
* - `catalog.draft_description_from_media`
|
|
42
|
+
* - `catalog.suggest_title_variants`
|
|
43
|
+
* - `catalog.suggest_price_adjustment`
|
|
44
|
+
*
|
|
45
|
+
* `catalog.list_price_kinds` (D18) and `catalog.list_price_kinds_base` (base)
|
|
46
|
+
* coexist as distinct tools — both route through the shared `listPriceKindsCore`
|
|
47
|
+
* helper in `ai-tools/_shared.ts` so they cannot drift.
|
|
48
|
+
*
|
|
49
|
+
* See `.ai/runs/2026-04-18-ai-framework-unification/step-3.10-checks.md`,
|
|
50
|
+
* `step-3.11-checks.md`, and `step-3.12-checks.md` for the matrix of
|
|
51
|
+
* required features and decisions.
|
|
52
|
+
*/
|
|
53
|
+
import productsAiTools from './ai-tools/products-pack'
|
|
54
|
+
import categoriesAiTools from './ai-tools/categories-pack'
|
|
55
|
+
import variantsAiTools from './ai-tools/variants-pack'
|
|
56
|
+
import pricesOffersAiTools from './ai-tools/prices-offers-pack'
|
|
57
|
+
import mediaTagsAiTools from './ai-tools/media-tags-pack'
|
|
58
|
+
import configurationAiTools from './ai-tools/configuration-pack'
|
|
59
|
+
import merchandisingAiTools from './ai-tools/merchandising-pack'
|
|
60
|
+
import authoringAiTools from './ai-tools/authoring-pack'
|
|
61
|
+
import mutationAiTools from './ai-tools/mutation-pack'
|
|
62
|
+
import statsAiTools from './ai-tools/stats-pack'
|
|
63
|
+
import type { CatalogAiToolDefinition } from './ai-tools/types'
|
|
64
|
+
|
|
65
|
+
export const aiTools: CatalogAiToolDefinition[] = [
|
|
66
|
+
...productsAiTools,
|
|
67
|
+
...categoriesAiTools,
|
|
68
|
+
...variantsAiTools,
|
|
69
|
+
...pricesOffersAiTools,
|
|
70
|
+
...mediaTagsAiTools,
|
|
71
|
+
...configurationAiTools,
|
|
72
|
+
...merchandisingAiTools,
|
|
73
|
+
...authoringAiTools,
|
|
74
|
+
...mutationAiTools,
|
|
75
|
+
...statsAiTools,
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
export default aiTools
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MerchandisingAssistantSheet — Step 4.9 (Spec §10 D18).
|
|
5
|
+
*
|
|
6
|
+
* Embeds `<AiChat agent="catalog.merchandising_assistant" pageContext={...} />`
|
|
7
|
+
* in a right-side sheet (built on the shared Dialog primitive because
|
|
8
|
+
* `packages/ui` does not ship a dedicated Sheet/Drawer primitive in
|
|
9
|
+
* Phase 2). The trigger is a button rendered in the products-list page
|
|
10
|
+
* header.
|
|
11
|
+
*
|
|
12
|
+
* Phase 2 is strictly read-only: the sheet shows proposals (structured
|
|
13
|
+
* output), but the mutation tools (`catalog.update_product`,
|
|
14
|
+
* `catalog.bulk_update_products`, `catalog.apply_attribute_extraction`,
|
|
15
|
+
* `catalog.update_product_media_descriptions`) are intentionally NOT in
|
|
16
|
+
* the agent whitelist. Phase 5.14 introduces those via the pending-action
|
|
17
|
+
* contract.
|
|
18
|
+
*
|
|
19
|
+
* pageContext follows spec §10.1 exactly:
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* view: 'catalog.products.list',
|
|
23
|
+
* recordType: null,
|
|
24
|
+
* recordId: string, // "" or comma-separated UUIDs
|
|
25
|
+
* extra: {
|
|
26
|
+
* filter: { categoryId, priceRange, tags, status },
|
|
27
|
+
* totalMatching: number,
|
|
28
|
+
* selectedCount: number,
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import * as React from 'react'
|
|
34
|
+
import { Boxes, ChevronDown, FileText, Package, PanelRightOpen, PenLine, Sparkles, Tags, TrendingUp } from 'lucide-react'
|
|
35
|
+
import { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'
|
|
36
|
+
import { useAiDock } from '@open-mercato/ui/ai/AiDock'
|
|
37
|
+
import { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'
|
|
38
|
+
import { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'
|
|
39
|
+
// Side-effect import: registers the `catalog.stats-card` UI part on the
|
|
40
|
+
// global registry the first time this client bundle loads. Tools that
|
|
41
|
+
// emit `{ uiPart: { componentId: 'catalog.stats-card' } }` envelopes
|
|
42
|
+
// (catalog.show_stats today; user-defined tools tomorrow) automatically
|
|
43
|
+
// resolve to the card without dispatcher changes.
|
|
44
|
+
import '../../../components/CatalogStatsCard'
|
|
45
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
46
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
47
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
48
|
+
import {
|
|
49
|
+
Dialog,
|
|
50
|
+
DialogContent,
|
|
51
|
+
DialogDescription,
|
|
52
|
+
DialogHeader,
|
|
53
|
+
DialogTitle,
|
|
54
|
+
} from '@open-mercato/ui/primitives/dialog'
|
|
55
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
|
|
56
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
57
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
58
|
+
|
|
59
|
+
export interface MerchandisingPageContextFilter {
|
|
60
|
+
categoryId: string | null
|
|
61
|
+
priceRange: { min?: number; max?: number } | null
|
|
62
|
+
tags: string[]
|
|
63
|
+
status: string | null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MerchandisingPageContext {
|
|
67
|
+
view: 'catalog.products.list'
|
|
68
|
+
entityType?: 'catalog.products.list'
|
|
69
|
+
recordType: null
|
|
70
|
+
recordId: string
|
|
71
|
+
extra: {
|
|
72
|
+
filter: MerchandisingPageContextFilter
|
|
73
|
+
totalMatching: number
|
|
74
|
+
selectedCount: number
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface MerchandisingAssistantSheetProps {
|
|
79
|
+
/** Selection-aware page context, built by the products list host. */
|
|
80
|
+
pageContext: MerchandisingPageContext
|
|
81
|
+
/** When false (feature-gated by the host), the sheet renders nothing. */
|
|
82
|
+
enabled?: boolean
|
|
83
|
+
className?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const MERCHANDISING_AGENT_ID = 'catalog.merchandising_assistant'
|
|
87
|
+
|
|
88
|
+
function useMerchandisingSuggestions(
|
|
89
|
+
hasSelection: boolean,
|
|
90
|
+
selectedCount: number,
|
|
91
|
+
): AiChatSuggestion[] {
|
|
92
|
+
const t = useT()
|
|
93
|
+
return React.useMemo(() => {
|
|
94
|
+
if (hasSelection) {
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
label: t(
|
|
98
|
+
'catalog.merchandising_assistant.suggestions.draftDescriptions',
|
|
99
|
+
'Draft product descriptions for selected items',
|
|
100
|
+
),
|
|
101
|
+
prompt: `Draft compelling product descriptions for my ${selectedCount} selected products`,
|
|
102
|
+
icon: <PenLine className="size-4" />,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: t(
|
|
106
|
+
'catalog.merchandising_assistant.suggestions.extractAttributes',
|
|
107
|
+
'Extract attributes from descriptions',
|
|
108
|
+
),
|
|
109
|
+
prompt: `Extract structured attributes from the descriptions of my ${selectedCount} selected products`,
|
|
110
|
+
icon: <Tags className="size-4" />,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: t(
|
|
114
|
+
'catalog.merchandising_assistant.suggestions.titleVariants',
|
|
115
|
+
'Generate title variants for SEO',
|
|
116
|
+
),
|
|
117
|
+
prompt: `Generate SEO-optimized title variants for my ${selectedCount} selected products`,
|
|
118
|
+
icon: <FileText className="size-4" />,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: t(
|
|
122
|
+
'catalog.merchandising_assistant.suggestions.priceAdjustments',
|
|
123
|
+
'Suggest price adjustments',
|
|
124
|
+
),
|
|
125
|
+
prompt: `Analyze and suggest price adjustments for my ${selectedCount} selected products`,
|
|
126
|
+
icon: <TrendingUp className="size-4" />,
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
label: t(
|
|
133
|
+
'catalog.merchandising_assistant.suggestions.showStats',
|
|
134
|
+
'Show catalog overview',
|
|
135
|
+
),
|
|
136
|
+
// Triggers the `catalog.show_stats` tool, which returns the inline
|
|
137
|
+
// catalog-stats UI part (live counts of products, active products,
|
|
138
|
+
// categories, tags). Demo entry-point for the dynamic UI-part path.
|
|
139
|
+
prompt: 'Show me a quick catalog overview using the stats card.',
|
|
140
|
+
icon: <Boxes className="size-4" />,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
label: t(
|
|
144
|
+
'catalog.merchandising_assistant.suggestions.browseProducts',
|
|
145
|
+
'Show me an overview of my product catalog',
|
|
146
|
+
),
|
|
147
|
+
prompt: 'Give me an overview of my product catalog — categories, total products, and pricing ranges',
|
|
148
|
+
icon: <Package className="size-4" />,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
label: t(
|
|
152
|
+
'catalog.merchandising_assistant.suggestions.findMissingDescriptions',
|
|
153
|
+
'Find products with missing descriptions',
|
|
154
|
+
),
|
|
155
|
+
prompt: 'Find products that are missing descriptions or have very short descriptions',
|
|
156
|
+
icon: <PenLine className="size-4" />,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: t(
|
|
160
|
+
'catalog.merchandising_assistant.suggestions.analyzeAttributes',
|
|
161
|
+
'Analyze attribute coverage',
|
|
162
|
+
),
|
|
163
|
+
prompt: 'Analyze which products have incomplete attribute data',
|
|
164
|
+
icon: <Tags className="size-4" />,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: t(
|
|
168
|
+
'catalog.merchandising_assistant.suggestions.pricingOverview',
|
|
169
|
+
'Show pricing distribution',
|
|
170
|
+
),
|
|
171
|
+
prompt: 'Show me the pricing distribution across categories',
|
|
172
|
+
icon: <TrendingUp className="size-4" />,
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
}, [hasSelection, selectedCount, t])
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function useContextItems(pageContext: MerchandisingPageContext): AiChatContextItem[] {
|
|
179
|
+
const t = useT()
|
|
180
|
+
return React.useMemo(() => {
|
|
181
|
+
const items: AiChatContextItem[] = []
|
|
182
|
+
const { selectedCount, totalMatching, filter } = pageContext.extra
|
|
183
|
+
if (selectedCount > 0) {
|
|
184
|
+
items.push({
|
|
185
|
+
label: t(
|
|
186
|
+
'catalog.merchandising_assistant.context.selectedProducts',
|
|
187
|
+
'{count} products selected',
|
|
188
|
+
).replace('{count}', String(selectedCount)),
|
|
189
|
+
})
|
|
190
|
+
} else if (totalMatching > 0) {
|
|
191
|
+
items.push({
|
|
192
|
+
label: t(
|
|
193
|
+
'catalog.merchandising_assistant.context.matchingProducts',
|
|
194
|
+
'{count} products in view',
|
|
195
|
+
).replace('{count}', String(totalMatching)),
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
if (filter.categoryId) {
|
|
199
|
+
items.push({
|
|
200
|
+
label: t('catalog.merchandising_assistant.context.filteredByCategory', 'Filtered by category'),
|
|
201
|
+
detail: filter.categoryId,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
if (filter.status) {
|
|
205
|
+
items.push({ label: filter.status })
|
|
206
|
+
}
|
|
207
|
+
if (filter.tags.length > 0) {
|
|
208
|
+
items.push({
|
|
209
|
+
label: t('catalog.merchandising_assistant.context.tags', '{count} tags').replace(
|
|
210
|
+
'{count}',
|
|
211
|
+
String(filter.tags.length),
|
|
212
|
+
),
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
return items
|
|
216
|
+
}, [pageContext, t])
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
interface MerchandisingAgentDescriptor {
|
|
220
|
+
id: string
|
|
221
|
+
label: string
|
|
222
|
+
description: string
|
|
223
|
+
icon: React.ReactNode
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface AgentsResponse {
|
|
227
|
+
agents?: Array<{
|
|
228
|
+
id?: string | null
|
|
229
|
+
}>
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface MerchandisingAgentsState {
|
|
233
|
+
agents: MerchandisingAgentDescriptor[]
|
|
234
|
+
loaded: boolean
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function useMerchandisingAgents(): MerchandisingAgentsState {
|
|
238
|
+
const t = useT()
|
|
239
|
+
const [accessibleAgentIds, setAccessibleAgentIds] = React.useState<Set<string> | null>(null)
|
|
240
|
+
const declaredAgents = React.useMemo(
|
|
241
|
+
() => [
|
|
242
|
+
{
|
|
243
|
+
id: MERCHANDISING_AGENT_ID,
|
|
244
|
+
label: t(
|
|
245
|
+
'catalog.merchandising_assistant.agents.merchandising.label',
|
|
246
|
+
'Merchandising Assistant',
|
|
247
|
+
),
|
|
248
|
+
description: t(
|
|
249
|
+
'catalog.merchandising_assistant.agents.merchandising.description',
|
|
250
|
+
'Draft copy, normalize attributes, and propose price changes for the current selection.',
|
|
251
|
+
),
|
|
252
|
+
icon: <Sparkles className="size-4" />,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
[t],
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
React.useEffect(() => {
|
|
259
|
+
let cancelled = false
|
|
260
|
+
apiCall<AgentsResponse>('/api/ai_assistant/ai/agents', {
|
|
261
|
+
credentials: 'same-origin',
|
|
262
|
+
headers: { 'x-om-forbidden-redirect': '0', 'x-om-unauthorized-redirect': '0' },
|
|
263
|
+
})
|
|
264
|
+
.then((call) => {
|
|
265
|
+
if (cancelled) return
|
|
266
|
+
if (!call.ok || !call.result || !Array.isArray(call.result.agents)) {
|
|
267
|
+
setAccessibleAgentIds(new Set())
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
setAccessibleAgentIds(
|
|
271
|
+
new Set(
|
|
272
|
+
call.result.agents
|
|
273
|
+
.map((agent) => agent?.id)
|
|
274
|
+
.filter((id): id is string => typeof id === 'string' && id.length > 0),
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
})
|
|
278
|
+
.catch(() => {
|
|
279
|
+
if (!cancelled) setAccessibleAgentIds(new Set())
|
|
280
|
+
})
|
|
281
|
+
return () => {
|
|
282
|
+
cancelled = true
|
|
283
|
+
}
|
|
284
|
+
}, [])
|
|
285
|
+
|
|
286
|
+
return React.useMemo(
|
|
287
|
+
() => ({
|
|
288
|
+
agents:
|
|
289
|
+
accessibleAgentIds === null
|
|
290
|
+
? []
|
|
291
|
+
: declaredAgents.filter((agent) => accessibleAgentIds.has(agent.id)),
|
|
292
|
+
loaded: accessibleAgentIds !== null,
|
|
293
|
+
}),
|
|
294
|
+
[accessibleAgentIds, declaredAgents],
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function MerchandisingAssistantSheet({
|
|
299
|
+
pageContext,
|
|
300
|
+
enabled = true,
|
|
301
|
+
className,
|
|
302
|
+
}: MerchandisingAssistantSheetProps): React.ReactElement | null {
|
|
303
|
+
const t = useT()
|
|
304
|
+
const dock = useAiDock()
|
|
305
|
+
const [open, setOpen] = React.useState(false)
|
|
306
|
+
const [popoverOpen, setPopoverOpen] = React.useState(false)
|
|
307
|
+
const [activeAgent, setActiveAgent] = React.useState<string>(MERCHANDISING_AGENT_ID)
|
|
308
|
+
const [lastAgent, setLastAgent] = React.useState<string | null>(null)
|
|
309
|
+
|
|
310
|
+
const selectedCount = pageContext.extra.selectedCount
|
|
311
|
+
const hasSelection = selectedCount > 0
|
|
312
|
+
const suggestions = useMerchandisingSuggestions(hasSelection, selectedCount)
|
|
313
|
+
const contextItems = useContextItems(pageContext)
|
|
314
|
+
const { agents, loaded: agentsLoaded } = useMerchandisingAgents()
|
|
315
|
+
|
|
316
|
+
if (!enabled || !agentsLoaded || agents.length === 0) return null
|
|
317
|
+
|
|
318
|
+
const openAgent = (agentId: string) => {
|
|
319
|
+
setActiveAgent(agentId)
|
|
320
|
+
setLastAgent(agentId)
|
|
321
|
+
setPopoverOpen(false)
|
|
322
|
+
if (dock.state.assistant?.agent === agentId) {
|
|
323
|
+
dock.dock(dock.state.assistant)
|
|
324
|
+
setOpen(false)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
setOpen(true)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const handleSelectAgent = (agentId: string) => {
|
|
331
|
+
openAgent(agentId)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const handleMainTriggerClick = () => {
|
|
335
|
+
if (agents.length === 1) {
|
|
336
|
+
openAgent(agents[0].id)
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
if (lastAgent && agents.some((a) => a.id === lastAgent)) {
|
|
340
|
+
openAgent(lastAgent)
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
setPopoverOpen(true)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const handleDock = () => {
|
|
347
|
+
const agent = agents.find((a) => a.id === activeAgent) ?? agents[0]
|
|
348
|
+
if (!agent) return
|
|
349
|
+
dock.dock({
|
|
350
|
+
agent: agent.id,
|
|
351
|
+
label: agent.label,
|
|
352
|
+
description: t('catalog.merchandising_assistant.dock.subtitle', 'Catalog'),
|
|
353
|
+
pageContext: pageContext as unknown as Record<string, unknown>,
|
|
354
|
+
placeholder: t(
|
|
355
|
+
'catalog.merchandising_assistant.sheet.composerPlaceholder',
|
|
356
|
+
'Ask for descriptions, attributes, titles, or price ideas...',
|
|
357
|
+
),
|
|
358
|
+
suggestions,
|
|
359
|
+
contextItems,
|
|
360
|
+
welcomeTitle: t(
|
|
361
|
+
'catalog.merchandising_assistant.sheet.welcomeTitle',
|
|
362
|
+
'Merchandising Assistant',
|
|
363
|
+
),
|
|
364
|
+
welcomeDescription: hasSelection
|
|
365
|
+
? t(
|
|
366
|
+
'catalog.merchandising_assistant.sheet.welcomeDescriptionSelection',
|
|
367
|
+
'Ready to work with your {count} selected products. Try one of these:',
|
|
368
|
+
).replace('{count}', String(selectedCount))
|
|
369
|
+
: t(
|
|
370
|
+
'catalog.merchandising_assistant.sheet.welcomeDescriptionAll',
|
|
371
|
+
'Select products for targeted actions, or explore your catalog:',
|
|
372
|
+
),
|
|
373
|
+
})
|
|
374
|
+
setOpen(false)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const triggerLabel = t(
|
|
378
|
+
'catalog.merchandising_assistant.trigger.ariaLabel',
|
|
379
|
+
'Open AI merchandising assistant',
|
|
380
|
+
)
|
|
381
|
+
const labelText = t('catalog.merchandising_assistant.trigger.label', 'AI')
|
|
382
|
+
const moreAgentsLabel = t(
|
|
383
|
+
'catalog.merchandising_assistant.trigger.moreAgentsAriaLabel',
|
|
384
|
+
'Choose an AI assistant',
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<>
|
|
389
|
+
<div className={cn('inline-flex items-center', className)}>
|
|
390
|
+
<Button
|
|
391
|
+
type="button"
|
|
392
|
+
variant="outline"
|
|
393
|
+
onClick={handleMainTriggerClick}
|
|
394
|
+
data-ai-merchandising-trigger=""
|
|
395
|
+
aria-label={triggerLabel}
|
|
396
|
+
title={triggerLabel}
|
|
397
|
+
className={cn(
|
|
398
|
+
'relative',
|
|
399
|
+
agents.length > 1 && 'rounded-r-none border-r-0',
|
|
400
|
+
)}
|
|
401
|
+
>
|
|
402
|
+
<Sparkles className="size-4" aria-hidden />
|
|
403
|
+
<span>{labelText}</span>
|
|
404
|
+
{hasSelection ? (
|
|
405
|
+
<span
|
|
406
|
+
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"
|
|
407
|
+
data-ai-merchandising-selected-count={selectedCount}
|
|
408
|
+
>
|
|
409
|
+
{selectedCount}
|
|
410
|
+
</span>
|
|
411
|
+
) : null}
|
|
412
|
+
</Button>
|
|
413
|
+
{agents.length > 1 ? (
|
|
414
|
+
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
|
415
|
+
<PopoverTrigger asChild>
|
|
416
|
+
<IconButton
|
|
417
|
+
type="button"
|
|
418
|
+
variant="outline"
|
|
419
|
+
size="lg"
|
|
420
|
+
aria-label={moreAgentsLabel}
|
|
421
|
+
title={moreAgentsLabel}
|
|
422
|
+
className="rounded-l-none"
|
|
423
|
+
data-ai-merchandising-picker=""
|
|
424
|
+
>
|
|
425
|
+
<ChevronDown className="size-4" aria-hidden />
|
|
426
|
+
</IconButton>
|
|
427
|
+
</PopoverTrigger>
|
|
428
|
+
<PopoverContent align="end" className="w-72 p-1">
|
|
429
|
+
<div className="px-3 pt-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
430
|
+
{t('catalog.merchandising_assistant.popover.heading', 'AI assistants')}
|
|
431
|
+
</div>
|
|
432
|
+
<div className="flex flex-col gap-0.5">
|
|
433
|
+
{agents.map((agent) => (
|
|
434
|
+
<button
|
|
435
|
+
key={agent.id}
|
|
436
|
+
type="button"
|
|
437
|
+
onClick={() => handleSelectAgent(agent.id)}
|
|
438
|
+
data-ai-merchandising-agent-option={agent.id}
|
|
439
|
+
className="flex items-start gap-2 rounded-sm px-2 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none"
|
|
440
|
+
>
|
|
441
|
+
<span className="mt-0.5 inline-flex size-6 items-center justify-center rounded-full bg-secondary text-secondary-foreground">
|
|
442
|
+
{agent.icon}
|
|
443
|
+
</span>
|
|
444
|
+
<span className="flex-1 min-w-0">
|
|
445
|
+
<span className="block font-medium leading-tight">{agent.label}</span>
|
|
446
|
+
<span className="block text-xs text-muted-foreground leading-snug">
|
|
447
|
+
{agent.description}
|
|
448
|
+
</span>
|
|
449
|
+
</span>
|
|
450
|
+
</button>
|
|
451
|
+
))}
|
|
452
|
+
</div>
|
|
453
|
+
</PopoverContent>
|
|
454
|
+
</Popover>
|
|
455
|
+
) : null}
|
|
456
|
+
</div>
|
|
457
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
458
|
+
<DialogContent
|
|
459
|
+
className={cn(
|
|
460
|
+
// Mobile: full-screen sheet. Desktop (≥sm): right-anchored side sheet.
|
|
461
|
+
// The Dialog primitive applies a centering transform at the
|
|
462
|
+
// sm breakpoint; each piece (`top`, `left`, transform, inset)
|
|
463
|
+
// must be overridden at the same breakpoint or the panel
|
|
464
|
+
// renders half off the viewport.
|
|
465
|
+
'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',
|
|
466
|
+
'sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',
|
|
467
|
+
'sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen',
|
|
468
|
+
'flex flex-col gap-3 p-4 z-[70]',
|
|
469
|
+
)}
|
|
470
|
+
data-ai-merchandising-sheet=""
|
|
471
|
+
>
|
|
472
|
+
<DialogHeader>
|
|
473
|
+
<div className="flex items-center gap-3 pr-8">
|
|
474
|
+
{/* Dock button lives on the LEFT — the Dialog primitive
|
|
475
|
+
auto-renders an X close button absolutely positioned in
|
|
476
|
+
the top-right corner, so anything we drop in the header's
|
|
477
|
+
right side visually collides with it. Mobile hides the
|
|
478
|
+
dock entirely (the side panel is desktop-only). */}
|
|
479
|
+
<IconButton
|
|
480
|
+
type="button"
|
|
481
|
+
variant="ghost"
|
|
482
|
+
size="sm"
|
|
483
|
+
aria-label={t('catalog.merchandising_assistant.sheet.dock', 'Dock to side')}
|
|
484
|
+
title={t('catalog.merchandising_assistant.sheet.dock', 'Dock to side')}
|
|
485
|
+
onClick={handleDock}
|
|
486
|
+
data-ai-merchandising-dock=""
|
|
487
|
+
className="hidden lg:inline-flex shrink-0"
|
|
488
|
+
>
|
|
489
|
+
<PanelRightOpen className="size-4" aria-hidden />
|
|
490
|
+
</IconButton>
|
|
491
|
+
<DialogTitle className="flex-1 min-w-0 truncate">
|
|
492
|
+
{t('catalog.merchandising_assistant.sheet.title', 'Catalog merchandising assistant')}
|
|
493
|
+
</DialogTitle>
|
|
494
|
+
{hasSelection ? (
|
|
495
|
+
<span
|
|
496
|
+
className="shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
497
|
+
data-ai-merchandising-selection-pill=""
|
|
498
|
+
data-ai-merchandising-selected-count={selectedCount}
|
|
499
|
+
>
|
|
500
|
+
{t(
|
|
501
|
+
'catalog.merchandising_assistant.sheet.selectionPill',
|
|
502
|
+
'Acting on {count} products',
|
|
503
|
+
).replace('{count}', String(selectedCount))}
|
|
504
|
+
</span>
|
|
505
|
+
) : null}
|
|
506
|
+
</div>
|
|
507
|
+
<DialogDescription>
|
|
508
|
+
{hasSelection
|
|
509
|
+
? t(
|
|
510
|
+
'catalog.merchandising_assistant.sheet.descriptionWithSelection',
|
|
511
|
+
'Working with {count} selected products. Ask for descriptions, attribute extraction, title suggestions, or pricing analysis.',
|
|
512
|
+
).replace('{count}', String(selectedCount))
|
|
513
|
+
: t(
|
|
514
|
+
'catalog.merchandising_assistant.sheet.description',
|
|
515
|
+
'Your AI merchandising copilot. Select products from the list for targeted actions, or explore your full catalog.',
|
|
516
|
+
)}
|
|
517
|
+
</DialogDescription>
|
|
518
|
+
</DialogHeader>
|
|
519
|
+
<MerchandisingChatBody
|
|
520
|
+
activeAgent={activeAgent}
|
|
521
|
+
pageContext={pageContext}
|
|
522
|
+
suggestions={suggestions}
|
|
523
|
+
contextItems={contextItems}
|
|
524
|
+
hasSelection={hasSelection}
|
|
525
|
+
selectedCount={selectedCount}
|
|
526
|
+
/>
|
|
527
|
+
</DialogContent>
|
|
528
|
+
</Dialog>
|
|
529
|
+
</>
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
interface MerchandisingChatBodyProps {
|
|
534
|
+
activeAgent: string
|
|
535
|
+
pageContext: MerchandisingPageContext
|
|
536
|
+
suggestions: AiChatSuggestion[]
|
|
537
|
+
contextItems: AiChatContextItem[]
|
|
538
|
+
hasSelection: boolean
|
|
539
|
+
selectedCount: number
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function MerchandisingChatBody({
|
|
543
|
+
activeAgent,
|
|
544
|
+
pageContext,
|
|
545
|
+
suggestions,
|
|
546
|
+
contextItems,
|
|
547
|
+
hasSelection,
|
|
548
|
+
selectedCount,
|
|
549
|
+
}: MerchandisingChatBodyProps) {
|
|
550
|
+
const t = useT()
|
|
551
|
+
const sessions = useAiChatSessions()
|
|
552
|
+
const session = sessions.getActiveSession(activeAgent)
|
|
553
|
+
|
|
554
|
+
React.useEffect(() => {
|
|
555
|
+
if (!session) sessions.ensureSession(activeAgent)
|
|
556
|
+
}, [activeAgent, session, sessions])
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<>
|
|
560
|
+
<ChatPaneTabs agentId={activeAgent} className="border-b" />
|
|
561
|
+
<div className="min-h-0 flex-1" data-ai-merchandising-chat-container="">
|
|
562
|
+
{session ? (
|
|
563
|
+
<AiChat
|
|
564
|
+
key={session.id}
|
|
565
|
+
agent={activeAgent}
|
|
566
|
+
conversationId={session.conversationId}
|
|
567
|
+
pageContext={pageContext as unknown as Record<string, unknown>}
|
|
568
|
+
className="h-full"
|
|
569
|
+
placeholder={t(
|
|
570
|
+
'catalog.merchandising_assistant.sheet.composerPlaceholder',
|
|
571
|
+
'Ask for descriptions, attributes, titles, or price ideas...',
|
|
572
|
+
)}
|
|
573
|
+
suggestions={suggestions}
|
|
574
|
+
contextItems={contextItems}
|
|
575
|
+
welcomeTitle={t(
|
|
576
|
+
'catalog.merchandising_assistant.sheet.welcomeTitle',
|
|
577
|
+
'Merchandising Assistant',
|
|
578
|
+
)}
|
|
579
|
+
welcomeDescription={
|
|
580
|
+
hasSelection
|
|
581
|
+
? t(
|
|
582
|
+
'catalog.merchandising_assistant.sheet.welcomeDescriptionSelection',
|
|
583
|
+
'Ready to work with your {count} selected products. Try one of these:',
|
|
584
|
+
).replace('{count}', String(selectedCount))
|
|
585
|
+
: t(
|
|
586
|
+
'catalog.merchandising_assistant.sheet.welcomeDescriptionAll',
|
|
587
|
+
'Select products for targeted actions, or explore your catalog:',
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
/>
|
|
591
|
+
) : null}
|
|
592
|
+
</div>
|
|
593
|
+
</>
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export default MerchandisingAssistantSheet
|