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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +14 -0
  3. package/dist/modules/auth/backend/roles/[id]/edit/page.js +2 -1
  4. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -5
  6. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  7. package/dist/modules/catalog/ai-agents.js +47 -5
  8. package/dist/modules/catalog/ai-agents.js.map +2 -2
  9. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -5
  10. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  11. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +4 -3
  12. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -9
  14. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +2 -9
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  17. package/dist/modules/customers/ai-agents.js +174 -1
  18. package/dist/modules/customers/ai-agents.js.map +2 -2
  19. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +196 -0
  20. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +7 -0
  21. package/dist/modules/customers/ai-tools.js +3 -1
  22. package/dist/modules/customers/ai-tools.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/companies/page.js +11 -1
  24. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  25. package/dist/modules/customers/backend/customers/deals/page.js +11 -1
  26. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  27. package/dist/modules/customers/backend/customers/people/page.js +11 -1
  28. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  29. package/dist/modules/customers/commands/companies.js +37 -15
  30. package/dist/modules/customers/commands/companies.js.map +2 -2
  31. package/dist/modules/customers/components/detail/AiActionChips.js +2 -2
  32. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  33. package/dist/modules/customers/components/detail/ChangelogTab.js +3 -9
  34. package/dist/modules/customers/components/detail/ChangelogTab.js.map +2 -2
  35. package/dist/modules/customers/components/detail/changelogValues.js +35 -0
  36. package/dist/modules/customers/components/detail/changelogValues.js.map +7 -0
  37. package/dist/modules/customers/components/detail/customFieldUtils.js +1 -1
  38. package/dist/modules/customers/components/detail/customFieldUtils.js.map +1 -1
  39. package/dist/modules/customers/components/formConfig.js +3 -2
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +4 -9
  42. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  43. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +302 -0
  44. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +7 -0
  45. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js +17 -0
  46. package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js.map +7 -0
  47. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +2 -2
  48. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +2 -2
  49. package/dist/modules/customers/widgets/injection-table.js +2 -2
  50. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  51. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -5
  52. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  53. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +2 -5
  54. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  55. package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -5
  56. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  57. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +2 -25
  58. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  59. package/dist/modules/sales/components/channels/ChannelOfferForm.js +2 -43
  60. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  61. package/dist/modules/sales/components/documents/LineItemDialog.js +162 -42
  62. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  63. package/dist/modules/sales/components/documents/customFieldHelpers.js +4 -31
  64. package/dist/modules/sales/components/documents/customFieldHelpers.js.map +2 -2
  65. package/package.json +16 -8
  66. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +2 -1
  67. package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -5
  68. package/src/modules/business_rules/migrations/.snapshot-open-mercato.json +746 -394
  69. package/src/modules/catalog/ai-agents.ts +62 -4
  70. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +2 -5
  71. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
  72. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -12
  73. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -10
  74. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +2519 -1307
  75. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +828 -834
  76. package/src/modules/customers/AGENTS.md +6 -1
  77. package/src/modules/customers/ai-agents.ts +213 -6
  78. package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +286 -0
  79. package/src/modules/customers/ai-tools.ts +2 -0
  80. package/src/modules/customers/backend/customers/companies/page.tsx +10 -0
  81. package/src/modules/customers/backend/customers/deals/page.tsx +10 -0
  82. package/src/modules/customers/backend/customers/people/page.tsx +10 -0
  83. package/src/modules/customers/commands/companies.ts +38 -14
  84. package/src/modules/customers/components/detail/AiActionChips.tsx +2 -2
  85. package/src/modules/customers/components/detail/ChangelogTab.tsx +3 -10
  86. package/src/modules/customers/components/detail/changelogValues.ts +42 -0
  87. package/src/modules/customers/components/detail/customFieldUtils.ts +1 -1
  88. package/src/modules/customers/components/formConfig.tsx +3 -2
  89. package/src/modules/customers/i18n/de.json +7 -0
  90. package/src/modules/customers/i18n/en.json +7 -0
  91. package/src/modules/customers/i18n/es.json +7 -0
  92. package/src/modules/customers/i18n/pl.json +7 -0
  93. package/src/modules/customers/migrations/.snapshot-open-mercato.json +2313 -2336
  94. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -6
  95. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +358 -0
  96. package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.ts +34 -0
  97. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +2 -2
  98. package/src/modules/customers/widgets/injection-table.ts +12 -9
  99. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +554 -210
  100. package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +146 -146
  101. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +2 -5
  102. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +2 -5
  103. package/src/modules/directory/migrations/.snapshot-open-mercato.json +273 -140
  104. package/src/modules/feature_toggles/migrations/.snapshot-open-mercato.json +242 -137
  105. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +498 -195
  106. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +158 -157
  107. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +274 -129
  108. package/src/modules/planner/migrations/.snapshot-open-mercato.json +327 -157
  109. package/src/modules/progress/AGENTS.md +72 -0
  110. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -5
  111. package/src/modules/resources/migrations/.snapshot-open-mercato.json +920 -470
  112. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +2 -27
  113. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +2 -49
  114. package/src/modules/sales/components/documents/LineItemDialog.tsx +214 -78
  115. package/src/modules/sales/components/documents/customFieldHelpers.ts +4 -43
  116. package/src/modules/sales/i18n/de.json +3 -0
  117. package/src/modules/sales/i18n/en.json +3 -0
  118. package/src/modules/sales/i18n/es.json +3 -0
  119. package/src/modules/sales/i18n/pl.json +3 -0
  120. package/src/modules/sales/migrations/.snapshot-open-mercato.json +4740 -4778
  121. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +340 -81
  122. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1439 -729
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/catalog/backend/catalog/categories/%5Bid%5D/edit/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { CategorySelect } from '../../../../../components/categories/CategorySelect'\nimport { CategorySlugFieldSync } from '../../../../../components/categories/CategorySlugFieldSync'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\n\ntype CategoryRow = {\n id: string\n name: string\n slug: string | null\n description: string | null\n parentId: string | null\n isActive: boolean\n pathLabel?: string\n}\n\ntype CategoryResponse = {\n items?: CategoryRow[]\n}\n\ntype CategoryFormValues = {\n id?: string\n name: string\n slug?: string\n description?: string\n parentId?: string | null\n isActive?: boolean\n}\n\nasync function submitCategoryUpdate(\n categoryId: string,\n values: CategoryFormValues,\n t: (key: string, fallback?: string) => string,\n) {\n const resolvedId = typeof values.id === 'string' && values.id.length ? values.id : categoryId\n if (!resolvedId) {\n const message = t('catalog.categories.form.errors.idRequired', 'Category identifier is required.')\n throw createCrudFormError(message, { id: message })\n }\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n if (!name) {\n const message = t('catalog.categories.form.errors.name', 'Provide the category name.')\n throw createCrudFormError(message, { name: message })\n }\n const slug = typeof values.slug === 'string' && values.slug.trim().length ? values.slug.trim() : undefined\n const description =\n typeof values.description === 'string' && values.description.trim().length\n ? values.description.trim()\n : undefined\n const parentId =\n typeof values.parentId === 'string' && values.parentId.trim().length\n ? values.parentId.trim()\n : null\n const customFields = collectCustomFieldValues(values as Record<string, unknown>)\n const payload: Record<string, unknown> = {\n id: resolvedId,\n name,\n slug,\n description,\n parentId,\n isActive: values.isActive !== false,\n }\n if (Object.keys(customFields).length > 0) payload.customFields = customFields\n await updateCrud('catalog/categories', payload)\n}\n\nexport default function EditCatalogCategoryPage({ params }: { params?: { id?: string } }) {\n const categoryId = params?.id ?? ''\n const t = useT()\n const [initialValues, setInitialValues] = React.useState<CategoryFormValues | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [loading, setLoading] = React.useState<boolean>(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!categoryId) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const { ok, result } = await apiCall<CategoryResponse>(\n `/api/catalog/categories?view=manage&ids=${encodeURIComponent(categoryId)}&status=all&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(t('catalog.categories.form.errors.load', 'Failed to load category'))\n const record = Array.isArray(result?.items) ? result.items?.[0] : null\n if (!record) throw new Error(t('catalog.categories.form.errors.notFound', 'Category not found'))\n if (cancelled) return\n const customValues: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record as Record<string, unknown>)) {\n if (key.startsWith('cf_')) customValues[key] = value\n else if (key.startsWith('cf:')) customValues[`cf_${key.slice(3)}`] = value\n }\n setInitialValues({\n id: record.id,\n name: record.name,\n slug: record.slug ?? '',\n description: record.description ?? '',\n parentId: record.parentId ?? '',\n isActive: record.isActive,\n ...customValues,\n })\n setPathLabel(record.pathLabel ?? '')\n } catch (err) {\n if (!cancelled) {\n const fallback = t('catalog.categories.form.errors.load', 'Failed to load category')\n const message = err instanceof Error ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [categoryId, t])\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'name',\n label: t('catalog.categories.form.field.name', 'Name'),\n type: 'text',\n required: true,\n placeholder: t('catalog.categories.form.field.namePlaceholder', 'e.g., Footwear'),\n },\n {\n id: 'slug',\n label: t('catalog.categories.form.field.slug', 'Slug'),\n type: 'text',\n description: t('catalog.categories.form.field.slugHelp', 'Lowercase identifier for URLs or imports.'),\n },\n {\n id: 'description',\n label: t('catalog.categories.form.field.description', 'Description'),\n type: 'textarea',\n },\n {\n id: 'parentId',\n label: t('catalog.categories.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <CategorySelect\n id={id}\n value={typeof value === 'string' ? value : null}\n onChange={(next) => setValue(next ?? '')}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'isActive',\n label: t('catalog.categories.form.field.isActive', 'Active'),\n type: 'checkbox',\n },\n ], [t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('catalog.categories.form.group.details', 'Details'),\n column: 1,\n fields: ['name', 'slug', 'description', 'parentId', 'isActive'],\n component: (ctx) => <CategorySlugFieldSync {...ctx} />,\n },\n {\n id: 'custom',\n title: t('catalog.categories.form.group.custom', 'Custom data'),\n column: 2,\n kind: 'customFields',\n },\n ], [t])\n\n if (!categoryId) {\n return (\n <Page>\n <PageBody>\n <p className=\"text-sm text-destructive\">\n {t('catalog.categories.form.errors.idRequired', 'Category identifier is required.')}\n </p>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n <CrudForm<CategoryFormValues>\n title={t('catalog.categories.form.editTitle', 'Edit category')}\n backHref=\"/backend/catalog/categories\"\n versionHistory={{ resourceKind: 'catalog.category', resourceId: categoryId ? String(categoryId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.catalog.catalog_product_category}\n initialValues={initialValues ?? { id: categoryId, name: '', slug: '', description: '', parentId: '', isActive: true }}\n isLoading={loading}\n loadingMessage={t('catalog.categories.form.loading', 'Loading category...')}\n submitLabel={t('catalog.categories.form.action.save', 'Save')}\n cancelHref=\"/backend/catalog/categories\"\n successRedirect={`/backend/catalog/categories?flash=${encodeURIComponent(t('catalog.categories.flash.updated', 'Category updated'))}&type=success`}\n extraActions={(\n <>\n <SendObjectMessageDialog\n object={{\n entityModule: 'catalog',\n entityType: 'category',\n entityId: categoryId,\n previewData: { title: initialValues?.name ?? categoryId },\n }}\n viewHref={`/backend/catalog/categories/${categoryId}/edit`}\n />\n {pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('catalog.categories.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n </>\n )}\n onSubmit={async (values) => {\n await submitCategoryUpdate(categoryId, values, t)\n }}\n onDelete={async () => {\n await deleteCrud('catalog/categories', categoryId, {\n errorMessage: t('catalog.categories.form.errors.delete', 'Failed to delete category'),\n })\n }}\n deleteRedirect={`/backend/catalog/categories?flash=${encodeURIComponent(t('catalog.categories.flash.deleted', 'Category archived'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { E } from '#generated/entities.ids.generated'\nimport { CategorySelect } from '../../../../../components/categories/CategorySelect'\nimport { CategorySlugFieldSync } from '../../../../../components/categories/CategorySlugFieldSync'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\n\ntype CategoryRow = {\n id: string\n name: string\n slug: string | null\n description: string | null\n parentId: string | null\n isActive: boolean\n pathLabel?: string\n}\n\ntype CategoryResponse = {\n items?: CategoryRow[]\n}\n\ntype CategoryFormValues = {\n id?: string\n name: string\n slug?: string\n description?: string\n parentId?: string | null\n isActive?: boolean\n}\n\nasync function submitCategoryUpdate(\n categoryId: string,\n values: CategoryFormValues,\n t: (key: string, fallback?: string) => string,\n) {\n const resolvedId = typeof values.id === 'string' && values.id.length ? values.id : categoryId\n if (!resolvedId) {\n const message = t('catalog.categories.form.errors.idRequired', 'Category identifier is required.')\n throw createCrudFormError(message, { id: message })\n }\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n if (!name) {\n const message = t('catalog.categories.form.errors.name', 'Provide the category name.')\n throw createCrudFormError(message, { name: message })\n }\n const slug = typeof values.slug === 'string' && values.slug.trim().length ? values.slug.trim() : undefined\n const description =\n typeof values.description === 'string' && values.description.trim().length\n ? values.description.trim()\n : undefined\n const parentId =\n typeof values.parentId === 'string' && values.parentId.trim().length\n ? values.parentId.trim()\n : null\n const customFields = collectCustomFieldValues(values as Record<string, unknown>)\n const payload: Record<string, unknown> = {\n id: resolvedId,\n name,\n slug,\n description,\n parentId,\n isActive: values.isActive !== false,\n }\n if (Object.keys(customFields).length > 0) payload.customFields = customFields\n await updateCrud('catalog/categories', payload)\n}\n\nexport default function EditCatalogCategoryPage({ params }: { params?: { id?: string } }) {\n const categoryId = params?.id ?? ''\n const t = useT()\n const [initialValues, setInitialValues] = React.useState<CategoryFormValues | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [loading, setLoading] = React.useState<boolean>(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!categoryId) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const { ok, result } = await apiCall<CategoryResponse>(\n `/api/catalog/categories?view=manage&ids=${encodeURIComponent(categoryId)}&status=all&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(t('catalog.categories.form.errors.load', 'Failed to load category'))\n const record = Array.isArray(result?.items) ? result.items?.[0] : null\n if (!record) throw new Error(t('catalog.categories.form.errors.notFound', 'Category not found'))\n if (cancelled) return\n const customValues = extractCustomFieldEntries(record as Record<string, unknown>)\n setInitialValues({\n id: record.id,\n name: record.name,\n slug: record.slug ?? '',\n description: record.description ?? '',\n parentId: record.parentId ?? '',\n isActive: record.isActive,\n ...customValues,\n })\n setPathLabel(record.pathLabel ?? '')\n } catch (err) {\n if (!cancelled) {\n const fallback = t('catalog.categories.form.errors.load', 'Failed to load category')\n const message = err instanceof Error ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [categoryId, t])\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'name',\n label: t('catalog.categories.form.field.name', 'Name'),\n type: 'text',\n required: true,\n placeholder: t('catalog.categories.form.field.namePlaceholder', 'e.g., Footwear'),\n },\n {\n id: 'slug',\n label: t('catalog.categories.form.field.slug', 'Slug'),\n type: 'text',\n description: t('catalog.categories.form.field.slugHelp', 'Lowercase identifier for URLs or imports.'),\n },\n {\n id: 'description',\n label: t('catalog.categories.form.field.description', 'Description'),\n type: 'textarea',\n },\n {\n id: 'parentId',\n label: t('catalog.categories.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <CategorySelect\n id={id}\n value={typeof value === 'string' ? value : null}\n onChange={(next) => setValue(next ?? '')}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'isActive',\n label: t('catalog.categories.form.field.isActive', 'Active'),\n type: 'checkbox',\n },\n ], [t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('catalog.categories.form.group.details', 'Details'),\n column: 1,\n fields: ['name', 'slug', 'description', 'parentId', 'isActive'],\n component: (ctx) => <CategorySlugFieldSync {...ctx} />,\n },\n {\n id: 'custom',\n title: t('catalog.categories.form.group.custom', 'Custom data'),\n column: 2,\n kind: 'customFields',\n },\n ], [t])\n\n if (!categoryId) {\n return (\n <Page>\n <PageBody>\n <p className=\"text-sm text-destructive\">\n {t('catalog.categories.form.errors.idRequired', 'Category identifier is required.')}\n </p>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n <CrudForm<CategoryFormValues>\n title={t('catalog.categories.form.editTitle', 'Edit category')}\n backHref=\"/backend/catalog/categories\"\n versionHistory={{ resourceKind: 'catalog.category', resourceId: categoryId ? String(categoryId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.catalog.catalog_product_category}\n initialValues={initialValues ?? { id: categoryId, name: '', slug: '', description: '', parentId: '', isActive: true }}\n isLoading={loading}\n loadingMessage={t('catalog.categories.form.loading', 'Loading category...')}\n submitLabel={t('catalog.categories.form.action.save', 'Save')}\n cancelHref=\"/backend/catalog/categories\"\n successRedirect={`/backend/catalog/categories?flash=${encodeURIComponent(t('catalog.categories.flash.updated', 'Category updated'))}&type=success`}\n extraActions={(\n <>\n <SendObjectMessageDialog\n object={{\n entityModule: 'catalog',\n entityType: 'category',\n entityId: categoryId,\n previewData: { title: initialValues?.name ?? categoryId },\n }}\n viewHref={`/backend/catalog/categories/${categoryId}/edit`}\n />\n {pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('catalog.categories.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n </>\n )}\n onSubmit={async (values) => {\n await submitCategoryUpdate(categoryId, values, t)\n }}\n onDelete={async () => {\n await deleteCrud('catalog/categories', categoryId, {\n errorMessage: t('catalog.categories.form.errors.delete', 'Failed to delete category'),\n })\n }}\n deleteRedirect={`/backend/catalog/categories?flash=${encodeURIComponent(t('catalog.categories.flash.deleted', 'Category archived'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Boxes, ChevronDown, FileText, Package, PanelRightOpen, PenLine, Sparkles, Tags, TrendingUp } from "lucide-react";
4
+ import { Boxes, ChevronDown, FileText, Package, PanelRightOpen, PenLine, Tags, TrendingUp } from "lucide-react";
5
5
  import { AiChat } from "@open-mercato/ui/ai/AiChat";
6
+ import { AiIcon } from "@open-mercato/ui/ai/AiIcon";
6
7
  import { useAiDock } from "@open-mercato/ui/ai/AiDock";
7
8
  import { useAiChatSessions } from "@open-mercato/ui/ai/AiChatSessions";
8
9
  import { ChatPaneTabs } from "@open-mercato/ui/ai/ChatPaneTabs";
@@ -162,7 +163,7 @@ function useMerchandisingAgents() {
162
163
  "catalog.merchandising_assistant.agents.merchandising.description",
163
164
  "Draft copy, normalize attributes, and propose price changes for the current selection."
164
165
  ),
165
- icon: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" })
166
+ icon: /* @__PURE__ */ jsx(AiIcon, { className: "size-4" })
166
167
  }
167
168
  ],
168
169
  [t]
@@ -293,7 +294,7 @@ function MerchandisingAssistantSheet({
293
294
  agents.length > 1 && "rounded-r-none border-r-0"
294
295
  ),
295
296
  children: [
296
- /* @__PURE__ */ jsx(Sparkles, { className: "size-4", "aria-hidden": true }),
297
+ /* @__PURE__ */ jsx(AiIcon, { className: "size-4" }),
297
298
  /* @__PURE__ */ jsx("span", { children: labelText }),
298
299
  hasSelection ? /* @__PURE__ */ jsx(
299
300
  "span",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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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",
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, Tags, TrendingUp } from 'lucide-react'\nimport { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'\nimport { AiIcon } from '@open-mercato/ui/ai/AiIcon'\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: <AiIcon 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 <AiIcon className=\"size-4\" />\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
6
  "names": []
7
7
  }
@@ -29,6 +29,7 @@ import {
29
29
  import { TagsInput } from "@open-mercato/ui/backend/inputs/TagsInput";
30
30
  import { Textarea } from "@open-mercato/ui/primitives/textarea";
31
31
  import { DataLoader } from "@open-mercato/ui/primitives/DataLoader";
32
+ import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
32
33
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
33
34
  import {
34
35
  apiCall,
@@ -405,7 +406,7 @@ function EditCatalogProductPage({
405
406
  ]);
406
407
  const conversionRows = conversionsRes.ok ? readProductConversionRows(conversionsRes.result?.items) : [];
407
408
  initialConversionsRef.current = conversionRows;
408
- const { customValues } = extractCustomFields(record);
409
+ const customValues = extractCustomFieldEntries(record);
409
410
  const offers = readOfferSnapshots(record);
410
411
  offerSnapshotsRef.current = offers;
411
412
  const channelIds = extractChannelIds(offers);
@@ -2131,14 +2132,6 @@ function readOptionSchema(metadata) {
2131
2132
  };
2132
2133
  }).filter((entry) => !!entry);
2133
2134
  }
2134
- function extractCustomFields(record) {
2135
- const customValues = {};
2136
- Object.entries(record).forEach(([key, value]) => {
2137
- if (key.startsWith("cf_")) customValues[key] = value;
2138
- else if (key.startsWith("cf:")) customValues[`cf_${key.slice(3)}`] = value;
2139
- });
2140
- return { customValues };
2141
- }
2142
2135
  function normalizeIdList(value) {
2143
2136
  const ids = Array.isArray(value) ? value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length) : [];
2144
2137
  return Array.from(new Set(ids));