ocean-brain 0.8.2 → 0.10.0

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 (232) hide show
  1. package/README.md +2 -0
  2. package/dist/mcp.js +47 -75
  3. package/package.json +3 -2
  4. package/server/client/dist/assets/{ArrowRight.es-g2YZTklZ.js → ArrowRight.es-Bm-ZXpEC.js} +1 -1
  5. package/server/client/dist/assets/Calendar-BsNs3NzO.js +25 -0
  6. package/server/client/dist/assets/{Callout-B_IPSF_5.js → Callout-CviyPHKO.js} +1 -1
  7. package/server/client/dist/assets/{Copy.es-CNxe4ujr.js → Copy.es-BPreRnP9.js} +1 -1
  8. package/server/client/dist/assets/{Graph-D9GeO71k.js → Graph-vXQQ0a-Q.js} +1 -1
  9. package/server/client/dist/assets/Graph-yPE3ljW0.js +46 -0
  10. package/server/client/dist/assets/{Image.es-DnON8cv2.js → Image.es-DnR3qc_c.js} +1 -1
  11. package/server/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  12. package/server/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  13. package/server/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  14. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  15. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  16. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  17. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  18. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  19. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  20. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  21. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  22. package/server/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  23. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  24. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  25. package/server/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  26. package/server/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  27. package/server/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  28. package/server/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  29. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  30. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  31. package/server/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  32. package/server/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  33. package/server/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  34. package/server/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  35. package/server/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  36. package/server/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  37. package/server/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  38. package/server/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  39. package/server/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  40. package/server/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  41. package/server/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  42. package/server/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  43. package/server/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  44. package/server/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  45. package/server/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  46. package/server/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  47. package/server/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  48. package/server/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  49. package/server/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  50. package/server/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  51. package/server/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  52. package/server/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  53. package/server/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  54. package/server/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  55. package/server/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  56. package/server/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  57. package/server/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  58. package/server/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  59. package/server/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  60. package/server/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  61. package/server/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  62. package/server/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  63. package/server/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  64. package/server/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  65. package/server/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  66. package/server/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  67. package/server/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  68. package/server/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  69. package/server/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  70. package/server/client/dist/assets/Note-CmreVzvQ.js +25 -0
  71. package/server/client/dist/assets/{NotePropertiesPanel-2DgXEM4B.js → NotePropertiesPanel-DXYgE8h2.js} +2 -2
  72. package/server/client/dist/assets/PencilLine.es-B9V6u64t.js +1 -0
  73. package/server/client/dist/assets/{Plus.es-CQ8wn2is.js → Plus.es-CRRp2u5J.js} +1 -1
  74. package/server/client/dist/assets/Reminders-BOtfNeYD.js +1 -0
  75. package/server/client/dist/assets/Search-C-FB3pKf.js +1 -0
  76. package/server/client/dist/assets/SurfaceCard-GMt5xM7v.js +1 -0
  77. package/server/client/dist/assets/Tag-lBxDRE77.js +1 -0
  78. package/server/client/dist/assets/TagNotes-Bgorp_rC.js +1 -0
  79. package/server/client/dist/assets/{Trash.es-0aPpA-3S.js → Trash.es-svHOb5LN.js} +1 -1
  80. package/server/client/dist/assets/ViewNotes-C7d10VFf.js +1 -0
  81. package/server/client/dist/assets/ViewSectionDialog-Bhw-ILop.js +102 -0
  82. package/server/client/dist/assets/Views-C7V3saUo.js +1 -0
  83. package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
  84. package/server/client/dist/assets/app-DQBV36r_.js +50 -0
  85. package/server/client/dist/assets/app-ZgwR5t_I.css +1 -0
  86. package/server/client/dist/assets/arc-BV-n6RUX.js +1 -0
  87. package/server/client/dist/assets/architectureDiagram-ZJ3FMSHR-CekzMXdu.js +36 -0
  88. package/server/client/dist/assets/blockDiagram-677ZJIJ3-B49yHoxq.js +132 -0
  89. package/server/client/dist/assets/c4Diagram-LMCZKHZV-0AfB9NBC.js +10 -0
  90. package/server/client/dist/assets/channel-CigOulR-.js +1 -0
  91. package/server/client/dist/assets/chunk-2Q5K7J3B-DaPQAU1q.js +1 -0
  92. package/server/client/dist/assets/chunk-32BRIVSS-CfL3kZAg.js +1 -0
  93. package/server/client/dist/assets/chunk-5VM5RSS4-DeOwWPE_.js +15 -0
  94. package/server/client/dist/assets/chunk-EX3LRPZG-C3W1oc9y.js +231 -0
  95. package/server/client/dist/assets/chunk-JWPE2WC7-_Oqh79OY.js +1 -0
  96. package/server/client/dist/assets/chunk-MOJQB5TN-DDKNNzI6.js +88 -0
  97. package/server/client/dist/assets/chunk-RYQCIY6F-DCHah7CC.js +1 -0
  98. package/server/client/dist/assets/chunk-V7JOEXUC-uUOIzgxj.js +206 -0
  99. package/server/client/dist/assets/chunk-VR4S4FIN-DDVw2WPs.js +1 -0
  100. package/server/client/dist/assets/chunk-XXDRQBXY-Coo4UFm2.js +1 -0
  101. package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +1 -0
  102. package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +1 -0
  103. package/server/client/dist/assets/colors-Cc3OSVma.js +1 -0
  104. package/server/client/dist/assets/cose-bilkent-JH36ORCC-C3pHmHpD.js +1 -0
  105. package/server/client/dist/assets/cynefin-VYW2F7L2-DU_G4Atc.js +166 -0
  106. package/server/client/dist/assets/cynefinDiagram-TSTJHNR4-DZ6iiJFo.js +62 -0
  107. package/server/client/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  108. package/server/client/dist/assets/dagre-VKFMJZFB-DNELbhwC.js +4 -0
  109. package/server/client/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  110. package/server/client/dist/assets/diagram-FQU43EPY-CMEvZJ61.js +3 -0
  111. package/server/client/dist/assets/diagram-G47NLZAW-BYDtf6j9.js +24 -0
  112. package/server/client/dist/assets/diagram-NH7WQ7WH-CmXfcCQA.js +24 -0
  113. package/server/client/dist/assets/diagram-OA4YK3LP-BflTKJxU.js +30 -0
  114. package/server/client/dist/assets/diagram-WEI45ONY-KW3fKqna.js +41 -0
  115. package/server/client/dist/assets/ebnfDiagram-CCIWWBDH-CbyhY5Nj.js +1 -0
  116. package/server/client/dist/assets/erDiagram-Q63AITRT-CC2eZV8g.js +85 -0
  117. package/server/client/dist/assets/{floating-ui.react-dom-wWLZ1CdM.js → floating-ui.react-dom-BWTnA00G.js} +1 -1
  118. package/server/client/dist/assets/flowDiagram-23GEKE2U-unplo7Qm.js +156 -0
  119. package/server/client/dist/assets/ganttDiagram-NO4QXBWP-BcBK5HcZ.js +292 -0
  120. package/server/client/dist/assets/gitGraphDiagram-IHSO6WYX-DgVbbb5k.js +106 -0
  121. package/server/client/dist/assets/graph-C5DFNLrC.js +1 -0
  122. package/server/client/dist/assets/image-delete-confirmation-D7xrODQV.js +1 -0
  123. package/server/client/dist/assets/image.api-QmHYjp3z.js +17 -0
  124. package/server/client/dist/assets/index-CaF9d-zG.js +1 -0
  125. package/server/client/dist/assets/infoDiagram-FWYZ7A6U-N_D9RdfG.js +2 -0
  126. package/server/client/dist/assets/init-Gi6I4Gst.js +1 -0
  127. package/server/client/dist/assets/isObject-U9U2HXiq.js +1 -0
  128. package/server/client/dist/assets/ishikawaDiagram-FXEZZL3T-BBKrzmB2.js +70 -0
  129. package/server/client/dist/assets/journeyDiagram-5HDEW3XC-NdCxLmQB.js +139 -0
  130. package/server/client/dist/assets/kanban-definition-HUTT4EX6-Ck7t4-tt.js +89 -0
  131. package/server/client/dist/assets/katex-DywYRkvg.css +1 -0
  132. package/server/client/dist/assets/katex-HP8lGamR.js +257 -0
  133. package/server/client/dist/assets/katex-x288kqHd.js +257 -0
  134. package/server/client/dist/assets/layout-Cz0MDlGi.js +1 -0
  135. package/server/client/dist/assets/linear-D6yRgDLq.js +1 -0
  136. package/server/client/dist/assets/manage-image-DCDF8AM9.js +1 -0
  137. package/server/client/dist/assets/manage-image-detail-e0ViRAj-.js +1 -0
  138. package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
  139. package/server/client/dist/assets/mcp-admin-adapter-D39G3udJ.js +1 -0
  140. package/server/client/dist/assets/{mcp-CT_9IV2c.js → mcp-uFZkdhCi.js} +4 -4
  141. package/server/client/dist/assets/mermaid.core-B14ZAmWA.js +308 -0
  142. package/server/client/dist/assets/min-tv6RD-6E.js +1 -0
  143. package/server/client/dist/assets/mindmap-definition-LN4V7U3C-BTUqSdcP.js +96 -0
  144. package/server/client/dist/assets/{note-core-Buwzyxi7.js → note-core-DUTqUh5N.js} +2 -2
  145. package/server/client/dist/assets/{note-runtime-CPDVOFEo.js → note-runtime-zN5ddTba.js} +1 -1
  146. package/server/client/dist/assets/{note-ui-CHI-tzj-.js → note-ui-CxK8ymUc.js} +1 -1
  147. package/server/client/dist/assets/{note.api-D_00ZzJC.js → note.api-BQovzEzd.js} +6 -6
  148. package/server/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  149. package/server/client/dist/assets/pegDiagram-2B236MQR-Cjcu3mVz.js +1 -0
  150. package/server/client/dist/assets/pieDiagram-ENE6RG2P-BxI_c22Z.js +39 -0
  151. package/server/client/dist/assets/{placeholder-DbdGiYGL.js → placeholder-D3b8lZGE.js} +4 -4
  152. package/server/client/dist/assets/properties-M-yWq1D7.js +1 -0
  153. package/server/client/dist/assets/quadrantDiagram-ABIIQ3AL-DGm0a-yR.js +7 -0
  154. package/server/client/dist/assets/railroadDiagram-RFXS5EU6-DmCDu89O.js +1 -0
  155. package/server/client/dist/assets/requirementDiagram-TGXJPOKE-CBpUt7Ig.js +84 -0
  156. package/server/client/dist/assets/route-preload.js +2 -2
  157. package/server/client/dist/assets/sankeyDiagram-HTMAVEWB-BjGnDZlp.js +40 -0
  158. package/server/client/dist/assets/search-DFnzZKLd.js +1 -0
  159. package/server/client/dist/assets/sequenceDiagram-DBY2YBRQ-B7rpCfS6.js +162 -0
  160. package/server/client/dist/assets/sizeCapture-X5ZJPWSS-CUJhD0uW.js +1 -0
  161. package/server/client/dist/assets/stateDiagram-2N3HPSRC-Y9-3XqRX.js +1 -0
  162. package/server/client/dist/assets/stateDiagram-v2-6OUMAXLB-DbHXhT02.js +1 -0
  163. package/server/client/dist/assets/swimlanes-5IMT3BWC-Cs1NO9ng.js +2 -0
  164. package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +8 -0
  165. package/server/client/dist/assets/{tag.api-CX0SE06J.js → tag.api-DUfjr8bg.js} +3 -3
  166. package/server/client/dist/assets/time-CfAYZipR.js +9 -0
  167. package/server/client/dist/assets/timeline-definition-FHXFAJF6-8fqD4WNL.js +120 -0
  168. package/server/client/dist/assets/toNumber-DE3J3kMD.js +1 -0
  169. package/server/client/dist/assets/transform-BUB79_za.js +1 -0
  170. package/server/client/dist/assets/trash-CClKfP1x.js +1 -0
  171. package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
  172. package/server/client/dist/assets/{useReminderMutate-CLK1tHvT.js → useReminderMutate--omDLmBV.js} +4 -4
  173. package/server/client/dist/assets/vennDiagram-L72KCM5P-BPqKkd5a.js +34 -0
  174. package/server/client/dist/assets/wardleyDiagram-EHGQE667-Cq11kU2M.js +78 -0
  175. package/server/client/dist/assets/xychartDiagram-FW5EYKEG-DJ6UzFj-.js +7 -0
  176. package/server/client/dist/index.html +7 -7
  177. package/server/dist/features/image/services/delete.js +25 -1
  178. package/server/dist/features/note/graphql/note.mutation.resolver.js +38 -12
  179. package/server/dist/features/note/graphql/note.type-defs.js +1 -1
  180. package/server/dist/features/note/http/mcp.js +1 -60
  181. package/server/dist/features/note/services/authoring.js +2 -70
  182. package/server/dist/features/note/services/markdown-patch.js +80 -8
  183. package/server/dist/features/note/services/properties.js +21 -1
  184. package/server/dist/features/note/services/property-restore.js +26 -0
  185. package/server/dist/features/note/services/snapshot.js +79 -59
  186. package/server/dist/features/note/services/trash.js +16 -31
  187. package/server/dist/features/search/embedding-api-key-store.js +57 -0
  188. package/server/dist/features/search/embedding-client.js +179 -0
  189. package/server/dist/features/search/graphql/index.js +6 -0
  190. package/server/dist/features/search/graphql/resolver.js +20 -0
  191. package/server/dist/features/search/graphql/type-defs.js +30 -0
  192. package/server/dist/features/search/http/handlers.js +108 -0
  193. package/server/dist/features/search/hybrid-ranking.js +55 -0
  194. package/server/dist/features/search/hybrid-search.js +174 -0
  195. package/server/dist/features/search/note-change.js +16 -0
  196. package/server/dist/features/search/note-chunking.js +75 -0
  197. package/server/dist/features/search/search-config.js +150 -0
  198. package/server/dist/features/search/search-manager.js +431 -0
  199. package/server/dist/features/search/semantic-indexer.js +138 -0
  200. package/server/dist/features/search/sqlite-vector-index.js +636 -0
  201. package/server/dist/features/search/url-normalization.js +10 -0
  202. package/server/dist/features/tag/services/organization.js +3 -0
  203. package/server/dist/features/view/services/workspace.js +1 -1
  204. package/server/dist/main.js +2 -0
  205. package/server/dist/modules/blocknote.js +4 -5
  206. package/server/dist/paths.js +7 -2
  207. package/server/dist/routes/api.js +37 -0
  208. package/server/dist/routes/mcp.js +0 -5
  209. package/server/dist/schema/index.js +3 -0
  210. package/server/client/dist/assets/Calendar-BP6HTuSn.js +0 -25
  211. package/server/client/dist/assets/Graph-Xha3fwhp.js +0 -46
  212. package/server/client/dist/assets/Image-BLEjLUqd.js +0 -1
  213. package/server/client/dist/assets/Note-DjrCYtat.js +0 -24
  214. package/server/client/dist/assets/Reminders-B4l3KdlK.js +0 -1
  215. package/server/client/dist/assets/Search-BN5sf7bE.js +0 -1
  216. package/server/client/dist/assets/SurfaceCard-BZm73nyS.js +0 -1
  217. package/server/client/dist/assets/Tag-oSnmViVp.js +0 -1
  218. package/server/client/dist/assets/TagNotes-BDTCcJcL.js +0 -1
  219. package/server/client/dist/assets/ViewNotes-CiZtGJNE.js +0 -1
  220. package/server/client/dist/assets/ViewSectionDialog-Ctl_694Y.js +0 -102
  221. package/server/client/dist/assets/Views-D-zlatYD.js +0 -1
  222. package/server/client/dist/assets/app-DmqIOMWF.css +0 -1
  223. package/server/client/dist/assets/app-cTRcKMGc.js +0 -26
  224. package/server/client/dist/assets/image.api-CVuuvTBk.js +0 -17
  225. package/server/client/dist/assets/index-DKJChPr4.js +0 -1
  226. package/server/client/dist/assets/manage-image-BZjaxp-f.js +0 -1
  227. package/server/client/dist/assets/manage-image-detail-DPv0FQ2m.js +0 -1
  228. package/server/client/dist/assets/mcp-admin-adapter-xAuyZc_M.js +0 -1
  229. package/server/client/dist/assets/properties-D447wsWk.js +0 -1
  230. package/server/client/dist/assets/time-B3pt8vt4.js +0 -9
  231. package/server/client/dist/assets/trash-8sH7EZgc.js +0 -1
  232. package/server/client/dist/assets/url-BSnWpmJx.js +0 -1
