@open-mercato/search 0.4.2-canary-c02407ff85

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 (237) hide show
  1. package/AGENTS.md +678 -0
  2. package/build.mjs +92 -0
  3. package/dist/di.js +157 -0
  4. package/dist/di.js.map +7 -0
  5. package/dist/fulltext/drivers/index.js +21 -0
  6. package/dist/fulltext/drivers/index.js.map +7 -0
  7. package/dist/fulltext/drivers/meilisearch/index.js +320 -0
  8. package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
  9. package/dist/fulltext/index.js +7 -0
  10. package/dist/fulltext/index.js.map +7 -0
  11. package/dist/fulltext/types.js +1 -0
  12. package/dist/fulltext/types.js.map +7 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +7 -0
  15. package/dist/indexer/index.js +8 -0
  16. package/dist/indexer/index.js.map +7 -0
  17. package/dist/indexer/search-indexer.js +848 -0
  18. package/dist/indexer/search-indexer.js.map +7 -0
  19. package/dist/indexer/subscribers/delete.js +41 -0
  20. package/dist/indexer/subscribers/delete.js.map +7 -0
  21. package/dist/lib/debug.js +34 -0
  22. package/dist/lib/debug.js.map +7 -0
  23. package/dist/lib/fallback-presenter.js +107 -0
  24. package/dist/lib/fallback-presenter.js.map +7 -0
  25. package/dist/lib/field-policy.js +75 -0
  26. package/dist/lib/field-policy.js.map +7 -0
  27. package/dist/lib/index.js +19 -0
  28. package/dist/lib/index.js.map +7 -0
  29. package/dist/lib/merger.js +93 -0
  30. package/dist/lib/merger.js.map +7 -0
  31. package/dist/lib/presenter-enricher.js +192 -0
  32. package/dist/lib/presenter-enricher.js.map +7 -0
  33. package/dist/modules/search/acl.js +14 -0
  34. package/dist/modules/search/acl.js.map +7 -0
  35. package/dist/modules/search/ai-tools.js +284 -0
  36. package/dist/modules/search/ai-tools.js.map +7 -0
  37. package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
  38. package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
  39. package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
  40. package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
  41. package/dist/modules/search/api/embeddings/route.js +246 -0
  42. package/dist/modules/search/api/embeddings/route.js.map +7 -0
  43. package/dist/modules/search/api/index/route.js +245 -0
  44. package/dist/modules/search/api/index/route.js.map +7 -0
  45. package/dist/modules/search/api/reindex/cancel/route.js +65 -0
  46. package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
  47. package/dist/modules/search/api/reindex/route.js +332 -0
  48. package/dist/modules/search/api/reindex/route.js.map +7 -0
  49. package/dist/modules/search/api/search/global/route.js +100 -0
  50. package/dist/modules/search/api/search/global/route.js.map +7 -0
  51. package/dist/modules/search/api/search/route.js +101 -0
  52. package/dist/modules/search/api/search/route.js.map +7 -0
  53. package/dist/modules/search/api/settings/fulltext/route.js +55 -0
  54. package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
  55. package/dist/modules/search/api/settings/global-search/route.js +80 -0
  56. package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
  57. package/dist/modules/search/api/settings/route.js +118 -0
  58. package/dist/modules/search/api/settings/route.js.map +7 -0
  59. package/dist/modules/search/api/settings/vector-store/route.js +77 -0
  60. package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
  61. package/dist/modules/search/backend/config/search/page.js +10 -0
  62. package/dist/modules/search/backend/config/search/page.js.map +7 -0
  63. package/dist/modules/search/backend/config/search/page.meta.js +24 -0
  64. package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
  65. package/dist/modules/search/cli.js +698 -0
  66. package/dist/modules/search/cli.js.map +7 -0
  67. package/dist/modules/search/di.js +32 -0
  68. package/dist/modules/search/di.js.map +7 -0
  69. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
  70. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
  71. package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
  72. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
  73. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
  74. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
  75. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
  76. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
  77. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
  78. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
  79. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
  80. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
  81. package/dist/modules/search/frontend/index.js +9 -0
  82. package/dist/modules/search/frontend/index.js.map +7 -0
  83. package/dist/modules/search/frontend/utils.js +41 -0
  84. package/dist/modules/search/frontend/utils.js.map +7 -0
  85. package/dist/modules/search/i18n/de.json +61 -0
  86. package/dist/modules/search/i18n/en.json +72 -0
  87. package/dist/modules/search/i18n/es.json +61 -0
  88. package/dist/modules/search/i18n/pl.json +61 -0
  89. package/dist/modules/search/index.js +11 -0
  90. package/dist/modules/search/index.js.map +7 -0
  91. package/dist/modules/search/lib/auto-indexing.js +29 -0
  92. package/dist/modules/search/lib/auto-indexing.js.map +7 -0
  93. package/dist/modules/search/lib/embedding-config.js +131 -0
  94. package/dist/modules/search/lib/embedding-config.js.map +7 -0
  95. package/dist/modules/search/lib/global-search-config.js +45 -0
  96. package/dist/modules/search/lib/global-search-config.js.map +7 -0
  97. package/dist/modules/search/lib/reindex-lock.js +99 -0
  98. package/dist/modules/search/lib/reindex-lock.js.map +7 -0
  99. package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
  100. package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
  101. package/dist/modules/search/subscribers/vector_delete.js +58 -0
  102. package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
  103. package/dist/modules/search/subscribers/vector_purge.js +142 -0
  104. package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
  105. package/dist/modules/search/subscribers/vector_upsert.js +58 -0
  106. package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
  107. package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
  108. package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
  109. package/dist/modules/search/workers/vector-index.worker.js +234 -0
  110. package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
  111. package/dist/queue/fulltext-indexing.js +15 -0
  112. package/dist/queue/fulltext-indexing.js.map +7 -0
  113. package/dist/queue/index.js +3 -0
  114. package/dist/queue/index.js.map +7 -0
  115. package/dist/queue/vector-indexing.js +15 -0
  116. package/dist/queue/vector-indexing.js.map +7 -0
  117. package/dist/service.js +286 -0
  118. package/dist/service.js.map +7 -0
  119. package/dist/strategies/fulltext.strategy.js +116 -0
  120. package/dist/strategies/fulltext.strategy.js.map +7 -0
  121. package/dist/strategies/index.js +12 -0
  122. package/dist/strategies/index.js.map +7 -0
  123. package/dist/strategies/token.strategy.js +80 -0
  124. package/dist/strategies/token.strategy.js.map +7 -0
  125. package/dist/strategies/vector.strategy.js +137 -0
  126. package/dist/strategies/vector.strategy.js.map +7 -0
  127. package/dist/types.js +1 -0
  128. package/dist/types.js.map +7 -0
  129. package/dist/vector/drivers/chromadb/index.js +44 -0
  130. package/dist/vector/drivers/chromadb/index.js.map +7 -0
  131. package/dist/vector/drivers/index.js +9 -0
  132. package/dist/vector/drivers/index.js.map +7 -0
  133. package/dist/vector/drivers/pgvector/index.js +509 -0
  134. package/dist/vector/drivers/pgvector/index.js.map +7 -0
  135. package/dist/vector/drivers/qdrant/index.js +44 -0
  136. package/dist/vector/drivers/qdrant/index.js.map +7 -0
  137. package/dist/vector/index.js +4 -0
  138. package/dist/vector/index.js.map +7 -0
  139. package/dist/vector/lib/vector-logs.js +33 -0
  140. package/dist/vector/lib/vector-logs.js.map +7 -0
  141. package/dist/vector/services/checksum.js +20 -0
  142. package/dist/vector/services/checksum.js.map +7 -0
  143. package/dist/vector/services/embedding.js +222 -0
  144. package/dist/vector/services/embedding.js.map +7 -0
  145. package/dist/vector/services/index.js +4 -0
  146. package/dist/vector/services/index.js.map +7 -0
  147. package/dist/vector/services/vector-index.service.js +960 -0
  148. package/dist/vector/services/vector-index.service.js.map +7 -0
  149. package/dist/vector/types/pg.d.js +1 -0
  150. package/dist/vector/types/pg.d.js.map +7 -0
  151. package/dist/vector/types.js +75 -0
  152. package/dist/vector/types.js.map +7 -0
  153. package/jest.config.cjs +19 -0
  154. package/package.json +142 -0
  155. package/src/__tests__/queue.test.ts +148 -0
  156. package/src/__tests__/service.test.ts +345 -0
  157. package/src/__tests__/workers.test.ts +319 -0
  158. package/src/di.ts +291 -0
  159. package/src/fulltext/drivers/index.ts +41 -0
  160. package/src/fulltext/drivers/meilisearch/index.ts +410 -0
  161. package/src/fulltext/index.ts +13 -0
  162. package/src/fulltext/types.ts +115 -0
  163. package/src/index.ts +36 -0
  164. package/src/indexer/index.ts +13 -0
  165. package/src/indexer/search-indexer.ts +1141 -0
  166. package/src/indexer/subscribers/delete.ts +49 -0
  167. package/src/lib/debug.ts +46 -0
  168. package/src/lib/fallback-presenter.ts +106 -0
  169. package/src/lib/field-policy.ts +169 -0
  170. package/src/lib/index.ts +13 -0
  171. package/src/lib/merger.ts +159 -0
  172. package/src/lib/presenter-enricher.ts +323 -0
  173. package/src/modules/search/README.md +694 -0
  174. package/src/modules/search/acl.ts +10 -0
  175. package/src/modules/search/ai-tools.ts +467 -0
  176. package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
  177. package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
  178. package/src/modules/search/api/embeddings/route.ts +304 -0
  179. package/src/modules/search/api/index/route.ts +297 -0
  180. package/src/modules/search/api/reindex/cancel/route.ts +77 -0
  181. package/src/modules/search/api/reindex/route.ts +419 -0
  182. package/src/modules/search/api/search/global/route.ts +120 -0
  183. package/src/modules/search/api/search/route.ts +121 -0
  184. package/src/modules/search/api/settings/fulltext/route.ts +82 -0
  185. package/src/modules/search/api/settings/global-search/route.ts +91 -0
  186. package/src/modules/search/api/settings/route.ts +187 -0
  187. package/src/modules/search/api/settings/vector-store/route.ts +105 -0
  188. package/src/modules/search/backend/config/search/page.meta.ts +22 -0
  189. package/src/modules/search/backend/config/search/page.tsx +12 -0
  190. package/src/modules/search/cli.ts +818 -0
  191. package/src/modules/search/di.ts +50 -0
  192. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
  193. package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
  194. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
  195. package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
  196. package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
  197. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
  198. package/src/modules/search/frontend/index.ts +3 -0
  199. package/src/modules/search/frontend/utils.ts +82 -0
  200. package/src/modules/search/i18n/de.json +61 -0
  201. package/src/modules/search/i18n/en.json +72 -0
  202. package/src/modules/search/i18n/es.json +61 -0
  203. package/src/modules/search/i18n/pl.json +61 -0
  204. package/src/modules/search/index.ts +9 -0
  205. package/src/modules/search/lib/auto-indexing.ts +35 -0
  206. package/src/modules/search/lib/embedding-config.ts +161 -0
  207. package/src/modules/search/lib/global-search-config.ts +69 -0
  208. package/src/modules/search/lib/reindex-lock.ts +201 -0
  209. package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
  210. package/src/modules/search/subscribers/vector_delete.ts +75 -0
  211. package/src/modules/search/subscribers/vector_purge.ts +161 -0
  212. package/src/modules/search/subscribers/vector_upsert.ts +75 -0
  213. package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
  214. package/src/modules/search/workers/vector-index.worker.ts +292 -0
  215. package/src/queue/fulltext-indexing.ts +87 -0
  216. package/src/queue/index.ts +2 -0
  217. package/src/queue/vector-indexing.ts +66 -0
  218. package/src/service.ts +397 -0
  219. package/src/strategies/fulltext.strategy.ts +155 -0
  220. package/src/strategies/index.ts +17 -0
  221. package/src/strategies/token.strategy.ts +153 -0
  222. package/src/strategies/vector.strategy.ts +234 -0
  223. package/src/types.ts +38 -0
  224. package/src/vector/drivers/chromadb/index.ts +49 -0
  225. package/src/vector/drivers/index.ts +4 -0
  226. package/src/vector/drivers/pgvector/index.ts +627 -0
  227. package/src/vector/drivers/qdrant/index.ts +49 -0
  228. package/src/vector/index.ts +3 -0
  229. package/src/vector/lib/vector-logs.ts +46 -0
  230. package/src/vector/services/checksum.ts +18 -0
  231. package/src/vector/services/embedding.ts +275 -0
  232. package/src/vector/services/index.ts +3 -0
  233. package/src/vector/services/vector-index.service.ts +1234 -0
  234. package/src/vector/types/pg.d.ts +1 -0
  235. package/src/vector/types.ts +220 -0
  236. package/tsconfig.json +9 -0
  237. package/watch.mjs +6 -0
