@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,360 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
6
+ import { Button } from "@open-mercato/ui/primitives/button";
7
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
8
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@open-mercato/ui/primitives/tabs";
9
+ const normalizeErrorMessage = (error, fallback) => {
10
+ if (typeof error === "string" && error.trim().length) return error.trim();
11
+ if (error instanceof Error && error.message.trim().length) return error.message.trim();
12
+ return fallback;
13
+ };
14
+ function FulltextSearchSection({
15
+ fulltextConfig,
16
+ fulltextConfigLoading,
17
+ fulltextStats,
18
+ fulltextReindexLock,
19
+ loading,
20
+ onStatsUpdate,
21
+ onRefresh
22
+ }) {
23
+ const t = useT();
24
+ const [reindexing, setReindexing] = React.useState(null);
25
+ const [showReindexDialog, setShowReindexDialog] = React.useState(null);
26
+ const [activityLogs, setActivityLogs] = React.useState([]);
27
+ const [activityLoading, setActivityLoading] = React.useState(true);
28
+ const fetchActivityLogs = React.useCallback(async () => {
29
+ setActivityLoading(true);
30
+ try {
31
+ const response = await fetch("/api/query_index/status");
32
+ if (response.ok) {
33
+ const body = await response.json();
34
+ const allLogs = [];
35
+ if (body.logs) {
36
+ allLogs.push(...body.logs);
37
+ }
38
+ if (body.errors) {
39
+ allLogs.push(...body.errors.map((err) => ({ ...err, level: "error" })));
40
+ }
41
+ const fulltextLogs = allLogs.filter((log) => {
42
+ const lowerSource = log.source?.toLowerCase() ?? "";
43
+ const lowerMessage = log.message?.toLowerCase() ?? "";
44
+ const lowerHandler = log.handler?.toLowerCase() ?? "";
45
+ const isVector = lowerSource.includes("vector") || lowerMessage.includes("vector") || lowerMessage.includes("embedding") || lowerHandler.includes("vector");
46
+ return !isVector;
47
+ });
48
+ fulltextLogs.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime());
49
+ setActivityLogs(fulltextLogs.slice(0, 50));
50
+ }
51
+ } catch {
52
+ } finally {
53
+ setActivityLoading(false);
54
+ }
55
+ }, []);
56
+ React.useEffect(() => {
57
+ fetchActivityLogs();
58
+ }, [fetchActivityLogs]);
59
+ React.useEffect(() => {
60
+ if (fulltextReindexLock || reindexing) {
61
+ const interval = setInterval(fetchActivityLogs, 5e3);
62
+ return () => clearInterval(interval);
63
+ }
64
+ }, [fulltextReindexLock, reindexing, fetchActivityLogs]);
65
+ const handleReindexClick = (action) => {
66
+ setShowReindexDialog(action);
67
+ };
68
+ const handleReindexCancel = () => {
69
+ setShowReindexDialog(null);
70
+ };
71
+ const handleReindexConfirm = React.useCallback(async () => {
72
+ const action = showReindexDialog;
73
+ if (!action) return;
74
+ setShowReindexDialog(null);
75
+ setReindexing(action);
76
+ try {
77
+ const response = await fetch("/api/search/reindex", {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ action, useQueue: action === "reindex" })
81
+ });
82
+ const body = await response.json();
83
+ if (!response.ok || body.error) {
84
+ throw new Error(body.error || t("search.settings.reindexErrorLabel", "Failed to reindex"));
85
+ }
86
+ if (body.stats) {
87
+ onStatsUpdate(body.stats);
88
+ }
89
+ const successLabel = t("search.settings.reindexSuccessLabel", "Operation completed successfully");
90
+ const successMessage = action === "reindex" && body.result ? `${successLabel}: ${body.result.recordsIndexed} documents indexed` : successLabel;
91
+ flash(successMessage, "success");
92
+ await onRefresh();
93
+ await fetchActivityLogs();
94
+ } catch (err) {
95
+ const message = normalizeErrorMessage(err, t("search.settings.reindexErrorLabel", "Failed to reindex"));
96
+ flash(message, "error");
97
+ } finally {
98
+ setReindexing(null);
99
+ }
100
+ }, [showReindexDialog, t, onStatsUpdate, onRefresh, fetchActivityLogs]);
101
+ const getDialogContent = (action) => {
102
+ switch (action) {
103
+ case "clear":
104
+ return {
105
+ title: t("search.settings.clearIndexDialogTitle", "Clear Index"),
106
+ description: t("search.settings.clearIndexDialogDescription", "This will remove all documents from the Meilisearch index but keep the index settings."),
107
+ warning: t("search.settings.clearIndexDialogWarning", "Search will not work until documents are re-indexed."),
108
+ confirmLabel: t("search.settings.clearIndexLabel", "Clear Index")
109
+ };
110
+ case "recreate":
111
+ return {
112
+ title: t("search.settings.recreateIndexDialogTitle", "Recreate Index"),
113
+ description: t("search.settings.recreateIndexDialogDescription", "This will delete the index completely and recreate it with fresh settings."),
114
+ warning: t("search.settings.recreateIndexDialogWarning", "All indexed documents will be permanently removed."),
115
+ confirmLabel: t("search.settings.recreateIndexLabel", "Recreate Index")
116
+ };
117
+ case "reindex":
118
+ return {
119
+ title: t("search.settings.fullReindexDialogTitle", "Full Reindex"),
120
+ description: t("search.settings.fullReindexDialogDescription", "This will recreate the index and re-index all data from the database."),
121
+ warning: t("search.settings.fullReindexDialogWarning", "This operation may take a while depending on the amount of data."),
122
+ confirmLabel: t("search.settings.fullReindexLabel", "Full Reindex")
123
+ };
124
+ }
125
+ };
126
+ const getStrategyIcon = () => /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) });
127
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-card p-5 shadow-sm", children: [
128
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: t("search.settings.fulltext.sectionTitle", "Full-Text Search") }),
129
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: t("search.settings.fulltext.sectionDescription", "Fast, typo-tolerant search using Meilisearch.") }),
130
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "configuration", children: [
131
+ /* @__PURE__ */ jsxs(TabsList, { className: "mb-4", children: [
132
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "configuration", children: t("search.settings.tabs.configuration", "Configuration") }),
133
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "index", children: t("search.settings.tabs.indexManagement", "Index Management") }),
134
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "activity", children: t("search.settings.tabs.activity", "Activity") })
135
+ ] }),
136
+ /* @__PURE__ */ jsx(TabsContent, { value: "configuration", children: fulltextConfigLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
137
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
138
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
139
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 rounded-md border border-border bg-muted/30", children: [
141
+ /* @__PURE__ */ jsx("div", { className: `flex h-10 w-10 items-center justify-center rounded-full ${fulltextConfig?.configured ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-400"}`, children: getStrategyIcon() }),
142
+ /* @__PURE__ */ jsxs("div", { children: [
143
+ /* @__PURE__ */ jsxs("p", { className: "font-medium", children: [
144
+ t("search.settings.fulltext.driver", "Current Driver"),
145
+ ": ",
146
+ fulltextConfig?.driver ? "Meilisearch" : t("search.settings.fulltext.noDriver", "None")
147
+ ] }),
148
+ /* @__PURE__ */ jsx("p", { className: `text-sm ${fulltextConfig?.configured ? "text-emerald-600 dark:text-emerald-400" : "text-amber-600 dark:text-amber-400"}`, children: fulltextConfig?.configured ? t("search.settings.fulltext.ready", "Ready to use") : t("search.settings.fulltext.notReady", "Not configured - set environment variables below") })
149
+ ] })
150
+ ] }),
151
+ /* @__PURE__ */ jsxs("div", { children: [
152
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-2", children: t("search.settings.fulltext.envVars", "Required Environment Variables") }),
153
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: fulltextConfig?.envVars && Object.entries(fulltextConfig.envVars).map(([key, status]) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 rounded-md border border-border", children: [
154
+ /* @__PURE__ */ jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 mt-0.5 ${status.set ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400"}`, children: status.set ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }),
155
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
156
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
157
+ /* @__PURE__ */ jsx("code", { className: "text-sm font-mono bg-muted px-1.5 py-0.5 rounded", children: key }),
158
+ /* @__PURE__ */ jsx("span", { className: `text-xs ${status.set ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"}`, children: status.set ? t("search.settings.fulltext.envSet", "Set") : t("search.settings.fulltext.envMissing", "Missing") })
159
+ ] }),
160
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: status.hint })
161
+ ] })
162
+ ] }, key)) })
163
+ ] }),
164
+ /* @__PURE__ */ jsxs("div", { children: [
165
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-2", children: t("search.settings.fulltext.optional", "Optional Settings") }),
166
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: fulltextConfig?.optionalEnvVars && Object.entries(fulltextConfig.optionalEnvVars).map(([key, status]) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-2 rounded-md bg-muted/30", children: [
167
+ /* @__PURE__ */ jsx("code", { className: "text-xs font-mono bg-muted px-1.5 py-0.5 rounded", children: key }),
168
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 text-xs text-muted-foreground", children: [
169
+ /* @__PURE__ */ jsx("span", { children: status.hint }),
170
+ status.set ? /* @__PURE__ */ jsxs("span", { className: "ml-2 text-emerald-600 dark:text-emerald-400", children: [
171
+ "(",
172
+ t("search.settings.fulltext.currentValue", "Current"),
173
+ ": ",
174
+ String(status.value),
175
+ ")"
176
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
177
+ "(",
178
+ t("search.settings.fulltext.defaultValue", "Default"),
179
+ ": ",
180
+ String(status.default),
181
+ ")"
182
+ ] })
183
+ ] })
184
+ ] }, key)) })
185
+ ] }),
186
+ /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
187
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
188
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-blue-800 dark:text-blue-200", children: [
189
+ /* @__PURE__ */ jsx("p", { className: "font-medium mb-1", children: t("search.settings.fulltext.howTo", "How to set up") }),
190
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: t("search.settings.fulltext.howToDescription", "Add these variables to your .env file or deployment environment. You can use a hosted Meilisearch instance or run it locally with Docker.") }),
191
+ /* @__PURE__ */ jsxs(
192
+ "a",
193
+ {
194
+ href: "https://www.meilisearch.com/docs/learn/getting_started/quick_start",
195
+ target: "_blank",
196
+ rel: "noopener noreferrer",
197
+ className: "text-xs text-blue-600 dark:text-blue-400 hover:underline mt-1 inline-block",
198
+ children: [
199
+ t("search.settings.fulltext.learnMore", "Learn more: Meilisearch Quick Start"),
200
+ " \u2192"
201
+ ]
202
+ }
203
+ )
204
+ ] })
205
+ ] }) })
206
+ ] }) }),
207
+ /* @__PURE__ */ jsx(TabsContent, { value: "index", children: loading || fulltextConfigLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
208
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
209
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
210
+ ] }) : !fulltextConfig?.configured ? /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
211
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
212
+ /* @__PURE__ */ jsxs("div", { children: [
213
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-amber-800 dark:text-amber-200", children: t("search.settings.fulltextNotConfigured", "Full-text search driver not configured") }),
214
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-700 dark:text-amber-300 mt-1", children: t("search.settings.fulltextNotConfiguredHint", "Configure the required environment variables in the Configuration tab to enable indexing.") })
215
+ ] })
216
+ ] }) }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
217
+ fulltextStats ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border p-4 max-w-xs", children: [
218
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.documentsLabel", "Documents") }),
219
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: fulltextStats.numberOfDocuments.toLocaleString() })
220
+ ] }) : /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-muted/50", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.noIndexMessage", "No index found for this tenant. Click 'Full Reindex' to create one.") }) }),
221
+ fulltextReindexLock && /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
222
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400" }),
223
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
224
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-blue-800 dark:text-blue-200", children: t("search.settings.reindexInProgress", "Reindex operation in progress") }),
225
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-blue-700 dark:text-blue-300 mt-1", children: t("search.settings.reindexInProgressDetails", "Action: {{action}} | Started {{minutes}} minutes ago", {
226
+ action: fulltextReindexLock.action,
227
+ minutes: fulltextReindexLock.elapsedMinutes
228
+ }) })
229
+ ] })
230
+ ] }) }),
231
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3 pt-2", children: [
232
+ fulltextStats && /* @__PURE__ */ jsxs(Fragment, { children: [
233
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
234
+ /* @__PURE__ */ jsx(
235
+ Button,
236
+ {
237
+ type: "button",
238
+ variant: "outline",
239
+ size: "sm",
240
+ onClick: () => handleReindexClick("clear"),
241
+ disabled: reindexing !== null || fulltextReindexLock !== null,
242
+ children: reindexing === "clear" ? /* @__PURE__ */ jsxs(Fragment, { children: [
243
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
244
+ t("search.settings.processingLabel", "Processing...")
245
+ ] }) : t("search.settings.clearIndexLabel", "Clear Index")
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-1", children: t("search.settings.clearIndexDescription", "Remove all documents but keep index settings") })
249
+ ] }),
250
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
251
+ /* @__PURE__ */ jsx(
252
+ Button,
253
+ {
254
+ type: "button",
255
+ variant: "outline",
256
+ size: "sm",
257
+ onClick: () => handleReindexClick("recreate"),
258
+ disabled: reindexing !== null || fulltextReindexLock !== null,
259
+ children: reindexing === "recreate" ? /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
261
+ t("search.settings.processingLabel", "Processing...")
262
+ ] }) : t("search.settings.recreateIndexLabel", "Recreate Index")
263
+ }
264
+ ),
265
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-1", children: t("search.settings.recreateIndexDescription", "Delete and recreate the index with fresh settings") })
266
+ ] })
267
+ ] }),
268
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
269
+ /* @__PURE__ */ jsx(
270
+ Button,
271
+ {
272
+ type: "button",
273
+ variant: "default",
274
+ size: "sm",
275
+ onClick: () => handleReindexClick("reindex"),
276
+ disabled: reindexing !== null || fulltextReindexLock !== null,
277
+ children: reindexing === "reindex" || fulltextReindexLock !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
278
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
279
+ t("search.settings.processingLabel", "Processing...")
280
+ ] }) : t("search.settings.fullReindexLabel", "Full Reindex")
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-1", children: t("search.settings.fullReindexDescription", "Recreate index and re-index all data from database") })
284
+ ] })
285
+ ] })
286
+ ] }) }),
287
+ /* @__PURE__ */ jsxs(TabsContent, { value: "activity", children: [
288
+ activityLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
289
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
290
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading...") })
291
+ ] }) : activityLogs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-muted/50 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.activity.noLogs", "No recent indexing activity") }) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-80 overflow-y-auto", children: activityLogs.map((log) => /* @__PURE__ */ jsx(
292
+ "div",
293
+ {
294
+ className: `p-2 rounded-md text-sm ${log.level === "error" ? "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800" : "bg-muted/50"}`,
295
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
296
+ log.level === "error" && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
297
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
298
+ /* @__PURE__ */ jsx("p", { className: `text-xs ${log.level === "error" ? "text-red-800 dark:text-red-200" : "text-foreground"}`, children: log.message }),
299
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
300
+ (() => {
301
+ const d = new Date(log.occurredAt);
302
+ const pad = (n) => n.toString().padStart(2, "0");
303
+ return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
304
+ })(),
305
+ log.entityType && ` \xB7 ${log.entityType}`
306
+ ] })
307
+ ] })
308
+ ] })
309
+ },
310
+ log.id
311
+ )) }),
312
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
313
+ Button,
314
+ {
315
+ type: "button",
316
+ variant: "outline",
317
+ size: "sm",
318
+ onClick: fetchActivityLogs,
319
+ disabled: activityLoading,
320
+ children: activityLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
321
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
322
+ t("search.settings.loadingLabel", "Loading...")
323
+ ] }) : t("search.settings.refreshLabel", "Refresh")
324
+ }
325
+ ) })
326
+ ] })
327
+ ] }),
328
+ showReindexDialog && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg", children: [
329
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 mb-4", children: [
330
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/40", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }) }),
331
+ /* @__PURE__ */ jsxs("div", { children: [
332
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: getDialogContent(showReindexDialog).title }),
333
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: getDialogContent(showReindexDialog).description })
334
+ ] })
335
+ ] }),
336
+ /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 rounded-md bg-amber-50 dark:bg-amber-900/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
337
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
338
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-amber-800 dark:text-amber-200", children: getDialogContent(showReindexDialog).warning })
339
+ ] }) }),
340
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
341
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleReindexCancel, children: t("search.settings.cancelLabel", "Cancel") }),
342
+ /* @__PURE__ */ jsx(
343
+ Button,
344
+ {
345
+ type: "button",
346
+ variant: showReindexDialog === "reindex" ? "default" : "destructive",
347
+ onClick: handleReindexConfirm,
348
+ children: getDialogContent(showReindexDialog).confirmLabel
349
+ }
350
+ )
351
+ ] })
352
+ ] }) })
353
+ ] });
354
+ }
355
+ var FulltextSearchSection_default = FulltextSearchSection;
356
+ export {
357
+ FulltextSearchSection,
358
+ FulltextSearchSection_default as default
359
+ };
360
+ //# sourceMappingURL=FulltextSearchSection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/search/frontend/components/sections/FulltextSearchSection.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 { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'\n\n// Types\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 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\ntype ReindexResponse = {\n ok: boolean\n action: string\n entityId?: string | null\n result?: {\n entitiesProcessed: number\n recordsIndexed: number\n errors?: Array<{ entityId: string; error: string }>\n }\n stats?: FulltextStats | null\n error?: string\n}\n\ntype ReindexAction = 'clear' | 'recreate' | 'reindex'\n\ntype ActivityLog = {\n id: string\n source: string\n handler: string\n level: 'info' | 'error' | 'warn'\n entityType: string | null\n recordId: string | null\n message: string\n details: unknown\n occurredAt: string\n}\n\nexport type FulltextSearchSectionProps = {\n fulltextConfig: FulltextConfigResponse | null\n fulltextConfigLoading: boolean\n fulltextStats: FulltextStats | null\n fulltextReindexLock: ReindexLock | null\n loading: boolean\n onStatsUpdate: (stats: FulltextStats | null) => void\n onRefresh: () => Promise<void>\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 FulltextSearchSection({\n fulltextConfig,\n fulltextConfigLoading,\n fulltextStats,\n fulltextReindexLock,\n loading,\n onStatsUpdate,\n onRefresh,\n}: FulltextSearchSectionProps) {\n const t = useT()\n const [reindexing, setReindexing] = React.useState<ReindexAction | null>(null)\n const [showReindexDialog, setShowReindexDialog] = React.useState<ReindexAction | null>(null)\n const [activityLogs, setActivityLogs] = React.useState<ActivityLog[]>([])\n const [activityLoading, setActivityLoading] = React.useState(true)\n\n // Fetch activity logs\n const fetchActivityLogs = React.useCallback(async () => {\n setActivityLoading(true)\n try {\n const response = await fetch('/api/query_index/status')\n if (response.ok) {\n const body = await response.json() as { logs?: ActivityLog[]; errors?: ActivityLog[] }\n // Combine logs and errors\n const allLogs: ActivityLog[] = []\n if (body.logs) {\n allLogs.push(...body.logs)\n }\n if (body.errors) {\n allLogs.push(...body.errors.map(err => ({ ...err, level: 'error' as const })))\n }\n // Filter for fulltext-related logs (exclude vector/embedding related)\n const fulltextLogs = allLogs.filter(log => {\n const lowerSource = log.source?.toLowerCase() ?? ''\n const lowerMessage = log.message?.toLowerCase() ?? ''\n const lowerHandler = log.handler?.toLowerCase() ?? ''\n const isVector = lowerSource.includes('vector') || lowerMessage.includes('vector') ||\n lowerMessage.includes('embedding') || lowerHandler.includes('vector')\n return !isVector\n })\n // Sort by occurredAt descending\n fulltextLogs.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())\n setActivityLogs(fulltextLogs.slice(0, 50))\n }\n } catch {\n // Silently fail\n } finally {\n setActivityLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchActivityLogs()\n }, [fetchActivityLogs])\n\n // Poll for activity when reindexing\n React.useEffect(() => {\n if (fulltextReindexLock || reindexing) {\n const interval = setInterval(fetchActivityLogs, 5000)\n return () => clearInterval(interval)\n }\n }, [fulltextReindexLock, reindexing, fetchActivityLogs])\n\n const handleReindexClick = (action: ReindexAction) => {\n setShowReindexDialog(action)\n }\n\n const handleReindexCancel = () => {\n setShowReindexDialog(null)\n }\n\n const handleReindexConfirm = React.useCallback(async () => {\n const action = showReindexDialog\n if (!action) return\n\n setShowReindexDialog(null)\n setReindexing(action)\n\n try {\n const response = await fetch('/api/search/reindex', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action, useQueue: action === 'reindex' }),\n })\n\n const body = await response.json() as ReindexResponse\n\n if (!response.ok || body.error) {\n throw new Error(body.error || t('search.settings.reindexErrorLabel', 'Failed to reindex'))\n }\n\n if (body.stats) {\n onStatsUpdate(body.stats)\n }\n\n const successLabel = t('search.settings.reindexSuccessLabel', 'Operation completed successfully')\n const successMessage = action === 'reindex' && body.result\n ? `${successLabel}: ${body.result.recordsIndexed} documents indexed`\n : successLabel\n\n flash(successMessage, 'success')\n await onRefresh()\n await fetchActivityLogs()\n } catch (err) {\n const message = normalizeErrorMessage(err, t('search.settings.reindexErrorLabel', 'Failed to reindex'))\n flash(message, 'error')\n } finally {\n setReindexing(null)\n }\n }, [showReindexDialog, t, onStatsUpdate, onRefresh, fetchActivityLogs])\n\n const getDialogContent = (action: ReindexAction) => {\n switch (action) {\n case 'clear':\n return {\n title: t('search.settings.clearIndexDialogTitle', 'Clear Index'),\n description: t('search.settings.clearIndexDialogDescription', 'This will remove all documents from the Meilisearch index but keep the index settings.'),\n warning: t('search.settings.clearIndexDialogWarning', 'Search will not work until documents are re-indexed.'),\n confirmLabel: t('search.settings.clearIndexLabel', 'Clear Index'),\n }\n case 'recreate':\n return {\n title: t('search.settings.recreateIndexDialogTitle', 'Recreate Index'),\n description: t('search.settings.recreateIndexDialogDescription', 'This will delete the index completely and recreate it with fresh settings.'),\n warning: t('search.settings.recreateIndexDialogWarning', 'All indexed documents will be permanently removed.'),\n confirmLabel: t('search.settings.recreateIndexLabel', 'Recreate Index'),\n }\n case 'reindex':\n return {\n title: t('search.settings.fullReindexDialogTitle', 'Full Reindex'),\n description: t('search.settings.fullReindexDialogDescription', 'This will recreate the index and re-index all data from the database.'),\n warning: t('search.settings.fullReindexDialogWarning', 'This operation may take a while depending on the amount of data.'),\n confirmLabel: t('search.settings.fullReindexLabel', 'Full Reindex'),\n }\n }\n }\n\n const getStrategyIcon = () => (\n <svg className=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z\" />\n </svg>\n )\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-5 shadow-sm\">\n <h2 className=\"text-lg font-semibold mb-2\">\n {t('search.settings.fulltext.sectionTitle', 'Full-Text Search')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.fulltext.sectionDescription', 'Fast, typo-tolerant search using Meilisearch.')}\n </p>\n\n <Tabs defaultValue=\"configuration\">\n <TabsList className=\"mb-4\">\n <TabsTrigger value=\"configuration\">\n {t('search.settings.tabs.configuration', 'Configuration')}\n </TabsTrigger>\n <TabsTrigger value=\"index\">\n {t('search.settings.tabs.indexManagement', 'Index Management')}\n </TabsTrigger>\n <TabsTrigger value=\"activity\">\n {t('search.settings.tabs.activity', 'Activity')}\n </TabsTrigger>\n </TabsList>\n\n {/* Configuration Tab */}\n <TabsContent value=\"configuration\">\n {fulltextConfigLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Driver Status */}\n <div className=\"flex items-center gap-3 p-3 rounded-md border border-border bg-muted/30\">\n <div className={`flex h-10 w-10 items-center justify-center rounded-full ${\n fulltextConfig?.configured\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-400'\n }`}>\n {getStrategyIcon()}\n </div>\n <div>\n <p className=\"font-medium\">\n {t('search.settings.fulltext.driver', 'Current Driver')}: {fulltextConfig?.driver ? 'Meilisearch' : t('search.settings.fulltext.noDriver', 'None')}\n </p>\n <p className={`text-sm ${\n fulltextConfig?.configured\n ? 'text-emerald-600 dark:text-emerald-400'\n : 'text-amber-600 dark:text-amber-400'\n }`}>\n {fulltextConfig?.configured\n ? t('search.settings.fulltext.ready', 'Ready to use')\n : t('search.settings.fulltext.notReady', 'Not configured - set environment variables below')}\n </p>\n </div>\n </div>\n\n {/* Required Environment Variables */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">\n {t('search.settings.fulltext.envVars', 'Required Environment Variables')}\n </h3>\n <div className=\"space-y-2\">\n {fulltextConfig?.envVars && Object.entries(fulltextConfig.envVars).map(([key, status]) => (\n <div key={key} className=\"flex items-start gap-3 p-3 rounded-md border border-border\">\n <div className={`flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 mt-0.5 ${\n status.set\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400'\n }`}>\n {status.set ? (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ) : (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n )}\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <code className=\"text-sm font-mono bg-muted px-1.5 py-0.5 rounded\">{key}</code>\n <span className={`text-xs ${status.set ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}`}>\n {status.set ? t('search.settings.fulltext.envSet', 'Set') : t('search.settings.fulltext.envMissing', 'Missing')}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">{status.hint}</p>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Optional Settings */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">\n {t('search.settings.fulltext.optional', 'Optional Settings')}\n </h3>\n <div className=\"space-y-2\">\n {fulltextConfig?.optionalEnvVars && Object.entries(fulltextConfig.optionalEnvVars).map(([key, status]) => (\n <div key={key} className=\"flex items-start gap-3 p-2 rounded-md bg-muted/30\">\n <code className=\"text-xs font-mono bg-muted px-1.5 py-0.5 rounded\">{key}</code>\n <div className=\"flex-1 text-xs text-muted-foreground\">\n <span>{status.hint}</span>\n {status.set ? (\n <span className=\"ml-2 text-emerald-600 dark:text-emerald-400\">\n ({t('search.settings.fulltext.currentValue', 'Current')}: {String(status.value)})\n </span>\n ) : (\n <span className=\"ml-2\">\n ({t('search.settings.fulltext.defaultValue', 'Default')}: {String(status.default)})\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Setup Instructions */}\n <div className=\"p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-2\">\n <svg className=\"h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n <div className=\"text-sm text-blue-800 dark:text-blue-200\">\n <p className=\"font-medium mb-1\">{t('search.settings.fulltext.howTo', 'How to set up')}</p>\n <p className=\"text-xs\">{t('search.settings.fulltext.howToDescription', 'Add these variables to your .env file or deployment environment. You can use a hosted Meilisearch instance or run it locally with Docker.')}</p>\n <a\n href=\"https://www.meilisearch.com/docs/learn/getting_started/quick_start\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-blue-600 dark:text-blue-400 hover:underline mt-1 inline-block\"\n >\n {t('search.settings.fulltext.learnMore', 'Learn more: Meilisearch Quick Start')} \u2192\n </a>\n </div>\n </div>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Index Management Tab */}\n <TabsContent value=\"index\">\n {(loading || fulltextConfigLoading) ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : !fulltextConfig?.configured ? (\n <div className=\"p-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-amber-800 dark:text-amber-200\">\n {t('search.settings.fulltextNotConfigured', 'Full-text search driver not configured')}\n </p>\n <p className=\"text-xs text-amber-700 dark:text-amber-300 mt-1\">\n {t('search.settings.fulltextNotConfiguredHint', 'Configure the required environment variables in the Configuration tab to enable indexing.')}\n </p>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Stats */}\n {fulltextStats ? (\n <div className=\"rounded-md border border-border p-4 max-w-xs\">\n <p className=\"text-sm text-muted-foreground\">{t('search.settings.documentsLabel', 'Documents')}</p>\n <p className=\"text-2xl font-bold\">{fulltextStats.numberOfDocuments.toLocaleString()}</p>\n </div>\n ) : (\n <div className=\"p-3 rounded-md bg-muted/50\">\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.noIndexMessage', \"No index found for this tenant. Click 'Full Reindex' to create one.\")}\n </p>\n </div>\n )}\n\n {/* Active reindex lock banner */}\n {fulltextReindexLock && (\n <div className=\"p-4 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-blue-800 dark:text-blue-200\">\n {t('search.settings.reindexInProgress', 'Reindex operation in progress')}\n </p>\n <p className=\"text-xs text-blue-700 dark:text-blue-300 mt-1\">\n {t('search.settings.reindexInProgressDetails', 'Action: {{action}} | Started {{minutes}} minutes ago', {\n action: fulltextReindexLock.action,\n minutes: fulltextReindexLock.elapsedMinutes,\n })}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-wrap gap-3 pt-2\">\n {fulltextStats && (\n <>\n <div className=\"flex flex-col\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleReindexClick('clear')}\n disabled={reindexing !== null || fulltextReindexLock !== null}\n >\n {reindexing === 'clear' ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.processingLabel', 'Processing...')}\n </>\n ) : (\n t('search.settings.clearIndexLabel', 'Clear Index')\n )}\n </Button>\n <span className=\"text-xs text-muted-foreground mt-1\">\n {t('search.settings.clearIndexDescription', 'Remove all documents but keep index settings')}\n </span>\n </div>\n <div className=\"flex flex-col\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleReindexClick('recreate')}\n disabled={reindexing !== null || fulltextReindexLock !== null}\n >\n {reindexing === 'recreate' ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.processingLabel', 'Processing...')}\n </>\n ) : (\n t('search.settings.recreateIndexLabel', 'Recreate Index')\n )}\n </Button>\n <span className=\"text-xs text-muted-foreground mt-1\">\n {t('search.settings.recreateIndexDescription', 'Delete and recreate the index with fresh settings')}\n </span>\n </div>\n </>\n )}\n <div className=\"flex flex-col\">\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={() => handleReindexClick('reindex')}\n disabled={reindexing !== null || fulltextReindexLock !== null}\n >\n {reindexing === 'reindex' || fulltextReindexLock !== null ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.processingLabel', 'Processing...')}\n </>\n ) : (\n t('search.settings.fullReindexLabel', 'Full Reindex')\n )}\n </Button>\n <span className=\"text-xs text-muted-foreground mt-1\">\n {t('search.settings.fullReindexDescription', 'Recreate index and re-index all data from database')}\n </span>\n </div>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Activity Tab */}\n <TabsContent value=\"activity\">\n {activityLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading...')}</span>\n </div>\n ) : activityLogs.length === 0 ? (\n <div className=\"p-4 rounded-md bg-muted/50 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.activity.noLogs', 'No recent indexing activity')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {activityLogs.map((log) => (\n <div\n key={log.id}\n className={`p-2 rounded-md text-sm ${\n log.level === 'error'\n ? 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800'\n : 'bg-muted/50'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n {log.level === 'error' && (\n <svg className=\"h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className={`text-xs ${log.level === 'error' ? 'text-red-800 dark:text-red-200' : 'text-foreground'}`}>\n {log.message}\n </p>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {(() => {\n const d = new Date(log.occurredAt)\n const pad = (n: number) => n.toString().padStart(2, '0')\n return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`\n })()}\n {log.entityType && ` \u00B7 ${log.entityType}`}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n <div className=\"mt-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={fetchActivityLogs}\n disabled={activityLoading}\n >\n {activityLoading ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.loadingLabel', 'Loading...')}\n </>\n ) : (\n t('search.settings.refreshLabel', 'Refresh')\n )}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n\n {/* Reindex Confirmation Dialog */}\n {showReindexDialog && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/40\">\n <svg className=\"h-5 w-5 text-amber-600 dark:text-amber-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </div>\n <div>\n <h3 className=\"text-lg font-semibold\">{getDialogContent(showReindexDialog).title}</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">{getDialogContent(showReindexDialog).description}</p>\n </div>\n </div>\n\n <div className=\"mb-4 p-3 rounded-md bg-amber-50 dark:bg-amber-900/20\">\n <div className=\"flex items-start gap-2\">\n <svg className=\"h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <p className=\"text-sm text-amber-800 dark:text-amber-200\">{getDialogContent(showReindexDialog).warning}</p>\n </div>\n </div>\n\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={handleReindexCancel}>\n {t('search.settings.cancelLabel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant={showReindexDialog === 'reindex' ? 'default' : 'destructive'}\n onClick={handleReindexConfirm}\n >\n {getDialogContent(showReindexDialog).confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default FulltextSearchSection\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,101 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
5
+ function GlobalSearchSection({
6
+ loading,
7
+ saving,
8
+ strategies,
9
+ fulltextConfigured,
10
+ vectorConfigured,
11
+ onToggleStrategy
12
+ }) {
13
+ const t = useT();
14
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-card p-5 shadow-sm", children: [
15
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: t("search.settings.globalSearch.title", "Global Search (Cmd+K)") }),
16
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: t("search.settings.globalSearch.description", "Configure which search methods are used when searching with Cmd+K.") }),
17
+ loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
18
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
19
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
20
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
21
+ /* @__PURE__ */ jsxs(
22
+ "label",
23
+ {
24
+ className: `flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? "opacity-60" : ""}`,
25
+ children: [
26
+ /* @__PURE__ */ jsx(
27
+ "input",
28
+ {
29
+ type: "checkbox",
30
+ checked: strategies.has("fulltext"),
31
+ onChange: () => onToggleStrategy("fulltext"),
32
+ disabled: saving || strategies.has("fulltext") && strategies.size === 1,
33
+ className: "mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
37
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("search.settings.globalSearch.fulltext", "Full-Text Search") }),
39
+ !fulltextConfigured && /* @__PURE__ */ jsx("span", { className: "text-xs text-amber-600 dark:text-amber-400", children: t("search.settings.globalSearch.notConfigured", "(Not configured)") })
40
+ ] }),
41
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.globalSearch.fulltextDescription", "Fast, typo-tolerant search across all text fields.") })
42
+ ] })
43
+ ]
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsxs(
47
+ "label",
48
+ {
49
+ className: `flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? "opacity-60" : ""}`,
50
+ children: [
51
+ /* @__PURE__ */ jsx(
52
+ "input",
53
+ {
54
+ type: "checkbox",
55
+ checked: strategies.has("vector"),
56
+ onChange: () => onToggleStrategy("vector"),
57
+ disabled: saving || strategies.has("vector") && strategies.size === 1,
58
+ className: "mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
62
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
63
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("search.settings.globalSearch.vector", "Semantic Search (AI)") }),
64
+ !vectorConfigured && /* @__PURE__ */ jsx("span", { className: "text-xs text-amber-600 dark:text-amber-400", children: t("search.settings.globalSearch.notConfigured", "(Not configured)") })
65
+ ] }),
66
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.globalSearch.vectorDescription", "AI-powered search that understands meaning and finds related content.") })
67
+ ] })
68
+ ]
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsxs(
72
+ "label",
73
+ {
74
+ className: `flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? "opacity-60" : ""}`,
75
+ children: [
76
+ /* @__PURE__ */ jsx(
77
+ "input",
78
+ {
79
+ type: "checkbox",
80
+ checked: strategies.has("tokens"),
81
+ onChange: () => onToggleStrategy("tokens"),
82
+ disabled: saving || strategies.has("tokens") && strategies.size === 1,
83
+ className: "mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
87
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("search.settings.globalSearch.tokens", "Keyword Search") }),
88
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.globalSearch.tokensDescription", "Exact word matching in the database.") })
89
+ ] })
90
+ ]
91
+ }
92
+ )
93
+ ] })
94
+ ] });
95
+ }
96
+ var GlobalSearchSection_default = GlobalSearchSection;
97
+ export {
98
+ GlobalSearchSection,
99
+ GlobalSearchSection_default as default
100
+ };
101
+ //# sourceMappingURL=GlobalSearchSection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/search/frontend/components/sections/GlobalSearchSection.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\n\nexport type GlobalSearchSectionProps = {\n loading: boolean\n saving: boolean\n strategies: Set<string>\n fulltextConfigured: boolean\n vectorConfigured: boolean\n onToggleStrategy: (strategyId: string) => void\n}\n\nexport function GlobalSearchSection({\n loading,\n saving,\n strategies,\n fulltextConfigured,\n vectorConfigured,\n onToggleStrategy,\n}: GlobalSearchSectionProps) {\n const t = useT()\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-5 shadow-sm\">\n <h2 className=\"text-lg font-semibold mb-2\">\n {t('search.settings.globalSearch.title', 'Global Search (Cmd+K)')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.globalSearch.description', 'Configure which search methods are used when searching with Cmd+K.')}\n </p>\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {/* Full-Text Search Toggle */}\n <label\n className={`flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? 'opacity-60' : ''}`}\n >\n <input\n type=\"checkbox\"\n checked={strategies.has('fulltext')}\n onChange={() => onToggleStrategy('fulltext')}\n disabled={saving || (strategies.has('fulltext') && strategies.size === 1)}\n className=\"mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">\n {t('search.settings.globalSearch.fulltext', 'Full-Text Search')}\n </span>\n {!fulltextConfigured && (\n <span className=\"text-xs text-amber-600 dark:text-amber-400\">\n {t('search.settings.globalSearch.notConfigured', '(Not configured)')}\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.globalSearch.fulltextDescription', 'Fast, typo-tolerant search across all text fields.')}\n </p>\n </div>\n </label>\n\n {/* Semantic Search Toggle */}\n <label\n className={`flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? 'opacity-60' : ''}`}\n >\n <input\n type=\"checkbox\"\n checked={strategies.has('vector')}\n onChange={() => onToggleStrategy('vector')}\n disabled={saving || (strategies.has('vector') && strategies.size === 1)}\n className=\"mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">\n {t('search.settings.globalSearch.vector', 'Semantic Search (AI)')}\n </span>\n {!vectorConfigured && (\n <span className=\"text-xs text-amber-600 dark:text-amber-400\">\n {t('search.settings.globalSearch.notConfigured', '(Not configured)')}\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.globalSearch.vectorDescription', 'AI-powered search that understands meaning and finds related content.')}\n </p>\n </div>\n </label>\n\n {/* Keyword Search Toggle */}\n <label\n className={`flex items-start gap-3 p-3 rounded-md border border-border hover:bg-muted/50 cursor-pointer transition-colors ${saving ? 'opacity-60' : ''}`}\n >\n <input\n type=\"checkbox\"\n checked={strategies.has('tokens')}\n onChange={() => onToggleStrategy('tokens')}\n disabled={saving || (strategies.has('tokens') && strategies.size === 1)}\n className=\"mt-1 h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n <div className=\"flex-1\">\n <span className=\"font-medium\">\n {t('search.settings.globalSearch.tokens', 'Keyword Search')}\n </span>\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.globalSearch.tokensDescription', 'Exact word matching in the database.')}\n </p>\n </div>\n </label>\n </div>\n )}\n </div>\n )\n}\n\nexport default GlobalSearchSection\n"],
5
+ "mappings": ";AA2BM,cAQE,YARF;AAxBN,SAAS,YAAY;AACrB,SAAS,eAAe;AAWjB,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,IAAI,KAAK;AAEf,SACE,qBAAC,SAAI,WAAU,yDACb;AAAA,wBAAC,QAAG,WAAU,8BACX,YAAE,sCAAsC,uBAAuB,GAClE;AAAA,IACA,oBAAC,OAAE,WAAU,sCACV,YAAE,4CAA4C,oEAAoE,GACrH;AAAA,IAEC,UACC,qBAAC,SAAI,WAAU,iDACb;AAAA,0BAAC,WAAQ,MAAK,MAAK;AAAA,MACnB,oBAAC,UAAM,YAAE,gCAAgC,qBAAqB,GAAE;AAAA,OAClE,IAEA,qBAAC,SAAI,WAAU,aAEb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,iHAAiH,SAAS,eAAe,EAAE;AAAA,UAEtJ;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,WAAW,IAAI,UAAU;AAAA,gBAClC,UAAU,MAAM,iBAAiB,UAAU;AAAA,gBAC3C,UAAU,UAAW,WAAW,IAAI,UAAU,KAAK,WAAW,SAAS;AAAA,gBACvE,WAAU;AAAA;AAAA,YACZ;AAAA,YACA,qBAAC,SAAI,WAAU,UACb;AAAA,mCAAC,SAAI,WAAU,2BACb;AAAA,oCAAC,UAAK,WAAU,eACb,YAAE,yCAAyC,kBAAkB,GAChE;AAAA,gBACC,CAAC,sBACA,oBAAC,UAAK,WAAU,8CACb,YAAE,8CAA8C,kBAAkB,GACrE;AAAA,iBAEJ;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,oDAAoD,oDAAoD,GAC7G;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,MAGA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,iHAAiH,SAAS,eAAe,EAAE;AAAA,UAEtJ;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,WAAW,IAAI,QAAQ;AAAA,gBAChC,UAAU,MAAM,iBAAiB,QAAQ;AAAA,gBACzC,UAAU,UAAW,WAAW,IAAI,QAAQ,KAAK,WAAW,SAAS;AAAA,gBACrE,WAAU;AAAA;AAAA,YACZ;AAAA,YACA,qBAAC,SAAI,WAAU,UACb;AAAA,mCAAC,SAAI,WAAU,2BACb;AAAA,oCAAC,UAAK,WAAU,eACb,YAAE,uCAAuC,sBAAsB,GAClE;AAAA,gBACC,CAAC,oBACA,oBAAC,UAAK,WAAU,8CACb,YAAE,8CAA8C,kBAAkB,GACrE;AAAA,iBAEJ;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,kDAAkD,uEAAuE,GAC9H;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,MAGA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,iHAAiH,SAAS,eAAe,EAAE;AAAA,UAEtJ;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,WAAW,IAAI,QAAQ;AAAA,gBAChC,UAAU,MAAM,iBAAiB,QAAQ;AAAA,gBACzC,UAAU,UAAW,WAAW,IAAI,QAAQ,KAAK,WAAW,SAAS;AAAA,gBACrE,WAAU;AAAA;AAAA,YACZ;AAAA,YACA,qBAAC,SAAI,WAAU,UACb;AAAA,kCAAC,UAAK,WAAU,eACb,YAAE,uCAAuC,gBAAgB,GAC5D;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,kDAAkD,sCAAsC,GAC7F;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,OACF;AAAA,KAEJ;AAEJ;AAEA,IAAO,8BAAQ;",
6
+ "names": []
7
+ }