@@ -0,0 +1,20 @@
1
+ import { searchNotesHybrid } from "../hybrid-search.js";
2
+ const createSearchNotesResolver = (search = searchNotesHybrid) => {
3
+ return async (_, { query, pagination, mode = "HYBRID" }) => {
4
+ return search({
5
+ query,
6
+ limit: Math.min(50, Math.max(0, pagination.limit)),
7
+ offset: Math.max(0, pagination.offset),
8
+ mode: mode.toLowerCase()
9
+ });
10
+ };
11
+ };
12
+ const searchResolvers = {
13
+ Query: {
14
+ searchNotes: createSearchNotesResolver()
15
+ }
16
+ };
17
+ export {
18
+ createSearchNotesResolver,
19
+ searchResolvers
20
+ };
@@ -0,0 +1,30 @@
1
+ import { gql } from "../../../modules/graphql.js";
2
+ const searchTypeDefs = gql`
3
+ enum SearchMode {
4
+ HYBRID
5
+ LEXICAL
6
+ SEMANTIC
7
+ }
8
+
9
+ type SearchNoteMatch {
10
+ noteId: ID!
11
+ lexical: Boolean!
12
+ semantic: Boolean!
13
+ }
14
+
15
+ type SearchNotesResult {
16
+ totalCount: Int!
17
+ notes: [Note!]!
18
+ matches: [SearchNoteMatch!]!
19
+ semanticAvailable: Boolean!
20
+ semanticUsed: Boolean!
21
+ semanticError: String
22
+ }
23
+
24
+ extend type Query {
25
+ searchNotes(query: String!, pagination: PaginationInput!, mode: SearchMode = HYBRID): SearchNotesResult!
26
+ }
27
+ `;
28
+ export {
29
+ searchTypeDefs
30
+ };
@@ -0,0 +1,108 @@
1
+ import { createAppError } from "../../../modules/error-handler.js";
2
+ import { normalizeEmbeddingApiKey } from "../embedding-api-key-store.js";
3
+ import { normalizeEmbeddingModelsUrl } from "../embedding-client.js";
4
+ import { normalizeSemanticSearchConfig } from "../search-config.js";
5
+ import {
6
+ getDefaultSemanticSearchManager
7
+ } from "../search-manager.js";
8
+ import { stripTrailingSlashes } from "../url-normalization.js";
9
+ const parseApiKeyInput = (value) => {
10
+ if (!value || typeof value !== "object" || !Object.hasOwn(value, "apiKey")) {
11
+ return { provided: false };
12
+ }
13
+ const rawApiKey = value.apiKey;
14
+ if (rawApiKey !== null && typeof rawApiKey !== "string") {
15
+ throw createAppError(400, "INVALID_EMBEDDING_API_KEY", "Embedding API key must be a string or null.");
16
+ }
17
+ try {
18
+ return {
19
+ provided: true,
20
+ apiKey: rawApiKey === null ? void 0 : normalizeEmbeddingApiKey(rawApiKey)
21
+ };
22
+ } catch (error) {
23
+ throw createAppError(
24
+ 400,
25
+ "INVALID_EMBEDDING_API_KEY",
26
+ error instanceof Error ? error.message : "Embedding API key is invalid."
27
+ );
28
+ }
29
+ };
30
+ const parseConfig = (value) => {
31
+ if (!value || typeof value !== "object") {
32
+ throw createAppError(400, "INVALID_SEARCH_CONFIG", "Semantic search settings must be an object.");
33
+ }
34
+ const config = value;
35
+ if (typeof config.enabled !== "boolean" || typeof config.baseUrl !== "string" || typeof config.model !== "string" || typeof config.queryInstruction !== "string") {
36
+ throw createAppError(
37
+ 400,
38
+ "INVALID_SEARCH_CONFIG",
39
+ "enabled, baseUrl, model, and queryInstruction are required."
40
+ );
41
+ }
42
+ try {
43
+ return normalizeSemanticSearchConfig(config);
44
+ } catch (error) {
45
+ throw createAppError(
46
+ 400,
47
+ "INVALID_SEARCH_CONFIG",
48
+ error instanceof Error ? error.message : "Semantic search settings are invalid."
49
+ );
50
+ }
51
+ };
52
+ const parseBaseUrl = (value) => {
53
+ if (!value || typeof value !== "object" || typeof value.baseUrl !== "string") {
54
+ throw createAppError(400, "INVALID_EMBEDDING_API_URL", "Embedding API URL is required.");
55
+ }
56
+ const inputBaseUrl = value.baseUrl;
57
+ if (inputBaseUrl.length > 2048) {
58
+ throw createAppError(400, "INVALID_EMBEDDING_API_URL", "Embedding API URL is too long.");
59
+ }
60
+ const baseUrl = stripTrailingSlashes(inputBaseUrl.trim());
61
+ try {
62
+ normalizeEmbeddingModelsUrl(baseUrl);
63
+ } catch (error) {
64
+ throw createAppError(
65
+ 400,
66
+ "INVALID_EMBEDDING_API_URL",
67
+ error instanceof Error ? error.message : "Embedding API URL is invalid."
68
+ );
69
+ }
70
+ return baseUrl;
71
+ };
72
+ const createSearchAdminStatusHandler = (manager = getDefaultSemanticSearchManager()) => {
73
+ return async (_req, res) => {
74
+ res.status(200).json(await manager.getStatus()).end();
75
+ };
76
+ };
77
+ const createSearchAdminSaveConfigHandler = (manager = getDefaultSemanticSearchManager()) => {
78
+ return async (req, res) => {
79
+ const status = await manager.saveConfig(parseConfig(req.body), parseApiKeyInput(req.body));
80
+ res.status(200).json(status).end();
81
+ };
82
+ };
83
+ const createSearchAdminTestConnectionHandler = (manager = getDefaultSemanticSearchManager()) => {
84
+ return async (req, res) => {
85
+ const config = parseConfig({ ...req.body, enabled: true });
86
+ const result = await manager.testConnection(config, parseApiKeyInput(req.body));
87
+ res.status(200).json(result).end();
88
+ };
89
+ };
90
+ const createSearchAdminListModelsHandler = (listModels = (baseUrl, apiKeyInput) => getDefaultSemanticSearchManager().listModels(baseUrl, apiKeyInput)) => {
91
+ return async (req, res) => {
92
+ const models = await listModels(parseBaseUrl(req.body), parseApiKeyInput(req.body));
93
+ res.status(200).json({ models }).end();
94
+ };
95
+ };
96
+ const createSearchAdminReindexHandler = (manager = getDefaultSemanticSearchManager()) => {
97
+ return async (_req, res) => {
98
+ const result = await manager.startReindex();
99
+ res.status(result.started ? 202 : 200).json(result).end();
100
+ };
101
+ };
102
+ export {
103
+ createSearchAdminListModelsHandler,
104
+ createSearchAdminReindexHandler,
105
+ createSearchAdminSaveConfigHandler,
106
+ createSearchAdminStatusHandler,
107
+ createSearchAdminTestConnectionHandler
108
+ };
@@ -0,0 +1,55 @@
1
+ const DEFAULT_RANK_CONSTANT = 60;
2
+ const uniqueNoteIds = (noteIds) => {
3
+ return [...new Set(noteIds.filter((noteId) => Number.isInteger(noteId) && noteId > 0))];
4
+ };
5
+ const fuseHybridSearchRanks = ({
6
+ lexicalNoteIds,
7
+ semanticNoteIds,
8
+ lexicalWeight = 1,
9
+ semanticWeight = 1,
10
+ rankConstant = DEFAULT_RANK_CONSTANT
11
+ }) => {
12
+ const lexicalIds = uniqueNoteIds(lexicalNoteIds);
13
+ const semanticIds = uniqueNoteIds(semanticNoteIds);
14
+ const candidates = /* @__PURE__ */ new Map();
15
+ const addRankedIds = (noteIds, source, weight) => {
16
+ noteIds.forEach((noteId, index) => {
17
+ const rank = index + 1;
18
+ const candidate = candidates.get(noteId) ?? {
19
+ noteId,
20
+ lexicalRank: null,
21
+ semanticRank: null,
22
+ score: 0
23
+ };
24
+ candidate.score += weight / (rankConstant + rank);
25
+ if (source === "lexical") {
26
+ candidate.lexicalRank = rank;
27
+ } else {
28
+ candidate.semanticRank = rank;
29
+ }
30
+ candidates.set(noteId, candidate);
31
+ });
32
+ };
33
+ addRankedIds(lexicalIds, "lexical", lexicalWeight);
34
+ addRankedIds(semanticIds, "semantic", semanticWeight);
35
+ return [...candidates.values()].sort((left, right) => {
36
+ if (left.score !== right.score) {
37
+ return right.score - left.score;
38
+ }
39
+ const leftBestRank = Math.min(left.lexicalRank ?? Number.POSITIVE_INFINITY, left.semanticRank ?? Infinity);
40
+ const rightBestRank = Math.min(right.lexicalRank ?? Number.POSITIVE_INFINITY, right.semanticRank ?? Infinity);
41
+ if (leftBestRank !== rightBestRank) {
42
+ return leftBestRank - rightBestRank;
43
+ }
44
+ if (left.lexicalRank !== null && right.lexicalRank === null) {
45
+ return -1;
46
+ }
47
+ if (left.lexicalRank === null && right.lexicalRank !== null) {
48
+ return 1;
49
+ }
50
+ return left.noteId - right.noteId;
51
+ });
52
+ };
53
+ export {
54
+ fuseHybridSearchRanks
55
+ };
@@ -0,0 +1,174 @@
1
+ import {
2
+ buildNoteSearchText,
3
+ NOTE_SEARCH_TEXT_SCHEMA_VERSION,
4
+ parseNoteSearchQuery
5
+ } from "../../features/note/services/search.js";
6
+ import models from "../../models.js";
7
+ import { runDataMaintenanceInBackground } from "../../modules/data-maintenance.js";
8
+ import { fuseHybridSearchRanks } from "./hybrid-ranking.js";
9
+ import { getDefaultSemanticSearchManager } from "./search-manager.js";
10
+ const MAX_HYBRID_CANDIDATES = 80;
11
+ const unavailableSemanticAttempt = (error) => ({
12
+ available: false,
13
+ used: false,
14
+ matches: [],
15
+ error: error instanceof Error ? error.message : "Semantic search is unavailable."
16
+ });
17
+ const buildLexicalWhere = (query) => {
18
+ const parsedQuery = parseNoteSearchQuery(query);
19
+ if (!parsedQuery.hasFilters) {
20
+ return void 0;
21
+ }
22
+ return {
23
+ AND: [
24
+ { searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION },
25
+ ...parsedQuery.included.length > 0 ? [
26
+ {
27
+ OR: parsedQuery.included.map((term) => ({ searchableText: { contains: term } }))
28
+ }
29
+ ] : [],
30
+ ...parsedQuery.excluded.map((term) => ({ NOT: { searchableText: { contains: term } } }))
31
+ ]
32
+ };
33
+ };
34
+ const matchesHybridLexicalQuery = (searchableText, query) => {
35
+ const parsedQuery = parseNoteSearchQuery(query);
36
+ const normalizedSearchableText = searchableText.toLowerCase();
37
+ const includesAnyTerm = parsedQuery.included.length === 0 || parsedQuery.included.some((term) => normalizedSearchableText.includes(term));
38
+ return includesAnyTerm && parsedQuery.excluded.every((term) => !normalizedSearchableText.includes(term));
39
+ };
40
+ const scoreLexicalCandidate = (candidate, query) => {
41
+ const parsedQuery = parseNoteSearchQuery(query);
42
+ const phrase = parsedQuery.included.join(" ");
43
+ const title = candidate.title.toLowerCase();
44
+ const searchableText = candidate.searchableText.toLowerCase();
45
+ const matchingTermCount = parsedQuery.included.filter((term) => searchableText.includes(term)).length;
46
+ let score = 0;
47
+ if (phrase && title === phrase) {
48
+ score += 100;
49
+ } else if (phrase && title.includes(phrase)) {
50
+ score += 40;
51
+ }
52
+ if (phrase && searchableText.includes(phrase)) {
53
+ score += 15;
54
+ }
55
+ if (parsedQuery.included.length > 1 && matchingTermCount === parsedQuery.included.length) {
56
+ score += 20;
57
+ }
58
+ for (const term of parsedQuery.included) {
59
+ if (title.includes(term)) {
60
+ score += 8;
61
+ }
62
+ if (searchableText.includes(term)) {
63
+ score += 1;
64
+ }
65
+ }
66
+ return score;
67
+ };
68
+ const rankLexicalCandidates = (candidates, query) => {
69
+ return [...candidates].sort((left, right) => {
70
+ const scoreDifference = scoreLexicalCandidate(right, query) - scoreLexicalCandidate(left, query);
71
+ if (scoreDifference !== 0) {
72
+ return scoreDifference;
73
+ }
74
+ const updatedAtDifference = right.updatedAt.getTime() - left.updatedAt.getTime();
75
+ return updatedAtDifference !== 0 ? updatedAtDifference : left.id - right.id;
76
+ });
77
+ };
78
+ const listDefaultLexicalNoteIds = async (query, limit) => {
79
+ const where = buildLexicalWhere(query);
80
+ if (!where) {
81
+ return [];
82
+ }
83
+ const [freshCandidates, staleCandidates] = await Promise.all([
84
+ models.note.findMany({
85
+ where,
86
+ select: {
87
+ id: true,
88
+ title: true,
89
+ searchableText: true,
90
+ updatedAt: true
91
+ }
92
+ }),
93
+ models.note.findMany({
94
+ where: { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },
95
+ select: {
96
+ id: true,
97
+ title: true,
98
+ content: true,
99
+ searchableText: true,
100
+ updatedAt: true
101
+ }
102
+ })
103
+ ]);
104
+ if (staleCandidates.length > 0) {
105
+ void runDataMaintenanceInBackground();
106
+ }
107
+ const matchingStaleCandidates = staleCandidates.map((candidate) => ({
108
+ ...candidate,
109
+ searchableText: buildNoteSearchText(candidate)
110
+ })).filter((candidate) => matchesHybridLexicalQuery(candidate.searchableText, query));
111
+ return rankLexicalCandidates([...freshCandidates, ...matchingStaleCandidates], query).slice(0, limit).map((candidate) => candidate.id);
112
+ };
113
+ const defaultDependencies = {
114
+ listLexicalNoteIds: listDefaultLexicalNoteIds,
115
+ trySemanticSearch: (query, limit) => getDefaultSemanticSearchManager().trySearch(query, limit),
116
+ findNotesByIds: (ids) => models.note.findMany({ where: { id: { in: ids } } })
117
+ };
118
+ const createHybridNoteSearch = (dependencies = defaultDependencies) => {
119
+ return async ({
120
+ query,
121
+ limit,
122
+ offset,
123
+ mode = "hybrid"
124
+ }) => {
125
+ const normalizedQuery = query.trim();
126
+ if (!normalizedQuery || limit <= 0 || offset < 0) {
127
+ return {
128
+ totalCount: 0,
129
+ notes: [],
130
+ matches: [],
131
+ semanticAvailable: false,
132
+ semanticUsed: false,
133
+ semanticError: null
134
+ };
135
+ }
136
+ const candidateLimit = MAX_HYBRID_CANDIDATES;
137
+ const [lexicalNoteIds, semanticAttempt] = await Promise.all([
138
+ mode === "semantic" ? Promise.resolve([]) : dependencies.listLexicalNoteIds(normalizedQuery, candidateLimit),
139
+ mode === "lexical" ? Promise.resolve({
140
+ available: false,
141
+ used: false,
142
+ matches: [],
143
+ error: null
144
+ }) : dependencies.trySemanticSearch(normalizedQuery, candidateLimit).catch((error) => unavailableSemanticAttempt(error))
145
+ ]);
146
+ const rankedCandidates = fuseHybridSearchRanks({
147
+ lexicalNoteIds,
148
+ semanticNoteIds: semanticAttempt.matches.map((match) => match.noteId)
149
+ });
150
+ const pageCandidates = rankedCandidates.slice(offset, offset + limit);
151
+ const notes = await dependencies.findNotesByIds(pageCandidates.map((candidate) => candidate.noteId));
152
+ const notesById = new Map(notes.map((note) => [note.id, note]));
153
+ const existingCandidates = pageCandidates.filter((candidate) => notesById.has(candidate.noteId));
154
+ return {
155
+ totalCount: rankedCandidates.length,
156
+ notes: existingCandidates.map((candidate) => notesById.get(candidate.noteId)),
157
+ matches: existingCandidates.map((candidate) => ({
158
+ noteId: candidate.noteId,
159
+ lexical: candidate.lexicalRank !== null,
160
+ semantic: candidate.semanticRank !== null
161
+ })),
162
+ semanticAvailable: semanticAttempt.available,
163
+ semanticUsed: semanticAttempt.used,
164
+ semanticError: semanticAttempt.error
165
+ };
166
+ };
167
+ };
168
+ const searchNotesHybrid = createHybridNoteSearch();
169
+ export {
170
+ createHybridNoteSearch,
171
+ matchesHybridLexicalQuery,
172
+ rankLexicalCandidates,
173
+ searchNotesHybrid
174
+ };
@@ -0,0 +1,16 @@
1
+ import { EventEmitter } from "node:events";
2
+ const NOTE_CHANGE_CHANNEL = "semantic-search-note-change";
3
+ const noteChangeEmitter = new EventEmitter();
4
+ const notifySemanticSearchNoteChanged = (noteId) => {
5
+ if (Number.isInteger(noteId) && noteId > 0) {
6
+ noteChangeEmitter.emit(NOTE_CHANGE_CHANNEL, noteId);
7
+ }
8
+ };
9
+ const subscribeSemanticSearchNoteChanges = (listener) => {
10
+ noteChangeEmitter.on(NOTE_CHANGE_CHANNEL, listener);
11
+ return () => noteChangeEmitter.off(NOTE_CHANGE_CHANNEL, listener);
12
+ };
13
+ export {
14
+ notifySemanticSearchNoteChanged,
15
+ subscribeSemanticSearchNoteChanges
16
+ };
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { extractVisibleSearchTextFromContent } from "../../features/note/services/search.js";
3
+ const NOTE_EMBEDDING_TEXT_SCHEMA_VERSION = 1;
4
+ const DEFAULT_NOTE_CHUNK_MAX_LENGTH = 900;
5
+ const DEFAULT_NOTE_CHUNK_OVERLAP = 120;
6
+ const normalizeText = (value) => value.replace(/\s+/g, " ").trim();
7
+ const findWindowEnd = (text, start, maxLength) => {
8
+ const desiredEnd = Math.min(text.length, start + maxLength);
9
+ if (desiredEnd === text.length) {
10
+ return desiredEnd;
11
+ }
12
+ const minimumEnd = start + Math.floor(maxLength * 0.7);
13
+ const breakAt = text.lastIndexOf(" ", desiredEnd);
14
+ return breakAt >= minimumEnd ? breakAt : desiredEnd;
15
+ };
16
+ const findNextWindowStart = (text, start, end, overlap) => {
17
+ const desiredStart = Math.max(start + 1, end - overlap);
18
+ const nextSpace = text.indexOf(" ", desiredStart);
19
+ if (nextSpace === -1 || nextSpace >= end) {
20
+ return desiredStart;
21
+ }
22
+ return nextSpace + 1;
23
+ };
24
+ const splitSearchText = (text, options = {}) => {
25
+ const normalizedText = normalizeText(text);
26
+ if (!normalizedText) {
27
+ return [];
28
+ }
29
+ const maxLength = options.maxLength ?? DEFAULT_NOTE_CHUNK_MAX_LENGTH;
30
+ const overlap = options.overlap ?? DEFAULT_NOTE_CHUNK_OVERLAP;
31
+ if (!Number.isInteger(maxLength) || maxLength < 100) {
32
+ throw new Error("Note chunk maxLength must be an integer of at least 100.");
33
+ }
34
+ if (!Number.isInteger(overlap) || overlap < 0 || overlap >= maxLength) {
35
+ throw new Error("Note chunk overlap must be a non-negative integer smaller than maxLength.");
36
+ }
37
+ const chunks = [];
38
+ let start = 0;
39
+ while (start < normalizedText.length) {
40
+ const end = findWindowEnd(normalizedText, start, maxLength);
41
+ const chunk = normalizedText.slice(start, end).trim();
42
+ if (chunk) {
43
+ chunks.push(chunk);
44
+ }
45
+ if (end >= normalizedText.length) {
46
+ break;
47
+ }
48
+ start = findNextWindowStart(normalizedText, start, end, overlap);
49
+ }
50
+ return chunks;
51
+ };
52
+ const buildSourceHash = (title, body) => {
53
+ return createHash("sha256").update(`${NOTE_EMBEDDING_TEXT_SCHEMA_VERSION}\0${title}\0${body}`).digest("hex");
54
+ };
55
+ const buildNoteEmbeddingChunks = (note, options = {}) => {
56
+ const title = normalizeText(note.title);
57
+ const body = normalizeText(extractVisibleSearchTextFromContent(note.content));
58
+ const bodyChunks = splitSearchText(body, options);
59
+ const searchableChunks = bodyChunks.length > 0 ? bodyChunks : title ? [""] : [];
60
+ const sourceHash = buildSourceHash(title, body);
61
+ return searchableChunks.map((chunk, chunkIndex) => ({
62
+ noteId: note.id,
63
+ chunkIndex,
64
+ sourceHash,
65
+ text: title ? `Title: ${title}
66
+ Content: ${chunk}`.trimEnd() : chunk
67
+ }));
68
+ };
69
+ export {
70
+ DEFAULT_NOTE_CHUNK_MAX_LENGTH,
71
+ DEFAULT_NOTE_CHUNK_OVERLAP,
72
+ NOTE_EMBEDDING_TEXT_SCHEMA_VERSION,
73
+ buildNoteEmbeddingChunks,
74
+ splitSearchText
75
+ };
@@ -0,0 +1,150 @@
1
+ import { normalizeEmbeddingApiUrl } from "./embedding-client.js";
2
+ import { stripTrailingSlashes } from "./url-normalization.js";
3
+ const SEMANTIC_SEARCH_CONFIG_CACHE_KEY = "SEMANTIC_SEARCH_CONFIG_V1";
4
+ const SEMANTIC_SEARCH_VALIDATED_CONNECTION_CACHE_KEY = "SEMANTIC_SEARCH_VALIDATED_CONNECTION_V1";
5
+ const LEGACY_KOREAN_QUERY_INSTRUCTION = "Given a vague Korean memory query, retrieve relevant passages from personal notes.";
6
+ const DEFAULT_SEMANTIC_SEARCH_CONFIG = {
7
+ enabled: false,
8
+ baseUrl: "",
9
+ model: "",
10
+ queryInstruction: ""
11
+ };
12
+ const parseStoredConfig = (value) => {
13
+ try {
14
+ const parsed = JSON.parse(value);
15
+ if (typeof parsed.enabled !== "boolean" || typeof parsed.baseUrl !== "string" || typeof parsed.model !== "string" || typeof parsed.queryInstruction !== "string") {
16
+ return null;
17
+ }
18
+ return {
19
+ enabled: parsed.enabled,
20
+ baseUrl: parsed.baseUrl,
21
+ model: parsed.model,
22
+ queryInstruction: parsed.queryInstruction
23
+ };
24
+ } catch {
25
+ return null;
26
+ }
27
+ };
28
+ const readStoredAuthFingerprint = ({
29
+ authFingerprint,
30
+ usesBearerAuth
31
+ }) => {
32
+ if (typeof authFingerprint === "string") {
33
+ return authFingerprint;
34
+ }
35
+ if (usesBearerAuth === false) {
36
+ return "";
37
+ }
38
+ return "legacy-bearer-auth";
39
+ };
40
+ const parseValidatedConnection = (value) => {
41
+ try {
42
+ const parsed = JSON.parse(value);
43
+ if (typeof parsed.baseUrl !== "string" || typeof parsed.model !== "string" || typeof parsed.validatedAt !== "string") {
44
+ return null;
45
+ }
46
+ return {
47
+ baseUrl: parsed.baseUrl,
48
+ model: parsed.model,
49
+ validatedAt: parsed.validatedAt,
50
+ authFingerprint: readStoredAuthFingerprint(parsed)
51
+ };
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+ const normalizeSemanticSearchConfig = (input) => {
57
+ if (input.baseUrl.length > 2048) {
58
+ throw new Error("Embedding API URL is too long.");
59
+ }
60
+ const config = {
61
+ enabled: input.enabled,
62
+ baseUrl: stripTrailingSlashes(input.baseUrl.trim()),
63
+ model: input.model.trim(),
64
+ queryInstruction: input.queryInstruction.trim()
65
+ };
66
+ if (config.baseUrl) {
67
+ normalizeEmbeddingApiUrl(config.baseUrl);
68
+ }
69
+ if (config.enabled && (!config.baseUrl || !config.model)) {
70
+ throw new Error("Embedding API URL and model are required when semantic search is enabled.");
71
+ }
72
+ if (config.model.length > 200) {
73
+ throw new Error("Embedding model name is too long.");
74
+ }
75
+ if (config.queryInstruction.length > 1e3) {
76
+ throw new Error("Embedding query instruction is too long.");
77
+ }
78
+ return config;
79
+ };
80
+ class SemanticSearchConfigStore {
81
+ constructor(cache) {
82
+ this.cache = cache;
83
+ }
84
+ cache;
85
+ async get() {
86
+ const row = await this.cache.findUnique({ where: { key: SEMANTIC_SEARCH_CONFIG_CACHE_KEY } });
87
+ const config = row ? parseStoredConfig(row.value) : null;
88
+ if (!config) {
89
+ return DEFAULT_SEMANTIC_SEARCH_CONFIG;
90
+ }
91
+ if (config.queryInstruction.trim() === LEGACY_KOREAN_QUERY_INSTRUCTION) {
92
+ return this.set({ ...config, queryInstruction: "" });
93
+ }
94
+ return config;
95
+ }
96
+ async set(input) {
97
+ const config = normalizeSemanticSearchConfig(input);
98
+ const value = JSON.stringify(config);
99
+ await this.cache.upsert({
100
+ where: { key: SEMANTIC_SEARCH_CONFIG_CACHE_KEY },
101
+ create: { key: SEMANTIC_SEARCH_CONFIG_CACHE_KEY, value },
102
+ update: { value }
103
+ });
104
+ return config;
105
+ }
106
+ async getValidatedConnection() {
107
+ const row = await this.cache.findUnique({
108
+ where: { key: SEMANTIC_SEARCH_VALIDATED_CONNECTION_CACHE_KEY }
109
+ });
110
+ return row ? parseValidatedConnection(row.value) : null;
111
+ }
112
+ async isConnectionValidated(input, authFingerprint = "") {
113
+ const config = normalizeSemanticSearchConfig({
114
+ ...DEFAULT_SEMANTIC_SEARCH_CONFIG,
115
+ baseUrl: input.baseUrl,
116
+ model: input.model
117
+ });
118
+ const validated = await this.getValidatedConnection();
119
+ return Boolean(
120
+ validated && validated.baseUrl === config.baseUrl && validated.model === config.model && validated.authFingerprint === authFingerprint
121
+ );
122
+ }
123
+ async markConnectionValidated(input, authFingerprint = "") {
124
+ const config = normalizeSemanticSearchConfig({
125
+ ...DEFAULT_SEMANTIC_SEARCH_CONFIG,
126
+ baseUrl: input.baseUrl,
127
+ model: input.model
128
+ });
129
+ const validated = {
130
+ baseUrl: config.baseUrl,
131
+ model: config.model,
132
+ validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
133
+ authFingerprint
134
+ };
135
+ const value = JSON.stringify(validated);
136
+ await this.cache.upsert({
137
+ where: { key: SEMANTIC_SEARCH_VALIDATED_CONNECTION_CACHE_KEY },
138
+ create: { key: SEMANTIC_SEARCH_VALIDATED_CONNECTION_CACHE_KEY, value },
139
+ update: { value }
140
+ });
141
+ return validated;
142
+ }
143
+ }
144
+ export {
145
+ DEFAULT_SEMANTIC_SEARCH_CONFIG,
146
+ SEMANTIC_SEARCH_CONFIG_CACHE_KEY,
147
+ SEMANTIC_SEARCH_VALIDATED_CONNECTION_CACHE_KEY,
148
+ SemanticSearchConfigStore,
149
+ normalizeSemanticSearchConfig
150
+ };