@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +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,466 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Boxes, ChevronDown, FileText, Package, PanelRightOpen, PenLine, Sparkles, Tags, TrendingUp } from "lucide-react";
|
|
5
|
+
import { AiChat } from "@open-mercato/ui/ai/AiChat";
|
|
6
|
+
import { useAiDock } from "@open-mercato/ui/ai/AiDock";
|
|
7
|
+
import { useAiChatSessions } from "@open-mercato/ui/ai/AiChatSessions";
|
|
8
|
+
import { ChatPaneTabs } from "@open-mercato/ui/ai/ChatPaneTabs";
|
|
9
|
+
import "../../../components/CatalogStatsCard.js";
|
|
10
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
12
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
13
|
+
import {
|
|
14
|
+
Dialog,
|
|
15
|
+
DialogContent,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
DialogHeader,
|
|
18
|
+
DialogTitle
|
|
19
|
+
} from "@open-mercato/ui/primitives/dialog";
|
|
20
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@open-mercato/ui/primitives/popover";
|
|
21
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
22
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
23
|
+
const MERCHANDISING_AGENT_ID = "catalog.merchandising_assistant";
|
|
24
|
+
function useMerchandisingSuggestions(hasSelection, selectedCount) {
|
|
25
|
+
const t = useT();
|
|
26
|
+
return React.useMemo(() => {
|
|
27
|
+
if (hasSelection) {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
label: t(
|
|
31
|
+
"catalog.merchandising_assistant.suggestions.draftDescriptions",
|
|
32
|
+
"Draft product descriptions for selected items"
|
|
33
|
+
),
|
|
34
|
+
prompt: `Draft compelling product descriptions for my ${selectedCount} selected products`,
|
|
35
|
+
icon: /* @__PURE__ */ jsx(PenLine, { className: "size-4" })
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: t(
|
|
39
|
+
"catalog.merchandising_assistant.suggestions.extractAttributes",
|
|
40
|
+
"Extract attributes from descriptions"
|
|
41
|
+
),
|
|
42
|
+
prompt: `Extract structured attributes from the descriptions of my ${selectedCount} selected products`,
|
|
43
|
+
icon: /* @__PURE__ */ jsx(Tags, { className: "size-4" })
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: t(
|
|
47
|
+
"catalog.merchandising_assistant.suggestions.titleVariants",
|
|
48
|
+
"Generate title variants for SEO"
|
|
49
|
+
),
|
|
50
|
+
prompt: `Generate SEO-optimized title variants for my ${selectedCount} selected products`,
|
|
51
|
+
icon: /* @__PURE__ */ jsx(FileText, { className: "size-4" })
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: t(
|
|
55
|
+
"catalog.merchandising_assistant.suggestions.priceAdjustments",
|
|
56
|
+
"Suggest price adjustments"
|
|
57
|
+
),
|
|
58
|
+
prompt: `Analyze and suggest price adjustments for my ${selectedCount} selected products`,
|
|
59
|
+
icon: /* @__PURE__ */ jsx(TrendingUp, { className: "size-4" })
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
label: t(
|
|
66
|
+
"catalog.merchandising_assistant.suggestions.showStats",
|
|
67
|
+
"Show catalog overview"
|
|
68
|
+
),
|
|
69
|
+
// Triggers the `catalog.show_stats` tool, which returns the inline
|
|
70
|
+
// catalog-stats UI part (live counts of products, active products,
|
|
71
|
+
// categories, tags). Demo entry-point for the dynamic UI-part path.
|
|
72
|
+
prompt: "Show me a quick catalog overview using the stats card.",
|
|
73
|
+
icon: /* @__PURE__ */ jsx(Boxes, { className: "size-4" })
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: t(
|
|
77
|
+
"catalog.merchandising_assistant.suggestions.browseProducts",
|
|
78
|
+
"Show me an overview of my product catalog"
|
|
79
|
+
),
|
|
80
|
+
prompt: "Give me an overview of my product catalog \u2014 categories, total products, and pricing ranges",
|
|
81
|
+
icon: /* @__PURE__ */ jsx(Package, { className: "size-4" })
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: t(
|
|
85
|
+
"catalog.merchandising_assistant.suggestions.findMissingDescriptions",
|
|
86
|
+
"Find products with missing descriptions"
|
|
87
|
+
),
|
|
88
|
+
prompt: "Find products that are missing descriptions or have very short descriptions",
|
|
89
|
+
icon: /* @__PURE__ */ jsx(PenLine, { className: "size-4" })
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: t(
|
|
93
|
+
"catalog.merchandising_assistant.suggestions.analyzeAttributes",
|
|
94
|
+
"Analyze attribute coverage"
|
|
95
|
+
),
|
|
96
|
+
prompt: "Analyze which products have incomplete attribute data",
|
|
97
|
+
icon: /* @__PURE__ */ jsx(Tags, { className: "size-4" })
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: t(
|
|
101
|
+
"catalog.merchandising_assistant.suggestions.pricingOverview",
|
|
102
|
+
"Show pricing distribution"
|
|
103
|
+
),
|
|
104
|
+
prompt: "Show me the pricing distribution across categories",
|
|
105
|
+
icon: /* @__PURE__ */ jsx(TrendingUp, { className: "size-4" })
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
}, [hasSelection, selectedCount, t]);
|
|
109
|
+
}
|
|
110
|
+
function useContextItems(pageContext) {
|
|
111
|
+
const t = useT();
|
|
112
|
+
return React.useMemo(() => {
|
|
113
|
+
const items = [];
|
|
114
|
+
const { selectedCount, totalMatching, filter } = pageContext.extra;
|
|
115
|
+
if (selectedCount > 0) {
|
|
116
|
+
items.push({
|
|
117
|
+
label: t(
|
|
118
|
+
"catalog.merchandising_assistant.context.selectedProducts",
|
|
119
|
+
"{count} products selected"
|
|
120
|
+
).replace("{count}", String(selectedCount))
|
|
121
|
+
});
|
|
122
|
+
} else if (totalMatching > 0) {
|
|
123
|
+
items.push({
|
|
124
|
+
label: t(
|
|
125
|
+
"catalog.merchandising_assistant.context.matchingProducts",
|
|
126
|
+
"{count} products in view"
|
|
127
|
+
).replace("{count}", String(totalMatching))
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (filter.categoryId) {
|
|
131
|
+
items.push({
|
|
132
|
+
label: t("catalog.merchandising_assistant.context.filteredByCategory", "Filtered by category"),
|
|
133
|
+
detail: filter.categoryId
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (filter.status) {
|
|
137
|
+
items.push({ label: filter.status });
|
|
138
|
+
}
|
|
139
|
+
if (filter.tags.length > 0) {
|
|
140
|
+
items.push({
|
|
141
|
+
label: t("catalog.merchandising_assistant.context.tags", "{count} tags").replace(
|
|
142
|
+
"{count}",
|
|
143
|
+
String(filter.tags.length)
|
|
144
|
+
)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return items;
|
|
148
|
+
}, [pageContext, t]);
|
|
149
|
+
}
|
|
150
|
+
function useMerchandisingAgents() {
|
|
151
|
+
const t = useT();
|
|
152
|
+
const [accessibleAgentIds, setAccessibleAgentIds] = React.useState(null);
|
|
153
|
+
const declaredAgents = React.useMemo(
|
|
154
|
+
() => [
|
|
155
|
+
{
|
|
156
|
+
id: MERCHANDISING_AGENT_ID,
|
|
157
|
+
label: t(
|
|
158
|
+
"catalog.merchandising_assistant.agents.merchandising.label",
|
|
159
|
+
"Merchandising Assistant"
|
|
160
|
+
),
|
|
161
|
+
description: t(
|
|
162
|
+
"catalog.merchandising_assistant.agents.merchandising.description",
|
|
163
|
+
"Draft copy, normalize attributes, and propose price changes for the current selection."
|
|
164
|
+
),
|
|
165
|
+
icon: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" })
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
[t]
|
|
169
|
+
);
|
|
170
|
+
React.useEffect(() => {
|
|
171
|
+
let cancelled = false;
|
|
172
|
+
apiCall("/api/ai_assistant/ai/agents", {
|
|
173
|
+
credentials: "same-origin",
|
|
174
|
+
headers: { "x-om-forbidden-redirect": "0", "x-om-unauthorized-redirect": "0" }
|
|
175
|
+
}).then((call) => {
|
|
176
|
+
if (cancelled) return;
|
|
177
|
+
if (!call.ok || !call.result || !Array.isArray(call.result.agents)) {
|
|
178
|
+
setAccessibleAgentIds(/* @__PURE__ */ new Set());
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setAccessibleAgentIds(
|
|
182
|
+
new Set(
|
|
183
|
+
call.result.agents.map((agent) => agent?.id).filter((id) => typeof id === "string" && id.length > 0)
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}).catch(() => {
|
|
187
|
+
if (!cancelled) setAccessibleAgentIds(/* @__PURE__ */ new Set());
|
|
188
|
+
});
|
|
189
|
+
return () => {
|
|
190
|
+
cancelled = true;
|
|
191
|
+
};
|
|
192
|
+
}, []);
|
|
193
|
+
return React.useMemo(
|
|
194
|
+
() => ({
|
|
195
|
+
agents: accessibleAgentIds === null ? [] : declaredAgents.filter((agent) => accessibleAgentIds.has(agent.id)),
|
|
196
|
+
loaded: accessibleAgentIds !== null
|
|
197
|
+
}),
|
|
198
|
+
[accessibleAgentIds, declaredAgents]
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function MerchandisingAssistantSheet({
|
|
202
|
+
pageContext,
|
|
203
|
+
enabled = true,
|
|
204
|
+
className
|
|
205
|
+
}) {
|
|
206
|
+
const t = useT();
|
|
207
|
+
const dock = useAiDock();
|
|
208
|
+
const [open, setOpen] = React.useState(false);
|
|
209
|
+
const [popoverOpen, setPopoverOpen] = React.useState(false);
|
|
210
|
+
const [activeAgent, setActiveAgent] = React.useState(MERCHANDISING_AGENT_ID);
|
|
211
|
+
const [lastAgent, setLastAgent] = React.useState(null);
|
|
212
|
+
const selectedCount = pageContext.extra.selectedCount;
|
|
213
|
+
const hasSelection = selectedCount > 0;
|
|
214
|
+
const suggestions = useMerchandisingSuggestions(hasSelection, selectedCount);
|
|
215
|
+
const contextItems = useContextItems(pageContext);
|
|
216
|
+
const { agents, loaded: agentsLoaded } = useMerchandisingAgents();
|
|
217
|
+
if (!enabled || !agentsLoaded || agents.length === 0) return null;
|
|
218
|
+
const openAgent = (agentId) => {
|
|
219
|
+
setActiveAgent(agentId);
|
|
220
|
+
setLastAgent(agentId);
|
|
221
|
+
setPopoverOpen(false);
|
|
222
|
+
if (dock.state.assistant?.agent === agentId) {
|
|
223
|
+
dock.dock(dock.state.assistant);
|
|
224
|
+
setOpen(false);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
setOpen(true);
|
|
228
|
+
};
|
|
229
|
+
const handleSelectAgent = (agentId) => {
|
|
230
|
+
openAgent(agentId);
|
|
231
|
+
};
|
|
232
|
+
const handleMainTriggerClick = () => {
|
|
233
|
+
if (agents.length === 1) {
|
|
234
|
+
openAgent(agents[0].id);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (lastAgent && agents.some((a) => a.id === lastAgent)) {
|
|
238
|
+
openAgent(lastAgent);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
setPopoverOpen(true);
|
|
242
|
+
};
|
|
243
|
+
const handleDock = () => {
|
|
244
|
+
const agent = agents.find((a) => a.id === activeAgent) ?? agents[0];
|
|
245
|
+
if (!agent) return;
|
|
246
|
+
dock.dock({
|
|
247
|
+
agent: agent.id,
|
|
248
|
+
label: agent.label,
|
|
249
|
+
description: t("catalog.merchandising_assistant.dock.subtitle", "Catalog"),
|
|
250
|
+
pageContext,
|
|
251
|
+
placeholder: t(
|
|
252
|
+
"catalog.merchandising_assistant.sheet.composerPlaceholder",
|
|
253
|
+
"Ask for descriptions, attributes, titles, or price ideas..."
|
|
254
|
+
),
|
|
255
|
+
suggestions,
|
|
256
|
+
contextItems,
|
|
257
|
+
welcomeTitle: t(
|
|
258
|
+
"catalog.merchandising_assistant.sheet.welcomeTitle",
|
|
259
|
+
"Merchandising Assistant"
|
|
260
|
+
),
|
|
261
|
+
welcomeDescription: hasSelection ? t(
|
|
262
|
+
"catalog.merchandising_assistant.sheet.welcomeDescriptionSelection",
|
|
263
|
+
"Ready to work with your {count} selected products. Try one of these:"
|
|
264
|
+
).replace("{count}", String(selectedCount)) : t(
|
|
265
|
+
"catalog.merchandising_assistant.sheet.welcomeDescriptionAll",
|
|
266
|
+
"Select products for targeted actions, or explore your catalog:"
|
|
267
|
+
)
|
|
268
|
+
});
|
|
269
|
+
setOpen(false);
|
|
270
|
+
};
|
|
271
|
+
const triggerLabel = t(
|
|
272
|
+
"catalog.merchandising_assistant.trigger.ariaLabel",
|
|
273
|
+
"Open AI merchandising assistant"
|
|
274
|
+
);
|
|
275
|
+
const labelText = t("catalog.merchandising_assistant.trigger.label", "AI");
|
|
276
|
+
const moreAgentsLabel = t(
|
|
277
|
+
"catalog.merchandising_assistant.trigger.moreAgentsAriaLabel",
|
|
278
|
+
"Choose an AI assistant"
|
|
279
|
+
);
|
|
280
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
281
|
+
/* @__PURE__ */ jsxs("div", { className: cn("inline-flex items-center", className), children: [
|
|
282
|
+
/* @__PURE__ */ jsxs(
|
|
283
|
+
Button,
|
|
284
|
+
{
|
|
285
|
+
type: "button",
|
|
286
|
+
variant: "outline",
|
|
287
|
+
onClick: handleMainTriggerClick,
|
|
288
|
+
"data-ai-merchandising-trigger": "",
|
|
289
|
+
"aria-label": triggerLabel,
|
|
290
|
+
title: triggerLabel,
|
|
291
|
+
className: cn(
|
|
292
|
+
"relative",
|
|
293
|
+
agents.length > 1 && "rounded-r-none border-r-0"
|
|
294
|
+
),
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "size-4", "aria-hidden": true }),
|
|
297
|
+
/* @__PURE__ */ jsx("span", { children: labelText }),
|
|
298
|
+
hasSelection ? /* @__PURE__ */ jsx(
|
|
299
|
+
"span",
|
|
300
|
+
{
|
|
301
|
+
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",
|
|
302
|
+
"data-ai-merchandising-selected-count": selectedCount,
|
|
303
|
+
children: selectedCount
|
|
304
|
+
}
|
|
305
|
+
) : null
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
),
|
|
309
|
+
agents.length > 1 ? /* @__PURE__ */ jsxs(Popover, { open: popoverOpen, onOpenChange: setPopoverOpen, children: [
|
|
310
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
311
|
+
IconButton,
|
|
312
|
+
{
|
|
313
|
+
type: "button",
|
|
314
|
+
variant: "outline",
|
|
315
|
+
size: "lg",
|
|
316
|
+
"aria-label": moreAgentsLabel,
|
|
317
|
+
title: moreAgentsLabel,
|
|
318
|
+
className: "rounded-l-none",
|
|
319
|
+
"data-ai-merchandising-picker": "",
|
|
320
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4", "aria-hidden": true })
|
|
321
|
+
}
|
|
322
|
+
) }),
|
|
323
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-72 p-1", children: [
|
|
324
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 pt-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("catalog.merchandising_assistant.popover.heading", "AI assistants") }),
|
|
325
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: agents.map((agent) => /* @__PURE__ */ jsxs(
|
|
326
|
+
"button",
|
|
327
|
+
{
|
|
328
|
+
type: "button",
|
|
329
|
+
onClick: () => handleSelectAgent(agent.id),
|
|
330
|
+
"data-ai-merchandising-agent-option": agent.id,
|
|
331
|
+
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",
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ jsx("span", { className: "mt-0.5 inline-flex size-6 items-center justify-center rounded-full bg-secondary text-secondary-foreground", children: agent.icon }),
|
|
334
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0", children: [
|
|
335
|
+
/* @__PURE__ */ jsx("span", { className: "block font-medium leading-tight", children: agent.label }),
|
|
336
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground leading-snug", children: agent.description })
|
|
337
|
+
] })
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
agent.id
|
|
341
|
+
)) })
|
|
342
|
+
] })
|
|
343
|
+
] }) : null
|
|
344
|
+
] }),
|
|
345
|
+
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(
|
|
346
|
+
DialogContent,
|
|
347
|
+
{
|
|
348
|
+
className: cn(
|
|
349
|
+
// Mobile: full-screen sheet. Desktop (≥sm): right-anchored side sheet.
|
|
350
|
+
// The Dialog primitive applies a centering transform at the
|
|
351
|
+
// sm breakpoint; each piece (`top`, `left`, transform, inset)
|
|
352
|
+
// must be overridden at the same breakpoint or the panel
|
|
353
|
+
// renders half off the viewport.
|
|
354
|
+
"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",
|
|
355
|
+
"sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0",
|
|
356
|
+
"sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen",
|
|
357
|
+
"flex flex-col gap-3 p-4 z-[70]"
|
|
358
|
+
),
|
|
359
|
+
"data-ai-merchandising-sheet": "",
|
|
360
|
+
children: [
|
|
361
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
362
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pr-8", children: [
|
|
363
|
+
/* @__PURE__ */ jsx(
|
|
364
|
+
IconButton,
|
|
365
|
+
{
|
|
366
|
+
type: "button",
|
|
367
|
+
variant: "ghost",
|
|
368
|
+
size: "sm",
|
|
369
|
+
"aria-label": t("catalog.merchandising_assistant.sheet.dock", "Dock to side"),
|
|
370
|
+
title: t("catalog.merchandising_assistant.sheet.dock", "Dock to side"),
|
|
371
|
+
onClick: handleDock,
|
|
372
|
+
"data-ai-merchandising-dock": "",
|
|
373
|
+
className: "hidden lg:inline-flex shrink-0",
|
|
374
|
+
children: /* @__PURE__ */ jsx(PanelRightOpen, { className: "size-4", "aria-hidden": true })
|
|
375
|
+
}
|
|
376
|
+
),
|
|
377
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "flex-1 min-w-0 truncate", children: t("catalog.merchandising_assistant.sheet.title", "Catalog merchandising assistant") }),
|
|
378
|
+
hasSelection ? /* @__PURE__ */ jsx(
|
|
379
|
+
"span",
|
|
380
|
+
{
|
|
381
|
+
className: "shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground",
|
|
382
|
+
"data-ai-merchandising-selection-pill": "",
|
|
383
|
+
"data-ai-merchandising-selected-count": selectedCount,
|
|
384
|
+
children: t(
|
|
385
|
+
"catalog.merchandising_assistant.sheet.selectionPill",
|
|
386
|
+
"Acting on {count} products"
|
|
387
|
+
).replace("{count}", String(selectedCount))
|
|
388
|
+
}
|
|
389
|
+
) : null
|
|
390
|
+
] }),
|
|
391
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: hasSelection ? t(
|
|
392
|
+
"catalog.merchandising_assistant.sheet.descriptionWithSelection",
|
|
393
|
+
"Working with {count} selected products. Ask for descriptions, attribute extraction, title suggestions, or pricing analysis."
|
|
394
|
+
).replace("{count}", String(selectedCount)) : t(
|
|
395
|
+
"catalog.merchandising_assistant.sheet.description",
|
|
396
|
+
"Your AI merchandising copilot. Select products from the list for targeted actions, or explore your full catalog."
|
|
397
|
+
) })
|
|
398
|
+
] }),
|
|
399
|
+
/* @__PURE__ */ jsx(
|
|
400
|
+
MerchandisingChatBody,
|
|
401
|
+
{
|
|
402
|
+
activeAgent,
|
|
403
|
+
pageContext,
|
|
404
|
+
suggestions,
|
|
405
|
+
contextItems,
|
|
406
|
+
hasSelection,
|
|
407
|
+
selectedCount
|
|
408
|
+
}
|
|
409
|
+
)
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
) })
|
|
413
|
+
] });
|
|
414
|
+
}
|
|
415
|
+
function MerchandisingChatBody({
|
|
416
|
+
activeAgent,
|
|
417
|
+
pageContext,
|
|
418
|
+
suggestions,
|
|
419
|
+
contextItems,
|
|
420
|
+
hasSelection,
|
|
421
|
+
selectedCount
|
|
422
|
+
}) {
|
|
423
|
+
const t = useT();
|
|
424
|
+
const sessions = useAiChatSessions();
|
|
425
|
+
const session = sessions.getActiveSession(activeAgent);
|
|
426
|
+
React.useEffect(() => {
|
|
427
|
+
if (!session) sessions.ensureSession(activeAgent);
|
|
428
|
+
}, [activeAgent, session, sessions]);
|
|
429
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
430
|
+
/* @__PURE__ */ jsx(ChatPaneTabs, { agentId: activeAgent, className: "border-b" }),
|
|
431
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", "data-ai-merchandising-chat-container": "", children: session ? /* @__PURE__ */ jsx(
|
|
432
|
+
AiChat,
|
|
433
|
+
{
|
|
434
|
+
agent: activeAgent,
|
|
435
|
+
conversationId: session.conversationId,
|
|
436
|
+
pageContext,
|
|
437
|
+
className: "h-full",
|
|
438
|
+
placeholder: t(
|
|
439
|
+
"catalog.merchandising_assistant.sheet.composerPlaceholder",
|
|
440
|
+
"Ask for descriptions, attributes, titles, or price ideas..."
|
|
441
|
+
),
|
|
442
|
+
suggestions,
|
|
443
|
+
contextItems,
|
|
444
|
+
welcomeTitle: t(
|
|
445
|
+
"catalog.merchandising_assistant.sheet.welcomeTitle",
|
|
446
|
+
"Merchandising Assistant"
|
|
447
|
+
),
|
|
448
|
+
welcomeDescription: hasSelection ? t(
|
|
449
|
+
"catalog.merchandising_assistant.sheet.welcomeDescriptionSelection",
|
|
450
|
+
"Ready to work with your {count} selected products. Try one of these:"
|
|
451
|
+
).replace("{count}", String(selectedCount)) : t(
|
|
452
|
+
"catalog.merchandising_assistant.sheet.welcomeDescriptionAll",
|
|
453
|
+
"Select products for targeted actions, or explore your catalog:"
|
|
454
|
+
)
|
|
455
|
+
},
|
|
456
|
+
session.id
|
|
457
|
+
) : null })
|
|
458
|
+
] });
|
|
459
|
+
}
|
|
460
|
+
var MerchandisingAssistantSheet_default = MerchandisingAssistantSheet;
|
|
461
|
+
export {
|
|
462
|
+
MERCHANDISING_AGENT_ID,
|
|
463
|
+
MerchandisingAssistantSheet,
|
|
464
|
+
MerchandisingAssistantSheet_default as default
|
|
465
|
+
};
|
|
466
|
+
//# sourceMappingURL=MerchandisingAssistantSheet.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\n/**\n * MerchandisingAssistantSheet \u2014 Step 4.9 (Spec \u00A710 D18).\n *\n * Embeds `<AiChat agent=\"catalog.merchandising_assistant\" pageContext={...} />`\n * in a right-side sheet (built on the shared Dialog primitive because\n * `packages/ui` does not ship a dedicated Sheet/Drawer primitive in\n * Phase 2). The trigger is a button rendered in the products-list page\n * header.\n *\n * Phase 2 is strictly read-only: the sheet shows proposals (structured\n * output), but the mutation tools (`catalog.update_product`,\n * `catalog.bulk_update_products`, `catalog.apply_attribute_extraction`,\n * `catalog.update_product_media_descriptions`) are intentionally NOT in\n * the agent whitelist. Phase 5.14 introduces those via the pending-action\n * contract.\n *\n * pageContext follows spec \u00A710.1 exactly:\n *\n * {\n * view: 'catalog.products.list',\n * recordType: null,\n * recordId: string, // \"\" or comma-separated UUIDs\n * extra: {\n * filter: { categoryId, priceRange, tags, status },\n * totalMatching: number,\n * selectedCount: number,\n * }\n * }\n */\n\nimport * as React from 'react'\nimport { Boxes, ChevronDown, FileText, Package, PanelRightOpen, PenLine, Sparkles, Tags, TrendingUp } from 'lucide-react'\nimport { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'\nimport { useAiDock } from '@open-mercato/ui/ai/AiDock'\nimport { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'\nimport { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'\n// Side-effect import: registers the `catalog.stats-card` UI part on the\n// global registry the first time this client bundle loads. Tools that\n// emit `{ uiPart: { componentId: 'catalog.stats-card' } }` envelopes\n// (catalog.show_stats today; user-defined tools tomorrow) automatically\n// resolve to the card without dispatcher changes.\nimport '../../../components/CatalogStatsCard'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport interface MerchandisingPageContextFilter {\n categoryId: string | null\n priceRange: { min?: number; max?: number } | null\n tags: string[]\n status: string | null\n}\n\nexport interface MerchandisingPageContext {\n view: 'catalog.products.list'\n entityType?: 'catalog.products.list'\n recordType: null\n recordId: string\n extra: {\n filter: MerchandisingPageContextFilter\n totalMatching: number\n selectedCount: number\n }\n}\n\nexport interface MerchandisingAssistantSheetProps {\n /** Selection-aware page context, built by the products list host. */\n pageContext: MerchandisingPageContext\n /** When false (feature-gated by the host), the sheet renders nothing. */\n enabled?: boolean\n className?: string\n}\n\nexport const MERCHANDISING_AGENT_ID = 'catalog.merchandising_assistant'\n\nfunction useMerchandisingSuggestions(\n hasSelection: boolean,\n selectedCount: number,\n): AiChatSuggestion[] {\n const t = useT()\n return React.useMemo(() => {\n if (hasSelection) {\n return [\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.draftDescriptions',\n 'Draft product descriptions for selected items',\n ),\n prompt: `Draft compelling product descriptions for my ${selectedCount} selected products`,\n icon: <PenLine className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.extractAttributes',\n 'Extract attributes from descriptions',\n ),\n prompt: `Extract structured attributes from the descriptions of my ${selectedCount} selected products`,\n icon: <Tags className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.titleVariants',\n 'Generate title variants for SEO',\n ),\n prompt: `Generate SEO-optimized title variants for my ${selectedCount} selected products`,\n icon: <FileText className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.priceAdjustments',\n 'Suggest price adjustments',\n ),\n prompt: `Analyze and suggest price adjustments for my ${selectedCount} selected products`,\n icon: <TrendingUp className=\"size-4\" />,\n },\n ]\n }\n return [\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.showStats',\n 'Show catalog overview',\n ),\n // Triggers the `catalog.show_stats` tool, which returns the inline\n // catalog-stats UI part (live counts of products, active products,\n // categories, tags). Demo entry-point for the dynamic UI-part path.\n prompt: 'Show me a quick catalog overview using the stats card.',\n icon: <Boxes className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.browseProducts',\n 'Show me an overview of my product catalog',\n ),\n prompt: 'Give me an overview of my product catalog \u2014 categories, total products, and pricing ranges',\n icon: <Package className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.findMissingDescriptions',\n 'Find products with missing descriptions',\n ),\n prompt: 'Find products that are missing descriptions or have very short descriptions',\n icon: <PenLine className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.analyzeAttributes',\n 'Analyze attribute coverage',\n ),\n prompt: 'Analyze which products have incomplete attribute data',\n icon: <Tags className=\"size-4\" />,\n },\n {\n label: t(\n 'catalog.merchandising_assistant.suggestions.pricingOverview',\n 'Show pricing distribution',\n ),\n prompt: 'Show me the pricing distribution across categories',\n icon: <TrendingUp className=\"size-4\" />,\n },\n ]\n }, [hasSelection, selectedCount, t])\n}\n\nfunction useContextItems(pageContext: MerchandisingPageContext): AiChatContextItem[] {\n const t = useT()\n return React.useMemo(() => {\n const items: AiChatContextItem[] = []\n const { selectedCount, totalMatching, filter } = pageContext.extra\n if (selectedCount > 0) {\n items.push({\n label: t(\n 'catalog.merchandising_assistant.context.selectedProducts',\n '{count} products selected',\n ).replace('{count}', String(selectedCount)),\n })\n } else if (totalMatching > 0) {\n items.push({\n label: t(\n 'catalog.merchandising_assistant.context.matchingProducts',\n '{count} products in view',\n ).replace('{count}', String(totalMatching)),\n })\n }\n if (filter.categoryId) {\n items.push({\n label: t('catalog.merchandising_assistant.context.filteredByCategory', 'Filtered by category'),\n detail: filter.categoryId,\n })\n }\n if (filter.status) {\n items.push({ label: filter.status })\n }\n if (filter.tags.length > 0) {\n items.push({\n label: t('catalog.merchandising_assistant.context.tags', '{count} tags').replace(\n '{count}',\n String(filter.tags.length),\n ),\n })\n }\n return items\n }, [pageContext, t])\n}\n\ninterface MerchandisingAgentDescriptor {\n id: string\n label: string\n description: string\n icon: React.ReactNode\n}\n\ninterface AgentsResponse {\n agents?: Array<{\n id?: string | null\n }>\n}\n\ninterface MerchandisingAgentsState {\n agents: MerchandisingAgentDescriptor[]\n loaded: boolean\n}\n\nfunction useMerchandisingAgents(): MerchandisingAgentsState {\n const t = useT()\n const [accessibleAgentIds, setAccessibleAgentIds] = React.useState<Set<string> | null>(null)\n const declaredAgents = React.useMemo(\n () => [\n {\n id: MERCHANDISING_AGENT_ID,\n label: t(\n 'catalog.merchandising_assistant.agents.merchandising.label',\n 'Merchandising Assistant',\n ),\n description: t(\n 'catalog.merchandising_assistant.agents.merchandising.description',\n 'Draft copy, normalize attributes, and propose price changes for the current selection.',\n ),\n icon: <Sparkles className=\"size-4\" />,\n },\n ],\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n apiCall<AgentsResponse>('/api/ai_assistant/ai/agents', {\n credentials: 'same-origin',\n headers: { 'x-om-forbidden-redirect': '0', 'x-om-unauthorized-redirect': '0' },\n })\n .then((call) => {\n if (cancelled) return\n if (!call.ok || !call.result || !Array.isArray(call.result.agents)) {\n setAccessibleAgentIds(new Set())\n return\n }\n setAccessibleAgentIds(\n new Set(\n call.result.agents\n .map((agent) => agent?.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0),\n ),\n )\n })\n .catch(() => {\n if (!cancelled) setAccessibleAgentIds(new Set())\n })\n return () => {\n cancelled = true\n }\n }, [])\n\n return React.useMemo(\n () => ({\n agents:\n accessibleAgentIds === null\n ? []\n : declaredAgents.filter((agent) => accessibleAgentIds.has(agent.id)),\n loaded: accessibleAgentIds !== null,\n }),\n [accessibleAgentIds, declaredAgents],\n )\n}\n\nexport function MerchandisingAssistantSheet({\n pageContext,\n enabled = true,\n className,\n}: MerchandisingAssistantSheetProps): React.ReactElement | null {\n const t = useT()\n const dock = useAiDock()\n const [open, setOpen] = React.useState(false)\n const [popoverOpen, setPopoverOpen] = React.useState(false)\n const [activeAgent, setActiveAgent] = React.useState<string>(MERCHANDISING_AGENT_ID)\n const [lastAgent, setLastAgent] = React.useState<string | null>(null)\n\n const selectedCount = pageContext.extra.selectedCount\n const hasSelection = selectedCount > 0\n const suggestions = useMerchandisingSuggestions(hasSelection, selectedCount)\n const contextItems = useContextItems(pageContext)\n const { agents, loaded: agentsLoaded } = useMerchandisingAgents()\n\n if (!enabled || !agentsLoaded || agents.length === 0) return null\n\n const openAgent = (agentId: string) => {\n setActiveAgent(agentId)\n setLastAgent(agentId)\n setPopoverOpen(false)\n if (dock.state.assistant?.agent === agentId) {\n dock.dock(dock.state.assistant)\n setOpen(false)\n return\n }\n setOpen(true)\n }\n\n const handleSelectAgent = (agentId: string) => {\n openAgent(agentId)\n }\n\n const handleMainTriggerClick = () => {\n if (agents.length === 1) {\n openAgent(agents[0].id)\n return\n }\n if (lastAgent && agents.some((a) => a.id === lastAgent)) {\n openAgent(lastAgent)\n return\n }\n setPopoverOpen(true)\n }\n\n const handleDock = () => {\n const agent = agents.find((a) => a.id === activeAgent) ?? agents[0]\n if (!agent) return\n dock.dock({\n agent: agent.id,\n label: agent.label,\n description: t('catalog.merchandising_assistant.dock.subtitle', 'Catalog'),\n pageContext: pageContext as unknown as Record<string, unknown>,\n placeholder: t(\n 'catalog.merchandising_assistant.sheet.composerPlaceholder',\n 'Ask for descriptions, attributes, titles, or price ideas...',\n ),\n suggestions,\n contextItems,\n welcomeTitle: t(\n 'catalog.merchandising_assistant.sheet.welcomeTitle',\n 'Merchandising Assistant',\n ),\n welcomeDescription: hasSelection\n ? t(\n 'catalog.merchandising_assistant.sheet.welcomeDescriptionSelection',\n 'Ready to work with your {count} selected products. Try one of these:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'catalog.merchandising_assistant.sheet.welcomeDescriptionAll',\n 'Select products for targeted actions, or explore your catalog:',\n ),\n })\n setOpen(false)\n }\n\n const triggerLabel = t(\n 'catalog.merchandising_assistant.trigger.ariaLabel',\n 'Open AI merchandising assistant',\n )\n const labelText = t('catalog.merchandising_assistant.trigger.label', 'AI')\n const moreAgentsLabel = t(\n 'catalog.merchandising_assistant.trigger.moreAgentsAriaLabel',\n 'Choose an AI assistant',\n )\n\n return (\n <>\n <div className={cn('inline-flex items-center', className)}>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleMainTriggerClick}\n data-ai-merchandising-trigger=\"\"\n aria-label={triggerLabel}\n title={triggerLabel}\n className={cn(\n 'relative',\n agents.length > 1 && 'rounded-r-none border-r-0',\n )}\n >\n <Sparkles className=\"size-4\" aria-hidden />\n <span>{labelText}</span>\n {hasSelection ? (\n <span\n 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\"\n data-ai-merchandising-selected-count={selectedCount}\n >\n {selectedCount}\n </span>\n ) : null}\n </Button>\n {agents.length > 1 ? (\n <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>\n <PopoverTrigger asChild>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"lg\"\n aria-label={moreAgentsLabel}\n title={moreAgentsLabel}\n className=\"rounded-l-none\"\n data-ai-merchandising-picker=\"\"\n >\n <ChevronDown className=\"size-4\" aria-hidden />\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-72 p-1\">\n <div className=\"px-3 pt-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('catalog.merchandising_assistant.popover.heading', 'AI assistants')}\n </div>\n <div className=\"flex flex-col gap-0.5\">\n {agents.map((agent) => (\n <button\n key={agent.id}\n type=\"button\"\n onClick={() => handleSelectAgent(agent.id)}\n data-ai-merchandising-agent-option={agent.id}\n 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\"\n >\n <span className=\"mt-0.5 inline-flex size-6 items-center justify-center rounded-full bg-secondary text-secondary-foreground\">\n {agent.icon}\n </span>\n <span className=\"flex-1 min-w-0\">\n <span className=\"block font-medium leading-tight\">{agent.label}</span>\n <span className=\"block text-xs text-muted-foreground leading-snug\">\n {agent.description}\n </span>\n </span>\n </button>\n ))}\n </div>\n </PopoverContent>\n </Popover>\n ) : null}\n </div>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent\n className={cn(\n // Mobile: full-screen sheet. Desktop (\u2265sm): right-anchored side sheet.\n // The Dialog primitive applies a centering transform at the\n // sm breakpoint; each piece (`top`, `left`, transform, inset)\n // must be overridden at the same breakpoint or the panel\n // renders half off the viewport.\n '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',\n 'sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',\n 'sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen',\n 'flex flex-col gap-3 p-4 z-[70]',\n )}\n data-ai-merchandising-sheet=\"\"\n >\n <DialogHeader>\n <div className=\"flex items-center gap-3 pr-8\">\n {/* Dock button lives on the LEFT \u2014 the Dialog primitive\n auto-renders an X close button absolutely positioned in\n the top-right corner, so anything we drop in the header's\n right side visually collides with it. Mobile hides the\n dock entirely (the side panel is desktop-only). */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('catalog.merchandising_assistant.sheet.dock', 'Dock to side')}\n title={t('catalog.merchandising_assistant.sheet.dock', 'Dock to side')}\n onClick={handleDock}\n data-ai-merchandising-dock=\"\"\n className=\"hidden lg:inline-flex shrink-0\"\n >\n <PanelRightOpen className=\"size-4\" aria-hidden />\n </IconButton>\n <DialogTitle className=\"flex-1 min-w-0 truncate\">\n {t('catalog.merchandising_assistant.sheet.title', 'Catalog merchandising assistant')}\n </DialogTitle>\n {hasSelection ? (\n <span\n className=\"shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground\"\n data-ai-merchandising-selection-pill=\"\"\n data-ai-merchandising-selected-count={selectedCount}\n >\n {t(\n 'catalog.merchandising_assistant.sheet.selectionPill',\n 'Acting on {count} products',\n ).replace('{count}', String(selectedCount))}\n </span>\n ) : null}\n </div>\n <DialogDescription>\n {hasSelection\n ? t(\n 'catalog.merchandising_assistant.sheet.descriptionWithSelection',\n 'Working with {count} selected products. Ask for descriptions, attribute extraction, title suggestions, or pricing analysis.',\n ).replace('{count}', String(selectedCount))\n : t(\n 'catalog.merchandising_assistant.sheet.description',\n 'Your AI merchandising copilot. Select products from the list for targeted actions, or explore your full catalog.',\n )}\n </DialogDescription>\n </DialogHeader>\n <MerchandisingChatBody\n activeAgent={activeAgent}\n pageContext={pageContext}\n suggestions={suggestions}\n contextItems={contextItems}\n hasSelection={hasSelection}\n selectedCount={selectedCount}\n />\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\ninterface MerchandisingChatBodyProps {\n activeAgent: string\n pageContext: MerchandisingPageContext\n suggestions: AiChatSuggestion[]\n contextItems: AiChatContextItem[]\n hasSelection: boolean\n selectedCount: number\n}\n\nfunction MerchandisingChatBody({\n activeAgent,\n pageContext,\n suggestions,\n contextItems,\n hasSelection,\n selectedCount,\n}: MerchandisingChatBodyProps) {\n const t = useT()\n const sessions = useAiChatSessions()\n const session = sessions.getActiveSession(activeAgent)\n\n React.useEffect(() => {\n if (!session) sessions.ensureSession(activeAgent)\n }, [activeAgent, session, sessions])\n\n return (\n <>\n <ChatPaneTabs agentId={activeAgent} className=\"border-b\" />\n <div className=\"min-h-0 flex-1\" data-ai-merchandising-chat-container=\"\">\n {session ? (\n <AiChat\n key={session.id}\n agent={activeAgent}\n conversationId={session.conversationId}\n pageContext={pageContext as unknown as Record<string, unknown>}\n className=\"h-full\"\n placeholder={t(\n 'catalog.merchandising_assistant.sheet.composerPlaceholder',\n 'Ask for descriptions, attributes, titles, or price ideas...',\n )}\n suggestions={suggestions}\n contextItems={contextItems}\n welcomeTitle={t(\n 'catalog.merchandising_assistant.sheet.welcomeTitle',\n 'Merchandising Assistant',\n )}\n welcomeDescription={\n hasSelection\n ? t(\n 'catalog.merchandising_assistant.sheet.welcomeDescriptionSelection',\n 'Ready to work with your {count} selected products. Try one of these:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'catalog.merchandising_assistant.sheet.welcomeDescriptionAll',\n 'Select products for targeted actions, or explore your catalog:',\n )\n }\n />\n ) : null}\n </div>\n </>\n )\n}\n\nexport default MerchandisingAssistantSheet\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
4
5
|
import ProductsDataTable from "../../../components/products/ProductsDataTable.js";
|
|
5
6
|
function CatalogProductsPage() {
|
|
6
|
-
|
|
7
|
+
const [, setSnapshot] = React.useState({
|
|
8
|
+
search: "",
|
|
9
|
+
filterValues: {},
|
|
10
|
+
total: 0
|
|
11
|
+
});
|
|
12
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ProductsDataTable, { onSnapshotChange: setSnapshot }) }) });
|
|
7
13
|
}
|
|
8
14
|
export {
|
|
9
15
|
CatalogProductsPage as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/catalog/backend/catalog/products/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport ProductsDataTable from '../../../components/products/ProductsDataTable'\n\nexport default function CatalogProductsPage() {\n return (\n <Page>\n <PageBody>\n <ProductsDataTable />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport ProductsDataTable, {\n type ProductsDataTableSnapshot,\n} from '../../../components/products/ProductsDataTable'\n\n/**\n * Step 5.15 \u2014 Phase 3 WS-D.\n *\n * The catalog merchandising AI trigger moved behind the widget-injection\n * system and now mounts in `data-table:catalog.products:header`. The\n * products list page no longer imports `MerchandisingAssistantSheet`,\n * `hasAllFeatures`, or the `/api/auth/feature-check` polling helper \u2014\n * feature gating is handled by the injection widget's `features`\n * metadata (`catalog.products.view` + `ai_assistant.view`). The snapshot\n * subscription is kept so host-side observability hooks the DataTable's\n * current filter/total count for future extensions.\n */\nexport default function CatalogProductsPage() {\n const [, setSnapshot] = React.useState<ProductsDataTableSnapshot>({\n search: '',\n filterValues: {},\n total: 0,\n })\n\n return (\n <Page>\n <PageBody>\n <ProductsDataTable onSnapshotChange={setSnapshot} />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA8BQ;AA5BR,YAAY,WAAW;AACvB,SAAS,MAAM,gBAAgB;AAC/B,OAAO,uBAEA;AAcQ,SAAR,sBAAuC;AAC5C,QAAM,CAAC,EAAE,WAAW,IAAI,MAAM,SAAoC;AAAA,IAChE,QAAQ;AAAA,IACR,cAAc,CAAC;AAAA,IACf,OAAO;AAAA,EACT,CAAC;AAED,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,qBAAkB,kBAAkB,aAAa,GACpD,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Boxes, FolderTree, PackageCheck, Tags } from "lucide-react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import {
|
|
6
|
+
defaultAiUiPartRegistry
|
|
7
|
+
} from "@open-mercato/ui/ai";
|
|
8
|
+
function formatCount(value) {
|
|
9
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
10
|
+
return value.toLocaleString();
|
|
11
|
+
}
|
|
12
|
+
return "\u2014";
|
|
13
|
+
}
|
|
14
|
+
function StatTile({
|
|
15
|
+
icon,
|
|
16
|
+
label,
|
|
17
|
+
value
|
|
18
|
+
}) {
|
|
19
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 rounded-md border border-border bg-card p-3", children: [
|
|
20
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs uppercase tracking-wide text-muted-foreground", children: [
|
|
21
|
+
icon,
|
|
22
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
23
|
+
] }),
|
|
24
|
+
/* @__PURE__ */ jsx("div", { className: "text-2xl font-semibold leading-none", children: value })
|
|
25
|
+
] });
|
|
26
|
+
}
|
|
27
|
+
function CatalogStatsCard({ payload }) {
|
|
28
|
+
const t = useT();
|
|
29
|
+
const data = payload ?? {};
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
"div",
|
|
32
|
+
{
|
|
33
|
+
className: "rounded-lg border border-border bg-muted/30 p-3",
|
|
34
|
+
"data-ai-ui-part": "catalog.stats-card",
|
|
35
|
+
children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 text-sm font-medium", children: [
|
|
37
|
+
/* @__PURE__ */ jsx(Boxes, { className: "size-4 text-primary", "aria-hidden": true }),
|
|
38
|
+
/* @__PURE__ */ jsx("span", { children: t("catalog.stats.title", "Catalog overview") })
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: [
|
|
41
|
+
/* @__PURE__ */ jsx(
|
|
42
|
+
StatTile,
|
|
43
|
+
{
|
|
44
|
+
icon: /* @__PURE__ */ jsx(Boxes, { className: "size-3", "aria-hidden": true }),
|
|
45
|
+
label: t("catalog.stats.products", "Products"),
|
|
46
|
+
value: formatCount(data.products)
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
StatTile,
|
|
51
|
+
{
|
|
52
|
+
icon: /* @__PURE__ */ jsx(PackageCheck, { className: "size-3", "aria-hidden": true }),
|
|
53
|
+
label: t("catalog.stats.active", "Active"),
|
|
54
|
+
value: formatCount(data.activeProducts)
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
StatTile,
|
|
59
|
+
{
|
|
60
|
+
icon: /* @__PURE__ */ jsx(FolderTree, { className: "size-3", "aria-hidden": true }),
|
|
61
|
+
label: t("catalog.stats.categories", "Categories"),
|
|
62
|
+
value: formatCount(data.categories)
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
StatTile,
|
|
67
|
+
{
|
|
68
|
+
icon: /* @__PURE__ */ jsx(Tags, { className: "size-3", "aria-hidden": true }),
|
|
69
|
+
label: t("catalog.stats.tags", "Tags"),
|
|
70
|
+
value: formatCount(data.tags)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
] }),
|
|
74
|
+
data.note ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: data.note }) : null,
|
|
75
|
+
data.generatedAt ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-[10px] text-muted-foreground", children: t("catalog.stats.snapshotAt", "Snapshot at {time}").replace("{time}", new Date(data.generatedAt).toLocaleString()) }) : null
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
let registered = false;
|
|
81
|
+
function registerCatalogStatsCard() {
|
|
82
|
+
if (registered) return;
|
|
83
|
+
registered = true;
|
|
84
|
+
defaultAiUiPartRegistry.register("catalog.stats-card", CatalogStatsCard);
|
|
85
|
+
}
|
|
86
|
+
registerCatalogStatsCard();
|
|
87
|
+
export {
|
|
88
|
+
CatalogStatsCard,
|
|
89
|
+
registerCatalogStatsCard
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=CatalogStatsCard.js.map
|