fix: prevent silent failures in code indexing with OpenAI-compatible endpoints (#4398)

This commit is contained in:
hannesrudolph 2025-07-02 14:01:00 -06:00 committed by Daniel Riccio
parent b7d5a964c7
commit 4277572e83
No known key found for this signature in database
GPG key ID: FFD5FD825F8E8209
29 changed files with 535 additions and 55 deletions

View file

@ -1807,6 +1807,43 @@ export const webviewMessageHandler = async (
break
}
case "codebaseIndexConfig": {
// Handle test action separately
if (message.action === "test") {
try {
if (!provider.codeIndexManager) {
throw new Error("Code index manager not available")
}
// Get the service factory from the manager
const serviceFactory = provider.codeIndexManager.getServiceFactory()
if (!serviceFactory) {
throw new Error("Service factory not available")
}
// Test the configuration
const isValid = await serviceFactory.validateEmbedderConfig()
// Send test result back to webview
provider.postMessageToWebview({
type: "codebaseIndexTestResult",
success: isValid,
message: isValid ? "Configuration is valid" : "Configuration test failed",
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
provider.log(`[CodeIndexManager] Configuration test error: ${errorMessage}`)
// Send error result back to webview
provider.postMessageToWebview({
type: "codebaseIndexTestResult",
success: false,
message: errorMessage,
})
}
break
}
// Normal configuration update flow
const codebaseIndexConfig = message.values ?? {
codebaseIndexEnabled: false,
codebaseIndexQdrantUrl: "http://localhost:6333",
@ -1823,16 +1860,42 @@ export const webviewMessageHandler = async (
// If now configured and enabled, start indexing automatically
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
if (!provider.codeIndexManager.isInitialized) {
await provider.codeIndexManager.initialize(provider.contextProxy)
try {
await provider.codeIndexManager.initialize(provider.contextProxy)
} catch (initError) {
// Initialization failed - send error status to webview
const errorMessage = initError instanceof Error ? initError.message : String(initError)
provider.log(`[CodeIndexManager] Initialization error: ${errorMessage}`)
// Send error status update to webview
const status = provider.codeIndexManager.getCurrentStatus()
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: status,
})
// Re-throw to prevent indexing attempt
throw initError
}
}
// Start indexing in background (no await)
provider.codeIndexManager.startIndexing()
}
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
provider.log(
`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`,
`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${errorMessage}`,
)
// Send error notification to webview if manager exists
if (provider.codeIndexManager) {
const status = provider.codeIndexManager.getCurrentStatus()
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: status,
})
}
}
await provider.postStateToWebview()

View file

