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,431 @@
1
+ import models from "../../models.js";
2
+ import { subscribeServerEvents } from "../../modules/server-events.js";
3
+ import { paths } from "../../paths.js";
4
+ import {
5
+ createEmbeddingApiKeyFingerprint,
6
+ FileEmbeddingApiKeyStore
7
+ } from "./embedding-api-key-store.js";
8
+ import {
9
+ createOpenAiCompatibleEmbeddingClient,
10
+ listOpenAiCompatibleEmbeddingModels
11
+ } from "./embedding-client.js";
12
+ import { subscribeSemanticSearchNoteChanges } from "./note-change.js";
13
+ import {
14
+ buildNoteEmbeddingChunks,
15
+ NOTE_EMBEDDING_TEXT_SCHEMA_VERSION
16
+ } from "./note-chunking.js";
17
+ import { SemanticSearchConfigStore } from "./search-config.js";
18
+ import {
19
+ buildSemanticSearchIndex,
20
+ searchSemanticIndex,
21
+ updateSemanticSearchNotes
22
+ } from "./semantic-indexer.js";
23
+ import {
24
+ SqliteSemanticVectorIndex
25
+ } from "./sqlite-vector-index.js";
26
+ const errorMessage = (error) => error instanceof Error ? error.message : "Unknown semantic search error";
27
+ const NOTE_SYNC_QUIET_PERIOD_MS = 1e4;
28
+ const NOTE_SYNC_MAX_WAIT_MS = 6e4;
29
+ const NOTE_SYNC_POLL_INTERVAL_MS = 5e3;
30
+ const NOTE_SYNC_RECONCILIATION_INTERVAL_MS = 5 * 6e4;
31
+ const NOTE_SYNC_BATCH_SIZE = 20;
32
+ const NOTE_SYNC_RETRY_BASE_MS = 15e3;
33
+ const NOTE_SYNC_RETRY_MAX_MS = 5 * 6e4;
34
+ const profileMatchesConfig = (profile, config) => {
35
+ return Boolean(
36
+ profile && profile.baseUrl === config.baseUrl && profile.model === config.model && profile.textSchemaVersion === NOTE_EMBEDDING_TEXT_SCHEMA_VERSION
37
+ );
38
+ };
39
+ class SemanticSearchManager {
40
+ constructor(dependencies) {
41
+ this.dependencies = dependencies;
42
+ }
43
+ dependencies;
44
+ activeReindex = null;
45
+ activeNoteSync = null;
46
+ activeReconciliation = null;
47
+ noteSyncPollTimer = null;
48
+ noteSyncReconciliationTimer = null;
49
+ progress = null;
50
+ lastError = null;
51
+ lastQueueError = null;
52
+ resolveApiKey(input) {
53
+ return input?.provided ? input.apiKey : this.dependencies.apiKeyStore.get();
54
+ }
55
+ createClient(config, apiKeyInput) {
56
+ const factory = this.dependencies.createEmbeddingClient ?? createOpenAiCompatibleEmbeddingClient;
57
+ return factory({
58
+ ...config,
59
+ apiKey: this.resolveApiKey(apiKeyInput)
60
+ });
61
+ }
62
+ getAuthFingerprint(apiKeyInput) {
63
+ return createEmbeddingApiKeyFingerprint(this.resolveApiKey(apiKeyInput));
64
+ }
65
+ now() {
66
+ return this.dependencies.now?.() ?? Date.now();
67
+ }
68
+ async getStatus() {
69
+ const [config, indexStatus, noteSyncStatus] = await Promise.all([
70
+ this.dependencies.configStore.get(),
71
+ this.dependencies.vectorIndex.getStatus(),
72
+ this.dependencies.vectorIndex.getNoteSyncQueueStatus()
73
+ ]);
74
+ const connectionValidated = await this.dependencies.configStore.isConnectionValidated(
75
+ config,
76
+ this.getAuthFingerprint()
77
+ );
78
+ const profileMatches = profileMatchesConfig(indexStatus.profile, config);
79
+ const indexReady = indexStatus.ready && profileMatches;
80
+ const available = config.enabled && connectionValidated && indexReady;
81
+ const needsReindex = config.enabled && connectionValidated && !indexReady;
82
+ let phase;
83
+ if (this.activeReindex) {
84
+ phase = "indexing";
85
+ } else if (this.lastError) {
86
+ phase = "error";
87
+ } else if (!config.enabled) {
88
+ phase = "disabled";
89
+ } else if (!connectionValidated) {
90
+ phase = "needs-connection";
91
+ } else if (available) {
92
+ phase = "ready";
93
+ } else {
94
+ phase = "needs-index";
95
+ }
96
+ return {
97
+ config,
98
+ connectionValidated,
99
+ apiKeyConfigured: Boolean(this.dependencies.apiKeyStore.get()),
100
+ phase,
101
+ available,
102
+ needsReindex,
103
+ noteCount: indexStatus.noteCount,
104
+ chunkCount: indexStatus.chunkCount,
105
+ indexedAt: indexStatus.indexedAt,
106
+ dimensions: indexStatus.profile?.dimensions ?? null,
107
+ pendingNoteCount: noteSyncStatus.pendingNoteCount,
108
+ lastSyncedAt: noteSyncStatus.lastSyncedAt ?? indexStatus.indexedAt,
109
+ syncError: noteSyncStatus.error ?? this.lastQueueError,
110
+ progress: this.progress,
111
+ error: this.lastError
112
+ };
113
+ }
114
+ async saveConfig(config, apiKeyInput) {
115
+ if (this.activeReindex) {
116
+ throw new Error("Semantic search settings cannot change while indexing is running.");
117
+ }
118
+ const nextApiKey = this.resolveApiKey(apiKeyInput);
119
+ const apiKeyChanged = Boolean(apiKeyInput?.provided && nextApiKey !== this.dependencies.apiKeyStore.get());
120
+ this.lastError = null;
121
+ this.progress = null;
122
+ if (apiKeyChanged) {
123
+ this.dependencies.apiKeyStore.set(nextApiKey);
124
+ }
125
+ await this.dependencies.configStore.set(config);
126
+ return this.getStatus();
127
+ }
128
+ async validateConnection(config, apiKeyInput) {
129
+ const client = this.createClient({ ...config, enabled: true }, apiKeyInput);
130
+ const [embedding] = await client.embedDocuments(["Ocean Brain embedding connection test"]);
131
+ await this.dependencies.configStore.markConnectionValidated(config, this.getAuthFingerprint(apiKeyInput));
132
+ return embedding;
133
+ }
134
+ async testConnection(configInput, apiKeyInput) {
135
+ const config = configInput ?? await this.dependencies.configStore.get();
136
+ const embedding = await this.validateConnection(config, apiKeyInput);
137
+ return {
138
+ ok: true,
139
+ dimensions: embedding.length,
140
+ model: config.model
141
+ };
142
+ }
143
+ async listModels(baseUrl, apiKeyInput) {
144
+ return listOpenAiCompatibleEmbeddingModels(baseUrl, {
145
+ apiKey: this.resolveApiKey(apiKeyInput)
146
+ });
147
+ }
148
+ async startReindex() {
149
+ if (this.activeReindex) {
150
+ return { started: false, status: await this.getStatus() };
151
+ }
152
+ if (this.activeNoteSync) {
153
+ await this.activeNoteSync;
154
+ }
155
+ const config = await this.dependencies.configStore.get();
156
+ if (!config.enabled) {
157
+ throw new Error("Enable semantic search before building its index.");
158
+ }
159
+ const currentStatus = await this.getStatus();
160
+ if (!currentStatus.needsReindex) {
161
+ return { started: false, status: currentStatus };
162
+ }
163
+ this.lastError = null;
164
+ this.progress = { processedChunks: 0, totalChunks: 0 };
165
+ const run = async () => {
166
+ const notes = await this.dependencies.listNotes();
167
+ await buildSemanticSearchIndex({
168
+ notes,
169
+ embeddingClient: this.createClient(config),
170
+ vectorIndex: this.dependencies.vectorIndex,
171
+ model: config.model,
172
+ baseUrl: config.baseUrl,
173
+ queryInstruction: config.queryInstruction,
174
+ onProgress: (progress) => {
175
+ this.progress = progress;
176
+ }
177
+ });
178
+ await this.dependencies.vectorIndex.recordNoteSyncSuccess(this.now());
179
+ };
180
+ this.activeReindex = run().then(() => {
181
+ this.progress = null;
182
+ this.lastError = null;
183
+ }).catch((error) => {
184
+ this.progress = null;
185
+ this.lastError = errorMessage(error);
186
+ }).finally(() => {
187
+ this.activeReindex = null;
188
+ void this.processNextNoteSyncBatch().catch((error) => {
189
+ this.lastQueueError = errorMessage(error);
190
+ });
191
+ });
192
+ return { started: true, status: await this.getStatus() };
193
+ }
194
+ async waitForActiveReindex() {
195
+ await this.activeReindex;
196
+ return this.getStatus();
197
+ }
198
+ getRetryAt(entries) {
199
+ const attemptCount = Math.max(0, ...entries.map((entry) => entry.attemptCount));
200
+ const retryDelay = Math.min(NOTE_SYNC_RETRY_BASE_MS * 2 ** attemptCount, NOTE_SYNC_RETRY_MAX_MS);
201
+ return this.now() + retryDelay;
202
+ }
203
+ processNextNoteSyncBatch(force = false) {
204
+ if (this.activeNoteSync) {
205
+ return this.activeNoteSync;
206
+ }
207
+ const run = async () => {
208
+ if (this.activeReindex) {
209
+ if (!force) {
210
+ return { failed: false, processed: false };
211
+ }
212
+ await this.activeReindex;
213
+ }
214
+ const status = await this.getStatus();
215
+ if (!status.config.enabled || !status.available) {
216
+ return { failed: false, processed: false };
217
+ }
218
+ const entries = await this.dependencies.vectorIndex.listPendingNoteSyncs({
219
+ now: this.now(),
220
+ quietPeriodMs: NOTE_SYNC_QUIET_PERIOD_MS,
221
+ maxWaitMs: NOTE_SYNC_MAX_WAIT_MS,
222
+ limit: NOTE_SYNC_BATCH_SIZE,
223
+ force
224
+ });
225
+ if (entries.length === 0) {
226
+ return { failed: false, processed: false };
227
+ }
228
+ try {
229
+ const noteIds = entries.map((entry) => entry.noteId);
230
+ const notes = await this.dependencies.findNotes(noteIds);
231
+ const existingNoteIds = new Set(notes.map((note) => note.id));
232
+ const removedNoteIds = noteIds.filter((noteId) => !existingNoteIds.has(noteId));
233
+ await updateSemanticSearchNotes({
234
+ notes,
235
+ removedNoteIds,
236
+ embeddingClient: this.createClient(status.config),
237
+ vectorIndex: this.dependencies.vectorIndex
238
+ });
239
+ await this.dependencies.vectorIndex.completeNoteSyncs(entries, this.now());
240
+ this.lastQueueError = null;
241
+ return { failed: false, processed: true };
242
+ } catch (error) {
243
+ const message = errorMessage(error);
244
+ await this.dependencies.vectorIndex.failNoteSyncs(entries, message, this.getRetryAt(entries));
245
+ this.lastQueueError = message;
246
+ return { failed: true, processed: true };
247
+ }
248
+ };
249
+ const activeNoteSync = run().finally(() => {
250
+ if (this.activeNoteSync === activeNoteSync) {
251
+ this.activeNoteSync = null;
252
+ }
253
+ });
254
+ this.activeNoteSync = activeNoteSync;
255
+ return activeNoteSync;
256
+ }
257
+ async scheduleNoteSync(noteId) {
258
+ if (!Number.isInteger(noteId) || noteId <= 0) {
259
+ return;
260
+ }
261
+ try {
262
+ await this.dependencies.vectorIndex.enqueueNoteSync(noteId, this.now());
263
+ this.lastQueueError = null;
264
+ } catch (error) {
265
+ this.lastQueueError = errorMessage(error);
266
+ throw error;
267
+ }
268
+ }
269
+ async waitForPendingNoteSync() {
270
+ while ((await this.dependencies.vectorIndex.getNoteSyncQueueStatus()).pendingNoteCount > 0) {
271
+ const result = await this.processNextNoteSyncBatch(true);
272
+ if (!result.processed || result.failed) {
273
+ break;
274
+ }
275
+ }
276
+ return this.getStatus();
277
+ }
278
+ runNoteSyncReconciliation() {
279
+ if (this.activeReconciliation) {
280
+ return this.activeReconciliation;
281
+ }
282
+ const run = async () => {
283
+ if (this.activeReindex) {
284
+ return;
285
+ }
286
+ const status = await this.getStatus();
287
+ if (!status.available) {
288
+ return;
289
+ }
290
+ const [notes, indexedSourceHashes] = await Promise.all([
291
+ this.dependencies.listNotes(),
292
+ this.dependencies.vectorIndex.getAllNoteSourceHashes()
293
+ ]);
294
+ const currentSourceHashes = /* @__PURE__ */ new Map();
295
+ for (const note of notes) {
296
+ const sourceHash = buildNoteEmbeddingChunks(note)[0]?.sourceHash;
297
+ if (sourceHash) {
298
+ currentSourceHashes.set(note.id, sourceHash);
299
+ }
300
+ }
301
+ const changedNoteIds = /* @__PURE__ */ new Set();
302
+ for (const [noteId, sourceHash] of currentSourceHashes) {
303
+ if (indexedSourceHashes.get(noteId) !== sourceHash) {
304
+ changedNoteIds.add(noteId);
305
+ }
306
+ }
307
+ for (const noteId of indexedSourceHashes.keys()) {
308
+ if (!currentSourceHashes.has(noteId)) {
309
+ changedNoteIds.add(noteId);
310
+ }
311
+ }
312
+ const reconciledAt = this.now();
313
+ for (const noteId of changedNoteIds) {
314
+ await this.dependencies.vectorIndex.enqueueNoteSync(noteId, reconciledAt);
315
+ }
316
+ await this.dependencies.vectorIndex.recordNoteSyncReconciliation(reconciledAt);
317
+ this.lastQueueError = null;
318
+ };
319
+ const activeReconciliation = run().catch((error) => {
320
+ this.lastQueueError = errorMessage(error);
321
+ }).finally(() => {
322
+ if (this.activeReconciliation === activeReconciliation) {
323
+ this.activeReconciliation = null;
324
+ }
325
+ });
326
+ this.activeReconciliation = activeReconciliation;
327
+ return activeReconciliation;
328
+ }
329
+ startBackgroundSync() {
330
+ if (this.noteSyncPollTimer || this.noteSyncReconciliationTimer) {
331
+ return;
332
+ }
333
+ const poll = () => {
334
+ void this.processNextNoteSyncBatch().catch((error) => {
335
+ this.lastQueueError = errorMessage(error);
336
+ });
337
+ };
338
+ const reconcile = () => {
339
+ void this.runNoteSyncReconciliation();
340
+ };
341
+ poll();
342
+ reconcile();
343
+ this.noteSyncPollTimer = setInterval(poll, NOTE_SYNC_POLL_INTERVAL_MS);
344
+ this.noteSyncPollTimer.unref?.();
345
+ this.noteSyncReconciliationTimer = setInterval(reconcile, NOTE_SYNC_RECONCILIATION_INTERVAL_MS);
346
+ this.noteSyncReconciliationTimer.unref?.();
347
+ }
348
+ stopBackgroundSync() {
349
+ if (this.noteSyncPollTimer) {
350
+ clearInterval(this.noteSyncPollTimer);
351
+ this.noteSyncPollTimer = null;
352
+ }
353
+ if (this.noteSyncReconciliationTimer) {
354
+ clearInterval(this.noteSyncReconciliationTimer);
355
+ this.noteSyncReconciliationTimer = null;
356
+ }
357
+ }
358
+ async trySearch(query, limit) {
359
+ const status = await this.getStatus();
360
+ if (!status.available) {
361
+ return {
362
+ available: false,
363
+ used: false,
364
+ matches: [],
365
+ error: status.error
366
+ };
367
+ }
368
+ try {
369
+ const matches = await searchSemanticIndex(
370
+ query,
371
+ limit,
372
+ this.createClient(status.config),
373
+ this.dependencies.vectorIndex
374
+ );
375
+ return {
376
+ available: true,
377
+ used: true,
378
+ matches,
379
+ error: null
380
+ };
381
+ } catch (error) {
382
+ return {
383
+ available: true,
384
+ used: false,
385
+ matches: [],
386
+ error: errorMessage(error)
387
+ };
388
+ }
389
+ }
390
+ }
391
+ let defaultSemanticSearchManager = null;
392
+ const getDefaultSemanticSearchManager = () => {
393
+ if (!defaultSemanticSearchManager) {
394
+ defaultSemanticSearchManager = new SemanticSearchManager({
395
+ configStore: new SemanticSearchConfigStore({
396
+ findUnique: (args) => models.cache.findUnique(args),
397
+ upsert: (args) => models.cache.upsert(args)
398
+ }),
399
+ vectorIndex: new SqliteSemanticVectorIndex(paths.searchIndex),
400
+ listNotes: () => models.note.findMany({
401
+ orderBy: { id: "asc" },
402
+ select: {
403
+ id: true,
404
+ title: true,
405
+ content: true
406
+ }
407
+ }),
408
+ findNotes: (noteIds) => models.note.findMany({
409
+ where: { id: { in: noteIds } },
410
+ select: {
411
+ id: true,
412
+ title: true,
413
+ content: true
414
+ }
415
+ }),
416
+ apiKeyStore: new FileEmbeddingApiKeyStore(paths.embeddingApiKey)
417
+ });
418
+ subscribeServerEvents((event) => {
419
+ void defaultSemanticSearchManager?.scheduleNoteSync(Number(event.noteId)).catch(() => void 0);
420
+ });
421
+ subscribeSemanticSearchNoteChanges((noteId) => {
422
+ void defaultSemanticSearchManager?.scheduleNoteSync(noteId).catch(() => void 0);
423
+ });
424
+ defaultSemanticSearchManager.startBackgroundSync();
425
+ }
426
+ return defaultSemanticSearchManager;
427
+ };
428
+ export {
429
+ SemanticSearchManager,
430
+ getDefaultSemanticSearchManager
431
+ };
@@ -0,0 +1,138 @@
1
+ import {
2
+ buildNoteEmbeddingChunks,
3
+ NOTE_EMBEDDING_TEXT_SCHEMA_VERSION
4
+ } from "./note-chunking.js";
5
+ const DEFAULT_EMBEDDING_BATCH_SIZE = 16;
6
+ const validateBatchSize = (batchSize) => {
7
+ if (!Number.isInteger(batchSize) || batchSize <= 0 || batchSize > 256) {
8
+ throw new Error("Embedding batch size must be an integer between 1 and 256.");
9
+ }
10
+ };
11
+ const embedChunks = async (chunks, embeddingClient, batchSize, onProgress) => {
12
+ const indexedChunks = [];
13
+ let dimensions = null;
14
+ for (let start = 0; start < chunks.length; start += batchSize) {
15
+ const batch = chunks.slice(start, start + batchSize);
16
+ const embeddings = await embeddingClient.embedDocuments(batch.map((chunk) => chunk.text));
17
+ if (embeddings.length !== batch.length) {
18
+ throw new Error(`Embedding provider returned ${embeddings.length} vectors for ${batch.length} chunks.`);
19
+ }
20
+ embeddings.forEach((embedding, index) => {
21
+ dimensions ??= embedding.length;
22
+ if (embedding.length !== dimensions) {
23
+ throw new Error("Embedding provider changed vector dimensions while building the index.");
24
+ }
25
+ indexedChunks.push({
26
+ ...batch[index],
27
+ embedding
28
+ });
29
+ });
30
+ onProgress?.(indexedChunks.length);
31
+ }
32
+ return { indexedChunks, dimensions };
33
+ };
34
+ const buildSemanticSearchIndex = async ({
35
+ notes,
36
+ embeddingClient,
37
+ vectorIndex,
38
+ model,
39
+ baseUrl,
40
+ queryInstruction,
41
+ batchSize = DEFAULT_EMBEDDING_BATCH_SIZE,
42
+ onProgress
43
+ }) => {
44
+ validateBatchSize(batchSize);
45
+ const chunks = notes.flatMap((note) => buildNoteEmbeddingChunks(note));
46
+ const totalChunks = chunks.length;
47
+ onProgress?.({ processedChunks: 0, totalChunks });
48
+ if (totalChunks === 0) {
49
+ await vectorIndex.clear();
50
+ return vectorIndex.getStatus();
51
+ }
52
+ const { indexedChunks, dimensions } = await embedChunks(
53
+ chunks,
54
+ embeddingClient,
55
+ batchSize,
56
+ (processedChunks) => onProgress?.({ processedChunks, totalChunks })
57
+ );
58
+ if (!dimensions) {
59
+ throw new Error("Embedding provider returned no vector dimensions.");
60
+ }
61
+ return vectorIndex.replaceAll(
62
+ {
63
+ model,
64
+ baseUrl,
65
+ dimensions,
66
+ queryInstruction,
67
+ textSchemaVersion: NOTE_EMBEDDING_TEXT_SCHEMA_VERSION
68
+ },
69
+ indexedChunks
70
+ );
71
+ };
72
+ const updateSemanticSearchNote = async ({
73
+ note,
74
+ embeddingClient,
75
+ vectorIndex,
76
+ batchSize = DEFAULT_EMBEDDING_BATCH_SIZE
77
+ }) => {
78
+ validateBatchSize(batchSize);
79
+ const chunks = buildNoteEmbeddingChunks(note);
80
+ const sourceHash = chunks[0]?.sourceHash ?? null;
81
+ const indexedSourceHash = await vectorIndex.getNoteSourceHash(note.id);
82
+ if (sourceHash === indexedSourceHash) {
83
+ return vectorIndex.getStatus();
84
+ }
85
+ if (chunks.length === 0) {
86
+ return vectorIndex.removeNote(note.id);
87
+ }
88
+ const { indexedChunks } = await embedChunks(chunks, embeddingClient, batchSize);
89
+ return vectorIndex.replaceNote(note.id, indexedChunks);
90
+ };
91
+ const updateSemanticSearchNotes = async ({
92
+ notes,
93
+ removedNoteIds,
94
+ embeddingClient,
95
+ vectorIndex,
96
+ batchSize = DEFAULT_EMBEDDING_BATCH_SIZE
97
+ }) => {
98
+ validateBatchSize(batchSize);
99
+ const changedNotes = [];
100
+ for (const note of notes) {
101
+ const chunks = buildNoteEmbeddingChunks(note);
102
+ const sourceHash = chunks[0]?.sourceHash ?? null;
103
+ const indexedSourceHash = await vectorIndex.getNoteSourceHash(note.id);
104
+ if (sourceHash !== indexedSourceHash) {
105
+ changedNotes.push({ note, chunks });
106
+ }
107
+ }
108
+ const chunksToEmbed = changedNotes.flatMap(({ chunks }) => chunks);
109
+ const { indexedChunks } = await embedChunks(chunksToEmbed, embeddingClient, batchSize);
110
+ for (const { note, chunks } of changedNotes) {
111
+ if (chunks.length === 0) {
112
+ await vectorIndex.removeNote(note.id);
113
+ continue;
114
+ }
115
+ await vectorIndex.replaceNote(
116
+ note.id,
117
+ indexedChunks.filter((chunk) => chunk.noteId === note.id)
118
+ );
119
+ }
120
+ for (const noteId of new Set(removedNoteIds)) {
121
+ await vectorIndex.removeNote(noteId);
122
+ }
123
+ return vectorIndex.getStatus();
124
+ };
125
+ const searchSemanticIndex = async (query, limit, embeddingClient, vectorIndex) => {
126
+ const status = await vectorIndex.getStatus();
127
+ if (!status.ready) {
128
+ return [];
129
+ }
130
+ const queryEmbedding = await embeddingClient.embedQuery(query);
131
+ return vectorIndex.search(queryEmbedding, limit);
132
+ };
133
+ export {
134
+ buildSemanticSearchIndex,
135
+ searchSemanticIndex,
136
+ updateSemanticSearchNote,
137
+ updateSemanticSearchNotes
138
+ };