diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 09c472cc0d..3acff26ba5 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1327,6 +1327,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We codebaseIndexEmbedderProvider: "openai", codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", + codebaseIndexEmbedderDimension: null, } await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig) diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 904eba8530..481ef8a5c6 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -86,6 +86,7 @@ type GlobalSettings = { codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined @@ -865,6 +866,7 @@ type IpcMessage = codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined @@ -1377,6 +1379,7 @@ type TaskCommand = codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 6f4989df62..54a931bca4 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -86,6 +86,7 @@ type GlobalSettings = { codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined @@ -879,6 +880,7 @@ type IpcMessage = codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined @@ -1393,6 +1395,7 @@ type TaskCommand = codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined codebaseIndexEmbedderBaseUrl?: string | undefined codebaseIndexEmbedderModelId?: string | undefined + codebaseIndexEmbedderDimension?: (number | null) | undefined } | undefined alwaysAllowWrite?: boolean | undefined diff --git a/src/package.json b/src/package.json index c98d7f8537..616da339d6 100644 --- a/src/package.json +++ b/src/package.json @@ -313,6 +313,57 @@ "type": "string", "default": "", "description": "%settings.customStoragePath.description%" + }, + "roo-cline.codebaseIndexEnabled": { + "type": "boolean", + "default": false, + "description": "Enable codebase indexing for semantic search.", + "scope": "resource" + }, + "roo-cline.codebaseIndexEmbedderProvider": { + "type": "string", + "enum": [ + "openai", + "ollama" + ], + "default": "openai", + "description": "Select the embedding provider (OpenAI or Ollama).", + "scope": "resource" + }, + "roo-cline.codebaseIndexEmbedderBaseUrl": { + "type": "string", + "default": "", + "description": "Optional: Base URL for the selected embedding provider API (e.g., for proxies, Azure OpenAI, or self-hosted Ollama).", + "scope": "resource" + }, + "roo-cline.codebaseIndexEmbedderModelId": { + "type": "string", + "default": "", + "description": "Optional: Specify a custom embedding model ID. Leave empty to use the provider's default (e.g., text-embedding-3-small for OpenAI).", + "scope": "resource" + }, + "roo-cline.codebaseIndexEmbedderDimension": { + "type": [ + "number", + "null" + ], + "default": null, + "description": "Optional: Specify the embedding dimension for your custom model ID if it's not automatically recognized. Required only if using an unknown model ID.", + "scope": "resource" + }, + "roo-cline.codebaseIndexQdrantUrl": { + "type": "string", + "default": "http://localhost:6333", + "description": "URL for the Qdrant vector database instance.", + "scope": "resource" + }, + "roo-cline.codebaseIndexSearchMinScore": { + "type": "number", + "default": 0.4, + "minimum": 0, + "maximum": 1, + "description": "Minimum similarity score (0-1) for codebase search results.", + "scope": "resource" } } } diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 4fb893ae1f..5b9798ad1a 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -232,6 +232,7 @@ export const codebaseIndexConfigSchema = z.object({ codebaseIndexEmbedderProvider: z.enum(["openai", "ollama"]).optional(), codebaseIndexEmbedderBaseUrl: z.string().optional(), codebaseIndexEmbedderModelId: z.string().optional(), + codebaseIndexEmbedderDimension: z.number().nullish(), }) export type CodebaseIndexConfig = z.infer @@ -244,7 +245,7 @@ export const codebaseIndexModelsSchema = z.object({ export type CodebaseIndexModels = z.infer export const codebaseIndexProviderSchema = z.object({ - codeIndexOpenAiKey: z.string().optional(), + codeIndexOpenAiKey: z.string().optional(), codeIndexQdrantApiKey: z.string().optional(), }) @@ -661,7 +662,7 @@ export const providerSettingsSchema = z.object({ ...groqSchema.shape, ...chutesSchema.shape, ...litellmSchema.shape, - ...codebaseIndexProviderSchema.shape + ...codebaseIndexProviderSchema.shape, }) export type ProviderSettings = z.infer diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 0b0bf2e7e8..96a4729eb9 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -17,6 +17,7 @@ export class CodeIndexConfigManager { private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string private searchMinScore?: number + private dimension?: number // Added for custom model dimension constructor(private readonly contextProxy: ContextProxy) {} @@ -35,6 +36,7 @@ export class CodeIndexConfigManager { qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number + dimension?: number // Add dimension to return type } requiresRestart: boolean }> { @@ -47,6 +49,7 @@ export class CodeIndexConfigManager { ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl, qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, + dimension: this.dimension, // Added dimension to snapshot } let codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? { @@ -56,8 +59,10 @@ export class CodeIndexConfigManager { codebaseIndexEmbedderProvider: "openai", codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", + codebaseIndexEmbedderDimension: null, } + // Destructure known properties, access dimension defensively const { codebaseIndexEnabled, codebaseIndexQdrantUrl, @@ -65,6 +70,7 @@ export class CodeIndexConfigManager { codebaseIndexEmbedderBaseUrl, codebaseIndexEmbedderModelId, } = codebaseIndexConfig + const codebaseIndexEmbedderDimension = (codebaseIndexConfig as any)?.codebaseIndexEmbedderDimension const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? "" const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? "" @@ -72,15 +78,23 @@ export class CodeIndexConfigManager { this.isEnabled = codebaseIndexEnabled || false this.qdrantUrl = codebaseIndexQdrantUrl this.qdrantApiKey = qdrantApiKey ?? "" - this.openAiOptions = { openAiNativeApiKey: openAiKey } this.searchMinScore = SEARCH_MIN_SCORE this.embedderProvider = codebaseIndexEmbedderProvider === "ollama" ? "ollama" : "openai" + this.openAiOptions = { + openAiNativeApiKey: openAiKey, + openAiBaseUrl: this.embedderProvider === "openai" ? codebaseIndexEmbedderBaseUrl : undefined, + } this.modelId = codebaseIndexEmbedderModelId || undefined this.ollamaOptions = { ollamaBaseUrl: codebaseIndexEmbedderBaseUrl, } + // Parse and store dimension, ensuring it's a positive number or undefined + this.dimension = + typeof codebaseIndexEmbedderDimension === "number" && codebaseIndexEmbedderDimension > 0 + ? codebaseIndexEmbedderDimension + : undefined return { configSnapshot: previousConfigSnapshot, @@ -94,6 +108,7 @@ export class CodeIndexConfigManager { qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.searchMinScore, + dimension: this.dimension, }, requiresRestart: this._didConfigChangeRequireRestart(previousConfigSnapshot), } @@ -103,7 +118,6 @@ export class CodeIndexConfigManager { * Checks if the service is properly configured based on the embedder type. */ public isConfigured(): boolean { - if (this.embedderProvider === "openai") { const openAiKey = this.openAiOptions?.openAiNativeApiKey const qdrantUrl = this.qdrantUrl @@ -160,6 +174,9 @@ export class CodeIndexConfigManager { if (prev.qdrantUrl !== this.qdrantUrl || prev.qdrantApiKey !== this.qdrantApiKey) { return true } + + // Check dimension change + if (prev.dimension !== this.dimension) return true } return false @@ -179,6 +196,7 @@ export class CodeIndexConfigManager { qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.searchMinScore, + dimension: this.dimension, } } diff --git a/src/services/code-index/embedders/ollama.ts b/src/services/code-index/embedders/ollama.ts index 8601f0c606..ccf13a1bd0 100644 --- a/src/services/code-index/embedders/ollama.ts +++ b/src/services/code-index/embedders/ollama.ts @@ -22,50 +22,55 @@ export class CodeIndexOllamaEmbedder implements IEmbedder { */ async createEmbeddings(texts: string[], model?: string): Promise { const modelToUse = model || this.defaultModelId - const url = `${this.baseUrl}/api/embed` // Endpoint as specified + const url = `${this.baseUrl}/api/embed` try { - // Note: Standard Ollama API uses 'prompt' for single text, not 'input' for array. - // Implementing based on user's specific request structure. - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ + // Ollama API processes one text at a time using 'prompt' field + const embeddings: number[][] = [] + + for (const text of texts) { + const requestBody = { model: modelToUse, - input: texts, // Using 'input' as requested - }), - }) + prompt: text, + } + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }) - if (!response.ok) { - let errorBody = "Could not read error body" - try { - errorBody = await response.text() - } catch (e) { - // Ignore error reading body + if (!response.ok) { + let errorBody = "Could not read error body" + try { + errorBody = await response.text() + } catch (e) { + // Ignore error reading body + } + throw new Error( + `Ollama API request failed with status ${response.status} ${response.statusText}: ${errorBody}`, + ) } - throw new Error( - `Ollama API request failed with status ${response.status} ${response.statusText}: ${errorBody}`, - ) - } - const data = await response.json() + const data = await response.json() + + // Extract embedding using 'embedding' key (singular) + const embedding = data.embedding + if (!embedding || !Array.isArray(embedding)) { + throw new Error( + 'Invalid response structure from Ollama API: "embedding" array not found or not an array.', + ) + } - // Extract embeddings using 'embeddings' key as requested - const embeddings = data.embeddings - if (!embeddings || !Array.isArray(embeddings)) { - throw new Error( - 'Invalid response structure from Ollama API: "embeddings" array not found or not an array.', - ) + embeddings.push(embedding) } return { embeddings: embeddings, } } catch (error: any) { - // Log the original error for debugging purposes - console.error("Ollama embedding failed:", error) // Re-throw a more specific error for the caller throw new Error(`Ollama embedding failed: ${error.message}`) } diff --git a/src/services/code-index/embedders/openai.ts b/src/services/code-index/embedders/openai.ts index 907c9e1283..e7ccf683e3 100644 --- a/src/services/code-index/embedders/openai.ts +++ b/src/services/code-index/embedders/openai.ts @@ -15,15 +15,21 @@ import { export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder { private embeddingsClient: OpenAI private readonly defaultModelId: string + private readonly configuredDimension?: number /** * Creates a new OpenAI embedder - * @param options API handler options + * @param options API handler options including optional model ID and dimension */ - constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string }) { + constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string; embeddingDimension?: number }) { super(options) const apiKey = this.options.openAiNativeApiKey ?? "not-provided" - this.embeddingsClient = new OpenAI({ apiKey }) + const baseURL = this.options.openAiBaseUrl + this.configuredDimension = options.embeddingDimension + this.embeddingsClient = new OpenAI({ + apiKey, + ...(baseURL && { baseURL }), + }) this.defaultModelId = options.openAiEmbeddingModelId || "text-embedding-3-small" } @@ -98,10 +104,15 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder { ): Promise<{ embeddings: number[][]; usage: { promptTokens: number; totalTokens: number } }> { for (let attempts = 0; attempts < MAX_RETRIES; attempts++) { try { - const response = await this.embeddingsClient.embeddings.create({ + const params: OpenAI.Embeddings.EmbeddingCreateParams = { input: batchTexts, model: model, - }) + } + if (this.configuredDimension) { + params.dimensions = this.configuredDimension + } + + const response = await this.embeddingsClient.embeddings.create(params) return { embeddings: response.data.map((item) => item.embedding), diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index 2d07911783..e762ecf702 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -14,6 +14,7 @@ export interface CodeIndexConfig { qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number + dimension?: number } /** @@ -28,4 +29,5 @@ export type PreviousConfigSnapshot = { ollamaBaseUrl?: string qdrantUrl?: string qdrantApiKey?: string + dimension?: number } diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 84304a348c..1c1ba2d03b 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -31,12 +31,20 @@ export class CodeIndexServiceFactory { if (!config.openAiOptions?.openAiNativeApiKey) { throw new Error("OpenAI configuration missing for embedder creation") } - return new OpenAiEmbedder(config.openAiOptions) // Reverted temporarily + return new OpenAiEmbedder({ + ...config.openAiOptions, + openAiEmbeddingModelId: config.modelId, + embeddingDimension: config.dimension, + }) } else if (provider === "ollama") { if (!config.ollamaOptions?.ollamaBaseUrl) { throw new Error("Ollama configuration missing for embedder creation") } - return new CodeIndexOllamaEmbedder(config.ollamaOptions) // Reverted temporarily + const ollamaOptions = { + ...config.ollamaOptions, + ollamaModelId: config.modelId, + } + return new CodeIndexOllamaEmbedder(ollamaOptions) } throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`) @@ -49,19 +57,23 @@ export class CodeIndexServiceFactory { const config = this.configManager.getConfig() const provider = config.embedderProvider as EmbedderProvider - const defaultModel = getDefaultModelId(provider) - // Determine the modelId based on the provider and config, using apiModelId - const modelId = - provider === "openai" - ? (config.openAiOptions?.apiModelId ?? defaultModel) - : (config.ollamaOptions?.apiModelId ?? defaultModel) + const defaultModelId = getDefaultModelId(provider) + // Use the actual configured modelId, falling back to the provider default + const modelIdToUse = config.modelId || defaultModelId - const vectorSize = getModelDimension(provider, modelId) + // Try to get dimension from known profiles + let vectorSize = getModelDimension(provider, modelIdToUse) + // If not found in profiles, check if a dimension was manually configured if (vectorSize === undefined) { - throw new Error( - `Could not determine vector dimension for model '${modelId}'. Check model profiles or config.`, - ) + if (config.dimension !== undefined && config.dimension > 0) { + vectorSize = config.dimension + } else { + // Dimension still unknown, throw error + throw new Error( + `Could not determine vector dimension for model '${modelIdToUse}'. Model not found in built-in profiles, and no valid dimension was manually configured in settings (roo-cline.codebaseIndexEmbedderDimension).`, + ) + } } if (!config.qdrantUrl) { diff --git a/webview-ui/eslint.config.mjs b/webview-ui/eslint.config.mjs index db76f49211..bc224652b9 100644 --- a/webview-ui/eslint.config.mjs +++ b/webview-ui/eslint.config.mjs @@ -15,6 +15,14 @@ export default [ caughtErrorsIgnorePattern: "^_", }, ], + "@typescript-eslint/no-unused-expressions": [ + "error", + { + allowShortCircuit: true, + allowTernary: true, + allowTaggedTemplates: true, + }, + ], "@typescript-eslint/no-explicit-any": "off", "react/prop-types": "off", "react/display-name": "off", diff --git a/webview-ui/package.json b/webview-ui/package.json index cb9e5c91e3..0352e04454 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "lint": "eslint src --ext=ts,tsx --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc", "test": "jest -w=40%", "format": "prettier --write src", diff --git a/webview-ui/src/components/settings/CodeIndexSettings.tsx b/webview-ui/src/components/settings/CodeIndexSettings.tsx index 8aaaa888bb..ee02f08504 100644 --- a/webview-ui/src/components/settings/CodeIndexSettings.tsx +++ b/webview-ui/src/components/settings/CodeIndexSettings.tsx @@ -210,15 +210,12 @@ export const CodeIndexSettings: React.FC = ({ value={codebaseIndexConfig?.codebaseIndexEmbedderProvider || "openai"} onValueChange={(value) => { const newProvider = value as EmbedderProvider - const models = codebaseIndexModels?.[newProvider] - const modelIds = models ? Object.keys(models) : [] - const defaultModelId = modelIds.length > 0 ? modelIds[0] : "" // Use empty string if no models - + // Don't reset modelId when provider changes, allow user to manage it explicitly if (codebaseIndexConfig) { setCachedStateField("codebaseIndexConfig", { ...codebaseIndexConfig, codebaseIndexEmbedderProvider: newProvider, - codebaseIndexEmbedderModelId: defaultModelId, + // codebaseIndexEmbedderModelId: defaultModelId, // Removed this line }) } }}> @@ -233,48 +230,49 @@ export const CodeIndexSettings: React.FC = ({ + {/* OpenAI Specific Settings */} {codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai" && (
+ {/* OpenAI API Key */}
{t("settings:codeIndex.openaiKeyLabel")}
setApiConfigurationField("codeIndexOpenAiKey", e.target.value)} style={{ width: "100%" }}>
+ {/* OpenAI Base URL */} +
+
+ {t("settings:codeIndex.openaiUrlLabel", { + defaultValue: "OpenAI Base URL (Optional)", + })} +
+
+
+ + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexEmbedderBaseUrl: e.target.value, + }) + } + style={{ width: "100%" }}> +
)} -
-
{t("settings:codeIndex.modelLabel")}
-
-
-
- -
-
- + {/* Ollama Specific Settings */} {codebaseIndexConfig?.codebaseIndexEmbedderProvider === "ollama" && (
@@ -282,6 +280,11 @@ export const CodeIndexSettings: React.FC = ({
setCachedStateField("codebaseIndexConfig", { @@ -294,6 +297,87 @@ export const CodeIndexSettings: React.FC = ({
)} + {/* Model ID (Common to OpenAI/Ollama) */} +
+
{t("settings:codeIndex.modelLabel")}
+
+
+ + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexEmbedderModelId: e.target.value, + }) + } + style={{ width: "100%" }}> +

+ {t("settings:codeIndex.modelIdDescription", { + defaultValue: "Available models depend on your provider and configuration.", + })} + {currentProvider === "openai" && availableModelIds.length > 0 && ( + <> + {" "} + {t("settings:codeIndex.openaiDefaults", { defaultValue: "Defaults:" })}{" "} + {availableModelIds.slice(0, 3).join(", ")} + {availableModelIds.length > 3 ? "..." : ""} + + )} + {currentProvider === "ollama" && availableModelIds.length > 0 && ( + <> + {" "} + {t("settings:codeIndex.ollamaDefaults", { defaultValue: "Common:" })}{" "} + {availableModelIds.slice(0, 3).join(", ")} + {availableModelIds.length > 3 ? "..." : ""} + + )} +

+
+ + {/* Custom Model Dimension (Optional) */} +
+
+
+ {t("settings:codeIndex.dimensionLabel", { + defaultValue: "Custom Model Dimension (Optional)", + })} +
+
+
+ { + const rawValue = e.target.value + const parsedValue = parseInt(rawValue, 10) + const dimensionToSave = !isNaN(parsedValue) && parsedValue > 0 ? parsedValue : null + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexEmbedderDimension: dimensionToSave, + }) + }} + style={{ width: "100%" }}> +