@ -106,4 +106,46 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
name: "ollama",
}
}
/**
* Validates the Ollama configuration by attempting to connect to the endpoint.
* @param baseUrl - The base URL of the Ollama instance
* @param modelId - The model ID to check
* @returns A promise that resolves to true if valid, or throws an error with details
*/
static async validateEndpoint(baseUrl: string, modelId: string): Promise<boolean> {
const url = `${baseUrl}/api/tags`
try {
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Ollama API not found at ${baseUrl}. Is Ollama running?`)
}
throw new Error(`Failed to connect to Ollama: ${response.status} ${response.statusText}`)
}
const data = await response.json()
const models = data.models || []
const modelNames = models.map((m: any) => m.name)
// Check if the specified model exists
if (!modelNames.includes(modelId)) {
throw new Error(`Model '${modelId}' not found. Available models: ${modelNames.join(", ") || "none"}`)
}
return true
} catch (error: any) {
if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
throw new Error(`Cannot connect to Ollama at ${baseUrl}. Please ensure Ollama is running.`)
}
throw error
}
}
}

View file

@ -68,6 +68,47 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
this.maxItemTokens = maxItemTokens || MAX_ITEM_TOKENS
}
/**
* Validates the endpoint by attempting a minimal embedding request
* @param baseUrl The base URL to validate
* @param apiKey The API key to use for validation
* @param modelId Optional model ID to test with
* @returns Promise resolving to true if valid
* @throws Error with descriptive message if validation fails
*/
static async validateEndpoint(baseUrl: string, apiKey: string, modelId?: string): Promise<boolean> {
try {
const client = new OpenAI({
baseURL: baseUrl,
apiKey: apiKey,
})
const testModel = modelId || getDefaultModelId("openai-compatible")
// Try a minimal embedding request
await client.embeddings.create({
input: "test",
model: testModel,
})
return true
} catch (error: any) {
let errorMessage = t("embeddings:unknownError")
if (error?.status === 401) {
errorMessage = t("embeddings:authenticationFailed")
} else if (error?.status === 404) {
errorMessage = `Endpoint not found: ${baseUrl}`
} else if (error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND") {
errorMessage = `Cannot connect to ${baseUrl}`
} else if (error?.message) {
errorMessage = error.message
}
throw new Error(errorMessage)
}
}
/**
* Creates embeddings for the given texts with batching and rate limiting
* @param texts Array of text strings to embed

View file

@ -193,4 +193,42 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
name: "openai",
}
}
/**
* Validates the OpenAI configuration by attempting to list models.
* @param apiKey - The OpenAI API key
* @param modelId - The model ID to check
* @returns A promise that resolves to true if valid, or throws an error with details
*/
static async validateEndpoint(apiKey: string, modelId: string): Promise<boolean> {
const client = new OpenAI({ apiKey })
try {
// Try to list models to validate the API key
const models = await client.models.list()
const modelIds = models.data.map((m) => m.id)
// Check if the specified embedding model exists or is a known model
const knownEmbeddingModels = ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]
if (!modelIds.includes(modelId) && !knownEmbeddingModels.includes(modelId)) {
throw new Error(
`Model '${modelId}' not found. Available embedding models: ${knownEmbeddingModels.join(", ")}`,
)
}
return true
} catch (error: any) {
if (error?.status === 401) {
throw new Error("Invalid API key. Please check your OpenAI API key.")
}
if (error?.status === 429) {
throw new Error("Rate limit exceeded. Please try again later.")
}
if (error?.message?.includes("fetch failed") || error?.message?.includes("ECONNREFUSED")) {
throw new Error("Network error. Please check your internet connection.")
}
throw new Error(`Failed to validate OpenAI configuration: ${error?.message || "Unknown error"}`)
}
}
}

View file

@ -72,6 +72,21 @@ export interface ICodeIndexManager {
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini"
export interface IndexingStatus {
systemStatus: IndexingState
message?: string
processedItems?: number
totalItems?: number
currentItemUnit?: string
errorDetails?: {
type: "configuration" | "authentication" | "network" | "validation" | "unknown"
message: string
suggestion?: string
endpoint?: string
timestamp: number
}
}
export interface IndexProgressUpdate {
systemStatus: IndexingState
message?: string

View file

@ -229,31 +229,66 @@ export class CodeIndexManager {
console.error("Unexpected error loading .gitignore:", error)
}
// (Re)Create shared service instances
const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices(
this.context,
this._cacheManager!,
ignoreInstance,
)
try {
// (Re)Create shared service instances
const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices(
this.context,
this._cacheManager!,
ignoreInstance,
)
// (Re)Initialize orchestrator
this._orchestrator = new CodeIndexOrchestrator(
this._configManager!,
this._stateManager,
this.workspacePath,
this._cacheManager!,
vectorStore,
scanner,
fileWatcher,
)
// (Re)Initialize orchestrator
this._orchestrator = new CodeIndexOrchestrator(
this._configManager!,
this._stateManager,
this.workspacePath,
this._cacheManager!,
vectorStore,
scanner,
fileWatcher,
)
// (Re)Initialize search service
this._searchService = new CodeIndexSearchService(
this._configManager!,
this._stateManager,
embedder,
vectorStore,
)
// (Re)Initialize search service
this._searchService = new CodeIndexSearchService(
this._configManager!,
this._stateManager,
embedder,
vectorStore,
)
} catch (error) {
// Handle service creation errors
console.error("Failed to create code index services:", error)
// Determine error type and create appropriate error details
let errorType: "configuration" | "authentication" | "network" | "validation" | "unknown" = "unknown"
let errorMessage = error instanceof Error ? error.message : String(error)
let suggestion: string | undefined
if (errorMessage.includes("configuration missing") || errorMessage.includes("missing for")) {
errorType = "configuration"
suggestion = "Please check your embedder configuration in the settings."
} else if (errorMessage.includes("authentication") || errorMessage.includes("API key")) {
errorType = "authentication"
suggestion = "Please verify your API key is correct and has the necessary permissions."
} else if (errorMessage.includes("network") || errorMessage.includes("connect")) {
errorType = "network"
suggestion = "Please check your network connection and ensure the service endpoints are accessible."
} else if (errorMessage.includes("dimension") || errorMessage.includes("model")) {
errorType = "validation"
suggestion = "Please ensure your model configuration is compatible with the selected provider."
}
// Set error state with details
this._stateManager.setSystemState("Error", errorMessage, {
type: errorType,
message: errorMessage,
suggestion,
timestamp: Date.now(),
})
// Re-throw to be handled by caller
throw error
}
}
/**
@ -279,4 +314,12 @@ export class CodeIndexManager {
}
}
}
/**
* Gets the service factory instance for testing configurations.
* @returns The service factory instance or undefined if not initialized
*/
public getServiceFactory(): CodeIndexServiceFactory | undefined {
return this._serviceFactory
}
}

View file

@ -31,7 +31,7 @@ export class CodeIndexServiceFactory {
if (provider === "openai") {
if (!config.openAiOptions?.openAiNativeApiKey) {
throw new Error("OpenAI configuration missing for embedder creation")
throw new Error("OpenAI API key is required. Please configure it in the settings.")
}
return new OpenAiEmbedder({
...config.openAiOptions,
@ -39,7 +39,7 @@ export class CodeIndexServiceFactory {
})
} else if (provider === "ollama") {
if (!config.ollamaOptions?.ollamaBaseUrl) {
throw new Error("Ollama configuration missing for embedder creation")
throw new Error("Ollama base URL is required. Please configure it in the settings.")
}
return new CodeIndexOllamaEmbedder({
...config.ollamaOptions,
@ -47,7 +47,12 @@ export class CodeIndexServiceFactory {
})
} else if (provider === "openai-compatible") {
if (!config.openAiCompatibleOptions?.baseUrl || !config.openAiCompatibleOptions?.apiKey) {
throw new Error("OpenAI Compatible configuration missing for embedder creation")
const missing = []
if (!config.openAiCompatibleOptions?.baseUrl) missing.push("base URL")
if (!config.openAiCompatibleOptions?.apiKey) missing.push("API key")
throw new Error(
`OpenAI-compatible ${missing.join(" and ")} required. Please configure in the settings.`,
)
}
return new OpenAICompatibleEmbedder(
config.openAiCompatibleOptions.baseUrl,
@ -64,6 +69,45 @@ export class CodeIndexServiceFactory {
throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`)
}
/**
* Validates the embedder configuration by testing the connection.
* @returns A promise that resolves to true if valid, or throws an error with details
*/
public async validateEmbedderConfig(): Promise<boolean> {
const config = this.configManager.getConfig()
const provider = config.embedderProvider as EmbedderProvider
try {
if (provider === "openai") {
if (!config.openAiOptions?.openAiNativeApiKey) {
throw new Error("OpenAI API key is required")
}
const modelId = config.modelId || "text-embedding-3-small"
return await OpenAiEmbedder.validateEndpoint(config.openAiOptions.openAiNativeApiKey, modelId)
} else if (provider === "ollama") {
if (!config.ollamaOptions?.ollamaBaseUrl) {
throw new Error("Ollama base URL is required")
}
const modelId = config.modelId || "nomic-embed-text:latest"
return await CodeIndexOllamaEmbedder.validateEndpoint(config.ollamaOptions.ollamaBaseUrl, modelId)
} else if (provider === "openai-compatible") {
if (!config.openAiCompatibleOptions?.baseUrl || !config.openAiCompatibleOptions?.apiKey) {
throw new Error("OpenAI-compatible base URL and API key are required")
}
const modelId = config.modelId || "text-embedding-3-small"
return await OpenAICompatibleEmbedder.validateEndpoint(
config.openAiCompatibleOptions.baseUrl,
config.openAiCompatibleOptions.apiKey,
modelId,
)
}
throw new Error(`Invalid embedder type: ${provider}`)
} catch (error: any) {
// Re-throw with more context
throw new Error(`${provider} validation failed: ${error.message}`)
}
}
/**
* Creates a vector store instance using the current configuration.
*/

