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.
- package/README.md +2 -0
- package/dist/mcp.js +47 -75
- package/package.json +3 -2
- package/server/client/dist/assets/{ArrowRight.es-g2YZTklZ.js → ArrowRight.es-Bm-ZXpEC.js} +1 -1
- package/server/client/dist/assets/Calendar-BsNs3NzO.js +25 -0
- package/server/client/dist/assets/{Callout-B_IPSF_5.js → Callout-CviyPHKO.js} +1 -1
- package/server/client/dist/assets/{Copy.es-CNxe4ujr.js → Copy.es-BPreRnP9.js} +1 -1
- package/server/client/dist/assets/{Graph-D9GeO71k.js → Graph-vXQQ0a-Q.js} +1 -1
- package/server/client/dist/assets/Graph-yPE3ljW0.js +46 -0
- package/server/client/dist/assets/{Image.es-DnON8cv2.js → Image.es-DnR3qc_c.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-CmreVzvQ.js +25 -0
- package/server/client/dist/assets/{NotePropertiesPanel-2DgXEM4B.js → NotePropertiesPanel-DXYgE8h2.js} +2 -2
- package/server/client/dist/assets/PencilLine.es-B9V6u64t.js +1 -0
- package/server/client/dist/assets/{Plus.es-CQ8wn2is.js → Plus.es-CRRp2u5J.js} +1 -1
- package/server/client/dist/assets/Reminders-BOtfNeYD.js +1 -0
- package/server/client/dist/assets/Search-C-FB3pKf.js +1 -0
- package/server/client/dist/assets/SurfaceCard-GMt5xM7v.js +1 -0
- package/server/client/dist/assets/Tag-lBxDRE77.js +1 -0
- package/server/client/dist/assets/TagNotes-Bgorp_rC.js +1 -0
- package/server/client/dist/assets/{Trash.es-0aPpA-3S.js → Trash.es-svHOb5LN.js} +1 -1
- package/server/client/dist/assets/ViewNotes-C7d10VFf.js +1 -0
- package/server/client/dist/assets/ViewSectionDialog-Bhw-ILop.js +102 -0
- package/server/client/dist/assets/Views-C7V3saUo.js +1 -0
- package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
- package/server/client/dist/assets/app-DQBV36r_.js +50 -0
- package/server/client/dist/assets/app-ZgwR5t_I.css +1 -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-QmHYjp3z.js +17 -0
- package/server/client/dist/assets/index-CaF9d-zG.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-DCDF8AM9.js +1 -0
- package/server/client/dist/assets/manage-image-detail-e0ViRAj-.js +1 -0
- package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
- package/server/client/dist/assets/mcp-admin-adapter-D39G3udJ.js +1 -0
- package/server/client/dist/assets/{mcp-CT_9IV2c.js → mcp-uFZkdhCi.js} +4 -4
- 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-CxK8ymUc.js} +1 -1
- package/server/client/dist/assets/{note.api-D_00ZzJC.js → note.api-BQovzEzd.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-D3b8lZGE.js} +4 -4
- package/server/client/dist/assets/properties-M-yWq1D7.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-DFnzZKLd.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-DUfjr8bg.js} +3 -3
- package/server/client/dist/assets/time-CfAYZipR.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-CClKfP1x.js +1 -0
- package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-CLK1tHvT.js → useReminderMutate--omDLmBV.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.mutation.resolver.js +38 -12
- package/server/dist/features/note/graphql/note.type-defs.js +1 -1
- package/server/dist/features/note/http/mcp.js +1 -60
- package/server/dist/features/note/services/authoring.js +2 -70
- package/server/dist/features/note/services/markdown-patch.js +80 -8
- 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/snapshot.js +79 -59
- package/server/dist/features/note/services/trash.js +16 -31
- 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 +20 -0
- package/server/dist/features/search/graphql/type-defs.js +30 -0
- package/server/dist/features/search/http/handlers.js +108 -0
- package/server/dist/features/search/hybrid-ranking.js +55 -0
- package/server/dist/features/search/hybrid-search.js +174 -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/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 +2 -0
- 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/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/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/mcp-admin-adapter-xAuyZc_M.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
|
@@ -21,6 +21,66 @@ const getLineRange = (markdown, match) => {
|
|
|
21
21
|
lineEnd
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
+
const MARKDOWN_PATCH_WHITESPACE_PATTERN = /\p{Zs}| |&#(?:x[0-9a-f]+|\d+);/giu;
|
|
25
|
+
const SPACE_SEPARATOR_PATTERN = /^\p{Zs}$/u;
|
|
26
|
+
const normalizePatchWhitespaceToken = (value) => {
|
|
27
|
+
if (!value.startsWith("&")) {
|
|
28
|
+
return " ";
|
|
29
|
+
}
|
|
30
|
+
if (/^ $/i.test(value)) {
|
|
31
|
+
return " ";
|
|
32
|
+
}
|
|
33
|
+
const numericValue = value.slice(2, -1);
|
|
34
|
+
const isHexadecimal = numericValue.toLowerCase().startsWith("x");
|
|
35
|
+
const codePoint = Number.parseInt(isHexadecimal ? numericValue.slice(1) : numericValue, isHexadecimal ? 16 : 10);
|
|
36
|
+
if (!Number.isSafeInteger(codePoint) || codePoint < 0 || codePoint > 1114111) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return SPACE_SEPARATOR_PATTERN.test(String.fromCodePoint(codePoint)) ? " " : value;
|
|
40
|
+
};
|
|
41
|
+
const normalizeMarkdownPatchText = (value) => {
|
|
42
|
+
let text = "";
|
|
43
|
+
const rawStarts = [];
|
|
44
|
+
const rawEnds = [];
|
|
45
|
+
const appendText = (appendValue, rawStart, rawEnd) => {
|
|
46
|
+
text += appendValue;
|
|
47
|
+
for (let index = 0; index < appendValue.length; index += 1) {
|
|
48
|
+
rawStarts.push(rawStart);
|
|
49
|
+
rawEnds.push(rawEnd);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const appendRawRange = (start, end) => {
|
|
53
|
+
for (let index = start; index < end; ) {
|
|
54
|
+
const codePoint = value.codePointAt(index);
|
|
55
|
+
if (codePoint === void 0) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const character = String.fromCodePoint(codePoint);
|
|
59
|
+
appendText(character, index, index + character.length);
|
|
60
|
+
index += character.length;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
let sourceCursor = 0;
|
|
64
|
+
for (const match of value.matchAll(MARKDOWN_PATCH_WHITESPACE_PATTERN)) {
|
|
65
|
+
const start = match.index;
|
|
66
|
+
const token = match[0];
|
|
67
|
+
const end = start + token.length;
|
|
68
|
+
appendRawRange(sourceCursor, start);
|
|
69
|
+
const normalizedToken = normalizePatchWhitespaceToken(token);
|
|
70
|
+
if (normalizedToken === token) {
|
|
71
|
+
appendRawRange(start, end);
|
|
72
|
+
} else {
|
|
73
|
+
appendText(normalizedToken, start, end);
|
|
74
|
+
}
|
|
75
|
+
sourceCursor = end;
|
|
76
|
+
}
|
|
77
|
+
appendRawRange(sourceCursor, value.length);
|
|
78
|
+
return {
|
|
79
|
+
text,
|
|
80
|
+
rawStarts,
|
|
81
|
+
rawEnds
|
|
82
|
+
};
|
|
83
|
+
};
|
|
24
84
|
const buildExcerpt = (markdown, start, end) => {
|
|
25
85
|
const excerpt = markdown.slice(start, end).trim();
|
|
26
86
|
return excerpt.length > 0 ? excerpt : void 0;
|
|
@@ -46,19 +106,27 @@ const toPatchMatch = (markdown, match, totalMatches) => {
|
|
|
46
106
|
};
|
|
47
107
|
const findExactTextMatches = (markdown, text) => {
|
|
48
108
|
const matches = [];
|
|
109
|
+
const normalizedMarkdown = normalizeMarkdownPatchText(markdown);
|
|
110
|
+
const normalizedText = normalizeMarkdownPatchText(text).text;
|
|
49
111
|
let searchStart = 0;
|
|
50
|
-
while (searchStart <=
|
|
51
|
-
const start =
|
|
112
|
+
while (searchStart <= normalizedMarkdown.text.length) {
|
|
113
|
+
const start = normalizedMarkdown.text.indexOf(normalizedText, searchStart);
|
|
52
114
|
if (start === -1) {
|
|
53
115
|
break;
|
|
54
116
|
}
|
|
117
|
+
const end = start + normalizedText.length;
|
|
118
|
+
const rawStart = normalizedMarkdown.rawStarts[start];
|
|
119
|
+
const rawEnd = normalizedMarkdown.rawEnds[end - 1];
|
|
120
|
+
if (rawStart === void 0 || rawEnd === void 0) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
55
123
|
matches.push({
|
|
56
124
|
matchIndex: matches.length,
|
|
57
|
-
start,
|
|
58
|
-
end:
|
|
59
|
-
text
|
|
125
|
+
start: rawStart,
|
|
126
|
+
end: rawEnd,
|
|
127
|
+
text: markdown.slice(rawStart, rawEnd)
|
|
60
128
|
});
|
|
61
|
-
searchStart =
|
|
129
|
+
searchStart = end;
|
|
62
130
|
}
|
|
63
131
|
return matches;
|
|
64
132
|
};
|
|
@@ -238,10 +306,14 @@ const createChangePlan = ({
|
|
|
238
306
|
};
|
|
239
307
|
};
|
|
240
308
|
const matchHasAnchors = (markdown, match, selector) => {
|
|
241
|
-
if (selector.before !== void 0 && !markdown.slice(0, match.start).endsWith(
|
|
309
|
+
if (selector.before !== void 0 && !normalizeMarkdownPatchText(markdown.slice(0, match.start)).text.endsWith(
|
|
310
|
+
normalizeMarkdownPatchText(selector.before).text
|
|
311
|
+
)) {
|
|
242
312
|
return false;
|
|
243
313
|
}
|
|
244
|
-
if (selector.after !== void 0 && !markdown.slice(match.end).startsWith(
|
|
314
|
+
if (selector.after !== void 0 && !normalizeMarkdownPatchText(markdown.slice(match.end)).text.startsWith(
|
|
315
|
+
normalizeMarkdownPatchText(selector.after).text
|
|
316
|
+
)) {
|
|
245
317
|
return false;
|
|
246
318
|
}
|
|
247
319
|
return true;
|
|
@@ -329,6 +329,13 @@ const findReferencedRemovedPropertyOptionValue = ({
|
|
|
329
329
|
}
|
|
330
330
|
return null;
|
|
331
331
|
};
|
|
332
|
+
const viewQueryReferencesProperty = ({ query, key }) => {
|
|
333
|
+
const parsed = parseViewQueryRecord(query);
|
|
334
|
+
if (!parsed || !Array.isArray(parsed.propertyFilters)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return parsed.propertyFilters.some((filter) => isRecord(filter) && getNormalizedViewFilterKey(filter) === key);
|
|
338
|
+
};
|
|
332
339
|
const buildTypedValueData = async (input, definitionId, tx = models) => {
|
|
333
340
|
const resetValues = {
|
|
334
341
|
textValue: null,
|
|
@@ -695,6 +702,18 @@ const deleteNotePropertyDefinition = async ({
|
|
|
695
702
|
if (!definition) {
|
|
696
703
|
return null;
|
|
697
704
|
}
|
|
705
|
+
const referencingViewSections = await tx.viewSection.findMany({
|
|
706
|
+
where: { query: { contains: definition.key } },
|
|
707
|
+
select: { title: true, query: true }
|
|
708
|
+
});
|
|
709
|
+
const referencingView = referencingViewSections.find(
|
|
710
|
+
(section) => viewQueryReferencesProperty({ query: section.query, key: definition.key })
|
|
711
|
+
);
|
|
712
|
+
if (referencingView) {
|
|
713
|
+
throw new InvalidNotePropertyInputError(
|
|
714
|
+
`Property ${definition.key} is used by view ${referencingView.title} and cannot be deleted.`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
698
717
|
const affectedNoteCount = definition._count.properties;
|
|
699
718
|
if (affectedNoteCount > 0 && !confirmImpact) {
|
|
700
719
|
throw new NotePropertyDeleteConfirmationRequiredError(normalizedKey, affectedNoteCount);
|
|
@@ -869,5 +888,6 @@ export {
|
|
|
869
888
|
updateNotePropertiesWithVersionGuard,
|
|
870
889
|
updateNotePropertiesWithVersionGuardAndSnapshot,
|
|
871
890
|
updateNotePropertyDefinition,
|
|
872
|
-
validateNotePropertiesPatchValues
|
|
891
|
+
validateNotePropertiesPatchValues,
|
|
892
|
+
viewQueryReferencesProperty
|
|
873
893
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const resolveRestoredPropertyTarget = async (property, deps) => {
|
|
2
|
+
const definition = await deps.findDefinitionByKey(property.key);
|
|
3
|
+
if (!definition || definition.valueType !== property.valueType) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
if (property.valueType !== "select") {
|
|
7
|
+
return {
|
|
8
|
+
propertyDefinitionId: definition.id,
|
|
9
|
+
optionId: null
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (!property.optionValue) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const option = await deps.findOptionByValue(definition.id, property.optionValue);
|
|
16
|
+
if (!option) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
propertyDefinitionId: definition.id,
|
|
21
|
+
optionId: option.id
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export {
|
|
25
|
+
resolveRestoredPropertyTarget
|
|
26
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FILE_HEADERS_ONLY, formatPatch, structuredPatch } from "diff";
|
|
2
2
|
import { ensureTagByName } from "../../../features/tag/services/organization.js";
|
|
3
|
-
import models from "../../../models.js";
|
|
3
|
+
import models, { Prisma } from "../../../models.js";
|
|
4
4
|
import { blocksToMarkdown, extractTagIdsFromContentJson } from "../../../modules/blocknote.js";
|
|
5
5
|
import { parseBlockNoteContent, walkBlockNoteTree } from "../../../modules/blocknote-tree.js";
|
|
6
6
|
import {
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
SNAPSHOT_RETENTION_DAYS
|
|
11
11
|
} from "../../../modules/recovery-retention.js";
|
|
12
12
|
import { calculateMarkdownSha256 } from "./markdown-patch.js";
|
|
13
|
+
import { resolveRestoredPropertyTarget } from "./property-restore.js";
|
|
13
14
|
import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
|
|
15
|
+
import { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from "./write-conflict.js";
|
|
14
16
|
const SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;
|
|
15
17
|
const parseMeta = (value) => {
|
|
16
18
|
if (!value) {
|
|
@@ -384,6 +386,19 @@ const createNoteSnapshotService = (deps) => ({
|
|
|
384
386
|
if (!note) {
|
|
385
387
|
return null;
|
|
386
388
|
}
|
|
389
|
+
if (!note.updatedAt) {
|
|
390
|
+
throw new Error("Note update time is required to restore a snapshot.");
|
|
391
|
+
}
|
|
392
|
+
const expectedTimestamp = parseNoteVersion(options.expectedUpdatedAt);
|
|
393
|
+
if (expectedTimestamp === null) {
|
|
394
|
+
throw new MissingNoteVersionError();
|
|
395
|
+
}
|
|
396
|
+
if (note.updatedAt.getTime() !== expectedTimestamp) {
|
|
397
|
+
throw createNoteVersionConflictError({
|
|
398
|
+
expectedUpdatedAt: expectedTimestamp,
|
|
399
|
+
currentUpdatedAt: note.updatedAt.getTime()
|
|
400
|
+
});
|
|
401
|
+
}
|
|
387
402
|
const payload = parsePayload(snapshot.payload);
|
|
388
403
|
const resolvedTags = deps.resolveRestoredTags ? await deps.resolveRestoredTags(payload.content) : extractRestoredTagIds(payload.content);
|
|
389
404
|
const restoredPayload = resolvedTags && !Array.isArray(resolvedTags) ? {
|
|
@@ -403,7 +418,8 @@ const createNoteSnapshotService = (deps) => ({
|
|
|
403
418
|
}
|
|
404
419
|
const restoredNote = await deps.updateNote(snapshot.noteId, {
|
|
405
420
|
...restoredPayload,
|
|
406
|
-
...restoredTagIds !== void 0 ? { tagIds: restoredTagIds } : {}
|
|
421
|
+
...restoredTagIds !== void 0 ? { tagIds: restoredTagIds } : {},
|
|
422
|
+
expectedUpdatedAt: options.expectedUpdatedAt
|
|
407
423
|
});
|
|
408
424
|
await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);
|
|
409
425
|
return restoredNote;
|
|
@@ -527,68 +543,72 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
|
|
|
527
543
|
});
|
|
528
544
|
},
|
|
529
545
|
updateNote: async (id, input) => {
|
|
530
|
-
const { tagIds, properties, ...noteInput } = input;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
546
|
+
const { tagIds, properties, expectedUpdatedAt, ...noteInput } = input;
|
|
547
|
+
const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);
|
|
548
|
+
if (expectedTimestamp === null) {
|
|
549
|
+
throw new MissingNoteVersionError();
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
return await models.$transaction(async (tx) => {
|
|
553
|
+
const note = await tx.note.update({
|
|
554
|
+
where: { id, updatedAt: new Date(expectedTimestamp) },
|
|
555
|
+
data: {
|
|
556
|
+
...noteInput,
|
|
557
|
+
...buildNoteSearchProjection({
|
|
558
|
+
title: noteInput.title,
|
|
559
|
+
content: noteInput.content
|
|
560
|
+
}),
|
|
561
|
+
...tagIds !== void 0 ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
if (properties !== void 0) {
|
|
565
|
+
await tx.noteProperty.deleteMany({ where: { noteId: id } });
|
|
566
|
+
for (const property of properties) {
|
|
567
|
+
const target = await resolveRestoredPropertyTarget(property, {
|
|
568
|
+
findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
|
|
569
|
+
findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
|
|
570
|
+
where: {
|
|
571
|
+
propertyDefinitionId_value: {
|
|
572
|
+
propertyDefinitionId,
|
|
573
|
+
value
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
})
|
|
577
|
+
});
|
|
578
|
+
if (!target) {
|
|
579
|
+
continue;
|
|
556
580
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
581
|
+
await tx.noteProperty.create({
|
|
582
|
+
data: {
|
|
583
|
+
noteId: id,
|
|
584
|
+
propertyDefinitionId: target.propertyDefinitionId,
|
|
585
|
+
optionId: target.optionId,
|
|
586
|
+
textValue: property.textValue ?? null,
|
|
587
|
+
textValueNormalized: property.textValueNormalized ?? null,
|
|
588
|
+
numberValue: property.numberValue ?? null,
|
|
589
|
+
dateValue: property.dateValue ? new Date(property.dateValue) : null,
|
|
590
|
+
boolValue: property.boolValue ?? null
|
|
563
591
|
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
propertyDefinitionId: definition.id,
|
|
580
|
-
optionId: option?.id ?? null,
|
|
581
|
-
textValue: property.textValue ?? null,
|
|
582
|
-
textValueNormalized: property.textValueNormalized ?? null,
|
|
583
|
-
numberValue: property.numberValue ?? null,
|
|
584
|
-
dateValue: property.dateValue ? new Date(property.dateValue) : null,
|
|
585
|
-
boolValue: property.boolValue ?? null
|
|
586
|
-
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return note;
|
|
596
|
+
});
|
|
597
|
+
} catch (error) {
|
|
598
|
+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025") {
|
|
599
|
+
const currentNote = await models.note.findUnique({
|
|
600
|
+
where: { id },
|
|
601
|
+
select: { updatedAt: true }
|
|
602
|
+
});
|
|
603
|
+
if (currentNote && currentNote.updatedAt.getTime() !== expectedTimestamp) {
|
|
604
|
+
throw createNoteVersionConflictError({
|
|
605
|
+
expectedUpdatedAt: expectedTimestamp,
|
|
606
|
+
currentUpdatedAt: currentNote.updatedAt.getTime()
|
|
587
607
|
});
|
|
588
608
|
}
|
|
589
609
|
}
|
|
590
|
-
|
|
591
|
-
}
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
592
612
|
}
|
|
593
613
|
});
|
|
594
614
|
const captureNoteBaseline = async (input) => {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
RECOVERY_CLEANUP_BATCH_LIMIT,
|
|
5
5
|
TRASH_RETENTION_DAYS
|
|
6
6
|
} from "../../../modules/recovery-retention.js";
|
|
7
|
+
import { resolveRestoredPropertyTarget } from "./property-restore.js";
|
|
7
8
|
import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
|
|
8
9
|
const TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;
|
|
9
10
|
class NoteRestoreConflictError extends Error {
|
|
@@ -289,41 +290,25 @@ const noteTrashService = createNoteTrashService({
|
|
|
289
290
|
}
|
|
290
291
|
});
|
|
291
292
|
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
|
|
293
|
+
const target = await resolveRestoredPropertyTarget(property, {
|
|
294
|
+
findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
|
|
295
|
+
findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
|
|
296
|
+
where: {
|
|
297
|
+
propertyDefinitionId_value: {
|
|
298
|
+
propertyDefinitionId,
|
|
299
|
+
value
|
|
300
|
+
}
|
|
309
301
|
}
|
|
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;
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
if (!target) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
322
307
|
await tx.noteProperty.create({
|
|
323
308
|
data: {
|
|
324
309
|
noteId: note.id,
|
|
325
|
-
propertyDefinitionId:
|
|
326
|
-
optionId:
|
|
310
|
+
propertyDefinitionId: target.propertyDefinitionId,
|
|
311
|
+
optionId: target.optionId,
|
|
327
312
|
textValue: property.textValue,
|
|
328
313
|
textValueNormalized: property.textValueNormalized,
|
|
329
314
|
numberValue: property.numberValue,
|
|
@@ -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
|
+
};
|