diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index 61009ba3011..43fcfddfd00 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -50,6 +50,9 @@ export const codebaseIndexConfigSchema = z.object({ codebaseIndexBedrockProfile: z.string().optional(), // OpenRouter specific fields codebaseIndexOpenRouterSpecificProvider: z.string().optional(), + // Ollama specific timeout fields + codebaseIndexOllamaEmbeddingTimeoutMs: z.number().int().optional(), + codebaseIndexOllamaValidationTimeoutMs: z.number().int().optional(), }) export type CodebaseIndexConfig = z.infer diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index d713a47d6b4..499430ba2d7 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -265,6 +265,9 @@ const ollamaSchema = baseProviderSettingsSchema.extend({ ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), ollamaApiKey: z.string().optional(), + ollamaEmbeddingTimeoutMs: z.number().int().optional(), + // Negative values disable the validation timeout. If set to a positive integer, it specifies the timeout in milliseconds. + ollamaValidationTimeoutMs: z.number().int().optional(), ollamaNumCtx: z.number().int().min(128).optional(), }) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 3e054ce7d25..c064114b90a 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2074,6 +2074,8 @@ export class ClineProvider codebaseIndexEmbedderBaseUrl: codebaseIndexConfig?.codebaseIndexEmbedderBaseUrl ?? "", codebaseIndexEmbedderModelId: codebaseIndexConfig?.codebaseIndexEmbedderModelId ?? "", codebaseIndexEmbedderModelDimension: codebaseIndexConfig?.codebaseIndexEmbedderModelDimension ?? 1536, + codebaseIndexOllamaEmbeddingTimeoutMs: codebaseIndexConfig?.codebaseIndexOllamaEmbeddingTimeoutMs, + codebaseIndexOllamaValidationTimeoutMs: codebaseIndexConfig?.codebaseIndexOllamaValidationTimeoutMs, codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig?.codebaseIndexOpenAiCompatibleBaseUrl, codebaseIndexSearchMaxResults: codebaseIndexConfig?.codebaseIndexSearchMaxResults, codebaseIndexSearchMinScore: codebaseIndexConfig?.codebaseIndexSearchMinScore, @@ -2305,6 +2307,10 @@ export class ClineProvider codebaseIndexEmbedderModelId: stateValues.codebaseIndexConfig?.codebaseIndexEmbedderModelId ?? "", codebaseIndexEmbedderModelDimension: stateValues.codebaseIndexConfig?.codebaseIndexEmbedderModelDimension, + codebaseIndexOllamaEmbeddingTimeoutMs: + stateValues.codebaseIndexConfig?.codebaseIndexOllamaEmbeddingTimeoutMs, + codebaseIndexOllamaValidationTimeoutMs: + stateValues.codebaseIndexConfig?.codebaseIndexOllamaValidationTimeoutMs, codebaseIndexOpenAiCompatibleBaseUrl: stateValues.codebaseIndexConfig?.codebaseIndexOpenAiCompatibleBaseUrl, codebaseIndexSearchMaxResults: stateValues.codebaseIndexConfig?.codebaseIndexSearchMaxResults, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index f9bc7a85cda..731f2e0a39f 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2363,6 +2363,8 @@ export const webviewMessageHandler = async ( codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl, codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId, codebaseIndexEmbedderModelDimension: settings.codebaseIndexEmbedderModelDimension, // Generic dimension + codebaseIndexOllamaEmbeddingTimeoutMs: settings.codebaseIndexOllamaEmbeddingTimeoutMs, + codebaseIndexOllamaValidationTimeoutMs: settings.codebaseIndexOllamaValidationTimeoutMs, codebaseIndexOpenAiCompatibleBaseUrl: settings.codebaseIndexOpenAiCompatibleBaseUrl, codebaseIndexBedrockRegion: settings.codebaseIndexBedrockRegion, codebaseIndexBedrockProfile: settings.codebaseIndexBedrockProfile, diff --git a/src/services/code-index/__tests__/config-manager.spec.ts b/src/services/code-index/__tests__/config-manager.spec.ts index 27815c0bef0..cf011b9a408 100644 --- a/src/services/code-index/__tests__/config-manager.spec.ts +++ b/src/services/code-index/__tests__/config-manager.spec.ts @@ -103,7 +103,11 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai", modelId: undefined, openAiOptions: { openAiNativeApiKey: "" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, bedrockOptions: { region: "us-east-1", profile: undefined }, qdrantUrl: "http://localhost:6333", qdrantApiKey: "", @@ -135,7 +139,11 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai", modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: "test-openai-key" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, qdrantUrl: "http://qdrant.local", qdrantApiKey: "test-qdrant-key", searchMinScore: 0.4, @@ -168,7 +176,11 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai-compatible", modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: "" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", @@ -206,7 +218,11 @@ describe("CodeIndexConfigManager", () => { modelId: "custom-model", modelDimension: 1024, openAiOptions: { openAiNativeApiKey: "" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", @@ -243,7 +259,11 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai-compatible", modelId: "custom-model", openAiOptions: { openAiNativeApiKey: "" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", @@ -282,7 +302,11 @@ describe("CodeIndexConfigManager", () => { modelId: "custom-model", modelDimension: undefined, // Invalid dimension is converted to undefined openAiOptions: { openAiNativeApiKey: "" }, - ollamaOptions: { ollamaBaseUrl: "" }, + ollamaOptions: { + ollamaBaseUrl: "", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", @@ -1292,7 +1316,11 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai", modelId: "text-embedding-3-large", openAiOptions: { openAiNativeApiKey: "test-openai-key" }, - ollamaOptions: { ollamaBaseUrl: undefined }, + ollamaOptions: { + ollamaBaseUrl: undefined, + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, + }, geminiOptions: undefined, openAiCompatibleOptions: undefined, qdrantUrl: "http://qdrant.local", diff --git a/src/services/code-index/__tests__/service-factory.spec.ts b/src/services/code-index/__tests__/service-factory.spec.ts index 1d8f7ba4786..ed154def112 100644 --- a/src/services/code-index/__tests__/service-factory.spec.ts +++ b/src/services/code-index/__tests__/service-factory.spec.ts @@ -87,6 +87,8 @@ describe("CodeIndexServiceFactory", () => { modelId: testModelId, ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, }, } mockConfigManager.getConfig.mockReturnValue(testConfig as any) @@ -129,6 +131,8 @@ describe("CodeIndexServiceFactory", () => { modelId: undefined, ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, }, } mockConfigManager.getConfig.mockReturnValue(testConfig as any) @@ -165,6 +169,8 @@ describe("CodeIndexServiceFactory", () => { modelId: "nomic-embed-text:latest", ollamaOptions: { ollamaBaseUrl: undefined, + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, }, } mockConfigManager.getConfig.mockReturnValue(testConfig as any) @@ -726,6 +732,8 @@ describe("CodeIndexServiceFactory", () => { modelId: "nomic-embed-text", ollamaOptions: { ollamaBaseUrl: "http://localhost:11434", + ollamaEmbeddingTimeoutMs: undefined, + ollamaValidationTimeoutMs: undefined, }, } mockConfigManager.getConfig.mockReturnValue(testConfig as any) diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index e7f239e621f..fac3a1672d3 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -55,6 +55,8 @@ export class CodeIndexConfigManager { codebaseIndexSearchMaxResults: undefined, codebaseIndexBedrockRegion: "us-east-1", codebaseIndexBedrockProfile: "", + codebaseIndexOllamaEmbeddingTimeoutMs: undefined, + codebaseIndexOllamaValidationTimeoutMs: undefined, } const { @@ -128,6 +130,8 @@ export class CodeIndexConfigManager { this.ollamaOptions = { ollamaBaseUrl: codebaseIndexEmbedderBaseUrl, + ollamaEmbeddingTimeoutMs: codebaseIndexConfig.codebaseIndexOllamaEmbeddingTimeoutMs, + ollamaValidationTimeoutMs: codebaseIndexConfig.codebaseIndexOllamaValidationTimeoutMs, } this.openAiCompatibleOptions = @@ -183,6 +187,8 @@ export class CodeIndexConfigManager { modelDimension: this.modelDimension, openAiKey: this.openAiOptions?.openAiNativeApiKey ?? "", ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl ?? "", + ollamaEmbeddingTimeoutMs: this.ollamaOptions?.ollamaEmbeddingTimeoutMs, + ollamaValidationTimeoutMs: this.ollamaOptions?.ollamaValidationTimeoutMs, openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "", openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "", geminiApiKey: this.geminiOptions?.apiKey ?? "", @@ -301,6 +307,8 @@ export class CodeIndexConfigManager { const prevProvider = prev?.embedderProvider ?? "openai" const prevOpenAiKey = prev?.openAiKey ?? "" const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? "" + const prevOllamaEmbeddingTimeoutMs = prev?.ollamaEmbeddingTimeoutMs + const prevOllamaValidationTimeoutMs = prev?.ollamaValidationTimeoutMs const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? "" const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? "" const prevModelDimension = prev?.modelDimension @@ -343,6 +351,8 @@ export class CodeIndexConfigManager { // Authentication changes (API keys) const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? "" const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? "" + const currentOllamaEmbeddingTimeoutMs = this.ollamaOptions?.ollamaEmbeddingTimeoutMs + const currentOllamaValidationTimeoutMs = this.ollamaOptions?.ollamaValidationTimeoutMs const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? "" const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? "" const currentModelDimension = this.modelDimension @@ -363,6 +373,12 @@ export class CodeIndexConfigManager { if (prevOllamaBaseUrl !== currentOllamaBaseUrl) { return true } + if ( + prevOllamaEmbeddingTimeoutMs !== currentOllamaEmbeddingTimeoutMs || + prevOllamaValidationTimeoutMs !== currentOllamaValidationTimeoutMs + ) { + return true + } if ( prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl || diff --git a/src/services/code-index/embedders/__tests__/ollama.spec.ts b/src/services/code-index/embedders/__tests__/ollama.spec.ts index 650140beacf..f8332490a31 100644 --- a/src/services/code-index/embedders/__tests__/ollama.spec.ts +++ b/src/services/code-index/embedders/__tests__/ollama.spec.ts @@ -166,6 +166,52 @@ describe("CodeIndexOllamaEmbedder", () => { }) }) + describe("timeout configuration", () => { + it("should use custom embedding timeout when provided", () => { + const embedderWithCustomTimeout = new CodeIndexOllamaEmbedder({ + ollamaBaseUrl: "http://localhost:11434", + ollamaModelId: "nomic-embed-text", + ollamaEmbeddingTimeoutMs: 120000, // 2 minutes + }) + + // The timeout should be set to the custom value + expect(embedderWithCustomTimeout).toBeDefined() + }) + + it("should use custom validation timeout when provided", () => { + const embedderWithCustomTimeout = new CodeIndexOllamaEmbedder({ + ollamaBaseUrl: "http://localhost:11434", + ollamaModelId: "nomic-embed-text", + ollamaValidationTimeoutMs: 60000, // 1 minute + }) + + // The timeout should be set to the custom value + expect(embedderWithCustomTimeout).toBeDefined() + }) + + it("should use default timeouts when not provided", () => { + const embedderWithDefaults = new CodeIndexOllamaEmbedder({ + ollamaBaseUrl: "http://localhost:11434", + ollamaModelId: "nomic-embed-text", + }) + + // Should use default timeouts + expect(embedderWithDefaults).toBeDefined() + }) + + it("should handle -1 timeout to disable timeout", () => { + const embedderWithNoTimeout = new CodeIndexOllamaEmbedder({ + ollamaBaseUrl: "http://localhost:11434", + ollamaModelId: "nomic-embed-text", + ollamaEmbeddingTimeoutMs: -1, + ollamaValidationTimeoutMs: -1, + }) + + // Should handle -1 as no timeout + expect(embedderWithNoTimeout).toBeDefined() + }) + }) + describe("validateConfiguration", () => { it("should validate successfully when service is available and model exists", async () => { // Mock successful /api/tags call diff --git a/src/services/code-index/embedders/ollama.ts b/src/services/code-index/embedders/ollama.ts index 9688a15ff04..b62a2383002 100644 --- a/src/services/code-index/embedders/ollama.ts +++ b/src/services/code-index/embedders/ollama.ts @@ -10,23 +10,30 @@ import { TelemetryEventName } from "@roo-code/types" // Timeout constants for Ollama API requests const OLLAMA_EMBEDDING_TIMEOUT_MS = 60000 // 60 seconds for embedding requests const OLLAMA_VALIDATION_TIMEOUT_MS = 30000 // 30 seconds for validation requests +const OLLAMA_BASE_URL = "http://localhost:11434" +const OLLAMA_DEFAULT_MODEL_ID = "nomic-embed-text:latest" /** * Implements the IEmbedder interface using a local Ollama instance. */ export class CodeIndexOllamaEmbedder implements IEmbedder { private readonly baseUrl: string - private readonly defaultModelId: string + private readonly modelId: string + private readonly embeddingTimeoutMs: number + private readonly validationTimeoutMs: number constructor(options: ApiHandlerOptions) { // Ensure ollamaBaseUrl and ollamaModelId exist on ApiHandlerOptions or add defaults - let baseUrl = options.ollamaBaseUrl || "http://localhost:11434" + let baseUrl = options.ollamaBaseUrl || OLLAMA_BASE_URL // Normalize the baseUrl by removing all trailing slashes baseUrl = baseUrl.replace(/\/+$/, "") this.baseUrl = baseUrl - this.defaultModelId = options.ollamaModelId || "nomic-embed-text:latest" + this.modelId = options.ollamaModelId || OLLAMA_DEFAULT_MODEL_ID + + this.embeddingTimeoutMs = options.ollamaEmbeddingTimeoutMs ?? OLLAMA_EMBEDDING_TIMEOUT_MS + this.validationTimeoutMs = options.ollamaValidationTimeoutMs ?? OLLAMA_VALIDATION_TIMEOUT_MS } /** @@ -36,7 +43,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { * @returns A promise that resolves to an EmbeddingResponse containing the embeddings and usage data. */ async createEmbeddings(texts: string[], model?: string): Promise { - const modelToUse = model || this.defaultModelId + const modelToUse = model || this.modelId const url = `${this.baseUrl}/api/embed` // Endpoint as specified // Apply model-specific query prefix if required @@ -70,7 +77,12 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { // Add timeout to prevent indefinite hanging const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS) + let embeddingTimeoutId = undefined + // If embeddingTimeoutMs <= 0, the timeout is disabled and requests may hang indefinitely. + // To enable the timeout, set embeddingTimeoutMs to a positive integer (milliseconds). + if (this.embeddingTimeoutMs > 0) { + embeddingTimeoutId = setTimeout(() => controller.abort(), this.embeddingTimeoutMs) + } const response = await fetch(url, { method: "POST", @@ -83,7 +95,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { }), signal: controller.signal, }) - clearTimeout(timeoutId) + clearTimeout(embeddingTimeoutId) if (!response.ok) { let errorBody = t("embeddings:ollama.couldNotReadErrorBody") @@ -149,7 +161,10 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { // Add timeout to prevent indefinite hanging const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), OLLAMA_VALIDATION_TIMEOUT_MS) + let validationTimeoutId = undefined + if (this.validationTimeoutMs > 0) { + validationTimeoutId = setTimeout(() => controller.abort(), this.validationTimeoutMs) + } const modelsResponse = await fetch(modelsUrl, { method: "GET", @@ -158,7 +173,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { }, signal: controller.signal, }) - clearTimeout(timeoutId) + clearTimeout(validationTimeoutId) if (!modelsResponse.ok) { if (modelsResponse.status === 404) { @@ -184,9 +199,9 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { const modelExists = models.some((m: any) => { const modelName = m.name || "" return ( - modelName === this.defaultModelId || - modelName === `${this.defaultModelId}:latest` || - modelName === this.defaultModelId.replace(":latest", "") + modelName === this.modelId || + modelName === `${this.modelId}:latest` || + modelName === this.modelId.replace(":latest", "") ) }) @@ -195,7 +210,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { return { valid: false, error: t("embeddings:ollama.modelNotFound", { - modelId: this.defaultModelId, + modelId: this.modelId, availableModels, }), } @@ -206,7 +221,12 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { // Add timeout for test request too const testController = new AbortController() - const testTimeoutId = setTimeout(() => testController.abort(), OLLAMA_VALIDATION_TIMEOUT_MS) + let testTimeoutId = undefined + // If validationTimeoutMs is <= 0, the timeout is disabled (no abort will be triggered). + // Set validationTimeoutMs to a positive integer (milliseconds) to enable the timeout. + if (this.validationTimeoutMs > 0) { + testTimeoutId = setTimeout(() => testController.abort(), this.validationTimeoutMs) + } const testResponse = await fetch(testUrl, { method: "POST", @@ -214,7 +234,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { "Content-Type": "application/json", }, body: JSON.stringify({ - model: this.defaultModelId, + model: this.modelId, input: ["test"], }), signal: testController.signal, @@ -224,7 +244,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { if (!testResponse.ok) { return { valid: false, - error: t("embeddings:ollama.modelNotEmbeddingCapable", { modelId: this.defaultModelId }), + error: t("embeddings:ollama.modelNotEmbeddingCapable", { modelId: this.modelId }), } } diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index f52f98aaa0d..4fbf50cfaaf 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -34,6 +34,8 @@ export type PreviousConfigSnapshot = { modelDimension?: number // Generic dimension property openAiKey?: string ollamaBaseUrl?: string + ollamaEmbeddingTimeoutMs?: number + ollamaValidationTimeoutMs?: number openAiCompatibleBaseUrl?: string openAiCompatibleApiKey?: string geminiApiKey?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index b22e7ab3f64..b0b5096195b 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -250,6 +250,9 @@ export interface WebviewMessage { codebaseIndexSearchMinScore?: number codebaseIndexOpenRouterSpecificProvider?: string // OpenRouter provider routing + codebaseIndexOllamaEmbeddingTimeoutMs?: number + codebaseIndexOllamaValidationTimeoutMs?: number + // Secret settings codeIndexOpenAiKey?: string codeIndexQdrantApiKey?: string diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 368f0395eaf..04c0dd8f109 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -74,6 +74,10 @@ interface LocalCodeIndexSettings { codebaseIndexBedrockRegion?: string codebaseIndexBedrockProfile?: string + // Ollama-specific settings + codebaseIndexOllamaEmbeddingTimeoutMs?: number + codebaseIndexOllamaValidationTimeoutMs?: number + // Secret settings (start empty, will be loaded separately) codeIndexOpenAiKey?: string codeIndexQdrantApiKey?: string @@ -113,6 +117,8 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => { .min(1, t("settings:codeIndex.validation.ollamaBaseUrlRequired")) .url(t("settings:codeIndex.validation.invalidOllamaUrl")), codebaseIndexEmbedderModelId: z.string().min(1, t("settings:codeIndex.validation.modelIdRequired")), + codebaseIndexOllamaEmbeddingTimeoutMs: z.number().optional(), + codebaseIndexOllamaValidationTimeoutMs: z.number().optional(), codebaseIndexEmbedderModelDimension: z .number() .min(1, t("settings:codeIndex.validation.modelDimensionRequired")) @@ -219,6 +225,8 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexSearchMinScore: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE, codebaseIndexBedrockRegion: "", codebaseIndexBedrockProfile: "", + codebaseIndexOllamaEmbeddingTimeoutMs: undefined, + codebaseIndexOllamaValidationTimeoutMs: undefined, codeIndexOpenAiKey: "", codeIndexQdrantApiKey: "", codebaseIndexOpenAiCompatibleBaseUrl: "", @@ -258,6 +266,10 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexConfig.codebaseIndexSearchMinScore ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE, codebaseIndexBedrockRegion: codebaseIndexConfig.codebaseIndexBedrockRegion || "", codebaseIndexBedrockProfile: codebaseIndexConfig.codebaseIndexBedrockProfile || "", + codebaseIndexOllamaEmbeddingTimeoutMs: + codebaseIndexConfig.codebaseIndexOllamaEmbeddingTimeoutMs || undefined, + codebaseIndexOllamaValidationTimeoutMs: + codebaseIndexConfig.codebaseIndexOllamaValidationTimeoutMs || undefined, codeIndexOpenAiKey: "", codeIndexQdrantApiKey: "", codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "", @@ -888,6 +900,66 @@ export const CodeIndexPopover: React.FC = ({ )} +
+ + { + const value = e.target.value + ? parseInt(e.target.value, 10) || undefined + : undefined + updateSetting("codebaseIndexOllamaEmbeddingTimeoutMs", value) + }} + placeholder={t( + "settings:codeIndex.ollamaEmbeddingTimeoutMsPlaceholder", + )} + className={cn("w-full", { + "border-red-500": + formErrors.codebaseIndexOllamaEmbeddingTimeoutMs, + })} + /> + {formErrors.codebaseIndexOllamaEmbeddingTimeoutMs && ( +

+ {formErrors.codebaseIndexOllamaEmbeddingTimeoutMs} +

+ )} +
+ +
+ + { + const value = e.target.value + ? parseInt(e.target.value, 10) || undefined + : undefined + updateSetting("codebaseIndexOllamaValidationTimeoutMs", value) + }} + placeholder={t( + "settings:codeIndex.ollamaValidationTimeoutMsPlaceholder", + )} + className={cn("w-full", { + "border-red-500": + formErrors.codebaseIndexOllamaValidationTimeoutMs, + })} + /> + {formErrors.codebaseIndexOllamaValidationTimeoutMs && ( +

+ {formErrors.codebaseIndexOllamaValidationTimeoutMs} +

+ )} +
+