@open-mercato/search 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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 (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +7 -1
  3. package/dist/lib/presenter-enricher.js +12 -12
  4. package/dist/lib/presenter-enricher.js.map +2 -2
  5. package/dist/modules/search/ai-tools.js +5 -2
  6. package/dist/modules/search/ai-tools.js.map +2 -2
  7. package/dist/modules/search/api/openapi.js +2 -0
  8. package/dist/modules/search/api/openapi.js.map +2 -2
  9. package/dist/modules/search/api/settings/vector-store/route.js +34 -0
  10. package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
  11. package/dist/modules/search/events.js +13 -0
  12. package/dist/modules/search/events.js.map +7 -0
  13. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +2 -9
  14. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
  15. package/dist/modules/search/frontend/components/HybridSearchTable.js +2 -11
  16. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
  17. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
  18. package/dist/modules/search/frontend/components/TopbarSearchInline.js +2 -9
  19. package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
  20. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +15 -3
  21. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
  22. package/dist/modules/search/frontend/lib/entityTypeLabel.js +19 -0
  23. package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +7 -0
  24. package/dist/modules/search/i18n/de.json +49 -0
  25. package/dist/modules/search/i18n/en.json +49 -0
  26. package/dist/modules/search/i18n/es.json +49 -0
  27. package/dist/modules/search/i18n/ko.json +239 -0
  28. package/dist/modules/search/i18n/pl.json +49 -0
  29. package/dist/modules/search/lib/auto-indexing.js +7 -8
  30. package/dist/modules/search/lib/auto-indexing.js.map +2 -2
  31. package/dist/service.js +2 -5
  32. package/dist/service.js.map +2 -2
  33. package/dist/strategies/vector.strategy.js +3 -1
  34. package/dist/strategies/vector.strategy.js.map +2 -2
  35. package/dist/vector/drivers/pgvector/index.js +55 -1
  36. package/dist/vector/drivers/pgvector/index.js.map +2 -2
  37. package/dist/vector/types.js.map +2 -2
  38. package/package.json +11 -10
  39. package/src/__tests__/pgvector-extension-availability.test.ts +158 -0
  40. package/src/__tests__/presenter-enricher.test.ts +48 -0
  41. package/src/__tests__/presenter-locale.test.ts +76 -0
  42. package/src/lib/presenter-enricher.ts +14 -14
  43. package/src/modules/search/README.md +4 -1
  44. package/src/modules/search/ai-tools.ts +5 -2
  45. package/src/modules/search/api/__tests__/global-search.routes.test.ts +167 -0
  46. package/src/modules/search/api/openapi.ts +2 -0
  47. package/src/modules/search/api/settings/vector-store/route.ts +43 -0
  48. package/src/modules/search/events.ts +10 -0
  49. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +2 -15
  50. package/src/modules/search/frontend/components/HybridSearchTable.tsx +2 -17
  51. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +2 -0
  52. package/src/modules/search/frontend/components/TopbarSearchInline.tsx +2 -15
  53. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +32 -2
  54. package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +36 -1
  55. package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +25 -0
  56. package/src/modules/search/frontend/lib/entityTypeLabel.ts +22 -0
  57. package/src/modules/search/i18n/de.json +49 -0
  58. package/src/modules/search/i18n/en.json +49 -0
  59. package/src/modules/search/i18n/es.json +49 -0
  60. package/src/modules/search/i18n/ko.json +239 -0
  61. package/src/modules/search/i18n/pl.json +49 -0
  62. package/src/modules/search/lib/auto-indexing.ts +7 -12
  63. package/src/service.ts +2 -7
  64. package/src/strategies/vector.strategy.ts +7 -1
  65. package/src/vector/drivers/pgvector/index.ts +77 -1
  66. package/src/vector/types.ts +14 -0
@@ -49,6 +49,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
49
49
  import { cn } from '@open-mercato/shared/lib/utils'
50
50
  import type { SearchResult, SearchResultLink, SearchStrategyId } from '@open-mercato/shared/modules/search'
51
51
  import { useT } from '@open-mercato/shared/lib/i18n/context'
52
+ import { resolveEntityTypeLabel } from '../lib/entityTypeLabel'
52
53
  import {
53
54
  getCurrentOrganizationScope,
54
55
  subscribeOrganizationScopeChanged,
@@ -87,14 +88,6 @@ function hasActiveOrganizationSelection(): boolean {
87
88
  return !isAllOrganizationsSelection(cookieValue);
88
89
  }
89
90
 
90
- function humanizeSegment(segment: string): string {
91
- return segment
92
- .split(/[_-]+/)
93
- .filter(Boolean)
94
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
95
- .join(' ')
96
- }
97
-
98
91
  const ICON_MAP: Record<string, LucideIcon> = {
99
92
  bolt: Zap,
100
93
  zap: Zap,
@@ -140,12 +133,6 @@ function resolveIcon(name?: string): LucideIcon | null {
140
133
  return ICON_MAP[name.toLowerCase()] ?? null
141
134
  }
142
135
 
143
- function formatEntityId(entityId: string): string {
144
- if (!entityId.includes(':')) return humanizeSegment(entityId)
145
- const [module, entity] = entityId.split(':')
146
- return `${humanizeSegment(module)} · ${humanizeSegment(entity)}`
147
- }
148
-
149
136
  export type GlobalSearchDialogProps = {
150
137
  /** Whether embedding provider is configured for vector search */
151
138
  embeddingConfigured: boolean
@@ -423,7 +410,7 @@ export function GlobalSearchDialog({
423
410
  <div className="flex flex-wrap items-center gap-2">
424
411
  <span className={cn('font-medium text-base whitespace-normal break-all', !hasLink && 'text-muted-foreground')}>{presenter?.title ?? result.recordId}</span>
425
412
  <span className="rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground">
426
- {formatEntityId(result.entityId)}
413
+ {resolveEntityTypeLabel(t, result.entityId)}
427
414
  </span>
428
415
  {!hasLink && (
429
416
  <span className="rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text">
@@ -11,6 +11,7 @@ import type { SearchResult, SearchStrategyId } from '@open-mercato/shared/module
11
11
  import { cn } from '@open-mercato/shared/lib/utils'
12
12
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+ import { resolveEntityTypeLabel } from '../lib/entityTypeLabel'
14
15
  import {
15
16
  getCurrentOrganizationScope,
16
17
  subscribeOrganizationScopeChanged,
@@ -60,7 +61,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
60
61
  const title = resolveRowTitle(item)
61
62
  const iconName = item.presenter?.icon
62
63
  const Icon = iconName ? resolveIcon(iconName) : null
63
- const typeLabel = formatEntityId(item.entityId)
64
+ const typeLabel = resolveEntityTypeLabel(t, item.entityId)
64
65
  const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata)
65
66
  const links = normalizeLinks(item.links)
66
67
  return (
@@ -160,22 +161,6 @@ function resolveIcon(name?: string): LucideIcon | null {
160
161
  return null
161
162
  }
162
163
 
163
- function humanizeSegment(segment: string): string {
164
- return segment
165
- .split(/[_-]+/)
166
- .filter(Boolean)
167
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
168
- .join(' ')
169
- }
170
-
171
- function formatEntityId(entityId: string): string {
172
- if (!entityId.includes(':')) return humanizeSegment(entityId)
173
- const [module, entity] = entityId.split(':')
174
- const moduleLabel = humanizeSegment(module)
175
- const entityLabel = humanizeSegment(entity)
176
- return `${moduleLabel} · ${entityLabel}`
177
- }
178
-
179
164
  function resolveRowTitle(row: Row): string {
180
165
  const presenterTitle = row.presenter?.title
181
166
  if (typeof presenterTitle === 'string') {
@@ -118,6 +118,8 @@ type VectorDriverStatus = {
118
118
  name: string
119
119
  configured: boolean
120
120
  implemented: boolean
121
+ available?: boolean | null
122
+ unavailableReason?: string | null
121
123
  envVars: VectorDriverEnvVar[]
122
124
  }
123
125
 
@@ -49,6 +49,7 @@ import type { LucideIcon } from 'lucide-react'
49
49
  import { cn } from '@open-mercato/shared/lib/utils'
50
50
  import type { SearchResult, SearchResultLink } from '@open-mercato/shared/modules/search'
51
51
  import { useT } from '@open-mercato/shared/lib/i18n/context'
52
+ import { resolveEntityTypeLabel } from '../lib/entityTypeLabel'
52
53
  import {
53
54
  getCurrentOrganizationScope,
54
55
  subscribeOrganizationScopeChanged,
@@ -84,14 +85,6 @@ function hasActiveOrganizationSelection(): boolean {
84
85
  return !isAllOrganizationsSelection(cookieValue)
85
86
  }
86
87
 
87
- function humanizeSegment(segment: string): string {
88
- return segment
89
- .split(/[_-]+/)
90
- .filter(Boolean)
91
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
92
- .join(' ')
93
- }
94
-
95
88
  const ICON_MAP: Record<string, LucideIcon> = {
96
89
  bolt: Zap,
97
90
  zap: Zap,
@@ -137,12 +130,6 @@ function resolveIcon(name?: string): LucideIcon | null {
137
130
  return ICON_MAP[name.toLowerCase()] ?? null
138
131
  }
139
132
 
140
- function formatEntityId(entityId: string): string {
141
- if (!entityId.includes(':')) return humanizeSegment(entityId)
142
- const [module, entity] = entityId.split(':')
143
- return `${humanizeSegment(module)} · ${humanizeSegment(entity)}`
144
- }
145
-
146
133
  export type TopbarSearchInlineProps = {
147
134
  /** Whether embedding provider is configured for vector search */
148
135
  embeddingConfigured: boolean
@@ -521,7 +508,7 @@ export function TopbarSearchInline({
521
508
  </span>
522
509
  </span>
523
510
  <span className="flex items-center gap-2 text-overline text-muted-foreground">
524
- <span className="truncate">{formatEntityId(result.entityId)}</span>
511
+ <span className="truncate">{resolveEntityTypeLabel(t, result.entityId)}</span>
525
512
  {presenter?.subtitle ? (
526
513
  <>
527
514
  <span aria-hidden="true">·</span>
@@ -79,6 +79,8 @@ type VectorDriverStatus = {
79
79
  name: string
80
80
  configured: boolean
81
81
  implemented: boolean
82
+ available?: boolean | null
83
+ unavailableReason?: string | null
82
84
  envVars: VectorDriverEnvVar[]
83
85
  }
84
86
 
@@ -447,6 +449,31 @@ export function VectorSearchSection({
447
449
  const isEmbeddingConfigured = embeddingSettings?.configuredProviders?.includes(savedProvider)
448
450
  const providerOptions: EmbeddingProviderId[] = ['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama']
449
451
 
452
+ const activeDriver = vectorStoreConfig?.drivers.find((driver) => driver.id === vectorStoreConfig.currentDriver)
453
+ const isStoreUnavailable = activeDriver?.available === false
454
+ const storeUnavailableReason = activeDriver?.unavailableReason ?? null
455
+
456
+ const storeUnavailableBanner = isStoreUnavailable ? (
457
+ <div className="p-4 rounded-md bg-status-warning-bg border border-status-warning-border">
458
+ <div className="flex items-start gap-3">
459
+ <svg className="h-5 w-5 text-status-warning-icon flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
460
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
461
+ </svg>
462
+ <div>
463
+ <p className="text-sm font-medium text-status-warning-text">
464
+ {t('search.settings.vector.storeUnavailable', 'Vector store is not available')}
465
+ </p>
466
+ <p className="text-xs text-status-warning-text mt-1">
467
+ {t('search.settings.vector.storeUnavailableHint', 'Vector indexing and semantic search are skipped until the store is reachable. Install the pgvector extension on your PostgreSQL server; the status is rechecked about once a minute.')}
468
+ </p>
469
+ {storeUnavailableReason ? (
470
+ <p className="text-xs text-status-warning-text mt-1 font-mono">{storeUnavailableReason}</p>
471
+ ) : null}
472
+ </div>
473
+ </div>
474
+ </div>
475
+ ) : null
476
+
450
477
  const autoIndexingChecked = embeddingSettings ? embeddingSettings.autoIndexingEnabled : true
451
478
  const autoIndexingDisabled = embeddingLoading || embeddingSaving || Boolean(embeddingSettings?.autoIndexingLocked)
452
479
 
@@ -481,13 +508,14 @@ export function VectorSearchSection({
481
508
  </div>
482
509
  ) : (
483
510
  <div className="space-y-4">
511
+ {storeUnavailableBanner}
484
512
  {/* Vector Store Driver Status */}
485
513
  <div>
486
514
  <h3 className="text-sm font-semibold mb-2">{t('search.settings.vector.store', 'Vector Store')}</h3>
487
515
  <div className="grid gap-2 sm:grid-cols-3">
488
516
  {vectorStoreConfig?.drivers.map((driver) => {
489
517
  const isCurrent = driver.id === vectorStoreConfig.currentDriver
490
- const isReady = driver.configured && driver.implemented
518
+ const isReady = driver.configured && driver.implemented && driver.available !== false
491
519
  return (
492
520
  <div
493
521
  key={driver.id}
@@ -746,6 +774,8 @@ export function VectorSearchSection({
746
774
  <Spinner size="sm" />
747
775
  <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>
748
776
  </div>
777
+ ) : isStoreUnavailable ? (
778
+ storeUnavailableBanner
749
779
  ) : !isEmbeddingConfigured ? (
750
780
  <div className="p-4 rounded-md bg-status-warning-bg border border-status-warning-border">
751
781
  <div className="flex items-start gap-3">
@@ -979,7 +1009,7 @@ export function VectorSearchSection({
979
1009
  <Button type="button" variant="outline" onClick={handleEmbeddingCancelChange} disabled={embeddingSaving}>
980
1010
  {t('search.settings.actions.cancel', 'Cancel')}
981
1011
  </Button>
982
- <Button type="button" variant="destructive" onClick={handleEmbeddingConfirmChange} disabled={embeddingSaving}>
1012
+ <Button type="button" variant="destructive-solid" onClick={handleEmbeddingConfirmChange} disabled={embeddingSaving}>
983
1013
  {embeddingSaving ? <Spinner size="sm" className="mr-2" /> : null}
984
1014
  {t('search.settings.actions.confirm', 'Confirm')}
985
1015
  </Button>
@@ -50,13 +50,14 @@ const vectorStoreConfig: NonNullable<VectorSearchSectionProps['vectorStoreConfig
50
50
 
51
51
  function renderSection(
52
52
  settings: NonNullable<VectorSearchSectionProps['embeddingSettings']> = embeddingSettings,
53
+ storeConfig: NonNullable<VectorSearchSectionProps['vectorStoreConfig']> = vectorStoreConfig,
53
54
  ) {
54
55
  return render(
55
56
  <I18nProvider locale="en" dict={{}}>
56
57
  <VectorSearchSection
57
58
  embeddingSettings={settings}
58
59
  embeddingLoading={false}
59
- vectorStoreConfig={vectorStoreConfig}
60
+ vectorStoreConfig={storeConfig}
60
61
  vectorStoreConfigLoading={false}
61
62
  vectorReindexLock={null}
62
63
  onEmbeddingSettingsUpdate={jest.fn()}
@@ -113,3 +114,37 @@ describe('VectorSearchSection provider controls', () => {
113
114
  expect(document.getElementById('provider-ollama-configuration')).toBeNull()
114
115
  })
115
116
  })
117
+
118
+ describe('VectorSearchSection store availability', () => {
119
+ beforeEach(() => {
120
+ Object.defineProperty(globalThis, 'fetch', {
121
+ configurable: true,
122
+ value: jest.fn(() => new Promise(() => undefined)),
123
+ })
124
+ })
125
+
126
+ it('warns that the vector store is unavailable and surfaces the driver reason', () => {
127
+ renderSection(embeddingSettings, {
128
+ ...vectorStoreConfig,
129
+ drivers: [
130
+ {
131
+ ...vectorStoreConfig.drivers[0],
132
+ available: false,
133
+ unavailableReason: 'extension "vector" is not available on this PostgreSQL server',
134
+ },
135
+ ],
136
+ })
137
+
138
+ expect(screen.getByText('Vector store is not available')).toBeTruthy()
139
+ expect(screen.getByText(/extension "vector" is not available/)).toBeTruthy()
140
+ })
141
+
142
+ it('keeps the banner hidden while the store is reachable', () => {
143
+ renderSection(embeddingSettings, {
144
+ ...vectorStoreConfig,
145
+ drivers: [{ ...vectorStoreConfig.drivers[0], available: true, unavailableReason: null }],
146
+ })
147
+
148
+ expect(screen.queryByText('Vector store is not available')).toBeNull()
149
+ })
150
+ })
@@ -0,0 +1,25 @@
1
+ import { formatEntityId, resolveEntityTypeLabel } from '../entityTypeLabel'
2
+ import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
3
+
4
+ describe('formatEntityId', () => {
5
+ it('humanizes module · entity', () => {
6
+ expect(formatEntityId('customers:customer_person_profile')).toBe('Customers · Customer Person Profile')
7
+ })
8
+ it('humanizes a bare segment', () => {
9
+ expect(formatEntityId('messages')).toBe('Messages')
10
+ })
11
+ })
12
+
13
+ describe('resolveEntityTypeLabel', () => {
14
+ const t: TranslateFn = (key, fallbackOrParams) =>
15
+ key === 'search.entityType.sales.sales_order'
16
+ ? 'Order'
17
+ : (typeof fallbackOrParams === 'string' ? fallbackOrParams : key)
18
+
19
+ it('returns the translated label when a key exists', () => {
20
+ expect(resolveEntityTypeLabel(t, 'sales:sales_order')).toBe('Order')
21
+ })
22
+ it('falls back to the humanized string for unknown entity types', () => {
23
+ expect(resolveEntityTypeLabel(t, 'thirdparty:widget_thing')).toBe('Thirdparty · Widget Thing')
24
+ })
25
+ })
@@ -0,0 +1,22 @@
1
+ import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
2
+
3
+ function humanizeSegment(segment: string): string {
4
+ return segment
5
+ .split(/[_-]+/)
6
+ .filter(Boolean)
7
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
8
+ .join(' ')
9
+ }
10
+
11
+ export function formatEntityId(entityId: string): string {
12
+ if (!entityId.includes(':')) return humanizeSegment(entityId)
13
+ const [module, entity] = entityId.split(':')
14
+ return `${humanizeSegment(module)} · ${humanizeSegment(entity)}`
15
+ }
16
+
17
+ export function resolveEntityTypeLabel(t: TranslateFn, entityId: string): string {
18
+ const fallback = formatEntityId(entityId)
19
+ if (!entityId.includes(':')) return fallback
20
+ const [module, entity] = entityId.split(':')
21
+ return t(`search.entityType.${module}.${entity}`, fallback)
22
+ }
@@ -50,6 +50,53 @@
50
50
  "search.dialog.strategies.toggle": "Suchoptionen",
51
51
  "search.dialog.title": "Globale Suche",
52
52
  "search.dialog.warnings.configureLink": "Sucheinstellungen konfigurieren",
53
+ "search.entityType.catalog.catalog_offer": "Angebot",
54
+ "search.entityType.catalog.catalog_option_schema_template": "Optionsvorlage",
55
+ "search.entityType.catalog.catalog_price_kind": "Preistyp",
56
+ "search.entityType.catalog.catalog_product": "Produkt",
57
+ "search.entityType.catalog.catalog_product_category": "Kategorie",
58
+ "search.entityType.catalog.catalog_product_tag": "Tag",
59
+ "search.entityType.catalog.catalog_product_unit_conversion": "Einheitenumrechnung",
60
+ "search.entityType.catalog.catalog_product_variant": "Variante",
61
+ "search.entityType.customer_accounts.customer_role": "Kundenrolle",
62
+ "search.entityType.customer_accounts.customer_user": "Kundenbenutzer",
63
+ "search.entityType.customers.customer_activity": "Aktivität",
64
+ "search.entityType.customers.customer_comment": "Kommentar",
65
+ "search.entityType.customers.customer_company_profile": "Unternehmen",
66
+ "search.entityType.customers.customer_deal": "Deal",
67
+ "search.entityType.customers.customer_entity": "Kunde",
68
+ "search.entityType.customers.customer_person_profile": "Person",
69
+ "search.entityType.customers.customer_todo_link": "Aufgabe",
70
+ "search.entityType.inbox_ops.inbox_proposal": "Posteingang-Vorschlag",
71
+ "search.entityType.messages.message": "Nachricht",
72
+ "search.entityType.planner.planner_availability_rule_set": "Verfügbarkeitsregeln",
73
+ "search.entityType.resources.resources_resource": "Ressource",
74
+ "search.entityType.resources.resources_resource_type": "Ressourcentyp",
75
+ "search.entityType.sales.sales_channel": "Kanal",
76
+ "search.entityType.sales.sales_credit_memo": "Gutschrift",
77
+ "search.entityType.sales.sales_credit_memo_line": "Gutschriftsposition",
78
+ "search.entityType.sales.sales_delivery_window": "Lieferfenster",
79
+ "search.entityType.sales.sales_document_address": "Dokumentadresse",
80
+ "search.entityType.sales.sales_document_tag": "Dokument-Tag",
81
+ "search.entityType.sales.sales_invoice": "Rechnung",
82
+ "search.entityType.sales.sales_invoice_line": "Rechnungsposition",
83
+ "search.entityType.sales.sales_note": "Notiz",
84
+ "search.entityType.sales.sales_order": "Bestellung",
85
+ "search.entityType.sales.sales_order_adjustment": "Bestellanpassung",
86
+ "search.entityType.sales.sales_order_line": "Bestellposition",
87
+ "search.entityType.sales.sales_payment": "Zahlung",
88
+ "search.entityType.sales.sales_payment_allocation": "Zahlungszuordnung",
89
+ "search.entityType.sales.sales_payment_method": "Zahlungsmethode",
90
+ "search.entityType.sales.sales_quote": "Angebot",
91
+ "search.entityType.sales.sales_quote_adjustment": "Angebotsanpassung",
92
+ "search.entityType.sales.sales_quote_line": "Angebotsposition",
93
+ "search.entityType.sales.sales_shipment": "Sendung",
94
+ "search.entityType.sales.sales_shipment_item": "Sendungsposition",
95
+ "search.entityType.sales.sales_shipping_method": "Versandart",
96
+ "search.entityType.sales.sales_tax_rate": "Steuersatz",
97
+ "search.entityType.staff.staff_team": "Team",
98
+ "search.entityType.staff.staff_team_member": "Teammitglied",
99
+ "search.entityType.staff.staff_team_role": "Teamrolle",
53
100
  "search.messages.missingConfig": "Die Suche erfordert die Konfiguration eines Embedding-Anbieters für die semantische Suche.",
54
101
  "search.nav.hybridSearch": "Suche",
55
102
  "search.scopeHint.currentOrg": "Bereich: aktuelle Organisation",
@@ -167,6 +214,8 @@
167
214
  "search.settings.vector.sectionTitle": "Vektorsuche",
168
215
  "search.settings.vector.setEnvVar": "Umgebungsvariable setzen",
169
216
  "search.settings.vector.store": "Vektorspeicher",
217
+ "search.settings.vector.storeUnavailable": "Vektorspeicher ist nicht verfügbar",
218
+ "search.settings.vector.storeUnavailableHint": "Vektorindizierung und semantische Suche werden übersprungen, bis der Speicher erreichbar ist. Installieren Sie die pgvector-Erweiterung auf Ihrem PostgreSQL-Server; der Status wird etwa einmal pro Minute neu geprüft.",
170
219
  "search.settings.vector.usingTenantConfig": "Dieser Mandant verwendet eigene Embedding-Einstellungen.",
171
220
  "search.settings.vectorDocumentsLabel": "Vektordokumente",
172
221
  "search.settings.vectorNotConfigured": "Vektorsuche ist nicht konfiguriert.",
@@ -50,6 +50,53 @@
50
50
  "search.dialog.strategies.toggle": "Search options",
51
51
  "search.dialog.title": "Global Search",
52
52
  "search.dialog.warnings.configureLink": "Configure search settings",
53
+ "search.entityType.catalog.catalog_offer": "Offer",
54
+ "search.entityType.catalog.catalog_option_schema_template": "Option Template",
55
+ "search.entityType.catalog.catalog_price_kind": "Price Type",
56
+ "search.entityType.catalog.catalog_product": "Product",
57
+ "search.entityType.catalog.catalog_product_category": "Category",
58
+ "search.entityType.catalog.catalog_product_tag": "Tag",
59
+ "search.entityType.catalog.catalog_product_unit_conversion": "Unit Conversion",
60
+ "search.entityType.catalog.catalog_product_variant": "Variant",
61
+ "search.entityType.customer_accounts.customer_role": "Customer Role",
62
+ "search.entityType.customer_accounts.customer_user": "Customer User",
63
+ "search.entityType.customers.customer_activity": "Activity",
64
+ "search.entityType.customers.customer_comment": "Comment",
65
+ "search.entityType.customers.customer_company_profile": "Company",
66
+ "search.entityType.customers.customer_deal": "Deal",
67
+ "search.entityType.customers.customer_entity": "Customer",
68
+ "search.entityType.customers.customer_person_profile": "Person",
69
+ "search.entityType.customers.customer_todo_link": "Task",
70
+ "search.entityType.inbox_ops.inbox_proposal": "Inbox Proposal",
71
+ "search.entityType.messages.message": "Message",
72
+ "search.entityType.planner.planner_availability_rule_set": "Availability Rules",
73
+ "search.entityType.resources.resources_resource": "Resource",
74
+ "search.entityType.resources.resources_resource_type": "Resource Type",
75
+ "search.entityType.sales.sales_channel": "Channel",
76
+ "search.entityType.sales.sales_credit_memo": "Credit Memo",
77
+ "search.entityType.sales.sales_credit_memo_line": "Credit Memo Line",
78
+ "search.entityType.sales.sales_delivery_window": "Delivery Window",
79
+ "search.entityType.sales.sales_document_address": "Document Address",
80
+ "search.entityType.sales.sales_document_tag": "Document Tag",
81
+ "search.entityType.sales.sales_invoice": "Invoice",
82
+ "search.entityType.sales.sales_invoice_line": "Invoice Line",
83
+ "search.entityType.sales.sales_note": "Note",
84
+ "search.entityType.sales.sales_order": "Order",
85
+ "search.entityType.sales.sales_order_adjustment": "Order Adjustment",
86
+ "search.entityType.sales.sales_order_line": "Order Line",
87
+ "search.entityType.sales.sales_payment": "Payment",
88
+ "search.entityType.sales.sales_payment_allocation": "Payment Allocation",
89
+ "search.entityType.sales.sales_payment_method": "Payment Method",
90
+ "search.entityType.sales.sales_quote": "Quote",
91
+ "search.entityType.sales.sales_quote_adjustment": "Quote Adjustment",
92
+ "search.entityType.sales.sales_quote_line": "Quote Line",
93
+ "search.entityType.sales.sales_shipment": "Shipment",
94
+ "search.entityType.sales.sales_shipment_item": "Shipment Item",
95
+ "search.entityType.sales.sales_shipping_method": "Shipping Method",
96
+ "search.entityType.sales.sales_tax_rate": "Tax Rate",
97
+ "search.entityType.staff.staff_team": "Team",
98
+ "search.entityType.staff.staff_team_member": "Team Member",
99
+ "search.entityType.staff.staff_team_role": "Team Role",
53
100
  "search.messages.missingConfig": "Search requires configuring an embedding provider for semantic search.",
54
101
  "search.nav.hybridSearch": "Search",
55
102
  "search.scopeHint.currentOrg": "Scoped to current organization",
@@ -167,6 +214,8 @@
167
214
  "search.settings.vector.sectionTitle": "Vector Search",
168
215
  "search.settings.vector.setEnvVar": "Set environment variable",
169
216
  "search.settings.vector.store": "Vector store",
217
+ "search.settings.vector.storeUnavailable": "Vector store is not available",
218
+ "search.settings.vector.storeUnavailableHint": "Vector indexing and semantic search are skipped until the store is reachable. Install the pgvector extension on your PostgreSQL server; the status is rechecked about once a minute.",
170
219
  "search.settings.vector.usingTenantConfig": "This tenant is using its own embedding settings.",
171
220
  "search.settings.vectorDocumentsLabel": "Vector documents",
172
221
  "search.settings.vectorNotConfigured": "Vector search is not configured.",
@@ -50,6 +50,53 @@
50
50
  "search.dialog.strategies.toggle": "Opciones de búsqueda",
51
51
  "search.dialog.title": "Búsqueda Global",
52
52
  "search.dialog.warnings.configureLink": "Configurar búsqueda",
53
+ "search.entityType.catalog.catalog_offer": "Oferta",
54
+ "search.entityType.catalog.catalog_option_schema_template": "Plantilla de opciones",
55
+ "search.entityType.catalog.catalog_price_kind": "Tipo de precio",
56
+ "search.entityType.catalog.catalog_product": "Producto",
57
+ "search.entityType.catalog.catalog_product_category": "Categoría",
58
+ "search.entityType.catalog.catalog_product_tag": "Etiqueta",
59
+ "search.entityType.catalog.catalog_product_unit_conversion": "Conversión de unidades",
60
+ "search.entityType.catalog.catalog_product_variant": "Variante",
61
+ "search.entityType.customer_accounts.customer_role": "Rol de cliente",
62
+ "search.entityType.customer_accounts.customer_user": "Usuario de cliente",
63
+ "search.entityType.customers.customer_activity": "Actividad",
64
+ "search.entityType.customers.customer_comment": "Comentario",
65
+ "search.entityType.customers.customer_company_profile": "Empresa",
66
+ "search.entityType.customers.customer_deal": "Oportunidad",
67
+ "search.entityType.customers.customer_entity": "Cliente",
68
+ "search.entityType.customers.customer_person_profile": "Persona",
69
+ "search.entityType.customers.customer_todo_link": "Tarea",
70
+ "search.entityType.inbox_ops.inbox_proposal": "Propuesta de bandeja",
71
+ "search.entityType.messages.message": "Mensaje",
72
+ "search.entityType.planner.planner_availability_rule_set": "Reglas de disponibilidad",
73
+ "search.entityType.resources.resources_resource": "Recurso",
74
+ "search.entityType.resources.resources_resource_type": "Tipo de recurso",
75
+ "search.entityType.sales.sales_channel": "Canal",
76
+ "search.entityType.sales.sales_credit_memo": "Nota de crédito",
77
+ "search.entityType.sales.sales_credit_memo_line": "Línea de nota de crédito",
78
+ "search.entityType.sales.sales_delivery_window": "Ventana de entrega",
79
+ "search.entityType.sales.sales_document_address": "Dirección del documento",
80
+ "search.entityType.sales.sales_document_tag": "Etiqueta de documento",
81
+ "search.entityType.sales.sales_invoice": "Factura",
82
+ "search.entityType.sales.sales_invoice_line": "Línea de factura",
83
+ "search.entityType.sales.sales_note": "Nota",
84
+ "search.entityType.sales.sales_order": "Pedido",
85
+ "search.entityType.sales.sales_order_adjustment": "Ajuste de pedido",
86
+ "search.entityType.sales.sales_order_line": "Línea de pedido",
87
+ "search.entityType.sales.sales_payment": "Pago",
88
+ "search.entityType.sales.sales_payment_allocation": "Asignación de pago",
89
+ "search.entityType.sales.sales_payment_method": "Método de pago",
90
+ "search.entityType.sales.sales_quote": "Presupuesto",
91
+ "search.entityType.sales.sales_quote_adjustment": "Ajuste de presupuesto",
92
+ "search.entityType.sales.sales_quote_line": "Línea de presupuesto",
93
+ "search.entityType.sales.sales_shipment": "Envío",
94
+ "search.entityType.sales.sales_shipment_item": "Artículo de envío",
95
+ "search.entityType.sales.sales_shipping_method": "Método de envío",
96
+ "search.entityType.sales.sales_tax_rate": "Tasa de impuesto",
97
+ "search.entityType.staff.staff_team": "Equipo",
98
+ "search.entityType.staff.staff_team_member": "Miembro del equipo",
99
+ "search.entityType.staff.staff_team_role": "Rol del equipo",
53
100
  "search.messages.missingConfig": "La búsqueda requiere configurar un proveedor de embeddings para la búsqueda semántica.",
54
101
  "search.nav.hybridSearch": "Búsqueda",
55
102
  "search.scopeHint.currentOrg": "Alcance: organización actual",
@@ -167,6 +214,8 @@
167
214
  "search.settings.vector.sectionTitle": "Búsqueda vectorial",
168
215
  "search.settings.vector.setEnvVar": "Configurar variable de entorno",
169
216
  "search.settings.vector.store": "Almacén vectorial",
217
+ "search.settings.vector.storeUnavailable": "El almacén vectorial no está disponible",
218
+ "search.settings.vector.storeUnavailableHint": "La indexación vectorial y la búsqueda semántica se omiten hasta que el almacén esté disponible. Instala la extensión pgvector en tu servidor PostgreSQL; el estado se vuelve a comprobar aproximadamente cada minuto.",
170
219
  "search.settings.vector.usingTenantConfig": "Este inquilino usa su propia configuración de incrustaciones.",
171
220
  "search.settings.vectorDocumentsLabel": "Documentos vectoriales",
172
221
  "search.settings.vectorNotConfigured": "La búsqueda vectorial no está configurada.",