View file

@ -2,12 +2,21 @@ import * as vscode from "vscode"
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
export interface ErrorDetails {
type: "configuration" | "authentication" | "network" | "validation" | "unknown"
message: string
suggestion?: string
endpoint?: string
timestamp: number
}
export class CodeIndexStateManager {
private _systemStatus: IndexingState = "Standby"
private _statusMessage: string = ""
private _processedItems: number = 0
private _totalItems: number = 0
private _currentItemUnit: string = "blocks"
private _errorDetails: ErrorDetails | undefined = undefined
private _progressEmitter = new vscode.EventEmitter<ReturnType<typeof this.getCurrentStatus>>()
// --- Public API ---
@ -25,14 +34,17 @@ export class CodeIndexStateManager {
processedItems: this._processedItems,
totalItems: this._totalItems,
currentItemUnit: this._currentItemUnit,
errorDetails: this._errorDetails,
}
}
// --- State Management ---
public setSystemState(newState: IndexingState, message?: string): void {
public setSystemState(newState: IndexingState, message?: string, errorDetails?: ErrorDetails): void {
const stateChanged =
newState !== this._systemStatus || (message !== undefined && message !== this._statusMessage)
newState !== this._systemStatus ||
(message !== undefined && message !== this._statusMessage) ||
(errorDetails !== undefined && errorDetails !== this._errorDetails)
if (stateChanged) {
this._systemStatus = newState
@ -40,6 +52,14 @@ export class CodeIndexStateManager {
this._statusMessage = message
}
// Handle error details
if (newState === "Error" && errorDetails) {
this._errorDetails = errorDetails
} else if (newState !== "Error") {
// Clear error details when transitioning to non-error states
this._errorDetails = undefined
}
// Reset progress counters if moving to a non-indexing state or starting fresh
if (newState !== "Indexing") {
this._processedItems = 0

View file

@ -32,6 +32,13 @@ export interface IndexingStatus {
processedItems: number
totalItems: number
currentItemUnit?: string
errorDetails?: {
type: "configuration" | "authentication" | "network" | "validation" | "unknown"
message: string
suggestion?: string
endpoint?: string
timestamp: number
}
}
export interface IndexingStatusUpdateMessage {
@ -100,6 +107,7 @@ export interface ExtensionMessage {
| "indexingStatusUpdate"
| "indexCleared"
| "codebaseIndexConfig"
| "codebaseIndexTestResult"
| "marketplaceInstallResult"
| "marketplaceData"
| "shareTaskSuccess"
@ -154,6 +162,7 @@ export interface ExtensionMessage {
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
visibility?: ShareVisibility
rulesFolderPath?: string
message?: string // For test results and other messages
}
export type ExtensionState = Pick<

View file

@ -163,6 +163,7 @@ export interface WebviewMessage {
| "indexCleared"
| "focusPanelRequest"
| "codebaseIndexConfig"
| "codebaseIndexTestResult"
| "profileThresholds"
| "setHistoryPreviewCollapsed"
| "openExternal"
@ -223,6 +224,7 @@ export interface WebviewMessage {
visibility?: ShareVisibility // For share visibility
hasContent?: boolean // For checkRulesDirectoryResult
checkOnly?: boolean // For deleteCustomMode check
action?: string // For actions like "test" in codebaseIndexConfig
}
export const checkoutDiffPayloadSchema = z.object({
@ -245,6 +247,16 @@ export type CheckpointRestorePayload = z.infer<typeof checkoutRestorePayloadSche
export interface IndexingStatusPayload {
state: "Standby" | "Indexing" | "Indexed" | "Error"
message: string
processedItems?: number
totalItems?: number
currentItemUnit?: string
errorDetails?: {
type: "configuration" | "authentication" | "network" | "validation" | "unknown"
message: string
suggestion?: string
endpoint?: string
timestamp: number
}
}
export interface IndexClearedPayload {

View file

@ -47,7 +47,7 @@ interface CodeIndexSettingsProps {
areSettingsCommitted: boolean
}
import type { IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"
import type { IndexingStatusUpdateMessage, ExtensionMessage } from "@roo/ExtensionMessage"
export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
codebaseIndexModels,
@ -59,7 +59,20 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
}) => {
const { t } = useAppTranslation()
const DEFAULT_QDRANT_URL = "http://localhost:6333"
const [indexingStatus, setIndexingStatus] = useState({
const [indexingStatus, setIndexingStatus] = useState<{
systemStatus: string
message: string
processedItems: number
totalItems: number
currentItemUnit: string
errorDetails?: {
type: "configuration" | "authentication" | "network" | "validation" | "unknown"
message: string
suggestion?: string
endpoint?: string
timestamp: number
}
}>({
systemStatus: "Standby",
message: "",
processedItems: 0,
@ -67,6 +80,8 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
currentItemUnit: "items",
})
const [advancedExpanded, setAdvancedExpanded] = useState(false)
const [testingConfig, setTestingConfig] = useState(false)
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null)
// Safely calculate available models for current provider
const currentProvider = codebaseIndexConfig?.codebaseIndexEmbedderProvider
@ -83,15 +98,25 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
// Set up interval for periodic status updates
// Set up message listener for status updates
const handleMessage = (event: MessageEvent<IndexingStatusUpdateMessage>) => {
const handleMessage = (event: MessageEvent<ExtensionMessage>) => {
if (event.data.type === "indexingStatusUpdate") {
const data = event.data as IndexingStatusUpdateMessage
setIndexingStatus({
systemStatus: event.data.values.systemStatus,
message: event.data.values.message || "",
processedItems: event.data.values.processedItems,
totalItems: event.data.values.totalItems,
currentItemUnit: event.data.values.currentItemUnit || "items",
systemStatus: data.values.systemStatus,
message: data.values.message || "",
processedItems: data.values.processedItems || 0,
totalItems: data.values.totalItems || 0,
currentItemUnit: data.values.currentItemUnit || "items",
errorDetails: data.values.errorDetails,
})
} else if (event.data.type === "codebaseIndexTestResult") {
setTestingConfig(false)
setTestResult({
success: event.data.success || false,
message: event.data.message || "Test completed",
})
// Clear test result after 5 seconds
setTimeout(() => setTestResult(null), 5000)
}
}
@ -240,6 +265,39 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
{indexingStatus.message ? ` - ${indexingStatus.message}` : ""}
</div>
{/* Error Details Display */}
{indexingStatus.systemStatus === "Error" && indexingStatus.errorDetails && (
<div className="bg-vscode-inputValidation-errorBackground border border-vscode-inputValidation-errorBorder rounded p-3 mt-2">
<div className="flex items-start gap-2">
<span className="codicon codicon-error text-vscode-inputValidation-errorForeground flex-shrink-0 mt-0.5"></span>
<div className="flex-1">
<div className="text-sm font-medium text-vscode-inputValidation-errorForeground mb-1">
{indexingStatus.errorDetails.type === "configuration" && "Configuration Error"}
{indexingStatus.errorDetails.type === "authentication" &&
"Authentication Error"}
{indexingStatus.errorDetails.type === "network" && "Network Error"}
{indexingStatus.errorDetails.type === "validation" && "Validation Error"}
{indexingStatus.errorDetails.type === "unknown" && "Unknown Error"}
</div>
<div className="text-sm text-vscode-foreground mb-2">
{indexingStatus.errorDetails.message}
</div>
{indexingStatus.errorDetails.suggestion && (
<div className="text-sm text-vscode-descriptionForeground">
<span className="codicon codicon-lightbulb mr-1"></span>
{indexingStatus.errorDetails.suggestion}
</div>
)}
{indexingStatus.errorDetails.endpoint && (
<div className="text-xs text-vscode-descriptionForeground mt-1">
Endpoint: {indexingStatus.errorDetails.endpoint}
</div>
)}
</div>
</div>
</div>
)}
{indexingStatus.systemStatus === "Indexing" && (
<div className="space-y-1">
<ProgressPrimitive.Root
@ -501,6 +559,45 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
)}
<div className="flex gap-2">
<VSCodeButton
onClick={() => {
setTestingConfig(true)
setTestResult(null)
vscode.postMessage({
type: "codebaseIndexConfig",
action: "test",
values: {
...codebaseIndexConfig,
// Include API configuration values based on provider
...(codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai" && {
codeIndexOpenAiKey: apiConfiguration.codeIndexOpenAiKey,
}),
...(codebaseIndexConfig?.codebaseIndexEmbedderProvider ===
"openai-compatible" && {
codebaseIndexOpenAiCompatibleBaseUrl:
apiConfiguration.codebaseIndexOpenAiCompatibleBaseUrl,
codebaseIndexOpenAiCompatibleApiKey:
apiConfiguration.codebaseIndexOpenAiCompatibleApiKey,
codebaseIndexOpenAiCompatibleModelDimension:
apiConfiguration.codebaseIndexOpenAiCompatibleModelDimension,
}),
...(codebaseIndexConfig?.codebaseIndexEmbedderProvider === "ollama" && {
codebaseIndexEmbedderBaseUrl:
codebaseIndexConfig.codebaseIndexEmbedderBaseUrl,
}),
},
})
}}
disabled={
testingConfig ||
!areSettingsCommitted ||
!validateIndexingConfig(codebaseIndexConfig, apiConfiguration)
}
appearance="secondary">
{testingConfig
? t("settings:codeIndex.testingButton")
: t("settings:codeIndex.testConfigButton")}
</VSCodeButton>
{(indexingStatus.systemStatus === "Error" || indexingStatus.systemStatus === "Standby") && (
<VSCodeButton
onClick={() => vscode.postMessage({ type: "startIndexing" })}
@ -541,6 +638,26 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
)}
</div>
{/* Test Result Display */}
{testResult && (
<div
className={`p-3 rounded border ${
testResult.success
? "bg-vscode-testing-iconPassed/10 border-vscode-testing-iconPassed"
: "bg-vscode-inputValidation-errorBackground border-vscode-inputValidation-errorBorder"
}`}>
<div className="flex items-center gap-2">
<span
className={`codicon ${
testResult.success
? "codicon-pass text-vscode-testing-iconPassed"
: "codicon-error text-vscode-inputValidation-errorForeground"
}`}></span>
<span className="text-sm">{testResult.message}</span>
</div>
</div>
)}
{/* Advanced Configuration Section */}
<div className="mt-4">
<button

View file

@ -71,7 +71,9 @@
"description": "Aquesta acció no es pot desfer. Eliminarà permanentment les dades d'índex de la vostra base de codi.",
"cancelButton": "Cancel·lar",
"confirmButton": "Esborrar dades"
}
},
"testConfigButton": "Prova la configuració",
"testingButton": "Provant..."
},
"autoApprove": {
"description": "Permet que Roo realitzi operacions automàticament sense requerir aprovació. Activeu aquesta configuració només si confieu plenament en la IA i enteneu els riscos de seguretat associats.",

View file

@ -71,7 +71,9 @@
"description": "Diese Aktion kann nicht rückgängig gemacht werden. Dies wird Ihre Codebase-Indexdaten dauerhaft löschen.",
"cancelButton": "Abbrechen",
"confirmButton": "Daten löschen"
}
},
"testConfigButton": "Konfiguration testen",
"testingButton": "Testen..."
},
"autoApprove": {
"description": "Erlaubt Roo, Operationen automatisch ohne Genehmigung durchzuführen. Aktiviere diese Einstellungen nur, wenn du der KI vollständig vertraust und die damit verbundenen Sicherheitsrisiken verstehst.",

View file

@ -66,6 +66,8 @@
"startIndexingButton": "Start Indexing",
"clearIndexDataButton": "Clear Index Data",
"unsavedSettingsMessage": "Please save your settings before starting the indexing process.",
"testConfigButton": "Test Configuration",
"testingButton": "Testing...",
"clearDataDialog": {
"title": "Are you sure?",
"description": "This action cannot be undone. This will permanently delete your codebase index data.",

View file

@ -71,7 +71,9 @@
"description": "Esta acción no se puede deshacer. Esto eliminará permanentemente los datos de índice de tu base de código.",
"cancelButton": "Cancelar",
"confirmButton": "Borrar datos"
}
},
"testConfigButton": "Probar configuración",
"testingButton": "Probando..."
},
"autoApprove": {
"description": "Permitir que Roo realice operaciones automáticamente sin requerir aprobación. Habilite esta configuración solo si confía plenamente en la IA y comprende los riesgos de seguridad asociados.",

View file

@ -71,7 +71,9 @@
"description": "Cette action ne peut pas être annulée. Cela supprimera définitivement les données d'index de votre base de code.",
"cancelButton": "Annuler",
"confirmButton": "Effacer les données"
}
},
"testConfigButton": "Tester la configuration",
"testingButton": "Test en cours..."
},
"autoApprove": {
"description": "Permettre à Roo d'effectuer automatiquement des opérations sans requérir d'approbation. Activez ces paramètres uniquement si vous faites entièrement confiance à l'IA et que vous comprenez les risques de sécurité associés.",

View file

@ -71,7 +71,9 @@
"description": "यह क्रिया पूर्ववत नहीं की जा सकती। यह आपके कोडबेस इंडेक्स डेटा को स्थायी रूप से हटा देगी।",
"cancelButton": "रद्द करें",
"confirmButton": "डेटा साफ़ करें"
}
},
"testConfigButton": "कॉन्फ़िगरेशन का परीक्षण करें",
"testingButton": "परीक्षण हो रहा है..."
},
"autoApprove": {
"description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।",

View file

@ -71,7 +71,9 @@
"description": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus data indeks codebase kamu secara permanen.",
"cancelButton": "Batal",
"confirmButton": "Hapus Data"
}
},
"testConfigButton": "Uji Konfigurasi",
"testingButton": "Menguji..."
},
"autoApprove": {
"description": "Izinkan Roo untuk secara otomatis melakukan operasi tanpa memerlukan persetujuan. Aktifkan pengaturan ini hanya jika kamu sepenuhnya mempercayai AI dan memahami risiko keamanan yang terkait.",

View file

@ -71,7 +71,9 @@
"description": "Questa azione non può essere annullata. Eliminerà permanentemente i dati di indice del tuo codice.",
"cancelButton": "Annulla",
"confirmButton": "Cancella dati"
}
},
"testConfigButton": "Testa configurazione",
"testingButton": "Test in corso..."
},
"autoApprove": {
"description": "Permetti a Roo di eseguire automaticamente operazioni senza richiedere approvazione. Abilita queste impostazioni solo se ti fidi completamente dell'IA e comprendi i rischi di sicurezza associati.",

View file

@ -71,7 +71,9 @@
"description": "この操作は元に戻せません。コードベースのインデックスデータが完全に削除されます。",
"cancelButton": "キャンセル",
"confirmButton": "データをクリア"
}
},
"testConfigButton": "設定をテスト",
"testingButton": "テスト中..."
},
"autoApprove": {
"description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。",

View file

@ -71,7 +71,9 @@
"description": "이 작업은 취소할 수 없습니다. 코드베이스 인덱스 데이터가 영구적으로 삭제됩니다.",
"cancelButton": "취소",
"confirmButton": "데이터 지우기"
}
},
"testConfigButton": "구성 테스트",
"testingButton": "테스트 중..."
},
"autoApprove": {
"description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.",

View file

@ -71,7 +71,9 @@
"description": "Deze actie kan niet ongedaan worden gemaakt. Dit zal je codebase-indexgegevens permanent verwijderen.",
"cancelButton": "Annuleren",
"confirmButton": "Gegevens wissen"
}
},
"testConfigButton": "Configuratie testen",
"testingButton": "Testen..."
},
"autoApprove": {
"description": "Sta Roo toe om automatisch handelingen uit te voeren zonder goedkeuring. Schakel deze instellingen alleen in als je de AI volledig vertrouwt en de bijbehorende beveiligingsrisico's begrijpt.",

View file

@ -71,7 +71,9 @@
"description": "Tej akcji nie można cofnąć. Spowoduje to trwałe usunięcie danych indeksu Twojego kodu.",
"cancelButton": "Anuluj",
"confirmButton": "Wyczyść dane"
}
},
"testConfigButton": "Testuj konfigurację",
"testingButton": "Testowanie..."
},
"autoApprove": {
"description": "Pozwól Roo na automatyczne wykonywanie operacji bez wymagania zatwierdzenia. Włącz te ustawienia tylko jeśli w pełni ufasz AI i rozumiesz związane z tym zagrożenia bezpieczeństwa.",

View file

@ -71,7 +71,9 @@
"description": "Esta ação não pode ser desfeita. Isso excluirá permanentemente os dados de índice da sua base de código.",
"cancelButton": "Cancelar",
"confirmButton": "Limpar Dados"
}
},
"testConfigButton": "Testar Configuração",
"testingButton": "Testando..."
},
"autoApprove": {
"description": "Permitir que o Roo realize operações automaticamente sem exigir aprovação. Ative essas configurações apenas se confiar totalmente na IA e compreender os riscos de segurança associados.",

View file

@ -71,7 +71,9 @@
"description": "Это действие нельзя отменить. Оно навсегда удалит данные индекса вашей кодовой базы.",
"cancelButton": "Отмена",
"confirmButton": "Очистить данные"
}
},
"testConfigButton": "Тестировать конфигурацию",
"testingButton": "Тестирование..."
},
"autoApprove": {
"description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.",

View file

@ -71,7 +71,9 @@
"description": "Bu işlem geri alınamaz. Bu, kod tabanı indeks verilerinizi kalıcı olarak silecektir.",
"cancelButton": "İptal",
"confirmButton": "Verileri Temizle"
}
},
"testConfigButton": "Yapılandırmayı Test Et",
"testingButton": "Test ediliyor..."
},
"autoApprove": {
"description": "Roo'nun onay gerektirmeden otomatik olarak işlemler gerçekleştirmesine izin verin. Bu ayarları yalnızca yapay zekaya tamamen güveniyorsanız ve ilgili güvenlik risklerini anlıyorsanız etkinleştirin.",

View file

@ -71,7 +71,9 @@
"description": "Hành động này không thể hoàn tác. Điều này sẽ xóa vĩnh viễn dữ liệu chỉ mục mã nguồn của bạn.",
"cancelButton": "Hủy",
"confirmButton": "Xóa dữ liệu"
}
},
"testConfigButton": "Kiểm tra cấu hình",
"testingButton": "Đang kiểm tra..."
},
"autoApprove": {
"description": "Cho phép Roo tự động thực hiện các hoạt động mà không cần phê duyệt. Chỉ bật những cài đặt này nếu bạn hoàn toàn tin tưởng AI và hiểu rõ các rủi ro bảo mật liên quan.",

View file

@ -71,7 +71,9 @@
"description": "此操作无法撤消。这将永久删除您的代码库索引数据。",
"cancelButton": "取消",
"confirmButton": "清除数据"
}
},
"testConfigButton": "测试配置",
"testingButton": "测试中..."
},
"autoApprove": {
"description": "允许 Roo 自动执行操作而无需批准。只有在您完全信任 AI 并了解相关安全风险的情况下才启用这些设置。",

View file

@ -71,7 +71,9 @@
"description": "此操作無法復原。這將永久刪除您的程式碼庫索引資料。",
"cancelButton": "取消",
"confirmButton": "清除資料"
}
},
"testConfigButton": "測試配置",
"testingButton": "測試中..."
},
"autoApprove": {
"description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。",