@open-mercato/search 0.6.7-develop.6860.1.f796b22e80 → 0.6.7

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 +1 -7
  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 +2 -5
  6. package/dist/modules/search/ai-tools.js.map +2 -2
  7. package/dist/modules/search/api/openapi.js +0 -2
  8. package/dist/modules/search/api/openapi.js.map +2 -2
  9. package/dist/modules/search/api/settings/vector-store/route.js +0 -34
  10. package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
  11. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +9 -2
  12. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
  13. package/dist/modules/search/frontend/components/HybridSearchTable.js +11 -2
  14. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
  15. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
  16. package/dist/modules/search/frontend/components/TopbarSearchInline.js +9 -2
  17. package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
  18. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +3 -15
  19. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
  20. package/dist/modules/search/i18n/de.json +0 -49
  21. package/dist/modules/search/i18n/en.json +0 -49
  22. package/dist/modules/search/i18n/es.json +0 -49
  23. package/dist/modules/search/i18n/pl.json +0 -49
  24. package/dist/modules/search/lib/auto-indexing.js +8 -7
  25. package/dist/modules/search/lib/auto-indexing.js.map +2 -2
  26. package/dist/service.js +5 -2
  27. package/dist/service.js.map +2 -2
  28. package/dist/strategies/vector.strategy.js +1 -3
  29. package/dist/strategies/vector.strategy.js.map +2 -2
  30. package/dist/vector/drivers/pgvector/index.js +1 -55
  31. package/dist/vector/drivers/pgvector/index.js.map +2 -2
  32. package/dist/vector/types.js.map +2 -2
  33. package/package.json +10 -11
  34. package/src/__tests__/presenter-enricher.test.ts +0 -48
  35. package/src/lib/presenter-enricher.ts +14 -14
  36. package/src/modules/search/README.md +1 -4
  37. package/src/modules/search/ai-tools.ts +2 -5
  38. package/src/modules/search/api/openapi.ts +0 -2
  39. package/src/modules/search/api/settings/vector-store/route.ts +0 -43
  40. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +15 -2
  41. package/src/modules/search/frontend/components/HybridSearchTable.tsx +17 -2
  42. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +0 -2
  43. package/src/modules/search/frontend/components/TopbarSearchInline.tsx +15 -2
  44. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +2 -32
  45. package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +1 -36
  46. package/src/modules/search/i18n/de.json +0 -49
  47. package/src/modules/search/i18n/en.json +0 -49
  48. package/src/modules/search/i18n/es.json +0 -49
  49. package/src/modules/search/i18n/pl.json +0 -49
  50. package/src/modules/search/lib/auto-indexing.ts +12 -7
  51. package/src/service.ts +7 -2
  52. package/src/strategies/vector.strategy.ts +1 -7
  53. package/src/vector/drivers/pgvector/index.ts +1 -77
  54. package/src/vector/types.ts +0 -14
  55. package/dist/modules/search/events.js +0 -13
  56. package/dist/modules/search/events.js.map +0 -7
  57. package/dist/modules/search/frontend/lib/entityTypeLabel.js +0 -19
  58. package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +0 -7
  59. package/dist/modules/search/i18n/ko.json +0 -239
  60. package/src/__tests__/pgvector-extension-availability.test.ts +0 -158
  61. package/src/__tests__/presenter-locale.test.ts +0 -76
  62. package/src/modules/search/api/__tests__/global-search.routes.test.ts +0 -167
  63. package/src/modules/search/events.ts +0 -10
  64. package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +0 -25
  65. package/src/modules/search/frontend/lib/entityTypeLabel.ts +0 -22
  66. package/src/modules/search/i18n/ko.json +0 -239
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/search/api/settings/vector-store/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { VectorDriverId } from '@open-mercato/shared/modules/vector'\nimport type { VectorDriver } from '../../../../../vector'\nimport { vectorStoreSettingsOpenApi } from '../../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['search.view'] },\n}\n\ntype DriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n /** Runtime reachability of the store itself; `null` when the driver exposes no probe. */\n available: boolean | null\n unavailableReason: string | null\n envVars: {\n name: string\n set: boolean\n hint: string\n }[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: DriverStatus[]\n}\n\nasync function probeDriverStatus(\n container: { resolve: <T = unknown>(name: string) => T },\n driverId: VectorDriverId,\n): Promise<{ available: boolean | null; unavailableReason: string | null }> {\n try {\n const drivers = container.resolve<VectorDriver[]>('vectorDrivers')\n const driver = drivers.find((entry) => entry.id === driverId)\n if (!driver?.getStatus) return { available: null, unavailableReason: null }\n const status = await driver.getStatus()\n return {\n available: status.available,\n unavailableReason: status.available ? null : status.reason ?? null,\n }\n } catch {\n return { available: null, unavailableReason: null }\n }\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n // Check pgvector - uses existing DATABASE_URL\n const databaseUrlSet = Boolean(process.env.DATABASE_URL?.trim())\n\n // Check qdrant - would need QDRANT_URL\n const qdrantUrlSet = Boolean(process.env.QDRANT_URL?.trim())\n const qdrantApiKeySet = Boolean(process.env.QDRANT_API_KEY?.trim())\n\n // Check chromadb - would need CHROMA_URL\n const chromaUrlSet = Boolean(process.env.CHROMA_URL?.trim())\n\n let pgvectorStatus: { available: boolean | null; unavailableReason: string | null } = {\n available: null,\n unavailableReason: null,\n }\n if (databaseUrlSet) {\n const container = await createRequestContainer()\n try {\n pgvectorStatus = await probeDriverStatus(container, 'pgvector')\n } finally {\n const disposable = container as { dispose?: () => Promise<unknown> }\n if (typeof disposable.dispose === 'function') await disposable.dispose()\n }\n }\n\n const drivers: DriverStatus[] = [\n {\n id: 'pgvector',\n name: 'PostgreSQL (pgvector)',\n configured: databaseUrlSet,\n implemented: true,\n available: pgvectorStatus.available,\n unavailableReason: pgvectorStatus.unavailableReason,\n envVars: [\n {\n name: 'DATABASE_URL',\n set: databaseUrlSet,\n hint: 'PostgreSQL connection string with pgvector extension',\n },\n ],\n },\n {\n id: 'qdrant',\n name: 'Qdrant',\n configured: qdrantUrlSet,\n implemented: false,\n available: null,\n unavailableReason: null,\n envVars: [\n {\n name: 'QDRANT_URL',\n set: qdrantUrlSet,\n hint: 'URL of your Qdrant server (e.g., http://localhost:6333)',\n },\n {\n name: 'QDRANT_API_KEY',\n set: qdrantApiKeySet,\n hint: 'API key for Qdrant authentication (optional for local)',\n },\n ],\n },\n {\n id: 'chromadb',\n name: 'ChromaDB',\n configured: chromaUrlSet,\n implemented: false,\n available: null,\n unavailableReason: null,\n envVars: [\n {\n name: 'CHROMA_URL',\n set: chromaUrlSet,\n hint: 'URL of your ChromaDB server (e.g., http://localhost:8000)',\n },\n ],\n },\n ]\n\n // Currently only pgvector is supported\n const currentDriver: VectorDriverId = 'pgvector'\n const configured = databaseUrlSet\n\n const response: VectorStoreConfigResponse = {\n currentDriver,\n configured,\n drivers,\n }\n\n return NextResponse.json(response)\n}\n\nexport const openApi = vectorStoreSettingsOpenApi\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { VectorDriverId } from '@open-mercato/shared/modules/vector'\nimport { vectorStoreSettingsOpenApi } from '../../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['search.view'] },\n}\n\ntype DriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n envVars: {\n name: string\n set: boolean\n hint: string\n }[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: DriverStatus[]\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n // Check pgvector - uses existing DATABASE_URL\n const databaseUrlSet = Boolean(process.env.DATABASE_URL?.trim())\n\n // Check qdrant - would need QDRANT_URL\n const qdrantUrlSet = Boolean(process.env.QDRANT_URL?.trim())\n const qdrantApiKeySet = Boolean(process.env.QDRANT_API_KEY?.trim())\n\n // Check chromadb - would need CHROMA_URL\n const chromaUrlSet = Boolean(process.env.CHROMA_URL?.trim())\n\n const drivers: DriverStatus[] = [\n {\n id: 'pgvector',\n name: 'PostgreSQL (pgvector)',\n configured: databaseUrlSet,\n implemented: true,\n envVars: [\n {\n name: 'DATABASE_URL',\n set: databaseUrlSet,\n hint: 'PostgreSQL connection string with pgvector extension',\n },\n ],\n },\n {\n id: 'qdrant',\n name: 'Qdrant',\n configured: qdrantUrlSet,\n implemented: false,\n envVars: [\n {\n name: 'QDRANT_URL',\n set: qdrantUrlSet,\n hint: 'URL of your Qdrant server (e.g., http://localhost:6333)',\n },\n {\n name: 'QDRANT_API_KEY',\n set: qdrantApiKeySet,\n hint: 'API key for Qdrant authentication (optional for local)',\n },\n ],\n },\n {\n id: 'chromadb',\n name: 'ChromaDB',\n configured: chromaUrlSet,\n implemented: false,\n envVars: [\n {\n name: 'CHROMA_URL',\n set: chromaUrlSet,\n hint: 'URL of your ChromaDB server (e.g., http://localhost:8000)',\n },\n ],\n },\n ]\n\n // Currently only pgvector is supported\n const currentDriver: VectorDriverId = 'pgvector'\n const configured = databaseUrlSet\n\n const response: VectorStoreConfigResponse = {\n currentDriver,\n configured,\n drivers,\n }\n\n return NextResponse.json(response)\n}\n\nexport const openApi = vectorStoreSettingsOpenApi\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAEpC,SAAS,kCAAkC;AAEpC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,aAAa,EAAE;AAC7D;AAoBA,MAAM,eAAe,YAAY;AAC/B,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,SAAO,aAAa,KAAK,EAAE,OAAO,EAAE,2BAA2B,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AACnG;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,IAAK,QAAO,MAAM,aAAa;AAG1C,QAAM,iBAAiB,QAAQ,QAAQ,IAAI,cAAc,KAAK,CAAC;AAG/D,QAAM,eAAe,QAAQ,QAAQ,IAAI,YAAY,KAAK,CAAC;AAC3D,QAAM,kBAAkB,QAAQ,QAAQ,IAAI,gBAAgB,KAAK,CAAC;AAGlE,QAAM,eAAe,QAAQ,QAAQ,IAAI,YAAY,KAAK,CAAC;AAE3D,QAAM,UAA0B;AAAA,IAC9B;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,MAAM;AAAA,QACR;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgC;AACtC,QAAM,aAAa;AAEnB,QAAM,WAAsC;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,aAAa,KAAK,QAAQ;AACnC;AAEO,MAAM,UAAU;",
6
6
  "names": []
7
7
  }