@@ -0,0 +1,343 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import * as LucideIcons from "lucide-react";
6
+ import { DataTable } from "@open-mercato/ui/backend/DataTable";
7
+ import { RowActions } from "@open-mercato/ui/backend/RowActions";
8
+ import { cn } from "@open-mercato/shared/lib/utils";
9
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
10
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
11
+ import { fetchHybridSearchResults } from "../utils.js";
12
+ const MIN_QUERY_LENGTH = 2;
13
+ const ALL_STRATEGIES = ["fulltext", "vector", "tokens"];
14
+ function createColumns(t) {
15
+ return [
16
+ {
17
+ id: "title",
18
+ header: () => t("search.table.columns.result", "Result"),
19
+ cell: ({ row }) => {
20
+ const item = row.original;
21
+ const title = resolveRowTitle(item);
22
+ const iconName = item.presenter?.icon;
23
+ const Icon = iconName ? resolveIcon(iconName) : null;
24
+ const typeLabel = formatEntityId(item.entityId);
25
+ const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata);
26
+ const links = normalizeLinks(item.links);
27
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
28
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 h-5 w-5 text-muted-foreground" }) : null,
29
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
31
+ /* @__PURE__ */ jsx("span", { className: "font-medium whitespace-normal break-all", children: title }),
32
+ /* @__PURE__ */ jsx("span", { className: "rounded border border-muted-foreground/40 px-2 py-0.5 text-xs text-muted-foreground", children: typeLabel })
33
+ ] }),
34
+ snapshot ? /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground whitespace-normal break-words", children: snapshot }) : null,
35
+ links.length ? /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: links.map((link) => /* @__PURE__ */ jsx(
36
+ "span",
37
+ {
38
+ className: cn(
39
+ "rounded-full border px-2 py-0.5 text-xs",
40
+ link.kind === "primary" ? "border-primary text-primary" : "border-muted-foreground/40 text-muted-foreground"
41
+ ),
42
+ children: link.label ?? link.href
43
+ },
44
+ `${item.entityId}:${item.recordId}:${link.href}`
45
+ )) }) : null
46
+ ] })
47
+ ] }) });
48
+ },
49
+ meta: { priority: 1 }
50
+ },
51
+ {
52
+ id: "source",
53
+ header: () => t("search.table.columns.source", "Source"),
54
+ cell: ({ row }) => {
55
+ const source = row.original.source;
56
+ const colorClass = getStrategyColorClass(source);
57
+ return /* @__PURE__ */ jsx("span", { className: cn("rounded px-2 py-0.5 text-xs font-medium", colorClass), children: source });
58
+ },
59
+ meta: { priority: 2 }
60
+ },
61
+ {
62
+ id: "score",
63
+ header: () => t("search.table.columns.score", "Score"),
64
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { children: row.original.score != null ? row.original.score.toFixed(2) : "\u2014" }),
65
+ meta: { priority: 2 }
66
+ }
67
+ ];
68
+ }
69
+ function getStrategyColorClass(strategy) {
70
+ switch (strategy) {
71
+ case "fulltext":
72
+ return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";
73
+ case "vector":
74
+ return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200";
75
+ case "tokens":
76
+ return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200";
77
+ default:
78
+ return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";
79
+ }
80
+ }
81
+ function normalizeLinks(links) {
82
+ if (!Array.isArray(links)) return [];
83
+ return links.filter((link) => typeof link?.href === "string");
84
+ }
85
+ function toPascalCase(input) {
86
+ return input.split(/[-_ ]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
87
+ }
88
+ function resolveIcon(name) {
89
+ if (!name) return null;
90
+ const key = toPascalCase(name);
91
+ const candidate = LucideIcons[key];
92
+ if (typeof candidate === "function") {
93
+ return candidate;
94
+ }
95
+ return null;
96
+ }
97
+ function humanizeSegment(segment) {
98
+ return segment.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
99
+ }
100
+ function formatEntityId(entityId) {
101
+ if (!entityId.includes(":")) return humanizeSegment(entityId);
102
+ const [module, entity] = entityId.split(":");
103
+ const moduleLabel = humanizeSegment(module);
104
+ const entityLabel = humanizeSegment(entity);
105
+ return `${moduleLabel} \xB7 ${entityLabel}`;
106
+ }
107
+ function resolveRowTitle(row) {
108
+ const presenterTitle = row.presenter?.title;
109
+ if (typeof presenterTitle === "string") {
110
+ const trimmed = presenterTitle.trim();
111
+ if (trimmed.length) return trimmed;
112
+ }
113
+ return row.recordId;
114
+ }
115
+ function extractSnapshot(metadata) {
116
+ if (!metadata) return null;
117
+ const candidateKeys = ["snapshot", "summary", "description", "body", "content", "note"];
118
+ for (const key of candidateKeys) {
119
+ const value = metadata[key];
120
+ if (typeof value === "string") {
121
+ const trimmed = value.trim();
122
+ if (trimmed.length) return trimmed;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+ function pickPrimaryLink(row) {
128
+ if (row.url) return row.url;
129
+ const links = normalizeLinks(row.links);
130
+ if (!links.length) return null;
131
+ const primary = links.find((link) => link.kind === "primary");
132
+ return (primary ?? links[0]).href;
133
+ }
134
+ function normalizeErrorMessage(input, fallback) {
135
+ const fallbackMessage = typeof fallback === "string" && fallback.trim().length ? fallback.trim() : null;
136
+ let message = null;
137
+ if (typeof input === "string") {
138
+ message = input;
139
+ } else if (input instanceof Error && typeof input.message === "string") {
140
+ message = input.message;
141
+ }
142
+ if (message) {
143
+ const trimmed = message.trim();
144
+ if (trimmed.length) {
145
+ const sanitized = trimmed.replace(/^\[[^\]]+\]\s*/, "").trim();
146
+ if (sanitized.length) return sanitized;
147
+ }
148
+ }
149
+ return fallbackMessage;
150
+ }
151
+ function HybridSearchTable({
152
+ showStrategySelector = false,
153
+ showSourceColumn = false
154
+ } = {}) {
155
+ const router = useRouter();
156
+ const t = useT();
157
+ const [searchValue, setSearchValue] = React.useState("");
158
+ const [rows, setRows] = React.useState([]);
159
+ const [page, setPage] = React.useState(1);
160
+ const [loading, setLoading] = React.useState(false);
161
+ const [error, setError] = React.useState(null);
162
+ const [timing, setTiming] = React.useState(null);
163
+ const [strategiesUsed, setStrategiesUsed] = React.useState([]);
164
+ const [enabledStrategies, setEnabledStrategies] = React.useState(
165
+ new Set(ALL_STRATEGIES)
166
+ );
167
+ const debounceRef = React.useRef(null);
168
+ const abortRef = React.useRef(null);
169
+ const columns = React.useMemo(() => {
170
+ const allColumns = createColumns(t);
171
+ if (!showSourceColumn) {
172
+ return allColumns.filter((col) => col.id !== "source");
173
+ }
174
+ return allColumns;
175
+ }, [t, showSourceColumn]);
176
+ const toggleStrategy = React.useCallback((strategy) => {
177
+ setEnabledStrategies((prev) => {
178
+ const next = new Set(prev);
179
+ if (next.has(strategy)) {
180
+ next.delete(strategy);
181
+ } else {
182
+ next.add(strategy);
183
+ }
184
+ return next;
185
+ });
186
+ }, []);
187
+ const openRow = React.useCallback(
188
+ (row) => {
189
+ const href = pickPrimaryLink(row);
190
+ if (!href) return;
191
+ router.push(href);
192
+ },
193
+ [router]
194
+ );
195
+ React.useEffect(() => {
196
+ const trimmed = searchValue.trim();
197
+ abortRef.current?.abort();
198
+ if (debounceRef.current) {
199
+ window.clearTimeout(debounceRef.current);
200
+ debounceRef.current = null;
201
+ }
202
+ if (trimmed.length < MIN_QUERY_LENGTH) {
203
+ setRows([]);
204
+ setTiming(null);
205
+ setStrategiesUsed([]);
206
+ setError(null);
207
+ setLoading(false);
208
+ return;
209
+ }
210
+ if (enabledStrategies.size === 0) {
211
+ setRows([]);
212
+ setTiming(null);
213
+ setStrategiesUsed([]);
214
+ setError(t("search.table.errors.noSources", "Select at least one search source"));
215
+ setLoading(false);
216
+ return;
217
+ }
218
+ const controller = new AbortController();
219
+ abortRef.current = controller;
220
+ setLoading(true);
221
+ debounceRef.current = window.setTimeout(async () => {
222
+ try {
223
+ const data = await fetchHybridSearchResults(trimmed, {
224
+ limit: 50,
225
+ strategies: Array.from(enabledStrategies),
226
+ signal: controller.signal
227
+ });
228
+ const mapped = data.results.map((item) => ({
229
+ entityId: item.entityId,
230
+ recordId: item.recordId,
231
+ source: item.source,
232
+ score: typeof item.score === "number" ? item.score : null,
233
+ url: item.url ?? null,
234
+ presenter: item.presenter ?? null,
235
+ links: item.links ?? null,
236
+ metadata: item.metadata ?? null
237
+ }));
238
+ setRows(mapped);
239
+ setTiming(data.timing);
240
+ setStrategiesUsed(data.strategiesUsed);
241
+ const message = data.error ? normalizeErrorMessage(data.error, t("search.table.errors.searchFailed", "Search failed")) : null;
242
+ setError(message ?? null);
243
+ setPage(1);
244
+ } catch (err) {
245
+ if (controller.signal.aborted) return;
246
+ if (err?.name === "AbortError") return;
247
+ setError(normalizeErrorMessage(err, t("search.table.errors.searchFailed", "Search failed")));
248
+ setRows([]);
249
+ setTiming(null);
250
+ setStrategiesUsed([]);
251
+ } finally {
252
+ if (!controller.signal.aborted) setLoading(false);
253
+ }
254
+ }, 250);
255
+ return () => {
256
+ controller.abort();
257
+ if (debounceRef.current) window.clearTimeout(debounceRef.current);
258
+ };
259
+ }, [searchValue, enabledStrategies, t]);
260
+ React.useEffect(() => {
261
+ if (!error) return;
262
+ flash(error, "error");
263
+ }, [error]);
264
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
265
+ showStrategySelector && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4 rounded-lg border bg-muted/50 p-3", children: [
266
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: t("search.table.sources", "Sources:") }),
267
+ ALL_STRATEGIES.map((strategy) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2", children: [
268
+ /* @__PURE__ */ jsx(
269
+ "input",
270
+ {
271
+ type: "checkbox",
272
+ className: "size-4 rounded border-gray-300",
273
+ checked: enabledStrategies.has(strategy),
274
+ onChange: () => toggleStrategy(strategy)
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsx(
278
+ "span",
279
+ {
280
+ className: cn(
281
+ "rounded px-2 py-0.5 text-xs font-medium",
282
+ getStrategyColorClass(strategy)
283
+ ),
284
+ children: strategy
285
+ }
286
+ )
287
+ ] }, strategy))
288
+ ] }),
289
+ timing !== null && rows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-sm text-muted-foreground", children: [
290
+ /* @__PURE__ */ jsxs("span", { children: [
291
+ rows.length,
292
+ " ",
293
+ t("search.table.stats.results", "results")
294
+ ] }),
295
+ /* @__PURE__ */ jsxs("span", { children: [
296
+ timing,
297
+ "ms"
298
+ ] }),
299
+ showStrategySelector && strategiesUsed.length > 0 && /* @__PURE__ */ jsxs("span", { children: [
300
+ t("search.table.stats.sources", "Sources:"),
301
+ " ",
302
+ strategiesUsed.join(", ")
303
+ ] })
304
+ ] }),
305
+ error ? /* @__PURE__ */ jsx(
306
+ "div",
307
+ {
308
+ role: "alert",
309
+ className: "w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive",
310
+ children: error
311
+ }
312
+ ) : null,
313
+ /* @__PURE__ */ jsx(
314
+ DataTable,
315
+ {
316
+ title: t("search.table.title", "Search"),
317
+ columns,
318
+ data: rows,
319
+ searchValue,
320
+ onSearchChange: (value) => {
321
+ setSearchValue(value);
322
+ setPage(1);
323
+ },
324
+ searchPlaceholder: t("search.table.searchPlaceholder", "Search across all strategies..."),
325
+ isLoading: loading,
326
+ pagination: { page, pageSize: rows.length || 1, total: rows.length, totalPages: 1, onPageChange: setPage },
327
+ onRowClick: (row) => openRow(row),
328
+ rowActions: (row) => {
329
+ const primaryHref = pickPrimaryLink(row);
330
+ if (!primaryHref) return null;
331
+ return /* @__PURE__ */ jsx(RowActions, { items: [{ label: t("search.table.actions.open", "Open"), href: primaryHref }] });
332
+ },
333
+ embedded: true
334
+ }
335
+ )
336
+ ] });
337
+ }
338
+ var HybridSearchTable_default = HybridSearchTable;
339
+ export {
340
+ HybridSearchTable,
341
+ HybridSearchTable_default as default
342
+ };
343
+ //# sourceMappingURL=HybridSearchTable.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 { 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 = 2\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 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-primary text-primary'\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-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'\n case 'vector':\n return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'\n case 'tokens':\n return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'\n default:\n return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'\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 [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 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-gray-300\"\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 {/* 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={[{ 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
+ "names": []
7
+ }