Implement collection scopes for vector models (#2846) * Implement collection scopes for vector models * Update makersuite-vectors.js to use Gemini API text-embedding-004 model * Add model scope for Google vectors * Fix purge log * Refactor header setting * Fix typo * Only display UI warning if scopes disabled * Remove i18n attribute --------- Co-authored-by: ceruleandeep <83318388+ceruleandeep@users.noreply.github.com>

9ef33852553975b0cc132e97454cab87cf7a134a

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
4 files changed, +158 -174Showing whitespace changes
default/config.yaml+3 -0
@@ -108,6 +108,9 @@ enableExtensionsAutoUpdate: true
108# Additional model tokenizers can be downloaded on demand.108# Additional model tokenizers can be downloaded on demand.
109# Disabling will fallback to another locally available tokenizer.109# Disabling will fallback to another locally available tokenizer.
110enableDownloadableTokenizers: true110enableDownloadableTokenizers: true
111# Vector storage settings
112vectors:
113 enableModelScopes: false
111# Extension settings114# Extension settings
112extras:115extras:
113 # Disables automatic model download from HuggingFace116 # Disables automatic model download from HuggingFace
public/scripts/extensions/vectors/index.js+47 -84
@@ -718,7 +718,7 @@ async function getQueryText(chat, initiator) {
718async function getSavedHashes(collectionId) {718async function getSavedHashes(collectionId) {
719 const response = await fetch('/api/vector/list', {719 const response = await fetch('/api/vector/list', {
720 method: 'POST',720 method: 'POST',
721 headers: getRequestHeaders(),721 headers: getVectorHeaders(),
722 body: JSON.stringify({722 body: JSON.stringify({
723 collectionId: collectionId,723 collectionId: collectionId,
724 source: settings.source,724 source: settings.source,
@@ -737,105 +737,48 @@ function getVectorHeaders() {
737 const headers = getRequestHeaders();737 const headers = getRequestHeaders();
738 switch (settings.source) {738 switch (settings.source) {
739 case 'extras':739 case 'extras':
740 addExtrasHeaders(headers);
741 break;
742 case 'togetherai':
743 addTogetherAiHeaders(headers);
744 break;
745 case 'openai':
746 addOpenAiHeaders(headers);
747 break;
748 case 'cohere':
749 addCohereHeaders(headers);
750 break;
751 case 'ollama':
752 addOllamaHeaders(headers);
753 break;
754 case 'llamacpp':
755 addLlamaCppHeaders(headers);
756 break;
757 case 'vllm':
758 addVllmHeaders(headers);
759 break;
760 default:
761 break;
762 }
763 return headers;
764}
765
766/**
767 * Add headers for the Extras API source.
768 * @param {object} headers Headers object
769 */
770function addExtrasHeaders(headers) {
771 console.log(`Vector source is extras, populating API URL: ${extension_settings.apiUrl}`);
772 Object.assign(headers, {740 Object.assign(headers, {
773 'X-Extras-Url': extension_settings.apiUrl,741 'X-Extras-Url': extension_settings.apiUrl,
774 'X-Extras-Key': extension_settings.apiKey,742 'X-Extras-Key': extension_settings.apiKey,
775 });743 });
776}744 break;
777745 case 'togetherai':
778/**
779 * Add headers for the TogetherAI API source.
780 * @param {object} headers Headers object
781 */
782function addTogetherAiHeaders(headers) {
783 Object.assign(headers, {746 Object.assign(headers, {
784 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,747 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,
785 });748 });
786}749 break;
787750 case 'openai':
788/**
789 * Add headers for the OpenAI API source.
790 * @param {object} headers Header object
791 */
792function addOpenAiHeaders(headers) {
793 Object.assign(headers, {751 Object.assign(headers, {
794 'X-OpenAI-Model': extension_settings.vectors.openai_model,752 'X-OpenAI-Model': extension_settings.vectors.openai_model,
795 });753 });
796}754 break;
797755 case 'cohere':
798/**
799 * Add headers for the Cohere API source.
800 * @param {object} headers Header object
801 */
802function addCohereHeaders(headers) {
803 Object.assign(headers, {756 Object.assign(headers, {
804 'X-Cohere-Model': extension_settings.vectors.cohere_model,757 'X-Cohere-Model': extension_settings.vectors.cohere_model,
805 });758 });
806}759 break;
807760 case 'ollama':
808/**
809 * Add headers for the Ollama API source.
810 * @param {object} headers Header object
811 */
812function addOllamaHeaders(headers) {
813 Object.assign(headers, {761 Object.assign(headers, {
814 'X-Ollama-Model': extension_settings.vectors.ollama_model,762 'X-Ollama-Model': extension_settings.vectors.ollama_model,
815 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],763 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],
816 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,764 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,
817 });765 });
818}766 break;
819767 case 'llamacpp':
820/**
821 * Add headers for the LlamaCpp API source.
822 * @param {object} headers Header object
823 */
824function addLlamaCppHeaders(headers) {
825 Object.assign(headers, {768 Object.assign(headers, {
826 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],769 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],
827 });770 });
828}771 break;
829772 case 'vllm':
830/**
831 * Add headers for the VLLM API source.
832 * @param {object} headers Header object
833 */
834function addVllmHeaders(headers) {
835 Object.assign(headers, {773 Object.assign(headers, {
836 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],774 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],
837 'X-Vllm-Model': extension_settings.vectors.vllm_model,775 'X-Vllm-Model': extension_settings.vectors.vllm_model,
838 });776 });
777 break;
778 default:
779 break;
780 }
781 return headers;
839}782}
840783
841/**784/**
@@ -901,7 +844,7 @@ function throwIfSourceInvalid() {
901async function deleteVectorItems(collectionId, hashes) {844async function deleteVectorItems(collectionId, hashes) {
902 const response = await fetch('/api/vector/delete', {845 const response = await fetch('/api/vector/delete', {
903 method: 'POST',846 method: 'POST',
904 headers: getRequestHeaders(),847 headers: getVectorHeaders(),
905 body: JSON.stringify({848 body: JSON.stringify({
906 collectionId: collectionId,849 collectionId: collectionId,
907 hashes: hashes,850 hashes: hashes,
@@ -987,7 +930,7 @@ async function purgeFileVectorIndex(fileUrl) {
987930
988 const response = await fetch('/api/vector/purge', {931 const response = await fetch('/api/vector/purge', {
989 method: 'POST',932 method: 'POST',
990 headers: getRequestHeaders(),933 headers: getVectorHeaders(),
991 body: JSON.stringify({934 body: JSON.stringify({
992 collectionId: collectionId,935 collectionId: collectionId,
993 }),936 }),
@@ -1016,7 +959,7 @@ async function purgeVectorIndex(collectionId) {
1016959
1017 const response = await fetch('/api/vector/purge', {960 const response = await fetch('/api/vector/purge', {
1018 method: 'POST',961 method: 'POST',
1019 headers: getRequestHeaders(),962 headers: getVectorHeaders(),
1020 body: JSON.stringify({963 body: JSON.stringify({
1021 collectionId: collectionId,964 collectionId: collectionId,
1022 }),965 }),
@@ -1041,7 +984,7 @@ async function purgeAllVectorIndexes() {
1041 try {984 try {
1042 const response = await fetch('/api/vector/purge-all', {985 const response = await fetch('/api/vector/purge-all', {
1043 method: 'POST',986 method: 'POST',
1044 headers: getRequestHeaders(),987 headers: getVectorHeaders(),
1045 });988 });
1046989
1047 if (!response.ok) {990 if (!response.ok) {
@@ -1056,6 +999,25 @@ async function purgeAllVectorIndexes() {
1056 }999 }
1057}1000}
10581001
1002async function isModelScopesEnabled() {
1003 try {
1004 const response = await fetch('/api/vector/scopes-enabled', {
1005 method: 'GET',
1006 headers: getVectorHeaders(),
1007 });
1008
1009 if (!response.ok) {
1010 return false;
1011 }
1012
1013 const data = await response.json();
1014 return data?.enabled ?? false;
1015 } catch (error) {
1016 console.error('Vectors: Failed to check model scopes', error);
1017 return false;
1018 }
1019}
1020
1059function toggleSettings() {1021function toggleSettings() {
1060 $('#vectors_files_settings').toggle(!!settings.enabled_files);1022 $('#vectors_files_settings').toggle(!!settings.enabled_files);
1061 $('#vectors_chats_settings').toggle(!!settings.enabled_chats);1023 $('#vectors_chats_settings').toggle(!!settings.enabled_chats);
@@ -1320,6 +1282,7 @@ jQuery(async () => {
1320 }1282 }
13211283
1322 Object.assign(settings, extension_settings.vectors);1284 Object.assign(settings, extension_settings.vectors);
1285 const scopesEnabled = await isModelScopesEnabled();
13231286
1324 // Migrate from TensorFlow to Transformers1287 // Migrate from TensorFlow to Transformers
1325 settings.source = settings.source !== 'local' ? settings.source : 'transformers';1288 settings.source = settings.source !== 'local' ? settings.source : 'transformers';
@@ -1371,31 +1334,31 @@ jQuery(async () => {
1371 saveSettingsDebounced();1334 saveSettingsDebounced();
1372 });1335 });
1373 $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {1336 $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => {
1374 $('#vectors_modelWarning').show();1337 !scopesEnabled && $('#vectors_modelWarning').show();
1375 settings.togetherai_model = String($('#vectors_togetherai_model').val());1338 settings.togetherai_model = String($('#vectors_togetherai_model').val());
1376 Object.assign(extension_settings.vectors, settings);1339 Object.assign(extension_settings.vectors, settings);
1377 saveSettingsDebounced();1340 saveSettingsDebounced();
1378 });1341 });
1379 $('#vectors_openai_model').val(settings.openai_model).on('change', () => {1342 $('#vectors_openai_model').val(settings.openai_model).on('change', () => {
1380 $('#vectors_modelWarning').show();1343 !scopesEnabled && $('#vectors_modelWarning').show();
1381 settings.openai_model = String($('#vectors_openai_model').val());1344 settings.openai_model = String($('#vectors_openai_model').val());
1382 Object.assign(extension_settings.vectors, settings);1345 Object.assign(extension_settings.vectors, settings);
1383 saveSettingsDebounced();1346 saveSettingsDebounced();
1384 });1347 });
1385 $('#vectors_cohere_model').val(settings.cohere_model).on('change', () => {1348 $('#vectors_cohere_model').val(settings.cohere_model).on('change', () => {
1386 $('#vectors_modelWarning').show();1349 !scopesEnabled && $('#vectors_modelWarning').show();
1387 settings.cohere_model = String($('#vectors_cohere_model').val());1350 settings.cohere_model = String($('#vectors_cohere_model').val());
1388 Object.assign(extension_settings.vectors, settings);1351 Object.assign(extension_settings.vectors, settings);
1389 saveSettingsDebounced();1352 saveSettingsDebounced();
1390 });1353 });
1391 $('#vectors_ollama_model').val(settings.ollama_model).on('input', () => {1354 $('#vectors_ollama_model').val(settings.ollama_model).on('input', () => {
1392 $('#vectors_modelWarning').show();1355 !scopesEnabled && $('#vectors_modelWarning').show();
1393 settings.ollama_model = String($('#vectors_ollama_model').val());1356 settings.ollama_model = String($('#vectors_ollama_model').val());
1394 Object.assign(extension_settings.vectors, settings);1357 Object.assign(extension_settings.vectors, settings);
1395 saveSettingsDebounced();1358 saveSettingsDebounced();
1396 });1359 });
1397 $('#vectors_vllm_model').val(settings.vllm_model).on('input', () => {1360 $('#vectors_vllm_model').val(settings.vllm_model).on('input', () => {
1398 $('#vectors_modelWarning').show();1361 !scopesEnabled && $('#vectors_modelWarning').show();
1399 settings.vllm_model = String($('#vectors_vllm_model').val());1362 settings.vllm_model = String($('#vectors_vllm_model').val());
1400 Object.assign(extension_settings.vectors, settings);1363 Object.assign(extension_settings.vectors, settings);
1401 saveSettingsDebounced();1364 saveSettingsDebounced();
public/scripts/extensions/vectors/settings.html+3 -2
@@ -98,8 +98,9 @@
9898
99 <small id="vectors_modelWarning">99 <small id="vectors_modelWarning">
100 <i class="fa-solid fa-exclamation-triangle"></i>100 <i class="fa-solid fa-exclamation-triangle"></i>
101 <span data-i18n="Vectors Model Warning">101 <span>
102 It is recommended to purge vectors when changing the model mid-chat. Otherwise, it will lead to sub-par results.102 Set <code>vectors.enableModelScopes</code> to true in config.yaml to switch between vectorization models without needing to purge existing vectors.
103 This option will soon be enabled by default.
103 </span>104 </span>
104 </small>105 </small>
105106
src/endpoints/vectors.js+105 -88
@@ -4,6 +4,7 @@ const fs = require('fs');
4const express = require('express');4const express = require('express');
5const sanitize = require('sanitize-filename');5const sanitize = require('sanitize-filename');
6const { jsonParser } = require('../express-common');6const { jsonParser } = require('../express-common');
7const { getConfigValue, color } = require('../util');
78
8// Don't forget to add new sources to the SOURCES array9// Don't forget to add new sources to the SOURCES array
9const SOURCES = [10const SOURCES = [
@@ -110,18 +111,94 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
110}111}
111112
112/**113/**
114 * Extracts settings for the vectorization sources from the HTTP request headers.
115 * @param {string} source - Which source to extract settings for.
116 * @param {object} request - The HTTP request object.
117 * @returns {object} - An object that can be used as `sourceSettings` in functions that take that parameter.
118 */
119function getSourceSettings(source, request) {
120 switch (source) {
121 case 'togetherai':
122 return {
123 model: String(request.headers['x-togetherai-model']),
124 };
125 case 'openai':
126 return {
127 model: String(request.headers['x-openai-model']),
128 };
129 case 'cohere':
130 return {
131 model: String(request.headers['x-cohere-model']),
132 };
133 case 'llamacpp':
134 return {
135 apiUrl: String(request.headers['x-llamacpp-url']),
136 };
137 case 'vllm':
138 return {
139 apiUrl: String(request.headers['x-vllm-url']),
140 model: String(request.headers['x-vllm-model']),
141 };
142 case 'ollama':
143 return {
144 apiUrl: String(request.headers['x-ollama-url']),
145 model: String(request.headers['x-ollama-model']),
146 keep: Boolean(request.headers['x-ollama-keep']),
147 };
148 case 'extras':
149 return {
150 extrasUrl: String(request.headers['x-extras-url']),
151 extrasKey: String(request.headers['x-extras-key']),
152 };
153 case 'local':
154 return {
155 model: getConfigValue('extras.embeddingModel', ''),
156 };
157 case 'palm':
158 return {
159 // TODO: Add support for multiple models
160 model: 'text-embedding-004',
161 };
162 default:
163 return {};
164 }
165}
166
167/**
168 * Gets the model scope for the source.
169 * @param {object} sourceSettings - The settings for the source
170 * @returns {string} The model scope for the source
171 */
172function getModelScope(sourceSettings) {
173 const scopesEnabled = getConfigValue('vectors.enableModelScopes', false);
174 const warningShown = global.process.env.VECTORS_MODEL_SCOPE_WARNING_SHOWN === 'true';
175
176 if (!scopesEnabled && !warningShown) {
177 console.log();
178 console.warn(color.red('[DEPRECATION NOTICE]'), 'Model scopes for Vectore Storage are disabled, but will soon be required.');
179 console.log(`To enable model scopes, set the ${color.cyan('vectors.enableModelScopes')} in config.yaml to ${color.green(true)}.`);
180 console.log('This message won\'t be shown again in the current session.');
181 console.log();
182 global.process.env.VECTORS_MODEL_SCOPE_WARNING_SHOWN = 'true';
183 }
184
185 return scopesEnabled ? (sourceSettings?.model || '') : '';
186}
187
188/**
113 * Gets the index for the vector collection189 * Gets the index for the vector collection
114 * @param {import('../users').UserDirectoryList} directories - User directories190 * @param {import('../users').UserDirectoryList} directories - User directories
115 * @param {string} collectionId - The collection ID191 * @param {string} collectionId - The collection ID
116 * @param {string} source - The source of the vector192 * @param {string} source - The source of the vector
117 * @param {boolean} create - Whether to create the index if it doesn't exist193 * @param {object} sourceSettings - The model for the source
118 * @returns {Promise<vectra.LocalIndex>} - The index for the collection194 * @returns {Promise<vectra.LocalIndex>} - The index for the collection
119 */195 */
120async function getIndex(directories, collectionId, source, create = true) {196async function getIndex(directories, collectionId, source, sourceSettings) {
121 const pathToFile = path.join(directories.vectors, sanitize(source), sanitize(collectionId));197 const model = getModelScope(sourceSettings);
198 const pathToFile = path.join(directories.vectors, sanitize(source), sanitize(collectionId), sanitize(model));
122 const store = new vectra.LocalIndex(pathToFile);199 const store = new vectra.LocalIndex(pathToFile);
123200
124 if (create && !await store.isIndexCreated()) {201 if (!await store.isIndexCreated()) {
125 await store.createIndex();202 await store.createIndex();
126 }203 }
127204
@@ -137,7 +214,7 @@ async function getIndex(directories, collectionId, source, create = true) {
137 * @param {{ hash: number; text: string; index: number; }[]} items - The items to insert214 * @param {{ hash: number; text: string; index: number; }[]} items - The items to insert
138 */215 */
139async function insertVectorItems(directories, collectionId, source, sourceSettings, items) {216async function insertVectorItems(directories, collectionId, source, sourceSettings, items) {
140 const store = await getIndex(directories, collectionId, source);217 const store = await getIndex(directories, collectionId, source, sourceSettings);
141218
142 await store.beginUpdate();219 await store.beginUpdate();
143220
@@ -157,10 +234,11 @@ async function insertVectorItems(directories, collectionId, source, sourceSettin
157 * @param {import('../users').UserDirectoryList} directories - User directories234 * @param {import('../users').UserDirectoryList} directories - User directories
158 * @param {string} collectionId - The collection ID235 * @param {string} collectionId - The collection ID
159 * @param {string} source - The source of the vector236 * @param {string} source - The source of the vector
237 * @param {Object} sourceSettings - Settings for the source, if it needs any
160 * @returns {Promise<number[]>} - The hashes of the items in the collection238 * @returns {Promise<number[]>} - The hashes of the items in the collection
161 */239 */
162async function getSavedHashes(directories, collectionId, source) {240async function getSavedHashes(directories, collectionId, source, sourceSettings) {
163 const store = await getIndex(directories, collectionId, source);241 const store = await getIndex(directories, collectionId, source, sourceSettings);
164242
165 const items = await store.listItems();243 const items = await store.listItems();
166 const hashes = items.map(x => Number(x.metadata.hash));244 const hashes = items.map(x => Number(x.metadata.hash));
@@ -173,10 +251,11 @@ async function getSavedHashes(directories, collectionId, source) {
173 * @param {import('../users').UserDirectoryList} directories - User directories251 * @param {import('../users').UserDirectoryList} directories - User directories
174 * @param {string} collectionId - The collection ID252 * @param {string} collectionId - The collection ID
175 * @param {string} source - The source of the vector253 * @param {string} source - The source of the vector
254 * @param {Object} sourceSettings - Settings for the source, if it needs any
176 * @param {number[]} hashes - The hashes of the items to delete255 * @param {number[]} hashes - The hashes of the items to delete
177 */256 */
178async function deleteVectorItems(directories, collectionId, source, hashes) {257async function deleteVectorItems(directories, collectionId, source, sourceSettings, hashes) {
179 const store = await getIndex(directories, collectionId, source);258 const store = await getIndex(directories, collectionId, source, sourceSettings);
180 const items = await store.listItemsByMetadata({ hash: { '$in': hashes } });259 const items = await store.listItemsByMetadata({ hash: { '$in': hashes } });
181260
182 await store.beginUpdate();261 await store.beginUpdate();
@@ -200,7 +279,7 @@ async function deleteVectorItems(directories, collectionId, source, hashes) {
200 * @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text279 * @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text
201 */280 */
202async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {281async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {
203 const store = await getIndex(directories, collectionId, source);282 const store = await getIndex(directories, collectionId, source, sourceSettings);
204 const vector = await getVector(source, sourceSettings, searchText, true, directories);283 const vector = await getVector(source, sourceSettings, searchText, true, directories);
205284
206 const result = await store.queryItems(vector, topK);285 const result = await store.queryItems(vector, topK);
@@ -226,7 +305,7 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
226 const results = [];305 const results = [];
227306
228 for (const collectionId of collectionIds) {307 for (const collectionId of collectionIds) {
229 const store = await getIndex(directories, collectionId, source);308 const store = await getIndex(directories, collectionId, source, sourceSettings);
230 const result = await store.queryItems(vector, topK);309 const result = await store.queryItems(vector, topK);
231 results.push(...result.map(result => ({ collectionId, result })));310 results.push(...result.map(result => ({ collectionId, result })));
232 }311 }
@@ -255,71 +334,6 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
255}334}
256335
257/**336/**
258 * Extracts settings for the vectorization sources from the HTTP request headers.
259 * @param {string} source - Which source to extract settings for.
260 * @param {object} request - The HTTP request object.
261 * @returns {object} - An object that can be used as `sourceSettings` in functions that take that parameter.
262 */
263function getSourceSettings(source, request) {
264 if (source === 'togetherai') {
265 const model = String(request.headers['x-togetherai-model']);
266
267 return {
268 model: model,
269 };
270 } else if (source === 'openai') {
271 const model = String(request.headers['x-openai-model']);
272
273 return {
274 model: model,
275 };
276 } else if (source === 'cohere') {
277 const model = String(request.headers['x-cohere-model']);
278
279 return {
280 model: model,
281 };
282 } else if (source === 'llamacpp') {
283 const apiUrl = String(request.headers['x-llamacpp-url']);
284
285 return {
286 apiUrl: apiUrl,
287 };
288 } else if (source === 'vllm') {
289 const apiUrl = String(request.headers['x-vllm-url']);
290 const model = String(request.headers['x-vllm-model']);
291
292 return {
293 apiUrl: apiUrl,
294 model: model,
295 };
296 } else if (source === 'ollama') {
297 const apiUrl = String(request.headers['x-ollama-url']);
298 const model = String(request.headers['x-ollama-model']);
299 const keep = Boolean(request.headers['x-ollama-keep']);
300
301 return {
302 apiUrl: apiUrl,
303 model: model,
304 keep: keep,
305 };
306 } else {
307 // Extras API settings to connect to the Extras embeddings provider
308 let extrasUrl = '';
309 let extrasKey = '';
310 if (source === 'extras') {
311 extrasUrl = String(request.headers['x-extras-url']);
312 extrasKey = String(request.headers['x-extras-key']);
313 }
314
315 return {
316 extrasUrl: extrasUrl,
317 extrasKey: extrasKey,
318 };
319 }
320}
321
322/**
323 * Performs a request to regenerate the index if it is corrupted.337 * Performs a request to regenerate the index if it is corrupted.
324 * @param {import('express').Request} req Express request object338 * @param {import('express').Request} req Express request object
325 * @param {import('express').Response} res Express response object339 * @param {import('express').Response} res Express response object
@@ -330,9 +344,10 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
330 if (error instanceof SyntaxError && !req.query.regenerated) {344 if (error instanceof SyntaxError && !req.query.regenerated) {
331 const collectionId = String(req.body.collectionId);345 const collectionId = String(req.body.collectionId);
332 const source = String(req.body.source) || 'transformers';346 const source = String(req.body.source) || 'transformers';
347 const sourceSettings = getSourceSettings(source, req);
333348
334 if (collectionId && source) {349 if (collectionId && source) {
335 const index = await getIndex(req.user.directories, collectionId, source, false);350 const index = await getIndex(req.user.directories, collectionId, source, sourceSettings);
336 const exists = await index.isIndexCreated();351 const exists = await index.isIndexCreated();
337352
338 if (exists) {353 if (exists) {
@@ -350,6 +365,11 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
350365
351const router = express.Router();366const router = express.Router();
352367
368router.get('/scopes-enabled', (_req, res) => {
369 const scopesEnabled = getConfigValue('vectors.enableModelScopes', false);
370 return res.json({ enabled: scopesEnabled });
371});
372
353router.post('/query', jsonParser, async (req, res) => {373router.post('/query', jsonParser, async (req, res) => {
354 try {374 try {
355 if (!req.body.collectionId || !req.body.searchText) {375 if (!req.body.collectionId || !req.body.searchText) {
@@ -416,8 +436,9 @@ router.post('/list', jsonParser, async (req, res) => {
416436
417 const collectionId = String(req.body.collectionId);437 const collectionId = String(req.body.collectionId);
418 const source = String(req.body.source) || 'transformers';438 const source = String(req.body.source) || 'transformers';
439 const sourceSettings = getSourceSettings(source, req);
419440
420 const hashes = await getSavedHashes(req.user.directories, collectionId, source);441 const hashes = await getSavedHashes(req.user.directories, collectionId, source, sourceSettings);
421 return res.json(hashes);442 return res.json(hashes);
422 } catch (error) {443 } catch (error) {
423 return regenerateCorruptedIndexErrorHandler(req, res, error);444 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -433,8 +454,9 @@ router.post('/delete', jsonParser, async (req, res) => {
433 const collectionId = String(req.body.collectionId);454 const collectionId = String(req.body.collectionId);
434 const hashes = req.body.hashes.map(x => Number(x));455 const hashes = req.body.hashes.map(x => Number(x));
435 const source = String(req.body.source) || 'transformers';456 const source = String(req.body.source) || 'transformers';
457 const sourceSettings = getSourceSettings(source, req);
436458
437 await deleteVectorItems(req.user.directories, collectionId, source, hashes);459 await deleteVectorItems(req.user.directories, collectionId, source, sourceSettings, hashes);
438 return res.sendStatus(200);460 return res.sendStatus(200);
439 } catch (error) {461 } catch (error) {
440 return regenerateCorruptedIndexErrorHandler(req, res, error);462 return regenerateCorruptedIndexErrorHandler(req, res, error);
@@ -468,17 +490,12 @@ router.post('/purge', jsonParser, async (req, res) => {
468 const collectionId = String(req.body.collectionId);490 const collectionId = String(req.body.collectionId);
469491
470 for (const source of SOURCES) {492 for (const source of SOURCES) {
471 const index = await getIndex(req.user.directories, collectionId, source, false);493 const sourcePath = path.join(req.user.directories.vectors, sanitize(source), sanitize(collectionId));
472494 if (!fs.existsSync(sourcePath)) {
473 const exists = await index.isIndexCreated();
474
475 if (!exists) {
476 continue;495 continue;
477 }496 }
478497 await fs.promises.rm(sourcePath, { recursive: true });
479 const path = index.folderPath;498 console.log(`Deleted vector index at ${sourcePath}`);
480 await index.deleteIndex();
481 console.log(`Deleted vector index at ${path}`);
482 }499 }
483500
484 return res.sendStatus(200);501 return res.sendStatus(200);