ocean-brain 0.8.2 → 0.11.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.
- package/README.md +2 -0
- package/dist/mcp.js +103 -105
- package/package.json +3 -2
- package/server/client/dist/assets/{ArrowRight.es-g2YZTklZ.js → ArrowRight.es-BEhnlwfe.js} +1 -1
- package/server/client/dist/assets/AuxiliaryPanelHeader-B1k7pZpn.js +1 -0
- package/server/client/dist/assets/Calendar-D7QJsAlg.js +25 -0
- package/server/client/dist/assets/{Callout-B_IPSF_5.js → Callout-BTd1BYtV.js} +1 -1
- package/server/client/dist/assets/{Copy.es-CNxe4ujr.js → Copy.es-BoLjilxv.js} +1 -1
- package/server/client/dist/assets/File.es-Ct7fscBg.js +1 -0
- package/server/client/dist/assets/Graph-DYUb7gQb.js +46 -0
- package/server/client/dist/assets/{Graph-D9GeO71k.js → Graph-LY_IgMYy.js} +1 -1
- package/server/client/dist/assets/{Image.es-DnON8cv2.js → Image.es-OYyN3BYt.js} +1 -1
- package/server/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/server/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/server/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/server/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/server/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/server/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/server/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/server/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/server/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/server/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/server/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/server/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/server/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/server/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/server/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/server/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/server/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/server/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/server/client/dist/assets/Note-DsMhwrHw.js +25 -0
- package/server/client/dist/assets/NotePropertiesPanel-aCKXAe5F.js +2 -0
- package/server/client/dist/assets/PencilLine.es-0JpEgxFY.js +1 -0
- package/server/client/dist/assets/{Plus.es-CQ8wn2is.js → Plus.es-DRfLW_bI.js} +1 -1
- package/server/client/dist/assets/Reminders-COr1OHvF.js +1 -0
- package/server/client/dist/assets/Search-Ctkx0BQk.js +31 -0
- package/server/client/dist/assets/SurfaceCard-t7pdfhEe.js +1 -0
- package/server/client/dist/assets/Tag-BjxtrIQW.js +1 -0
- package/server/client/dist/assets/TagNotes-DV_fdRbg.js +1 -0
- package/server/client/dist/assets/{Trash.es-0aPpA-3S.js → Trash.es-Bo1VyK9Q.js} +1 -1
- package/server/client/dist/assets/ViewChip-DlCkL4z4.js +1 -0
- package/server/client/dist/assets/ViewNotes-wWWeFJyU.js +1 -0
- package/server/client/dist/assets/ViewSectionDialog-DhH8harC.js +102 -0
- package/server/client/dist/assets/Views-CWE99SnI.js +1 -0
- package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
- package/server/client/dist/assets/app-B9WZRrAH.css +1 -0
- package/server/client/dist/assets/app-BpSMjzET.js +26 -0
- package/server/client/dist/assets/arc-BV-n6RUX.js +1 -0
- package/server/client/dist/assets/architectureDiagram-ZJ3FMSHR-CekzMXdu.js +36 -0
- package/server/client/dist/assets/blockDiagram-677ZJIJ3-B49yHoxq.js +132 -0
- package/server/client/dist/assets/c4Diagram-LMCZKHZV-0AfB9NBC.js +10 -0
- package/server/client/dist/assets/channel-CigOulR-.js +1 -0
- package/server/client/dist/assets/chunk-2Q5K7J3B-DaPQAU1q.js +1 -0
- package/server/client/dist/assets/chunk-32BRIVSS-CfL3kZAg.js +1 -0
- package/server/client/dist/assets/chunk-5VM5RSS4-DeOwWPE_.js +15 -0
- package/server/client/dist/assets/chunk-EX3LRPZG-C3W1oc9y.js +231 -0
- package/server/client/dist/assets/chunk-JWPE2WC7-_Oqh79OY.js +1 -0
- package/server/client/dist/assets/chunk-MOJQB5TN-DDKNNzI6.js +88 -0
- package/server/client/dist/assets/chunk-RYQCIY6F-DCHah7CC.js +1 -0
- package/server/client/dist/assets/chunk-V7JOEXUC-uUOIzgxj.js +206 -0
- package/server/client/dist/assets/chunk-VR4S4FIN-DDVw2WPs.js +1 -0
- package/server/client/dist/assets/chunk-XXDRQBXY-Coo4UFm2.js +1 -0
- package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +1 -0
- package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +1 -0
- package/server/client/dist/assets/colors-Cc3OSVma.js +1 -0
- package/server/client/dist/assets/cose-bilkent-JH36ORCC-C3pHmHpD.js +1 -0
- package/server/client/dist/assets/cynefin-VYW2F7L2-DU_G4Atc.js +166 -0
- package/server/client/dist/assets/cynefinDiagram-TSTJHNR4-DZ6iiJFo.js +62 -0
- package/server/client/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
- package/server/client/dist/assets/dagre-VKFMJZFB-DNELbhwC.js +4 -0
- package/server/client/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/server/client/dist/assets/diagram-FQU43EPY-CMEvZJ61.js +3 -0
- package/server/client/dist/assets/diagram-G47NLZAW-BYDtf6j9.js +24 -0
- package/server/client/dist/assets/diagram-NH7WQ7WH-CmXfcCQA.js +24 -0
- package/server/client/dist/assets/diagram-OA4YK3LP-BflTKJxU.js +30 -0
- package/server/client/dist/assets/diagram-WEI45ONY-KW3fKqna.js +41 -0
- package/server/client/dist/assets/ebnfDiagram-CCIWWBDH-CbyhY5Nj.js +1 -0
- package/server/client/dist/assets/erDiagram-Q63AITRT-CC2eZV8g.js +85 -0
- package/server/client/dist/assets/{floating-ui.react-dom-wWLZ1CdM.js → floating-ui.react-dom-BWTnA00G.js} +1 -1
- package/server/client/dist/assets/flowDiagram-23GEKE2U-unplo7Qm.js +156 -0
- package/server/client/dist/assets/ganttDiagram-NO4QXBWP-BcBK5HcZ.js +292 -0
- package/server/client/dist/assets/gitGraphDiagram-IHSO6WYX-DgVbbb5k.js +106 -0
- package/server/client/dist/assets/graph-C5DFNLrC.js +1 -0
- package/server/client/dist/assets/image-delete-confirmation-D7xrODQV.js +1 -0
- package/server/client/dist/assets/image.api-CyQisInP.js +17 -0
- package/server/client/dist/assets/index-B-E1Ilh9.js +1 -0
- package/server/client/dist/assets/infoDiagram-FWYZ7A6U-N_D9RdfG.js +2 -0
- package/server/client/dist/assets/init-Gi6I4Gst.js +1 -0
- package/server/client/dist/assets/isObject-U9U2HXiq.js +1 -0
- package/server/client/dist/assets/ishikawaDiagram-FXEZZL3T-BBKrzmB2.js +70 -0
- package/server/client/dist/assets/journeyDiagram-5HDEW3XC-NdCxLmQB.js +139 -0
- package/server/client/dist/assets/kanban-definition-HUTT4EX6-Ck7t4-tt.js +89 -0
- package/server/client/dist/assets/katex-DywYRkvg.css +1 -0
- package/server/client/dist/assets/katex-HP8lGamR.js +257 -0
- package/server/client/dist/assets/katex-x288kqHd.js +257 -0
- package/server/client/dist/assets/layout-Cz0MDlGi.js +1 -0
- package/server/client/dist/assets/linear-D6yRgDLq.js +1 -0
- package/server/client/dist/assets/manage-image-azmo5iVw.js +1 -0
- package/server/client/dist/assets/manage-image-detail-BF-uSqb_.js +1 -0
- package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
- package/server/client/dist/assets/{mcp-CT_9IV2c.js → mcp-CCaFd0zm.js} +4 -4
- package/server/client/dist/assets/{mcp-admin-adapter-xAuyZc_M.js → mcp-admin-adapter-PMmgPupc.js} +1 -1
- package/server/client/dist/assets/mermaid.core-B14ZAmWA.js +308 -0
- package/server/client/dist/assets/min-tv6RD-6E.js +1 -0
- package/server/client/dist/assets/mindmap-definition-LN4V7U3C-BTUqSdcP.js +96 -0
- package/server/client/dist/assets/{note-core-Buwzyxi7.js → note-core-DUTqUh5N.js} +2 -2
- package/server/client/dist/assets/{note-runtime-CPDVOFEo.js → note-runtime-zN5ddTba.js} +1 -1
- package/server/client/dist/assets/{note-ui-CHI-tzj-.js → note-ui-CtijppHK.js} +1 -1
- package/server/client/dist/assets/{note.api-D_00ZzJC.js → note.api-D3oJmG3q.js} +6 -6
- package/server/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/server/client/dist/assets/pegDiagram-2B236MQR-Cjcu3mVz.js +1 -0
- package/server/client/dist/assets/pieDiagram-ENE6RG2P-BxI_c22Z.js +39 -0
- package/server/client/dist/assets/{placeholder-DbdGiYGL.js → placeholder-AusSo8p9.js} +4 -4
- package/server/client/dist/assets/properties-BrNJLkxR.js +1 -0
- package/server/client/dist/assets/quadrantDiagram-ABIIQ3AL-DGm0a-yR.js +7 -0
- package/server/client/dist/assets/railroadDiagram-RFXS5EU6-DmCDu89O.js +1 -0
- package/server/client/dist/assets/requirementDiagram-TGXJPOKE-CBpUt7Ig.js +84 -0
- package/server/client/dist/assets/route-preload.js +2 -2
- package/server/client/dist/assets/sankeyDiagram-HTMAVEWB-BjGnDZlp.js +40 -0
- package/server/client/dist/assets/search-D-xKTPdO.js +1 -0
- package/server/client/dist/assets/search-admin-adapter-CJnJmL6w.js +1 -0
- package/server/client/dist/assets/sequenceDiagram-DBY2YBRQ-B7rpCfS6.js +162 -0
- package/server/client/dist/assets/sizeCapture-X5ZJPWSS-CUJhD0uW.js +1 -0
- package/server/client/dist/assets/stateDiagram-2N3HPSRC-Y9-3XqRX.js +1 -0
- package/server/client/dist/assets/stateDiagram-v2-6OUMAXLB-DbHXhT02.js +1 -0
- package/server/client/dist/assets/swimlanes-5IMT3BWC-Cs1NO9ng.js +2 -0
- package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +8 -0
- package/server/client/dist/assets/{tag.api-CX0SE06J.js → tag.api-DNSQpqwk.js} +3 -3
- package/server/client/dist/assets/time-D5LRMumI.js +9 -0
- package/server/client/dist/assets/timeline-definition-FHXFAJF6-8fqD4WNL.js +120 -0
- package/server/client/dist/assets/toNumber-DE3J3kMD.js +1 -0
- package/server/client/dist/assets/transform-BUB79_za.js +1 -0
- package/server/client/dist/assets/trash-D4-9sv8q.js +1 -0
- package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-CLK1tHvT.js → useReminderMutate-BrGO9M0o.js} +4 -4
- package/server/client/dist/assets/vennDiagram-L72KCM5P-BPqKkd5a.js +34 -0
- package/server/client/dist/assets/wardleyDiagram-EHGQE667-Cq11kU2M.js +78 -0
- package/server/client/dist/assets/xychartDiagram-FW5EYKEG-DJ6UzFj-.js +7 -0
- package/server/client/dist/index.html +7 -7
- package/server/dist/features/image/services/delete.js +25 -1
- package/server/dist/features/note/graphql/note.field.resolver.js +2 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js +54 -23
- package/server/dist/features/note/graphql/note.query.resolver.js +15 -5
- package/server/dist/features/note/graphql/note.type-defs.js +2 -1
- package/server/dist/features/note/http/mcp.js +1 -60
- package/server/dist/features/note/services/authoring.js +17 -80
- package/server/dist/features/note/services/markdown-patch.js +80 -8
- package/server/dist/features/note/services/note-reference-index.js +98 -0
- package/server/dist/features/note/services/properties.js +21 -1
- package/server/dist/features/note/services/property-restore.js +26 -0
- package/server/dist/features/note/services/search.js +5 -0
- package/server/dist/features/note/services/snapshot.js +82 -58
- package/server/dist/features/note/services/trash.js +18 -31
- package/server/dist/features/note/services/write.js +10 -3
- package/server/dist/features/search/embedding-api-key-store.js +57 -0
- package/server/dist/features/search/embedding-client.js +179 -0
- package/server/dist/features/search/graphql/index.js +6 -0
- package/server/dist/features/search/graphql/resolver.js +32 -0
- package/server/dist/features/search/graphql/type-defs.js +37 -0
- package/server/dist/features/search/http/handlers.js +108 -0
- package/server/dist/features/search/hybrid-ranking.js +57 -0
- package/server/dist/features/search/hybrid-search.js +176 -0
- package/server/dist/features/search/note-change.js +16 -0
- package/server/dist/features/search/note-chunking.js +75 -0
- package/server/dist/features/search/related-notes.js +186 -0
- package/server/dist/features/search/search-config.js +150 -0
- package/server/dist/features/search/search-manager.js +431 -0
- package/server/dist/features/search/semantic-indexer.js +138 -0
- package/server/dist/features/search/sqlite-vector-index.js +636 -0
- package/server/dist/features/search/url-normalization.js +10 -0
- package/server/dist/features/tag/services/organization.js +3 -0
- package/server/dist/features/view/services/workspace.js +1 -1
- package/server/dist/main.js +12 -3
- package/server/dist/modules/blocknote.js +4 -5
- package/server/dist/paths.js +7 -2
- package/server/dist/routes/api.js +37 -0
- package/server/dist/routes/mcp.js +0 -5
- package/server/dist/schema/index.js +3 -0
- package/server/prisma/migrations/20260728090000_0019_note_reference_index/migration.sql +15 -0
- package/server/prisma/schema.prisma +16 -0
- package/server/client/dist/assets/Calendar-BP6HTuSn.js +0 -25
- package/server/client/dist/assets/Graph-Xha3fwhp.js +0 -46
- package/server/client/dist/assets/Image-BLEjLUqd.js +0 -1
- package/server/client/dist/assets/Note-DjrCYtat.js +0 -24
- package/server/client/dist/assets/NotePropertiesPanel-2DgXEM4B.js +0 -2
- package/server/client/dist/assets/Reminders-B4l3KdlK.js +0 -1
- package/server/client/dist/assets/Search-BN5sf7bE.js +0 -1
- package/server/client/dist/assets/SurfaceCard-BZm73nyS.js +0 -1
- package/server/client/dist/assets/Tag-oSnmViVp.js +0 -1
- package/server/client/dist/assets/TagNotes-BDTCcJcL.js +0 -1
- package/server/client/dist/assets/ViewNotes-CiZtGJNE.js +0 -1
- package/server/client/dist/assets/ViewSectionDialog-Ctl_694Y.js +0 -102
- package/server/client/dist/assets/Views-D-zlatYD.js +0 -1
- package/server/client/dist/assets/app-DmqIOMWF.css +0 -1
- package/server/client/dist/assets/app-cTRcKMGc.js +0 -26
- package/server/client/dist/assets/image.api-CVuuvTBk.js +0 -17
- package/server/client/dist/assets/index-DKJChPr4.js +0 -1
- package/server/client/dist/assets/manage-image-BZjaxp-f.js +0 -1
- package/server/client/dist/assets/manage-image-detail-DPv0FQ2m.js +0 -1
- package/server/client/dist/assets/properties-D447wsWk.js +0 -1
- package/server/client/dist/assets/time-B3pt8vt4.js +0 -9
- package/server/client/dist/assets/trash-8sH7EZgc.js +0 -1
- package/server/client/dist/assets/url-BSnWpmJx.js +0 -1
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
RECOVERY_CLEANUP_BATCH_LIMIT,
|
|
5
5
|
TRASH_RETENTION_DAYS
|
|
6
6
|
} from "../../../modules/recovery-retention.js";
|
|
7
|
+
import { replaceNoteReferences } from "./note-reference-index.js";
|
|
8
|
+
import { resolveRestoredPropertyTarget } from "./property-restore.js";
|
|
7
9
|
import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
|
|
8
10
|
const TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;
|
|
9
11
|
class NoteRestoreConflictError extends Error {
|
|
@@ -288,42 +290,27 @@ const noteTrashService = createNoteTrashService({
|
|
|
288
290
|
} : {}
|
|
289
291
|
}
|
|
290
292
|
});
|
|
293
|
+
await replaceNoteReferences(tx, note.id, restoredContent);
|
|
291
294
|
for (const property of deletedNote.properties ?? []) {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
name: property.name,
|
|
301
|
-
valueType: property.valueType
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
const option = property.valueType === "select" && property.optionValue ? await tx.propertyOption.upsert({
|
|
305
|
-
where: {
|
|
306
|
-
propertyDefinitionId_value: {
|
|
307
|
-
propertyDefinitionId: definition.id,
|
|
308
|
-
value: property.optionValue
|
|
295
|
+
const target = await resolveRestoredPropertyTarget(property, {
|
|
296
|
+
findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
|
|
297
|
+
findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
|
|
298
|
+
where: {
|
|
299
|
+
propertyDefinitionId_value: {
|
|
300
|
+
propertyDefinitionId,
|
|
301
|
+
value
|
|
302
|
+
}
|
|
309
303
|
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
color: property.optionColor
|
|
316
|
-
},
|
|
317
|
-
update: {
|
|
318
|
-
label: property.optionLabel ?? property.optionValue,
|
|
319
|
-
color: property.optionColor
|
|
320
|
-
}
|
|
321
|
-
}) : null;
|
|
304
|
+
})
|
|
305
|
+
});
|
|
306
|
+
if (!target) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
322
309
|
await tx.noteProperty.create({
|
|
323
310
|
data: {
|
|
324
311
|
noteId: note.id,
|
|
325
|
-
propertyDefinitionId:
|
|
326
|
-
optionId:
|
|
312
|
+
propertyDefinitionId: target.propertyDefinitionId,
|
|
313
|
+
optionId: target.optionId,
|
|
327
314
|
textValue: property.textValue,
|
|
328
315
|
textValueNormalized: property.textValueNormalized,
|
|
329
316
|
numberValue: property.numberValue,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import models, { Prisma } from "../../../models.js";
|
|
2
|
+
import { replaceNoteReferences } from "./note-reference-index.js";
|
|
2
3
|
import { buildNoteSearchProjection } from "./search.js";
|
|
3
4
|
import { captureNoteBaseline } from "./snapshot.js";
|
|
4
5
|
import { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from "./write-conflict.js";
|
|
@@ -101,9 +102,15 @@ const defaultNoteWriteService = createNoteWriteService({
|
|
|
101
102
|
}
|
|
102
103
|
}),
|
|
103
104
|
captureBaseline: captureNoteBaseline,
|
|
104
|
-
updateNote: ({ where, data }) => models
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
updateNote: ({ where, data }) => models.$transaction(async (tx) => {
|
|
106
|
+
const note = await tx.note.update({
|
|
107
|
+
where,
|
|
108
|
+
data
|
|
109
|
+
});
|
|
110
|
+
if (data.content !== void 0) {
|
|
111
|
+
await replaceNoteReferences(tx, note.id, data.content);
|
|
112
|
+
}
|
|
113
|
+
return note;
|
|
107
114
|
}),
|
|
108
115
|
isRecordNotFoundError
|
|
109
116
|
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const MAX_EMBEDDING_API_KEY_LENGTH = 8192;
|
|
5
|
+
const normalizeEmbeddingApiKey = (value) => {
|
|
6
|
+
const apiKey = value.trim();
|
|
7
|
+
if (apiKey.length > MAX_EMBEDDING_API_KEY_LENGTH) {
|
|
8
|
+
throw new Error("Embedding API key is too long.");
|
|
9
|
+
}
|
|
10
|
+
return apiKey || void 0;
|
|
11
|
+
};
|
|
12
|
+
const createEmbeddingApiKeyFingerprint = (apiKey) => {
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
return createHash("sha256").update(apiKey).digest("hex");
|
|
17
|
+
};
|
|
18
|
+
class FileEmbeddingApiKeyStore {
|
|
19
|
+
constructor(filePath) {
|
|
20
|
+
this.filePath = filePath;
|
|
21
|
+
try {
|
|
22
|
+
this.apiKey = normalizeEmbeddingApiKey(readFileSync(filePath, "utf8"));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.code !== "ENOENT") {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
filePath;
|
|
30
|
+
apiKey;
|
|
31
|
+
get() {
|
|
32
|
+
return this.apiKey;
|
|
33
|
+
}
|
|
34
|
+
set(value) {
|
|
35
|
+
const apiKey = value === void 0 ? void 0 : normalizeEmbeddingApiKey(value);
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
rmSync(this.filePath, { force: true });
|
|
38
|
+
this.apiKey = void 0;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
42
|
+
const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
43
|
+
try {
|
|
44
|
+
writeFileSync(temporaryPath, apiKey, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
45
|
+
renameSync(temporaryPath, this.filePath);
|
|
46
|
+
this.apiKey = apiKey;
|
|
47
|
+
} finally {
|
|
48
|
+
rmSync(temporaryPath, { force: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
FileEmbeddingApiKeyStore,
|
|
54
|
+
MAX_EMBEDDING_API_KEY_LENGTH,
|
|
55
|
+
createEmbeddingApiKeyFingerprint,
|
|
56
|
+
normalizeEmbeddingApiKey
|
|
57
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { stripTrailingSlashes } from "./url-normalization.js";
|
|
2
|
+
const DEFAULT_EMBEDDING_TIMEOUT_MS = 6e4;
|
|
3
|
+
const DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS = 1e4;
|
|
4
|
+
const buildOpenAiCompatibleUrl = (baseUrl, resource) => {
|
|
5
|
+
let parsedUrl;
|
|
6
|
+
try {
|
|
7
|
+
parsedUrl = new URL(baseUrl);
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error("Embedding API URL must be a valid http or https URL.");
|
|
10
|
+
}
|
|
11
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
12
|
+
throw new Error("Embedding API URL must use http or https.");
|
|
13
|
+
}
|
|
14
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
15
|
+
throw new Error("Embedding API URL must not contain credentials.");
|
|
16
|
+
}
|
|
17
|
+
const normalizedPath = stripTrailingSlashes(parsedUrl.pathname);
|
|
18
|
+
const normalizedPathLower = normalizedPath.toLowerCase();
|
|
19
|
+
const resourceSuffix = ["/embeddings", "/models"].find((suffix) => normalizedPathLower.endsWith(suffix));
|
|
20
|
+
const basePath = resourceSuffix ? normalizedPath.slice(0, -resourceSuffix.length) : normalizedPath;
|
|
21
|
+
parsedUrl.pathname = `${basePath || "/v1"}/${resource}`;
|
|
22
|
+
parsedUrl.search = "";
|
|
23
|
+
parsedUrl.hash = "";
|
|
24
|
+
return parsedUrl.toString();
|
|
25
|
+
};
|
|
26
|
+
const buildEmbeddingsUrl = (baseUrl) => buildOpenAiCompatibleUrl(baseUrl, "embeddings");
|
|
27
|
+
const buildModelsUrl = (baseUrl) => buildOpenAiCompatibleUrl(baseUrl, "models");
|
|
28
|
+
const validateEmbedding = (value, expectedIndex) => {
|
|
29
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
30
|
+
throw new Error(`Embedding API returned an empty vector for input ${expectedIndex}.`);
|
|
31
|
+
}
|
|
32
|
+
const embedding = value.map((item) => {
|
|
33
|
+
if (typeof item !== "number" || !Number.isFinite(item)) {
|
|
34
|
+
throw new Error(`Embedding API returned an invalid vector value for input ${expectedIndex}.`);
|
|
35
|
+
}
|
|
36
|
+
return item;
|
|
37
|
+
});
|
|
38
|
+
return embedding;
|
|
39
|
+
};
|
|
40
|
+
const parseEmbeddingResponse = (payload, expectedCount) => {
|
|
41
|
+
if (!payload || typeof payload !== "object" || !("data" in payload) || !Array.isArray(payload.data)) {
|
|
42
|
+
throw new Error("Embedding API response does not contain a data array.");
|
|
43
|
+
}
|
|
44
|
+
const items = payload.data;
|
|
45
|
+
if (items.length !== expectedCount) {
|
|
46
|
+
throw new Error(`Embedding API returned ${items.length} vectors for ${expectedCount} inputs.`);
|
|
47
|
+
}
|
|
48
|
+
const orderedItems = [...items].sort((left, right) => {
|
|
49
|
+
const leftIndex = typeof left.index === "number" ? left.index : items.indexOf(left);
|
|
50
|
+
const rightIndex = typeof right.index === "number" ? right.index : items.indexOf(right);
|
|
51
|
+
return leftIndex - rightIndex;
|
|
52
|
+
});
|
|
53
|
+
const embeddings = orderedItems.map((item, index) => validateEmbedding(item.embedding, index));
|
|
54
|
+
const dimensions = embeddings[0]?.length;
|
|
55
|
+
if (!dimensions || embeddings.some((embedding) => embedding.length !== dimensions)) {
|
|
56
|
+
throw new Error("Embedding API returned vectors with inconsistent dimensions.");
|
|
57
|
+
}
|
|
58
|
+
return embeddings;
|
|
59
|
+
};
|
|
60
|
+
const redactSecret = (message, secret) => {
|
|
61
|
+
if (!secret || !message.includes(secret)) {
|
|
62
|
+
return message;
|
|
63
|
+
}
|
|
64
|
+
return message.split(secret).join("[redacted]");
|
|
65
|
+
};
|
|
66
|
+
const readApiErrorMessage = async (response, secret) => {
|
|
67
|
+
try {
|
|
68
|
+
const payload = await response.json();
|
|
69
|
+
const message = payload.error?.message ?? payload.message;
|
|
70
|
+
return typeof message === "string" && message.trim() ? redactSecret(message.trim(), secret) : void 0;
|
|
71
|
+
} catch {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const looksLikeEmbeddingModel = (modelId) => /(^|[-_/])(embed|embedding|bge|e5|gte)([-_/.]|$)/i.test(modelId);
|
|
76
|
+
const listOpenAiCompatibleEmbeddingModels = async (baseUrl, options = {}) => {
|
|
77
|
+
const endpoint = buildModelsUrl(baseUrl);
|
|
78
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
79
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS;
|
|
80
|
+
const response = await fetchImpl(endpoint, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: {
|
|
83
|
+
Accept: "application/json",
|
|
84
|
+
...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}
|
|
85
|
+
},
|
|
86
|
+
redirect: "error",
|
|
87
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const apiMessage = await readApiErrorMessage(response, options.apiKey);
|
|
91
|
+
throw new Error(
|
|
92
|
+
apiMessage ? `Model discovery failed (${response.status}): ${apiMessage}` : `Model discovery failed with status ${response.status}.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const payload = await response.json();
|
|
96
|
+
if (!Array.isArray(payload.data)) {
|
|
97
|
+
throw new Error("Model discovery response does not contain a data array.");
|
|
98
|
+
}
|
|
99
|
+
const modelIds = [
|
|
100
|
+
...new Set(
|
|
101
|
+
payload.data.map((item) => {
|
|
102
|
+
if (!item || typeof item !== "object") return "";
|
|
103
|
+
const id = item.id;
|
|
104
|
+
return typeof id === "string" ? id.trim() : "";
|
|
105
|
+
}).filter(Boolean)
|
|
106
|
+
)
|
|
107
|
+
];
|
|
108
|
+
if (modelIds.length === 0) {
|
|
109
|
+
throw new Error("The API returned no models.");
|
|
110
|
+
}
|
|
111
|
+
return modelIds.map((id) => ({ id, likelyEmbedding: looksLikeEmbeddingModel(id) })).sort((left, right) => {
|
|
112
|
+
if (left.likelyEmbedding !== right.likelyEmbedding) {
|
|
113
|
+
return left.likelyEmbedding ? -1 : 1;
|
|
114
|
+
}
|
|
115
|
+
return left.id.localeCompare(right.id);
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
const buildQueryInput = (query, instruction) => {
|
|
119
|
+
const normalizedInstruction = instruction?.trim();
|
|
120
|
+
if (!normalizedInstruction) {
|
|
121
|
+
return query;
|
|
122
|
+
}
|
|
123
|
+
return `Instruct: ${normalizedInstruction}
|
|
124
|
+
Query: ${query}`;
|
|
125
|
+
};
|
|
126
|
+
const createOpenAiCompatibleEmbeddingClient = (config, options = {}) => {
|
|
127
|
+
const endpoint = buildEmbeddingsUrl(config.baseUrl);
|
|
128
|
+
const model = config.model.trim();
|
|
129
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
130
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_TIMEOUT_MS;
|
|
131
|
+
if (!model) {
|
|
132
|
+
throw new Error("Embedding model is required.");
|
|
133
|
+
}
|
|
134
|
+
const embed = async (inputs) => {
|
|
135
|
+
if (inputs.length === 0) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const response = await fetchImpl(endpoint, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: {
|
|
141
|
+
"Content-Type": "application/json",
|
|
142
|
+
...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
model,
|
|
146
|
+
input: inputs,
|
|
147
|
+
encoding_format: "float"
|
|
148
|
+
}),
|
|
149
|
+
redirect: "error",
|
|
150
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const apiMessage = await readApiErrorMessage(response, config.apiKey);
|
|
154
|
+
throw new Error(
|
|
155
|
+
apiMessage ? `Embedding API request failed (${response.status}): ${apiMessage}` : `Embedding API request failed with status ${response.status}.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return parseEmbeddingResponse(await response.json(), inputs.length);
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
embedDocuments: embed,
|
|
162
|
+
async embedQuery(query) {
|
|
163
|
+
const normalizedQuery = query.trim();
|
|
164
|
+
if (!normalizedQuery) {
|
|
165
|
+
throw new Error("Embedding query must not be empty.");
|
|
166
|
+
}
|
|
167
|
+
const [embedding] = await embed([buildQueryInput(normalizedQuery, config.queryInstruction)]);
|
|
168
|
+
return embedding;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const normalizeEmbeddingApiUrl = buildEmbeddingsUrl;
|
|
173
|
+
const normalizeEmbeddingModelsUrl = buildModelsUrl;
|
|
174
|
+
export {
|
|
175
|
+
createOpenAiCompatibleEmbeddingClient,
|
|
176
|
+
listOpenAiCompatibleEmbeddingModels,
|
|
177
|
+
normalizeEmbeddingApiUrl,
|
|
178
|
+
normalizeEmbeddingModelsUrl
|
|
179
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { searchNotesHybrid } from "../hybrid-search.js";
|
|
2
|
+
import { searchRelatedNotes } from "../related-notes.js";
|
|
3
|
+
const createSearchNotesResolver = (search = searchNotesHybrid) => {
|
|
4
|
+
return async (_, { query, pagination, mode = "HYBRID" }) => {
|
|
5
|
+
return search({
|
|
6
|
+
query,
|
|
7
|
+
limit: Math.min(50, Math.max(0, pagination.limit)),
|
|
8
|
+
offset: Math.max(0, pagination.offset),
|
|
9
|
+
mode: mode.toLowerCase()
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
const createSearchRelatedNotesResolver = (findRelatedNotes = searchRelatedNotes) => {
|
|
14
|
+
return async (_, { noteId, limit = 5 }) => {
|
|
15
|
+
const numericNoteId = Number(noteId);
|
|
16
|
+
if (!Number.isSafeInteger(numericNoteId) || numericNoteId <= 0) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return findRelatedNotes(numericNoteId, Math.min(5, Math.max(0, Number(limit))));
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const searchResolvers = {
|
|
23
|
+
Query: {
|
|
24
|
+
searchNotes: createSearchNotesResolver(),
|
|
25
|
+
searchRelatedNotes: createSearchRelatedNotesResolver()
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
createSearchNotesResolver,
|
|
30
|
+
createSearchRelatedNotesResolver,
|
|
31
|
+
searchResolvers
|
|
32
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
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 SearchRelatedNote {
|
|
16
|
+
id: ID!
|
|
17
|
+
title: String!
|
|
18
|
+
reasons: [String!]!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type SearchNotesResult {
|
|
22
|
+
totalCount: Int!
|
|
23
|
+
notes: [Note!]!
|
|
24
|
+
matches: [SearchNoteMatch!]!
|
|
25
|
+
semanticAvailable: Boolean!
|
|
26
|
+
semanticUsed: Boolean!
|
|
27
|
+
semanticError: String
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
extend type Query {
|
|
31
|
+
searchNotes(query: String!, pagination: PaginationInput!, mode: SearchMode = HYBRID): SearchNotesResult!
|
|
32
|
+
searchRelatedNotes(noteId: ID!, limit: Int = 5): [SearchRelatedNote!]!
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export {
|
|
36
|
+
searchTypeDefs
|
|
37
|
+
};
|
|
@@ -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,57 @@
|
|
|
1
|
+
const DEFAULT_LEXICAL_WEIGHT = 1;
|
|
2
|
+
const DEFAULT_SEMANTIC_WEIGHT = 1.25;
|
|
3
|
+
const DEFAULT_RANK_CONSTANT = 60;
|
|
4
|
+
const uniqueNoteIds = (noteIds) => {
|
|
5
|
+
return [...new Set(noteIds.filter((noteId) => Number.isInteger(noteId) && noteId > 0))];
|
|
6
|
+
};
|
|
7
|
+
const fuseHybridSearchRanks = ({
|
|
8
|
+
lexicalNoteIds,
|
|
9
|
+
semanticNoteIds,
|
|
10
|
+
lexicalWeight = DEFAULT_LEXICAL_WEIGHT,
|
|
11
|
+
semanticWeight = DEFAULT_SEMANTIC_WEIGHT,
|
|
12
|
+
rankConstant = DEFAULT_RANK_CONSTANT
|
|
13
|
+
}) => {
|
|
14
|
+
const lexicalIds = uniqueNoteIds(lexicalNoteIds);
|
|
15
|
+
const semanticIds = uniqueNoteIds(semanticNoteIds);
|
|
16
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
17
|
+
const addRankedIds = (noteIds, source, weight) => {
|
|
18
|
+
noteIds.forEach((noteId, index) => {
|
|
19
|
+
const rank = index + 1;
|
|
20
|
+
const candidate = candidates.get(noteId) ?? {
|
|
21
|
+
noteId,
|
|
22
|
+
lexicalRank: null,
|
|
23
|
+
semanticRank: null,
|
|
24
|
+
score: 0
|
|
25
|
+
};
|
|
26
|
+
candidate.score += weight / (rankConstant + rank);
|
|
27
|
+
if (source === "lexical") {
|
|
28
|
+
candidate.lexicalRank = rank;
|
|
29
|
+
} else {
|
|
30
|
+
candidate.semanticRank = rank;
|
|
31
|
+
}
|
|
32
|
+
candidates.set(noteId, candidate);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
addRankedIds(lexicalIds, "lexical", lexicalWeight);
|
|
36
|
+
addRankedIds(semanticIds, "semantic", semanticWeight);
|
|
37
|
+
return [...candidates.values()].sort((left, right) => {
|
|
38
|
+
if (left.score !== right.score) {
|
|
39
|
+
return right.score - left.score;
|
|
40
|
+
}
|
|
41
|
+
const leftBestRank = Math.min(left.lexicalRank ?? Number.POSITIVE_INFINITY, left.semanticRank ?? Infinity);
|
|
42
|
+
const rightBestRank = Math.min(right.lexicalRank ?? Number.POSITIVE_INFINITY, right.semanticRank ?? Infinity);
|
|
43
|
+
if (leftBestRank !== rightBestRank) {
|
|
44
|
+
return leftBestRank - rightBestRank;
|
|
45
|
+
}
|
|
46
|
+
if (left.lexicalRank !== null && right.lexicalRank === null) {
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
if (left.lexicalRank === null && right.lexicalRank !== null) {
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
return left.noteId - right.noteId;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
fuseHybridSearchRanks
|
|
57
|
+
};
|