@@ -47,7 +47,6 @@ import { Input } from "@open-mercato/ui/primitives/input";
47
47
  import { Button } from "@open-mercato/ui/primitives/button";
48
48
  import { cn } from "@open-mercato/shared/lib/utils";
49
49
  import { useT } from "@open-mercato/shared/lib/i18n/context";
50
- import { resolveEntityTypeLabel } from "../lib/entityTypeLabel.js";
51
50
  import {
52
51
  getCurrentOrganizationScope,
53
52
  subscribeOrganizationScopeChanged
@@ -78,6 +77,9 @@ function hasActiveOrganizationSelection() {
78
77
  if (!cookieValue) return false;
79
78
  return !isAllOrganizationsSelection(cookieValue);
80
79
  }
80
+ function humanizeSegment(segment) {
81
+ return segment.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
82
+ }
81
83
  const ICON_MAP = {
82
84
  bolt: Zap,
83
85
  zap: Zap,
@@ -121,6 +123,11 @@ function resolveIcon(name) {
121
123
  if (!name) return null;
122
124
  return ICON_MAP[name.toLowerCase()] ?? null;
123
125
  }
126
+ function formatEntityId(entityId) {
127
+ if (!entityId.includes(":")) return humanizeSegment(entityId);
128
+ const [module, entity] = entityId.split(":");
129
+ return `${humanizeSegment(module)} \xB7 ${humanizeSegment(entity)}`;
130
+ }
124
131
  function GlobalSearchDialog({
125
132
  embeddingConfigured,
126
133
  missingConfigMessage,
@@ -352,7 +359,7 @@ function GlobalSearchDialog({
352
359
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
353
360
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
354
361
  /* @__PURE__ */ jsx("span", { className: cn("font-medium text-base whitespace-normal break-all", !hasLink && "text-muted-foreground"), children: presenter?.title ?? result.recordId }),
355
- /* @__PURE__ */ jsx("span", { className: "rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground", children: resolveEntityTypeLabel(t, result.entityId) }),
362
+ /* @__PURE__ */ jsx("span", { className: "rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground", children: formatEntityId(result.entityId) }),
356
363
  !hasLink && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text", children: t("search.dialog.noLink") })
357
364
  ] }),
358
365
  presenter?.subtitle ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground whitespace-normal break-words", children: presenter.subtitle }) : null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/frontend/components/GlobalSearchDialog.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link,\n Folder,\n Database,\n Activity,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { resolveEntityTypeLabel } from '../lib/entityTypeLabel'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\n\n/** Default strategies used when none are configured */\nconst DEFAULT_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: Link,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nexport type GlobalSearchDialogProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Message to show when embedding is not configured */\n missingConfigMessage: string\n /** Enabled strategies from tenant configuration (optional - uses defaults if not provided) */\n enabledStrategies?: SearchStrategyId[]\n}\n\nexport function GlobalSearchDialog({\n embeddingConfigured,\n missingConfigMessage,\n enabledStrategies: propStrategies,\n}: GlobalSearchDialogProps) {\n const router = useRouter()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Use configured strategies or fall back to defaults\n const enabledStrategies = React.useMemo(() => {\n if (propStrategies && propStrategies.length > 0) {\n return propStrategies\n }\n return DEFAULT_STRATEGIES\n }, [propStrategies])\n\n const resetState = React.useCallback(() => {\n setQuery('')\n setResults([])\n setError(null)\n setSelectedIndex(0)\n setLoading(false)\n }, [])\n\n React.useEffect(() => {\n if (!open) {\n resetState()\n return\n }\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, resetState])\n\n React.useEffect(() => {\n const shortcut = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n setOpen((prev) => !prev)\n }\n }\n window.addEventListener('keydown', shortcut)\n return () => window.removeEventListener('keydown', shortcut)\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const focusTimer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(focusTimer)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n\n abortRef.current?.abort()\n if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [open, query, enabledStrategies, t])\n\n const openResult = React.useCallback((result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n setOpen(false)\n }, [router])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n event.preventDefault()\n openResult(results[selectedIndex])\n return\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setSelectedIndex((prev) => (prev + 1) % Math.max(results.length || 1, 1))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setOpen(false)\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n }, [results, selectedIndex, openResult])\n\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n // Check if vector search is enabled but not configured\n const showVectorWarning = !embeddingConfigured && enabledStrategies.includes('vector') && !error\n\n // Check if selected result has a navigable link\n const selectedResult = results[selectedIndex]\n const selectedHasLink = selectedResult ? pickPrimaryLink(selectedResult) !== null : false\n\n return (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n className=\"hidden sm:inline-flex items-center gap-2 text-foreground\"\n >\n <Search className=\"h-4 w-4 text-foreground\" />\n <span>{t('search.dialog.actions.search')}</span>\n <span className=\"ml-2 rounded border px-1 text-xs text-muted-foreground\">\u2318K</span>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"sm:hidden\"\n onClick={() => setOpen(true)}\n aria-label={t('search.dialog.actions.openGlobalSearch')}\n >\n <Search className=\"h-4 w-4 text-foreground\" />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-xl p-0\" aria-describedby=\"global-search-description\">\n <DialogTitle className=\"sr-only\">\n {t('search.dialog.title', 'Global Search')}\n </DialogTitle>\n <span id=\"global-search-description\" className=\"sr-only\">\n {t('search.dialog.instructions')}\n </span>\n <div className=\"flex flex-col gap-3 border-b px-4 pb-3 pt-12\">\n <div className=\"flex items-center gap-2 rounded border border-border bg-background px-3 py-2 transition-colors focus-within:border-primary\">\n <Search className=\"h-4 w-4 text-muted-foreground\" />\n <TypedInput\n ref={inputRef}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.input.placeholder')}\n className=\"border-none px-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n autoFocus\n />\n {loading && <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />}\n </div>\n\n {error ? (\n <p className=\"rounded bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning ? (\n <p className=\"rounded bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text\">{missingConfigMessage}</p>\n ) : null}\n {showScopeHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n </div>\n <div ref={listRef} className=\"max-h-96 overflow-y-auto px-2 pb-3\">\n {results.length === 0 && !loading && !error ? (\n <div className=\"px-4 py-6 text-sm text-muted-foreground\">\n {query.trim().length < MIN_QUERY_LENGTH\n ? t('search.dialog.empty.hint', { count: MIN_QUERY_LENGTH })\n : t('search.dialog.empty.none')}\n </div>\n ) : null}\n <ul className=\"flex flex-col\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <li key={`${result.entityId}:${result.recordId}`} data-active={isActive}>\n <button\n type=\"button\"\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n className={cn(\n 'w-full rounded-lg px-4 py-3 text-left transition border',\n isActive\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-transparent hover:border-muted-foreground/30 hover:bg-muted/50',\n !hasLink && 'opacity-60'\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={cn('font-medium text-base whitespace-normal break-all', !hasLink && 'text-muted-foreground')}>{presenter?.title ?? result.recordId}</span>\n <span className=\"rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground\">\n {resolveEntityTypeLabel(t, result.entityId)}\n </span>\n {!hasLink && (\n <span className=\"rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text\">\n {t('search.dialog.noLink')}\n </span>\n )}\n </div>\n {presenter?.subtitle ? (\n <div className=\"text-sm text-muted-foreground whitespace-normal break-words\">{presenter.subtitle}</div>\n ) : null}\n {normalizeLinks(result.links).length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {normalizeLinks(result.links).map((link) => (\n <span\n key={`${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-accent-indigo text-foreground'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n {Icon ? (\n <div className=\"flex flex-col items-end gap-2\">\n <Icon className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n ) : null}\n </div>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n <div className=\"flex items-center justify-between border-t px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n {selectedResult && !selectedHasLink\n ? t('search.dialog.noLinkHint')\n : t('search.dialog.shortcuts.hint')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)}>\n {t('search.dialog.actions.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openResult(results[selectedIndex])}\n disabled={!results.length || !selectedHasLink}\n >\n {t('search.dialog.actions.openSelected')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nexport default GlobalSearchDialog\nconst TypedInput = Input as React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link,\n Folder,\n Database,\n Activity,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\n\n/** Default strategies used when none are configured */\nconst DEFAULT_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: Link,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n return `${humanizeSegment(module)} \u00B7 ${humanizeSegment(entity)}`\n}\n\nexport type GlobalSearchDialogProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Message to show when embedding is not configured */\n missingConfigMessage: string\n /** Enabled strategies from tenant configuration (optional - uses defaults if not provided) */\n enabledStrategies?: SearchStrategyId[]\n}\n\nexport function GlobalSearchDialog({\n embeddingConfigured,\n missingConfigMessage,\n enabledStrategies: propStrategies,\n}: GlobalSearchDialogProps) {\n const router = useRouter()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Use configured strategies or fall back to defaults\n const enabledStrategies = React.useMemo(() => {\n if (propStrategies && propStrategies.length > 0) {\n return propStrategies\n }\n return DEFAULT_STRATEGIES\n }, [propStrategies])\n\n const resetState = React.useCallback(() => {\n setQuery('')\n setResults([])\n setError(null)\n setSelectedIndex(0)\n setLoading(false)\n }, [])\n\n React.useEffect(() => {\n if (!open) {\n resetState()\n return\n }\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, resetState])\n\n React.useEffect(() => {\n const shortcut = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n setOpen((prev) => !prev)\n }\n }\n window.addEventListener('keydown', shortcut)\n return () => window.removeEventListener('keydown', shortcut)\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const focusTimer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(focusTimer)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n\n abortRef.current?.abort()\n if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [open, query, enabledStrategies, t])\n\n const openResult = React.useCallback((result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n setOpen(false)\n }, [router])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n event.preventDefault()\n openResult(results[selectedIndex])\n return\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setSelectedIndex((prev) => (prev + 1) % Math.max(results.length || 1, 1))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setOpen(false)\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n }, [results, selectedIndex, openResult])\n\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n // Check if vector search is enabled but not configured\n const showVectorWarning = !embeddingConfigured && enabledStrategies.includes('vector') && !error\n\n // Check if selected result has a navigable link\n const selectedResult = results[selectedIndex]\n const selectedHasLink = selectedResult ? pickPrimaryLink(selectedResult) !== null : false\n\n return (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n className=\"hidden sm:inline-flex items-center gap-2 text-foreground\"\n >\n <Search className=\"h-4 w-4 text-foreground\" />\n <span>{t('search.dialog.actions.search')}</span>\n <span className=\"ml-2 rounded border px-1 text-xs text-muted-foreground\">\u2318K</span>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"sm:hidden\"\n onClick={() => setOpen(true)}\n aria-label={t('search.dialog.actions.openGlobalSearch')}\n >\n <Search className=\"h-4 w-4 text-foreground\" />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-xl p-0\" aria-describedby=\"global-search-description\">\n <DialogTitle className=\"sr-only\">\n {t('search.dialog.title', 'Global Search')}\n </DialogTitle>\n <span id=\"global-search-description\" className=\"sr-only\">\n {t('search.dialog.instructions')}\n </span>\n <div className=\"flex flex-col gap-3 border-b px-4 pb-3 pt-12\">\n <div className=\"flex items-center gap-2 rounded border border-border bg-background px-3 py-2 transition-colors focus-within:border-primary\">\n <Search className=\"h-4 w-4 text-muted-foreground\" />\n <TypedInput\n ref={inputRef}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.input.placeholder')}\n className=\"border-none px-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n autoFocus\n />\n {loading && <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />}\n </div>\n\n {error ? (\n <p className=\"rounded bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning ? (\n <p className=\"rounded bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text\">{missingConfigMessage}</p>\n ) : null}\n {showScopeHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n </div>\n <div ref={listRef} className=\"max-h-96 overflow-y-auto px-2 pb-3\">\n {results.length === 0 && !loading && !error ? (\n <div className=\"px-4 py-6 text-sm text-muted-foreground\">\n {query.trim().length < MIN_QUERY_LENGTH\n ? t('search.dialog.empty.hint', { count: MIN_QUERY_LENGTH })\n : t('search.dialog.empty.none')}\n </div>\n ) : null}\n <ul className=\"flex flex-col\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <li key={`${result.entityId}:${result.recordId}`} data-active={isActive}>\n <button\n type=\"button\"\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n className={cn(\n 'w-full rounded-lg px-4 py-3 text-left transition border',\n isActive\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-transparent hover:border-muted-foreground/30 hover:bg-muted/50',\n !hasLink && 'opacity-60'\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={cn('font-medium text-base whitespace-normal break-all', !hasLink && 'text-muted-foreground')}>{presenter?.title ?? result.recordId}</span>\n <span className=\"rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground\">\n {formatEntityId(result.entityId)}\n </span>\n {!hasLink && (\n <span className=\"rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text\">\n {t('search.dialog.noLink')}\n </span>\n )}\n </div>\n {presenter?.subtitle ? (\n <div className=\"text-sm text-muted-foreground whitespace-normal break-words\">{presenter.subtitle}</div>\n ) : null}\n {normalizeLinks(result.links).length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {normalizeLinks(result.links).map((link) => (\n <span\n key={`${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-accent-indigo text-foreground'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n {Icon ? (\n <div className=\"flex flex-col items-end gap-2\">\n <Icon className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n ) : null}\n </div>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n <div className=\"flex items-center justify-between border-t px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n {selectedResult && !selectedHasLink\n ? t('search.dialog.noLinkHint')\n : t('search.dialog.shortcuts.hint')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)}>\n {t('search.dialog.actions.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openResult(results[selectedIndex])}\n disabled={!results.length || !selectedHasLink}\n >\n {t('search.dialog.actions.openSelected')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nexport default GlobalSearchDialog\nconst TypedInput = Input as React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,6 @@ import { RowActions } from "@open-mercato/ui/backend/RowActions";
8
8
  import { cn } from "@open-mercato/shared/lib/utils";
9
9
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
10
10
  import { useT } from "@open-mercato/shared/lib/i18n/context";
11
- import { resolveEntityTypeLabel } from "../lib/entityTypeLabel.js";
12
11
  import {
13
12
  getCurrentOrganizationScope,
14
13
  subscribeOrganizationScopeChanged
@@ -37,7 +36,7 @@ function createColumns(t) {
37
36
  const title = resolveRowTitle(item);
38
37
  const iconName = item.presenter?.icon;
39
38
  const Icon = iconName ? resolveIcon(iconName) : null;
40
- const typeLabel = resolveEntityTypeLabel(t, item.entityId);
39
+ const typeLabel = formatEntityId(item.entityId);
41
40
  const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata);
42
41
  const links = normalizeLinks(item.links);
43
42
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
@@ -110,6 +109,16 @@ function resolveIcon(name) {
110
109
  }
111
110
  return null;
112
111
  }
112
+ function humanizeSegment(segment) {
113
+ return segment.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
114
+ }
115
+ function formatEntityId(entityId) {
116
+ if (!entityId.includes(":")) return humanizeSegment(entityId);
117
+ const [module, entity] = entityId.split(":");
118
+ const moduleLabel = humanizeSegment(module);
119
+ const entityLabel = humanizeSegment(entity);
120
+ return `${moduleLabel} \xB7 ${entityLabel}`;
121
+ }
113
122
  function resolveRowTitle(row) {
114
123
  const presenterTitle = row.presenter?.title;
115
124
  if (typeof presenterTitle === "string") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/frontend/components/HybridSearchTable.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport * as LucideIcons from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { resolveEntityTypeLabel } from '../lib/entityTypeLabel'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchHybridSearchResults } from '../utils'\n\ntype Row = {\n entityId: string\n recordId: string\n source: string\n score: number | null\n url: string | null\n presenter: SearchResult['presenter'] | null\n links: SearchResult['links'] | null\n metadata: Record<string, unknown> | null\n}\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\nconst ALL_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\ntype Translator = (\n key: string,\n fallbackOrParams?: string | Record<string, string | number>,\n params?: Record<string, string | number>\n) => string\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'title',\n header: () => t('search.table.columns.result', 'Result'),\n cell: ({ row }) => {\n const item = row.original\n const title = resolveRowTitle(item)\n const iconName = item.presenter?.icon\n const Icon = iconName ? resolveIcon(iconName) : null\n const typeLabel = resolveEntityTypeLabel(t, item.entityId)\n const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata)\n const links = normalizeLinks(item.links)\n return (\n <div className=\"flex flex-col\">\n <div className=\"flex items-start gap-3\">\n {Icon ? <Icon className=\"mt-0.5 h-5 w-5 text-muted-foreground\" /> : null}\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium whitespace-normal break-all\">{title}</span>\n <span className=\"rounded border border-muted-foreground/40 px-2 py-0.5 text-xs text-muted-foreground\">\n {typeLabel}\n </span>\n </div>\n {snapshot ? (\n <span className=\"text-sm text-muted-foreground whitespace-normal break-words\">{snapshot}</span>\n ) : null}\n {links.length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {links.map((link) => (\n <span\n key={`${item.entityId}:${item.recordId}:${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-accent-indigo text-foreground'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </div>\n )\n },\n meta: { priority: 1 },\n },\n {\n id: 'source',\n header: () => t('search.table.columns.source', 'Source'),\n cell: ({ row }) => {\n const source = row.original.source\n const colorClass = getStrategyColorClass(source)\n return (\n <span className={cn('rounded px-2 py-0.5 text-xs font-medium', colorClass)}>\n {source}\n </span>\n )\n },\n meta: { priority: 2 },\n },\n {\n id: 'score',\n header: () => t('search.table.columns.score', 'Score'),\n cell: ({ row }) => <span>{row.original.score != null ? row.original.score.toFixed(2) : '\u2014'}</span>,\n meta: { priority: 2 },\n },\n ]\n}\n\nfunction getStrategyColorClass(strategy: string): string {\n switch (strategy) {\n case 'fulltext':\n return 'bg-status-info-bg text-status-info-text'\n case 'vector':\n return 'bg-brand-violet/10 text-brand-violet'\n case 'tokens':\n return 'bg-status-success-bg text-status-success-text'\n default:\n return 'bg-status-neutral-bg text-status-neutral-text'\n }\n}\n\nfunction normalizeLinks(links?: Row['links']): { href: string; label?: string; kind?: string }[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string') as Array<{ href: string; label?: string; kind?: string }>\n}\n\nfunction toPascalCase(input: string): string {\n return input\n .split(/[-_ ]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n const key = toPascalCase(name)\n const candidate = (LucideIcons as Record<string, unknown>)[key]\n if (typeof candidate === 'function') {\n return candidate as LucideIcon\n }\n return null\n}\n\nfunction resolveRowTitle(row: Row): string {\n const presenterTitle = row.presenter?.title\n if (typeof presenterTitle === 'string') {\n const trimmed = presenterTitle.trim()\n if (trimmed.length) return trimmed\n }\n return row.recordId\n}\n\nfunction extractSnapshot(metadata: Record<string, unknown> | null): string | null {\n if (!metadata) return null\n const candidateKeys = ['snapshot', 'summary', 'description', 'body', 'content', 'note']\n for (const key of candidateKeys) {\n const value = metadata[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n}\n\nfunction pickPrimaryLink(row: Row): string | null {\n if (row.url) return row.url\n const links = normalizeLinks(row.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction normalizeErrorMessage(input: unknown, fallback?: string): string | null {\n const fallbackMessage = typeof fallback === 'string' && fallback.trim().length ? fallback.trim() : null\n let message: string | null = null\n if (typeof input === 'string') {\n message = input\n } else if (input instanceof Error && typeof input.message === 'string') {\n message = input.message\n }\n if (message) {\n const trimmed = message.trim()\n if (trimmed.length) {\n const sanitized = trimmed.replace(/^\\[[^\\]]+\\]\\s*/, '').trim()\n if (sanitized.length) return sanitized\n }\n }\n return fallbackMessage\n}\n\ntype HybridSearchTableProps = {\n /** Show strategy selector checkboxes (default: false - hidden from regular users) */\n showStrategySelector?: boolean\n /** Show source column in results (default: false - hidden from regular users) */\n showSourceColumn?: boolean\n}\n\nexport function HybridSearchTable({\n showStrategySelector = false,\n showSourceColumn = false,\n}: HybridSearchTableProps = {}) {\n const router = useRouter()\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [searchValue, setSearchValue] = React.useState('')\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [timing, setTiming] = React.useState<number | null>(null)\n const [strategiesUsed, setStrategiesUsed] = React.useState<string[]>([])\n const [enabledStrategies, setEnabledStrategies] = React.useState<Set<SearchStrategyId>>(\n new Set(ALL_STRATEGIES)\n )\n const debounceRef = React.useRef<number | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const columns = React.useMemo(() => {\n const allColumns = createColumns(t)\n if (!showSourceColumn) {\n return allColumns.filter((col) => col.id !== 'source')\n }\n return allColumns\n }, [t, showSourceColumn])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n const toggleStrategy = React.useCallback((strategy: SearchStrategyId) => {\n setEnabledStrategies((prev) => {\n const next = new Set(prev)\n if (next.has(strategy)) {\n next.delete(strategy)\n } else {\n next.add(strategy)\n }\n return next\n })\n }, [])\n\n const openRow = React.useCallback(\n (row: Row) => {\n const href = pickPrimaryLink(row)\n if (!href) return\n router.push(href)\n },\n [router]\n )\n\n React.useEffect(() => {\n const trimmed = searchValue.trim()\n abortRef.current?.abort()\n if (debounceRef.current) {\n window.clearTimeout(debounceRef.current)\n debounceRef.current = null\n }\n\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(null)\n setLoading(false)\n return\n }\n\n if (enabledStrategies.size === 0) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(t('search.table.errors.noSources', 'Select at least one search source'))\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n debounceRef.current = window.setTimeout(async () => {\n try {\n const data = await fetchHybridSearchResults(trimmed, {\n limit: 50,\n strategies: Array.from(enabledStrategies),\n signal: controller.signal,\n })\n const mapped = data.results.map<Row>((item) => ({\n entityId: item.entityId,\n recordId: item.recordId,\n source: item.source,\n score: typeof item.score === 'number' ? item.score : null,\n url: item.url ?? null,\n presenter: item.presenter ?? null,\n links: item.links ?? null,\n metadata: (item.metadata as Record<string, unknown> | null) ?? null,\n }))\n setRows(mapped)\n setTiming(data.timing)\n setStrategiesUsed(data.strategiesUsed)\n const message = data.error ? normalizeErrorMessage(data.error, t('search.table.errors.searchFailed', 'Search failed')) : null\n setError(message ?? null)\n setPage(1)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n if ((err as { name?: string })?.name === 'AbortError') return\n setError(normalizeErrorMessage(err, t('search.table.errors.searchFailed', 'Search failed')))\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 250)\n\n return () => {\n controller.abort()\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n }\n }, [searchValue, enabledStrategies, t])\n\n React.useEffect(() => {\n if (!error) return\n flash(error, 'error')\n }, [error])\n\n return (\n <div className=\"flex w-full flex-col gap-4\">\n {/* Source Selector - only shown when showStrategySelector is true */}\n {showStrategySelector && (\n <div className=\"flex flex-wrap items-center gap-4 rounded-lg border bg-muted/50 p-3\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('search.table.sources', 'Sources:')}\n </span>\n {ALL_STRATEGIES.map((strategy) => (\n <label key={strategy} className=\"flex cursor-pointer items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"size-4 rounded border-input\"\n checked={enabledStrategies.has(strategy)}\n onChange={() => toggleStrategy(strategy)}\n />\n <span\n className={cn(\n 'rounded px-2 py-0.5 text-xs font-medium',\n getStrategyColorClass(strategy)\n )}\n >\n {strategy}\n </span>\n </label>\n ))}\n </div>\n )}\n\n {/* Stats Bar */}\n {timing !== null && rows.length > 0 && (\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{rows.length} {t('search.table.stats.results', 'results')}</span>\n <span>{timing}ms</span>\n {/* Only show sources when strategy selector is visible */}\n {showStrategySelector && strategiesUsed.length > 0 && (\n <span>\n {t('search.table.stats.sources', 'Sources:')} {strategiesUsed.join(', ')}\n </span>\n )}\n </div>\n )}\n\n {/* Error Alert */}\n {error ? (\n <div\n role=\"alert\"\n className=\"w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\"\n >\n {error}\n </div>\n ) : null}\n\n {showScopeHint ? (\n <div className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </div>\n ) : null}\n\n {/* Data Table */}\n <DataTable<Row>\n title={t('search.table.title', 'Search')}\n columns={columns}\n data={rows}\n searchValue={searchValue}\n onSearchChange={(value) => {\n setSearchValue(value)\n setPage(1)\n }}\n searchPlaceholder={t('search.table.searchPlaceholder', 'Search across all strategies...')}\n isLoading={loading}\n pagination={{ page, pageSize: rows.length || 1, total: rows.length, totalPages: 1, onPageChange: setPage }}\n onRowClick={(row) => openRow(row)}\n rowActions={(row) => {\n const primaryHref = pickPrimaryLink(row)\n if (!primaryHref) return null\n return <RowActions items={[{ id: 'open', label: t('search.table.actions.open', 'Open'), href: primaryHref }]} />\n }}\n embedded\n />\n </div>\n )\n}\n\nexport default HybridSearchTable\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport * as LucideIcons from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchHybridSearchResults } from '../utils'\n\ntype Row = {\n entityId: string\n recordId: string\n source: string\n score: number | null\n url: string | null\n presenter: SearchResult['presenter'] | null\n links: SearchResult['links'] | null\n metadata: Record<string, unknown> | null\n}\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\nconst ALL_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\ntype Translator = (\n key: string,\n fallbackOrParams?: string | Record<string, string | number>,\n params?: Record<string, string | number>\n) => string\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'title',\n header: () => t('search.table.columns.result', 'Result'),\n cell: ({ row }) => {\n const item = row.original\n const title = resolveRowTitle(item)\n const iconName = item.presenter?.icon\n const Icon = iconName ? resolveIcon(iconName) : null\n const typeLabel = formatEntityId(item.entityId)\n const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata)\n const links = normalizeLinks(item.links)\n return (\n <div className=\"flex flex-col\">\n <div className=\"flex items-start gap-3\">\n {Icon ? <Icon className=\"mt-0.5 h-5 w-5 text-muted-foreground\" /> : null}\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium whitespace-normal break-all\">{title}</span>\n <span className=\"rounded border border-muted-foreground/40 px-2 py-0.5 text-xs text-muted-foreground\">\n {typeLabel}\n </span>\n </div>\n {snapshot ? (\n <span className=\"text-sm text-muted-foreground whitespace-normal break-words\">{snapshot}</span>\n ) : null}\n {links.length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {links.map((link) => (\n <span\n key={`${item.entityId}:${item.recordId}:${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-accent-indigo text-foreground'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </div>\n )\n },\n meta: { priority: 1 },\n },\n {\n id: 'source',\n header: () => t('search.table.columns.source', 'Source'),\n cell: ({ row }) => {\n const source = row.original.source\n const colorClass = getStrategyColorClass(source)\n return (\n <span className={cn('rounded px-2 py-0.5 text-xs font-medium', colorClass)}>\n {source}\n </span>\n )\n },\n meta: { priority: 2 },\n },\n {\n id: 'score',\n header: () => t('search.table.columns.score', 'Score'),\n cell: ({ row }) => <span>{row.original.score != null ? row.original.score.toFixed(2) : '\u2014'}</span>,\n meta: { priority: 2 },\n },\n ]\n}\n\nfunction getStrategyColorClass(strategy: string): string {\n switch (strategy) {\n case 'fulltext':\n return 'bg-status-info-bg text-status-info-text'\n case 'vector':\n return 'bg-brand-violet/10 text-brand-violet'\n case 'tokens':\n return 'bg-status-success-bg text-status-success-text'\n default:\n return 'bg-status-neutral-bg text-status-neutral-text'\n }\n}\n\nfunction normalizeLinks(links?: Row['links']): { href: string; label?: string; kind?: string }[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string') as Array<{ href: string; label?: string; kind?: string }>\n}\n\nfunction toPascalCase(input: string): string {\n return input\n .split(/[-_ ]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n const key = toPascalCase(name)\n const candidate = (LucideIcons as Record<string, unknown>)[key]\n if (typeof candidate === 'function') {\n return candidate as LucideIcon\n }\n return null\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n const moduleLabel = humanizeSegment(module)\n const entityLabel = humanizeSegment(entity)\n return `${moduleLabel} \u00B7 ${entityLabel}`\n}\n\nfunction resolveRowTitle(row: Row): string {\n const presenterTitle = row.presenter?.title\n if (typeof presenterTitle === 'string') {\n const trimmed = presenterTitle.trim()\n if (trimmed.length) return trimmed\n }\n return row.recordId\n}\n\nfunction extractSnapshot(metadata: Record<string, unknown> | null): string | null {\n if (!metadata) return null\n const candidateKeys = ['snapshot', 'summary', 'description', 'body', 'content', 'note']\n for (const key of candidateKeys) {\n const value = metadata[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n}\n\nfunction pickPrimaryLink(row: Row): string | null {\n if (row.url) return row.url\n const links = normalizeLinks(row.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction normalizeErrorMessage(input: unknown, fallback?: string): string | null {\n const fallbackMessage = typeof fallback === 'string' && fallback.trim().length ? fallback.trim() : null\n let message: string | null = null\n if (typeof input === 'string') {\n message = input\n } else if (input instanceof Error && typeof input.message === 'string') {\n message = input.message\n }\n if (message) {\n const trimmed = message.trim()\n if (trimmed.length) {\n const sanitized = trimmed.replace(/^\\[[^\\]]+\\]\\s*/, '').trim()\n if (sanitized.length) return sanitized\n }\n }\n return fallbackMessage\n}\n\ntype HybridSearchTableProps = {\n /** Show strategy selector checkboxes (default: false - hidden from regular users) */\n showStrategySelector?: boolean\n /** Show source column in results (default: false - hidden from regular users) */\n showSourceColumn?: boolean\n}\n\nexport function HybridSearchTable({\n showStrategySelector = false,\n showSourceColumn = false,\n}: HybridSearchTableProps = {}) {\n const router = useRouter()\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [searchValue, setSearchValue] = React.useState('')\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [timing, setTiming] = React.useState<number | null>(null)\n const [strategiesUsed, setStrategiesUsed] = React.useState<string[]>([])\n const [enabledStrategies, setEnabledStrategies] = React.useState<Set<SearchStrategyId>>(\n new Set(ALL_STRATEGIES)\n )\n const debounceRef = React.useRef<number | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const columns = React.useMemo(() => {\n const allColumns = createColumns(t)\n if (!showSourceColumn) {\n return allColumns.filter((col) => col.id !== 'source')\n }\n return allColumns\n }, [t, showSourceColumn])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n const toggleStrategy = React.useCallback((strategy: SearchStrategyId) => {\n setEnabledStrategies((prev) => {\n const next = new Set(prev)\n if (next.has(strategy)) {\n next.delete(strategy)\n } else {\n next.add(strategy)\n }\n return next\n })\n }, [])\n\n const openRow = React.useCallback(\n (row: Row) => {\n const href = pickPrimaryLink(row)\n if (!href) return\n router.push(href)\n },\n [router]\n )\n\n React.useEffect(() => {\n const trimmed = searchValue.trim()\n abortRef.current?.abort()\n if (debounceRef.current) {\n window.clearTimeout(debounceRef.current)\n debounceRef.current = null\n }\n\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(null)\n setLoading(false)\n return\n }\n\n if (enabledStrategies.size === 0) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(t('search.table.errors.noSources', 'Select at least one search source'))\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n debounceRef.current = window.setTimeout(async () => {\n try {\n const data = await fetchHybridSearchResults(trimmed, {\n limit: 50,\n strategies: Array.from(enabledStrategies),\n signal: controller.signal,\n })\n const mapped = data.results.map<Row>((item) => ({\n entityId: item.entityId,\n recordId: item.recordId,\n source: item.source,\n score: typeof item.score === 'number' ? item.score : null,\n url: item.url ?? null,\n presenter: item.presenter ?? null,\n links: item.links ?? null,\n metadata: (item.metadata as Record<string, unknown> | null) ?? null,\n }))\n setRows(mapped)\n setTiming(data.timing)\n setStrategiesUsed(data.strategiesUsed)\n const message = data.error ? normalizeErrorMessage(data.error, t('search.table.errors.searchFailed', 'Search failed')) : null\n setError(message ?? null)\n setPage(1)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n if ((err as { name?: string })?.name === 'AbortError') return\n setError(normalizeErrorMessage(err, t('search.table.errors.searchFailed', 'Search failed')))\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 250)\n\n return () => {\n controller.abort()\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n }\n }, [searchValue, enabledStrategies, t])\n\n React.useEffect(() => {\n if (!error) return\n flash(error, 'error')\n }, [error])\n\n return (\n <div className=\"flex w-full flex-col gap-4\">\n {/* Source Selector - only shown when showStrategySelector is true */}\n {showStrategySelector && (\n <div className=\"flex flex-wrap items-center gap-4 rounded-lg border bg-muted/50 p-3\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('search.table.sources', 'Sources:')}\n </span>\n {ALL_STRATEGIES.map((strategy) => (\n <label key={strategy} className=\"flex cursor-pointer items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"size-4 rounded border-input\"\n checked={enabledStrategies.has(strategy)}\n onChange={() => toggleStrategy(strategy)}\n />\n <span\n className={cn(\n 'rounded px-2 py-0.5 text-xs font-medium',\n getStrategyColorClass(strategy)\n )}\n >\n {strategy}\n </span>\n </label>\n ))}\n </div>\n )}\n\n {/* Stats Bar */}\n {timing !== null && rows.length > 0 && (\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{rows.length} {t('search.table.stats.results', 'results')}</span>\n <span>{timing}ms</span>\n {/* Only show sources when strategy selector is visible */}\n {showStrategySelector && strategiesUsed.length > 0 && (\n <span>\n {t('search.table.stats.sources', 'Sources:')} {strategiesUsed.join(', ')}\n </span>\n )}\n </div>\n )}\n\n {/* Error Alert */}\n {error ? (\n <div\n role=\"alert\"\n className=\"w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\"\n >\n {error}\n </div>\n ) : null}\n\n {showScopeHint ? (\n <div className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </div>\n ) : null}\n\n {/* Data Table */}\n <DataTable<Row>\n title={t('search.table.title', 'Search')}\n columns={columns}\n data={rows}\n searchValue={searchValue}\n onSearchChange={(value) => {\n setSearchValue(value)\n setPage(1)\n }}\n searchPlaceholder={t('search.table.searchPlaceholder', 'Search across all strategies...')}\n isLoading={loading}\n pagination={{ page, pageSize: rows.length || 1, total: rows.length, totalPages: 1, onPageChange: setPage }}\n onRowClick={(row) => openRow(row)}\n rowActions={(row) => {\n const primaryHref = pickPrimaryLink(row)\n if (!primaryHref) return null\n return <RowActions items={[{ id: 'open', label: t('search.table.actions.open', 'Open'), href: primaryHref }]} />\n }}\n embedded\n />\n </div>\n )\n}\n\nexport default HybridSearchTable\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/frontend/components/SearchSettingsPageClient.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { GlobalSearchSection } from './sections/GlobalSearchSection'\nimport { FulltextSearchSection } from './sections/FulltextSearchSection'\nimport { VectorSearchSection } from './sections/VectorSearchSection'\n\n// Types\ntype StrategyStatus = {\n id: string\n name: string\n priority: number\n available: boolean\n}\n\ntype FulltextStats = {\n numberOfDocuments: number\n isIndexing: boolean\n fieldDistribution: Record<string, number>\n}\n\ntype ReindexLock = {\n type: 'fulltext' | 'vector'\n action: string\n startedAt: string\n elapsedMinutes: number\n}\n\ntype SearchSettings = {\n strategies: StrategyStatus[]\n fulltextConfigured: boolean\n fulltextStats: FulltextStats | null\n vectorConfigured: boolean\n tokensEnabled: boolean\n defaultStrategies: string[]\n reindexLock: ReindexLock | null\n fulltextReindexLock: ReindexLock | null\n vectorReindexLock: ReindexLock | null\n}\n\ntype SettingsResponse = {\n settings?: SearchSettings\n error?: string\n}\n\n// Embedding types\ntype EmbeddingProviderId = 'openai' | 'google' | 'mistral' | 'cohere' | 'bedrock' | 'ollama'\n\ntype EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\ntype EmbeddingSettings = {\n openaiConfigured: boolean\n autoIndexingEnabled: boolean\n autoIndexingLocked: boolean\n lockReason: string | null\n embeddingConfig: EmbeddingProviderConfig | null\n configuredProviders: EmbeddingProviderId[]\n indexedDimension: number | null\n reindexRequired: boolean\n documentCount: number | null\n}\n\ntype EmbeddingSettingsResponse = {\n settings?: EmbeddingSettings\n error?: string\n}\n\n// Full-text search config types\ntype FulltextEnvVarStatus = {\n set: boolean\n hint: string\n}\n\ntype FulltextOptionalEnvVarStatus = {\n set: boolean\n value?: string | boolean\n default?: string | boolean\n hint: string\n}\n\ntype FulltextConfigResponse = {\n driver: 'meilisearch' | null\n configured: boolean\n envVars: {\n MEILISEARCH_HOST: FulltextEnvVarStatus\n MEILISEARCH_API_KEY: FulltextEnvVarStatus\n }\n optionalEnvVars: {\n MEILISEARCH_INDEX_PREFIX: FulltextOptionalEnvVarStatus\n SEARCH_EXCLUDE_ENCRYPTED_FIELDS: FulltextOptionalEnvVarStatus\n }\n}\n\n// Vector store driver types\ntype VectorDriverId = 'pgvector' | 'qdrant' | 'chromadb'\n\ntype VectorDriverEnvVar = {\n name: string\n set: boolean\n hint: string\n}\n\ntype VectorDriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n available?: boolean | null\n unavailableReason?: string | null\n envVars: VectorDriverEnvVar[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: VectorDriverStatus[]\n}\n\nconst normalizeErrorMessage = (error: unknown, fallback: string): string => {\n if (typeof error === 'string' && error.trim().length) return error.trim()\n if (error instanceof Error && error.message.trim().length) return error.message.trim()\n return fallback\n}\n\nexport function SearchSettingsPageClient() {\n const t = useT()\n\n // Main settings state\n const [settings, setSettings] = React.useState<SearchSettings | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n // Embedding settings state\n const [embeddingSettings, setEmbeddingSettings] = React.useState<EmbeddingSettings | null>(null)\n const [embeddingLoading, setEmbeddingLoading] = React.useState(true)\n\n // Global search settings state\n const [globalSearchStrategies, setGlobalSearchStrategies] = React.useState<Set<string>>(() => new Set(['fulltext', 'vector', 'tokens']))\n const [globalSearchInitial, setGlobalSearchInitial] = React.useState<Set<string>>(() => new Set(['fulltext', 'vector', 'tokens']))\n const [globalSearchLoading, setGlobalSearchLoading] = React.useState(true)\n const [globalSearchSaving, setGlobalSearchSaving] = React.useState(false)\n\n // Full-text search config state\n const [fulltextConfig, setFulltextConfig] = React.useState<FulltextConfigResponse | null>(null)\n const [fulltextConfigLoading, setFulltextConfigLoading] = React.useState(true)\n\n // Vector store config state\n const [vectorStoreConfig, setVectorStoreConfig] = React.useState<VectorStoreConfigResponse | null>(null)\n const [vectorStoreConfigLoading, setVectorStoreConfigLoading] = React.useState(true)\n\n // Fetch main settings\n const fetchSettings = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/search/settings',\n undefined,\n { errorMessage: t('search.settings.errorLabel', 'Failed to load settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n } else {\n setSettings({\n strategies: [],\n fulltextConfigured: false,\n fulltextStats: null,\n vectorConfigured: false,\n tokensEnabled: true,\n defaultStrategies: [],\n reindexLock: null,\n fulltextReindexLock: null,\n vectorReindexLock: null,\n })\n }\n } catch (err) {\n const message = normalizeErrorMessage(err, t('search.settings.errorLabel', 'Failed to load settings'))\n setError(message)\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchSettings()\n }, [fetchSettings])\n\n // Lightweight stats refresh for polling during reindex\n const refreshStatsOnly = React.useCallback(async () => {\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/search/settings',\n { cache: 'no-store' },\n { errorMessage: '', allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n }\n } catch {\n // Silently ignore errors during polling\n }\n }, [])\n\n const hasActiveReindexLock = Boolean(settings?.fulltextReindexLock || settings?.vectorReindexLock)\n React.useEffect(() => {\n if (!hasActiveReindexLock) return\n\n const interval = setInterval(() => {\n void refreshStatsOnly()\n }, 5000)\n\n return () => clearInterval(interval)\n }, [hasActiveReindexLock, refreshStatsOnly])\n\n // Lightweight embedding stats refresh\n const refreshEmbeddingStatsOnly = React.useCallback(async () => {\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n { cache: 'no-store' },\n { errorMessage: '', allowNullResult: true },\n )\n if (body?.settings) {\n setEmbeddingSettings(body.settings)\n }\n } catch {\n // Silently ignore errors during polling\n }\n }, [])\n\n useAppEvent('progress.job.updated', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n useAppEvent('progress.job.completed', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n useAppEvent('om:bridge:reconnected', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n // Fetch embedding settings\n const fetchEmbeddingSettings = React.useCallback(async () => {\n setEmbeddingLoading(true)\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n undefined,\n { errorMessage: t('search.settings.errors.loadFailed', 'Failed to load settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setEmbeddingSettings(body.settings)\n } else {\n setEmbeddingSettings({\n openaiConfigured: false,\n autoIndexingEnabled: true,\n autoIndexingLocked: false,\n lockReason: null,\n embeddingConfig: null,\n configuredProviders: [],\n indexedDimension: null,\n reindexRequired: false,\n documentCount: null,\n })\n }\n } catch {\n // Error already handled\n } finally {\n setEmbeddingLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchEmbeddingSettings()\n }, [fetchEmbeddingSettings])\n\n // Fetch global search settings\n const fetchGlobalSearchSettings = React.useCallback(async () => {\n setGlobalSearchLoading(true)\n try {\n const response = await fetch('/api/search/settings/global-search')\n if (response.ok) {\n const body = await response.json() as { enabledStrategies?: string[] }\n if (body.enabledStrategies && Array.isArray(body.enabledStrategies) && body.enabledStrategies.length > 0) {\n const strategies = new Set(body.enabledStrategies)\n setGlobalSearchStrategies(strategies)\n setGlobalSearchInitial(new Set(strategies))\n }\n }\n } catch {\n // Silently use defaults\n } finally {\n setGlobalSearchLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchGlobalSearchSettings()\n }, [fetchGlobalSearchSettings])\n\n // Fetch fulltext config\n const fetchFulltextConfig = React.useCallback(async () => {\n setFulltextConfigLoading(true)\n try {\n const response = await fetch('/api/search/settings/fulltext')\n if (response.ok) {\n const body = await response.json() as FulltextConfigResponse\n setFulltextConfig(body)\n }\n } catch {\n // Silently use null\n } finally {\n setFulltextConfigLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchFulltextConfig()\n }, [fetchFulltextConfig])\n\n // Fetch vector store config\n const fetchVectorStoreConfig = React.useCallback(async () => {\n setVectorStoreConfigLoading(true)\n try {\n const response = await fetch('/api/search/settings/vector-store')\n if (response.ok) {\n const body = await response.json() as VectorStoreConfigResponse\n setVectorStoreConfig(body)\n }\n } catch {\n // Silently use null\n } finally {\n setVectorStoreConfigLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchVectorStoreConfig()\n }, [fetchVectorStoreConfig])\n\n // Global search settings handlers - auto-save on toggle\n const toggleGlobalSearchStrategy = React.useCallback(async (strategyId: string) => {\n const newStrategies = new Set(globalSearchStrategies)\n if (newStrategies.has(strategyId)) {\n if (newStrategies.size > 1) {\n newStrategies.delete(strategyId)\n } else {\n return\n }\n } else {\n newStrategies.add(strategyId)\n }\n\n setGlobalSearchStrategies(newStrategies)\n setGlobalSearchSaving(true)\n\n try {\n const response = await fetch('/api/search/settings/global-search', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabledStrategies: Array.from(newStrategies) }),\n })\n\n if (!response.ok) {\n const body = await response.json().catch(() => ({})) as { error?: string }\n throw new Error(body.error || t('search.settings.globalSearch.saveError', 'Failed to save settings'))\n }\n\n setGlobalSearchInitial(new Set(newStrategies))\n } catch (err) {\n setGlobalSearchStrategies(globalSearchInitial)\n flash(normalizeErrorMessage(err, t('search.settings.globalSearch.saveError', 'Failed to save settings')), 'error')\n } finally {\n setGlobalSearchSaving(false)\n }\n }, [globalSearchStrategies, globalSearchInitial, t])\n\n // Callbacks for section components\n const handleFulltextStatsUpdate = React.useCallback((stats: FulltextStats | null) => {\n setSettings(prev => prev ? { ...prev, fulltextStats: stats } : prev)\n }, [])\n\n const handleEmbeddingSettingsUpdate = React.useCallback((newSettings: EmbeddingSettings) => {\n setEmbeddingSettings(newSettings)\n }, [])\n\n return (\n <div className=\"flex flex-col gap-6\">\n {/* Header */}\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-bold\">{t('search.settings.pageTitle', 'Search Settings')}</h1>\n <p className=\"text-muted-foreground\">{t('search.settings.pageDescription', 'Configure search strategies and view their availability.')}</p>\n </div>\n\n {/* Section 1: Global Search Settings */}\n <GlobalSearchSection\n loading={globalSearchLoading}\n saving={globalSearchSaving}\n strategies={globalSearchStrategies}\n fulltextConfigured={settings?.fulltextConfigured ?? false}\n vectorConfigured={settings?.vectorConfigured ?? false}\n onToggleStrategy={toggleGlobalSearchStrategy}\n />\n\n {/* Section 2: Full-Text Search (with tabs) */}\n <FulltextSearchSection\n fulltextConfig={fulltextConfig}\n fulltextConfigLoading={fulltextConfigLoading}\n fulltextStats={settings?.fulltextStats ?? null}\n fulltextReindexLock={settings?.fulltextReindexLock ?? null}\n loading={loading}\n onStatsUpdate={handleFulltextStatsUpdate}\n onRefresh={fetchSettings}\n />\n\n {/* Section 3: Vector Search (with tabs) */}\n <VectorSearchSection\n embeddingSettings={embeddingSettings}\n embeddingLoading={embeddingLoading}\n vectorStoreConfig={vectorStoreConfig}\n vectorStoreConfigLoading={vectorStoreConfigLoading}\n vectorReindexLock={settings?.vectorReindexLock ?? null}\n onEmbeddingSettingsUpdate={handleEmbeddingSettingsUpdate}\n onRefreshEmbeddings={fetchEmbeddingSettings}\n />\n\n {/* Refresh Button */}\n <div className=\"flex items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => fetchSettings()}\n disabled={loading}\n >\n {loading ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.loadingLabel', 'Loading settings...')}\n </>\n ) : (\n t('search.settings.refreshLabel', 'Refresh')\n )}\n </Button>\n {error && <span className=\"text-sm text-destructive\">{error}</span>}\n </div>\n </div>\n )\n}\n\nexport default SearchSettingsPageClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { GlobalSearchSection } from './sections/GlobalSearchSection'\nimport { FulltextSearchSection } from './sections/FulltextSearchSection'\nimport { VectorSearchSection } from './sections/VectorSearchSection'\n\n// Types\ntype StrategyStatus = {\n id: string\n name: string\n priority: number\n available: boolean\n}\n\ntype FulltextStats = {\n numberOfDocuments: number\n isIndexing: boolean\n fieldDistribution: Record<string, number>\n}\n\ntype ReindexLock = {\n type: 'fulltext' | 'vector'\n action: string\n startedAt: string\n elapsedMinutes: number\n}\n\ntype SearchSettings = {\n strategies: StrategyStatus[]\n fulltextConfigured: boolean\n fulltextStats: FulltextStats | null\n vectorConfigured: boolean\n tokensEnabled: boolean\n defaultStrategies: string[]\n reindexLock: ReindexLock | null\n fulltextReindexLock: ReindexLock | null\n vectorReindexLock: ReindexLock | null\n}\n\ntype SettingsResponse = {\n settings?: SearchSettings\n error?: string\n}\n\n// Embedding types\ntype EmbeddingProviderId = 'openai' | 'google' | 'mistral' | 'cohere' | 'bedrock' | 'ollama'\n\ntype EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\ntype EmbeddingSettings = {\n openaiConfigured: boolean\n autoIndexingEnabled: boolean\n autoIndexingLocked: boolean\n lockReason: string | null\n embeddingConfig: EmbeddingProviderConfig | null\n configuredProviders: EmbeddingProviderId[]\n indexedDimension: number | null\n reindexRequired: boolean\n documentCount: number | null\n}\n\ntype EmbeddingSettingsResponse = {\n settings?: EmbeddingSettings\n error?: string\n}\n\n// Full-text search config types\ntype FulltextEnvVarStatus = {\n set: boolean\n hint: string\n}\n\ntype FulltextOptionalEnvVarStatus = {\n set: boolean\n value?: string | boolean\n default?: string | boolean\n hint: string\n}\n\ntype FulltextConfigResponse = {\n driver: 'meilisearch' | null\n configured: boolean\n envVars: {\n MEILISEARCH_HOST: FulltextEnvVarStatus\n MEILISEARCH_API_KEY: FulltextEnvVarStatus\n }\n optionalEnvVars: {\n MEILISEARCH_INDEX_PREFIX: FulltextOptionalEnvVarStatus\n SEARCH_EXCLUDE_ENCRYPTED_FIELDS: FulltextOptionalEnvVarStatus\n }\n}\n\n// Vector store driver types\ntype VectorDriverId = 'pgvector' | 'qdrant' | 'chromadb'\n\ntype VectorDriverEnvVar = {\n name: string\n set: boolean\n hint: string\n}\n\ntype VectorDriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n envVars: VectorDriverEnvVar[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: VectorDriverStatus[]\n}\n\nconst normalizeErrorMessage = (error: unknown, fallback: string): string => {\n if (typeof error === 'string' && error.trim().length) return error.trim()\n if (error instanceof Error && error.message.trim().length) return error.message.trim()\n return fallback\n}\n\nexport function SearchSettingsPageClient() {\n const t = useT()\n\n // Main settings state\n const [settings, setSettings] = React.useState<SearchSettings | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n // Embedding settings state\n const [embeddingSettings, setEmbeddingSettings] = React.useState<EmbeddingSettings | null>(null)\n const [embeddingLoading, setEmbeddingLoading] = React.useState(true)\n\n // Global search settings state\n const [globalSearchStrategies, setGlobalSearchStrategies] = React.useState<Set<string>>(() => new Set(['fulltext', 'vector', 'tokens']))\n const [globalSearchInitial, setGlobalSearchInitial] = React.useState<Set<string>>(() => new Set(['fulltext', 'vector', 'tokens']))\n const [globalSearchLoading, setGlobalSearchLoading] = React.useState(true)\n const [globalSearchSaving, setGlobalSearchSaving] = React.useState(false)\n\n // Full-text search config state\n const [fulltextConfig, setFulltextConfig] = React.useState<FulltextConfigResponse | null>(null)\n const [fulltextConfigLoading, setFulltextConfigLoading] = React.useState(true)\n\n // Vector store config state\n const [vectorStoreConfig, setVectorStoreConfig] = React.useState<VectorStoreConfigResponse | null>(null)\n const [vectorStoreConfigLoading, setVectorStoreConfigLoading] = React.useState(true)\n\n // Fetch main settings\n const fetchSettings = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/search/settings',\n undefined,\n { errorMessage: t('search.settings.errorLabel', 'Failed to load settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n } else {\n setSettings({\n strategies: [],\n fulltextConfigured: false,\n fulltextStats: null,\n vectorConfigured: false,\n tokensEnabled: true,\n defaultStrategies: [],\n reindexLock: null,\n fulltextReindexLock: null,\n vectorReindexLock: null,\n })\n }\n } catch (err) {\n const message = normalizeErrorMessage(err, t('search.settings.errorLabel', 'Failed to load settings'))\n setError(message)\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchSettings()\n }, [fetchSettings])\n\n // Lightweight stats refresh for polling during reindex\n const refreshStatsOnly = React.useCallback(async () => {\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/search/settings',\n { cache: 'no-store' },\n { errorMessage: '', allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n }\n } catch {\n // Silently ignore errors during polling\n }\n }, [])\n\n const hasActiveReindexLock = Boolean(settings?.fulltextReindexLock || settings?.vectorReindexLock)\n React.useEffect(() => {\n if (!hasActiveReindexLock) return\n\n const interval = setInterval(() => {\n void refreshStatsOnly()\n }, 5000)\n\n return () => clearInterval(interval)\n }, [hasActiveReindexLock, refreshStatsOnly])\n\n // Lightweight embedding stats refresh\n const refreshEmbeddingStatsOnly = React.useCallback(async () => {\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n { cache: 'no-store' },\n { errorMessage: '', allowNullResult: true },\n )\n if (body?.settings) {\n setEmbeddingSettings(body.settings)\n }\n } catch {\n // Silently ignore errors during polling\n }\n }, [])\n\n useAppEvent('progress.job.updated', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n useAppEvent('progress.job.completed', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n useAppEvent('om:bridge:reconnected', () => {\n void refreshStatsOnly()\n void refreshEmbeddingStatsOnly()\n }, [refreshStatsOnly, refreshEmbeddingStatsOnly])\n\n // Fetch embedding settings\n const fetchEmbeddingSettings = React.useCallback(async () => {\n setEmbeddingLoading(true)\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n undefined,\n { errorMessage: t('search.settings.errors.loadFailed', 'Failed to load settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setEmbeddingSettings(body.settings)\n } else {\n setEmbeddingSettings({\n openaiConfigured: false,\n autoIndexingEnabled: true,\n autoIndexingLocked: false,\n lockReason: null,\n embeddingConfig: null,\n configuredProviders: [],\n indexedDimension: null,\n reindexRequired: false,\n documentCount: null,\n })\n }\n } catch {\n // Error already handled\n } finally {\n setEmbeddingLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchEmbeddingSettings()\n }, [fetchEmbeddingSettings])\n\n // Fetch global search settings\n const fetchGlobalSearchSettings = React.useCallback(async () => {\n setGlobalSearchLoading(true)\n try {\n const response = await fetch('/api/search/settings/global-search')\n if (response.ok) {\n const body = await response.json() as { enabledStrategies?: string[] }\n if (body.enabledStrategies && Array.isArray(body.enabledStrategies) && body.enabledStrategies.length > 0) {\n const strategies = new Set(body.enabledStrategies)\n setGlobalSearchStrategies(strategies)\n setGlobalSearchInitial(new Set(strategies))\n }\n }\n } catch {\n // Silently use defaults\n } finally {\n setGlobalSearchLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchGlobalSearchSettings()\n }, [fetchGlobalSearchSettings])\n\n // Fetch fulltext config\n const fetchFulltextConfig = React.useCallback(async () => {\n setFulltextConfigLoading(true)\n try {\n const response = await fetch('/api/search/settings/fulltext')\n if (response.ok) {\n const body = await response.json() as FulltextConfigResponse\n setFulltextConfig(body)\n }\n } catch {\n // Silently use null\n } finally {\n setFulltextConfigLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchFulltextConfig()\n }, [fetchFulltextConfig])\n\n // Fetch vector store config\n const fetchVectorStoreConfig = React.useCallback(async () => {\n setVectorStoreConfigLoading(true)\n try {\n const response = await fetch('/api/search/settings/vector-store')\n if (response.ok) {\n const body = await response.json() as VectorStoreConfigResponse\n setVectorStoreConfig(body)\n }\n } catch {\n // Silently use null\n } finally {\n setVectorStoreConfigLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchVectorStoreConfig()\n }, [fetchVectorStoreConfig])\n\n // Global search settings handlers - auto-save on toggle\n const toggleGlobalSearchStrategy = React.useCallback(async (strategyId: string) => {\n const newStrategies = new Set(globalSearchStrategies)\n if (newStrategies.has(strategyId)) {\n if (newStrategies.size > 1) {\n newStrategies.delete(strategyId)\n } else {\n return\n }\n } else {\n newStrategies.add(strategyId)\n }\n\n setGlobalSearchStrategies(newStrategies)\n setGlobalSearchSaving(true)\n\n try {\n const response = await fetch('/api/search/settings/global-search', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabledStrategies: Array.from(newStrategies) }),\n })\n\n if (!response.ok) {\n const body = await response.json().catch(() => ({})) as { error?: string }\n throw new Error(body.error || t('search.settings.globalSearch.saveError', 'Failed to save settings'))\n }\n\n setGlobalSearchInitial(new Set(newStrategies))\n } catch (err) {\n setGlobalSearchStrategies(globalSearchInitial)\n flash(normalizeErrorMessage(err, t('search.settings.globalSearch.saveError', 'Failed to save settings')), 'error')\n } finally {\n setGlobalSearchSaving(false)\n }\n }, [globalSearchStrategies, globalSearchInitial, t])\n\n // Callbacks for section components\n const handleFulltextStatsUpdate = React.useCallback((stats: FulltextStats | null) => {\n setSettings(prev => prev ? { ...prev, fulltextStats: stats } : prev)\n }, [])\n\n const handleEmbeddingSettingsUpdate = React.useCallback((newSettings: EmbeddingSettings) => {\n setEmbeddingSettings(newSettings)\n }, [])\n\n return (\n <div className=\"flex flex-col gap-6\">\n {/* Header */}\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-bold\">{t('search.settings.pageTitle', 'Search Settings')}</h1>\n <p className=\"text-muted-foreground\">{t('search.settings.pageDescription', 'Configure search strategies and view their availability.')}</p>\n </div>\n\n {/* Section 1: Global Search Settings */}\n <GlobalSearchSection\n loading={globalSearchLoading}\n saving={globalSearchSaving}\n strategies={globalSearchStrategies}\n fulltextConfigured={settings?.fulltextConfigured ?? false}\n vectorConfigured={settings?.vectorConfigured ?? false}\n onToggleStrategy={toggleGlobalSearchStrategy}\n />\n\n {/* Section 2: Full-Text Search (with tabs) */}\n <FulltextSearchSection\n fulltextConfig={fulltextConfig}\n fulltextConfigLoading={fulltextConfigLoading}\n fulltextStats={settings?.fulltextStats ?? null}\n fulltextReindexLock={settings?.fulltextReindexLock ?? null}\n loading={loading}\n onStatsUpdate={handleFulltextStatsUpdate}\n onRefresh={fetchSettings}\n />\n\n {/* Section 3: Vector Search (with tabs) */}\n <VectorSearchSection\n embeddingSettings={embeddingSettings}\n embeddingLoading={embeddingLoading}\n vectorStoreConfig={vectorStoreConfig}\n vectorStoreConfigLoading={vectorStoreConfigLoading}\n vectorReindexLock={settings?.vectorReindexLock ?? null}\n onEmbeddingSettingsUpdate={handleEmbeddingSettingsUpdate}\n onRefreshEmbeddings={fetchEmbeddingSettings}\n />\n\n {/* Refresh Button */}\n <div className=\"flex items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => fetchSettings()}\n disabled={loading}\n >\n {loading ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.loadingLabel', 'Loading settings...')}\n </>\n ) : (\n t('search.settings.refreshLabel', 'Refresh')\n )}\n </Button>\n {error && <span className=\"text-sm text-destructive\">{error}</span>}\n </div>\n </div>\n )\n}\n\nexport default SearchSettingsPageClient\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -47,7 +47,6 @@ import {
47
47
  } from "lucide-react";
48
48
  import { cn } from "@open-mercato/shared/lib/utils";
49
49
  import { useT } from "@open-mercato/shared/lib/i18n/context";
50
- import { resolveEntityTypeLabel } from "../lib/entityTypeLabel.js";
51
50
  import {
52
51
  getCurrentOrganizationScope,
53
52
  subscribeOrganizationScopeChanged
@@ -78,6 +77,9 @@ function hasActiveOrganizationSelection() {
78
77
  if (!cookieValue) return false;
79
78
  return !isAllOrganizationsSelection(cookieValue);
80
79
  }
80
+ function humanizeSegment(segment) {
81
+ return segment.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
82
+ }
81
83
  const ICON_MAP = {
82
84
  bolt: Zap,
83
85
  zap: Zap,
@@ -121,6 +123,11 @@ function resolveIcon(name) {
121
123
  if (!name) return null;
122
124
  return ICON_MAP[name.toLowerCase()] ?? null;
123
125
  }
126
+ function formatEntityId(entityId) {
127
+ if (!entityId.includes(":")) return humanizeSegment(entityId);
128
+ const [module, entity] = entityId.split(":");
129
+ return `${humanizeSegment(module)} \xB7 ${humanizeSegment(entity)}`;
130
+ }
124
131
  function TopbarSearchInline({
125
132
  embeddingConfigured,
126
133
  missingConfigMessage
@@ -443,7 +450,7 @@ function TopbarSearchInline({
443
450
  }
444
451
  ) }),
445
452
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-overline text-muted-foreground", children: [
446
- /* @__PURE__ */ jsx("span", { className: "truncate", children: resolveEntityTypeLabel(t, result.entityId) }),
453
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: formatEntityId(result.entityId) }),
447
454
  presenter?.subtitle ? /* @__PURE__ */ jsxs(Fragment, { children: [
448
455
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
449
456
  /* @__PURE__ */ jsx("span", { className: "truncate", children: presenter.subtitle })