+ {t("settings:codeIndex.dimensionDescription", { + defaultValue: + "Specify the embedding dimension only if using a custom model ID not listed in the defaults.", + })} +

+
+
+ + {/* Qdrant Settings */} +
{t("settings:codeIndex.qdrantUrlLabel")}
diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index d77e2b3081..f701c224b8 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -44,6 +44,17 @@ "modelLabel": "Model", "selectModelPlaceholder": "Select model", "ollamaUrlLabel": "Ollama URL:", + "ollamaUrlPlaceholder": "e.g., http://localhost:11434", + "modelIdPlaceholder": "Leave empty for default (e.g., text-embedding-3-small)", + "modelIdDescription": "Available models depend on your provider and configuration.", + "openaiDefaults": "Defaults:", + "ollamaDefaults": "Common:", + "dimensionLabel": "Custom Model Dimension (Optional)", + "dimensionPlaceholder": "e.g., 1536. Only needed if model is not recognized.", + "dimensionDescription": "Specify the embedding dimension only if using a custom model ID not listed in the defaults.", + "openaiKeyPlaceholder": "Enter your OpenAI API key", + "openaiUrlLabel": "OpenAI Base URL (Optional)", + "openaiUrlPlaceholder": "e.g., https://my-proxy.com/v1 or Azure endpoint", "qdrantUrlLabel": "Qdrant URL", "qdrantKeyLabel": "Qdrant Key:", "startIndexingButton": "Start Indexing",