mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
fix: prevent silent failures in code indexing with OpenAI-compatible endpoints (#4398)
This commit is contained in:
parent
b7d5a964c7
commit
4277572e83
29 changed files with 535 additions and 55 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "यह क्रिया पूर्ववत नहीं की जा सकती। यह आपके कोडबेस इंडेक्स डेटा को स्थायी रूप से हटा देगी।",
|
||||
"cancelButton": "रद्द करें",
|
||||
"confirmButton": "डेटा साफ़ करें"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "कॉन्फ़िगरेशन का परीक्षण करें",
|
||||
"testingButton": "परीक्षण हो रहा है..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "この操作は元に戻せません。コードベースのインデックスデータが完全に削除されます。",
|
||||
"cancelButton": "キャンセル",
|
||||
"confirmButton": "データをクリア"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "設定をテスト",
|
||||
"testingButton": "テスト中..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "이 작업은 취소할 수 없습니다. 코드베이스 인덱스 데이터가 영구적으로 삭제됩니다.",
|
||||
"cancelButton": "취소",
|
||||
"confirmButton": "데이터 지우기"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "구성 테스트",
|
||||
"testingButton": "테스트 중..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "Это действие нельзя отменить. Оно навсегда удалит данные индекса вашей кодовой базы.",
|
||||
"cancelButton": "Отмена",
|
||||
"confirmButton": "Очистить данные"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "Тестировать конфигурацию",
|
||||
"testingButton": "Тестирование..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "此操作无法撤消。这将永久删除您的代码库索引数据。",
|
||||
"cancelButton": "取消",
|
||||
"confirmButton": "清除数据"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "测试配置",
|
||||
"testingButton": "测试中..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "允许 Roo 自动执行操作而无需批准。只有在您完全信任 AI 并了解相关安全风险的情况下才启用这些设置。",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
"description": "此操作無法復原。這將永久刪除您的程式碼庫索引資料。",
|
||||
"cancelButton": "取消",
|
||||
"confirmButton": "清除資料"
|
||||
}
|
||||
},
|
||||
"testConfigButton": "測試配置",
|
||||
"testingButton": "測試中..."
|
||||
},
|
||||
"autoApprove": {
|
||||
"description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue