mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
* fix: add embedder validation to prevent misleading status indicators (#4398) * fix: address PR feedback and fix critical issues - Fixed settings-save flow to save before validation - Fixed Error constructor usage in scanner.ts - Fixed segment identification in file-watcher.ts - Added missing translation keys for embedder validation errors * fix: add missing Ollama translation keys - Added missing ollama.title, description, and settings keys - Fixed translation check failure in CI/CD pipeline - Synchronized all 17 non-English locale files * feat: add proactive embedder validation on provider switch - Validate embedder connection when switching providers - Prevent misleading 'Indexed' status when embedder is unavailable - Show immediate error feedback for invalid configurations - Add comprehensive test coverage for validation flow This ensures users get immediate feedback when configuring embedders, preventing confusion when providers like Ollama are not accessible. * fix: improve error handling and validation in code indexing process * refactor: extract common embedder validation and error handling logic - Created shared/validation-helpers.ts with centralized error handling utilities - Refactored OpenAI, OpenAI-Compatible, and Ollama embedders to use shared helpers - Eliminated duplicate error handling code across embedders - Improved maintainability and consistency of error handling - Fixed test compatibility in manager.spec.ts - All 2721 tests passing * refactor: simplify validation helpers by removing unnecessary wrapper functions - Removed getErrorMessageForConnectionError and inlined logic into handleValidationError - Removed isRateLimitError, logRateLimitRetry, and logEmbeddingError wrapper functions - Updated openai.ts and openai-compatible.ts to inline rate limit checking and logging - Reduced code complexity while maintaining all functionality - All 311 tests continue to pass * fix: add missing invalidResponse i18n key and fix French translation - Added missing 'invalidResponse' key to all locale files - Fixed French translation: changed 'and accessible' to 'et accessible' - Ensures proper error messages are displayed when embedder returns invalid responses * fix: restore removed score settings in webviewMessageHandler - Restored codebaseIndexSearchMaxResults and codebaseIndexSearchMinScore settings that were unintentionally removed - Keep embedder validation related changes * fix: revert unintended changes to file-watcher and scanner - Reverted point ID generation back to using line numbers instead of segmentHash - Restored { cause: deleteError } parameter in scanner error handling - These changes were unrelated to the embedder validation feature --------- Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
7645aad435
commit
d4abe73875
34 changed files with 1801 additions and 119 deletions
|
|
@ -1843,8 +1843,12 @@ export const webviewMessageHandler = async (
|
|||
const settings = message.codeIndexSettings
|
||||
|
||||
try {
|
||||
// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
|
||||
// Check if embedder provider has changed
|
||||
const currentConfig = getGlobalState("codebaseIndexConfig") || {}
|
||||
const embedderProviderChanged =
|
||||
currentConfig.codebaseIndexEmbedderProvider !== settings.codebaseIndexEmbedderProvider
|
||||
|
||||
// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
|
||||
const globalStateConfig = {
|
||||
...currentConfig,
|
||||
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
|
||||
|
|
@ -1880,23 +1884,7 @@ export const webviewMessageHandler = async (
|
|||
)
|
||||
}
|
||||
|
||||
// Verify secrets are actually stored
|
||||
const storedOpenAiKey = provider.contextProxy.getSecret("codeIndexOpenAiKey")
|
||||
|
||||
// Notify code index manager of changes
|
||||
if (provider.codeIndexManager) {
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
|
||||
// Auto-start indexing if now enabled and configured
|
||||
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
|
||||
if (!provider.codeIndexManager.isInitialized) {
|
||||
await provider.codeIndexManager.initialize(provider.contextProxy)
|
||||
}
|
||||
provider.codeIndexManager.startIndexing()
|
||||
}
|
||||
}
|
||||
|
||||
// Send success response
|
||||
// Send success response first - settings are saved regardless of validation
|
||||
await provider.postMessageToWebview({
|
||||
type: "codeIndexSettingsSaved",
|
||||
success: true,
|
||||
|
|
@ -1905,6 +1893,61 @@ export const webviewMessageHandler = async (
|
|||
|
||||
// Update webview state
|
||||
await provider.postStateToWebview()
|
||||
|
||||
// Then handle validation and initialization
|
||||
if (provider.codeIndexManager) {
|
||||
// If embedder provider changed, perform proactive validation
|
||||
if (embedderProviderChanged) {
|
||||
try {
|
||||
// Force handleSettingsChange which will trigger validation
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
} catch (error) {
|
||||
// Validation failed - the error state is already set by handleSettingsChange
|
||||
provider.log(
|
||||
`Embedder validation failed after provider change: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
// Send validation error to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: provider.codeIndexManager.getCurrentStatus(),
|
||||
})
|
||||
// Exit early - don't try to start indexing with invalid configuration
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// No provider change, just handle settings normally
|
||||
try {
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
} catch (error) {
|
||||
// Log but don't fail - settings are saved
|
||||
provider.log(
|
||||
`Settings change handling error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a bit more to ensure everything is ready
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Auto-start indexing if now enabled and configured
|
||||
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
|
||||
if (!provider.codeIndexManager.isInitialized) {
|
||||
try {
|
||||
await provider.codeIndexManager.initialize(provider.contextProxy)
|
||||
provider.log(`Code index manager initialized after settings save`)
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Code index initialization failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
// Send error status to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: provider.codeIndexManager.getCurrentStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
provider.log(`Error saving code index settings: ${error.message || error}`)
|
||||
await provider.postMessageToWebview({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "No s'ha pogut llegir el cos de l'error",
|
||||
"requestFailed": "La sol·licitud de l'API d'Ollama ha fallat amb l'estat {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estructura de resposta no vàlida de l'API d'Ollama: no s'ha trobat la matriu \"embeddings\" o no és una matriu.",
|
||||
"embeddingFailed": "La incrustació d'Ollama ha fallat: {{message}}"
|
||||
"embeddingFailed": "La incrustació d'Ollama ha fallat: {{message}}",
|
||||
"serviceNotRunning": "El servei d'Ollama no s'està executant a {{baseUrl}}",
|
||||
"serviceUnavailable": "El servei d'Ollama no està disponible (estat: {{status}})",
|
||||
"modelNotFound": "No s'ha trobat el model d'Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "El model d'Ollama no és capaç de fer incrustacions: {{modelId}}",
|
||||
"hostNotFound": "No s'ha trobat l'amfitrió d'Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error desconegut en processar el fitxer {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Ha fallat l'autenticació. Comproveu la vostra clau d'API a la configuració.",
|
||||
"connectionFailed": "No s'ha pogut connectar al servei d'incrustació. Comproveu la vostra configuració de connexió i assegureu-vos que el servei estigui funcionant.",
|
||||
"modelNotAvailable": "El model especificat no està disponible. Comproveu la vostra configuració de model.",
|
||||
"configurationError": "Configuració d'incrustació no vàlida. Reviseu la vostra configuració.",
|
||||
"serviceUnavailable": "El servei d'incrustació no està disponible. Assegureu-vos que estigui funcionant i sigui accessible.",
|
||||
"invalidEndpoint": "Punt final d'API no vàlid. Comproveu la vostra configuració d'URL.",
|
||||
"invalidEmbedderConfig": "Configuració d'incrustació no vàlida. Comproveu la vostra configuració.",
|
||||
"invalidApiKey": "Clau d'API no vàlida. Comproveu la vostra configuració de clau d'API.",
|
||||
"invalidBaseUrl": "URL base no vàlida. Comproveu la vostra configuració d'URL.",
|
||||
"invalidModel": "Model no vàlid. Comproveu la vostra configuració de model.",
|
||||
"invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Fehlerinhalt konnte nicht gelesen werden",
|
||||
"requestFailed": "Ollama API-Anfrage fehlgeschlagen mit Status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ungültige Antwortstruktur von Ollama API: \"embeddings\" Array nicht gefunden oder kein Array.",
|
||||
"embeddingFailed": "Ollama Einbettung fehlgeschlagen: {{message}}"
|
||||
"embeddingFailed": "Ollama Einbettung fehlgeschlagen: {{message}}",
|
||||
"serviceNotRunning": "Ollama-Dienst wird unter {{baseUrl}} nicht ausgeführt",
|
||||
"serviceUnavailable": "Ollama-Dienst ist nicht verfügbar (Status: {{status}})",
|
||||
"modelNotFound": "Ollama-Modell nicht gefunden: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama-Modell ist nicht für Einbettungen geeignet: {{modelId}}",
|
||||
"hostNotFound": "Ollama-Host nicht gefunden: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Unbekannter Fehler beim Verarbeiten der Datei {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfe deinen API-Schlüssel in den Einstellungen.",
|
||||
"connectionFailed": "Verbindung zum Embedder-Dienst fehlgeschlagen. Bitte überprüfe deine Verbindungseinstellungen und stelle sicher, dass der Dienst läuft.",
|
||||
"modelNotAvailable": "Das angegebene Modell ist nicht verfügbar. Bitte überprüfe deine Modellkonfiguration.",
|
||||
"configurationError": "Ungültige Embedder-Konfiguration. Bitte überprüfe deine Einstellungen.",
|
||||
"serviceUnavailable": "Der Embedder-Dienst ist nicht verfügbar. Bitte stelle sicher, dass er läuft und erreichbar ist.",
|
||||
"invalidEndpoint": "Ungültiger API-Endpunkt. Bitte überprüfe deine URL-Konfiguration.",
|
||||
"invalidEmbedderConfig": "Ungültige Embedder-Konfiguration. Bitte überprüfe deine Einstellungen.",
|
||||
"invalidApiKey": "Ungültiger API-Schlüssel. Bitte überprüfe deine API-Schlüssel-Konfiguration.",
|
||||
"invalidBaseUrl": "Ungültige Basis-URL. Bitte überprüfe deine URL-Konfiguration.",
|
||||
"invalidModel": "Ungültiges Modell. Bitte überprüfe deine Modellkonfiguration.",
|
||||
"invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Could not read error body",
|
||||
"requestFailed": "Ollama API request failed with status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Invalid response structure from Ollama API: \"embeddings\" array not found or not an array.",
|
||||
"embeddingFailed": "Ollama embedding failed: {{message}}"
|
||||
"embeddingFailed": "Ollama embedding failed: {{message}}",
|
||||
"serviceNotRunning": "Ollama service is not running at {{baseUrl}}",
|
||||
"serviceUnavailable": "Ollama service is unavailable (status: {{status}})",
|
||||
"modelNotFound": "Ollama model not found: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama model is not embedding capable: {{modelId}}",
|
||||
"hostNotFound": "Ollama host not found: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Unknown error processing file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authentication failed. Please check your API key in the settings.",
|
||||
"connectionFailed": "Failed to connect to the embedder service. Please check your connection settings and ensure the service is running.",
|
||||
"modelNotAvailable": "The specified model is not available. Please check your model configuration.",
|
||||
"configurationError": "Invalid embedder configuration. Please review your settings.",
|
||||
"serviceUnavailable": "The embedder service is not available. Please ensure it is running and accessible.",
|
||||
"invalidEndpoint": "Invalid API endpoint. Please check your URL configuration.",
|
||||
"invalidEmbedderConfig": "Invalid embedder configuration. Please check your settings.",
|
||||
"invalidApiKey": "Invalid API key. Please check your API key configuration.",
|
||||
"invalidBaseUrl": "Invalid base URL. Please check your URL configuration.",
|
||||
"invalidModel": "Invalid model. Please check your model configuration.",
|
||||
"invalidResponse": "Invalid response from embedder service. Please check your configuration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "No se pudo leer el cuerpo del error",
|
||||
"requestFailed": "La solicitud de la API de Ollama falló con estado {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estructura de respuesta inválida de la API de Ollama: array \"embeddings\" no encontrado o no es un array.",
|
||||
"embeddingFailed": "Incrustación de Ollama falló: {{message}}"
|
||||
"embeddingFailed": "Incrustación de Ollama falló: {{message}}",
|
||||
"serviceNotRunning": "El servicio Ollama no se está ejecutando en {{baseUrl}}",
|
||||
"serviceUnavailable": "El servicio Ollama no está disponible (estado: {{status}})",
|
||||
"modelNotFound": "No se encuentra el modelo Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "El modelo Ollama no es capaz de realizar incrustaciones: {{modelId}}",
|
||||
"hostNotFound": "No se encuentra el host de Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error desconocido procesando archivo {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Error de autenticación. Comprueba tu clave de API en los ajustes.",
|
||||
"connectionFailed": "Error al conectar con el servicio de embedder. Comprueba los ajustes de conexión y asegúrate de que el servicio esté funcionando.",
|
||||
"modelNotAvailable": "El modelo especificado no está disponible. Comprueba la configuración de tu modelo.",
|
||||
"configurationError": "Configuración de embedder no válida. Revisa tus ajustes.",
|
||||
"serviceUnavailable": "El servicio de embedder no está disponible. Asegúrate de que esté funcionando y sea accesible.",
|
||||
"invalidEndpoint": "Punto de conexión de API no válido. Comprueba la configuración de tu URL.",
|
||||
"invalidEmbedderConfig": "Configuración de embedder no válida. Comprueba tus ajustes.",
|
||||
"invalidApiKey": "Clave de API no válida. Comprueba la configuración de tu clave de API.",
|
||||
"invalidBaseUrl": "URL base no válida. Comprueba la configuración de tu URL.",
|
||||
"invalidModel": "Modelo no válido. Comprueba la configuración de tu modelo.",
|
||||
"invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Impossible de lire le corps de l'erreur",
|
||||
"requestFailed": "Échec de la requête API Ollama avec le statut {{status}} {{statusText}} : {{errorBody}}",
|
||||
"invalidResponseStructure": "Structure de réponse invalide de l'API Ollama : tableau \"embeddings\" non trouvé ou n'est pas un tableau.",
|
||||
"embeddingFailed": "Échec de l'embedding Ollama : {{message}}"
|
||||
"embeddingFailed": "Échec de l'embedding Ollama : {{message}}",
|
||||
"serviceNotRunning": "Le service Ollama n'est pas en cours d'exécution sur {{baseUrl}}",
|
||||
"serviceUnavailable": "Le service Ollama est indisponible (statut : {{status}})",
|
||||
"modelNotFound": "Modèle Ollama introuvable : {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Le modèle Ollama n'est pas capable d'intégrer : {{modelId}}",
|
||||
"hostNotFound": "Hôte Ollama introuvable : {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Erreur inconnue lors du traitement du fichier {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Échec de l'authentification. Veuillez vérifier votre clé API dans les paramètres.",
|
||||
"connectionFailed": "Échec de la connexion au service d'embedding. Veuillez vérifier vos paramètres de connexion et vous assurer que le service est en cours d'exécution.",
|
||||
"modelNotAvailable": "Le modèle spécifié n'est pas disponible. Veuillez vérifier la configuration de votre modèle.",
|
||||
"configurationError": "Configuration de l'embedder invalide. Veuillez vérifier vos paramètres.",
|
||||
"serviceUnavailable": "Le service d'embedding n'est pas disponible. Veuillez vous assurer qu'il est en cours d'exécution et accessible.",
|
||||
"invalidEndpoint": "Point de terminaison d'API invalide. Veuillez vérifier votre configuration d'URL.",
|
||||
"invalidEmbedderConfig": "Configuration de l'embedder invalide. Veuillez vérifier vos paramètres.",
|
||||
"invalidApiKey": "Clé API invalide. Veuillez vérifier votre configuration de clé API.",
|
||||
"invalidBaseUrl": "URL de base invalide. Veuillez vérifier votre configuration d'URL.",
|
||||
"invalidModel": "Modèle invalide. Veuillez vérifier votre configuration de modèle.",
|
||||
"invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "त्रुटि सामग्री पढ़ नहीं सका",
|
||||
"requestFailed": "Ollama API अनुरोध स्थिति {{status}} {{statusText}} के साथ विफल: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API से अमान्य प्रतिक्रिया संरचना: \"embeddings\" सरणी नहीं मिली या सरणी नहीं है।",
|
||||
"embeddingFailed": "Ollama एम्बेडिंग विफल: {{message}}"
|
||||
"embeddingFailed": "Ollama एम्बेडिंग विफल: {{message}}",
|
||||
"serviceNotRunning": "ओलामा सेवा {{baseUrl}} पर नहीं चल रही है",
|
||||
"serviceUnavailable": "ओलामा सेवा अनुपलब्ध है (स्थिति: {{status}})",
|
||||
"modelNotFound": "ओलामा मॉडल नहीं मिला: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "ओलामा मॉडल एम्बेडिंग में सक्षम नहीं है: {{modelId}}",
|
||||
"hostNotFound": "ओलामा होस्ट नहीं मिला: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "फ़ाइल {{filePath}} प्रसंस्करण में अज्ञात त्रुटि",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "प्रमाणीकरण विफल। कृपया सेटिंग्स में अपनी एपीआई कुंजी जांचें।",
|
||||
"connectionFailed": "एम्बेडर सेवा से कनेक्ट करने में विफल। कृपया अपनी कनेक्शन सेटिंग्स जांचें और सुनिश्चित करें कि सेवा चल रही है।",
|
||||
"modelNotAvailable": "निर्दिष्ट मॉडल उपलब्ध नहीं है। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
|
||||
"configurationError": "अमान्य एम्बेडर कॉन्फ़िगरेशन। कृपया अपनी सेटिंग्स की समीक्षा करें।",
|
||||
"serviceUnavailable": "एम्बेडर सेवा उपलब्ध नहीं है। कृपया सुनिश्चित करें कि यह चल रहा है और पहुंच योग्य है।",
|
||||
"invalidEndpoint": "अमान्य एपीआई एंडपॉइंट। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidEmbedderConfig": "अमान्य एम्बेडर कॉन्फ़िगरेशन। कृपया अपनी सेटिंग्स जांचें।",
|
||||
"invalidApiKey": "अमान्य एपीआई कुंजी। कृपया अपनी एपीआई कुंजी कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidBaseUrl": "अमान्य बेस यूआरएल। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Tidak dapat membaca body error",
|
||||
"requestFailed": "Permintaan API Ollama gagal dengan status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Struktur respons tidak valid dari API Ollama: array \"embeddings\" tidak ditemukan atau bukan array.",
|
||||
"embeddingFailed": "Embedding Ollama gagal: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama gagal: {{message}}",
|
||||
"serviceNotRunning": "Layanan Ollama tidak berjalan di {{baseUrl}}",
|
||||
"serviceUnavailable": "Layanan Ollama tidak tersedia (status: {{status}})",
|
||||
"modelNotFound": "Model Ollama tidak ditemukan: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Model Ollama tidak mampu melakukan embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama tidak ditemukan: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error tidak dikenal saat memproses file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Autentikasi gagal. Silakan periksa kunci API Anda di pengaturan.",
|
||||
"connectionFailed": "Gagal terhubung ke layanan embedder. Silakan periksa pengaturan koneksi Anda dan pastikan layanan berjalan.",
|
||||
"modelNotAvailable": "Model yang ditentukan tidak tersedia. Silakan periksa konfigurasi model Anda.",
|
||||
"configurationError": "Konfigurasi embedder tidak valid. Harap tinjau pengaturan Anda.",
|
||||
"serviceUnavailable": "Layanan embedder tidak tersedia. Pastikan layanan tersebut berjalan dan dapat diakses.",
|
||||
"invalidEndpoint": "Endpoint API tidak valid. Silakan periksa konfigurasi URL Anda.",
|
||||
"invalidEmbedderConfig": "Konfigurasi embedder tidak valid. Silakan periksa pengaturan Anda.",
|
||||
"invalidApiKey": "Kunci API tidak valid. Silakan periksa konfigurasi kunci API Anda.",
|
||||
"invalidBaseUrl": "URL dasar tidak valid. Silakan periksa konfigurasi URL Anda.",
|
||||
"invalidModel": "Model tidak valid. Silakan periksa konfigurasi model Anda.",
|
||||
"invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Impossibile leggere il corpo dell'errore",
|
||||
"requestFailed": "Richiesta API Ollama fallita con stato {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Struttura di risposta non valida dall'API Ollama: array \"embeddings\" non trovato o non è un array.",
|
||||
"embeddingFailed": "Embedding Ollama fallito: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama fallito: {{message}}",
|
||||
"serviceNotRunning": "Il servizio Ollama non è in esecuzione su {{baseUrl}}",
|
||||
"serviceUnavailable": "Il servizio Ollama non è disponibile (stato: {{status}})",
|
||||
"modelNotFound": "Modello Ollama non trovato: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Il modello Ollama non è in grado di eseguire l'embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama non trovato: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Errore sconosciuto nell'elaborazione del file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Autenticazione fallita. Controlla la tua chiave API nelle impostazioni.",
|
||||
"connectionFailed": "Connessione al servizio di embedder fallita. Controlla le impostazioni di connessione e assicurati che il servizio sia in esecuzione.",
|
||||
"modelNotAvailable": "Il modello specificato non è disponibile. Controlla la configurazione del tuo modello.",
|
||||
"configurationError": "Configurazione dell'embedder non valida. Rivedi le tue impostazioni.",
|
||||
"serviceUnavailable": "Il servizio di embedder non è disponibile. Assicurati che sia in esecuzione e accessibile.",
|
||||
"invalidEndpoint": "Endpoint API non valido. Controlla la configurazione del tuo URL.",
|
||||
"invalidEmbedderConfig": "Configurazione dell'embedder non valida. Controlla le tue impostazioni.",
|
||||
"invalidApiKey": "Chiave API non valida. Controlla la configurazione della tua chiave API.",
|
||||
"invalidBaseUrl": "URL di base non valido. Controlla la configurazione del tuo URL.",
|
||||
"invalidModel": "Modello non valido. Controlla la configurazione del tuo modello.",
|
||||
"invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "エラー本文を読み取れませんでした",
|
||||
"requestFailed": "Ollama APIリクエストが失敗しました。ステータス {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama APIからの無効な応答構造:\"embeddings\"配列が見つからないか、配列ではありません。",
|
||||
"embeddingFailed": "Ollama埋め込みが失敗しました:{{message}}"
|
||||
"embeddingFailed": "Ollama埋め込みが失敗しました:{{message}}",
|
||||
"serviceNotRunning": "Ollamaサービスは{{baseUrl}}で実行されていません",
|
||||
"serviceUnavailable": "Ollamaサービスは利用できません(ステータス:{{status}})",
|
||||
"modelNotFound": "Ollamaモデルが見つかりません:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollamaモデルは埋め込みに対応していません:{{modelId}}",
|
||||
"hostNotFound": "Ollamaホストが見つかりません:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "ファイル{{filePath}}の処理中に不明なエラーが発生しました",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "認証に失敗しました。設定でAPIキーを確認してください。",
|
||||
"connectionFailed": "エンベッダーサービスへの接続に失敗しました。接続設定を確認し、サービスが実行されていることを確認してください。",
|
||||
"modelNotAvailable": "指定されたモデルは利用できません。モデル構成を確認してください。",
|
||||
"configurationError": "無効なエンベッダー構成です。設定を確認してください。",
|
||||
"serviceUnavailable": "エンベッダーサービスは利用できません。実行中でアクセス可能であることを確認してください。",
|
||||
"invalidEndpoint": "無効なAPIエンドポイントです。URL構成を確認してください。",
|
||||
"invalidEmbedderConfig": "無効なエンベッダー構成です。設定を確認してください。",
|
||||
"invalidApiKey": "無効なAPIキーです。APIキー構成を確認してください。",
|
||||
"invalidBaseUrl": "無効なベースURLです。URL構成を確認してください。",
|
||||
"invalidModel": "無効なモデルです。モデル構成を確認してください。",
|
||||
"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "오류 본문을 읽을 수 없습니다",
|
||||
"requestFailed": "Ollama API 요청이 실패했습니다. 상태 {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API에서 잘못된 응답 구조: \"embeddings\" 배열을 찾을 수 없거나 배열이 아닙니다.",
|
||||
"embeddingFailed": "Ollama 임베딩 실패: {{message}}"
|
||||
"embeddingFailed": "Ollama 임베딩 실패: {{message}}",
|
||||
"serviceNotRunning": "Ollama 서비스가 {{baseUrl}}에서 실행되고 있지 않습니다",
|
||||
"serviceUnavailable": "Ollama 서비스를 사용할 수 없습니다 (상태: {{status}})",
|
||||
"modelNotFound": "Ollama 모델을 찾을 수 없습니다: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 모델은 임베딩이 불가능합니다: {{modelId}}",
|
||||
"hostNotFound": "Ollama 호스트를 찾을 수 없습니다: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "파일 {{filePath}} 처리 중 알 수 없는 오류",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "인증에 실패했습니다. 설정에서 API 키를 확인하세요.",
|
||||
"connectionFailed": "임베더 서비스에 연결하지 못했습니다. 연결 설정을 확인하고 서비스가 실행 중인지 확인하세요.",
|
||||
"modelNotAvailable": "지정된 모델을 사용할 수 없습니다. 모델 구성을 확인하세요.",
|
||||
"configurationError": "잘못된 임베더 구성입니다. 설정을 검토하세요.",
|
||||
"serviceUnavailable": "임베더 서비스를 사용할 수 없습니다. 실행 중이고 액세스 가능한지 확인하세요.",
|
||||
"invalidEndpoint": "잘못된 API 엔드포인트입니다. URL 구성을 확인하세요.",
|
||||
"invalidEmbedderConfig": "잘못된 임베더 구성입니다. 설정을 확인하세요.",
|
||||
"invalidApiKey": "잘못된 API 키입니다. API 키 구성을 확인하세요.",
|
||||
"invalidBaseUrl": "잘못된 기본 URL입니다. URL 구성을 확인하세요.",
|
||||
"invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.",
|
||||
"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Kon foutinhoud niet lezen",
|
||||
"requestFailed": "Ollama API-verzoek mislukt met status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ongeldige responsstructuur van Ollama API: \"embeddings\" array niet gevonden of is geen array.",
|
||||
"embeddingFailed": "Ollama insluiting mislukt: {{message}}"
|
||||
"embeddingFailed": "Ollama insluiting mislukt: {{message}}",
|
||||
"serviceNotRunning": "Ollama-service draait niet op {{baseUrl}}",
|
||||
"serviceUnavailable": "Ollama-service is niet beschikbaar (status: {{status}})",
|
||||
"modelNotFound": "Ollama-model niet gevonden: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama-model is niet in staat tot insluiten: {{modelId}}",
|
||||
"hostNotFound": "Ollama-host niet gevonden: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Onbekende fout bij verwerken van bestand {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authenticatie mislukt. Controleer je API-sleutel in de instellingen.",
|
||||
"connectionFailed": "Verbinding met de embedder-service mislukt. Controleer je verbindingsinstellingen en zorg ervoor dat de service draait.",
|
||||
"modelNotAvailable": "Het opgegeven model is niet beschikbaar. Controleer je modelconfiguratie.",
|
||||
"configurationError": "Ongeldige embedder-configuratie. Controleer je instellingen.",
|
||||
"serviceUnavailable": "De embedder-service is niet beschikbaar. Zorg ervoor dat deze draait en toegankelijk is.",
|
||||
"invalidEndpoint": "Ongeldig API-eindpunt. Controleer je URL-configuratie.",
|
||||
"invalidEmbedderConfig": "Ongeldige embedder-configuratie. Controleer je instellingen.",
|
||||
"invalidApiKey": "Ongeldige API-sleutel. Controleer je API-sleutelconfiguratie.",
|
||||
"invalidBaseUrl": "Ongeldige basis-URL. Controleer je URL-configuratie.",
|
||||
"invalidModel": "Ongeldig model. Controleer je modelconfiguratie.",
|
||||
"invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Nie można odczytać treści błędu",
|
||||
"requestFailed": "Żądanie API Ollama nie powiodło się ze statusem {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Nieprawidłowa struktura odpowiedzi z API Ollama: tablica \"embeddings\" nie została znaleziona lub nie jest tablicą.",
|
||||
"embeddingFailed": "Osadzenie Ollama nie powiodło się: {{message}}"
|
||||
"embeddingFailed": "Osadzenie Ollama nie powiodło się: {{message}}",
|
||||
"serviceNotRunning": "Usługa Ollama nie jest uruchomiona pod adresem {{baseUrl}}",
|
||||
"serviceUnavailable": "Usługa Ollama jest niedostępna (status: {{status}})",
|
||||
"modelNotFound": "Nie znaleziono modelu Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Model Ollama nie jest zdolny do osadzania: {{modelId}}",
|
||||
"hostNotFound": "Nie znaleziono hosta Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Nieznany błąd podczas przetwarzania pliku {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Uwierzytelnianie nie powiodło się. Sprawdź swój klucz API w ustawieniach.",
|
||||
"connectionFailed": "Nie udało się połączyć z usługą embeddera. Sprawdź ustawienia połączenia i upewnij się, że usługa jest uruchomiona.",
|
||||
"modelNotAvailable": "Określony model jest niedostępny. Sprawdź konfigurację modelu.",
|
||||
"configurationError": "Nieprawidłowa konfiguracja embeddera. Sprawdź swoje ustawienia.",
|
||||
"serviceUnavailable": "Usługa embeddera jest niedostępna. Upewnij się, że jest uruchomiona i dostępna.",
|
||||
"invalidEndpoint": "Nieprawidłowy punkt końcowy API. Sprawdź konfigurację adresu URL.",
|
||||
"invalidEmbedderConfig": "Nieprawidłowa konfiguracja embeddera. Sprawdź swoje ustawienia.",
|
||||
"invalidApiKey": "Nieprawidłowy klucz API. Sprawdź konfigurację klucza API.",
|
||||
"invalidBaseUrl": "Nieprawidłowy podstawowy adres URL. Sprawdź konfigurację adresu URL.",
|
||||
"invalidModel": "Nieprawidłowy model. Sprawdź konfigurację modelu.",
|
||||
"invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Não foi possível ler o corpo do erro",
|
||||
"requestFailed": "Solicitação da API Ollama falhou com status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estrutura de resposta inválida da API Ollama: array \"embeddings\" não encontrado ou não é um array.",
|
||||
"embeddingFailed": "Embedding Ollama falhou: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama falhou: {{message}}",
|
||||
"serviceNotRunning": "O serviço Ollama não está em execução em {{baseUrl}}",
|
||||
"serviceUnavailable": "O serviço Ollama não está disponível (status: {{status}})",
|
||||
"modelNotFound": "Modelo Ollama não encontrado: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "O modelo Ollama não é capaz de embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama não encontrado: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Erro desconhecido ao processar arquivo {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Falha na autenticação. Verifique sua chave de API nas configurações.",
|
||||
"connectionFailed": "Falha ao conectar ao serviço do embedder. Verifique suas configurações de conexão e garanta que o serviço esteja em execução.",
|
||||
"modelNotAvailable": "O modelo especificado não está disponível. Verifique a configuração do seu modelo.",
|
||||
"configurationError": "Configuração do embedder inválida. Revise suas configurações.",
|
||||
"serviceUnavailable": "O serviço do embedder não está disponível. Garanta que ele esteja em execução e acessível.",
|
||||
"invalidEndpoint": "Endpoint de API inválido. Verifique sua configuração de URL.",
|
||||
"invalidEmbedderConfig": "Configuração do embedder inválida. Verifique suas configurações.",
|
||||
"invalidApiKey": "Chave de API inválida. Verifique sua configuração de chave de API.",
|
||||
"invalidBaseUrl": "URL base inválida. Verifique sua configuração de URL.",
|
||||
"invalidModel": "Modelo inválido. Verifique a configuração do seu modelo.",
|
||||
"invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Не удалось прочитать тело ошибки",
|
||||
"requestFailed": "Запрос к API Ollama не удался со статусом {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Неверная структура ответа от API Ollama: массив \"embeddings\" не найден или не является массивом.",
|
||||
"embeddingFailed": "Вложение Ollama не удалось: {{message}}"
|
||||
"embeddingFailed": "Вложение Ollama не удалось: {{message}}",
|
||||
"serviceNotRunning": "Сервис Ollama не запущен по адресу {{baseUrl}}",
|
||||
"serviceUnavailable": "Сервис Ollama недоступен (статус: {{status}})",
|
||||
"modelNotFound": "Модель Ollama не найдена: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Модель Ollama не способна к вложению: {{modelId}}",
|
||||
"hostNotFound": "Хост Ollama не найден: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Неизвестная ошибка при обработке файла {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Ошибка аутентификации. Проверьте свой ключ API в настройках.",
|
||||
"connectionFailed": "Не удалось подключиться к службе эмбеддера. Проверьте настройки подключения и убедитесь, что служба запущена.",
|
||||
"modelNotAvailable": "Указанная модель недоступна. Проверьте конфигурацию модели.",
|
||||
"configurationError": "Неверная конфигурация эмбеддера. Проверьте свои настройки.",
|
||||
"serviceUnavailable": "Служба эмбеддера недоступна. Убедитесь, что она запущена и доступна.",
|
||||
"invalidEndpoint": "Неверная конечная точка API. Проверьте конфигурацию URL.",
|
||||
"invalidEmbedderConfig": "Неверная конфигурация эмбеддера. Проверьте свои настройки.",
|
||||
"invalidApiKey": "Неверный ключ API. Проверьте конфигурацию ключа API.",
|
||||
"invalidBaseUrl": "Неверный базовый URL. Проверьте конфигурацию URL.",
|
||||
"invalidModel": "Неверная модель. Проверьте конфигурацию модели.",
|
||||
"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Hata gövdesi okunamadı",
|
||||
"requestFailed": "Ollama API isteği {{status}} {{statusText}} durumuyla başarısız oldu: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API'den geçersiz yanıt yapısı: \"embeddings\" dizisi bulunamadı veya dizi değil.",
|
||||
"embeddingFailed": "Ollama gömülmesi başarısız oldu: {{message}}"
|
||||
"embeddingFailed": "Ollama gömülmesi başarısız oldu: {{message}}",
|
||||
"serviceNotRunning": "Ollama hizmeti {{baseUrl}} adresinde çalışmıyor",
|
||||
"serviceUnavailable": "Ollama hizmeti kullanılamıyor (durum: {{status}})",
|
||||
"modelNotFound": "Ollama modeli bulunamadı: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama modeli gömme yeteneğine sahip değil: {{modelId}}",
|
||||
"hostNotFound": "Ollama ana bilgisayarı bulunamadı: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "{{filePath}} dosyası işlenirken bilinmeyen hata",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Kimlik doğrulama başarısız oldu. Lütfen ayarlardan API anahtarınızı kontrol edin.",
|
||||
"connectionFailed": "Gömücü hizmetine bağlanılamadı. Lütfen bağlantı ayarlarınızı kontrol edin ve hizmetin çalıştığından emin olun.",
|
||||
"modelNotAvailable": "Belirtilen model mevcut değil. Lütfen model yapılandırmanızı kontrol edin.",
|
||||
"configurationError": "Geçersiz gömücü yapılandırması. Lütfen ayarlarınızı gözden geçirin.",
|
||||
"serviceUnavailable": "Gömücü hizmeti mevcut değil. Lütfen çalıştığından ve erişilebilir olduğundan emin olun.",
|
||||
"invalidEndpoint": "Geçersiz API uç noktası. Lütfen URL yapılandırmanızı kontrol edin.",
|
||||
"invalidEmbedderConfig": "Geçersiz gömücü yapılandırması. Lütfen ayarlarınızı kontrol edin.",
|
||||
"invalidApiKey": "Geçersiz API anahtarı. Lütfen API anahtarı yapılandırmanızı kontrol edin.",
|
||||
"invalidBaseUrl": "Geçersiz temel URL. Lütfen URL yapılandırmanızı kontrol edin.",
|
||||
"invalidModel": "Geçersiz model. Lütfen model yapılandırmanızı kontrol edin.",
|
||||
"invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Không thể đọc nội dung lỗi",
|
||||
"requestFailed": "Yêu cầu API Ollama thất bại với trạng thái {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Cấu trúc phản hồi không hợp lệ từ API Ollama: không tìm thấy mảng \"embeddings\" hoặc không phải là mảng.",
|
||||
"embeddingFailed": "Nhúng Ollama thất bại: {{message}}"
|
||||
"embeddingFailed": "Nhúng Ollama thất bại: {{message}}",
|
||||
"serviceNotRunning": "Dịch vụ Ollama không chạy tại {{baseUrl}}",
|
||||
"serviceUnavailable": "Dịch vụ Ollama không khả dụng (trạng thái: {{status}})",
|
||||
"modelNotFound": "Không tìm thấy mô hình Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Mô hình Ollama không có khả năng nhúng: {{modelId}}",
|
||||
"hostNotFound": "Không tìm thấy máy chủ Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Lỗi không xác định khi xử lý tệp {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"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}}"
|
||||
},
|
||||
"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.",
|
||||
"connectionFailed": "Không thể kết nối với dịch vụ nhúng. Vui lòng kiểm tra cài đặt kết nối của bạn và đảm bảo dịch vụ đang chạy.",
|
||||
"modelNotAvailable": "Mô hình được chỉ định không có sẵn. Vui lòng kiểm tra cấu hình mô hình của bạn.",
|
||||
"configurationError": "Cấu hình nhúng không hợp lệ. Vui lòng xem lại cài đặt của bạn.",
|
||||
"serviceUnavailable": "Dịch vụ nhúng không có sẵn. Vui lòng đảm bảo nó đang chạy và có thể truy cập được.",
|
||||
"invalidEndpoint": "Điểm cuối API không hợp lệ. Vui lòng kiểm tra cấu hình URL của bạn.",
|
||||
"invalidEmbedderConfig": "Cấu hình nhúng không hợp lệ. Vui lòng kiểm tra cài đặt của bạn.",
|
||||
"invalidApiKey": "Khóa API không hợp lệ. Vui lòng kiểm tra cấu hình khóa API của bạn.",
|
||||
"invalidBaseUrl": "URL cơ sở không hợp lệ. Vui lòng kiểm tra cấu hình URL của bạn.",
|
||||
"invalidModel": "Mô hình không hợp lệ. Vui lòng kiểm tra cấu hình mô hình của bạn.",
|
||||
"invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "无法读取错误内容",
|
||||
"requestFailed": "Ollama API 请求失败,状态码 {{status}} {{statusText}}:{{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API 响应结构无效:未找到 \"embeddings\" 数组或不是数组。",
|
||||
"embeddingFailed": "Ollama 嵌入失败:{{message}}"
|
||||
"embeddingFailed": "Ollama 嵌入失败:{{message}}",
|
||||
"serviceNotRunning": "Ollama 服务未在 {{baseUrl}} 运行",
|
||||
"serviceUnavailable": "Ollama 服务不可用(状态:{{status}})",
|
||||
"modelNotFound": "未找到 Ollama 模型:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 模型不具备嵌入能力:{{modelId}}",
|
||||
"hostNotFound": "未找到 Ollama 主机:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "处理文件 {{filePath}} 时出现未知错误",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "身份验证失败。请在设置中检查您的 API 密钥。",
|
||||
"connectionFailed": "连接嵌入器服务失败。请检查您的连接设置并确保服务正在运行。",
|
||||
"modelNotAvailable": "指定的模型不可用。请检查您的模型配置。",
|
||||
"configurationError": "嵌入器配置无效。请查看您的设置。",
|
||||
"serviceUnavailable": "嵌入器服务不可用。请确保它正在运行且可访问。",
|
||||
"invalidEndpoint": "API 端点无效。请检查您的 URL 配置。",
|
||||
"invalidEmbedderConfig": "嵌入器配置无效。请检查您的设置。",
|
||||
"invalidApiKey": "API 密钥无效。请检查您的 API 密钥配置。",
|
||||
"invalidBaseUrl": "基础 URL 无效。请检查您的 URL 配置。",
|
||||
"invalidModel": "模型无效。请检查您的模型配置。",
|
||||
"invalidResponse": "嵌入服务响应无效。请检查您的配置。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "無法讀取錯誤內容",
|
||||
"requestFailed": "Ollama API 請求失敗,狀態碼 {{status}} {{statusText}}:{{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API 回應結構無效:未找到 \"embeddings\" 陣列或不是陣列。",
|
||||
"embeddingFailed": "Ollama 內嵌失敗:{{message}}"
|
||||
"embeddingFailed": "Ollama 內嵌失敗:{{message}}",
|
||||
"serviceNotRunning": "Ollama 服務未在 {{baseUrl}} 執行",
|
||||
"serviceUnavailable": "Ollama 服務不可用(狀態:{{status}})",
|
||||
"modelNotFound": "找不到 Ollama 模型:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 模型不具備內嵌能力:{{modelId}}",
|
||||
"hostNotFound": "找不到 Ollama 主機:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "處理檔案 {{filePath}} 時發生未知錯誤",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "驗證失敗。請在設定中檢查您的 API 金鑰。",
|
||||
"connectionFailed": "連線至內嵌服務失敗。請檢查您的連線設定並確保服務正在執行。",
|
||||
"modelNotAvailable": "指定的模型不可用。請檢查您的模型組態。",
|
||||
"configurationError": "無效的內嵌程式組態。請檢閱您的設定。",
|
||||
"serviceUnavailable": "內嵌服務不可用。請確保它正在執行且可存取。",
|
||||
"invalidEndpoint": "無效的 API 端點。請檢查您的 URL 組態。",
|
||||
"invalidEmbedderConfig": "無效的內嵌程式組態。請檢查您的設定。",
|
||||
"invalidApiKey": "無效的 API 金鑰。請檢查您的 API 金鑰組態。",
|
||||
"invalidBaseUrl": "無效的基礎 URL。請檢查您的 URL 組態。",
|
||||
"invalidModel": "無效的模型。請檢查您的模型組態。",
|
||||
"invalidResponse": "內嵌服務回應無效。請檢查您的組態。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import { CodeIndexManager } from "../manager"
|
||||
import { CodeIndexServiceFactory } from "../service-factory"
|
||||
import type { MockedClass } from "vitest"
|
||||
|
||||
// Mock vscode module
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
|
|
@ -21,10 +25,12 @@ vi.mock("../state-manager", () => ({
|
|||
onProgressUpdate: vi.fn(),
|
||||
getCurrentStatus: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
setSystemState: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
import { CodeIndexManager } from "../manager"
|
||||
vi.mock("../service-factory")
|
||||
const MockedCodeIndexServiceFactory = CodeIndexServiceFactory as MockedClass<typeof CodeIndexServiceFactory>
|
||||
|
||||
describe("CodeIndexManager - handleSettingsChange regression", () => {
|
||||
let mockContext: any
|
||||
|
|
@ -72,13 +78,63 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
// Mock a minimal config manager that simulates first-time configuration
|
||||
const mockConfigManager = {
|
||||
loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: true }),
|
||||
isFeatureConfigured: true,
|
||||
isFeatureEnabled: true,
|
||||
getConfig: vi.fn().mockReturnValue({
|
||||
isEnabled: true,
|
||||
isConfigured: true,
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: { openAiNativeApiKey: "test-key" },
|
||||
qdrantUrl: "http://localhost:6333",
|
||||
qdrantApiKey: "test-key",
|
||||
searchMinScore: 0.4,
|
||||
}),
|
||||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
|
||||
// Mock cache manager
|
||||
const mockCacheManager = {
|
||||
initialize: vi.fn(),
|
||||
clearCacheFile: vi.fn(),
|
||||
}
|
||||
;(manager as any)._cacheManager = mockCacheManager
|
||||
|
||||
// Mock the feature state to simulate valid configuration that would normally trigger restart
|
||||
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
|
||||
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
|
||||
|
||||
// Mock service factory to handle _recreateServices call
|
||||
const mockServiceFactoryInstance = {
|
||||
configManager: mockConfigManager,
|
||||
workspacePath: "/test/workspace",
|
||||
cacheManager: mockCacheManager,
|
||||
createEmbedder: vi.fn().mockReturnValue({ embedderInfo: { name: "openai" } }),
|
||||
createVectorStore: vi.fn().mockReturnValue({}),
|
||||
createDirectoryScanner: vi.fn().mockReturnValue({}),
|
||||
createFileWatcher: vi.fn().mockReturnValue({
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}),
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: { embedderInfo: { name: "openai" } },
|
||||
vectorStore: {},
|
||||
scanner: {},
|
||||
fileWatcher: {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
}),
|
||||
validateEmbedder: vi.fn().mockResolvedValue({ valid: true }),
|
||||
}
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance as any)
|
||||
|
||||
// The key test: this should NOT throw "CodeIndexManager not initialized" error
|
||||
await expect(manager.handleSettingsChange()).resolves.not.toThrow()
|
||||
|
||||
|
|
@ -105,29 +161,65 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
|
||||
// Mock cache manager
|
||||
const mockCacheManager = {
|
||||
initialize: vi.fn(),
|
||||
clearCacheFile: vi.fn(),
|
||||
}
|
||||
;(manager as any)._cacheManager = mockCacheManager
|
||||
|
||||
// Simulate an initialized manager by setting the required properties
|
||||
;(manager as any)._orchestrator = { stopWatcher: vi.fn() }
|
||||
;(manager as any)._searchService = {}
|
||||
;(manager as any)._cacheManager = {}
|
||||
|
||||
// Verify manager is considered initialized
|
||||
expect(manager.isInitialized).toBe(true)
|
||||
|
||||
// Mock the methods that would be called during restart
|
||||
const recreateServicesSpy = vi.spyOn(manager as any, "_recreateServices").mockImplementation(() => {})
|
||||
const startIndexingSpy = vi.spyOn(manager, "startIndexing").mockResolvedValue()
|
||||
|
||||
// Mock the feature state
|
||||
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
|
||||
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
|
||||
|
||||
// Mock service factory to handle _recreateServices call
|
||||
const mockServiceFactoryInstance = {
|
||||
configManager: mockConfigManager,
|
||||
workspacePath: "/test/workspace",
|
||||
cacheManager: mockCacheManager,
|
||||
createEmbedder: vi.fn().mockReturnValue({ embedderInfo: { name: "openai" } }),
|
||||
createVectorStore: vi.fn().mockReturnValue({}),
|
||||
createDirectoryScanner: vi.fn().mockReturnValue({}),
|
||||
createFileWatcher: vi.fn().mockReturnValue({
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}),
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: { embedderInfo: { name: "openai" } },
|
||||
vectorStore: {},
|
||||
scanner: {},
|
||||
fileWatcher: {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
}),
|
||||
validateEmbedder: vi.fn().mockResolvedValue({ valid: true }),
|
||||
}
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance as any)
|
||||
|
||||
// Mock the methods that would be called during restart
|
||||
const recreateServicesSpy = vi.spyOn(manager as any, "_recreateServices")
|
||||
|
||||
await manager.handleSettingsChange()
|
||||
|
||||
// Verify that the restart sequence was called
|
||||
expect(mockConfigManager.loadConfiguration).toHaveBeenCalled()
|
||||
// stopWatcher is called inside _recreateServices, which we mocked
|
||||
// _recreateServices should be called when requiresRestart is true
|
||||
expect(recreateServicesSpy).toHaveBeenCalled()
|
||||
expect(startIndexingSpy).toHaveBeenCalled()
|
||||
// Note: startIndexing is NOT called by handleSettingsChange - it's only called by initialize()
|
||||
})
|
||||
|
||||
it("should handle case when config manager is not set", async () => {
|
||||
|
|
@ -138,4 +230,135 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
await expect(manager.handleSettingsChange()).resolves.not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("embedder validation integration", () => {
|
||||
let mockServiceFactoryInstance: any
|
||||
let mockStateManager: any
|
||||
let mockEmbedder: any
|
||||
let mockVectorStore: any
|
||||
let mockScanner: any
|
||||
let mockFileWatcher: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock service factory objects
|
||||
mockEmbedder = { embedderInfo: { name: "openai" } }
|
||||
mockVectorStore = {}
|
||||
mockScanner = {}
|
||||
mockFileWatcher = {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}
|
||||
|
||||
// Mock service factory instance
|
||||
mockServiceFactoryInstance = {
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: mockEmbedder,
|
||||
vectorStore: mockVectorStore,
|
||||
scanner: mockScanner,
|
||||
fileWatcher: mockFileWatcher,
|
||||
}),
|
||||
validateEmbedder: vi.fn(),
|
||||
}
|
||||
|
||||
// Mock the ServiceFactory constructor
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance)
|
||||
|
||||
// Mock state manager methods directly on the existing instance
|
||||
mockStateManager = (manager as any)._stateManager
|
||||
mockStateManager.setSystemState = vi.fn()
|
||||
|
||||
// Mock config manager
|
||||
const mockConfigManager = {
|
||||
loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: false }),
|
||||
isFeatureConfigured: true,
|
||||
isFeatureEnabled: true,
|
||||
getConfig: vitest.fn().mockReturnValue({
|
||||
isEnabled: true,
|
||||
isConfigured: true,
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: { openAiNativeApiKey: "test-key" },
|
||||
qdrantUrl: "http://localhost:6333",
|
||||
qdrantApiKey: "test-key",
|
||||
searchMinScore: 0.4,
|
||||
}),
|
||||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
})
|
||||
|
||||
it("should validate embedder during _recreateServices when validation succeeds", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act - directly call the private method for testing
|
||||
await (manager as any)._recreateServices()
|
||||
|
||||
// Assert
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Error", expect.any(String))
|
||||
})
|
||||
|
||||
it("should set error state when embedder validation fails", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act & Assert
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow(
|
||||
"embeddings:validation.authenticationFailed",
|
||||
)
|
||||
|
||||
// Assert other expectations
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).toHaveBeenCalledWith(
|
||||
"Error",
|
||||
"embeddings:validation.authenticationFailed",
|
||||
)
|
||||
})
|
||||
|
||||
it("should set generic error state when embedder validation throws", async () => {
|
||||
// Arrange
|
||||
// Since the real service factory catches exceptions, we should mock it to resolve with an error
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.configurationError",
|
||||
})
|
||||
|
||||
// Act & Assert
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow(
|
||||
"embeddings:validation.configurationError",
|
||||
)
|
||||
|
||||
// Assert other expectations
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).toHaveBeenCalledWith(
|
||||
"Error",
|
||||
"embeddings:validation.configurationError",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle embedder creation failure", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.createServices.mockImplementation(() => {
|
||||
throw new Error("Invalid configuration")
|
||||
})
|
||||
|
||||
// Act & Assert - should throw the error
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow("Invalid configuration")
|
||||
|
||||
// Should not attempt validation if embedder creation fails
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -580,4 +580,187 @@ describe("CodeIndexServiceFactory", () => {
|
|||
expect(() => factory.createVectorStore()).toThrow("Qdrant URL missing for vector store creation")
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateEmbedder", () => {
|
||||
let mockEmbedderInstance: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockEmbedderInstance = {
|
||||
validateConfiguration: vitest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
it("should validate OpenAI embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return validation error from OpenAI embedder", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "invalid-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate Ollama embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "ollama",
|
||||
modelId: "nomic-embed-text",
|
||||
ollamaOptions: {
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedCodeIndexOllamaEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should validate OpenAI Compatible embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai-compatible",
|
||||
modelId: "custom-model",
|
||||
openAiCompatibleOptions: {
|
||||
baseUrl: "https://api.example.com/v1",
|
||||
apiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAICompatibleEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should validate Gemini embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "gemini",
|
||||
geminiOptions: {
|
||||
apiKey: "test-gemini-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedGeminiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle validation exceptions", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
const networkError = new Error("Network error")
|
||||
mockEmbedderInstance.validateConfiguration.mockRejectedValue(networkError)
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "Network error",
|
||||
})
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return error for invalid embedder configuration", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: undefined, // Missing API key
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
|
||||
// Act & Assert
|
||||
// This should throw when trying to create the embedder
|
||||
await expect(async () => {
|
||||
const embedder = factory.createEmbedder()
|
||||
await factory.validateEmbedder(embedder)
|
||||
}).rejects.toThrow("OpenAI configuration missing for embedder creation")
|
||||
})
|
||||
|
||||
it("should return error for unknown embedder provider", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "unknown-provider",
|
||||
modelId: "some-model",
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
|
||||
// Act & Assert
|
||||
// This should throw when trying to create the embedder
|
||||
expect(() => factory.createEmbedder()).toThrow("Invalid embedder type configured: unknown-provider")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -55,4 +55,54 @@ describe("GeminiEmbedder", () => {
|
|||
expect(GeminiEmbedder.dimension).toBe(768)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
let mockValidateConfiguration: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockValidateConfiguration = vitest.fn()
|
||||
MockedOpenAICompatibleEmbedder.prototype.validateConfiguration = mockValidateConfiguration
|
||||
})
|
||||
|
||||
it("should delegate validation to OpenAICompatibleEmbedder", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
// Assert
|
||||
expect(mockValidateConfiguration).toHaveBeenCalled()
|
||||
expect(result).toEqual({ valid: true })
|
||||
})
|
||||
|
||||
it("should pass through validation errors from OpenAICompatibleEmbedder", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
// Assert
|
||||
expect(mockValidateConfiguration).toHaveBeenCalled()
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle validation exceptions", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockRejectedValue(new Error("Validation failed"))
|
||||
|
||||
// Act & Assert
|
||||
await expect(embedder.validateConfiguration()).rejects.toThrow("Validation failed")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
238
src/services/code-index/embedders/__tests__/ollama.spec.ts
Normal file
238
src/services/code-index/embedders/__tests__/ollama.spec.ts
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import { vitest, describe, it, expect, beforeEach, afterEach } from "vitest"
|
||||
import type { MockedFunction } from "vitest"
|
||||
import { CodeIndexOllamaEmbedder } from "../ollama"
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vitest.fn() as MockedFunction<typeof fetch>
|
||||
|
||||
// Mock i18n
|
||||
vitest.mock("../../../../i18n", () => ({
|
||||
t: (key: string, params?: Record<string, any>) => {
|
||||
const translations: Record<string, string> = {
|
||||
"embeddings:validation.serviceUnavailable":
|
||||
"The embedder service is not available. Please ensure it is running and accessible.",
|
||||
"embeddings:validation.modelNotAvailable":
|
||||
"The specified model is not available. Please check your model configuration.",
|
||||
"embeddings:validation.connectionFailed":
|
||||
"Failed to connect to the embedder service. Please check your connection settings and ensure the service is running.",
|
||||
"embeddings:validation.configurationError": "Invalid embedder configuration. Please review your settings.",
|
||||
"embeddings:errors.ollama.serviceNotRunning":
|
||||
"Ollama service is not running at {{baseUrl}}. Please start Ollama first.",
|
||||
"embeddings:errors.ollama.serviceUnavailable":
|
||||
"Ollama service is unavailable at {{baseUrl}}. HTTP status: {{status}}",
|
||||
"embeddings:errors.ollama.modelNotFound":
|
||||
"Model '{{model}}' not found. Available models: {{availableModels}}",
|
||||
"embeddings:errors.ollama.modelNotEmbedding": "Model '{{model}}' is not embedding capable",
|
||||
"embeddings:errors.ollama.hostNotFound": "Ollama host not found: {{baseUrl}}",
|
||||
"embeddings:errors.ollama.connectionTimeout": "Connection to Ollama timed out at {{baseUrl}}",
|
||||
}
|
||||
// Handle parameter substitution
|
||||
let result = translations[key] || key
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([param, value]) => {
|
||||
result = result.replace(new RegExp(`{{${param}}}`, "g"), String(value))
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock console methods
|
||||
const consoleMocks = {
|
||||
error: vitest.spyOn(console, "error").mockImplementation(() => {}),
|
||||
}
|
||||
|
||||
describe("CodeIndexOllamaEmbedder", () => {
|
||||
let embedder: CodeIndexOllamaEmbedder
|
||||
let mockFetch: MockedFunction<typeof fetch>
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
consoleMocks.error.mockClear()
|
||||
|
||||
mockFetch = global.fetch as MockedFunction<typeof fetch>
|
||||
|
||||
embedder = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
it("should initialize with provided options", () => {
|
||||
expect(embedder.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
|
||||
it("should use default values when not provided", () => {
|
||||
const embedderWithDefaults = new CodeIndexOllamaEmbedder({})
|
||||
expect(embedderWithDefaults.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
it("should validate successfully when service is available and model exists", async () => {
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text:latest" }, { name: "llama2:latest" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock successful /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Check first call (GET /api/tags)
|
||||
const firstCall = mockFetch.mock.calls[0]
|
||||
expect(firstCall[0]).toBe("http://localhost:11434/api/tags")
|
||||
expect(firstCall[1]?.method).toBe("GET")
|
||||
expect(firstCall[1]?.headers).toEqual({ "Content-Type": "application/json" })
|
||||
expect(firstCall[1]?.signal).toBeDefined() // AbortSignal for timeout
|
||||
|
||||
// Check second call (POST /api/embed)
|
||||
const secondCall = mockFetch.mock.calls[1]
|
||||
expect(secondCall[0]).toBe("http://localhost:11434/api/embed")
|
||||
expect(secondCall[1]?.method).toBe("POST")
|
||||
expect(secondCall[1]?.headers).toEqual({ "Content-Type": "application/json" })
|
||||
expect(secondCall[1]?.body).toBe(JSON.stringify({ model: "nomic-embed-text", input: ["test"] }))
|
||||
expect(secondCall[1]?.signal).toBeDefined() // AbortSignal for timeout
|
||||
})
|
||||
|
||||
it("should fail validation when service is not available", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should fail validation when tags endpoint returns 404", async () => {
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 404,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe(
|
||||
"Ollama service is not running at http://localhost:11434. Please start Ollama first.",
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation when tags endpoint returns other error", async () => {
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 500,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Ollama service is unavailable at http://localhost:11434. HTTP status: 500")
|
||||
})
|
||||
|
||||
it("should fail validation when model does not exist", async () => {
|
||||
// Mock successful /api/tags call with different models
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "llama2:latest" }, { name: "mistral:latest" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe(
|
||||
"Model 'nomic-embed-text' not found. Available models: llama2:latest, mistral:latest",
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation when model exists but doesn't support embeddings", async () => {
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock failed /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 400,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Model 'nomic-embed-text' is not embedding capable")
|
||||
})
|
||||
|
||||
it("should handle ECONNREFUSED errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should handle ENOTFOUND errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ENOTFOUND"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Ollama host not found: http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should handle generic network errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("Network timeout"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Network timeout")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -882,4 +882,133 @@ describe("OpenAICompatibleEmbedder", () => {
|
|||
expect(mockCreate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
let embedder: OpenAICompatibleEmbedder
|
||||
let mockFetch: MockedFunction<typeof fetch>
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
// Reset and re-assign the global fetch mock
|
||||
global.fetch = vitest.fn()
|
||||
mockFetch = global.fetch as MockedFunction<typeof fetch>
|
||||
})
|
||||
|
||||
it("should validate successfully with valid configuration and base URL", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const mockResponse = {
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}
|
||||
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: testModelId,
|
||||
encoding_format: "base64",
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate successfully with full endpoint URL", async () => {
|
||||
const fullUrl = "https://api.example.com/v1/embeddings"
|
||||
embedder = new OpenAICompatibleEmbedder(fullUrl, testApiKey, testModelId)
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}),
|
||||
text: async () => "",
|
||||
} as any)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
fullUrl,
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
Authorization: `Bearer ${testApiKey}`,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation with authentication error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const authError = new Error("Invalid API key")
|
||||
;(authError as any).status = 401
|
||||
mockEmbeddingsCreate.mockRejectedValue(authError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.authenticationFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with connection error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const connectionError = new Error("ECONNREFUSED")
|
||||
mockEmbeddingsCreate.mockRejectedValue(connectionError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.connectionFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with invalid endpoint for full URL", async () => {
|
||||
const fullUrl = "https://api.example.com/v1/embeddings"
|
||||
embedder = new OpenAICompatibleEmbedder(fullUrl, testApiKey, testModelId)
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({ error: "Not found" }),
|
||||
text: async () => "Not found",
|
||||
} as any)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.invalidEndpoint")
|
||||
})
|
||||
|
||||
it("should fail validation with rate limit error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const rateLimitError = new Error("Rate limit exceeded")
|
||||
;(rateLimitError as any).status = 429
|
||||
mockEmbeddingsCreate.mockRejectedValue(rateLimitError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.serviceUnavailable")
|
||||
})
|
||||
|
||||
it("should fail validation with generic error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const genericError = new Error("Unknown error")
|
||||
;(genericError as any).status = 500
|
||||
mockEmbeddingsCreate.mockRejectedValue(genericError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.configurationError")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -464,4 +464,66 @@ describe("OpenAiEmbedder", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
it("should validate successfully with valid configuration", async () => {
|
||||
const mockResponse = {
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}
|
||||
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: "text-embedding-3-small",
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail validation with authentication error", async () => {
|
||||
const authError = new Error("Invalid API key")
|
||||
;(authError as any).status = 401
|
||||
mockEmbeddingsCreate.mockRejectedValue(authError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.authenticationFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with rate limit error", async () => {
|
||||
const rateLimitError = new Error("Rate limit exceeded")
|
||||
;(rateLimitError as any).status = 429
|
||||
mockEmbeddingsCreate.mockRejectedValue(rateLimitError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.serviceUnavailable")
|
||||
})
|
||||
|
||||
it("should fail validation with connection error", async () => {
|
||||
const connectionError = new Error("ECONNREFUSED")
|
||||
mockEmbeddingsCreate.mockRejectedValue(connectionError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.connectionFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with generic error", async () => {
|
||||
const genericError = new Error("Unknown error")
|
||||
;(genericError as any).status = 500
|
||||
mockEmbeddingsCreate.mockRejectedValue(genericError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.configurationError")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ export class GeminiEmbedder implements IEmbedder {
|
|||
return this.openAICompatibleEmbedder.createEmbeddings(texts, GeminiEmbedder.GEMINI_MODEL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the Gemini embedder configuration by delegating to the underlying OpenAI-compatible embedder
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
// Delegate validation to the OpenAI-compatible embedder
|
||||
// The error messages will be specific to Gemini since we're using Gemini's base URL
|
||||
return this.openAICompatibleEmbedder.validateConfiguration()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this embedder
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { EmbedderInfo, EmbeddingResponse, IEmbedder } from "../interfaces"
|
|||
import { getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { MAX_ITEM_TOKENS } from "../constants"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling } from "../shared/validation-helpers"
|
||||
|
||||
/**
|
||||
* Implements the IEmbedder interface using a local Ollama instance.
|
||||
|
|
@ -101,6 +102,127 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the Ollama embedder configuration by checking service availability and model existence
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(
|
||||
async () => {
|
||||
// First check if Ollama service is running by trying to list models
|
||||
const modelsUrl = `${this.baseUrl}/api/tags`
|
||||
|
||||
// Add timeout to prevent indefinite hanging
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
|
||||
|
||||
const modelsResponse = await fetch(modelsUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!modelsResponse.ok) {
|
||||
if (modelsResponse.status === 404) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.serviceUnavailable", {
|
||||
baseUrl: this.baseUrl,
|
||||
status: modelsResponse.status,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the specific model exists
|
||||
const modelsData = await modelsResponse.json()
|
||||
const models = modelsData.models || []
|
||||
|
||||
// Check both with and without :latest suffix
|
||||
const modelExists = models.some((m: any) => {
|
||||
const modelName = m.name || ""
|
||||
return (
|
||||
modelName === this.defaultModelId ||
|
||||
modelName === `${this.defaultModelId}:latest` ||
|
||||
modelName === this.defaultModelId.replace(":latest", "")
|
||||
)
|
||||
})
|
||||
|
||||
if (!modelExists) {
|
||||
const availableModels = models.map((m: any) => m.name).join(", ")
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.modelNotFound", {
|
||||
model: this.defaultModelId,
|
||||
availableModels,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Try a test embedding to ensure the model works for embeddings
|
||||
const testUrl = `${this.baseUrl}/api/embed`
|
||||
|
||||
// Add timeout for test request too
|
||||
const testController = new AbortController()
|
||||
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
|
||||
|
||||
const testResponse = await fetch(testUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.defaultModelId,
|
||||
input: ["test"],
|
||||
}),
|
||||
signal: testController.signal,
|
||||
})
|
||||
clearTimeout(testTimeoutId)
|
||||
|
||||
if (!testResponse.ok) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.modelNotEmbedding", { model: this.defaultModelId }),
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
},
|
||||
"ollama",
|
||||
{
|
||||
beforeStandardHandling: (error: any) => {
|
||||
// Handle Ollama-specific connection errors
|
||||
if (error?.message === "ECONNREFUSED") {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
} else if (error?.message === "ENOTFOUND") {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.hostNotFound", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
} else if (error?.name === "AbortError") {
|
||||
// Handle timeout
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
}
|
||||
// Let standard handling take over
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
get embedderInfo(): EmbedderInfo {
|
||||
return {
|
||||
name: "ollama",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "../constants"
|
||||
import { getDefaultModelId, getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "../shared/validation-helpers"
|
||||
|
||||
interface EmbeddingItem {
|
||||
embedding: string | number[]
|
||||
|
|
@ -26,12 +27,6 @@ interface OpenAIEmbeddingResponse {
|
|||
* OpenAI Compatible implementation of the embedder interface with batching and rate limiting.
|
||||
* This embedder allows using any OpenAI-compatible API endpoint by specifying a custom baseURL.
|
||||
*/
|
||||
interface HttpError extends Error {
|
||||
status?: number
|
||||
response?: {
|
||||
status?: number
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenAICompatibleEmbedder implements IEmbedder {
|
||||
private embeddingsClient: OpenAI
|
||||
|
|
@ -201,14 +196,31 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
const error = new Error(`HTTP ${response.status}: ${errorText}`) as HttpError
|
||||
error.status = response.status
|
||||
if (!response || !response.ok) {
|
||||
const status = response?.status || 0
|
||||
let errorText = "No response"
|
||||
try {
|
||||
if (response && typeof response.text === "function") {
|
||||
errorText = await response.text()
|
||||
} else if (response) {
|
||||
errorText = `Error ${status}`
|
||||
}
|
||||
} catch {
|
||||
// Ignore text parsing errors
|
||||
errorText = `Error ${status}`
|
||||
}
|
||||
const error = new Error(`HTTP ${status}: ${errorText}`) as HttpError
|
||||
error.status = status || response?.status || 0
|
||||
throw error
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
try {
|
||||
return await response.json()
|
||||
} catch (e) {
|
||||
const error = new Error(`Failed to parse response JSON`) as HttpError
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -272,11 +284,11 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
},
|
||||
}
|
||||
} catch (error) {
|
||||
const httpError = error as HttpError
|
||||
const isRateLimitError = httpError?.status === 429
|
||||
const hasMoreAttempts = attempts < MAX_RETRIES - 1
|
||||
|
||||
if (isRateLimitError && hasMoreAttempts) {
|
||||
// Check if it's a rate limit error
|
||||
const httpError = error as HttpError
|
||||
if (httpError?.status === 429 && hasMoreAttempts) {
|
||||
const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
|
||||
console.warn(
|
||||
t("embeddings:rateLimitRetry", {
|
||||
|
|
@ -292,37 +304,50 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
// Log the error for debugging
|
||||
console.error(`OpenAI Compatible embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
|
||||
|
||||
// Provide more context in the error message using robust error extraction
|
||||
let errorMessage = t("embeddings:unknownError")
|
||||
if (httpError?.message) {
|
||||
errorMessage = httpError.message
|
||||
} else if (typeof error === "string") {
|
||||
errorMessage = error
|
||||
} else if (error && typeof error === "object" && "toString" in error) {
|
||||
try {
|
||||
errorMessage = String(error)
|
||||
} catch {
|
||||
errorMessage = t("embeddings:unknownError")
|
||||
}
|
||||
}
|
||||
|
||||
const statusCode = httpError?.status || httpError?.response?.status
|
||||
|
||||
if (statusCode === 401) {
|
||||
throw new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
throw new Error(
|
||||
t("embeddings:failedWithStatus", { attempts: MAX_RETRIES, statusCode, errorMessage }),
|
||||
)
|
||||
} else {
|
||||
throw new Error(t("embeddings:failedWithError", { attempts: MAX_RETRIES, errorMessage }))
|
||||
}
|
||||
// Format and throw the error
|
||||
throw formatEmbeddingError(error, MAX_RETRIES)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the OpenAI-compatible embedder configuration by testing endpoint connectivity and API key
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(async () => {
|
||||
// Test with a minimal embedding request
|
||||
const testTexts = ["test"]
|
||||
const modelToUse = this.defaultModelId
|
||||
|
||||
let response: OpenAIEmbeddingResponse
|
||||
|
||||
if (this.isFullUrl) {
|
||||
// Test direct HTTP request for full endpoint URLs
|
||||
response = await this.makeDirectEmbeddingRequest(this.baseUrl, testTexts, modelToUse)
|
||||
} else {
|
||||
// Test using OpenAI SDK for base URLs
|
||||
response = (await this.embeddingsClient.embeddings.create({
|
||||
input: testTexts,
|
||||
model: modelToUse,
|
||||
encoding_format: "base64",
|
||||
})) as OpenAIEmbeddingResponse
|
||||
}
|
||||
|
||||
// Check if we got a valid response
|
||||
if (!response?.data || response.data.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "embeddings:validation.invalidResponse",
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}, "openai-compatible")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this embedder
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "../constants"
|
||||
import { getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling, formatEmbeddingError, HttpError } from "../shared/validation-helpers"
|
||||
|
||||
/**
|
||||
* OpenAI implementation of the embedder interface with batching and rate limiting
|
||||
|
|
@ -138,10 +139,11 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
|
|||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
const isRateLimitError = error?.status === 429
|
||||
const hasMoreAttempts = attempts < MAX_RETRIES - 1
|
||||
|
||||
if (isRateLimitError && hasMoreAttempts) {
|
||||
// Check if it's a rate limit error
|
||||
const httpError = error as HttpError
|
||||
if (httpError?.status === 429 && hasMoreAttempts) {
|
||||
const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
|
||||
console.warn(
|
||||
t("embeddings:rateLimitRetry", {
|
||||
|
|
@ -157,37 +159,38 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
|
|||
// Log the error for debugging
|
||||
console.error(`OpenAI embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
|
||||
|
||||
// Provide more context in the error message using robust error extraction
|
||||
let errorMessage = "Unknown error"
|
||||
if (error?.message) {
|
||||
errorMessage = error.message
|
||||
} else if (typeof error === "string") {
|
||||
errorMessage = error
|
||||
} else if (error && typeof error.toString === "function") {
|
||||
try {
|
||||
errorMessage = error.toString()
|
||||
} catch {
|
||||
errorMessage = "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
const statusCode = error?.status || error?.response?.status
|
||||
|
||||
if (statusCode === 401) {
|
||||
throw new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
throw new Error(
|
||||
t("embeddings:failedWithStatus", { attempts: MAX_RETRIES, statusCode, errorMessage }),
|
||||
)
|
||||
} else {
|
||||
throw new Error(t("embeddings:failedWithError", { attempts: MAX_RETRIES, errorMessage }))
|
||||
}
|
||||
// Format and throw the error
|
||||
throw formatEmbeddingError(error, MAX_RETRIES)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the OpenAI embedder configuration by attempting a minimal embedding request
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(async () => {
|
||||
// Test with a minimal embedding request
|
||||
const response = await this.embeddingsClient.embeddings.create({
|
||||
input: ["test"],
|
||||
model: this.defaultModelId,
|
||||
})
|
||||
|
||||
// Check if we got a valid response
|
||||
if (!response.data || response.data.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:openai.invalidResponseFormat"),
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}, "openai")
|
||||
}
|
||||
|
||||
get embedderInfo(): EmbedderInfo {
|
||||
return {
|
||||
name: "openai",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ export interface IEmbedder {
|
|||
* @returns Promise resolving to an EmbeddingResponse
|
||||
*/
|
||||
createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse>
|
||||
|
||||
/**
|
||||
* Validates the embedder configuration by testing connectivity and credentials.
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
validateConfiguration(): Promise<{ valid: boolean; error?: string }>
|
||||
|
||||
get embedderInfo(): EmbedderInfo
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,14 @@ export class CodeIndexManager {
|
|||
return { requiresRestart }
|
||||
}
|
||||
|
||||
// 3. CacheManager Initialization
|
||||
// 3. Check if workspace is available
|
||||
const workspacePath = getWorkspacePath()
|
||||
if (!workspacePath) {
|
||||
this._stateManager.setSystemState("Standby", "No workspace folder open")
|
||||
return { requiresRestart }
|
||||
}
|
||||
|
||||
// 4. CacheManager Initialization
|
||||
if (!this._cacheManager) {
|
||||
this._cacheManager = new CacheManager(this.context, this.workspacePath)
|
||||
await this._cacheManager.initialize()
|
||||
|
|
@ -215,6 +222,9 @@ export class CodeIndexManager {
|
|||
if (this._orchestrator) {
|
||||
this.stopWatcher()
|
||||
}
|
||||
// Clear existing services to ensure clean state
|
||||
this._orchestrator = undefined
|
||||
this._searchService = undefined
|
||||
|
||||
// (Re)Initialize service factory
|
||||
this._serviceFactory = new CodeIndexServiceFactory(
|
||||
|
|
@ -224,7 +234,14 @@ export class CodeIndexManager {
|
|||
)
|
||||
|
||||
const ignoreInstance = ignore()
|
||||
const ignorePath = path.join(getWorkspacePath(), ".gitignore")
|
||||
const workspacePath = getWorkspacePath()
|
||||
|
||||
if (!workspacePath) {
|
||||
this._stateManager.setSystemState("Standby", "")
|
||||
return
|
||||
}
|
||||
|
||||
const ignorePath = path.join(workspacePath, ".gitignore")
|
||||
try {
|
||||
const content = await fs.readFile(ignorePath, "utf8")
|
||||
ignoreInstance.add(content)
|
||||
|
|
@ -241,6 +258,17 @@ export class CodeIndexManager {
|
|||
ignoreInstance,
|
||||
)
|
||||
|
||||
// Validate embedder configuration before proceeding
|
||||
const validationResult = await this._serviceFactory.validateEmbedder(embedder)
|
||||
if (!validationResult.valid) {
|
||||
// Set error state with clear message
|
||||
this._stateManager.setSystemState(
|
||||
"Error",
|
||||
validationResult.error || "Embedder configuration validation failed",
|
||||
)
|
||||
throw new Error(validationResult.error || "Invalid embedder configuration")
|
||||
}
|
||||
|
||||
// (Re)Initialize orchestrator
|
||||
this._orchestrator = new CodeIndexOrchestrator(
|
||||
this._configManager!,
|
||||
|
|
@ -259,6 +287,9 @@ export class CodeIndexManager {
|
|||
embedder,
|
||||
vectorStore,
|
||||
)
|
||||
|
||||
// Clear any error state after successful recreation
|
||||
this._stateManager.setSystemState("Standby", "")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -274,13 +305,16 @@ export class CodeIndexManager {
|
|||
const isFeatureEnabled = this.isFeatureEnabled
|
||||
const isFeatureConfigured = this.isFeatureConfigured
|
||||
|
||||
// If configuration changes require a restart and the manager is initialized, restart the service
|
||||
if (requiresRestart && isFeatureEnabled && isFeatureConfigured && this.isInitialized) {
|
||||
// Recreate services with new configuration
|
||||
await this._recreateServices()
|
||||
|
||||
// Start indexing with new services
|
||||
this.startIndexing()
|
||||
if (requiresRestart && isFeatureEnabled && isFeatureConfigured) {
|
||||
try {
|
||||
// Recreate services with new configuration
|
||||
await this._recreateServices()
|
||||
} catch (error) {
|
||||
// Error state already set in _recreateServices
|
||||
console.error("Failed to recreate services:", error)
|
||||
// Re-throw the error so the caller knows validation failed
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,31 @@ export class CodeIndexOrchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for partial failures - if a significant portion of blocks failed
|
||||
const failureRate = (cumulativeBlocksFoundSoFar - cumulativeBlocksIndexed) / cumulativeBlocksFoundSoFar
|
||||
if (batchErrors.length > 0 && failureRate > 0.1) {
|
||||
// More than 10% of blocks failed to index
|
||||
const firstError = batchErrors[0]
|
||||
throw new Error(
|
||||
`Indexing partially failed: Only ${cumulativeBlocksIndexed} of ${cumulativeBlocksFoundSoFar} blocks were indexed. ${firstError.message}`,
|
||||
)
|
||||
}
|
||||
|
||||
// CRITICAL: If there were ANY batch errors and NO blocks were successfully indexed,
|
||||
// this is a complete failure regardless of the failure rate calculation
|
||||
if (batchErrors.length > 0 && cumulativeBlocksIndexed === 0) {
|
||||
const firstError = batchErrors[0]
|
||||
throw new Error(`Indexing failed completely: ${firstError.message}`)
|
||||
}
|
||||
|
||||
// Final sanity check: If we found blocks but indexed none and somehow no errors were reported,
|
||||
// this is still a failure
|
||||
if (cumulativeBlocksFoundSoFar > 0 && cumulativeBlocksIndexed === 0) {
|
||||
throw new Error(
|
||||
"Indexing failed: No code blocks were successfully indexed despite finding files to process. This indicates a critical embedder failure.",
|
||||
)
|
||||
}
|
||||
|
||||
await this._startWatcher()
|
||||
|
||||
this.stateManager.setSystemState("Indexed", "File watcher started.")
|
||||
|
|
|
|||
|
|
@ -66,6 +66,23 @@ export class CodeIndexServiceFactory {
|
|||
throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an embedder instance to ensure it's properly configured.
|
||||
* @param embedder The embedder instance to validate
|
||||
* @returns Promise resolving to validation result
|
||||
*/
|
||||
public async validateEmbedder(embedder: IEmbedder): Promise<{ valid: boolean; error?: string }> {
|
||||
try {
|
||||
return await embedder.validateConfiguration()
|
||||
} catch (error) {
|
||||
// If validation throws an exception, preserve the original error message
|
||||
return {
|
||||
valid: false,
|
||||
error: error instanceof Error ? error.message : "embeddings:validation.configurationError",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vector store instance using the current configuration.
|
||||
*/
|
||||
|
|
|
|||
187
src/services/code-index/shared/validation-helpers.ts
Normal file
187
src/services/code-index/shared/validation-helpers.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import { t } from "../../../i18n"
|
||||
import { serializeError } from "serialize-error"
|
||||
|
||||
/**
|
||||
* HTTP error interface for embedder errors
|
||||
*/
|
||||
export interface HttpError extends Error {
|
||||
status?: number
|
||||
response?: {
|
||||
status?: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common error types that can occur during embedder validation
|
||||
*/
|
||||
export interface ValidationError {
|
||||
status?: number
|
||||
message?: string
|
||||
name?: string
|
||||
code?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps HTTP status codes to appropriate error messages
|
||||
*/
|
||||
export function getErrorMessageForStatus(status: number | undefined, embedderType: string): string | undefined {
|
||||
switch (status) {
|
||||
case 401:
|
||||
case 403:
|
||||
return "embeddings:validation.authenticationFailed"
|
||||
case 404:
|
||||
return embedderType === "openai"
|
||||
? "embeddings:validation.modelNotAvailable"
|
||||
: "embeddings:validation.invalidEndpoint"
|
||||
case 429:
|
||||
return "embeddings:validation.serviceUnavailable"
|
||||
default:
|
||||
if (status && status >= 400 && status < 600) {
|
||||
return "embeddings:validation.configurationError"
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts status code from various error formats
|
||||
*/
|
||||
export function extractStatusCode(error: any): number | undefined {
|
||||
// Direct status property
|
||||
if (error?.status) return error.status
|
||||
|
||||
// Response status property
|
||||
if (error?.response?.status) return error.response.status
|
||||
|
||||
// Extract from error message (e.g., "HTTP 404: Not Found")
|
||||
if (error?.message) {
|
||||
const match = error.message.match(/HTTP (\d+):/)
|
||||
if (match) {
|
||||
return parseInt(match[1], 10)
|
||||
}
|
||||
}
|
||||
|
||||
// Use serialize-error as fallback for complex objects
|
||||
const serialized = serializeError(error)
|
||||
if (serialized?.status) return serialized.status
|
||||
if (serialized?.response?.status) return serialized.response.status
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts error message from various error formats
|
||||
*/
|
||||
export function extractErrorMessage(error: any): string {
|
||||
if (error?.message) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error
|
||||
}
|
||||
|
||||
if (error && typeof error === "object" && "toString" in error) {
|
||||
try {
|
||||
return String(error)
|
||||
} catch {
|
||||
return "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
// Use serialize-error as fallback for complex objects
|
||||
const serialized = serializeError(error)
|
||||
if (serialized?.message) {
|
||||
return serialized.message
|
||||
}
|
||||
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard validation error handler for embedder configuration validation
|
||||
* Returns a consistent error response based on the error type
|
||||
*/
|
||||
export function handleValidationError(
|
||||
error: any,
|
||||
embedderType: string,
|
||||
customHandlers?: {
|
||||
beforeStandardHandling?: (error: any) => { valid: boolean; error: string } | undefined
|
||||
},
|
||||
): { valid: boolean; error: string } {
|
||||
// Serialize the error to ensure we have access to all properties
|
||||
const serializedError = serializeError(error)
|
||||
|
||||
// Allow custom handling first (pass original error for backward compatibility)
|
||||
if (customHandlers?.beforeStandardHandling) {
|
||||
const customResult = customHandlers.beforeStandardHandling(error)
|
||||
if (customResult) return customResult
|
||||
}
|
||||
|
||||
// Extract status code and error message from serialized error
|
||||
const statusCode = extractStatusCode(serializedError)
|
||||
const errorMessage = extractErrorMessage(serializedError)
|
||||
|
||||
// Check for status-based errors first
|
||||
const statusError = getErrorMessageForStatus(statusCode, embedderType)
|
||||
if (statusError) {
|
||||
return { valid: false, error: statusError }
|
||||
}
|
||||
|
||||
// Check for connection errors
|
||||
if (errorMessage) {
|
||||
if (
|
||||
errorMessage.includes("ENOTFOUND") ||
|
||||
errorMessage.includes("ECONNREFUSED") ||
|
||||
errorMessage.includes("ETIMEDOUT") ||
|
||||
errorMessage === "AbortError" ||
|
||||
errorMessage.includes("HTTP 0:") ||
|
||||
errorMessage === "No response"
|
||||
) {
|
||||
return { valid: false, error: "embeddings:validation.connectionFailed" }
|
||||
}
|
||||
|
||||
if (errorMessage.includes("Failed to parse response JSON")) {
|
||||
return { valid: false, error: "embeddings:validation.invalidResponse" }
|
||||
}
|
||||
}
|
||||
|
||||
// For generic errors, preserve the original error message if it's not a standard one
|
||||
if (errorMessage && errorMessage !== "Unknown error") {
|
||||
return { valid: false, error: errorMessage }
|
||||
}
|
||||
|
||||
// Fallback to generic error
|
||||
return { valid: false, error: "embeddings:validation.configurationError" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an async validation function with standard error handling
|
||||
*/
|
||||
export async function withValidationErrorHandling<T extends { valid: boolean; error?: string }>(
|
||||
validationFn: () => Promise<T>,
|
||||
embedderType: string,
|
||||
customHandlers?: Parameters<typeof handleValidationError>[2],
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
try {
|
||||
return await validationFn()
|
||||
} catch (error) {
|
||||
return handleValidationError(error, embedderType, customHandlers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an embedding error message based on the error type and context
|
||||
*/
|
||||
export function formatEmbeddingError(error: any, maxRetries: number): Error {
|
||||
const errorMessage = extractErrorMessage(error)
|
||||
const statusCode = extractStatusCode(error)
|
||||
|
||||
if (statusCode === 401) {
|
||||
return new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
return new Error(t("embeddings:failedWithStatus", { attempts: maxRetries, statusCode, errorMessage }))
|
||||
} else {
|
||||
return new Error(t("embeddings:failedWithError", { attempts: maxRetries, errorMessage }))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue