diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index 89d5b168d7..c04a19b1af 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -22,6 +22,8 @@ export const codebaseIndexConfigSchema = z.object({ codebaseIndexEnabled: z.boolean().optional(), codebaseIndexQdrantUrl: z.string().optional(), codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral"]).optional(), + codebaseIndexVectorStoreProvider: z.enum(["local", "qdrant"]).optional(), + codebaseIndexLocalVectorStoreDirectory: z.string().optional(), codebaseIndexEmbedderBaseUrl: z.string().optional(), codebaseIndexEmbedderModelId: z.string().optional(), codebaseIndexEmbedderModelDimension: z.number().optional(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e7bb79b64..15830e366c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -578,6 +578,9 @@ importers: '@google/genai': specifier: ^1.0.0 version: 1.3.0(@modelcontextprotocol/sdk@1.12.0) + '@lancedb/lancedb': + specifier: ^0.21.2 + version: 0.21.2(apache-arrow@18.1.0) '@lmstudio/sdk': specifier: ^1.1.1 version: 1.2.0 @@ -685,7 +688,7 @@ importers: version: 12.0.0 openai: specifier: ^5.0.0 - version: 5.5.1(ws@8.18.2)(zod@3.25.61) + version: 5.5.1(ws@8.18.3)(zod@3.25.61) os-name: specifier: ^6.0.0 version: 6.1.0 @@ -805,8 +808,8 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: 20.x - version: 20.17.50 + specifier: ~22.15.29 + version: 22.15.29 '@types/node-cache': specifier: ^4.1.3 version: 4.2.5 @@ -875,7 +878,7 @@ importers: version: 5.8.3 vitest: specifier: ^3.2.3 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) zod-to-ts: specifier: ^1.2.0 version: 1.2.0(typescript@5.8.3)(zod@3.25.61) @@ -2005,6 +2008,62 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@lancedb/lancedb-darwin-arm64@0.21.2': + resolution: {integrity: sha512-aCRc/Jrpll7AMQejK18HzNmYtEzV+oU0zTSFhYJTu1SqDTA+CoXoibj9SmXHYwY6MYlfx5y6kI8UkjtU3T8zHQ==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] + + '@lancedb/lancedb-darwin-x64@0.21.2': + resolution: {integrity: sha512-Oiiz1W9E2VwKpmPCrRF+40Hd6M1kj/ej6FNQwXaCy688+CJC0dKlz95omQU3B2WnynDiwszUyNVknyMwNI3sCQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [darwin] + + '@lancedb/lancedb-linux-arm64-gnu@0.21.2': + resolution: {integrity: sha512-LyLlEYUDlpm2sSMzoqmVWIYbY6U5AVZhpZucX0L6a3usyuce6V/Z9UGR6Uw9284xZKsPFnYNRlLZwwUGkb2tKw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + + '@lancedb/lancedb-linux-arm64-musl@0.21.2': + resolution: {integrity: sha512-J7fK1gzBrrBx+CnUk+BnvU3V/A+F7EfqveOw2SUbTP7Gy5bXdUL2sieVVuJlRmmhfvjLTAGxsITr2RkO9n3wZw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + + '@lancedb/lancedb-linux-x64-gnu@0.21.2': + resolution: {integrity: sha512-yD1lWUe/gLR4QFA3ZdWc2ukdgbfXDcRoi9iTgqedUwAj++Wld64wOFjYhZbYwzGNClXlkJJiP70jzhomBv3RIw==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@lancedb/lancedb-linux-x64-musl@0.21.2': + resolution: {integrity: sha512-Dq3G6KtGlxidS3mY4cebp7GHRT7rQoe6wpXTzlCankANvEOwxBI68wxid3+GDcqi4jkGHrOrHTQVZqIKZ2NXnQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@lancedb/lancedb-win32-arm64-msvc@0.21.2': + resolution: {integrity: sha512-cPxzm5G2LhiA+DjVGueEnc7K909gzH6q+UGeNXF5Ly4IsvE6BRrjSQNmTRBGflD/aQEV7VD4SqiIj/rpmIZ4Sg==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [win32] + + '@lancedb/lancedb-win32-x64-msvc@0.21.2': + resolution: {integrity: sha512-jewJB5ggDi4jlZocmmjMnwQYDHHr+t0jhiZQINRs/I4tkt9RxaRVPPz7D4rP+NXILj4PgKfDqKcDVeJ/5bc4hQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] + + '@lancedb/lancedb@0.21.2': + resolution: {integrity: sha512-3RPsGbdhFbwgy7GisQnlgf+5r6R73N4xIkPNISJfvratJ5kVoQIuswra/jJxAf8N5tKriD9UnrQyUsRbmblwFQ==} + engines: {node: '>= 18'} + cpu: [x64, arm64] + os: [darwin, linux, win32] + peerDependencies: + apache-arrow: '>=15.0.0 <=18.1.0' + '@libsql/client@0.15.8': resolution: {integrity: sha512-TskygwF+ToZeWhPPT0WennyGrP3tmkKraaKopT2YwUjqD6DWDRm6SG5iy0VqnaO+HC9FNBCDX0oQPODU3gqqPQ==} @@ -3693,6 +3752,12 @@ packages: '@types/clone-deep@4.0.4': resolution: {integrity: sha512-vXh6JuuaAha6sqEbJueYdh5zNBPPgG1OYumuz2UvLvriN6ABHDSW8ludREGWJb1MLIzbwZn4q4zUbUCerJTJfA==} + '@types/command-line-args@5.2.3': + resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} + + '@types/command-line-usage@5.0.4': + resolution: {integrity: sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==} + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -3877,15 +3942,9 @@ packages: '@types/node@18.19.100': resolution: {integrity: sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==} - '@types/node@20.17.50': - resolution: {integrity: sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==} - '@types/node@20.17.57': resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==} - '@types/node@20.19.1': - resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==} - '@types/node@20.19.4': resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==} @@ -4215,6 +4274,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + apache-arrow@18.1.0: + resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==} + hasBin: true + aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -4250,6 +4313,14 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4513,6 +4584,10 @@ packages: chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -4692,6 +4767,14 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@7.0.3: + resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} + engines: {node: '>=12.20.0'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -5771,6 +5854,10 @@ packages: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -5790,6 +5877,9 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true + flatbuffers@24.12.23: + resolution: {integrity: sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==} + flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -6639,6 +6729,10 @@ packages: json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-bignum@0.0.3: + resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==} + engines: {node: '>=0.8'} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -6922,6 +7016,9 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} @@ -8273,6 +8370,9 @@ packages: resolution: {integrity: sha512-x7vpciikEY7nptGzQrE5I+/pvwFZJDadPk/uEoyGSg/pZ2m/CX2n5EhSgUh+S5T7Gz3uKM6YzWcXEu3ioAsdFQ==} engines: {node: '>= 18'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -8888,6 +8988,10 @@ packages: tabbable@5.3.3: resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==} + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + tailwind-merge@3.3.0: resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} @@ -9193,6 +9297,14 @@ packages: engines: {node: '>=14.17'} hasBin: true + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -9625,6 +9737,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrapjs@5.1.0: + resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==} + engines: {node: '>=12.17'} + workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} @@ -11104,7 +11220,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.57 + '@types/node': 22.15.29 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -11133,6 +11249,44 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@lancedb/lancedb-darwin-arm64@0.21.2': + optional: true + + '@lancedb/lancedb-darwin-x64@0.21.2': + optional: true + + '@lancedb/lancedb-linux-arm64-gnu@0.21.2': + optional: true + + '@lancedb/lancedb-linux-arm64-musl@0.21.2': + optional: true + + '@lancedb/lancedb-linux-x64-gnu@0.21.2': + optional: true + + '@lancedb/lancedb-linux-x64-musl@0.21.2': + optional: true + + '@lancedb/lancedb-win32-arm64-msvc@0.21.2': + optional: true + + '@lancedb/lancedb-win32-x64-msvc@0.21.2': + optional: true + + '@lancedb/lancedb@0.21.2(apache-arrow@18.1.0)': + dependencies: + apache-arrow: 18.1.0 + reflect-metadata: 0.2.2 + optionalDependencies: + '@lancedb/lancedb-darwin-arm64': 0.21.2 + '@lancedb/lancedb-darwin-x64': 0.21.2 + '@lancedb/lancedb-linux-arm64-gnu': 0.21.2 + '@lancedb/lancedb-linux-arm64-musl': 0.21.2 + '@lancedb/lancedb-linux-x64-gnu': 0.21.2 + '@lancedb/lancedb-linux-x64-musl': 0.21.2 + '@lancedb/lancedb-win32-arm64-msvc': 0.21.2 + '@lancedb/lancedb-win32-x64-msvc': 0.21.2 + '@libsql/client@0.15.8': dependencies: '@libsql/core': 0.15.9 @@ -12944,6 +13098,10 @@ snapshots: '@types/clone-deep@4.0.4': {} + '@types/command-line-args@5.2.3': {} + + '@types/command-line-usage@5.0.4': {} + '@types/d3-array@3.2.1': {} '@types/d3-axis@3.0.6': @@ -13084,7 +13242,7 @@ snapshots: '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.17.57 + '@types/node': 22.15.29 '@types/hast@3.0.4': dependencies: @@ -13137,7 +13295,7 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.17.57 + '@types/node': 22.15.29 form-data: 4.0.4 '@types/node-ipc@9.2.3': @@ -13152,22 +13310,13 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.17.50': - dependencies: - undici-types: 6.19.8 - '@types/node@20.17.57': dependencies: undici-types: 6.19.8 - '@types/node@20.19.1': - dependencies: - undici-types: 6.21.0 - '@types/node@20.19.4': dependencies: undici-types: 6.21.0 - optional: true '@types/node@22.15.29': dependencies: @@ -13202,11 +13351,11 @@ snapshots: '@types/stream-chain@2.1.0': dependencies: - '@types/node': 20.19.1 + '@types/node': 22.15.29 '@types/stream-json@1.7.8': dependencies: - '@types/node': 20.19.1 + '@types/node': 22.15.29 '@types/stream-chain': 2.1.0 '@types/string-similarity@4.0.2': {} @@ -13232,7 +13381,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.4 + '@types/node': 22.15.29 optional: true '@types/yargs-parser@21.0.3': {} @@ -13243,7 +13392,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.17.50 + '@types/node': 22.15.29 optional: true '@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)': @@ -13352,14 +13501,6 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 @@ -13405,7 +13546,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: @@ -13583,6 +13724,18 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + apache-arrow@18.1.0: + dependencies: + '@swc/helpers': 0.5.15 + '@types/command-line-args': 5.2.3 + '@types/command-line-usage': 5.0.4 + '@types/node': 20.19.4 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + flatbuffers: 24.12.23 + json-bignum: 0.0.3 + tslib: 2.8.1 + aproba@2.0.0: {} archiver-utils@2.1.0: @@ -13639,6 +13792,10 @@ snapshots: aria-query@5.3.2: {} + array-back@3.1.0: {} + + array-back@6.2.2: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -13934,6 +14091,10 @@ snapshots: dependencies: traverse: 0.3.9 + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -14141,6 +14302,20 @@ snapshots: comma-separated-tokens@2.0.3: {} + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@7.0.3: + dependencies: + array-back: 6.2.2 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + commander@11.1.0: {} commander@12.1.0: {} @@ -15344,6 +15519,10 @@ snapshots: transitivePeerDependencies: - supports-color + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -15367,6 +15546,8 @@ snapshots: flat@5.0.2: {} + flatbuffers@24.12.23: {} + flatted@3.3.3: {} follow-redirects@1.15.9: {} @@ -16219,7 +16400,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.57 + '@types/node': 22.15.29 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -16290,6 +16471,8 @@ snapshots: dependencies: bignumber.js: 9.3.0 + json-bignum@0.0.3: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@4.0.0: {} @@ -16588,6 +16771,8 @@ snapshots: lodash-es@4.17.21: {} + lodash.camelcase@4.3.0: {} + lodash.castarray@4.4.0: {} lodash.debounce@4.0.8: {} @@ -17520,9 +17705,9 @@ snapshots: is-inside-container: 1.0.0 is-wsl: 3.1.0 - openai@5.5.1(ws@8.18.2)(zod@3.25.61): + openai@5.5.1(ws@8.18.3)(zod@3.25.61): optionalDependencies: - ws: 8.18.2 + ws: 8.18.3 zod: 3.25.61 option@0.2.4: {} @@ -18280,6 +18465,8 @@ snapshots: '@redis/search': 5.5.5(@redis/client@5.5.5) '@redis/time-series': 5.5.5(@redis/client@5.5.5) + reflect-metadata@0.2.2: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -19035,6 +19222,11 @@ snapshots: tabbable@5.3.3: {} + table-layout@4.1.1: + dependencies: + array-back: 6.2.2 + wordwrapjs: 5.1.0 + tailwind-merge@3.3.0: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.17): @@ -19372,6 +19564,10 @@ snapshots: typescript@5.8.3: {} + typical@4.0.0: {} + + typical@7.3.0: {} + uc.micro@2.1.0: {} ufo@1.6.1: {} @@ -19627,27 +19823,6 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -19690,22 +19865,6 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - esbuild: 0.25.5 - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.4 - rollup: 4.40.2 - tinyglobby: 0.2.13 - optionalDependencies: - '@types/node': 20.17.50 - fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.30.1 - tsx: 4.19.4 - yaml: 2.8.0 - vite@6.3.5(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.5 @@ -19738,50 +19897,6 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 - debug: 4.4.1(supports-color@8.1.1) - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 20.17.50 - '@vitest/ui': 3.2.4(vitest@3.2.4) - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -20013,6 +20128,8 @@ snapshots: word-wrap@1.2.5: {} + wordwrapjs@5.1.0: {} + workerpool@6.5.1: {} workerpool@9.2.0: {} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 99c2a514b2..58ee871e14 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1645,6 +1645,8 @@ export class ClineProvider codebaseIndexConfig: { codebaseIndexEnabled: codebaseIndexConfig?.codebaseIndexEnabled ?? true, codebaseIndexQdrantUrl: codebaseIndexConfig?.codebaseIndexQdrantUrl ?? "http://localhost:6333", + codebaseIndexVectorStoreProvider: codebaseIndexConfig?.codebaseIndexVectorStoreProvider ?? "qdrant", + codebaseIndexLocalVectorStoreDirectory: codebaseIndexConfig?.codebaseIndexLocalVectorStoreDirectory, codebaseIndexEmbedderProvider: codebaseIndexConfig?.codebaseIndexEmbedderProvider ?? "openai", codebaseIndexEmbedderBaseUrl: codebaseIndexConfig?.codebaseIndexEmbedderBaseUrl ?? "", codebaseIndexEmbedderModelId: codebaseIndexConfig?.codebaseIndexEmbedderModelId ?? "", @@ -1834,6 +1836,10 @@ export class ClineProvider stateValues.codebaseIndexConfig?.codebaseIndexQdrantUrl ?? "http://localhost:6333", codebaseIndexEmbedderProvider: stateValues.codebaseIndexConfig?.codebaseIndexEmbedderProvider ?? "openai", + codebaseIndexVectorStoreProvider: + stateValues.codebaseIndexConfig?.codebaseIndexVectorStoreProvider ?? "qdrant", + codebaseIndexLocalVectorStoreDirectory: + stateValues.codebaseIndexConfig?.codebaseIndexLocalVectorStoreDirectory, codebaseIndexEmbedderBaseUrl: stateValues.codebaseIndexConfig?.codebaseIndexEmbedderBaseUrl ?? "", codebaseIndexEmbedderModelId: stateValues.codebaseIndexConfig?.codebaseIndexEmbedderModelId ?? "", codebaseIndexEmbedderModelDimension: diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index fdb7e90425..ab3d5d56ea 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2010,6 +2010,8 @@ export const webviewMessageHandler = async ( codebaseIndexEnabled: settings.codebaseIndexEnabled, codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl, codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider, + codebaseIndexVectorStoreProvider: settings.codebaseIndexVectorStoreProvider, + codebaseIndexLocalVectorStoreDirectory: settings.codebaseIndexLocalVectorStoreDirectory, codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl, codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId, codebaseIndexEmbedderModelDimension: settings.codebaseIndexEmbedderModelDimension, // Generic dimension diff --git a/src/esbuild.mjs b/src/esbuild.mjs index f99b077e9f..6dd0f55ac4 100644 --- a/src/esbuild.mjs +++ b/src/esbuild.mjs @@ -100,7 +100,7 @@ async function main() { plugins, entryPoints: ["extension.ts"], outfile: "dist/extension.js", - external: ["vscode"], + external: ["vscode", "@lancedb/lancedb"], } /** diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 35ddca3b10..06f300040e 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}", - "vectorDimensionMismatch": "No s'ha pogut actualitzar l'índex de vectors per al nou model. Prova d'esborrar l'índex i tornar a començar. Detalls: {{errorMessage}}" + "vectorDimensionMismatch": "No s'ha pogut actualitzar l'índex de vectors per al nou model. Prova d'esborrar l'índex i tornar a començar. Detalls: {{errorMessage}}", + "localStoreInitFailed": "No s'ha pogut inicialitzar l'emmagatzematge vectorial local: {{errorMessage}}", + "lancedbLoadFailed": "No s'ha pogut carregar el mòdul LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Ha fallat l'autenticació. Comproveu la vostra clau d'API a la configuració.", diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index f5aa7339ef..e60aaab67b 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}", - "vectorDimensionMismatch": "Aktualisierung des Vektorindex für neues Modell fehlgeschlagen. Bitte versuche, den Index zu löschen und von vorne zu beginnen. Details: {{errorMessage}}" + "vectorDimensionMismatch": "Aktualisierung des Vektorindex für neues Modell fehlgeschlagen. Bitte versuche, den Index zu löschen und von vorne zu beginnen. Details: {{errorMessage}}", + "localStoreInitFailed": "Lokaler Vektorspeicher konnte nicht initialisiert werden: {{errorMessage}}", + "lancedbLoadFailed": "LanceDB-Modul konnte nicht geladen werden: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfe deinen API-Schlüssel in den Einstellungen.", diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index 66465d8c35..ce1aa14b39 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}", - "vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}" + "vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}", + "localStoreInitFailed": "Failed to initialize local vector store: {{errorMessage}}", + "lancedbLoadFailed": "Failed to load LanceDB module: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authentication failed. Please check your API key in the settings.", diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index 51621b6d17..ad01169401 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}", - "vectorDimensionMismatch": "No se pudo actualizar el índice de vectores para el nuevo modelo. Intenta borrar el índice y empezar de nuevo. Detalles: {{errorMessage}}" + "vectorDimensionMismatch": "No se pudo actualizar el índice de vectores para el nuevo modelo. Intenta borrar el índice y empezar de nuevo. Detalles: {{errorMessage}}", + "localStoreInitFailed": "No se pudo inicializar el almacén vectorial local: {{errorMessage}}", + "lancedbLoadFailed": "No se pudo cargar el módulo LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Error de autenticación. Comprueba tu clave de API en los ajustes.", diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index e3a9227234..75d9ffb548 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}", - "vectorDimensionMismatch": "Échec de la mise à jour de l'index vectoriel pour le nouveau modèle. Veuillez essayer de vider l'index et de recommencer. Détails : {{errorMessage}}" + "vectorDimensionMismatch": "Échec de la mise à jour de l'index vectoriel pour le nouveau modèle. Veuillez essayer de vider l'index et de recommencer. Détails : {{errorMessage}}", + "localStoreInitFailed": "Échec de l'initialisation du stockage vectoriel local : {{errorMessage}}", + "lancedbLoadFailed": "Échec du chargement du module LanceDB : {{errorMessage}}" }, "validation": { "authenticationFailed": "Échec de l'authentification. Veuillez vérifier votre clé API dans les paramètres.", diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index 01563e833a..dce05c1228 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}", - "vectorDimensionMismatch": "नए मॉडल के लिए वेक्टर इंडेक्स को अपडेट करने में विफल। कृपया इंडेक्स को साफ़ करने और फिर से शुरू करने का प्रयास करें। विवरण: {{errorMessage}}" + "vectorDimensionMismatch": "नए मॉडल के लिए वेक्टर इंडेक्स को अपडेट करने में विफल। कृपया इंडेक्स को साफ़ करने और फिर से शुरू करने का प्रयास करें। विवरण: {{errorMessage}}", + "localStoreInitFailed": "स्थानीय वेक्टर स्टोर प्रारंभ करने में विफल: {{errorMessage}}", + "lancedbLoadFailed": "LanceDB मॉड्यूल लोड करने में विफल: {{errorMessage}}" }, "validation": { "authenticationFailed": "प्रमाणीकरण विफल। कृपया सेटिंग्स में अपनी एपीआई कुंजी जांचें।", diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index a66c1965ab..7a050a9aca 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}", - "vectorDimensionMismatch": "Gagal memperbarui indeks vektor untuk model baru. Silakan coba bersihkan indeks dan mulai lagi. Detail: {{errorMessage}}" + "vectorDimensionMismatch": "Gagal memperbarui indeks vektor untuk model baru. Silakan coba bersihkan indeks dan mulai lagi. Detail: {{errorMessage}}", + "localStoreInitFailed": "Gagal menginisialisasi penyimpanan vektor lokal: {{errorMessage}}", + "lancedbLoadFailed": "Gagal memuat modul LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Autentikasi gagal. Silakan periksa kunci API Anda di pengaturan.", diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index d59bc2c26d..d214be2680 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}", - "vectorDimensionMismatch": "Impossibile aggiornare l'indice vettoriale per il nuovo modello. Prova a cancellare l'indice e a ricominciare. Dettagli: {{errorMessage}}" + "vectorDimensionMismatch": "Impossibile aggiornare l'indice vettoriale per il nuovo modello. Prova a cancellare l'indice e a ricominciare. Dettagli: {{errorMessage}}", + "localStoreInitFailed": "Impossibile inizializzare l'archivio vettoriale locale: {{errorMessage}}", + "lancedbLoadFailed": "Impossibile caricare il modulo LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Autenticazione fallita. Controlla la tua chiave API nelle impostazioni.", diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index 799c6745fa..ebc3a5bc4b 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}", - "vectorDimensionMismatch": "新しいモデルのベクトルインデックスの更新に失敗しました。インデックスをクリアして再試行してください。詳細:{{errorMessage}}" + "vectorDimensionMismatch": "新しいモデルのベクトルインデックスの更新に失敗しました。インデックスをクリアして再試行してください。詳細:{{errorMessage}}", + "localStoreInitFailed": "ローカルベクトルストアの初期化に失敗しました: {{errorMessage}}", + "lancedbLoadFailed": "LanceDBモジュールの読み込みに失敗しました: {{errorMessage}}" }, "validation": { "authenticationFailed": "認証に失敗しました。設定でAPIキーを確認してください。", diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 3817135982..92500cb8bc 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}", - "vectorDimensionMismatch": "새 모델의 벡터 인덱스를 업데이트하지 못했습니다. 인덱스를 지우고 다시 시작해 보세요. 세부 정보: {{errorMessage}}" + "vectorDimensionMismatch": "새 모델의 벡터 인덱스를 업데이트하지 못했습니다. 인덱스를 지우고 다시 시작해 보세요. 세부 정보: {{errorMessage}}", + "localStoreInitFailed": "로컬 벡터 저장소 초기화 실패: {{errorMessage}}", + "lancedbLoadFailed": "LanceDB 모듈 로드 실패: {{errorMessage}}" }, "validation": { "authenticationFailed": "인증에 실패했습니다. 설정에서 API 키를 확인하세요.", diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index 52d675c890..68f91b5f4f 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}", - "vectorDimensionMismatch": "Kan de vectorindex voor het nieuwe model niet bijwerken. Probeer de index te wissen en opnieuw te beginnen. Details: {{errorMessage}}" + "vectorDimensionMismatch": "Kan de vectorindex voor het nieuwe model niet bijwerken. Probeer de index te wissen en opnieuw te beginnen. Details: {{errorMessage}}", + "localStoreInitFailed": "Lokale vectoropslag kon niet worden geïnitialiseerd: {{errorMessage}}", + "lancedbLoadFailed": "LanceDB-module kon niet worden geladen: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authenticatie mislukt. Controleer je API-sleutel in de instellingen.", diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 4d1ad0316c..0586749b98 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}", - "vectorDimensionMismatch": "Nie udało się zaktualizować indeksu wektorowego dla nowego modelu. Spróbuj wyczyścić indeks i zacząć od nowa. Szczegóły: {{errorMessage}}" + "vectorDimensionMismatch": "Nie udało się zaktualizować indeksu wektorowego dla nowego modelu. Spróbuj wyczyścić indeks i zacząć od nowa. Szczegóły: {{errorMessage}}", + "localStoreInitFailed": "Nie udało się zainicjować lokalnego magazynu wektorowego: {{errorMessage}}", + "lancedbLoadFailed": "Nie udało się załadować modułu LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Uwierzytelnianie nie powiodło się. Sprawdź swój klucz API w ustawieniach.", diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index 875bba95dc..7aa3e59b3a 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}", - "vectorDimensionMismatch": "Falha ao atualizar o índice de vetores para o novo modelo. Tente limpar o índice e começar novamente. Detalhes: {{errorMessage}}" + "vectorDimensionMismatch": "Falha ao atualizar o índice de vetores para o novo modelo. Tente limpar o índice e começar novamente. Detalhes: {{errorMessage}}", + "localStoreInitFailed": "Falha ao inicializar o armazenamento vetorial local: {{errorMessage}}", + "lancedbLoadFailed": "Falha ao carregar o módulo LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Falha na autenticação. Verifique sua chave de API nas configurações.", diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 80dfa9a594..8ed2e7fc5a 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}", - "vectorDimensionMismatch": "Не удалось обновить векторный индекс для новой модели. Попробуйте очистить индекс и начать сначала. Подробности: {{errorMessage}}" + "vectorDimensionMismatch": "Не удалось обновить векторный индекс для новой модели. Попробуйте очистить индекс и начать сначала. Подробности: {{errorMessage}}", + "localStoreInitFailed": "Не удалось инициализировать локальное векторное хранилище: {{errorMessage}}", + "lancedbLoadFailed": "Не удалось загрузить модуль LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Ошибка аутентификации. Проверьте свой ключ API в настройках.", diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index ba717b7c82..382b2292a2 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}", - "vectorDimensionMismatch": "Yeni model için vektör dizini güncellenemedi. Lütfen dizini temizleyip yeniden başlatmayı deneyin. Detaylar: {{errorMessage}}" + "vectorDimensionMismatch": "Yeni model için vektör dizini güncellenemedi. Lütfen dizini temizleyip yeniden başlatmayı deneyin. Detaylar: {{errorMessage}}", + "localStoreInitFailed": "Yerel vektör deposu başlatılamadı: {{errorMessage}}", + "lancedbLoadFailed": "LanceDB modülü yüklenemedi: {{errorMessage}}" }, "validation": { "authenticationFailed": "Kimlik doğrulama başarısız oldu. Lütfen ayarlardan API anahtarınızı kontrol edin.", diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index 12980b3345..d746938279 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}", - "vectorDimensionMismatch": "Không thể cập nhật chỉ mục vector cho mô hình mới. Vui lòng thử xóa chỉ mục và bắt đầu lại. Chi tiết: {{errorMessage}}" + "vectorDimensionMismatch": "Không thể cập nhật chỉ mục vector cho mô hình mới. Vui lòng thử xóa chỉ mục và bắt đầu lại. Chi tiết: {{errorMessage}}", + "localStoreInitFailed": "Không thể khởi tạo kho lưu trữ vector cục bộ: {{errorMessage}}", + "lancedbLoadFailed": "Không thể tải mô-đun LanceDB: {{errorMessage}}" }, "validation": { "authenticationFailed": "Xác thực không thành công. Vui lòng kiểm tra khóa API của bạn trong cài đặt.", diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index 1589689c06..f985b3d1d1 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}", - "vectorDimensionMismatch": "无法更新新模型的向量索引。请尝试清除索引并重新开始。详细信息:{{errorMessage}}" + "vectorDimensionMismatch": "无法更新新模型的向量索引。请尝试清除索引并重新开始。详细信息:{{errorMessage}}", + "localStoreInitFailed": "初始化本地向量存储失败:{{errorMessage}}", + "lancedbLoadFailed": "加载 LanceDB 模块失败:{{errorMessage}}" }, "validation": { "authenticationFailed": "身份验证失败。请在设置中检查您的 API 密钥。", diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 2dc41221f3..a6c625a35f 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -24,7 +24,9 @@ }, "vectorStore": { "qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}", - "vectorDimensionMismatch": "無法更新新模型的向量索引。請嘗試清除索引並重新開始。詳細資訊: {{errorMessage}}" + "vectorDimensionMismatch": "無法更新新模型的向量索引。請嘗試清除索引並重新開始。詳細資訊: {{errorMessage}}", + "localStoreInitFailed": "初始化本地向量儲存失敗:{{errorMessage}}", + "lancedbLoadFailed": "載入 LanceDB 模組失敗:{{errorMessage}}" }, "validation": { "authenticationFailed": "驗證失敗。請在設定中檢查您的 API 金鑰。", diff --git a/src/package.json b/src/package.json index d29a00e80b..9a534664e1 100644 --- a/src/package.json +++ b/src/package.json @@ -416,6 +416,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.848.0", "@aws-sdk/credential-providers": "^3.848.0", "@google/genai": "^1.0.0", + "@lancedb/lancedb": "^0.21.2", "@lmstudio/sdk": "^1.1.1", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", @@ -493,7 +494,7 @@ "@types/diff-match-patch": "^1.0.36", "@types/glob": "^8.1.0", "@types/mocha": "^10.0.10", - "@types/node": "20.x", + "@types/node": "~22.15.29", "@types/node-cache": "^4.1.3", "@types/node-ipc": "^9.2.3", "@types/proper-lockfile": "^4.1.4", diff --git a/src/services/code-index/__tests__/config-manager.spec.ts b/src/services/code-index/__tests__/config-manager.spec.ts index 2d6e704d76..9ffd9fe3b6 100644 --- a/src/services/code-index/__tests__/config-manager.spec.ts +++ b/src/services/code-index/__tests__/config-manager.spec.ts @@ -1300,6 +1300,7 @@ describe("CodeIndexConfigManager", () => { qdrantApiKey: "test-qdrant-key", searchMinScore: 0.4, searchMaxResults: 50, + vectorStoreProvider: "qdrant", }) }) diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 1723f1c2a0..b89121f3d6 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -12,6 +12,8 @@ import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from ".. export class CodeIndexConfigManager { private codebaseIndexEnabled: boolean = true private embedderProvider: EmbedderProvider = "openai" + private vectorStoreProvider: "local" | "qdrant" = "qdrant" + private localVectorStoreDirectory?: string private modelId?: string private modelDimension?: number private openAiOptions?: ApiHandlerOptions @@ -46,10 +48,16 @@ export class CodeIndexConfigManager { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://localhost:6333", codebaseIndexEmbedderProvider: "openai", + codebaseIndexVectorStoreProvider: "qdrant", + codebaseIndexLocalVectorStoreDirectory: undefined, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", + codebaseIndexEmbedderModelDimension: undefined, codebaseIndexSearchMinScore: undefined, codebaseIndexSearchMaxResults: undefined, + codebaseIndexOpenAiCompatibleBaseUrl: "", + codebaseIndexOpenAiCompatibleApiKey: "", + codebaseIndexGeminiApiKey: "", } const { @@ -58,9 +66,11 @@ export class CodeIndexConfigManager { codebaseIndexEmbedderProvider, codebaseIndexEmbedderBaseUrl, codebaseIndexEmbedderModelId, + codebaseIndexLocalVectorStoreDirectory, codebaseIndexSearchMinScore, codebaseIndexSearchMaxResults, } = codebaseIndexConfig + const codebaseIndexVectorStoreProvider = codebaseIndexConfig.codebaseIndexVectorStoreProvider ?? "qdrant" const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? "" const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? "" @@ -72,6 +82,8 @@ export class CodeIndexConfigManager { // Update instance variables with configuration this.codebaseIndexEnabled = codebaseIndexEnabled ?? true + this.vectorStoreProvider = codebaseIndexVectorStoreProvider ?? "qdrant" + this.localVectorStoreDirectory = codebaseIndexLocalVectorStoreDirectory this.qdrantUrl = codebaseIndexQdrantUrl this.qdrantApiKey = qdrantApiKey ?? "" this.searchMinScore = codebaseIndexSearchMinScore @@ -152,6 +164,8 @@ export class CodeIndexConfigManager { enabled: this.codebaseIndexEnabled, configured: this.isConfigured(), embedderProvider: this.embedderProvider, + vectorStoreProvider: this.vectorStoreProvider, + localVectorStoreDirectory: this.localVectorStoreDirectory, modelId: this.modelId, modelDimension: this.modelDimension, openAiKey: this.openAiOptions?.openAiNativeApiKey ?? "", @@ -257,6 +271,8 @@ export class CodeIndexConfigManager { const prevMistralApiKey = prev?.mistralApiKey ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" + const prevVectorStoreProvider = prev?.vectorStoreProvider ?? "qdrant" + const prevLocalDbPath = prev?.localVectorStoreDirectory ?? "" // 1. Transition from disabled/unconfigured to enabled/configured if ((!prevEnabled || !prevConfigured) && this.codebaseIndexEnabled && nowConfigured) { @@ -284,6 +300,16 @@ export class CodeIndexConfigManager { return true } + // Vector store provider change + if (prevVectorStoreProvider !== this.vectorStoreProvider) { + return true + } + + // Local DB path change (only affects local vector store) + if (this.vectorStoreProvider === "local" && prevLocalDbPath !== (this.localVectorStoreDirectory ?? "")) { + return true + } + // Authentication changes (API keys) const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? "" const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? "" @@ -294,6 +320,7 @@ export class CodeIndexConfigManager { const currentMistralApiKey = this.mistralOptions?.apiKey ?? "" const currentQdrantUrl = this.qdrantUrl ?? "" const currentQdrantApiKey = this.qdrantApiKey ?? "" + const currentLocalDbPath = this.localVectorStoreDirectory ?? "" if (prevOpenAiKey !== currentOpenAiKey) { return true @@ -327,6 +354,11 @@ export class CodeIndexConfigManager { return true } + // Check for local database path changes (affects local vector store) + if (prevLocalDbPath !== currentLocalDbPath) { + return true + } + // Vector dimension changes (still important for compatibility) if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) { return true @@ -368,6 +400,8 @@ export class CodeIndexConfigManager { return { isConfigured: this.isConfigured(), embedderProvider: this.embedderProvider, + vectorStoreProvider: this.vectorStoreProvider ?? "qdrant", + localVectorStoreDirectoryPlaceholder: this.localVectorStoreDirectory, modelId: this.modelId, modelDimension: this.modelDimension, openAiOptions: this.openAiOptions, @@ -460,4 +494,11 @@ export class CodeIndexConfigManager { public get currentSearchMaxResults(): number { return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS } + + /** + * Gets the current local database path for vector storage + */ + public get currentLocalDbPath(): string | undefined { + return this.localVectorStoreDirectory + } } diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index 9098a60091..eaab65f5cf 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -7,6 +7,8 @@ import { EmbedderProvider } from "./manager" export interface CodeIndexConfig { isConfigured: boolean embedderProvider: EmbedderProvider + vectorStoreProvider?: "local" | "qdrant" + localVectorStoreDirectoryPlaceholder?: string modelId?: string modelDimension?: number // Generic dimension property for all providers openAiOptions?: ApiHandlerOptions @@ -27,6 +29,8 @@ export type PreviousConfigSnapshot = { enabled: boolean configured: boolean embedderProvider: EmbedderProvider + vectorStoreProvider?: "local" | "qdrant" + localVectorStoreDirectory?: string modelId?: string modelDimension?: number // Generic dimension property openAiKey?: string diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 68b0f5c0bc..1027863041 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -6,6 +6,7 @@ import { GeminiEmbedder } from "./embedders/gemini" import { MistralEmbedder } from "./embedders/mistral" import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels" import { QdrantVectorStore } from "./vector-store/qdrant-client" +import { LocalVectorStore } from "./vector-store/local-vector-store" import { codeParser, DirectoryScanner, FileWatcher } from "./processors" import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces" import { CodeIndexConfigManager } from "./config-manager" @@ -14,6 +15,8 @@ import { Ignore } from "ignore" import { t } from "../../i18n" import { TelemetryService } from "@roo-code/telemetry" import { TelemetryEventName } from "@roo-code/types" +import { getLocalVectorStoreDirectoryPath } from "../../utils/storage" +import { LanceDBManager } from "../lancedb-manager" /** * Factory class responsible for creating and configuring code indexing service dependencies. @@ -131,7 +134,20 @@ export class CodeIndexServiceFactory { throw new Error(t("embeddings:serviceFactory.vectorDimensionNotDetermined", { modelId, provider })) } } - + // Use Local + if (config.vectorStoreProvider === "local") { + const { workspacePath } = this + const globalStorageUri = this.configManager.getContextProxy().globalStorageUri.fsPath + const localVectorStoreDirectoryPlaceholder = + config.localVectorStoreDirectoryPlaceholder || getLocalVectorStoreDirectoryPath(globalStorageUri) + return new LocalVectorStore( + workspacePath, + vectorSize, + localVectorStoreDirectoryPlaceholder, + new LanceDBManager(globalStorageUri), + ) + } + // Use Qdrant if (!config.qdrantUrl) { throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing")) } diff --git a/src/services/code-index/vector-store/__tests__/local-vector-store.spec.ts b/src/services/code-index/vector-store/__tests__/local-vector-store.spec.ts new file mode 100644 index 0000000000..d5c619e24e --- /dev/null +++ b/src/services/code-index/vector-store/__tests__/local-vector-store.spec.ts @@ -0,0 +1,322 @@ +// npx vitest run services/code-index/vector-store/__tests__/local-vector-store.spec.ts + +/** + * Comprehensive tests for LocalVectorStore. + * All LanceDB and fs operations are mocked. + */ + +import { describe, it, beforeEach, afterEach, expect, vi } from "vitest" +import { LocalVectorStore } from "../local-vector-store" +import { LanceDBManager } from "../../../lancedb-manager" +import { Payload } from "../../interfaces" +import * as path from "path" +const fs = require("fs") +const mockTable = { + delete: vi.fn().mockResolvedValue(undefined), + add: vi.fn().mockResolvedValue(undefined), + query: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + toArray: vi.fn().mockResolvedValue([]), + vectorSearch: vi.fn().mockReturnThis(), + limit: vi.fn().mockReturnThis(), + refineFactor: vi.fn().mockReturnThis(), + postfilter: vi.fn().mockReturnThis(), + openTable: vi.fn().mockResolvedValue(undefined), + search: vi.fn().mockReturnThis(), + name: "vector", + isOpen: true, + close: vi.fn(), + display: vi.fn(), + schema: {}, + count: vi.fn(), + get: vi.fn(), + create: vi.fn(), + drop: vi.fn(), + insert: vi.fn(), + update: vi.fn(), + find: vi.fn(), + remove: vi.fn(), + createIndex: vi.fn(), + dropIndex: vi.fn(), + indexes: [], + columns: [], + primaryKey: "id", + metadata: {}, + batch: vi.fn(), + distanceRange: vi.fn().mockReturnThis(), +} +const mockDb = { + openTable: vi.fn().mockResolvedValue(mockTable), + createTable: vi.fn().mockResolvedValue(mockTable), + dropTable: vi.fn().mockResolvedValue(undefined), + tableNames: vi.fn().mockResolvedValue(["vector", "metadata"]), + close: vi.fn().mockResolvedValue(undefined), + isOpen: true, + display: vi.fn(), + createEmptyTable: vi.fn(), + dropAllTables: vi.fn(), +} + +const mockLanceDBModule = { + connect: vi.fn().mockResolvedValue(mockDb), +} +const mockLanceDBManager = { + ensureLanceDBAvailable: vi.fn(), + getNodeModulesPath: vi.fn(() => "mock_node_modules"), +} + +vi.mock("@lancedb/lancedb", () => mockLanceDBModule) + +const workspacePath = path.join("mock", "workspace") +const vectorSize = 768 +const dbDirectory = path.join("mock", "db") +let store: LocalVectorStore + +describe("LocalVectorStore", () => { + beforeEach(() => { + vi.clearAllMocks() + store = new LocalVectorStore( + workspacePath, + vectorSize, + dbDirectory, + mockLanceDBManager as unknown as LanceDBManager, + ) + // Patch LanceDB module directly for loadLanceDBModule + // @ts-ignore + store.lancedbModule = mockLanceDBModule + // Patch db/table for getDb/getTable + // @ts-ignore + store.db = mockDb + // @ts-ignore + store.table = mockTable + }) + + afterEach(async () => { + await store["closeConnect"]() + }) + + describe("constructor", () => { + it("should set dbPath and vectorSize correctly", () => { + expect(store["vectorSize"]).toBe(vectorSize) + expect(store["workspacePath"]).toBe(workspacePath) + expect(store["dbPath"]).toContain("mock") + }) + }) + + describe("initialize", () => { + it("should create tables if not exist", async () => { + mockDb.tableNames.mockResolvedValue([]) + mockDb.createTable.mockResolvedValue(mockTable) + const result = await store.initialize() + expect(result).toBe(true) + expect(mockDb.createTable).toHaveBeenCalled() + }) + + it("should recreate tables if vector size changed", async () => { + mockDb.tableNames.mockResolvedValue(["vector", "metadata"]) + mockDb.openTable.mockResolvedValue(mockTable) + store["_getStoredVectorSize"] = vi.fn().mockResolvedValue(vectorSize + 1) + mockDb.dropTable.mockResolvedValue(undefined) + mockDb.createTable.mockResolvedValue(mockTable) + const result = await store.initialize() + expect(result).toBe(true) + expect(mockDb.dropTable).toHaveBeenCalled() + }) + + it("should not recreate if vector size matches", async () => { + mockDb.tableNames.mockResolvedValue(["vector", "metadata"]) + mockDb.openTable.mockResolvedValue(mockTable) + store["_getStoredVectorSize"] = vi.fn().mockResolvedValue(vectorSize) + const result = await store.initialize() + expect(result).toBe(false) + }) + + it("should throw error on LanceDB failure", async () => { + mockDb.tableNames.mockRejectedValue(new Error("fail")) + await expect(store.initialize()).rejects.toThrow() + }) + }) + + describe("upsertPoints", () => { + it("should do nothing for empty points", async () => { + await expect(store.upsertPoints([])).resolves.toBeUndefined() + }) + + it("should do nothing for invalid payloads", async () => { + const points = [{ id: "1", vector: [1, 2, 3], payload: {} }] + mockTable.add.mockResolvedValue(undefined) + await expect(store.upsertPoints(points)).resolves.toBeUndefined() + expect(mockTable.add).not.toHaveBeenCalled() + }) + + it("should upsert valid points", async () => { + const points = [ + { + id: "1", + vector: [1, 2, 3], + payload: { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + }, + ] + mockTable.delete.mockResolvedValue(undefined) + mockTable.add.mockResolvedValue(undefined) + await store.upsertPoints(points) + expect(mockTable.delete).toHaveBeenCalled() + expect(mockTable.add).toHaveBeenCalled() + }) + + it("should throw error on add failure", async () => { + const points = [ + { + id: "1", + vector: [1, 2, 3], + payload: { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + }, + ] + mockTable.delete.mockResolvedValue(undefined) + mockTable.add.mockRejectedValue(new Error("fail")) + await expect(store.upsertPoints(points)).rejects.toThrow() + }) + }) + + describe("search", () => { + it("should return filtered results", async () => { + mockTable.search.mockResolvedValue({ + where: vi.fn().mockReturnThis(), + limit: vi.fn().mockReturnThis(), + distanceType: vi.fn().mockReturnThis(), + toArray: vi.fn().mockResolvedValue([ + { id: "1", _distance: 0.8, filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + { id: "2", _distance: 0.6, filePath: "a", codeChunk: "c", startLine: 3, endLine: 4 }, + ]), + }) + const results = await store.search([1, 2, 3], "a", 0.1, 1) + expect(results.length).toBe(1) + expect(results[0].id).toBe("2") + expect(results[0].score).toBeCloseTo(1 - 0.6) + }) + + it("should filter by minScore", async () => { + mockTable.search.mockResolvedValue({ + where: vi.fn().mockReturnThis(), + limit: vi.fn().mockReturnThis(), + distanceType: vi.fn().mockReturnThis(), + toArray: vi.fn().mockResolvedValue([ + { id: "1", _distance: 0.99, filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + { id: "2", _distance: 0.2, filePath: "a", codeChunk: "c", startLine: 3, endLine: 4 }, + ]), + }) + const results = await store.search([1, 2, 3], "a", 0.1, 2) + expect(results.length).toBe(1) + expect(results[0].id).toBe("2") + }) + + it("should throw error on search failure", async () => { + mockTable.search.mockRejectedValue(new Error("fail")) + await expect(store.search([1, 2, 3])).rejects.toThrow() + }) + }) + + describe("deletePointsByFilePath", () => { + it("should call deletePointsByMultipleFilePaths", async () => { + const spy = vi.spyOn(store, "deletePointsByMultipleFilePaths").mockResolvedValue(undefined) + await store.deletePointsByFilePath("a") + expect(spy).toHaveBeenCalledWith(["a"]) + }) + }) + + describe("deletePointsByMultipleFilePaths", () => { + it("should do nothing for empty filePaths", async () => { + await expect(store.deletePointsByMultipleFilePaths([])).resolves.toBeUndefined() + }) + + it("should delete points for valid filePaths", async () => { + mockTable.delete.mockResolvedValue(undefined) + await store.deletePointsByMultipleFilePaths(["a", "b"]) + expect(mockTable.delete).toHaveBeenCalled() + }) + + it("should throw error on delete failure", async () => { + mockTable.delete.mockRejectedValue(new Error("fail")) + await expect(store.deletePointsByMultipleFilePaths(["a"])).rejects.toThrow() + }) + }) + + describe("deleteCollection", () => { + it("should remove dbPath if exists", async () => { + vi.spyOn(fs, "existsSync").mockResolvedValue(true) + vi.spyOn(fs, "rmSync").mockImplementation(() => {}) + await expect(store.deleteCollection()).resolves.toBeUndefined() + expect(fs.rmSync).toHaveBeenCalled() + }) + + it("should clear tables if rmSync fails", async () => { + vi.spyOn(fs, "existsSync").mockResolvedValue(true) + vi.spyOn(fs, "rmSync").mockImplementation(() => { + throw new Error("fail") + }) + mockDb.tableNames.mockImplementation(() => ["vector"]) + mockDb.dropTable.mockResolvedValue(undefined) + await expect(store.deleteCollection()).rejects.toThrow() + expect(mockDb.dropTable).toHaveBeenCalled() + }) + }) + + describe("clearCollection", () => { + it("should delete all records from table and metadata", async () => { + mockTable.delete.mockResolvedValue(undefined) + mockDb.tableNames.mockResolvedValue(["metadata"]) + mockDb.openTable.mockResolvedValue(mockTable) + mockTable.delete.mockResolvedValue(undefined) + await expect(store.clearCollection()).resolves.toBeUndefined() + expect(mockTable.delete).toHaveBeenCalledWith("true") + }) + + it("should warn if metadata table clear fails", async () => { + mockTable.delete.mockResolvedValue(undefined) + mockDb.tableNames.mockResolvedValue(["metadata"]) + mockDb.openTable.mockRejectedValue(new Error("fail")) + await expect(store.clearCollection()).resolves.toBeUndefined() + }) + + it("should throw error on main table clear failure", async () => { + mockTable.delete.mockRejectedValue(new Error("fail")) + await expect(store.clearCollection()).rejects.toThrow() + }) + }) + + describe("collectionExists", () => { + it("should return true if vector table exists", async () => { + mockDb.tableNames.mockResolvedValue(["vector"]) + const exists = await store.collectionExists() + expect(exists).toBe(true) + }) + + it("should return false if vector table does not exist", async () => { + mockDb.tableNames.mockResolvedValue([]) + const exists = await store.collectionExists() + expect(exists).toBe(false) + }) + + it("should return false on error", async () => { + mockDb.tableNames.mockRejectedValue(new Error("fail")) + const exists = await store.collectionExists() + expect(exists).toBe(false) + }) + }) + + describe("isPayloadValid", () => { + it("should return false for null/undefined", () => { + expect(store["isPayloadValid"](null)).toBe(false) + expect(store["isPayloadValid"](undefined)).toBe(false) + }) + + it("should return false for missing keys", () => { + expect(store["isPayloadValid"]({ filePath: "a" })).toBe(false) + }) + + it("should return true for valid payload", () => { + const payload: Payload = { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 } + expect(store["isPayloadValid"](payload)).toBe(true) + }) + }) +}) diff --git a/src/services/code-index/vector-store/local-vector-store.ts b/src/services/code-index/vector-store/local-vector-store.ts new file mode 100644 index 0000000000..530f655b38 --- /dev/null +++ b/src/services/code-index/vector-store/local-vector-store.ts @@ -0,0 +1,446 @@ +import { createHash } from "crypto" +import * as path from "path" +import { Connection, Table, VectorQuery } from "@lancedb/lancedb" +import { IVectorStore } from "../interfaces/vector-store" +import { Payload, VectorStoreSearchResult } from "../interfaces" +import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../constants" +import { t } from "../../../i18n" +import { LanceDBManager } from "../../lancedb-manager" +const fs = require("fs") + +/** + * Local implementation of the vector store using LanceDB + */ +export class LocalVectorStore implements IVectorStore { + private readonly vectorSize: number + private readonly dbPath: string + private readonly workspacePath: string + private db: Connection | null = null + private table: Table | null = null + private readonly vectorTableName = "vector" + private readonly metadataTableName = "metadata" + private lancedbManager: LanceDBManager + private lancedbModule: any = null + + constructor(workspacePath: string, vectorSize: number, dbDirectory: string, lancedbManager: LanceDBManager) { + this.vectorSize = vectorSize + this.workspacePath = workspacePath + const basename = path.basename(workspacePath) + // Generate database directory name from workspace path + const hash = createHash("sha256").update(workspacePath).digest("hex") + const dbName = `${basename}-${hash.substring(0, 16)}` + // Set up database path + this.dbPath = path.join(dbDirectory, dbName) + this.lancedbManager = lancedbManager + } + + /** + * Dynamically loads the LanceDB module. + * @returns The LanceDB module. + */ + private async loadLanceDBModule(): Promise { + if (this.lancedbModule) { + return this.lancedbModule + } + + // Ensure LanceDB dependencies are available + await this.lancedbManager.ensureLanceDBAvailable() + + const nodeModulesPath = this.lancedbManager.getNodeModulesPath() + + // Add the custom node_modules path to the module search paths + // This should be done before requiring the module + if (!module.paths.includes(nodeModulesPath)) { + module.paths.unshift(nodeModulesPath) + } + + try { + // Dynamically import LanceDB + this.lancedbModule = require("@lancedb/lancedb") + return this.lancedbModule + } catch (error: unknown) { + console.error("Failed to load LanceDB module:", error) + throw new Error(t("embeddings:vectorStore.lancedbLoadFailed", { errorMessage: (error as Error).message })) + } + } + + /** + * Gets or connects to the LanceDB database. + * @returns The LanceDB connection. + */ + private async getDb(): Promise { + if (this.db) { + return this.db + } + + const lancedb = await this.loadLanceDBModule() + + // Create parent directory if needed + if (!fs.existsSync(this.dbPath)) { + fs.mkdirSync(this.dbPath, { recursive: true }) + } + + this.db = await lancedb.connect(this.dbPath) + return this.db as Connection + } + + /** + * Gets or opens the vector table. + * @returns The LanceDB table. + */ + private async getTable(): Promise { + if (this.table) { + return this.table + } + + const db = await this.getDb() + + try { + // Try to open existing table + const table = await db.openTable(this.vectorTableName) + this.table = table + return table + } catch (error) { + // Table doesn't exist, will be created in initialize() + throw new Error(`Table ${this.vectorTableName} does not exist`) + } + } + + /** + * Creates sample data for the vector table schema. + * @returns An array containing sample data. + */ + private _createSampleData() { + return [ + { + id: "sample", + vector: new Array(this.vectorSize).fill(0), + filePath: "sample", + codeChunk: "sample", + startLine: 0, + endLine: 0, + }, + ] + } + + /** + * Creates metadata for the vector size. + * @returns An array containing metadata. + */ + private _createMetadataData() { + return [ + { + key: "vector_size", + value: this.vectorSize, + }, + ] + } + + /** + * Creates the vector table and deletes the sample data. + * @param db The LanceDB connection. + */ + private async _createVectorTable(db: Connection): Promise { + this.table = await db.createTable(this.vectorTableName, this._createSampleData()) + if (this.table) { + await this.table.delete("id = 'sample'") + } + } + + /** + * Creates the metadata table. + * @param db The LanceDB connection. + */ + private async _createMetadataTable(db: Connection): Promise { + await db.createTable(this.metadataTableName, this._createMetadataData()) + } + + /** + * Drops a table if it exists. + * @param db The LanceDB connection. + * @param tableName The name of the table to drop. + */ + private async _dropTableIfExists(db: Connection, tableName: string): Promise { + const tableNames = await db.tableNames() + if (tableNames.includes(tableName)) { + await db.dropTable(tableName) + } + } + + /** + * Retrieves the stored vector size from the metadata table. + * @param db The LanceDB connection. + * @returns The stored vector size, or null if not found. + */ + private async _getStoredVectorSize(db: Connection): Promise { + try { + const metadataTable = await db.openTable(this.metadataTableName) + const metadataResults = await metadataTable.query().where("key = 'vector_size'").toArray() + return metadataResults.length > 0 ? metadataResults[0].value : null + } catch (error) { + console.warn("Failed to read metadata table:", error) + return null + } + } + + async initialize(): Promise { + try { + await this.closeConnect() + const db = await this.getDb() + + const tableNames = await db.tableNames() + const vectorTableExists = tableNames.includes(this.vectorTableName) + const metadataTableExists = tableNames.includes(this.metadataTableName) + + let needsRecreation = false + + if (!vectorTableExists) { + await this._createVectorTable(db) + await this._createMetadataTable(db) + return true + } + + this.table = await db.openTable(this.vectorTableName) + + const storedVectorSize = metadataTableExists ? await this._getStoredVectorSize(db) : null + + if (storedVectorSize === null || storedVectorSize !== this.vectorSize) { + needsRecreation = true + } + + if (needsRecreation) { + await this._dropTableIfExists(db, this.vectorTableName) + await this._dropTableIfExists(db, this.metadataTableName) + await this._createVectorTable(db) + await this._createMetadataTable(db) + this.optimizeTable() + + return true + } + this.optimizeTable() + return false + } catch (error) { + console.error(`[LocalVectorStore] Failed to initialize:`, error) + throw new Error(t("embeddings:vectorStore.localStoreInitFailed", { errorMessage: error.message })) + } + } + + async upsertPoints( + points: Array<{ + id: string + vector: number[] + payload: Record + }>, + ): Promise { + if (points.length === 0) { + return + } + + const table = await this.getTable() + const valids = points.filter((point) => this.isPayloadValid(point.payload)) + + if (valids.length === 0) { + return + } + + try { + // Convert points to LanceDB format + const lanceData = valids.map((point) => ({ + id: point.id, + vector: point.vector, + filePath: point.payload.filePath, + codeChunk: point.payload.codeChunk, + startLine: point.payload.startLine, + endLine: point.payload.endLine, + })) + + // Delete existing points with same IDs first + const existingIds = lanceData.map((d) => d.id) + if (existingIds.length > 0) { + const idFilter = existingIds.map((id) => `id = '${id}'`).join(" OR ") + await table.delete(idFilter) + } + + // Insert new data + await table.add(lanceData) + } catch (error) { + console.error("Failed to upsert points:", error) + throw error + } + } + + private isPayloadValid(payload: Record | null | undefined): payload is Payload { + if (!payload) { + return false + } + const validKeys = ["filePath", "codeChunk", "startLine", "endLine"] + const hasValidKeys = validKeys.every((key) => key in payload) + return hasValidKeys + } + + async search( + queryVector: number[], + directoryPrefix?: string, + minScore?: number, + maxResults?: number, + ): Promise { + try { + const table = await this.getTable() + const actualMinScore = minScore ?? DEFAULT_SEARCH_MIN_SCORE + const actualMaxResults = maxResults ?? DEFAULT_MAX_SEARCH_RESULTS + + // Build filter condition + let filter = "" + if (directoryPrefix) { + // Use backticks for column name and escape single quotes in directoryPrefix + const filterDirectoryPrefix = directoryPrefix.replace(/\\/g, "\\\\") + filter = `\`filePath\` LIKE '${filterDirectoryPrefix}%'` + } + // Perform vector search + let searchQuery = (await table.search(queryVector)) as VectorQuery + if (filter !== "") { + searchQuery = searchQuery.where(filter) + } + searchQuery = searchQuery.limit(actualMaxResults).distanceType("cosine") + + const list = await searchQuery.toArray() + const results = list + .map((i) => { + i._distance = 1 - i._distance + return i + }) + .filter((i) => i._distance >= actualMinScore) + .sort((a: any, b: any) => b._distance - a._distance) + .slice(0, actualMaxResults) + .map((result: any) => ({ + id: result.id, + score: result._distance, + payload: { + filePath: result.filePath, + codeChunk: result.codeChunk, + startLine: result.startLine, + endLine: result.endLine, + } as Payload, + })) + // Filter by minimum score and convert to expected format + return results + } catch (error) { + console.error("Failed to search points:", error) + throw error + } + } + + async deletePointsByFilePath(filePath: string): Promise { + return this.deletePointsByMultipleFilePaths([filePath]) + } + + async deletePointsByMultipleFilePaths(filePaths: string[]): Promise { + if (filePaths.length === 0) { + return + } + + try { + const table = await this.getTable() + const workspaceRoot = this.workspacePath + const normalizedPaths = filePaths.map((fp) => + path.normalize(path.isAbsolute(fp) ? path.relative(workspaceRoot, fp) : fp), + ) + + // Create filter condition for multiple file paths + const filterCondition = normalizedPaths + .map((fp) => { + // Escape single quotes in file path + return `\`filePath\` = '${fp}'` + }) + .join(" OR ") + await table.delete(filterCondition) + } catch (error) { + console.error("Failed to delete points by file paths:", error) + throw error + } + } + + async deleteCollection(): Promise { + await this.closeConnect() + try { + if (fs.existsSync(this.dbPath)) { + fs.rmSync(this.dbPath, { recursive: true, force: true }) + } + } catch (error) { + // If file deletion fails, try to clear the collection and metadata table + try { + const db = await this.getDb() + await this._dropTableIfExists(db, this.vectorTableName) + await this._dropTableIfExists(db, this.metadataTableName) + } catch (clearError) { + console.error("Failed to clear collection and metadata:", clearError) + } + throw error + } + } + + async clearCollection(): Promise { + try { + const table = await this.getTable() + // Delete all records from the table + await table.delete("true") // Delete all records + + // Also clear metadata table + try { + const db = await this.getDb() + const tableNames = await db.tableNames() + + if (tableNames.includes(this.metadataTableName)) { + const metadataTable = await db.openTable(this.metadataTableName) + await metadataTable.delete("true") + } + } catch (metadataError) { + console.warn("Failed to clear metadata table:", metadataError) + } + + // Run optimization to clean up disk space after clearing + await this.optimizeTable() + } catch (error) { + console.error("Failed to clear collection:", error) + throw error + } + } + + async collectionExists(): Promise { + try { + const db = await this.getDb() + const tableNames = await db.tableNames() + return tableNames.includes(this.vectorTableName) + } catch (error) { + return false + } + } + + private async closeConnect(): Promise { + if (this.table) { + this.table = null + } + if (this.db) { + await this.db.close() + this.db = null + } + } + + /** + * Optimizes the table to reduce disk space usage and improve performance. + * This method performs compaction, pruning of old versions, and index optimization. + * Should be called periodically to prevent unbounded disk space growth. + */ + async optimizeTable(): Promise { + try { + const table = await this.getTable() + + await table.optimize({ + cleanupOlderThan: new Date(), + deleteUnverified: false, + }) + } catch (error) { + console.error("[LocalVectorStore] Failed to optimize table:", error) + } + } +} diff --git a/src/services/lancedb-manager.ts b/src/services/lancedb-manager.ts new file mode 100644 index 0000000000..77af2575e0 --- /dev/null +++ b/src/services/lancedb-manager.ts @@ -0,0 +1,206 @@ +import * as vscode from "vscode" +import * as fs from "fs" +import * as path from "path" +import * as os from "os" +import { promisify } from "util" +import { exec } from "child_process" +import { safeWriteJson } from "../utils/safeWriteJson" + +const execAsync = promisify(exec) + +export interface PlatformInfo { + platform: string + arch: string + packageName: string + nodeFileName: string +} + +export class LanceDBManager { + private readonly dependenciesPath: string + private readonly lancedbVersion = "0.21.2" + + constructor(dependenciesPath: string) { + this.dependenciesPath = dependenciesPath + } + + /** + * get current platform information + */ + private getCurrentPlatform(): PlatformInfo { + const platform = os.platform() + const arch = os.arch() + + let packageName: string + let nodeFileName: string + + let isMusl = false + switch (platform) { + case "win32": + if (arch === "x64") { + packageName = "@lancedb/lancedb-win32-x64-msvc" + nodeFileName = "lancedb.win32-x64-msvc.node" + } else if (arch === "arm64") { + packageName = "@lancedb/lancedb-win32-arm64-msvc" + nodeFileName = "lancedb.win32-arm64-msvc.node" + } else { + throw new Error(`Unsupported Windows architecture: ${arch}`) + } + break + case "darwin": + if (arch === "x64") { + packageName = "@lancedb/lancedb-darwin-x64" + nodeFileName = "lancedb.darwin-x64.node" + } else if (arch === "arm64") { + packageName = "@lancedb/lancedb-darwin-arm64" + nodeFileName = "lancedb.darwin-arm64.node" + } else { + throw new Error(`Unsupported macOS architecture: ${arch}`) + } + break + case "linux": + isMusl = process.versions?.musl !== undefined + if (arch === "x64") { + if (isMusl) { + packageName = "@lancedb/lancedb-linux-x64-musl" + nodeFileName = "lancedb.linux-x64-musl.node" + } else { + packageName = "@lancedb/lancedb-linux-x64-gnu" + nodeFileName = "lancedb.linux-x64-gnu.node" + } + } else if (arch === "arm64") { + if (isMusl) { + packageName = "@lancedb/lancedb-linux-arm64-musl" + nodeFileName = "lancedb.linux-arm64-musl.node" + } else { + packageName = "@lancedb/lancedb-linux-arm64-gnu" + nodeFileName = "lancedb.linux-arm64-gnu.node" + } + } else { + throw new Error(`Unsupported Linux architecture: ${arch}`) + } + break + default: + throw new Error(`Unsupported platform: ${platform}, arch: ${arch}`) + } + + return { platform, arch, packageName, nodeFileName } + } + + /** + * get lancedb dependencies path + */ + private getDependenciesPath(): string { + return path.join(this.dependenciesPath, "lancedb-deps") + } + + /** + * checking LanceDB binaries + */ + async checkLanceDBBinaries(): Promise { + try { + const platformInfo = this.getCurrentPlatform() + const depsPath = this.getDependenciesPath() + + const lancedbPath = path.join(depsPath, "node_modules", "@lancedb", "lancedb") + const packageJsonPath = path.join(lancedbPath, "package.json") + const distPath = path.join(lancedbPath, "dist") + + const platformBinaryPath = path.join( + depsPath, + "node_modules", + "@lancedb", + platformInfo.packageName.split("/")[1], + ) + const nodeFilePath = path.join(platformBinaryPath, platformInfo.nodeFileName) + + if (fs.existsSync(packageJsonPath) && fs.existsSync(distPath) && fs.existsSync(nodeFilePath)) { + try { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) + if (pkg.version === this.lancedbVersion) { + return true + } + } catch (e) { + console.error("Error reading LanceDB package.json:", e) + } + } + return false + } catch (error) { + console.error("Error checking LanceDB binaries:", error) + return false + } + } + + /** + * Install LanceDB dependencies using npm only. + * This method generates a package.json and runs npm install for @lancedb/lancedb. + */ + async installLanceDBDependencies( + progress?: vscode.Progress<{ message?: string; increment?: number }>, + ): Promise { + try { + const depsPath = this.getDependenciesPath() + + // Ensure the dependencies directory exists + if (!fs.existsSync(depsPath)) { + fs.mkdirSync(depsPath, { recursive: true }) + } + + // Generate package.json if it does not exist + const packageJsonPath = path.join(depsPath, "package.json") + safeWriteJson(packageJsonPath, { + name: "lancedb-deps", + version: "1.0.0", + description: "LanceDB dependencies for VSCode extension", + private: true, + dependencies: {}, + }) + + progress?.report({ message: "Installing LanceDB dependencies...", increment: 50 }) + await execAsync(`npm install @lancedb/lancedb@${this.lancedbVersion}`, { cwd: depsPath }) + + progress?.report({ message: "Installation completed!", increment: 50 }) + + console.log("LanceDB dependencies installed successfully") + } catch (error) { + console.error("Failed to install LanceDB dependencies:", error) + throw new Error(`Failed to install LanceDB dependencies: ${error.message}`) + } + } + + /** + * ensure LanceDB dependencies are available + */ + async ensureLanceDBAvailable(): Promise { + const isAvailable = await this.checkLanceDBBinaries() + + if (!isAvailable) { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Installing LanceDB dependencies", + cancellable: false, + }, + async (progress) => { + await this.installLanceDBDependencies(progress) + }, + ) + } + } + + /** + * get dependencies path for setting NODE_PATH + */ + getNodeModulesPath(): string { + return path.join(this.getDependenciesPath(), "node_modules") + } + + /** + * clean up downloaded dependencies + */ + async cleanupDependencies(): Promise { + const depsPath = this.getDependenciesPath() + if (fs.existsSync(depsPath)) { + fs.rmSync(depsPath, { recursive: true, force: true }) + } + } +} diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index cb8759d851..104a2b20d3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -258,6 +258,8 @@ export interface WebviewMessage { codebaseIndexEnabled: boolean codebaseIndexQdrantUrl: string codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" + codebaseIndexVectorStoreProvider?: "local" | "qdrant" + codebaseIndexLocalVectorStoreDirectory?: string codebaseIndexEmbedderBaseUrl?: string codebaseIndexEmbedderModelId: string codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 8240588794..08e7498a11 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import * as path from "path" import * as fs from "fs/promises" +import * as fsSync from "fs" import { Package } from "../shared/package" import { t } from "../i18n" @@ -48,6 +49,32 @@ export async function getStorageBasePath(defaultPath: string): Promise { } } +/** + * Gets the base storage path for conversations(Sync) + */ +export function getStorageBasePathSync(defaultPath: string): string { + let customStoragePath = "" + try { + const config = vscode.workspace.getConfiguration(Package.name) + customStoragePath = config.get("customStoragePath", "") + } catch (error) { + console.warn("Could not access VSCode configuration - using default path") + return defaultPath + } + if (!customStoragePath) { + return defaultPath + } + try { + fsSync.mkdirSync(customStoragePath, { recursive: true }) + const testFile = path.join(customStoragePath, ".write_test") + fsSync.writeFileSync(testFile, "test") + fsSync.rmSync(testFile) + return customStoragePath + } catch (error) { + return defaultPath + } +} + /** * Gets the storage directory path for a task */ @@ -78,6 +105,16 @@ export async function getCacheDirectoryPath(globalStoragePath: string): Promise< return cacheDir } +/** + * Gets the local vector store directory path + */ +export function getLocalVectorStoreDirectoryPath(globalStoragePath: string): string { + const basePath = getStorageBasePathSync(globalStoragePath) + const cacheDir = path.join(basePath, "vector") + fsSync.mkdirSync(cacheDir, { recursive: true }) + return cacheDir +} + /** * Prompts the user to set a custom storage path * Displays an input box allowing the user to enter a custom path diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index c85aaf6ea5..6ee2c2345f 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -57,6 +57,8 @@ interface LocalCodeIndexSettings { codebaseIndexEnabled: boolean codebaseIndexQdrantUrl: string codebaseIndexEmbedderProvider: EmbedderProvider + codebaseIndexVectorStoreProvider: "local" | "qdrant" + codebaseIndexLocalVectorStoreDirectory?: string codebaseIndexEmbedderBaseUrl?: string codebaseIndexEmbedderModelId: string codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers @@ -169,6 +171,8 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "", codebaseIndexEmbedderProvider: "openai", + codebaseIndexVectorStoreProvider: "qdrant", + codebaseIndexLocalVectorStoreDirectory: undefined, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", codebaseIndexEmbedderModelDimension: undefined, @@ -200,6 +204,8 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true, codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "", codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai", + codebaseIndexVectorStoreProvider: codebaseIndexConfig.codebaseIndexVectorStoreProvider || "qdrant", + codebaseIndexLocalVectorStoreDirectory: codebaseIndexConfig.codebaseIndexLocalVectorStoreDirectory, codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "", codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "", codebaseIndexEmbedderModelDimension: @@ -1020,54 +1026,104 @@ export const CodeIndexPopover: React.FC = ({ )} - {/* Qdrant Settings */} + {/* vectorStoreProviderLabel */}
- - updateSetting("codebaseIndexQdrantUrl", e.target.value) - } - onBlur={(e: any) => { - // Set default Qdrant URL if field is empty - if (!e.target.value.trim()) { - currentSettings.codebaseIndexQdrantUrl = DEFAULT_QDRANT_URL - updateSetting("codebaseIndexQdrantUrl", DEFAULT_QDRANT_URL) - } - }} - placeholder={t("settings:codeIndex.qdrantUrlPlaceholder")} - className={cn("w-full", { - "border-red-500": formErrors.codebaseIndexQdrantUrl, - })} - /> - {formErrors.codebaseIndexQdrantUrl && ( -

- {formErrors.codebaseIndexQdrantUrl} -

- )} +
+ {/* Qdrant Settings */} + {currentSettings.codebaseIndexVectorStoreProvider === "qdrant" && ( + <> +
+ + + updateSetting("codebaseIndexQdrantUrl", e.target.value) + } + onBlur={(e: any) => { + // Set default Qdrant URL if field is empty + if (!e.target.value.trim()) { + currentSettings.codebaseIndexQdrantUrl = DEFAULT_QDRANT_URL + updateSetting("codebaseIndexQdrantUrl", DEFAULT_QDRANT_URL) + } + }} + placeholder={t("settings:codeIndex.qdrantUrlPlaceholder")} + className={cn("w-full", { + "border-red-500": formErrors.codebaseIndexQdrantUrl, + })} + /> + {formErrors.codebaseIndexQdrantUrl && ( +

+ {formErrors.codebaseIndexQdrantUrl} +

+ )} +
-
- - updateSetting("codeIndexQdrantApiKey", e.target.value)} - placeholder={t("settings:codeIndex.qdrantApiKeyPlaceholder")} - className={cn("w-full", { - "border-red-500": formErrors.codeIndexQdrantApiKey, - })} - /> - {formErrors.codeIndexQdrantApiKey && ( -

- {formErrors.codeIndexQdrantApiKey} +

+ + + updateSetting("codeIndexQdrantApiKey", e.target.value) + } + placeholder={t("settings:codeIndex.qdrantApiKeyPlaceholder")} + className={cn("w-full", { + "border-red-500": formErrors.codeIndexQdrantApiKey, + })} + /> + {formErrors.codeIndexQdrantApiKey && ( +

+ {formErrors.codeIndexQdrantApiKey} +

+ )} +
+ + )} + + {/* Local Vector Store Settings */} + {currentSettings.codebaseIndexVectorStoreProvider === "local" && ( +
+ + + updateSetting( + "codebaseIndexLocalVectorStoreDirectory", + e.target.value, + ) + } + placeholder={t( + "settings:codeIndex.localVectorStoreDirectoryPlaceholder", + )} + className="w-full" + /> +

+ {t("settings:codeIndex.localVectorStoreDirectoryDescription")}

- )} -
+
+ )} )} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index da7ab63358..37e0377395 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -238,6 +238,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://localhost:6333", codebaseIndexEmbedderProvider: "openai", + codebaseIndexVectorStoreProvider: "qdrant", + codebaseIndexLocalVectorStoreDirectory: undefined, codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", codebaseIndexSearchMaxResults: undefined, diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 82c8f40516..81c695fbd5 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Introduïu el nom del model", "selectModel": "Seleccioneu un model", "ollamaBaseUrlLabel": "URL base d'Ollama", + "vectorStoreProviderLabel": "Proveïdor d'emmagatzematge de vectors", "qdrantApiKeyLabel": "Clau API de Qdrant", "qdrantApiKeyPlaceholder": "Introduïu la vostra clau API de Qdrant (opcional)", + "localVectorStoreDirectoryLabel": "Camí d'emmagatzematge de vectors local", + "localVectorStoreDirectoryPlaceholder": "Introduïu un camí d'emmagatzematge de vectors personalitzat (opcional)", + "localVectorStoreDirectoryDescription": "Camí per emmagatzemar la base de dades de vectors local. Si està buit, utilitza la ubicació per defecte a globalStorageUri/vector.", "setupConfigLabel": "Configuració", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index df61c3142e..a0f432f6ad 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -71,10 +71,14 @@ "selectModelPlaceholder": "Modell auswählen", "ollamaUrlLabel": "Ollama-URL:", "ollamaBaseUrlLabel": "Ollama Basis-URL", + "vectorStoreProviderLabel": "Vektorspeicher-Anbieter", "qdrantUrlLabel": "Qdrant-URL", "qdrantKeyLabel": "Qdrant-Schlüssel:", "qdrantApiKeyLabel": "Qdrant API-Schlüssel", "qdrantApiKeyPlaceholder": "Gib deinen Qdrant API-Schlüssel ein (optional)", + "localVectorStoreDirectoryLabel": "Lokaler Vektorspeicher Pfad", + "localVectorStoreDirectoryPlaceholder": "Gib einen benutzerdefinierten Vektorspeicher Pfad ein (optional)", + "localVectorStoreDirectoryDescription": "Pfad zur Speicherung der lokalen Vektordatenbank. Falls leer, wird der Standardort in globalStorageUri/vector verwendet.", "setupConfigLabel": "Einrichtung", "startIndexingButton": "Start", "clearIndexDataButton": "Index löschen", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 11c575bdf3..f90f359360 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -71,10 +71,14 @@ "selectModelPlaceholder": "Select model", "ollamaUrlLabel": "Ollama URL:", "ollamaBaseUrlLabel": "Ollama Base URL", + "vectorStoreProviderLabel": "Vector Store Provider", "qdrantUrlLabel": "Qdrant URL", "qdrantKeyLabel": "Qdrant Key:", "qdrantApiKeyLabel": "Qdrant API Key", "qdrantApiKeyPlaceholder": "Enter your Qdrant API key (optional)", + "localVectorStoreDirectoryLabel": "Local Vector Store Path", + "localVectorStoreDirectoryPlaceholder": "Enter custom vector store path (optional)", + "localVectorStoreDirectoryDescription": "Path to store the local vector database. If empty, uses the default location in the globalStorageUri/vector.", "setupConfigLabel": "Setup", "advancedConfigLabel": "Advanced Configuration", "searchMinScoreLabel": "Search Score Threshold", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 3afeb091ef..37af708668 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -71,10 +71,14 @@ "selectModelPlaceholder": "Seleccionar modelo", "ollamaUrlLabel": "URL de Ollama:", "ollamaBaseUrlLabel": "URL base de Ollama", + "vectorStoreProviderLabel": "Proveedor de almacén de vectores", "qdrantUrlLabel": "URL de Qdrant", "qdrantKeyLabel": "Clave de Qdrant:", "qdrantApiKeyLabel": "Clave API de Qdrant", "qdrantApiKeyPlaceholder": "Introduce tu clave API de Qdrant (opcional)", + "localVectorStoreDirectoryLabel": "Ruta del almacén de vectores local", + "localVectorStoreDirectoryPlaceholder": "Introduce una ruta personalizada del almacén de vectores (opcional)", + "localVectorStoreDirectoryDescription": "Ruta para almacenar la base de datos de vectores local. Si está vacía, usa la ubicación predeterminada en globalStorageUri/vector.", "setupConfigLabel": "Configuración", "startIndexingButton": "Iniciar", "clearIndexDataButton": "Borrar índice", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 5b1c0431fe..ac993473fd 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -71,10 +71,14 @@ "selectModelPlaceholder": "Sélectionner un modèle", "ollamaUrlLabel": "URL Ollama :", "ollamaBaseUrlLabel": "URL de base Ollama", + "vectorStoreProviderLabel": "Fournisseur de stockage vectoriel", "qdrantUrlLabel": "URL Qdrant", "qdrantKeyLabel": "Clé Qdrant :", "qdrantApiKeyLabel": "Clé API Qdrant", "qdrantApiKeyPlaceholder": "Entrez votre clé API Qdrant (optionnel)", + "localVectorStoreDirectoryLabel": "Chemin du stockage vectoriel local", + "localVectorStoreDirectoryPlaceholder": "Entrez un chemin personnalisé du stockage vectoriel (optionnel)", + "localVectorStoreDirectoryDescription": "Chemin pour stocker la base de données vectorielle locale. Si vide, utilise l'emplacement par défaut dans globalStorageUri/vector.", "setupConfigLabel": "Configuration", "startIndexingButton": "Démarrer", "clearIndexDataButton": "Effacer l'index", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0c7ab3a0bc..31c6f1fd85 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "मॉडल नाम दर्ज करें", "selectModel": "एक मॉडल चुनें", "ollamaBaseUrlLabel": "Ollama आधार URL", + "vectorStoreProviderLabel": "वेक्टर स्टोर प्रदाता", "qdrantApiKeyLabel": "Qdrant API कुंजी", "qdrantApiKeyPlaceholder": "अपनी Qdrant API कुंजी दर्ज करें (वैकल्पिक)", + "localVectorStoreDirectoryLabel": "स्थानीय वेक्टर स्टोर पथ", + "localVectorStoreDirectoryPlaceholder": "कस्टम वेक्टर स्टोर पथ दर्ज करें (वैकल्पिक)", + "localVectorStoreDirectoryDescription": "स्थानीय वेक्टर डेटाबेस संग्रहीत करने के लिए पथ। यदि खाली है, तो globalStorageUri/vector में डिफ़ॉल्ट स्थान का उपयोग करता है।", "setupConfigLabel": "सेटअप", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index fc1b1915ab..1ff319ec3f 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Masukkan nama model", "selectModel": "Pilih model", "ollamaBaseUrlLabel": "URL Dasar Ollama", + "vectorStoreProviderLabel": "Penyedia Penyimpanan Vektor", "qdrantApiKeyLabel": "Kunci API Qdrant", "qdrantApiKeyPlaceholder": "Masukkan kunci API Qdrant kamu (opsional)", + "localVectorStoreDirectoryLabel": "Jalur Penyimpanan Vektor Lokal", + "localVectorStoreDirectoryPlaceholder": "Masukkan jalur penyimpanan vektor kustom (opsional)", + "localVectorStoreDirectoryDescription": "Jalur untuk menyimpan database vektor lokal. Jika kosong, menggunakan lokasi default di globalStorageUri/vector.", "setupConfigLabel": "Pengaturan", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 3b82f073b3..e5a29ce89c 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Inserisci il nome del modello", "selectModel": "Seleziona un modello", "ollamaBaseUrlLabel": "URL base Ollama", + "vectorStoreProviderLabel": "Fornitore di archivio vettoriale", "qdrantApiKeyLabel": "Chiave API Qdrant", "qdrantApiKeyPlaceholder": "Inserisci la tua chiave API Qdrant (opzionale)", + "localVectorStoreDirectoryLabel": "Percorso archivio vettoriale locale", + "localVectorStoreDirectoryPlaceholder": "Inserisci un percorso personalizzato dell'archivio vettoriale (opzionale)", + "localVectorStoreDirectoryDescription": "Percorso per archiviare il database vettoriale locale. Se vuoto, usa la posizione predefinita in globalStorageUri/vector.", "setupConfigLabel": "Impostazione", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 321b269a8a..6689c3ba96 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "モデル名を入力", "selectModel": "モデルを選択", "ollamaBaseUrlLabel": "Ollama ベースURL", + "vectorStoreProviderLabel": "ベクターストアプロバイダー", "qdrantApiKeyLabel": "Qdrant APIキー", "qdrantApiKeyPlaceholder": "Qdrant APIキーを入力(オプション)", + "localVectorStoreDirectoryLabel": "ローカルベクターストアパス", + "localVectorStoreDirectoryPlaceholder": "カスタムベクターストアパスを入力(オプション)", + "localVectorStoreDirectoryDescription": "ローカルベクターデータベースを格納するパス。空の場合、globalStorageUri/vector のデフォルトの場所を使用します。", "setupConfigLabel": "設定", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index d286ac71a2..6d1f5a9a6c 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "모델 이름을 입력하세요", "selectModel": "모델 선택", "ollamaBaseUrlLabel": "Ollama 기본 URL", + "vectorStoreProviderLabel": "벡터 스토어 제공자", "qdrantApiKeyLabel": "Qdrant API 키", "qdrantApiKeyPlaceholder": "Qdrant API 키를 입력하세요 (선택사항)", + "localVectorStoreDirectoryLabel": "로컬 벡터 스토어 경로", + "localVectorStoreDirectoryPlaceholder": "사용자 정의 벡터 스토어 경로를 입력하세요 (선택사항)", + "localVectorStoreDirectoryDescription": "로컬 벡터 데이터베이스를 저장할 경로입니다. 비어있으면 globalStorageUri/vector의 기본 위치를 사용합니다.", "setupConfigLabel": "설정", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index e8c1db5ace..c79aa06c7f 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Voer modelnaam in", "selectModel": "Selecteer een model", "ollamaBaseUrlLabel": "Ollama Basis-URL", + "vectorStoreProviderLabel": "Vectoropslag Provider", "qdrantApiKeyLabel": "Qdrant API-sleutel", "qdrantApiKeyPlaceholder": "Voer je Qdrant API-sleutel in (optioneel)", + "localVectorStoreDirectoryLabel": "Lokaal vectoropslag pad", + "localVectorStoreDirectoryPlaceholder": "Voer een aangepast vectoropslag pad in (optioneel)", + "localVectorStoreDirectoryDescription": "Pad om de lokale vectordatabase op te slaan. Als leeg, gebruikt het de standaardlocatie in globalStorageUri/vector.", "setupConfigLabel": "Instellen", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index ab208ffe14..46bbb35b1a 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Wprowadź nazwę modelu", "selectModel": "Wybierz model", "ollamaBaseUrlLabel": "Bazowy URL Ollama", + "vectorStoreProviderLabel": "Dostawca magazynu wektorów", "qdrantApiKeyLabel": "Klucz API Qdrant", "qdrantApiKeyPlaceholder": "Wprowadź swój klucz API Qdrant (opcjonalnie)", + "localVectorStoreDirectoryLabel": "Ścieżka lokalnego magazynu wektorów", + "localVectorStoreDirectoryPlaceholder": "Wprowadź niestandardową ścieżkę magazynu wektorów (opcjonalnie)", + "localVectorStoreDirectoryDescription": "Ścieżka do przechowywania lokalnej bazy danych wektorów. Jeśli pusta, używa domyślnej lokalizacji w globalStorageUri/vector.", "setupConfigLabel": "Konfiguracja", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 6bcfbb564c..1e97fe930e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Insira o nome do modelo", "selectModel": "Selecione um modelo", "ollamaBaseUrlLabel": "URL Base do Ollama", + "vectorStoreProviderLabel": "Provedor de Armazenamento de Vetores", "qdrantApiKeyLabel": "Chave da API Qdrant", "qdrantApiKeyPlaceholder": "Insira sua chave da API Qdrant (opcional)", + "localVectorStoreDirectoryLabel": "Caminho do Armazenamento de Vetores Local", + "localVectorStoreDirectoryPlaceholder": "Insira um caminho personalizado do armazenamento de vetores (opcional)", + "localVectorStoreDirectoryDescription": "Caminho para armazenar o banco de dados de vetores local. Se vazio, usa a localização padrão em globalStorageUri/vector.", "setupConfigLabel": "Configuração", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 8d52241d6c..c91a1589e9 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Введите название модели", "selectModel": "Выберите модель", "ollamaBaseUrlLabel": "Базовый URL Ollama", + "vectorStoreProviderLabel": "Поставщик векторного хранилища", "qdrantApiKeyLabel": "API-ключ Qdrant", "qdrantApiKeyPlaceholder": "Введите ваш API-ключ Qdrant (необязательно)", + "localVectorStoreDirectoryLabel": "Путь локального векторного хранилища", + "localVectorStoreDirectoryPlaceholder": "Введите пользовательский путь векторного хранилища (необязательно)", + "localVectorStoreDirectoryDescription": "Путь для хранения локальной векторной базы данных. Если пустой, используется местоположение по умолчанию в globalStorageUri/vector.", "setupConfigLabel": "Настройка", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 486dad0540..9e32fa9873 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Model adını girin", "selectModel": "Bir model seçin", "ollamaBaseUrlLabel": "Ollama Temel URL", + "vectorStoreProviderLabel": "Vektör Depolama Sağlayıcısı", "qdrantApiKeyLabel": "Qdrant API Anahtarı", "qdrantApiKeyPlaceholder": "Qdrant API anahtarınızı girin (isteğe bağlı)", + "localVectorStoreDirectoryLabel": "Yerel Vektör Depolama Yolu", + "localVectorStoreDirectoryPlaceholder": "Özel vektör depolama yolu girin (isteğe bağlı)", + "localVectorStoreDirectoryDescription": "Yerel vektör veritabanının depolanacağı yol. Boşsa, globalStorageUri/vector konumunu kullanır.", "setupConfigLabel": "Kurulum", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index dbe0e73736..6287c74aa8 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "Nhập tên mô hình", "selectModel": "Chọn một mô hình", "ollamaBaseUrlLabel": "URL cơ sở Ollama", + "vectorStoreProviderLabel": "Nhà cung cấp kho lưu trữ vector", "qdrantApiKeyLabel": "Khóa API Qdrant", "qdrantApiKeyPlaceholder": "Nhập khóa API Qdrant của bạn (tùy chọn)", + "localVectorStoreDirectoryLabel": "Đường dẫn kho lưu trữ vector cục bộ", + "localVectorStoreDirectoryPlaceholder": "Nhập đường dẫn kho lưu trữ vector tùy chỉnh (tùy chọn)", + "localVectorStoreDirectoryDescription": "Đường dẫn để lưu trữ cơ sở dữ liệu vector cục bộ. Nếu trống, sử dụng vị trí mặc định trong globalStorageUri/vector.", "setupConfigLabel": "Cài đặt", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 32e5c96d02..e4b2d1986b 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -71,10 +71,14 @@ "selectModelPlaceholder": "选择模型", "ollamaUrlLabel": "Ollama URL:", "ollamaBaseUrlLabel": "Ollama 基础 URL", + "vectorStoreProviderLabel": "向量存储提供商", "qdrantUrlLabel": "Qdrant URL", "qdrantKeyLabel": "Qdrant 密钥:", "qdrantApiKeyLabel": "Qdrant API 密钥", "qdrantApiKeyPlaceholder": "输入你的 Qdrant API 密钥(可选)", + "localVectorStoreDirectoryLabel": "本地向量存储路径", + "localVectorStoreDirectoryPlaceholder": "输入自定义向量存储路径(可选)", + "localVectorStoreDirectoryDescription": "存储本地向量数据库的路径。如果为空,将使用 globalStorageUri/vector 的默认位置。", "setupConfigLabel": "设置", "startIndexingButton": "开始", "clearIndexDataButton": "清除索引", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index b8e09bc373..ab32f609d1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -82,8 +82,12 @@ "modelPlaceholder": "輸入模型名稱", "selectModel": "選擇模型", "ollamaBaseUrlLabel": "Ollama 基礎 URL", + "vectorStoreProviderLabel": "向量儲存提供者", "qdrantApiKeyLabel": "Qdrant API 金鑰", "qdrantApiKeyPlaceholder": "輸入您的 Qdrant API 金鑰(選用)", + "localVectorStoreDirectoryLabel": "本地向量儲存路徑", + "localVectorStoreDirectoryPlaceholder": "輸入自訂向量儲存路徑(選用)", + "localVectorStoreDirectoryDescription": "儲存本地向量資料庫的路徑。如果為空,將使用 globalStorageUri/vector 的預設位置。", "setupConfigLabel": "設定", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",