@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,608 @@
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 { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
6
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { Label } from "@open-mercato/ui/primitives/label";
9
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
10
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@open-mercato/ui/primitives/tabs";
11
+ const EMBEDDING_PROVIDERS = {
12
+ openai: {
13
+ name: "OpenAI",
14
+ envKeyRequired: "OPENAI_API_KEY",
15
+ defaultModel: "text-embedding-3-small",
16
+ models: [
17
+ { id: "text-embedding-3-small", name: "text-embedding-3-small", dimension: 1536 },
18
+ { id: "text-embedding-3-large", name: "text-embedding-3-large", dimension: 3072, configurableDimension: true, minDimension: 256, maxDimension: 3072 },
19
+ { id: "text-embedding-ada-002", name: "text-embedding-ada-002", dimension: 1536 }
20
+ ]
21
+ },
22
+ google: {
23
+ name: "Google Generative AI",
24
+ envKeyRequired: "GOOGLE_GENERATIVE_AI_API_KEY",
25
+ defaultModel: "text-embedding-004",
26
+ models: [
27
+ { id: "text-embedding-004", name: "text-embedding-004", dimension: 768, configurableDimension: true, minDimension: 1, maxDimension: 768 },
28
+ { id: "embedding-001", name: "embedding-001", dimension: 768 }
29
+ ]
30
+ },
31
+ mistral: {
32
+ name: "Mistral",
33
+ envKeyRequired: "MISTRAL_API_KEY",
34
+ defaultModel: "mistral-embed",
35
+ models: [
36
+ { id: "mistral-embed", name: "mistral-embed", dimension: 1024 }
37
+ ]
38
+ },
39
+ cohere: {
40
+ name: "Cohere",
41
+ envKeyRequired: "COHERE_API_KEY",
42
+ defaultModel: "embed-english-v3.0",
43
+ models: [
44
+ { id: "embed-english-v3.0", name: "embed-english-v3.0", dimension: 1024 },
45
+ { id: "embed-multilingual-v3.0", name: "embed-multilingual-v3.0", dimension: 1024 },
46
+ { id: "embed-english-light-v3.0", name: "embed-english-light-v3.0", dimension: 384 },
47
+ { id: "embed-multilingual-light-v3.0", name: "embed-multilingual-light-v3.0", dimension: 384 }
48
+ ]
49
+ },
50
+ bedrock: {
51
+ name: "Amazon Bedrock",
52
+ envKeyRequired: "AWS_ACCESS_KEY_ID",
53
+ defaultModel: "amazon.titan-embed-text-v2:0",
54
+ models: [
55
+ { id: "amazon.titan-embed-text-v2:0", name: "Titan Embed Text v2", dimension: 1024, configurableDimension: true, minDimension: 256, maxDimension: 1024 },
56
+ { id: "amazon.titan-embed-text-v1", name: "Titan Embed Text v1", dimension: 1536 },
57
+ { id: "cohere.embed-english-v3", name: "Cohere Embed English v3", dimension: 1024 },
58
+ { id: "cohere.embed-multilingual-v3", name: "Cohere Embed Multilingual v3", dimension: 1024 }
59
+ ]
60
+ },
61
+ ollama: {
62
+ name: "Ollama (Local)",
63
+ envKeyRequired: "OLLAMA_BASE_URL",
64
+ defaultModel: "nomic-embed-text",
65
+ models: [
66
+ { id: "nomic-embed-text", name: "nomic-embed-text", dimension: 768 },
67
+ { id: "mxbai-embed-large", name: "mxbai-embed-large", dimension: 1024 },
68
+ { id: "all-minilm", name: "all-minilm", dimension: 384 },
69
+ { id: "snowflake-arctic-embed", name: "snowflake-arctic-embed", dimension: 1024 }
70
+ ]
71
+ }
72
+ };
73
+ function VectorSearchSection({
74
+ embeddingSettings,
75
+ embeddingLoading,
76
+ vectorStoreConfig,
77
+ vectorStoreConfigLoading,
78
+ vectorReindexLock,
79
+ onEmbeddingSettingsUpdate,
80
+ onRefreshEmbeddings
81
+ }) {
82
+ const t = useT();
83
+ const [embeddingSaving, setEmbeddingSaving] = React.useState(false);
84
+ const autoIndexingPreviousRef = React.useRef(true);
85
+ const [selectedProvider, setSelectedProvider] = React.useState(null);
86
+ const [selectedModel, setSelectedModel] = React.useState(null);
87
+ const [customModelName, setCustomModelName] = React.useState("");
88
+ const [customDimension, setCustomDimension] = React.useState(768);
89
+ const [pendingEmbeddingConfig, setPendingEmbeddingConfig] = React.useState(null);
90
+ const [showEmbeddingConfirmDialog, setShowEmbeddingConfirmDialog] = React.useState(false);
91
+ const [vectorReindexing, setVectorReindexing] = React.useState(false);
92
+ const [showVectorReindexDialog, setShowVectorReindexDialog] = React.useState(false);
93
+ const [activityLogs, setActivityLogs] = React.useState([]);
94
+ const [activityLoading, setActivityLoading] = React.useState(true);
95
+ const fetchActivityLogs = React.useCallback(async () => {
96
+ setActivityLoading(true);
97
+ try {
98
+ const response = await fetch("/api/query_index/status");
99
+ if (response.ok) {
100
+ const body = await response.json();
101
+ const allLogs = [];
102
+ if (body.logs) {
103
+ allLogs.push(...body.logs);
104
+ }
105
+ if (body.errors) {
106
+ allLogs.push(...body.errors.map((err) => ({ ...err, level: "error" })));
107
+ }
108
+ const vectorLogs = allLogs.filter((log) => {
109
+ const lowerSource = log.source?.toLowerCase() ?? "";
110
+ const lowerMessage = log.message?.toLowerCase() ?? "";
111
+ const lowerHandler = log.handler?.toLowerCase() ?? "";
112
+ return lowerSource.includes("vector") || lowerMessage.includes("vector") || lowerMessage.includes("embedding") || lowerHandler.includes("vector");
113
+ });
114
+ vectorLogs.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime());
115
+ setActivityLogs(vectorLogs.slice(0, 50));
116
+ }
117
+ } catch {
118
+ } finally {
119
+ setActivityLoading(false);
120
+ }
121
+ }, []);
122
+ React.useEffect(() => {
123
+ fetchActivityLogs();
124
+ }, [fetchActivityLogs]);
125
+ React.useEffect(() => {
126
+ if (vectorReindexLock || vectorReindexing) {
127
+ const interval = setInterval(fetchActivityLogs, 5e3);
128
+ return () => clearInterval(interval);
129
+ }
130
+ }, [vectorReindexLock, vectorReindexing, fetchActivityLogs]);
131
+ const updateAutoIndexing = React.useCallback(async (nextValue) => {
132
+ autoIndexingPreviousRef.current = embeddingSettings?.autoIndexingEnabled ?? true;
133
+ if (embeddingSettings) {
134
+ onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: nextValue });
135
+ }
136
+ setEmbeddingSaving(true);
137
+ try {
138
+ const body = await readApiResultOrThrow(
139
+ "/api/search/embeddings",
140
+ {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify({ autoIndexingEnabled: nextValue })
144
+ },
145
+ { errorMessage: t("search.settings.errors.saveFailed", "Failed to save settings"), allowNullResult: true }
146
+ );
147
+ if (body?.settings) {
148
+ onEmbeddingSettingsUpdate(body.settings);
149
+ autoIndexingPreviousRef.current = body.settings.autoIndexingEnabled;
150
+ }
151
+ flash(t("search.settings.messages.saved", "Settings saved"), "success");
152
+ } catch {
153
+ if (embeddingSettings) {
154
+ onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: autoIndexingPreviousRef.current });
155
+ }
156
+ } finally {
157
+ setEmbeddingSaving(false);
158
+ }
159
+ }, [embeddingSettings, onEmbeddingSettingsUpdate, t]);
160
+ const handleProviderChange = (providerId) => {
161
+ setSelectedProvider(providerId);
162
+ setSelectedModel(null);
163
+ setCustomModelName("");
164
+ setCustomDimension(768);
165
+ };
166
+ const handleModelChange = (modelId) => {
167
+ setSelectedModel(modelId);
168
+ };
169
+ const handleApplyEmbeddingChanges = () => {
170
+ const newProviderId = selectedProvider ?? embeddingSettings?.embeddingConfig?.providerId ?? "openai";
171
+ const newProviderInfo = EMBEDDING_PROVIDERS[newProviderId];
172
+ const newModelId = selectedModel ?? (selectedProvider ? newProviderInfo.defaultModel : embeddingSettings?.embeddingConfig?.model ?? newProviderInfo.defaultModel);
173
+ let modelName;
174
+ let dimension;
175
+ if (newModelId === "custom") {
176
+ modelName = customModelName.trim();
177
+ dimension = customDimension;
178
+ if (!modelName) {
179
+ flash(t("search.settings.errors.modelRequired", "Please enter a model name"), "error");
180
+ return;
181
+ }
182
+ if (dimension <= 0) {
183
+ flash(t("search.settings.errors.dimensionRequired", "Please enter a valid dimension"), "error");
184
+ return;
185
+ }
186
+ } else {
187
+ const newModel = newProviderInfo.models.find((m) => m.id === newModelId) ?? newProviderInfo.models[0];
188
+ modelName = newModel.id;
189
+ dimension = newModel.dimension;
190
+ }
191
+ const newConfig = {
192
+ providerId: newProviderId,
193
+ model: modelName,
194
+ dimension,
195
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
196
+ };
197
+ if (embeddingSettings?.indexedDimension || embeddingSettings?.embeddingConfig) {
198
+ setPendingEmbeddingConfig(newConfig);
199
+ setShowEmbeddingConfirmDialog(true);
200
+ } else {
201
+ applyEmbeddingConfig(newConfig);
202
+ }
203
+ };
204
+ const handleCancelEmbeddingSelection = () => {
205
+ setSelectedProvider(null);
206
+ setSelectedModel(null);
207
+ setCustomModelName("");
208
+ setCustomDimension(768);
209
+ };
210
+ const applyEmbeddingConfig = async (config) => {
211
+ setEmbeddingSaving(true);
212
+ setShowEmbeddingConfirmDialog(false);
213
+ setPendingEmbeddingConfig(null);
214
+ try {
215
+ await readApiResultOrThrow(
216
+ "/api/search/embeddings",
217
+ {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify({ embeddingConfig: config })
221
+ },
222
+ { errorMessage: t("search.settings.errors.saveFailed", "Failed to save settings"), allowNullResult: true }
223
+ );
224
+ setSelectedProvider(null);
225
+ setSelectedModel(null);
226
+ flash(t("search.settings.messages.providerSaved", "Embedding provider saved"), "success");
227
+ await onRefreshEmbeddings();
228
+ } catch {
229
+ } finally {
230
+ setEmbeddingSaving(false);
231
+ }
232
+ };
233
+ const handleEmbeddingConfirmChange = () => {
234
+ if (pendingEmbeddingConfig) {
235
+ applyEmbeddingConfig(pendingEmbeddingConfig);
236
+ }
237
+ };
238
+ const handleEmbeddingCancelChange = () => {
239
+ setShowEmbeddingConfirmDialog(false);
240
+ setPendingEmbeddingConfig(null);
241
+ };
242
+ const handleVectorReindexClick = () => {
243
+ setShowVectorReindexDialog(true);
244
+ };
245
+ const handleVectorReindexConfirm = async () => {
246
+ setShowVectorReindexDialog(false);
247
+ setVectorReindexing(true);
248
+ try {
249
+ await readApiResultOrThrow(
250
+ "/api/search/embeddings/reindex",
251
+ {
252
+ method: "POST",
253
+ headers: { "Content-Type": "application/json" },
254
+ body: JSON.stringify({ purgeFirst: true })
255
+ },
256
+ { errorMessage: t("search.settings.errors.reindexFailed", "Reindex failed"), allowNullResult: true }
257
+ );
258
+ flash(t("search.settings.messages.reindexStarted", "Reindex started"), "success");
259
+ await fetchActivityLogs();
260
+ } catch {
261
+ } finally {
262
+ setVectorReindexing(false);
263
+ }
264
+ };
265
+ const handleVectorReindexCancel = () => {
266
+ setShowVectorReindexDialog(false);
267
+ };
268
+ const savedProvider = embeddingSettings?.embeddingConfig?.providerId ?? "openai";
269
+ const savedProviderInfo = EMBEDDING_PROVIDERS[savedProvider];
270
+ const savedModel = embeddingSettings?.embeddingConfig?.model ?? savedProviderInfo.defaultModel;
271
+ const savedDimension = embeddingSettings?.embeddingConfig?.dimension ?? savedProviderInfo.models[0]?.dimension ?? 768;
272
+ const savedModelIsPredefined = savedProviderInfo.models.some((m) => m.id === savedModel);
273
+ const savedCustomModel = !savedModelIsPredefined && savedModel ? { id: savedModel, name: savedModel, dimension: savedDimension } : null;
274
+ const displayProvider = selectedProvider ?? savedProvider;
275
+ const displayProviderInfo = EMBEDDING_PROVIDERS[displayProvider];
276
+ const displayModel = selectedModel ?? (selectedProvider ? displayProviderInfo.defaultModel : savedModel);
277
+ const isCustomModel = displayModel === "custom";
278
+ const displayModelIsSavedCustom = !isCustomModel && displayProvider === savedProvider && savedCustomModel && displayModel === savedCustomModel.id;
279
+ const displayModelInfo = isCustomModel ? null : displayModelIsSavedCustom ? savedCustomModel : displayProviderInfo.models.find((m) => m.id === displayModel) ?? displayProviderInfo.models[0];
280
+ const displayDimension = isCustomModel ? customDimension : displayModelInfo?.dimension ?? 768;
281
+ const hasUnsavedEmbeddingChanges = selectedProvider !== null && selectedProvider !== savedProvider || selectedModel !== null && selectedModel !== savedModel || selectedProvider !== null && selectedModel === null && displayProviderInfo.defaultModel !== savedModel || isCustomModel && (customModelName.trim() !== "" || customDimension !== 768);
282
+ const isEmbeddingConfigured = embeddingSettings?.configuredProviders?.includes(savedProvider);
283
+ const providerOptions = ["openai", "google", "mistral", "cohere", "bedrock", "ollama"];
284
+ const autoIndexingChecked = embeddingSettings ? embeddingSettings.autoIndexingEnabled : true;
285
+ const autoIndexingDisabled = embeddingLoading || embeddingSaving || Boolean(embeddingSettings?.autoIndexingLocked);
286
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-card p-5 shadow-sm", children: [
287
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: t("search.settings.vector.sectionTitle", "Vector Search") }),
288
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: t("search.settings.vector.sectionDescription", "AI-powered semantic search using embeddings.") }),
289
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "configuration", children: [
290
+ /* @__PURE__ */ jsxs(TabsList, { className: "mb-4", children: [
291
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "configuration", children: t("search.settings.tabs.configuration", "Configuration") }),
292
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "index", children: t("search.settings.tabs.indexManagement", "Index Management") }),
293
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "activity", children: t("search.settings.tabs.activity", "Activity") })
294
+ ] }),
295
+ /* @__PURE__ */ jsx(TabsContent, { value: "configuration", children: embeddingLoading || vectorStoreConfigLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
296
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
297
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
298
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
299
+ /* @__PURE__ */ jsxs("div", { children: [
300
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-2", children: t("search.settings.vector.store", "Vector Store") }),
301
+ /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-3", children: vectorStoreConfig?.drivers.map((driver) => {
302
+ const isCurrent = driver.id === vectorStoreConfig.currentDriver;
303
+ const isReady = driver.configured && driver.implemented;
304
+ return /* @__PURE__ */ jsxs(
305
+ "div",
306
+ {
307
+ className: `flex items-start gap-3 p-3 rounded-md border ${isCurrent && isReady ? "border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900/20" : !driver.implemented ? "border-border bg-muted/20 opacity-60" : "border-border bg-muted/30"}`,
308
+ children: [
309
+ /* @__PURE__ */ jsx("div", { className: `flex h-8 w-8 items-center justify-center rounded-full flex-shrink-0 ${isCurrent && isReady ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-muted text-muted-foreground"}`, children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" }) }) }),
310
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
311
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
312
+ /* @__PURE__ */ jsx("p", { className: `text-sm font-medium ${isCurrent && isReady ? "text-emerald-700 dark:text-emerald-300" : ""}`, children: driver.name }),
313
+ isCurrent && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300", children: t("search.settings.vector.active", "Active") }),
314
+ !driver.implemented && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground", children: t("search.settings.vector.comingSoon", "Coming soon") })
315
+ ] }),
316
+ /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-0.5", children: driver.envVars.map((envVar) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
317
+ /* @__PURE__ */ jsx("div", { className: `h-1.5 w-1.5 rounded-full ${envVar.set ? "bg-emerald-500" : "bg-muted-foreground/40"}` }),
318
+ /* @__PURE__ */ jsx("code", { className: "text-[10px] text-muted-foreground font-mono", children: envVar.name })
319
+ ] }, envVar.name)) })
320
+ ] })
321
+ ]
322
+ },
323
+ driver.id
324
+ );
325
+ }) })
326
+ ] }),
327
+ /* @__PURE__ */ jsxs("div", { children: [
328
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-2", children: t("search.settings.vector.providers", "Embedding Provider") }),
329
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-3", children: t("search.settings.vector.providersHint", "Select a provider to generate embeddings. Only providers with configured API keys can be selected.") }),
330
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3 items-start", children: providerOptions.map((providerId) => {
331
+ const info = EMBEDDING_PROVIDERS[providerId];
332
+ const isConfigured = embeddingSettings?.configuredProviders?.includes(providerId);
333
+ const isSelected = displayProvider === providerId;
334
+ const isCurrentlySaved = savedProvider === providerId;
335
+ return /* @__PURE__ */ jsxs(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ onClick: () => isConfigured && handleProviderChange(providerId),
340
+ disabled: !isConfigured || embeddingLoading || embeddingSaving,
341
+ className: `text-left p-3 rounded-lg border-2 transition-all ${isSelected ? "border-primary bg-primary/5 ring-1 ring-primary/20" : isConfigured ? "border-border hover:border-primary/50 hover:bg-muted/50 cursor-pointer" : "border-border bg-muted/20 opacity-50 cursor-not-allowed"}`,
342
+ children: [
343
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
344
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
345
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
346
+ /* @__PURE__ */ jsx("p", { className: `text-sm font-medium ${isSelected ? "text-primary" : isConfigured ? "" : "text-muted-foreground"}`, children: info.name }),
347
+ isCurrentlySaved && isConfigured && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300", children: t("search.settings.vector.active", "Active") })
348
+ ] }),
349
+ isConfigured ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
350
+ info.models.length,
351
+ " ",
352
+ t("search.settings.vector.modelsAvailable", "models available")
353
+ ] }) : /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
354
+ t("search.settings.vector.setEnvVar", "Set"),
355
+ " ",
356
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[10px] bg-muted px-1 rounded", children: info.envKeyRequired })
357
+ ] })
358
+ ] }),
359
+ /* @__PURE__ */ jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 ${isSelected ? "bg-primary text-primary-foreground" : isConfigured ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-muted text-muted-foreground"}`, children: isSelected ? /* @__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" }) }) : isConfigured ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, 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: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" }) }) })
360
+ ] }),
361
+ isSelected && isConfigured && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border space-y-2", onClick: (e) => e.stopPropagation(), children: [
362
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
363
+ /* @__PURE__ */ jsx(Label, { htmlFor: `model-${providerId}`, className: "text-xs font-medium", children: t("search.settings.model.label", "Model") }),
364
+ /* @__PURE__ */ jsxs(
365
+ "select",
366
+ {
367
+ id: `model-${providerId}`,
368
+ className: "w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60",
369
+ value: displayModel,
370
+ onChange: (e) => handleModelChange(e.target.value),
371
+ disabled: embeddingLoading || embeddingSaving,
372
+ children: [
373
+ savedCustomModel && displayProvider === savedProvider && /* @__PURE__ */ jsxs("option", { value: savedCustomModel.id, children: [
374
+ savedCustomModel.name,
375
+ " (",
376
+ savedCustomModel.dimension,
377
+ "d)"
378
+ ] }, savedCustomModel.id),
379
+ displayProviderInfo.models.map((model) => /* @__PURE__ */ jsxs("option", { value: model.id, children: [
380
+ model.name,
381
+ " (",
382
+ model.dimension,
383
+ "d)"
384
+ ] }, model.id)),
385
+ /* @__PURE__ */ jsx("option", { value: "custom", children: t("search.settings.model.custom", "Custom...") })
386
+ ]
387
+ }
388
+ )
389
+ ] }),
390
+ isCustomModel && /* @__PURE__ */ jsxs("div", { className: "space-y-2 p-2 rounded border border-input bg-muted/30", children: [
391
+ /* @__PURE__ */ jsx(
392
+ "input",
393
+ {
394
+ type: "text",
395
+ className: "w-full rounded border border-input bg-background px-2 py-1 text-sm",
396
+ value: customModelName,
397
+ onChange: (e) => setCustomModelName(e.target.value),
398
+ placeholder: t("search.settings.model.namePlaceholder", "Model name"),
399
+ disabled: embeddingLoading || embeddingSaving
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsx(
403
+ "input",
404
+ {
405
+ type: "number",
406
+ className: "w-full rounded border border-input bg-background px-2 py-1 text-sm",
407
+ value: customDimension,
408
+ onChange: (e) => setCustomDimension(Number(e.target.value) || 768),
409
+ placeholder: "768",
410
+ min: 1,
411
+ disabled: embeddingLoading || embeddingSaving
412
+ }
413
+ )
414
+ ] }),
415
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
416
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
417
+ t("search.settings.dimension.label", "Dimensions"),
418
+ ": ",
419
+ displayDimension
420
+ ] }),
421
+ embeddingSettings?.indexedDimension && embeddingSettings.indexedDimension !== displayDimension && /* @__PURE__ */ jsxs("span", { className: "text-amber-600 dark:text-amber-400", children: [
422
+ t("search.settings.dimension.mismatch", "mismatch"),
423
+ ": ",
424
+ embeddingSettings.indexedDimension
425
+ ] })
426
+ ] }),
427
+ hasUnsavedEmbeddingChanges && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
428
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "default", size: "sm", className: "flex-1", onClick: handleApplyEmbeddingChanges, disabled: embeddingLoading || embeddingSaving, children: [
429
+ embeddingSaving ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-1" }) : null,
430
+ t("search.settings.actions.apply", "Apply")
431
+ ] }),
432
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleCancelEmbeddingSelection, disabled: embeddingLoading || embeddingSaving, children: t("search.settings.actions.cancel", "Cancel") })
433
+ ] })
434
+ ] })
435
+ ]
436
+ },
437
+ providerId
438
+ );
439
+ }) })
440
+ ] }),
441
+ /* @__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: [
442
+ /* @__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" }) }),
443
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-blue-800 dark:text-blue-200", children: [
444
+ /* @__PURE__ */ jsx("p", { className: "font-medium mb-1", children: t("search.settings.vector.howTo", "How to set up") }),
445
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: t("search.settings.vector.howToDescription", "Add the API key for your preferred provider to your .env file. Only providers with configured API keys can be selected.") })
446
+ ] })
447
+ ] }) })
448
+ ] }) }),
449
+ /* @__PURE__ */ jsx(TabsContent, { value: "index", children: embeddingLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
450
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
451
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
452
+ ] }) : !isEmbeddingConfigured ? /* @__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: [
453
+ /* @__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" }) }),
454
+ /* @__PURE__ */ jsxs("div", { children: [
455
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-amber-800 dark:text-amber-200", children: t("search.settings.vectorNotConfigured", "No embedding provider configured") }),
456
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-700 dark:text-amber-300 mt-1", children: t("search.settings.vectorNotConfiguredHint", "Configure an embedding provider in the Configuration tab to enable indexing.") })
457
+ ] })
458
+ ] }) }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
459
+ embeddingSettings?.documentCount !== null && embeddingSettings?.documentCount !== void 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border p-4 max-w-xs", children: [
460
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.vectorDocumentsLabel", "Embeddings") }),
461
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: embeddingSettings.documentCount.toLocaleString() })
462
+ ] }),
463
+ /* @__PURE__ */ jsx("div", { className: "flex items-start gap-4 p-4 rounded-md border border-border", children: /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
464
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
465
+ /* @__PURE__ */ jsx(
466
+ "input",
467
+ {
468
+ id: "search-auto-indexing",
469
+ type: "checkbox",
470
+ className: "h-4 w-4 rounded border-muted-foreground/40",
471
+ checked: autoIndexingChecked,
472
+ onChange: (event) => updateAutoIndexing(event.target.checked),
473
+ disabled: autoIndexingDisabled
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsx(Label, { htmlFor: "search-auto-indexing", className: "text-sm font-medium", children: t("search.settings.autoIndexing.label", "Enable auto-indexing") }),
477
+ embeddingSaving ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "text-muted-foreground" }) : null
478
+ ] }),
479
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 ml-6", children: t("search.settings.autoIndexing.description", "Automatically index new and updated records for vector search.") }),
480
+ embeddingSettings?.autoIndexingLocked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1 ml-6", children: t("search.settings.autoIndexing.locked", "Disabled via environment variable.") })
481
+ ] }) }),
482
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
483
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("search.settings.vectorReindex.title", "Reindex Data") }),
484
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("search.settings.vectorReindex.description", "Rebuild vector embeddings for all indexed entities. This will purge existing data and regenerate all embeddings.") }),
485
+ vectorReindexLock && /* @__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-3", children: [
486
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400" }),
487
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
488
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-blue-800 dark:text-blue-200", children: t("search.settings.reindexInProgress", "Reindex operation in progress") }),
489
+ /* @__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", {
490
+ action: vectorReindexLock.action,
491
+ minutes: vectorReindexLock.elapsedMinutes
492
+ }) })
493
+ ] })
494
+ ] }) }),
495
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2 rounded bg-amber-50 dark:bg-amber-900/20", children: [
496
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-amber-600 dark:text-amber-400 flex-shrink-0", 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" }) }),
497
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-800 dark:text-amber-200", children: t("search.settings.vectorReindex.warning", "This may take a while for large datasets and will consume API credits.") })
498
+ ] }),
499
+ /* @__PURE__ */ jsx(
500
+ Button,
501
+ {
502
+ type: "button",
503
+ variant: "default",
504
+ size: "sm",
505
+ onClick: handleVectorReindexClick,
506
+ disabled: embeddingLoading || embeddingSaving || vectorReindexing || vectorReindexLock !== null,
507
+ children: vectorReindexing || vectorReindexLock !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
508
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
509
+ t("search.settings.vectorReindex.running", "Reindexing...")
510
+ ] }) : t("search.settings.vectorReindex.button", "Full Reindex")
511
+ }
512
+ )
513
+ ] })
514
+ ] }) }),
515
+ /* @__PURE__ */ jsxs(TabsContent, { value: "activity", children: [
516
+ activityLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
517
+ /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
518
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading...") })
519
+ ] }) : 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(
520
+ "div",
521
+ {
522
+ 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"}`,
523
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
524
+ 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" }) }),
525
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
526
+ /* @__PURE__ */ jsx("p", { className: `text-xs ${log.level === "error" ? "text-red-800 dark:text-red-200" : "text-foreground"}`, children: log.message }),
527
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
528
+ (() => {
529
+ const d = new Date(log.occurredAt);
530
+ const pad = (n) => n.toString().padStart(2, "0");
531
+ return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
532
+ })(),
533
+ log.entityType && ` \xB7 ${log.entityType}`
534
+ ] })
535
+ ] })
536
+ ] })
537
+ },
538
+ log.id
539
+ )) }),
540
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
541
+ Button,
542
+ {
543
+ type: "button",
544
+ variant: "outline",
545
+ size: "sm",
546
+ onClick: fetchActivityLogs,
547
+ disabled: activityLoading,
548
+ children: activityLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
549
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
550
+ t("search.settings.loadingLabel", "Loading...")
551
+ ] }) : t("search.settings.refreshLabel", "Refresh")
552
+ }
553
+ ) })
554
+ ] })
555
+ ] }),
556
+ showVectorReindexDialog && /* @__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: [
557
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: t("search.settings.reindex.confirmTitle", "Confirm Reindex") }),
558
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: t("search.settings.reindex.confirmDescription", "This will rebuild all vector embeddings. Existing data will be purged first.") }),
559
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
560
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleVectorReindexCancel, children: t("search.settings.actions.cancel", "Cancel") }),
561
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "default", onClick: handleVectorReindexConfirm, children: t("search.settings.reindex.confirmButton", "Start Reindex") })
562
+ ] })
563
+ ] }) }),
564
+ showEmbeddingConfirmDialog && pendingEmbeddingConfig && /* @__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-lg rounded-lg border border-border bg-card p-6 shadow-lg", children: [
565
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: t("search.settings.change.title", "Confirm Provider Change") }),
566
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: t("search.settings.change.description", "Changing the embedding provider will require reindexing all data.") }),
567
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 rounded-md bg-muted/50 text-sm", children: [
568
+ /* @__PURE__ */ jsxs("p", { className: "font-medium", children: [
569
+ embeddingSettings?.embeddingConfig ? `${EMBEDDING_PROVIDERS[embeddingSettings.embeddingConfig.providerId].name} (${embeddingSettings.embeddingConfig.model})` : "Default",
570
+ " \u2192 ",
571
+ EMBEDDING_PROVIDERS[pendingEmbeddingConfig.providerId].name,
572
+ " (",
573
+ pendingEmbeddingConfig.model,
574
+ ")"
575
+ ] }),
576
+ /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
577
+ embeddingSettings?.indexedDimension ?? "N/A",
578
+ " \u2192 ",
579
+ pendingEmbeddingConfig.dimension,
580
+ " dimensions"
581
+ ] })
582
+ ] }),
583
+ /* @__PURE__ */ jsxs("ul", { className: "mb-4 space-y-1 text-sm", children: [
584
+ /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
585
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "\u2022" }),
586
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.change.bullet1", "Existing vector data will be cleared") })
587
+ ] }),
588
+ /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
589
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "\u2022" }),
590
+ /* @__PURE__ */ jsx("span", { children: t("search.settings.change.bullet2", "Vector search will be unavailable during reindex") })
591
+ ] })
592
+ ] }),
593
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
594
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleEmbeddingCancelChange, disabled: embeddingSaving, children: t("search.settings.actions.cancel", "Cancel") }),
595
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "destructive", onClick: handleEmbeddingConfirmChange, disabled: embeddingSaving, children: [
596
+ embeddingSaving ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }) : null,
597
+ t("search.settings.actions.confirm", "Confirm")
598
+ ] })
599
+ ] })
600
+ ] }) })
601
+ ] });
602
+ }
603
+ var VectorSearchSection_default = VectorSearchSection;
604
+ export {
605
+ VectorSearchSection,
606
+ VectorSearchSection_default as default
607
+ };
608
+ //# sourceMappingURL=VectorSearchSection.js.map