Browse Source

Fix code index secret persistence and improve settings UX (#5158)

* Fix code index secret persistence with async VSCode storage

- Add async secret methods to CodeIndexConfigManager
- Implement direct VSCode secret storage access bypassing ContextProxy cache
- Update loadConfiguration to use async secret loading
- Modify webview message handler to use new async secret storage
- Add public secret methods to CodeIndexManager
- Enhance debugging throughout secret flow

This fixes the issue where API keys were saved but not loaded immediately
into services due to ContextProxy cache synchronization issues.

* Fix code index secret persistence and test failures

- Add async secret handling to CodeIndexConfigManager with new methods:
  - getSecretAsync(), storeSecretAsync() for individual secrets
  - loadSecretsAsync(), storeSecretsAsync() for batch operations
- Update doesConfigChangeRequireRestart() to check OpenAI Compatible modelDimension changes
- Fix all failing tests by using setupSecretMocks() helper consistently
- Update manager.spec.ts to properly mock _recreateServices to avoid real service creation

This ensures API keys and other secrets are properly loaded from VSCode's async secret storage
and that configuration changes requiring service restart are correctly detected.

* feat: improve code index settings secret handling in UI

- Show placeholder dots (••••••••••••••••) in password fields when secrets are already set
- Only send modified secret fields to prevent overwriting existing secrets with empty values
- Track which fields have been modified by the user
- Add requestCodeIndexSecretStatus message handler to check if secrets exist
- Fix console.log to handle empty string keys without errors
- Ensure changing one setting doesn't clear other unmodified secrets

* refactor: disconnect code index from unified settings system

- Rename handleExternalSettingsChange to handleSettingsChange for clarity
- Remove handleSettingsChange call from ClineProvider (not related to code index)
- Remove codebaseIndexConfig from general settings save in SettingsView
- Delete unused codebaseIndexConfig message handler
- Remove codebaseIndexConfig from WebviewMessage type definition
- Code index settings are now fully independent with their own dedicated UI

* feat: separate code index enable/disable from indexing settings

- Move 'Enable codebase indexing' toggle to global settings in Experimental section
- Keep indexing-specific settings (API keys, URLs, models) in dedicated Code Index Settings component
- Add codebaseIndexEnabled handler to webview message handler
- Update translations with new settings title and disabled message
- Ensure code index service properly responds to enable/disable changes
- Maintain backward compatibility with existing codebaseIndexConfig structure

* refactor: remove ContextProxy.getVSCodeContext() and pass ExtensionContext directly

- Updated CodeIndexConfigManager to accept vscode.ExtensionContext in constructor
- Modified CodeIndexManager to pass context directly to CodeIndexConfigManager
- Updated webviewMessageHandler to use provider.context.secrets directly
- Removed getVSCodeContext() method from ContextProxy
- Updated all related tests to reflect these changes
- Fixed CodeIndexSettings webview tests after UI changes

* refactor: streamline secret handling by removing async methods and utilizing ContextProxy directly

* feat: translations and popover component

* refactor: simplify test mocks and improve checkbox handling in CodeIndexSettings tests

* refactor: remove debug logging from CodeIndexConfigManager, CodeIndexManager, CodeIndexServiceFactory, and QdrantVectorStore

* fix: merge missing translation keys from main after rebase

- Add advancedConfigLabel, searchMinScoreLabel, searchMinScoreDescription, searchMinScoreResetTooltip keys
- Update startIndexingButton and clearIndexDataButton labels to match main
- Preserve all CodeIndexPopover translations added in this PR

* Revert "fix: merge missing translation keys from main after rebase"

This reverts commit beb1de4924ac1475731fcd06d994ddb96eb1e5fd.

* fix: add missing translation keys from main branch after rebase

- Added codeIndex.advancedConfigLabel
- Added codeIndex.searchMinScoreLabel
- Added codeIndex.searchMinScoreDescription
- Added codeIndex.searchMinScoreResetTooltip

These keys exist on main but were missing from non-English locales after rebase.

* fix: remove clickIndicatorMessage, fix toggle message type, and clean up translation key inconsistencies

* refactor: streamline settings management in CodeIndexPopover and improve secret handling

* refactor: remove debug logging from configuration checks in CodeIndexConfigManager and webviewMessageHandler

* fix: translations

* refactor: remove CodeIndexSettings component and associated tests
Daniel 8 months ago
parent
commit
e508eaf3df
35 changed files with 1787 additions and 2056 deletions
  1. 17 0
      src/core/config/ContextProxy.ts
  2. 0 5
      src/core/webview/ClineProvider.ts
  3. 99 16
      src/core/webview/webviewMessageHandler.ts
  4. 122 94
      src/services/code-index/__tests__/config-manager.spec.ts
  5. 20 7
      src/services/code-index/__tests__/manager.spec.ts
  6. 56 40
      src/services/code-index/config-manager.ts
  7. 5 5
      src/services/code-index/manager.ts
  8. 3 1
      src/services/code-index/service-factory.ts
  9. 3 0
      src/shared/ExtensionMessage.ts
  10. 19 1
      src/shared/WebviewMessage.ts
  11. 636 0
      webview-ui/src/components/chat/CodeIndexPopover.tsx
  12. 19 29
      webview-ui/src/components/chat/IndexingStatusBadge.tsx
  13. 6 9
      webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx
  14. 0 619
      webview-ui/src/components/settings/CodeIndexSettings.tsx
  15. 28 20
      webview-ui/src/components/settings/ExperimentalSettings.tsx
  16. 4 5
      webview-ui/src/components/settings/SettingsView.tsx
  17. 0 975
      webview-ui/src/components/settings/__tests__/CodeIndexSettings.spec.tsx
  18. 42 13
      webview-ui/src/i18n/locales/ca/settings.json
  19. 42 14
      webview-ui/src/i18n/locales/de/settings.json
  20. 36 7
      webview-ui/src/i18n/locales/en/settings.json
  21. 42 13
      webview-ui/src/i18n/locales/es/settings.json
  22. 42 14
      webview-ui/src/i18n/locales/fr/settings.json
  23. 42 13
      webview-ui/src/i18n/locales/hi/settings.json
  24. 42 13
      webview-ui/src/i18n/locales/id/settings.json
  25. 42 13
      webview-ui/src/i18n/locales/it/settings.json
  26. 42 13
      webview-ui/src/i18n/locales/ja/settings.json
  27. 42 13
      webview-ui/src/i18n/locales/ko/settings.json
  28. 42 13
      webview-ui/src/i18n/locales/nl/settings.json
  29. 42 13
      webview-ui/src/i18n/locales/pl/settings.json
  30. 42 13
      webview-ui/src/i18n/locales/pt-BR/settings.json
  31. 42 13
      webview-ui/src/i18n/locales/ru/settings.json
  32. 42 13
      webview-ui/src/i18n/locales/tr/settings.json
  33. 42 13
      webview-ui/src/i18n/locales/vi/settings.json
  34. 42 13
      webview-ui/src/i18n/locales/zh-CN/settings.json
  35. 42 13
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 17 - 0
src/core/config/ContextProxy.ts

@@ -147,6 +147,23 @@ export class ContextProxy {
 			: this.originalContext.secrets.store(key, value)
 	}
 
+	/**
+	 * Refresh secrets from storage and update cache
+	 * This is useful when you need to ensure the cache has the latest values
+	 */
+	async refreshSecrets(): Promise<void> {
+		const promises = SECRET_STATE_KEYS.map(async (key) => {
+			try {
+				this.secretCache[key] = await this.originalContext.secrets.get(key)
+			} catch (error) {
+				logger.error(
+					`Error refreshing secret ${key}: ${error instanceof Error ? error.message : String(error)}`,
+				)
+			}
+		})
+		await Promise.all(promises)
+	}
+
 	private getAllSecretState(): SecretState {
 		return Object.fromEntries(SECRET_STATE_KEYS.map((key) => [key, this.getSecret(key)]))
 	}

+ 0 - 5
src/core/webview/ClineProvider.ts

@@ -884,11 +884,6 @@ export class ClineProvider
 					this.contextProxy.setProviderSettings(providerSettings),
 				])
 
-				// Notify CodeIndexManager about the settings change
-				if (this.codeIndexManager) {
-					await this.codeIndexManager.handleExternalSettingsChange()
-				}
-
 				// Change the provider for the current task.
 				// TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
 				const task = this.getCurrentCline()

+ 99 - 16
src/core/webview/webviewMessageHandler.ts

@@ -1113,6 +1113,21 @@ export const webviewMessageHandler = async (
 			break
 		case "browserToolEnabled":
 			await updateGlobalState("browserToolEnabled", message.bool ?? true)
+			await provider.postStateToWebview()
+			break
+		case "codebaseIndexEnabled":
+			// Update the codebaseIndexConfig with the new enabled state
+			const currentCodebaseConfig = getGlobalState("codebaseIndexConfig") || {}
+			await updateGlobalState("codebaseIndexConfig", {
+				...currentCodebaseConfig,
+				codebaseIndexEnabled: message.bool ?? false,
+			})
+
+			// Notify the code index manager about the change
+			if (provider.codeIndexManager) {
+				await provider.codeIndexManager.handleSettingsChange()
+			}
+
 			await provider.postStateToWebview()
 			break
 		case "language":
@@ -1806,38 +1821,86 @@ export const webviewMessageHandler = async (
 
 			break
 		}
-		case "codebaseIndexConfig": {
-			const codebaseIndexConfig = message.values ?? {
-				codebaseIndexEnabled: false,
-				codebaseIndexQdrantUrl: "http://localhost:6333",
-				codebaseIndexEmbedderProvider: "openai",
-				codebaseIndexEmbedderBaseUrl: "",
-				codebaseIndexEmbedderModelId: "",
+
+		case "saveCodeIndexSettingsAtomic": {
+			if (!message.codeIndexSettings) {
+				break
 			}
-			await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig)
+
+			const settings = message.codeIndexSettings
 
 			try {
+				// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
+				const currentConfig = getGlobalState("codebaseIndexConfig") || {}
+				const globalStateConfig = {
+					...currentConfig,
+					codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
+					codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider,
+					codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl,
+					codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId,
+					codebaseIndexOpenAiCompatibleBaseUrl: settings.codebaseIndexOpenAiCompatibleBaseUrl,
+					codebaseIndexOpenAiCompatibleModelDimension: settings.codebaseIndexOpenAiCompatibleModelDimension,
+				}
+
+				// Save global state first
+				await updateGlobalState("codebaseIndexConfig", globalStateConfig)
+
+				// Save secrets directly using context proxy
+				if (settings.codeIndexOpenAiKey !== undefined) {
+					await provider.contextProxy.storeSecret("codeIndexOpenAiKey", settings.codeIndexOpenAiKey)
+				}
+				if (settings.codeIndexQdrantApiKey !== undefined) {
+					await provider.contextProxy.storeSecret("codeIndexQdrantApiKey", settings.codeIndexQdrantApiKey)
+				}
+				if (settings.codebaseIndexOpenAiCompatibleApiKey !== undefined) {
+					await provider.contextProxy.storeSecret(
+						"codebaseIndexOpenAiCompatibleApiKey",
+						settings.codebaseIndexOpenAiCompatibleApiKey,
+					)
+				}
+				if (settings.codebaseIndexGeminiApiKey !== undefined) {
+					await provider.contextProxy.storeSecret(
+						"codebaseIndexGeminiApiKey",
+						settings.codebaseIndexGeminiApiKey,
+					)
+				}
+
+				// Verify secrets are actually stored
+				const storedOpenAiKey = provider.contextProxy.getSecret("codeIndexOpenAiKey")
+
+				// Notify code index manager of changes
 				if (provider.codeIndexManager) {
-					await provider.codeIndexManager.handleExternalSettingsChange()
+					await provider.codeIndexManager.handleSettingsChange()
 
-					// If now configured and enabled, start indexing automatically
+					// Auto-start indexing if now enabled and configured
 					if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
 						if (!provider.codeIndexManager.isInitialized) {
 							await provider.codeIndexManager.initialize(provider.contextProxy)
 						}
-						// Start indexing in background (no await)
 						provider.codeIndexManager.startIndexing()
 					}
 				}
+
+				// Send success response
+				await provider.postMessageToWebview({
+					type: "codeIndexSettingsSaved",
+					success: true,
+					settings: globalStateConfig,
+				})
+
+				// Update webview state
+				await provider.postStateToWebview()
 			} catch (error) {
-				provider.log(
-					`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`,
-				)
+				provider.log(`Error saving code index settings: ${error.message || error}`)
+				await provider.postMessageToWebview({
+					type: "codeIndexSettingsSaved",
+					success: false,
+					error: error.message || "Failed to save settings",
+				})
 			}
-
-			await provider.postStateToWebview()
 			break
 		}
+
 		case "requestIndexingStatus": {
 			const status = provider.codeIndexManager!.getCurrentStatus()
 			provider.postMessageToWebview({
@@ -1846,6 +1909,26 @@ export const webviewMessageHandler = async (
 			})
 			break
 		}
+		case "requestCodeIndexSecretStatus": {
+			// Check if secrets are set using the VSCode context directly for async access
+			const hasOpenAiKey = !!(await provider.context.secrets.get("codeIndexOpenAiKey"))
+			const hasQdrantApiKey = !!(await provider.context.secrets.get("codeIndexQdrantApiKey"))
+			const hasOpenAiCompatibleApiKey = !!(await provider.context.secrets.get(
+				"codebaseIndexOpenAiCompatibleApiKey",
+			))
+			const hasGeminiApiKey = !!(await provider.context.secrets.get("codebaseIndexGeminiApiKey"))
+
+			provider.postMessageToWebview({
+				type: "codeIndexSecretStatus",
+				values: {
+					hasOpenAiKey,
+					hasQdrantApiKey,
+					hasOpenAiCompatibleApiKey,
+					hasGeminiApiKey,
+				},
+			})
+			break
+		}
 		case "startIndexing": {
 			try {
 				const manager = provider.codeIndexManager!

+ 122 - 94
src/services/code-index/__tests__/config-manager.spec.ts

@@ -9,11 +9,26 @@ describe("CodeIndexConfigManager", () => {
 		mockContextProxy = {
 			getGlobalState: vitest.fn(),
 			getSecret: vitest.fn().mockReturnValue(undefined),
+			refreshSecrets: vitest.fn().mockResolvedValue(undefined),
 		}
 
 		configManager = new CodeIndexConfigManager(mockContextProxy)
 	})
 
+	// Helper function to setup secret mocking
+	const setupSecretMocks = (secrets: Record<string, string>) => {
+		// Mock sync secret access
+		mockContextProxy.getSecret.mockImplementation((key: string) => {
+			return secrets[key] || undefined
+		})
+
+		// Mock refreshSecrets to update the getSecret mock with new values
+		mockContextProxy.refreshSecrets.mockImplementation(async () => {
+			// In real implementation, this would refresh from VSCode storage
+			// For tests, we just keep the existing mock behavior
+		})
+	}
+
 	describe("constructor", () => {
 		it("should initialize with ContextProxy", () => {
 			expect(configManager).toBeDefined()
@@ -52,10 +67,11 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderModelId: "text-embedding-3-large",
 			}
 			mockContextProxy.getGlobalState.mockReturnValue(mockGlobalState)
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-openai-key"
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				return undefined
+
+			// Mock both sync and async secret access
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-openai-key",
+				codeIndexQdrantApiKey: "test-qdrant-key",
 			})
 
 			const result = await configManager.loadConfiguration()
@@ -86,10 +102,10 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-openai-compatible-key"
-				return undefined
+
+			setupSecretMocks({
+				codeIndexQdrantApiKey: "test-qdrant-key",
+				codebaseIndexOpenAiCompatibleApiKey: "test-openai-compatible-key",
 			})
 
 			const result = await configManager.loadConfiguration()
@@ -125,10 +141,9 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-openai-compatible-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexQdrantApiKey: "test-qdrant-key",
+				codebaseIndexOpenAiCompatibleApiKey: "test-openai-compatible-key",
 			})
 
 			const result = await configManager.loadConfiguration()
@@ -165,10 +180,9 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleModelDimension") return undefined
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-openai-compatible-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexQdrantApiKey: "test-qdrant-key",
+				codebaseIndexOpenAiCompatibleApiKey: "test-openai-compatible-key",
 			})
 
 			const result = await configManager.loadConfiguration()
@@ -204,10 +218,9 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleModelDimension") return "invalid-dimension"
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-openai-compatible-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexQdrantApiKey: "test-qdrant-key",
+				codebaseIndexOpenAiCompatibleApiKey: "test-openai-compatible-key",
 			})
 
 			const result = await configManager.loadConfiguration()
@@ -238,9 +251,8 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-large",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-openai-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-openai-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -266,7 +278,10 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockReturnValue("test-key")
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
+				codeIndexQdrantApiKey: "test-key",
+			})
 
 			await configManager.loadConfiguration()
 
@@ -290,9 +305,8 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -324,7 +338,10 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockReturnValue("test-key")
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
+				codeIndexQdrantApiKey: "test-key",
+			})
 
 			const result = await configManager.loadConfiguration()
 			expect(result.requiresRestart).toBe(true)
@@ -339,14 +356,17 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexEmbedderProvider: "openai",
 					codebaseIndexEmbedderModelId: "text-embedding-3-small",
 				})
-				mockContextProxy.getSecret.mockReturnValue("old-key")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "old-key",
+					codeIndexQdrantApiKey: "old-key",
+				})
 
 				await configManager.loadConfiguration()
 
 				// Change API key
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codeIndexOpenAiKey") return "new-key"
-					return undefined
+				setupSecretMocks({
+					codeIndexOpenAiKey: "new-key",
+					codeIndexQdrantApiKey: "old-key",
 				})
 
 				const result = await configManager.loadConfiguration()
@@ -361,7 +381,10 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexEmbedderProvider: "openai",
 					codebaseIndexEmbedderModelId: "text-embedding-3-small",
 				})
-				mockContextProxy.getSecret.mockReturnValue("test-key")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "test-key",
+					codeIndexQdrantApiKey: "test-key",
+				})
 
 				await configManager.loadConfiguration()
 
@@ -385,7 +408,10 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexEmbedderProvider: "openai",
 					codebaseIndexEmbedderModelId: "text-embedding-3-small",
 				})
-				mockContextProxy.getSecret.mockReturnValue("test-key")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "test-key",
+					codeIndexQdrantApiKey: "test-key",
+				})
 
 				await configManager.loadConfiguration()
 
@@ -440,9 +466,9 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://old-api.example.com/v1"
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "old-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "old-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -479,17 +505,17 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "old-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "old-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
 
 				// Change OpenAI Compatible API key
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "new-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "new-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				const result = await configManager.loadConfiguration()
@@ -511,9 +537,9 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -552,9 +578,9 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -594,9 +620,9 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleModelDimension") return undefined
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -635,9 +661,9 @@ describe("CodeIndexConfigManager", () => {
 					if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
 					return undefined
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-					return undefined
+				setupSecretMocks({
+					codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
+					codeIndexQdrantApiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -668,9 +694,8 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexQdrantUrl: "http://qdrant.local",
 					codebaseIndexEmbedderProvider: "openai",
 				})
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codeIndexOpenAiKey") return "test-key"
-					return undefined
+				setupSecretMocks({
+					codeIndexOpenAiKey: "test-key",
 				})
 
 				await configManager.loadConfiguration()
@@ -694,7 +719,7 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexQdrantUrl: "http://qdrant.local",
 					codebaseIndexEmbedderProvider: "openai",
 				})
-				mockContextProxy.getSecret.mockReturnValue(undefined)
+				setupSecretMocks({})
 
 				await configManager.loadConfiguration()
 
@@ -867,7 +892,7 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexEmbedderProvider: "openai",
 					codebaseIndexEmbedderModelId: "text-embedding-3-small",
 				})
-				mockContextProxy.getSecret.mockReturnValue(undefined)
+				setupSecretMocks({})
 
 				await configManager.loadConfiguration()
 
@@ -892,12 +917,15 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexQdrantUrl: "http://qdrant.local",
 					codebaseIndexEmbedderProvider: "openai",
 				})
-				mockContextProxy.getSecret.mockReturnValue(undefined)
+				setupSecretMocks({})
 
 				await configManager.loadConfiguration()
 
 				// Change to empty string API keys (simulating what happens when secrets return "")
-				mockContextProxy.getSecret.mockReturnValue("")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "",
+					codeIndexQdrantApiKey: "",
+				})
 
 				const result = await configManager.loadConfiguration()
 				// Should NOT require restart since undefined and "" are both "empty"
@@ -911,14 +939,17 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexQdrantUrl: "http://qdrant.local",
 					codebaseIndexEmbedderProvider: "openai",
 				})
-				mockContextProxy.getSecret.mockReturnValue("")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "",
+					codeIndexQdrantApiKey: "",
+				})
 
 				await configManager.loadConfiguration()
 
 				// Add actual API key
-				mockContextProxy.getSecret.mockImplementation((key: string) => {
-					if (key === "codeIndexOpenAiKey") return "actual-api-key"
-					return ""
+				setupSecretMocks({
+					codeIndexOpenAiKey: "actual-api-key",
+					codeIndexQdrantApiKey: "",
 				})
 
 				const result = await configManager.loadConfiguration()
@@ -936,7 +967,10 @@ describe("CodeIndexConfigManager", () => {
 					codebaseIndexEmbedderProvider: "openai",
 					codebaseIndexEmbedderModelId: "text-embedding-3-small",
 				})
-				mockContextProxy.getSecret.mockReturnValue("test-key")
+				setupSecretMocks({
+					codeIndexOpenAiKey: "test-key",
+					codeIndexQdrantApiKey: "test-key",
+				})
 
 				await configManager.loadConfiguration()
 
@@ -965,9 +999,9 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexQdrantUrl: "http://qdrant.local",
 				codebaseIndexEmbedderProvider: "openai",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
+				codeIndexQdrantApiKey: "test-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -998,9 +1032,9 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-				return undefined
+			setupSecretMocks({
+				codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
+				codeIndexQdrantApiKey: "test-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -1019,9 +1053,8 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return ""
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
-				return undefined
+			setupSecretMocks({
+				codebaseIndexOpenAiCompatibleApiKey: "test-api-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -1040,9 +1073,8 @@ describe("CodeIndexConfigManager", () => {
 				if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
 				return undefined
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codebaseIndexOpenAiCompatibleApiKey") return ""
-				return undefined
+			setupSecretMocks({
+				codebaseIndexOpenAiCompatibleApiKey: "",
 			})
 
 			await configManager.loadConfiguration()
@@ -1108,10 +1140,9 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-large",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-openai-key"
-				if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-openai-key",
+				codeIndexQdrantApiKey: "test-qdrant-key",
 			})
 
 			await configManager.loadConfiguration()
@@ -1161,9 +1192,8 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
 			})
 
 			// First load - this will initialize the config manager with current state
@@ -1182,9 +1212,8 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
 			})
 
 			// Create a new config manager (simulating what happens in CodeIndexManager.initialize)
@@ -1196,8 +1225,8 @@ describe("CodeIndexConfigManager", () => {
 		})
 
 		it("should not require restart when settings are saved but code indexing config unchanged", async () => {
-			// This test simulates the original issue: handleExternalSettingsChange() being called
-			// when other settings are saved, but code indexing settings haven't changed
+			// This test simulates the scenario where handleSettingsChange() is called
+			// but code indexing settings haven't actually changed
 
 			// Setup initial state - enabled and configured
 			mockContextProxy.getGlobalState.mockReturnValue({
@@ -1206,9 +1235,8 @@ describe("CodeIndexConfigManager", () => {
 				codebaseIndexEmbedderProvider: "openai",
 				codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			})
-			mockContextProxy.getSecret.mockImplementation((key: string) => {
-				if (key === "codeIndexOpenAiKey") return "test-key"
-				return undefined
+			setupSecretMocks({
+				codeIndexOpenAiKey: "test-key",
 			})
 
 			// First load to establish baseline

+ 20 - 7
src/services/code-index/__tests__/manager.spec.ts

@@ -13,7 +13,7 @@ vitest.mock("../state-manager", () => ({
 	})),
 }))
 
-describe("CodeIndexManager - handleExternalSettingsChange regression", () => {
+describe("CodeIndexManager - handleSettingsChange regression", () => {
 	let mockContext: any
 	let manager: CodeIndexManager
 
@@ -48,9 +48,9 @@ describe("CodeIndexManager - handleExternalSettingsChange regression", () => {
 		CodeIndexManager.disposeAll()
 	})
 
-	describe("handleExternalSettingsChange", () => {
+	describe("handleSettingsChange", () => {
 		it("should not throw when called on uninitialized manager (regression test)", async () => {
-			// This is the core regression test: handleExternalSettingsChange() should not throw
+			// This is the core regression test: handleSettingsChange() should not throw
 			// when called before the manager is initialized (during first-time configuration)
 
 			// Ensure manager is not initialized
@@ -67,16 +67,28 @@ describe("CodeIndexManager - handleExternalSettingsChange regression", () => {
 			vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
 
 			// The key test: this should NOT throw "CodeIndexManager not initialized" error
-			await expect(manager.handleExternalSettingsChange()).resolves.not.toThrow()
+			await expect(manager.handleSettingsChange()).resolves.not.toThrow()
 
 			// Verify that loadConfiguration was called (the method should still work)
 			expect(mockConfigManager.loadConfiguration).toHaveBeenCalled()
 		})
 
 		it("should work normally when manager is initialized", async () => {
-			// Mock a minimal config manager
+			// Mock a complete config manager with all required properties
 			const mockConfigManager = {
 				loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: true }),
+				isFeatureConfigured: true,
+				isFeatureEnabled: true,
+				getConfig: vitest.fn().mockReturnValue({
+					isEnabled: true,
+					isConfigured: true,
+					embedderProvider: "openai",
+					modelId: "text-embedding-3-small",
+					openAiOptions: { openAiNativeApiKey: "test-key" },
+					qdrantUrl: "http://localhost:6333",
+					qdrantApiKey: "test-key",
+					searchMinScore: 0.4,
+				}),
 			}
 			;(manager as any)._configManager = mockConfigManager
 
@@ -96,10 +108,11 @@ describe("CodeIndexManager - handleExternalSettingsChange regression", () => {
 			vitest.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
 			vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
 
-			await manager.handleExternalSettingsChange()
+			await manager.handleSettingsChange()
 
 			// Verify that the restart sequence was called
 			expect(mockConfigManager.loadConfiguration).toHaveBeenCalled()
+			// stopWatcher is called inside _recreateServices, which we mocked
 			expect(recreateServicesSpy).toHaveBeenCalled()
 			expect(startIndexingSpy).toHaveBeenCalled()
 		})
@@ -109,7 +122,7 @@ describe("CodeIndexManager - handleExternalSettingsChange regression", () => {
 			;(manager as any)._configManager = undefined
 
 			// This should not throw an error
-			await expect(manager.handleExternalSettingsChange()).resolves.not.toThrow()
+			await expect(manager.handleSettingsChange()).resolves.not.toThrow()
 		})
 	})
 })

+ 56 - 40
src/services/code-index/config-manager.ts

@@ -26,6 +26,13 @@ export class CodeIndexConfigManager {
 		this._loadAndSetConfiguration()
 	}
 
+	/**
+	 * Gets the context proxy instance
+	 */
+	public getContextProxy(): ContextProxy {
+		return this.contextProxy
+	}
+
 	/**
 	 * Private method that handles loading configuration from storage and updating instance variables.
 	 * This eliminates code duplication between initializeWithCurrentConfig() and loadConfiguration().
@@ -131,6 +138,9 @@ export class CodeIndexConfigManager {
 			qdrantApiKey: this.qdrantApiKey ?? "",
 		}
 
+		// Refresh secrets from VSCode storage to ensure we have the latest values
+		await this.contextProxy.refreshSecrets()
+
 		// Load new configuration from storage and update instance variables
 		this._loadAndSetConfiguration()
 
@@ -162,38 +172,50 @@ export class CodeIndexConfigManager {
 		if (this.embedderProvider === "openai") {
 			const openAiKey = this.openAiOptions?.openAiNativeApiKey
 			const qdrantUrl = this.qdrantUrl
-			const isConfigured = !!(openAiKey && qdrantUrl)
-			return isConfigured
+			return !!(openAiKey && qdrantUrl)
 		} else if (this.embedderProvider === "ollama") {
 			// Ollama model ID has a default, so only base URL is strictly required for config
 			const ollamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl
 			const qdrantUrl = this.qdrantUrl
-			const isConfigured = !!(ollamaBaseUrl && qdrantUrl)
-			return isConfigured
+			return !!(ollamaBaseUrl && qdrantUrl)
 		} else if (this.embedderProvider === "openai-compatible") {
 			const baseUrl = this.openAiCompatibleOptions?.baseUrl
 			const apiKey = this.openAiCompatibleOptions?.apiKey
 			const qdrantUrl = this.qdrantUrl
-			return !!(baseUrl && apiKey && qdrantUrl)
+			const isConfigured = !!(baseUrl && apiKey && qdrantUrl)
+			return isConfigured
 		} else if (this.embedderProvider === "gemini") {
 			const apiKey = this.geminiOptions?.apiKey
 			const qdrantUrl = this.qdrantUrl
-			return !!(apiKey && qdrantUrl)
+			const isConfigured = !!(apiKey && qdrantUrl)
+			return isConfigured
 		}
 		return false // Should not happen if embedderProvider is always set correctly
 	}
 
 	/**
 	 * Determines if a configuration change requires restarting the indexing process.
+	 * Simplified logic: only restart for critical changes that affect service functionality.
+	 *
+	 * CRITICAL CHANGES (require restart):
+	 * - Provider changes (openai -> ollama, etc.)
+	 * - Authentication changes (API keys, base URLs)
+	 * - Vector dimension changes (model changes that affect embedding size)
+	 * - Qdrant connection changes (URL, API key)
+	 * - Feature enable/disable transitions
+	 *
+	 * MINOR CHANGES (no restart needed):
+	 * - Search minimum score adjustments
+	 * - UI-only settings
+	 * - Non-functional configuration tweaks
 	 */
 	doesConfigChangeRequireRestart(prev: PreviousConfigSnapshot): boolean {
 		const nowConfigured = this.isConfigured()
 
-		// Handle null/undefined values safely - use empty strings for consistency with loaded config
+		// Handle null/undefined values safely
 		const prevEnabled = prev?.enabled ?? false
 		const prevConfigured = prev?.configured ?? false
 		const prevProvider = prev?.embedderProvider ?? "openai"
-		const prevModelId = prev?.modelId ?? undefined
 		const prevOpenAiKey = prev?.openAiKey ?? ""
 		const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? ""
 		const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
@@ -218,57 +240,51 @@ export class CodeIndexConfigManager {
 			return false
 		}
 
-		// 4. Check for changes in relevant settings if the feature is enabled (or was enabled)
+		// 4. CRITICAL CHANGES - Always restart for these
 		if (this.isEnabled || prevEnabled) {
 			// Provider change
 			if (prevProvider !== this.embedderProvider) {
 				return true
 			}
 
-			if (this._hasVectorDimensionChanged(prevProvider, prevModelId)) {
+			// Authentication changes (API keys)
+			const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
+			const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
+			const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
+			const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
+			const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension
+			const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
+			const currentQdrantUrl = this.qdrantUrl ?? ""
+			const currentQdrantApiKey = this.qdrantApiKey ?? ""
+
+			if (prevOpenAiKey !== currentOpenAiKey) {
 				return true
 			}
 
-			// Authentication changes
-			if (this.embedderProvider === "openai") {
-				const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
-				if (prevOpenAiKey !== currentOpenAiKey) {
-					return true
-				}
+			if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
+				return true
 			}
 
-			if (this.embedderProvider === "ollama") {
-				const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
-				if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
-					return true
-				}
+			if (
+				prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
+				prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey
+			) {
+				return true
 			}
 
-			if (this.embedderProvider === "openai-compatible") {
-				const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
-				const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
-				const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension
-				if (
-					prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
-					prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey ||
-					prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension
-				) {
+			// Check for OpenAI Compatible modelDimension changes
+			if (this.embedderProvider === "openai-compatible" || prevProvider === "openai-compatible") {
+				if (prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension) {
 					return true
 				}
 			}
 
-			if (this.embedderProvider === "gemini") {
-				const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
-				if (prevGeminiApiKey !== currentGeminiApiKey) {
-					return true
-				}
+			if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
+				return true
 			}
 
-			// Qdrant configuration changes
-			const currentQdrantUrl = this.qdrantUrl ?? ""
-			const currentQdrantApiKey = this.qdrantApiKey ?? ""
-
-			if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
+			// Vector dimension changes (still important for compatibility)
+			if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) {
 				return true
 			}
 		}

+ 5 - 5
src/services/code-index/manager.ts

@@ -203,7 +203,7 @@ export class CodeIndexManager {
 
 	/**
 	 * Private helper method to recreate services with current configuration.
-	 * Used by both initialize() and handleExternalSettingsChange().
+	 * Used by both initialize() and handleSettingsChange().
 	 */
 	private async _recreateServices(): Promise<void> {
 		// Stop watcher if it exists
@@ -257,12 +257,12 @@ export class CodeIndexManager {
 	}
 
 	/**
-	 * Handles external settings changes by reloading configuration.
-	 * This method should be called when API provider settings are updated
+	 * Handle code index settings changes.
+	 * This method should be called when code index settings are updated
 	 * to ensure the CodeIndexConfigManager picks up the new configuration.
 	 * If the configuration changes require a restart, the service will be restarted.
 	 */
-	public async handleExternalSettingsChange(): Promise<void> {
+	public async handleSettingsChange(): Promise<void> {
 		if (this._configManager) {
 			const { requiresRestart } = await this._configManager.loadConfiguration()
 
@@ -275,7 +275,7 @@ export class CodeIndexManager {
 				await this._recreateServices()
 
 				// Start indexing with new services
-				await this.startIndexing()
+				this.startIndexing()
 			}
 		}
 	}

+ 3 - 1
src/services/code-index/service-factory.ts

@@ -30,7 +30,9 @@ export class CodeIndexServiceFactory {
 		const provider = config.embedderProvider as EmbedderProvider
 
 		if (provider === "openai") {
-			if (!config.openAiOptions?.openAiNativeApiKey) {
+			const apiKey = config.openAiOptions?.openAiNativeApiKey
+
+			if (!apiKey) {
 				throw new Error("OpenAI configuration missing for embedder creation")
 			}
 			return new OpenAiEmbedder({

+ 3 - 0
src/shared/ExtensionMessage.ts

@@ -103,6 +103,8 @@ export interface ExtensionMessage {
 		| "marketplaceInstallResult"
 		| "marketplaceData"
 		| "shareTaskSuccess"
+		| "codeIndexSettingsSaved"
+		| "codeIndexSecretStatus"
 	text?: string
 	payload?: any // Add a generic payload for now, can refine later
 	action?:
@@ -154,6 +156,7 @@ export interface ExtensionMessage {
 	marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
 	visibility?: ShareVisibility
 	rulesFolderPath?: string
+	settings?: any
 }
 
 export type ExtensionState = Pick<

+ 19 - 1
src/shared/WebviewMessage.ts

@@ -140,6 +140,7 @@ export interface WebviewMessage {
 		| "humanRelayResponse"
 		| "humanRelayCancel"
 		| "browserToolEnabled"
+		| "codebaseIndexEnabled"
 		| "telemetrySetting"
 		| "showRooIgnoredFiles"
 		| "testBrowserConnection"
@@ -162,7 +163,6 @@ export interface WebviewMessage {
 		| "indexingStatusUpdate"
 		| "indexCleared"
 		| "focusPanelRequest"
-		| "codebaseIndexConfig"
 		| "profileThresholds"
 		| "setHistoryPreviewCollapsed"
 		| "openExternal"
@@ -183,6 +183,8 @@ export interface WebviewMessage {
 		| "importModeResult"
 		| "checkRulesDirectory"
 		| "checkRulesDirectoryResult"
+		| "saveCodeIndexSettingsAtomic"
+		| "requestCodeIndexSecretStatus"
 	text?: string
 	tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
 	disabled?: boolean
@@ -223,6 +225,22 @@ export interface WebviewMessage {
 	visibility?: ShareVisibility // For share visibility
 	hasContent?: boolean // For checkRulesDirectoryResult
 	checkOnly?: boolean // For deleteCustomMode check
+	codeIndexSettings?: {
+		// Global state settings
+		codebaseIndexEnabled: boolean
+		codebaseIndexQdrantUrl: string
+		codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini"
+		codebaseIndexEmbedderBaseUrl?: string
+		codebaseIndexEmbedderModelId: string
+		codebaseIndexOpenAiCompatibleBaseUrl?: string
+		codebaseIndexOpenAiCompatibleModelDimension?: number
+
+		// Secret settings
+		codeIndexOpenAiKey?: string
+		codeIndexQdrantApiKey?: string
+		codebaseIndexOpenAiCompatibleApiKey?: string
+		codebaseIndexGeminiApiKey?: string
+	}
 }
 
 export const checkoutDiffPayloadSchema = z.object({

+ 636 - 0
webview-ui/src/components/chat/CodeIndexPopover.tsx

@@ -0,0 +1,636 @@
+import React, { useState, useEffect, useMemo } from "react"
+import { Trans } from "react-i18next"
+import {
+	VSCodeButton,
+	VSCodeTextField,
+	VSCodeDropdown,
+	VSCodeOption,
+	VSCodeLink,
+} from "@vscode/webview-ui-toolkit/react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+import { vscode } from "@src/utils/vscode"
+import { useExtensionState } from "@src/context/ExtensionStateContext"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { buildDocLink } from "@src/utils/docLinks"
+import { cn } from "@src/lib/utils"
+import {
+	Select,
+	SelectContent,
+	SelectItem,
+	SelectTrigger,
+	SelectValue,
+	AlertDialog,
+	AlertDialogAction,
+	AlertDialogCancel,
+	AlertDialogContent,
+	AlertDialogDescription,
+	AlertDialogFooter,
+	AlertDialogHeader,
+	AlertDialogTitle,
+	AlertDialogTrigger,
+	Popover,
+	PopoverContent,
+	PopoverTrigger,
+} from "@src/components/ui"
+import type { EmbedderProvider } from "@roo/embeddingModels"
+import type { IndexingStatus } from "@roo/ExtensionMessage"
+
+interface CodeIndexPopoverProps {
+	children: React.ReactNode
+	indexingStatus: IndexingStatus
+}
+
+interface LocalCodeIndexSettings {
+	// Global state settings
+	codebaseIndexEnabled: boolean
+	codebaseIndexQdrantUrl: string
+	codebaseIndexEmbedderProvider: EmbedderProvider
+	codebaseIndexEmbedderBaseUrl?: string
+	codebaseIndexEmbedderModelId: string
+
+	// Secret settings (start empty, will be loaded separately)
+	codeIndexOpenAiKey?: string
+	codeIndexQdrantApiKey?: string
+	codebaseIndexOpenAiCompatibleBaseUrl?: string
+	codebaseIndexOpenAiCompatibleApiKey?: string
+	codebaseIndexOpenAiCompatibleModelDimension?: number
+	codebaseIndexGeminiApiKey?: string
+}
+
+export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
+	children,
+	indexingStatus: externalIndexingStatus,
+}) => {
+	const SECRET_PLACEHOLDER = "••••••••••••••••"
+	const { t } = useAppTranslation()
+	const { codebaseIndexConfig, codebaseIndexModels } = useExtensionState()
+	const [open, setOpen] = useState(false)
+
+	const [indexingStatus, setIndexingStatus] = useState<IndexingStatus>(externalIndexingStatus)
+
+	const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle")
+	const [saveError, setSaveError] = useState<string | null>(null)
+
+	// Default settings template
+	const getDefaultSettings = (): LocalCodeIndexSettings => ({
+		codebaseIndexEnabled: false,
+		codebaseIndexQdrantUrl: "",
+		codebaseIndexEmbedderProvider: "openai",
+		codebaseIndexEmbedderBaseUrl: "",
+		codebaseIndexEmbedderModelId: "",
+		codeIndexOpenAiKey: "",
+		codeIndexQdrantApiKey: "",
+		codebaseIndexOpenAiCompatibleBaseUrl: "",
+		codebaseIndexOpenAiCompatibleApiKey: "",
+		codebaseIndexOpenAiCompatibleModelDimension: undefined,
+		codebaseIndexGeminiApiKey: "",
+	})
+
+	// Initial settings state - stores the settings when popover opens
+	const [initialSettings, setInitialSettings] = useState<LocalCodeIndexSettings>(getDefaultSettings())
+
+	// Current settings state - tracks user changes
+	const [currentSettings, setCurrentSettings] = useState<LocalCodeIndexSettings>(getDefaultSettings())
+
+	// Update indexing status from parent
+	useEffect(() => {
+		setIndexingStatus(externalIndexingStatus)
+	}, [externalIndexingStatus])
+
+	// Initialize settings from global state
+	useEffect(() => {
+		if (codebaseIndexConfig) {
+			const settings = {
+				codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled || false,
+				codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "",
+				codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai",
+				codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "",
+				codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "",
+				codeIndexOpenAiKey: "",
+				codeIndexQdrantApiKey: "",
+				codebaseIndexOpenAiCompatibleBaseUrl: "",
+				codebaseIndexOpenAiCompatibleApiKey: "",
+				codebaseIndexOpenAiCompatibleModelDimension: undefined,
+				codebaseIndexGeminiApiKey: "",
+			}
+			setInitialSettings(settings)
+			setCurrentSettings(settings)
+
+			// Request secret status to check if secrets exist
+			vscode.postMessage({ type: "requestCodeIndexSecretStatus" })
+		}
+	}, [codebaseIndexConfig])
+
+	// Request initial indexing status
+	useEffect(() => {
+		if (open) {
+			vscode.postMessage({ type: "requestIndexingStatus" })
+			vscode.postMessage({ type: "requestCodeIndexSecretStatus" })
+		}
+	}, [open])
+
+	// Listen for indexing status updates and save responses
+	useEffect(() => {
+		const handleMessage = (event: MessageEvent<any>) => {
+			if (event.data.type === "indexingStatusUpdate") {
+				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",
+				})
+			} else if (event.data.type === "codeIndexSettingsSaved") {
+				if (event.data.success) {
+					setSaveStatus("saved")
+					// Don't update initial settings here - wait for the secret status response
+					// Request updated secret status after save
+					vscode.postMessage({ type: "requestCodeIndexSecretStatus" })
+					// Reset status after 3 seconds
+					setTimeout(() => {
+						setSaveStatus("idle")
+					}, 3000)
+				} else {
+					setSaveStatus("error")
+					setSaveError(event.data.error || t("settings:codeIndex.saveError"))
+					// Clear error message after 5 seconds
+					setTimeout(() => {
+						setSaveStatus("idle")
+						setSaveError(null)
+					}, 5000)
+				}
+			}
+		}
+
+		window.addEventListener("message", handleMessage)
+		return () => window.removeEventListener("message", handleMessage)
+	}, [t])
+
+	// Listen for secret status
+	useEffect(() => {
+		const handleMessage = (event: MessageEvent) => {
+			if (event.data.type === "codeIndexSecretStatus") {
+				// Update settings to show placeholders for existing secrets
+				const secretStatus = event.data.values
+
+				// Update both current and initial settings based on what secrets exist
+				const updateWithSecrets = (prev: LocalCodeIndexSettings): LocalCodeIndexSettings => {
+					const updated = { ...prev }
+
+					// Only update to placeholder if the field is currently empty or already a placeholder
+					// This preserves user input when they're actively editing
+					if (!prev.codeIndexOpenAiKey || prev.codeIndexOpenAiKey === SECRET_PLACEHOLDER) {
+						updated.codeIndexOpenAiKey = secretStatus.hasOpenAiKey ? SECRET_PLACEHOLDER : ""
+					}
+					if (!prev.codeIndexQdrantApiKey || prev.codeIndexQdrantApiKey === SECRET_PLACEHOLDER) {
+						updated.codeIndexQdrantApiKey = secretStatus.hasQdrantApiKey ? SECRET_PLACEHOLDER : ""
+					}
+					if (
+						!prev.codebaseIndexOpenAiCompatibleApiKey ||
+						prev.codebaseIndexOpenAiCompatibleApiKey === SECRET_PLACEHOLDER
+					) {
+						updated.codebaseIndexOpenAiCompatibleApiKey = secretStatus.hasOpenAiCompatibleApiKey
+							? SECRET_PLACEHOLDER
+							: ""
+					}
+					if (!prev.codebaseIndexGeminiApiKey || prev.codebaseIndexGeminiApiKey === SECRET_PLACEHOLDER) {
+						updated.codebaseIndexGeminiApiKey = secretStatus.hasGeminiApiKey ? SECRET_PLACEHOLDER : ""
+					}
+
+					return updated
+				}
+
+				setCurrentSettings(updateWithSecrets)
+				setInitialSettings(updateWithSecrets)
+			}
+		}
+
+		window.addEventListener("message", handleMessage)
+		return () => window.removeEventListener("message", handleMessage)
+	}, [])
+
+	// Generic comparison function that detects changes between initial and current settings
+	const hasUnsavedChanges = useMemo(() => {
+		// Get all keys from both objects to handle any field
+		const allKeys = [...Object.keys(initialSettings), ...Object.keys(currentSettings)] as Array<
+			keyof LocalCodeIndexSettings
+		>
+
+		// Use a Set to ensure unique keys
+		const uniqueKeys = Array.from(new Set(allKeys))
+
+		for (const key of uniqueKeys) {
+			const currentValue = currentSettings[key]
+			const initialValue = initialSettings[key]
+
+			// For secret fields, check if the value has been modified from placeholder
+			if (currentValue === SECRET_PLACEHOLDER) {
+				// If it's still showing placeholder, no change
+				continue
+			}
+
+			// Compare values - handles all types including undefined
+			if (currentValue !== initialValue) {
+				return true
+			}
+		}
+
+		return false
+	}, [currentSettings, initialSettings])
+
+	const updateSetting = (key: keyof LocalCodeIndexSettings, value: any) => {
+		setCurrentSettings((prev) => ({ ...prev, [key]: value }))
+	}
+
+	const handleSaveSettings = () => {
+		setSaveStatus("saving")
+		setSaveError(null)
+
+		// Prepare settings to save - include all fields except secrets with placeholder values
+		const settingsToSave: any = {}
+
+		// Iterate through all current settings
+		for (const [key, value] of Object.entries(currentSettings)) {
+			// Skip secret fields that still have placeholder value
+			if (value === SECRET_PLACEHOLDER) {
+				continue
+			}
+
+			// Include all other fields
+			settingsToSave[key] = value
+		}
+
+		// Save settings to backend
+		vscode.postMessage({
+			type: "saveCodeIndexSettingsAtomic",
+			codeIndexSettings: settingsToSave,
+		})
+	}
+
+	const progressPercentage = useMemo(
+		() =>
+			indexingStatus.totalItems > 0
+				? Math.round((indexingStatus.processedItems / indexingStatus.totalItems) * 100)
+				: 0,
+		[indexingStatus.processedItems, indexingStatus.totalItems],
+	)
+
+	const transformStyleString = `translateX(-${100 - progressPercentage}%)`
+
+	const getAvailableModels = () => {
+		if (!codebaseIndexModels) return []
+
+		const models = codebaseIndexModels[currentSettings.codebaseIndexEmbedderProvider]
+		return models ? Object.keys(models) : []
+	}
+
+	return (
+		<Popover open={open} onOpenChange={setOpen}>
+			<PopoverTrigger asChild>{children}</PopoverTrigger>
+			<PopoverContent
+				className="w-[calc(100vw-32px)] max-w-[450px] max-h-[80vh] overflow-y-auto p-4"
+				align="end"
+				alignOffset={0}
+				side="bottom"
+				sideOffset={5}
+				collisionPadding={16}
+				avoidCollisions={true}>
+				<div className="mb-4">
+					<h3 className="text-base font-medium mb-2">{t("settings:codeIndex.title")}</h3>
+					<p className="text-sm text-vscode-descriptionForeground">
+						<Trans i18nKey="settings:codeIndex.description">
+							<VSCodeLink
+								href={buildDocLink("features/experimental/codebase-indexing", "settings")}
+								style={{ display: "inline" }}
+							/>
+						</Trans>
+					</p>
+				</div>
+
+				<div className="space-y-4">
+					{/* Status Section */}
+					<div className="space-y-2">
+						<h4 className="text-sm font-medium">{t("settings:codeIndex.statusTitle")}</h4>
+						<div className="text-sm text-vscode-descriptionForeground">
+							<span
+								className={cn("inline-block w-3 h-3 rounded-full mr-2", {
+									"bg-gray-400": indexingStatus.systemStatus === "Standby",
+									"bg-yellow-500 animate-pulse": indexingStatus.systemStatus === "Indexing",
+									"bg-green-500": indexingStatus.systemStatus === "Indexed",
+									"bg-red-500": indexingStatus.systemStatus === "Error",
+								})}
+							/>
+							{t(`settings:codeIndex.indexingStatuses.${indexingStatus.systemStatus.toLowerCase()}`)}
+							{indexingStatus.message ? ` - ${indexingStatus.message}` : ""}
+						</div>
+
+						{indexingStatus.systemStatus === "Indexing" && (
+							<div className="mt-2">
+								<ProgressPrimitive.Root
+									className="relative h-2 w-full overflow-hidden rounded-full bg-secondary"
+									value={progressPercentage}>
+									<ProgressPrimitive.Indicator
+										className="h-full w-full flex-1 bg-primary transition-transform duration-300 ease-in-out"
+										style={{
+											transform: transformStyleString,
+										}}
+									/>
+								</ProgressPrimitive.Root>
+							</div>
+						)}
+					</div>
+
+					{/* Embedder Provider Section */}
+					<div className="space-y-2">
+						<label className="text-sm font-medium">{t("settings:codeIndex.embedderProviderLabel")}</label>
+						<Select
+							value={currentSettings.codebaseIndexEmbedderProvider}
+							onValueChange={(value: EmbedderProvider) =>
+								updateSetting("codebaseIndexEmbedderProvider", value)
+							}>
+							<SelectTrigger className="w-full">
+								<SelectValue />
+							</SelectTrigger>
+							<SelectContent>
+								<SelectItem value="openai">{t("settings:codeIndex.openaiProvider")}</SelectItem>
+								<SelectItem value="ollama">{t("settings:codeIndex.ollamaProvider")}</SelectItem>
+								<SelectItem value="openai-compatible">
+									{t("settings:codeIndex.openaiCompatibleProvider")}
+								</SelectItem>
+								<SelectItem value="gemini">{t("settings:codeIndex.geminiProvider")}</SelectItem>
+							</SelectContent>
+						</Select>
+					</div>
+
+					{/* Provider-specific settings */}
+					{currentSettings.codebaseIndexEmbedderProvider === "openai" && (
+						<>
+							<div className="space-y-2">
+								<label className="text-sm font-medium">{t("settings:codeIndex.openAiKeyLabel")}</label>
+								<VSCodeTextField
+									type="password"
+									value={currentSettings.codeIndexOpenAiKey || ""}
+									onInput={(e: any) => updateSetting("codeIndexOpenAiKey", e.target.value)}
+									placeholder={t("settings:codeIndex.openAiKeyPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">{t("settings:codeIndex.modelLabel")}</label>
+								<VSCodeDropdown
+									value={currentSettings.codebaseIndexEmbedderModelId}
+									onChange={(e: any) => updateSetting("codebaseIndexEmbedderModelId", e.target.value)}
+									className="w-full">
+									<VSCodeOption value="">{t("settings:codeIndex.selectModel")}</VSCodeOption>
+									{getAvailableModels().map((modelId) => {
+										const model =
+											codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[
+												modelId
+											]
+										return (
+											<VSCodeOption key={modelId} value={modelId}>
+												{modelId}{" "}
+												{model
+													? t("settings:codeIndex.modelDimensions", {
+															dimension: model.dimension,
+														})
+													: ""}
+											</VSCodeOption>
+										)
+									})}
+								</VSCodeDropdown>
+							</div>
+						</>
+					)}
+
+					{currentSettings.codebaseIndexEmbedderProvider === "ollama" && (
+						<>
+							<div className="space-y-2">
+								<label className="text-sm font-medium">
+									{t("settings:codeIndex.ollamaBaseUrlLabel")}
+								</label>
+								<VSCodeTextField
+									value={currentSettings.codebaseIndexEmbedderBaseUrl || ""}
+									onInput={(e: any) => updateSetting("codebaseIndexEmbedderBaseUrl", e.target.value)}
+									placeholder={t("settings:codeIndex.ollamaUrlPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">{t("settings:codeIndex.modelLabel")}</label>
+								<VSCodeDropdown
+									value={currentSettings.codebaseIndexEmbedderModelId}
+									onChange={(e: any) => updateSetting("codebaseIndexEmbedderModelId", e.target.value)}
+									className="w-full">
+									<VSCodeOption value="">{t("settings:codeIndex.selectModel")}</VSCodeOption>
+									{getAvailableModels().map((modelId) => {
+										const model =
+											codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[
+												modelId
+											]
+										return (
+											<VSCodeOption key={modelId} value={modelId}>
+												{modelId}{" "}
+												{model
+													? t("settings:codeIndex.modelDimensions", {
+															dimension: model.dimension,
+														})
+													: ""}
+											</VSCodeOption>
+										)
+									})}
+								</VSCodeDropdown>
+							</div>
+						</>
+					)}
+
+					{currentSettings.codebaseIndexEmbedderProvider === "openai-compatible" && (
+						<>
+							<div className="space-y-2">
+								<label className="text-sm font-medium">
+									{t("settings:codeIndex.openAiCompatibleBaseUrlLabel")}
+								</label>
+								<VSCodeTextField
+									value={currentSettings.codebaseIndexOpenAiCompatibleBaseUrl || ""}
+									onInput={(e: any) =>
+										updateSetting("codebaseIndexOpenAiCompatibleBaseUrl", e.target.value)
+									}
+									placeholder={t("settings:codeIndex.openAiCompatibleBaseUrlPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">
+									{t("settings:codeIndex.openAiCompatibleApiKeyLabel")}
+								</label>
+								<VSCodeTextField
+									type="password"
+									value={currentSettings.codebaseIndexOpenAiCompatibleApiKey || ""}
+									onInput={(e: any) =>
+										updateSetting("codebaseIndexOpenAiCompatibleApiKey", e.target.value)
+									}
+									placeholder={t("settings:codeIndex.openAiCompatibleApiKeyPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">{t("settings:codeIndex.modelLabel")}</label>
+								<VSCodeTextField
+									value={currentSettings.codebaseIndexEmbedderModelId || ""}
+									onInput={(e: any) => updateSetting("codebaseIndexEmbedderModelId", e.target.value)}
+									placeholder={t("settings:codeIndex.modelPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">
+									{t("settings:codeIndex.modelDimensionLabel")}
+								</label>
+								<VSCodeTextField
+									value={
+										currentSettings.codebaseIndexOpenAiCompatibleModelDimension?.toString() || ""
+									}
+									onInput={(e: any) => {
+										const value = e.target.value ? parseInt(e.target.value) : undefined
+										updateSetting("codebaseIndexOpenAiCompatibleModelDimension", value)
+									}}
+									placeholder={t("settings:codeIndex.modelDimensionPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+						</>
+					)}
+
+					{currentSettings.codebaseIndexEmbedderProvider === "gemini" && (
+						<>
+							<div className="space-y-2">
+								<label className="text-sm font-medium">
+									{t("settings:codeIndex.geminiApiKeyLabel")}
+								</label>
+								<VSCodeTextField
+									type="password"
+									value={currentSettings.codebaseIndexGeminiApiKey || ""}
+									onInput={(e: any) => updateSetting("codebaseIndexGeminiApiKey", e.target.value)}
+									placeholder={t("settings:codeIndex.geminiApiKeyPlaceholder")}
+									className="w-full"
+								/>
+							</div>
+
+							<div className="space-y-2">
+								<label className="text-sm font-medium">{t("settings:codeIndex.modelLabel")}</label>
+								<VSCodeDropdown
+									value={currentSettings.codebaseIndexEmbedderModelId}
+									onChange={(e: any) => updateSetting("codebaseIndexEmbedderModelId", e.target.value)}
+									className="w-full">
+									<VSCodeOption value="">{t("settings:codeIndex.selectModel")}</VSCodeOption>
+									{getAvailableModels().map((modelId) => {
+										const model =
+											codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[
+												modelId
+											]
+										return (
+											<VSCodeOption key={modelId} value={modelId}>
+												{modelId}{" "}
+												{model
+													? t("settings:codeIndex.modelDimensions", {
+															dimension: model.dimension,
+														})
+													: ""}
+											</VSCodeOption>
+										)
+									})}
+								</VSCodeDropdown>
+							</div>
+						</>
+					)}
+
+					{/* Qdrant Settings */}
+					<div className="space-y-2">
+						<label className="text-sm font-medium">{t("settings:codeIndex.qdrantUrlLabel")}</label>
+						<VSCodeTextField
+							value={currentSettings.codebaseIndexQdrantUrl || ""}
+							onInput={(e: any) => updateSetting("codebaseIndexQdrantUrl", e.target.value)}
+							placeholder={t("settings:codeIndex.qdrantUrlPlaceholder")}
+							className="w-full"
+						/>
+					</div>
+
+					<div className="space-y-2">
+						<label className="text-sm font-medium">{t("settings:codeIndex.qdrantApiKeyLabel")}</label>
+						<VSCodeTextField
+							type="password"
+							value={currentSettings.codeIndexQdrantApiKey || ""}
+							onInput={(e: any) => updateSetting("codeIndexQdrantApiKey", e.target.value)}
+							placeholder={t("settings:codeIndex.qdrantApiKeyPlaceholder")}
+							className="w-full"
+						/>
+					</div>
+
+					{/* Action Buttons */}
+					<div className="flex items-center justify-between gap-2 pt-2">
+						<div className="flex gap-2">
+							{(indexingStatus.systemStatus === "Error" || indexingStatus.systemStatus === "Standby") && (
+								<VSCodeButton
+									onClick={() => vscode.postMessage({ type: "startIndexing" })}
+									disabled={saveStatus === "saving" || hasUnsavedChanges}>
+									{t("settings:codeIndex.startIndexingButton")}
+								</VSCodeButton>
+							)}
+
+							{(indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && (
+								<AlertDialog>
+									<AlertDialogTrigger asChild>
+										<VSCodeButton appearance="secondary">
+											{t("settings:codeIndex.clearIndexDataButton")}
+										</VSCodeButton>
+									</AlertDialogTrigger>
+									<AlertDialogContent>
+										<AlertDialogHeader>
+											<AlertDialogTitle>
+												{t("settings:codeIndex.clearDataDialog.title")}
+											</AlertDialogTitle>
+											<AlertDialogDescription>
+												{t("settings:codeIndex.clearDataDialog.description")}
+											</AlertDialogDescription>
+										</AlertDialogHeader>
+										<AlertDialogFooter>
+											<AlertDialogCancel>
+												{t("settings:codeIndex.clearDataDialog.cancelButton")}
+											</AlertDialogCancel>
+											<AlertDialogAction
+												onClick={() => vscode.postMessage({ type: "clearIndexData" })}>
+												{t("settings:codeIndex.clearDataDialog.confirmButton")}
+											</AlertDialogAction>
+										</AlertDialogFooter>
+									</AlertDialogContent>
+								</AlertDialog>
+							)}
+						</div>
+
+						<VSCodeButton
+							onClick={handleSaveSettings}
+							disabled={!hasUnsavedChanges || saveStatus === "saving"}>
+							{saveStatus === "saving"
+								? t("settings:codeIndex.saving")
+								: t("settings:codeIndex.saveSettings")}
+						</VSCodeButton>
+					</div>
+
+					{/* Save Status Messages */}
+					{saveStatus === "error" && (
+						<div className="mt-2">
+							<span className="text-sm text-red-600 block">
+								{saveError || t("settings:codeIndex.saveError")}
+							</span>
+						</div>
+					)}
+				</div>
+			</PopoverContent>
+		</Popover>
+	)
+}

+ 19 - 29
webview-ui/src/components/chat/IndexingStatusBadge.tsx

@@ -3,6 +3,7 @@ import { cn } from "@src/lib/utils"
 import { vscode } from "@src/utils/vscode"
 import { useAppTranslation } from "@/i18n/TranslationContext"
 import { useTooltip } from "@/hooks/useTooltip"
+import { CodeIndexPopover } from "./CodeIndexPopover"
 import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"
 
 interface IndexingStatusDotProps {
@@ -66,18 +67,6 @@ export const IndexingStatusDot: React.FC<IndexingStatusDotProps> = ({ className
 		}
 	}
 
-	// Navigate to settings when clicked
-	const handleClick = () => {
-		window.postMessage(
-			{
-				type: "action",
-				action: "settingsButtonClicked",
-				values: { section: "experimental" },
-			},
-			"*",
-		)
-	}
-
 	const handleMouseEnterButton = () => {
 		setIsHovered(true)
 		handleMouseEnter()
@@ -115,25 +104,26 @@ export const IndexingStatusDot: React.FC<IndexingStatusDotProps> = ({ className
 
 	return (
 		<div className={cn("relative inline-block", className)}>
-			<button
-				onClick={handleClick}
-				onMouseEnter={handleMouseEnterButton}
-				onMouseLeave={handleMouseLeaveButton}
-				className={cn(
-					"flex items-center justify-center w-7 h-7 rounded-md",
-					"bg-transparent hover:bg-vscode-list-hoverBackground",
-					"cursor-pointer transition-all duration-200",
-					"opacity-85 hover:opacity-100 relative",
-				)}
-				aria-label={getTooltipText()}>
-				{/* Status dot */}
-				<span
+			<CodeIndexPopover indexingStatus={indexingStatus}>
+				<button
+					onMouseEnter={handleMouseEnterButton}
+					onMouseLeave={handleMouseLeaveButton}
 					className={cn(
-						"inline-block w-2 h-2 rounded-full relative z-10 transition-colors duration-200",
-						getStatusColorClass(),
+						"flex items-center justify-center w-7 h-7 rounded-md",
+						"bg-transparent hover:bg-vscode-list-hoverBackground",
+						"cursor-pointer transition-all duration-200",
+						"opacity-85 hover:opacity-100 relative",
 					)}
-				/>
-			</button>
+					aria-label={getTooltipText()}>
+					{/* Status dot */}
+					<span
+						className={cn(
+							"inline-block w-2 h-2 rounded-full relative z-10 transition-colors duration-200",
+							getStatusColorClass(),
+						)}
+					/>
+				</button>
+			</CodeIndexPopover>
 			{showTooltip && (
 				<div
 					className={cn(

+ 6 - 9
webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx

@@ -45,6 +45,7 @@ vi.mock("react-i18next", () => ({
 		type: "3rdParty",
 		init: vi.fn(),
 	},
+	Trans: ({ children }: { children: React.ReactNode }) => <>{children}</>,
 }))
 
 // Mock vscode API
@@ -124,7 +125,7 @@ describe("IndexingStatusDot", () => {
 		expect(button).toHaveAttribute("aria-label", "Index ready")
 	})
 
-	it("posts settingsButtonClicked message when clicked", () => {
+	it("opens popover when clicked", () => {
 		// Mock window.postMessage
 		const postMessageSpy = vi.spyOn(window, "postMessage")
 
@@ -133,14 +134,10 @@ describe("IndexingStatusDot", () => {
 		const button = screen.getByRole("button")
 		fireEvent.click(button)
 
-		expect(postMessageSpy).toHaveBeenCalledWith(
-			{
-				type: "action",
-				action: "settingsButtonClicked",
-				values: { section: "experimental" },
-			},
-			"*",
-		)
+		// The button should be clickable and not post a message anymore
+		// Since the popover is rendered conditionally, we just verify the button is clickable
+		// and doesn't throw any errors. The actual popover rendering is tested in integration tests.
+		expect(postMessageSpy).not.toHaveBeenCalled()
 
 		postMessageSpy.mockRestore()
 	})

+ 0 - 619
webview-ui/src/components/settings/CodeIndexSettings.tsx

@@ -1,619 +0,0 @@
-import React, { useState, useEffect } from "react"
-import { z } from "zod"
-import * as ProgressPrimitive from "@radix-ui/react-progress"
-import { VSCodeCheckbox, VSCodeTextField, VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
-import { Trans } from "react-i18next"
-
-import { CodebaseIndexConfig, CodebaseIndexModels, ProviderSettings } from "@roo-code/types"
-
-import { EmbedderProvider } from "@roo/embeddingModels"
-import { SEARCH_MIN_SCORE } from "../../../../src/services/code-index/constants"
-
-import { vscode } from "@src/utils/vscode"
-import { useAppTranslation } from "@src/i18n/TranslationContext"
-import { buildDocLink } from "@src/utils/docLinks"
-
-import {
-	Select,
-	SelectContent,
-	SelectItem,
-	SelectTrigger,
-	SelectValue,
-	AlertDialog,
-	AlertDialogAction,
-	AlertDialogCancel,
-	AlertDialogContent,
-	AlertDialogDescription,
-	AlertDialogFooter,
-	AlertDialogHeader,
-	AlertDialogTitle,
-	AlertDialogTrigger,
-	Slider,
-	Button,
-	Tooltip,
-	TooltipContent,
-	TooltipProvider,
-	TooltipTrigger,
-} from "@src/components/ui"
-
-import { SetCachedStateField } from "./types"
-
-interface CodeIndexSettingsProps {
-	codebaseIndexModels: CodebaseIndexModels | undefined
-	codebaseIndexConfig: CodebaseIndexConfig | undefined
-	apiConfiguration: ProviderSettings
-	setCachedStateField: SetCachedStateField<"codebaseIndexConfig">
-	setApiConfigurationField: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
-	areSettingsCommitted: boolean
-}
-
-import type { IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"
-
-export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
-	codebaseIndexModels,
-	codebaseIndexConfig,
-	apiConfiguration,
-	setCachedStateField,
-	setApiConfigurationField,
-	areSettingsCommitted,
-}) => {
-	const { t } = useAppTranslation()
-	const DEFAULT_QDRANT_URL = "http://localhost:6333"
-	const [indexingStatus, setIndexingStatus] = useState({
-		systemStatus: "Standby",
-		message: "",
-		processedItems: 0,
-		totalItems: 0,
-		currentItemUnit: "items",
-	})
-	const [advancedExpanded, setAdvancedExpanded] = useState(false)
-
-	// Safely calculate available models for current provider
-	const currentProvider = codebaseIndexConfig?.codebaseIndexEmbedderProvider
-	const modelsForProvider =
-		currentProvider === "openai" || currentProvider === "openai-compatible"
-			? (codebaseIndexModels?.openai ?? codebaseIndexModels?.["openai-compatible"])
-			: codebaseIndexModels?.[currentProvider as keyof typeof codebaseIndexModels]
-	const availableModelIds = Object.keys(modelsForProvider || {})
-
-	useEffect(() => {
-		// Request initial indexing status from extension host
-		vscode.postMessage({ type: "requestIndexingStatus" })
-
-		// Set up interval for periodic status updates
-
-		// Set up message listener for status updates
-		const handleMessage = (event: MessageEvent<IndexingStatusUpdateMessage>) => {
-			if (event.data.type === "indexingStatusUpdate") {
-				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",
-				})
-			}
-		}
-
-		window.addEventListener("message", handleMessage)
-
-		// Cleanup function
-		return () => {
-			window.removeEventListener("message", handleMessage)
-		}
-	}, [codebaseIndexConfig, codebaseIndexModels])
-
-	/**
-	 * Determines the appropriate model ID when changing providers
-	 */
-	function getModelIdForProvider(
-		newProvider: EmbedderProvider,
-		currentProvider: EmbedderProvider | undefined,
-		currentModelId: string | undefined,
-		availableModels: CodebaseIndexModels | undefined,
-	): string {
-		if (newProvider === currentProvider && currentModelId) {
-			return currentModelId
-		}
-
-		const models = availableModels?.[newProvider]
-		const modelIds = models ? Object.keys(models) : []
-
-		if (currentModelId && modelIds.includes(currentModelId)) {
-			return currentModelId
-		}
-
-		const selectedModel = modelIds.length > 0 ? modelIds[0] : ""
-		return selectedModel
-	}
-
-	function validateIndexingConfig(config: CodebaseIndexConfig | undefined, apiConfig: ProviderSettings): boolean {
-		if (!config) return false
-
-		const baseSchema = z.object({
-			codebaseIndexQdrantUrl: z.string().url("Qdrant URL must be a valid URL"),
-			codebaseIndexEmbedderModelId: z.string().min(1, "Model ID is required"),
-		})
-
-		const providerSchemas = {
-			openai: baseSchema.extend({
-				codebaseIndexEmbedderProvider: z.literal("openai"),
-				codeIndexOpenAiKey: z.string().min(1, "OpenAI key is required"),
-			}),
-			ollama: baseSchema.extend({
-				codebaseIndexEmbedderProvider: z.literal("ollama"),
-				codebaseIndexEmbedderBaseUrl: z.string().url("Ollama URL must be a valid URL"),
-			}),
-			"openai-compatible": baseSchema.extend({
-				codebaseIndexEmbedderProvider: z.literal("openai-compatible"),
-				codebaseIndexOpenAiCompatibleBaseUrl: z.string().url("Base URL must be a valid URL"),
-				codebaseIndexOpenAiCompatibleApiKey: z.string().min(1, "API key is required"),
-				codebaseIndexOpenAiCompatibleModelDimension: z
-					.number()
-					.int("Dimension must be an integer")
-					.positive("Dimension must be a positive number")
-					.optional(),
-			}),
-			gemini: baseSchema.extend({
-				codebaseIndexEmbedderProvider: z.literal("gemini"),
-				codebaseIndexGeminiApiKey: z.string().min(1, "Gemini API key is required"),
-			}),
-		}
-
-		try {
-			const schema =
-				config.codebaseIndexEmbedderProvider === "openai"
-					? providerSchemas.openai
-					: config.codebaseIndexEmbedderProvider === "ollama"
-						? providerSchemas.ollama
-						: config.codebaseIndexEmbedderProvider === "gemini"
-							? providerSchemas.gemini
-							: providerSchemas["openai-compatible"]
-
-			schema.parse({
-				...config,
-				codeIndexOpenAiKey: apiConfig.codeIndexOpenAiKey,
-				codebaseIndexOpenAiCompatibleBaseUrl: apiConfig.codebaseIndexOpenAiCompatibleBaseUrl,
-				codebaseIndexOpenAiCompatibleApiKey: apiConfig.codebaseIndexOpenAiCompatibleApiKey,
-				codebaseIndexOpenAiCompatibleModelDimension: apiConfig.codebaseIndexOpenAiCompatibleModelDimension,
-				codebaseIndexGeminiApiKey: apiConfig.codebaseIndexGeminiApiKey,
-			})
-			return true
-		} catch {
-			return false
-		}
-	}
-
-	const progressPercentage =
-		indexingStatus.totalItems > 0
-			? (indexingStatus.processedItems / indexingStatus.totalItems) * 100
-			: indexingStatus.totalItems === 0 && indexingStatus.processedItems === 0
-				? 100
-				: 0
-
-	const transformValue = 100 - progressPercentage
-	const transformStyleString = `translateX(-${transformValue}%)`
-
-	return (
-		<>
-			<div>
-				<div className="flex items-center gap-2">
-					<VSCodeCheckbox
-						checked={codebaseIndexConfig?.codebaseIndexEnabled}
-						onChange={(e: any) =>
-							setCachedStateField("codebaseIndexConfig", {
-								...codebaseIndexConfig,
-								codebaseIndexEnabled: e.target.checked,
-							})
-						}>
-						<span className="font-medium">{t("settings:codeIndex.enableLabel")}</span>
-					</VSCodeCheckbox>
-				</div>
-				<p className="text-vscode-descriptionForeground text-sm mt-0">
-					<Trans i18nKey="settings:codeIndex.enableDescription">
-						<VSCodeLink
-							href={buildDocLink("features/experimental/codebase-indexing", "settings")}
-							style={{ display: "inline" }}></VSCodeLink>
-					</Trans>
-				</p>
-			</div>
-
-			{codebaseIndexConfig?.codebaseIndexEnabled && (
-				<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
-					<div className="text-sm text-vscode-descriptionForeground">
-						<span
-							className={`
-								inline-block w-3 h-3 rounded-full mr-2
-								${
-									indexingStatus.systemStatus === "Standby"
-										? "bg-gray-400"
-										: indexingStatus.systemStatus === "Indexing"
-											? "bg-yellow-500 animate-pulse"
-											: indexingStatus.systemStatus === "Indexed"
-												? "bg-green-500"
-												: indexingStatus.systemStatus === "Error"
-													? "bg-red-500"
-													: "bg-gray-400"
-								}
-							`}></span>
-						{indexingStatus.systemStatus}
-						{indexingStatus.message ? ` - ${indexingStatus.message}` : ""}
-					</div>
-
-					{indexingStatus.systemStatus === "Indexing" && (
-						<div className="space-y-1">
-							<ProgressPrimitive.Root
-								className="relative h-2 w-full overflow-hidden rounded-full bg-secondary"
-								value={progressPercentage}>
-								<ProgressPrimitive.Indicator
-									className="h-full w-full flex-1 bg-primary transition-transform duration-300 ease-in-out"
-									style={{
-										transform: transformStyleString,
-									}}
-								/>
-							</ProgressPrimitive.Root>
-						</div>
-					)}
-
-					<div className="flex items-center gap-4 font-bold">
-						<div>{t("settings:codeIndex.providerLabel")}</div>
-					</div>
-					<div>
-						<div className="flex items-center gap-2">
-							<Select
-								value={codebaseIndexConfig?.codebaseIndexEmbedderProvider || "openai"}
-								onValueChange={(value) => {
-									const newProvider = value as EmbedderProvider
-									const currentProvider = codebaseIndexConfig?.codebaseIndexEmbedderProvider
-									const currentModelId = codebaseIndexConfig?.codebaseIndexEmbedderModelId
-
-									const modelIdToUse = getModelIdForProvider(
-										newProvider,
-										currentProvider,
-										currentModelId,
-										codebaseIndexModels,
-									)
-
-									if (codebaseIndexConfig) {
-										setCachedStateField("codebaseIndexConfig", {
-											...codebaseIndexConfig,
-											codebaseIndexEmbedderProvider: newProvider,
-											codebaseIndexEmbedderModelId: modelIdToUse,
-										})
-									}
-								}}>
-								<SelectTrigger className="w-full">
-									<SelectValue placeholder={t("settings:codeIndex.selectProviderPlaceholder")} />
-								</SelectTrigger>
-								<SelectContent>
-									<SelectItem value="openai">{t("settings:codeIndex.openaiProvider")}</SelectItem>
-									<SelectItem value="ollama">{t("settings:codeIndex.ollamaProvider")}</SelectItem>
-									<SelectItem value="openai-compatible">
-										{t("settings:codeIndex.openaiCompatibleProvider")}
-									</SelectItem>
-									<SelectItem value="gemini">{t("settings:codeIndex.geminiProvider")}</SelectItem>
-								</SelectContent>
-							</Select>
-						</div>
-					</div>
-
-					{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai" && (
-						<div className="flex flex-col gap-3">
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.openaiKeyLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									type="password"
-									value={apiConfiguration.codeIndexOpenAiKey || ""}
-									onInput={(e: any) => setApiConfigurationField("codeIndexOpenAiKey", e.target.value)}
-									style={{ width: "100%" }}></VSCodeTextField>
-							</div>
-						</div>
-					)}
-
-					{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai-compatible" && (
-						<div className="flex flex-col gap-3">
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.openaiCompatibleBaseUrlLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									value={apiConfiguration.codebaseIndexOpenAiCompatibleBaseUrl || ""}
-									onInput={(e: any) =>
-										setApiConfigurationField("codebaseIndexOpenAiCompatibleBaseUrl", e.target.value)
-									}
-									style={{ width: "100%" }}></VSCodeTextField>
-							</div>
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.openaiCompatibleApiKeyLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									type="password"
-									value={apiConfiguration.codebaseIndexOpenAiCompatibleApiKey || ""}
-									onInput={(e: any) =>
-										setApiConfigurationField("codebaseIndexOpenAiCompatibleApiKey", e.target.value)
-									}
-									style={{ width: "100%" }}></VSCodeTextField>
-							</div>
-						</div>
-					)}
-
-					<div className="flex items-center gap-4 font-bold">
-						<div>{t("settings:codeIndex.modelLabel")}</div>
-					</div>
-					<div>
-						<div className="flex items-center gap-2">
-							{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai-compatible" ? (
-								<VSCodeTextField
-									value={codebaseIndexConfig?.codebaseIndexEmbedderModelId || ""}
-									onInput={(e: any) =>
-										setCachedStateField("codebaseIndexConfig", {
-											...codebaseIndexConfig,
-											codebaseIndexEmbedderModelId: e.target.value,
-										})
-									}
-									placeholder="Enter custom model ID"
-									style={{ width: "100%" }}></VSCodeTextField>
-							) : (
-								<Select
-									value={codebaseIndexConfig?.codebaseIndexEmbedderModelId || ""}
-									onValueChange={(value) =>
-										setCachedStateField("codebaseIndexConfig", {
-											...codebaseIndexConfig,
-											codebaseIndexEmbedderModelId: value,
-										})
-									}>
-									<SelectTrigger className="w-full">
-										<SelectValue placeholder={t("settings:codeIndex.selectModelPlaceholder")} />
-									</SelectTrigger>
-									<SelectContent>
-										{availableModelIds.map((modelId) => (
-											<SelectItem key={modelId} value={modelId}>
-												{modelId}
-											</SelectItem>
-										))}
-									</SelectContent>
-								</Select>
-							)}
-						</div>
-					</div>
-
-					{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai-compatible" && (
-						<div className="flex flex-col gap-3">
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.openaiCompatibleModelDimensionLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									type="text"
-									value={
-										apiConfiguration.codebaseIndexOpenAiCompatibleModelDimension?.toString() || ""
-									}
-									onInput={(e: any) => {
-										const value = e.target.value
-										if (value === "") {
-											setApiConfigurationField(
-												"codebaseIndexOpenAiCompatibleModelDimension",
-												undefined,
-											)
-										} else {
-											const parsedValue = parseInt(value, 10)
-											if (!isNaN(parsedValue)) {
-												setApiConfigurationField(
-													"codebaseIndexOpenAiCompatibleModelDimension",
-													parsedValue,
-												)
-											}
-										}
-									}}
-									placeholder={t("settings:codeIndex.openaiCompatibleModelDimensionPlaceholder")}
-									style={{ width: "100%" }}></VSCodeTextField>
-								<p className="text-vscode-descriptionForeground text-sm mt-1">
-									{t("settings:codeIndex.openaiCompatibleModelDimensionDescription")}
-								</p>
-							</div>
-						</div>
-					)}
-
-					{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "ollama" && (
-						<div className="flex flex-col gap-3">
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.ollamaUrlLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									value={codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || ""}
-									onInput={(e: any) =>
-										setCachedStateField("codebaseIndexConfig", {
-											...codebaseIndexConfig,
-											codebaseIndexEmbedderBaseUrl: e.target.value,
-										})
-									}
-									style={{ width: "100%" }}></VSCodeTextField>
-							</div>
-						</div>
-					)}
-
-					{codebaseIndexConfig?.codebaseIndexEmbedderProvider === "gemini" && (
-						<div className="flex flex-col gap-3">
-							<div className="flex items-center gap-4 font-bold">
-								<div>{t("settings:codeIndex.geminiApiKeyLabel")}</div>
-							</div>
-							<div>
-								<VSCodeTextField
-									type="password"
-									value={apiConfiguration.codebaseIndexGeminiApiKey || ""}
-									onInput={(e: any) =>
-										setApiConfigurationField("codebaseIndexGeminiApiKey", e.target.value)
-									}
-									placeholder={t("settings:codeIndex.geminiApiKeyPlaceholder")}
-									style={{ width: "100%" }}></VSCodeTextField>
-							</div>
-						</div>
-					)}
-
-					<div className="flex flex-col gap-3">
-						<div className="flex items-center gap-4 font-bold">
-							<div>{t("settings:codeIndex.qdrantUrlLabel")}</div>
-						</div>
-						<div>
-							<VSCodeTextField
-								value={codebaseIndexConfig.codebaseIndexQdrantUrl ?? DEFAULT_QDRANT_URL}
-								placeholder={DEFAULT_QDRANT_URL}
-								onInput={(e: any) =>
-									setCachedStateField("codebaseIndexConfig", {
-										...codebaseIndexConfig,
-										codebaseIndexQdrantUrl: e.target.value,
-									})
-								}
-								onBlur={(e: any) => {
-									// Set default value if field is empty on blur
-									if (!e.target.value) {
-										setCachedStateField("codebaseIndexConfig", {
-											...codebaseIndexConfig,
-											codebaseIndexQdrantUrl: DEFAULT_QDRANT_URL,
-										})
-									}
-								}}
-								style={{ width: "100%" }}></VSCodeTextField>
-						</div>
-					</div>
-
-					<div className="flex flex-col gap-3">
-						<div className="flex items-center gap-4 font-bold">
-							<div>{t("settings:codeIndex.qdrantKeyLabel")}</div>
-						</div>
-						<div>
-							<VSCodeTextField
-								type="password"
-								value={apiConfiguration.codeIndexQdrantApiKey}
-								onInput={(e: any) => setApiConfigurationField("codeIndexQdrantApiKey", e.target.value)}
-								style={{ width: "100%" }}></VSCodeTextField>
-						</div>
-					</div>
-
-					{(!areSettingsCommitted || !validateIndexingConfig(codebaseIndexConfig, apiConfiguration)) && (
-						<p className="text-sm text-vscode-descriptionForeground mb-2">
-							{t("settings:codeIndex.unsavedSettingsMessage")}
-						</p>
-					)}
-
-					<div className="flex gap-2">
-						{(indexingStatus.systemStatus === "Error" || indexingStatus.systemStatus === "Standby") && (
-							<VSCodeButton
-								onClick={() => vscode.postMessage({ type: "startIndexing" })}
-								disabled={
-									!areSettingsCommitted ||
-									!validateIndexingConfig(codebaseIndexConfig, apiConfiguration)
-								}>
-								{t("settings:codeIndex.startIndexingButton")}
-							</VSCodeButton>
-						)}
-						{(indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && (
-							<AlertDialog>
-								<AlertDialogTrigger asChild>
-									<VSCodeButton appearance="secondary">
-										{t("settings:codeIndex.clearIndexDataButton")}
-									</VSCodeButton>
-								</AlertDialogTrigger>
-								<AlertDialogContent>
-									<AlertDialogHeader>
-										<AlertDialogTitle>
-											{t("settings:codeIndex.clearDataDialog.title")}
-										</AlertDialogTitle>
-										<AlertDialogDescription>
-											{t("settings:codeIndex.clearDataDialog.description")}
-										</AlertDialogDescription>
-									</AlertDialogHeader>
-									<AlertDialogFooter>
-										<AlertDialogCancel>
-											{t("settings:codeIndex.clearDataDialog.cancelButton")}
-										</AlertDialogCancel>
-										<AlertDialogAction
-											onClick={() => vscode.postMessage({ type: "clearIndexData" })}>
-											{t("settings:codeIndex.clearDataDialog.confirmButton")}
-										</AlertDialogAction>
-									</AlertDialogFooter>
-								</AlertDialogContent>
-							</AlertDialog>
-						)}
-					</div>
-
-					{/* Advanced Configuration Section */}
-					<div className="mt-4">
-						<button
-							onClick={() => setAdvancedExpanded(!advancedExpanded)}
-							className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
-							aria-expanded={advancedExpanded}>
-							<span
-								className={`codicon codicon-${advancedExpanded ? "chevron-down" : "chevron-right"} mr-1`}></span>
-							<span>{t("settings:codeIndex.advancedConfigLabel")}</span>
-						</button>
-
-						{advancedExpanded && (
-							<div className="text-xs text-vscode-descriptionForeground mt-2 ml-5">
-								<div className="flex flex-col gap-3">
-									<div>
-										<span className="block font-medium mb-1">
-											{t("settings:codeIndex.searchMinScoreLabel")}
-										</span>
-										<div className="flex items-center gap-2">
-											<Slider
-												min={0}
-												max={1}
-												step={0.05}
-												value={[
-													codebaseIndexConfig.codebaseIndexSearchMinScore ?? SEARCH_MIN_SCORE,
-												]}
-												onValueChange={([value]) =>
-													setCachedStateField("codebaseIndexConfig", {
-														...codebaseIndexConfig,
-														codebaseIndexSearchMinScore: value,
-													})
-												}
-												data-testid="search-min-score-slider"
-												aria-label={t("settings:codeIndex.searchMinScoreLabel")}
-											/>
-											<span className="w-10">
-												{(
-													codebaseIndexConfig.codebaseIndexSearchMinScore ?? SEARCH_MIN_SCORE
-												).toFixed(2)}
-											</span>
-											<TooltipProvider>
-												<Tooltip>
-													<TooltipTrigger asChild>
-														<Button
-															variant="ghost"
-															size="sm"
-															onClick={() =>
-																setCachedStateField("codebaseIndexConfig", {
-																	...codebaseIndexConfig,
-																	codebaseIndexSearchMinScore: SEARCH_MIN_SCORE,
-																})
-															}
-															className="h-8 w-8 p-0"
-															data-testid="search-min-score-reset-button">
-															<span className="codicon codicon-debug-restart w-4 h-4" />
-														</Button>
-													</TooltipTrigger>
-													<TooltipContent>
-														<p>{t("settings:codeIndex.searchMinScoreResetTooltip")}</p>
-													</TooltipContent>
-												</Tooltip>
-											</TooltipProvider>
-										</div>
-										<div className="text-vscode-descriptionForeground text-sm mt-1">
-											{t("settings:codeIndex.searchMinScoreDescription")}
-										</div>
-									</div>
-								</div>
-							</div>
-						)}
-					</div>
-				</div>
-			)}
-		</>
-	)
-}

+ 28 - 20
webview-ui/src/components/settings/ExperimentalSettings.tsx

@@ -1,41 +1,40 @@
 import { HTMLAttributes } from "react"
 import { FlaskConical } from "lucide-react"
+import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { Trans } from "react-i18next"
 
-import type { Experiments, CodebaseIndexConfig, CodebaseIndexModels, ProviderSettings } from "@roo-code/types"
+import type { Experiments, CodebaseIndexConfig, CodebaseIndexModels } from "@roo-code/types"
 
 import { EXPERIMENT_IDS, experimentConfigsMap } from "@roo/experiments"
 
-import { ExtensionStateContextType } from "@src/context/ExtensionStateContext"
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { cn } from "@src/lib/utils"
+import { buildDocLink } from "@src/utils/docLinks"
 
-import { SetCachedStateField, SetExperimentEnabled } from "./types"
+import { SetExperimentEnabled } from "./types"
 import { SectionHeader } from "./SectionHeader"
 import { Section } from "./Section"
 import { ExperimentalFeature } from "./ExperimentalFeature"
-import { CodeIndexSettings } from "./CodeIndexSettings"
+import { SetCachedStateField } from "./types"
 
 type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
 	experiments: Experiments
 	setExperimentEnabled: SetExperimentEnabled
-	setCachedStateField: SetCachedStateField<"codebaseIndexConfig">
 	// CodeIndexSettings props
 	codebaseIndexModels: CodebaseIndexModels | undefined
 	codebaseIndexConfig: CodebaseIndexConfig | undefined
-	apiConfiguration: ProviderSettings
-	setApiConfigurationField: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
-	areSettingsCommitted: boolean
+	// For codebase index enabled toggle
+	codebaseIndexEnabled?: boolean
+	setCachedStateField?: SetCachedStateField<any>
 }
 
 export const ExperimentalSettings = ({
 	experiments,
 	setExperimentEnabled,
-	setCachedStateField,
 	codebaseIndexModels,
 	codebaseIndexConfig,
-	apiConfiguration,
-	setApiConfigurationField,
-	areSettingsCommitted,
+	codebaseIndexEnabled,
+	setCachedStateField,
 	className,
 	...props
 }: ExperimentalSettingsProps) => {
@@ -81,14 +80,23 @@ export const ExperimentalSettings = ({
 						)
 					})}
 
-				<CodeIndexSettings
-					codebaseIndexModels={codebaseIndexModels}
-					codebaseIndexConfig={codebaseIndexConfig}
-					apiConfiguration={apiConfiguration}
-					setCachedStateField={setCachedStateField as SetCachedStateField<keyof ExtensionStateContextType>}
-					setApiConfigurationField={setApiConfigurationField}
-					areSettingsCommitted={areSettingsCommitted}
-				/>
+				{/* Codebase Indexing Enable/Disable Toggle */}
+				<div className="mt-4">
+					<div className="flex items-center gap-2">
+						<VSCodeCheckbox
+							checked={codebaseIndexEnabled || false}
+							onChange={(e: any) => setCachedStateField?.("codebaseIndexEnabled", e.target.checked)}>
+							<span className="font-medium">{t("settings:codeIndex.enableLabel")}</span>
+						</VSCodeCheckbox>
+					</div>
+					<p className="text-vscode-descriptionForeground text-sm mt-1 ml-6">
+						<Trans i18nKey="settings:codeIndex.enableDescription">
+							<VSCodeLink
+								href={buildDocLink("features/experimental/codebase-indexing", "settings")}
+								style={{ display: "inline" }}></VSCodeLink>
+						</Trans>
+					</p>
+				</div>
 			</Section>
 		</div>
 	)

+ 4 - 5
webview-ui/src/components/settings/SettingsView.tsx

@@ -320,7 +320,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 			vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} })
 			vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
 			vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
-			vscode.postMessage({ type: "codebaseIndexConfig", values: codebaseIndexConfig })
+			// Code index config is now handled separately in CodeIndexSettings
+			// vscode.postMessage({ type: "codebaseIndexConfig", values: codebaseIndexConfig })
 			vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
 			setChangeDetected(false)
 		}
@@ -689,12 +690,10 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 						<ExperimentalSettings
 							setExperimentEnabled={setExperimentEnabled}
 							experiments={experiments}
-							setCachedStateField={setCachedStateField}
 							codebaseIndexModels={codebaseIndexModels}
 							codebaseIndexConfig={codebaseIndexConfig}
-							apiConfiguration={apiConfiguration}
-							setApiConfigurationField={setApiConfigurationField}
-							areSettingsCommitted={!isChangeDetected}
+							codebaseIndexEnabled={codebaseIndexConfig?.codebaseIndexEnabled}
+							setCachedStateField={setCachedStateField}
 						/>
 					)}
 

+ 0 - 975
webview-ui/src/components/settings/__tests__/CodeIndexSettings.spec.tsx

@@ -1,975 +0,0 @@
-// npx vitest src/components/settings/__tests__/CodeIndexSettings.spec.tsx
-
-import { render, screen, fireEvent } from "@/utils/test-utils"
-import userEvent from "@testing-library/user-event"
-
-import { CodeIndexSettings } from "../CodeIndexSettings"
-
-import { vscode } from "@src/utils/vscode"
-
-vi.mock("@src/utils/vscode", () => ({
-	vscode: {
-		postMessage: vi.fn(),
-	},
-}))
-
-vi.mock("@src/i18n/TranslationContext", () => ({
-	useAppTranslation: () => ({
-		t: (key: string) => {
-			const translations: Record<string, string> = {
-				"settings:codeIndex.providerLabel": "Provider",
-				"settings:codeIndex.selectProviderPlaceholder": "Select provider",
-				"settings:codeIndex.openaiProvider": "OpenAI",
-				"settings:codeIndex.ollamaProvider": "Ollama",
-				"settings:codeIndex.openaiCompatibleProvider": "OpenAI Compatible",
-				"settings:codeIndex.openaiKeyLabel": "OpenAI API Key",
-				"settings:codeIndex.openaiCompatibleBaseUrlLabel": "Base URL",
-				"settings:codeIndex.openaiCompatibleApiKeyLabel": "API Key",
-				"settings:codeIndex.openaiCompatibleModelDimensionLabel": "Embedding Dimension",
-				"settings:codeIndex.openaiCompatibleModelDimensionPlaceholder": "Enter dimension (e.g., 1536)",
-				"settings:codeIndex.openaiCompatibleModelDimensionDescription": "The dimension of the embedding model",
-				"settings:codeIndex.modelLabel": "Model",
-				"settings:codeIndex.selectModelPlaceholder": "Select model",
-				"settings:codeIndex.qdrantUrlLabel": "Qdrant URL",
-				"settings:codeIndex.qdrantApiKeyLabel": "Qdrant API Key",
-				"settings:codeIndex.ollamaUrlLabel": "Ollama URL",
-				"settings:codeIndex.qdrantKeyLabel": "Qdrant API Key",
-				"settings:codeIndex.enableLabel": "Enable Code Index",
-				"settings:codeIndex.enableDescription": "Enable semantic search across your codebase",
-				"settings:codeIndex.unsavedSettingsMessage": "Please save settings before indexing",
-				"settings:codeIndex.startIndexingButton": "Start Indexing",
-				"settings:codeIndex.clearIndexDataButton": "Clear Index Data",
-				"settings:codeIndex.clearDataDialog.title": "Clear Index Data",
-				"settings:codeIndex.clearDataDialog.description": "This will remove all indexed data",
-				"settings:codeIndex.clearDataDialog.cancelButton": "Cancel",
-				"settings:codeIndex.clearDataDialog.confirmButton": "Confirm",
-				"settings:codeIndex.searchMinScoreLabel": "Search Score Threshold",
-				"settings:codeIndex.searchMinScoreDescription":
-					"Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.",
-				"settings:codeIndex.searchMinScoreResetTooltip": "Reset to default value (0.4)",
-				"settings:codeIndex.advancedConfigLabel": "Advanced Configuration",
-			}
-			return translations[key] || key
-		},
-	}),
-}))
-
-vi.mock("react-i18next", () => ({
-	Trans: ({ children }: any) => <div>{children}</div>,
-}))
-
-vi.mock("@src/utils/docLinks", () => ({
-	buildDocLink: vi.fn(() => "https://docs.example.com"),
-}))
-
-vi.mock("@src/components/ui", () => ({
-	Select: ({ children, value, onValueChange }: any) => (
-		<div data-testid="select" data-value={value}>
-			<button onClick={() => onValueChange && onValueChange("test-change")}>{value}</button>
-			{children}
-		</div>
-	),
-	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
-	SelectItem: ({ children, value }: any) => (
-		<div data-testid={`select-item-${value}`} data-value={value}>
-			{children}
-		</div>
-	),
-	SelectTrigger: ({ children }: any) => <div data-testid="select-trigger">{children}</div>,
-	SelectValue: ({ placeholder }: any) => <div data-testid="select-value">{placeholder}</div>,
-	AlertDialog: ({ children }: any) => <div data-testid="alert-dialog">{children}</div>,
-	AlertDialogAction: ({ children, onClick }: any) => (
-		<button data-testid="alert-dialog-action" onClick={onClick}>
-			{children}
-		</button>
-	),
-	AlertDialogCancel: ({ children }: any) => <button data-testid="alert-dialog-cancel">{children}</button>,
-	AlertDialogContent: ({ children }: any) => <div data-testid="alert-dialog-content">{children}</div>,
-	AlertDialogDescription: ({ children }: any) => <div data-testid="alert-dialog-description">{children}</div>,
-	AlertDialogFooter: ({ children }: any) => <div data-testid="alert-dialog-footer">{children}</div>,
-	AlertDialogHeader: ({ children }: any) => <div data-testid="alert-dialog-header">{children}</div>,
-	AlertDialogTitle: ({ children }: any) => <div data-testid="alert-dialog-title">{children}</div>,
-	AlertDialogTrigger: ({ children }: any) => <div data-testid="alert-dialog-trigger">{children}</div>,
-	Slider: ({ value, onValueChange, "data-testid": dataTestId }: any) => (
-		<input
-			type="range"
-			value={value[0]}
-			onChange={(e) => onValueChange && onValueChange([parseFloat(e.target.value)])}
-			data-testid={dataTestId}
-			role="slider"
-		/>
-	),
-	Button: ({ children, onClick, "data-testid": dataTestId, ...props }: any) => (
-		<button onClick={onClick} data-testid={dataTestId} {...props}>
-			{children}
-		</button>
-	),
-	Tooltip: ({ children }: any) => <div data-testid="tooltip">{children}</div>,
-	TooltipContent: ({ children }: any) => <div data-testid="tooltip-content">{children}</div>,
-	TooltipProvider: ({ children }: any) => <div data-testid="tooltip-provider">{children}</div>,
-	TooltipTrigger: ({ children }: any) => <div data-testid="tooltip-trigger">{children}</div>,
-}))
-
-vi.mock("@vscode/webview-ui-toolkit/react", () => ({
-	VSCodeCheckbox: ({ checked, onChange, children }: any) => (
-		<label>
-			<input
-				type="checkbox"
-				checked={checked}
-				onChange={(e) => onChange && onChange({ target: { checked: e.target.checked } })}
-				data-testid="vscode-checkbox"
-			/>
-			{children}
-		</label>
-	),
-	VSCodeTextField: ({ value, onInput, type, style, ...props }: any) => {
-		const handleChange = (e: any) => {
-			if (onInput) {
-				onInput({ target: { value: e.target.value } })
-			}
-		}
-		return (
-			<input
-				type={type || "text"}
-				value={value || ""}
-				onChange={handleChange}
-				onInput={handleChange}
-				data-testid="vscode-textfield"
-				tabIndex={0}
-				{...props}
-			/>
-		)
-	},
-	VSCodeButton: ({ children, onClick, appearance }: any) => (
-		<button onClick={onClick} data-testid="vscode-button" data-appearance={appearance}>
-			{children}
-		</button>
-	),
-	VSCodeLink: ({ children, href }: any) => (
-		<a href={href} data-testid="vscode-link">
-			{children}
-		</a>
-	),
-}))
-
-vi.mock("@radix-ui/react-progress", () => ({
-	Root: ({ children, value }: any) => (
-		<div data-testid="progress-root" data-value={value}>
-			{children}
-		</div>
-	),
-	Indicator: ({ style }: any) => <div data-testid="progress-indicator" style={style} />,
-}))
-
-describe("CodeIndexSettings", () => {
-	const mockSetCachedStateField = vi.fn()
-	const mockSetApiConfigurationField = vi.fn()
-
-	const defaultProps = {
-		codebaseIndexModels: {
-			openai: {
-				"text-embedding-3-small": { dimension: 1536 },
-				"text-embedding-3-large": { dimension: 3072 },
-			},
-			"openai-compatible": {
-				"text-embedding-3-small": { dimension: 1536 },
-				"custom-model": { dimension: 768 },
-			},
-		},
-		codebaseIndexConfig: {
-			codebaseIndexEnabled: true,
-			codebaseIndexEmbedderProvider: "openai" as const,
-			codebaseIndexEmbedderModelId: "text-embedding-3-small",
-			codebaseIndexQdrantUrl: "http://localhost:6333",
-			codebaseIndexSearchMinScore: 0.4,
-		},
-		apiConfiguration: {
-			codeIndexOpenAiKey: "",
-			codebaseIndexOpenAiCompatibleBaseUrl: "",
-			codebaseIndexOpenAiCompatibleApiKey: "",
-			codeIndexQdrantApiKey: "",
-		},
-		setCachedStateField: mockSetCachedStateField,
-		setApiConfigurationField: mockSetApiConfigurationField,
-		areSettingsCommitted: true,
-	}
-
-	beforeEach(() => {
-		vi.clearAllMocks()
-		// Mock window.addEventListener for message handling
-		Object.defineProperty(window, "addEventListener", {
-			value: vi.fn(),
-			writable: true,
-		})
-		Object.defineProperty(window, "removeEventListener", {
-			value: vi.fn(),
-			writable: true,
-		})
-	})
-
-	describe("Provider Selection", () => {
-		it("should render OpenAI Compatible provider option", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expect(screen.getByTestId("select-item-openai-compatible")).toBeInTheDocument()
-			expect(screen.getByText("OpenAI Compatible")).toBeInTheDocument()
-		})
-
-		it("should show OpenAI Compatible configuration fields when provider is selected", () => {
-			const propsWithOpenAICompatible = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-			expect(screen.getByText("Base URL")).toBeInTheDocument()
-			expect(screen.getByText("API Key")).toBeInTheDocument()
-			expect(screen.getAllByTestId("vscode-textfield")).toHaveLength(6) // Base URL, API Key, Embedding Dimension, Model ID, Qdrant URL, Qdrant Key (Search Min Score is now a slider)
-		})
-
-		it("should hide OpenAI Compatible fields when different provider is selected", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expect(screen.queryByText("Base URL")).not.toBeInTheDocument()
-			expect(screen.getByText("OpenAI API Key")).toBeInTheDocument()
-		})
-
-		/**
-		 * Test provider switching functionality
-		 */
-		// Provider selection functionality is tested through integration tests
-		// Removed complex provider switching test that was difficult to mock properly
-	})
-
-	describe("OpenAI Compatible Configuration", () => {
-		const openAICompatibleProps = {
-			...defaultProps,
-			codebaseIndexConfig: {
-				...defaultProps.codebaseIndexConfig,
-				codebaseIndexEmbedderProvider: "openai-compatible" as const,
-			},
-		}
-
-		it("should render base URL input field", () => {
-			render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-			const textFields = screen.getAllByTestId("vscode-textfield")
-			const baseUrlField = textFields.find(
-				(field) =>
-					field.getAttribute("value") ===
-					openAICompatibleProps.apiConfiguration.codebaseIndexOpenAiCompatibleBaseUrl,
-			)
-			expect(baseUrlField).toBeInTheDocument()
-		})
-
-		it("should render API key input field with password type", () => {
-			render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-			const passwordFields = screen
-				.getAllByTestId("vscode-textfield")
-				.filter((field) => field.getAttribute("type") === "password")
-			expect(passwordFields.length).toBeGreaterThan(0)
-		})
-
-		it("should call setApiConfigurationField when base URL changes", async () => {
-			render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-			// Find the Base URL field - it should be the first text field (not password) in the OpenAI Compatible section
-			const textFields = screen.getAllByTestId("vscode-textfield")
-			// Filter for text type fields (not password) and find the one with empty value (base URL field)
-			const textTypeFields = textFields.filter((field) => field.getAttribute("type") === "text")
-			const baseUrlField = textTypeFields[0] // First text field should be base URL
-
-			expect(baseUrlField).toBeDefined()
-
-			// Use fireEvent to trigger the change
-			fireEvent.change(baseUrlField!, { target: { value: "test" } })
-
-			// Check that setApiConfigurationField was called with the right parameter name (accepts any value)
-			expect(mockSetApiConfigurationField).toHaveBeenCalledWith("codebaseIndexOpenAiCompatibleBaseUrl", "test")
-		})
-
-		it("should call setApiConfigurationField when API key changes", async () => {
-			render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-			// Find the API Key field by looking for the text and then finding the password input
-			screen.getByText("API Key")
-			const passwordFields = screen
-				.getAllByTestId("vscode-textfield")
-				.filter((field) => field.getAttribute("type") === "password")
-			const apiKeyField = passwordFields[0] // First password field in the OpenAI Compatible section
-			expect(apiKeyField).toBeDefined()
-
-			// Use fireEvent to trigger the change
-			fireEvent.change(apiKeyField!, { target: { value: "test" } })
-
-			// Check that setApiConfigurationField was called with the right parameter name (accepts any value)
-			expect(mockSetApiConfigurationField).toHaveBeenCalledWith("codebaseIndexOpenAiCompatibleApiKey", "test")
-		})
-
-		it("should display current base URL value", () => {
-			const propsWithValues = {
-				...openAICompatibleProps,
-				apiConfiguration: {
-					...openAICompatibleProps.apiConfiguration,
-					codebaseIndexOpenAiCompatibleBaseUrl: "https://existing-api.example.com/v1",
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithValues} />)
-
-			const textField = screen.getByDisplayValue("https://existing-api.example.com/v1")
-			expect(textField).toBeInTheDocument()
-		})
-
-		it("should display current API key value", () => {
-			const propsWithValues = {
-				...openAICompatibleProps,
-				apiConfiguration: {
-					...openAICompatibleProps.apiConfiguration,
-					codebaseIndexOpenAiCompatibleApiKey: "existing-api-key",
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithValues} />)
-
-			const textField = screen.getByDisplayValue("existing-api-key")
-			expect(textField).toBeInTheDocument()
-		})
-
-		it("should display embedding dimension input field for OpenAI Compatible provider", () => {
-			const propsWithOpenAICompatible = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-			// Look for the embedding dimension label
-			expect(screen.getByText("Embedding Dimension")).toBeInTheDocument()
-		})
-
-		it("should hide embedding dimension input field for non-OpenAI Compatible providers", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			// Should not show embedding dimension for OpenAI provider
-			expect(screen.queryByText("Embedding Dimension")).not.toBeInTheDocument()
-		})
-
-		it("should call setApiConfigurationField when embedding dimension changes", async () => {
-			const propsWithOpenAICompatible = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-			// Find the embedding dimension input field by placeholder
-			const dimensionField = screen.getByPlaceholderText("Enter dimension (e.g., 1536)")
-			expect(dimensionField).toBeDefined()
-
-			// Use fireEvent to trigger the change
-			fireEvent.change(dimensionField!, { target: { value: "1024" } })
-
-			// Check that setApiConfigurationField was called with the right parameter name
-			expect(mockSetApiConfigurationField).toHaveBeenCalledWith(
-				"codebaseIndexOpenAiCompatibleModelDimension",
-				1024,
-			)
-		})
-
-		it("should display current embedding dimension value", () => {
-			const propsWithDimension = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-				apiConfiguration: {
-					...defaultProps.apiConfiguration,
-					codebaseIndexOpenAiCompatibleModelDimension: 2048,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithDimension} />)
-
-			const textField = screen.getByDisplayValue("2048")
-			expect(textField).toBeInTheDocument()
-		})
-
-		it("should handle empty embedding dimension value", () => {
-			const propsWithEmptyDimension = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-				apiConfiguration: {
-					...defaultProps.apiConfiguration,
-					codebaseIndexOpenAiCompatibleModelDimension: undefined,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithEmptyDimension} />)
-
-			const dimensionField = screen.getByPlaceholderText("Enter dimension (e.g., 1536)")
-			expect(dimensionField).toHaveValue("")
-		})
-
-		it("should validate embedding dimension input accepts only positive numbers", async () => {
-			const propsWithOpenAICompatible = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-			const dimensionField = screen.getByPlaceholderText("Enter dimension (e.g., 1536)")
-			expect(dimensionField).toBeDefined()
-
-			// Test that the field is a text input (implementation uses text with validation logic)
-			expect(dimensionField).toHaveAttribute("type", "text")
-
-			// Test that invalid input (non-numeric) doesn't trigger setApiConfigurationField
-			fireEvent.change(dimensionField!, { target: { value: "invalid" } })
-
-			// The implementation only accepts valid numbers
-			// Verify that setApiConfigurationField was not called with invalid string values
-			expect(mockSetApiConfigurationField).not.toHaveBeenCalledWith(
-				"codebaseIndexOpenAiCompatibleModelDimension",
-				"invalid",
-			)
-
-			// Test that numeric values (including negative) are accepted by the current implementation
-			fireEvent.change(dimensionField!, { target: { value: "-5" } })
-			expect(mockSetApiConfigurationField).toHaveBeenCalledWith("codebaseIndexOpenAiCompatibleModelDimension", -5)
-		})
-	})
-
-	describe("Model Selection", () => {
-		/**
-		 * Test conditional rendering of Model ID input based on provider type
-		 */
-		describe("Conditional Model Input Rendering", () => {
-			it("should render VSCodeTextField for Model ID when provider is openai-compatible", () => {
-				const propsWithOpenAICompatible = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai-compatible" as const,
-						codebaseIndexEmbedderModelId: "custom-model-id",
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-				// Should render VSCodeTextField for Model ID
-				const modelTextFields = screen.getAllByTestId("vscode-textfield")
-				const modelIdField = modelTextFields.find(
-					(field) => field.getAttribute("placeholder") === "Enter custom model ID",
-				)
-				expect(modelIdField).toBeInTheDocument()
-				expect(modelIdField).toHaveValue("custom-model-id")
-
-				// Should NOT render Select dropdown for models (only provider select should exist)
-				const selectElements = screen.getAllByTestId("select")
-				expect(selectElements).toHaveLength(1) // Only provider select, no model select
-			})
-
-			it("should render Select dropdown for models when provider is openai", () => {
-				const propsWithOpenAI = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai" as const,
-						codebaseIndexEmbedderModelId: "text-embedding-3-small",
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOpenAI} />)
-
-				// Should render Select dropdown for models (second select element)
-				const selectElements = screen.getAllByTestId("select")
-				expect(selectElements).toHaveLength(2) // Provider and model selects
-				const modelSelect = selectElements[1] // Model select is second
-				expect(modelSelect).toHaveAttribute("data-value", "text-embedding-3-small")
-
-				// Should NOT render VSCodeTextField for Model ID (only other text fields)
-				const modelTextFields = screen.getAllByTestId("vscode-textfield")
-				const modelIdField = modelTextFields.find(
-					(field) => field.getAttribute("placeholder") === "Enter custom model ID",
-				)
-				expect(modelIdField).toBeUndefined()
-			})
-
-			it("should render Select dropdown for models when provider is ollama", () => {
-				const propsWithOllama = {
-					...defaultProps,
-					codebaseIndexModels: {
-						...defaultProps.codebaseIndexModels,
-						ollama: {
-							llama2: { dimension: 4096 },
-							codellama: { dimension: 4096 },
-						},
-					},
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "ollama" as const,
-						codebaseIndexEmbedderModelId: "llama2",
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOllama} />)
-
-				// Should render Select dropdown for models (second select element)
-				const selectElements = screen.getAllByTestId("select")
-				expect(selectElements).toHaveLength(2) // Provider and model selects
-				const modelSelect = selectElements[1] // Model select is second
-				expect(modelSelect).toHaveAttribute("data-value", "llama2")
-
-				// Should NOT render VSCodeTextField for Model ID
-				const modelTextFields = screen.getAllByTestId("vscode-textfield")
-				const modelIdField = modelTextFields.find(
-					(field) => field.getAttribute("placeholder") === "Enter custom model ID",
-				)
-				expect(modelIdField).toBeUndefined()
-			})
-		})
-
-		/**
-		 * Test VSCodeTextField interactions for OpenAI-Compatible provider
-		 */
-		describe("VSCodeTextField for OpenAI-Compatible Model ID", () => {
-			const openAICompatibleProps = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-					codebaseIndexEmbedderModelId: "existing-model",
-				},
-			}
-
-			it("should display current Model ID value in VSCodeTextField", () => {
-				render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-				expect(modelIdField).toHaveValue("existing-model")
-			})
-
-			it("should call setCachedStateField when Model ID changes", async () => {
-				render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-
-				// Use fireEvent to trigger the change
-				fireEvent.change(modelIdField, { target: { value: "new-model" } })
-
-				// Check that setCachedStateField was called with codebaseIndexConfig
-				expect(mockSetCachedStateField).toHaveBeenCalledWith(
-					"codebaseIndexConfig",
-					expect.objectContaining({
-						codebaseIndexEmbedderProvider: "openai-compatible",
-						codebaseIndexEnabled: true,
-						codebaseIndexQdrantUrl: "http://localhost:6333",
-						codebaseIndexEmbedderModelId: "new-model",
-					}),
-				)
-			})
-
-			it("should handle empty Model ID value", () => {
-				const propsWithEmptyModelId = {
-					...openAICompatibleProps,
-					codebaseIndexConfig: {
-						...openAICompatibleProps.codebaseIndexConfig,
-						codebaseIndexEmbedderModelId: "",
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithEmptyModelId} />)
-
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-				expect(modelIdField).toHaveValue("")
-			})
-
-			it("should show placeholder text for Model ID input", () => {
-				render(<CodeIndexSettings {...openAICompatibleProps} />)
-
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-				expect(modelIdField).toBeInTheDocument()
-				expect(modelIdField).toHaveAttribute("placeholder", "Enter custom model ID")
-			})
-		})
-
-		/**
-		 * Test Select dropdown interactions for other providers
-		 */
-		describe("Select Dropdown for Other Providers", () => {
-			it("should show available models for OpenAI provider in dropdown", () => {
-				const propsWithOpenAI = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai" as const,
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOpenAI} />)
-
-				expect(screen.getByTestId("select-item-text-embedding-3-small")).toBeInTheDocument()
-				expect(screen.getByTestId("select-item-text-embedding-3-large")).toBeInTheDocument()
-			})
-
-			it("should show available models for Ollama provider in dropdown", () => {
-				const propsWithOllama = {
-					...defaultProps,
-					codebaseIndexModels: {
-						...defaultProps.codebaseIndexModels,
-						ollama: {
-							llama2: { dimension: 4096 },
-							codellama: { dimension: 4096 },
-						},
-					},
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "ollama" as const,
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOllama} />)
-
-				expect(screen.getByTestId("select-item-llama2")).toBeInTheDocument()
-				expect(screen.getByTestId("select-item-codellama")).toBeInTheDocument()
-			})
-
-			it("should call setCachedStateField when model is selected from dropdown", async () => {
-				const user = userEvent.setup()
-				const propsWithOpenAI = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai" as const,
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOpenAI} />)
-
-				// Get all select elements and find the model select (second one)
-				const selectElements = screen.getAllByTestId("select")
-				const modelSelect = selectElements[1] // Provider is first, Model is second
-				const selectButton = modelSelect.querySelector("button")
-				expect(selectButton).toBeInTheDocument()
-				await user.click(selectButton!)
-
-				expect(mockSetCachedStateField).toHaveBeenCalledWith("codebaseIndexConfig", {
-					...propsWithOpenAI.codebaseIndexConfig,
-					codebaseIndexEmbedderModelId: "test-change",
-				})
-			})
-
-			it("should display current model selection in dropdown", () => {
-				const propsWithSelectedModel = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai" as const,
-						codebaseIndexEmbedderModelId: "text-embedding-3-large",
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithSelectedModel} />)
-
-				// Get all select elements and find the model select (second one)
-				const selectElements = screen.getAllByTestId("select")
-				const modelSelect = selectElements[1] // Provider is first, Model is second
-				expect(modelSelect).toHaveAttribute("data-value", "text-embedding-3-large")
-			})
-		})
-
-		/**
-		 * Test fallback behavior for OpenAI-Compatible provider
-		 */
-		describe("OpenAI-Compatible Provider Model Fallback", () => {
-			it("should show available models for OpenAI Compatible provider", () => {
-				const propsWithOpenAICompatible = {
-					...defaultProps,
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai-compatible" as const,
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithOpenAICompatible} />)
-
-				// Note: For openai-compatible, we render VSCodeTextField, not Select dropdown
-				// But the component still uses availableModelIds for other purposes
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-				expect(modelIdField).toBeInTheDocument()
-			})
-
-			it("should fall back to OpenAI models when OpenAI Compatible models are not available", () => {
-				const propsWithoutCompatibleModels = {
-					...defaultProps,
-					codebaseIndexModels: {
-						openai: {
-							"text-embedding-3-small": { dimension: 1536 },
-							"text-embedding-3-large": { dimension: 3072 },
-						},
-					},
-					codebaseIndexConfig: {
-						...defaultProps.codebaseIndexConfig,
-						codebaseIndexEmbedderProvider: "openai-compatible" as const,
-					},
-				}
-
-				render(<CodeIndexSettings {...propsWithoutCompatibleModels} />)
-
-				// Should still render VSCodeTextField for openai-compatible provider
-				const modelIdField = screen.getByPlaceholderText("Enter custom model ID")
-				expect(modelIdField).toBeInTheDocument()
-			})
-		})
-	})
-
-	describe("Form Validation", () => {
-		it("should handle empty configuration gracefully", () => {
-			const emptyProps = {
-				...defaultProps,
-				codebaseIndexConfig: undefined,
-				apiConfiguration: {},
-			}
-
-			expect(() => render(<CodeIndexSettings {...emptyProps} />)).not.toThrow()
-		})
-
-		it("should handle missing model configuration", () => {
-			const propsWithoutModels = {
-				...defaultProps,
-				codebaseIndexModels: undefined,
-			}
-
-			expect(() => render(<CodeIndexSettings {...propsWithoutModels} />)).not.toThrow()
-		})
-
-		it("should handle empty API configuration fields", () => {
-			const propsWithEmptyConfig = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "openai-compatible" as const,
-				},
-				apiConfiguration: {
-					codebaseIndexOpenAiCompatibleBaseUrl: "",
-					codebaseIndexOpenAiCompatibleApiKey: "",
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithEmptyConfig} />)
-
-			const textFields = screen.getAllByTestId("vscode-textfield")
-			expect(textFields[0]).toHaveValue("")
-			expect(textFields[1]).toHaveValue("")
-		})
-	})
-
-	describe("Integration", () => {
-		it("should request indexing status on mount", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expect(vscode.postMessage).toHaveBeenCalledWith({
-				type: "requestIndexingStatus",
-			})
-		})
-
-		it("should set up message listener for status updates", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expect(window.addEventListener).toHaveBeenCalledWith("message", expect.any(Function))
-		})
-
-		it("should clean up message listener on unmount", () => {
-			const { unmount } = render(<CodeIndexSettings {...defaultProps} />)
-
-			unmount()
-
-			expect(window.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function))
-		})
-
-		/**
-		 * Test indexing status updates
-		 */
-		it("should update indexing status when receiving status update message", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			// Get the message handler that was registered
-			const messageHandler = (window.addEventListener as any).mock.calls.find(
-				(call: any) => call[0] === "message",
-			)?.[1]
-
-			expect(messageHandler).toBeDefined()
-
-			// Simulate receiving a status update message
-			const mockEvent = {
-				data: {
-					type: "indexingStatusUpdate",
-					values: {
-						systemStatus: "Indexing",
-						message: "Processing files...",
-						processedItems: 50,
-						totalItems: 100,
-						currentItemUnit: "files",
-					},
-				},
-			}
-
-			messageHandler(mockEvent)
-
-			// Check that the status indicator shows "Indexing"
-			expect(screen.getByText(/Indexing/)).toBeInTheDocument()
-		})
-	})
-
-	describe("Search Minimum Score Slider", () => {
-		const expandAdvancedConfig = () => {
-			const advancedButton = screen.getByRole("button", { name: /Advanced Configuration/i })
-			fireEvent.click(advancedButton)
-		}
-
-		it("should render advanced configuration toggle button", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expect(screen.getByRole("button", { name: /Advanced Configuration/i })).toBeInTheDocument()
-			expect(screen.getByText("Advanced Configuration")).toBeInTheDocument()
-		})
-
-		it("should render search minimum score slider with reset button when expanded", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expandAdvancedConfig()
-
-			expect(screen.getByTestId("search-min-score-slider")).toBeInTheDocument()
-			expect(screen.getByTestId("search-min-score-reset-button")).toBeInTheDocument()
-			expect(screen.getByText("Search Score Threshold")).toBeInTheDocument()
-		})
-
-		it("should display current search minimum score value when expanded", () => {
-			const propsWithScore = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexSearchMinScore: 0.65,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithScore} />)
-
-			expandAdvancedConfig()
-
-			expect(screen.getByText("0.65")).toBeInTheDocument()
-		})
-
-		it("should call setCachedStateField when slider value changes", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			expandAdvancedConfig()
-
-			const slider = screen.getByTestId("search-min-score-slider")
-			fireEvent.change(slider, { target: { value: "0.8" } })
-
-			expect(mockSetCachedStateField).toHaveBeenCalledWith("codebaseIndexConfig", {
-				...defaultProps.codebaseIndexConfig,
-				codebaseIndexSearchMinScore: 0.8,
-			})
-		})
-
-		it("should reset to default value when reset button is clicked", () => {
-			const propsWithScore = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexSearchMinScore: 0.8,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithScore} />)
-
-			expandAdvancedConfig()
-
-			const resetButton = screen.getByTestId("search-min-score-reset-button")
-			fireEvent.click(resetButton)
-
-			expect(mockSetCachedStateField).toHaveBeenCalledWith("codebaseIndexConfig", {
-				...defaultProps.codebaseIndexConfig,
-				codebaseIndexSearchMinScore: 0.4,
-			})
-		})
-
-		it("should use default value when no score is set", () => {
-			const propsWithoutScore = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexSearchMinScore: undefined,
-				},
-			}
-
-			render(<CodeIndexSettings {...propsWithoutScore} />)
-
-			expandAdvancedConfig()
-
-			expect(screen.getByText("0.40")).toBeInTheDocument()
-		})
-
-		it("should toggle advanced section visibility", () => {
-			render(<CodeIndexSettings {...defaultProps} />)
-
-			// Initially collapsed - should not see slider
-			expect(screen.queryByTestId("search-min-score-slider")).not.toBeInTheDocument()
-
-			// Expand advanced section
-			expandAdvancedConfig()
-			expect(screen.getByTestId("search-min-score-slider")).toBeInTheDocument()
-
-			// Collapse again
-			expandAdvancedConfig()
-			expect(screen.queryByTestId("search-min-score-slider")).not.toBeInTheDocument()
-		})
-	})
-
-	describe("Error Handling", () => {
-		it("should handle invalid provider gracefully", () => {
-			const propsWithInvalidProvider = {
-				...defaultProps,
-				codebaseIndexConfig: {
-					...defaultProps.codebaseIndexConfig,
-					codebaseIndexEmbedderProvider: "invalid-provider" as any,
-				},
-			}
-
-			expect(() => render(<CodeIndexSettings {...propsWithInvalidProvider} />)).not.toThrow()
-		})
-
-		it("should handle missing translation keys gracefully", () => {
-			// Mock translation function to return undefined for some keys
-			vi.doMock("@src/i18n/TranslationContext", () => ({
-				useAppTranslation: () => ({
-					t: (key: string) => (key.includes("missing") ? undefined : key),
-				}),
-			}))
-
-			expect(() => render(<CodeIndexSettings {...defaultProps} />)).not.toThrow()
-		})
-	})
-})

+ 42 - 13
webview-ui/src/i18n/locales/ca/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Clau API:",
 		"geminiApiKeyPlaceholder": "Introduïu la vostra clau d'API de Gemini",
 		"openaiCompatibleProvider": "Compatible amb OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL base:",
-		"openaiCompatibleApiKeyLabel": "Clau API:",
-		"openaiCompatibleModelDimensionLabel": "Dimensió d'Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "p. ex., 1536",
-		"openaiCompatibleModelDimensionDescription": "La dimensió d'embedding (mida de sortida) per al teu model. Consulta la documentació del teu proveïdor per a aquest valor. Valors comuns: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Clau OpenAI:",
+		"openAiKeyLabel": "Clau API OpenAI",
+		"openAiKeyPlaceholder": "Introduïu la vostra clau API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "URL base",
+		"openAiCompatibleApiKeyLabel": "Clau API",
+		"openAiCompatibleApiKeyPlaceholder": "Introduïu la vostra clau API",
+		"openAiCompatibleModelDimensionLabel": "Dimensió d'Embedding:",
+		"modelDimensionLabel": "Dimensió del model",
+		"openAiCompatibleModelDimensionPlaceholder": "p. ex., 1536",
+		"openAiCompatibleModelDimensionDescription": "La dimensió d'embedding (mida de sortida) per al teu model. Consulta la documentació del teu proveïdor per a aquest valor. Valors comuns: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
 		"selectModelPlaceholder": "Seleccionar model",
 		"ollamaUrlLabel": "URL d'Ollama:",
 		"qdrantUrlLabel": "URL de Qdrant",
 		"qdrantKeyLabel": "Clau de Qdrant:",
-		"advancedConfigLabel": "Configuració avançada",
-		"searchMinScoreLabel": "Llindar de puntuació de cerca",
-		"searchMinScoreDescription": "Puntuació mínima de similitud (0.0-1.0) requerida per als resultats de la cerca. Valors més baixos retornen més resultats però poden ser menys rellevants. Valors més alts retornen menys resultats però més rellevants.",
-		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)",
-		"startIndexingButton": "Iniciar indexació",
-		"clearIndexDataButton": "Esborrar dades d'índex",
+		"startIndexingButton": "Iniciar",
+		"clearIndexDataButton": "Esborrar índex",
 		"unsavedSettingsMessage": "Si us plau, deseu la configuració abans d'iniciar el procés d'indexació.",
 		"clearDataDialog": {
 			"title": "Esteu segur?",
 			"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"
-		}
+		},
+		"description": "Configureu la configuració d'indexació de la base de codi per habilitar la cerca semàntica del vostre projecte. <0>Més informació</0>",
+		"statusTitle": "Estat",
+		"settingsTitle": "Configuració d'indexació",
+		"disabledMessage": "La indexació de la base de codi està actualment deshabilitada. Habiliteu-la a la configuració global per configurar les opcions d'indexació.",
+		"embedderProviderLabel": "Proveïdor d'embeddings",
+		"modelPlaceholder": "Introduïu el nom del model",
+		"selectModel": "Seleccioneu un model",
+		"ollamaBaseUrlLabel": "URL base d'Ollama",
+		"qdrantApiKeyLabel": "Clau API de Qdrant",
+		"qdrantApiKeyPlaceholder": "Introduïu la vostra clau API de Qdrant (opcional)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Error en desar la configuració",
+		"modelDimensions": "({{dimension}} dimensions)",
+		"saveSuccess": "Configuració desada correctament",
+		"saving": "Desant...",
+		"saveSettings": "Desar",
+		"indexingStatuses": {
+			"standby": "En espera",
+			"indexing": "Indexant",
+			"indexed": "Indexat",
+			"error": "Error"
+		},
+		"close": "Tancar",
+		"advancedConfigLabel": "Configuració avançada",
+		"searchMinScoreLabel": "Llindar de puntuació de cerca",
+		"searchMinScoreDescription": "Puntuació mínima de similitud (0.0-1.0) requerida per als resultats de la cerca. Valors més baixos retornen més resultats però poden ser menys rellevants. Valors més alts retornen menys resultats però més rellevants.",
+		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)"
 	},
 	"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.",

+ 42 - 14
webview-ui/src/i18n/locales/de/settings.json

@@ -38,9 +38,14 @@
 	},
 	"codeIndex": {
 		"title": "Codebase-Indexierung",
+		"description": "Konfiguriere Codebase-Indexierungseinstellungen, um semantische Suche in deinem Projekt zu aktivieren. <0>Mehr erfahren</0>",
+		"statusTitle": "Status",
 		"enableLabel": "Codebase-Indexierung aktivieren",
 		"enableDescription": "<0>Codebase-Indexierung</0> ist eine experimentelle Funktion, die einen semantischen Suchindex deines Projekts mit KI-Embeddings erstellt. Dies ermöglicht es Roo Code, große Codebasen besser zu verstehen und zu navigieren, indem relevanter Code basierend auf Bedeutung statt nur Schlüsselwörtern gefunden wird.",
+		"settingsTitle": "Indexierungseinstellungen",
+		"disabledMessage": "Codebase-Indexierung ist derzeit deaktiviert. Aktiviere sie in den globalen Einstellungen, um Indexierungsoptionen zu konfigurieren.",
 		"providerLabel": "Embeddings-Anbieter",
+		"embedderProviderLabel": "Embedder-Anbieter",
 		"selectProviderPlaceholder": "Anbieter auswählen",
 		"openaiProvider": "OpenAI",
 		"ollamaProvider": "Ollama",
@@ -48,30 +53,54 @@
 		"geminiApiKeyLabel": "API-Schlüssel:",
 		"geminiApiKeyPlaceholder": "Geben Sie Ihren Gemini-API-Schlüssel ein",
 		"openaiCompatibleProvider": "OpenAI-kompatibel",
-		"openaiCompatibleBaseUrlLabel": "Basis-URL:",
-		"openaiCompatibleApiKeyLabel": "API-Schlüssel:",
-		"openaiCompatibleModelDimensionLabel": "Embedding-Dimension:",
-		"openaiCompatibleModelDimensionPlaceholder": "z.B. 1536",
-		"openaiCompatibleModelDimensionDescription": "Die Embedding-Dimension (Ausgabegröße) für Ihr Modell. Überprüfen Sie die Dokumentation Ihres Anbieters für diesen Wert. Übliche Werte: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "OpenAI-Schlüssel:",
+		"openAiKeyLabel": "OpenAI API-Schlüssel",
+		"openAiKeyPlaceholder": "Gib deinen OpenAI API-Schlüssel ein",
+		"openAiCompatibleBaseUrlLabel": "Basis-URL",
+		"openAiCompatibleApiKeyLabel": "API-Schlüssel",
+		"openAiCompatibleApiKeyPlaceholder": "Gib deinen API-Schlüssel ein",
+		"openAiCompatibleModelDimensionLabel": "Embedding-Dimension:",
+		"modelDimensionLabel": "Modell-Dimension",
+		"openAiCompatibleModelDimensionPlaceholder": "z.B. 1536",
+		"openAiCompatibleModelDimensionDescription": "Die Embedding-Dimension (Ausgabegröße) für Ihr Modell. Überprüfen Sie die Dokumentation Ihres Anbieters für diesen Wert. Übliche Werte: 384, 768, 1536, 3072.",
 		"modelLabel": "Modell",
+		"modelPlaceholder": "Modellname eingeben",
+		"selectModel": "Modell auswählen",
 		"selectModelPlaceholder": "Modell auswählen",
 		"ollamaUrlLabel": "Ollama-URL:",
+		"ollamaBaseUrlLabel": "Ollama Basis-URL",
 		"qdrantUrlLabel": "Qdrant-URL",
 		"qdrantKeyLabel": "Qdrant-Schlüssel:",
-		"advancedConfigLabel": "Erweiterte Konfiguration",
-		"searchMinScoreLabel": "Suchergebnis-Schwellenwert",
-		"searchMinScoreDescription": "Mindestähnlichkeitswert (0.0-1.0), der für Suchergebnisse erforderlich ist. Niedrigere Werte liefern mehr Ergebnisse, die jedoch möglicherweise weniger relevant sind. Höhere Werte liefern weniger, aber relevantere Ergebnisse.",
-		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)",
-		"startIndexingButton": "Indexierung starten",
-		"clearIndexDataButton": "Indexdaten löschen",
+		"qdrantApiKeyLabel": "Qdrant API-Schlüssel",
+		"qdrantApiKeyPlaceholder": "Gib deinen Qdrant API-Schlüssel ein (optional)",
+		"startIndexingButton": "Start",
+		"clearIndexDataButton": "Index löschen",
 		"unsavedSettingsMessage": "Bitte speichere deine Einstellungen, bevor du den Indexierungsprozess startest.",
 		"clearDataDialog": {
 			"title": "Sind Sie sicher?",
 			"description": "Diese Aktion kann nicht rückgängig gemacht werden. Dies wird Ihre Codebase-Indexdaten dauerhaft löschen.",
 			"cancelButton": "Abbrechen",
 			"confirmButton": "Daten löschen"
-		}
+		},
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Fehler beim Speichern der Einstellungen",
+		"modelDimensions": "({{dimension}} Dimensionen)",
+		"saveSuccess": "Einstellungen erfolgreich gespeichert",
+		"saving": "Speichern...",
+		"saveSettings": "Speichern",
+		"indexingStatuses": {
+			"standby": "Bereitschaft",
+			"indexing": "Indexierung",
+			"indexed": "Indexiert",
+			"error": "Fehler"
+		},
+		"close": "Schließen",
+		"advancedConfigLabel": "Erweiterte Konfiguration",
+		"searchMinScoreLabel": "Suchergebnis-Schwellenwert",
+		"searchMinScoreDescription": "Mindestähnlichkeitswert (0.0-1.0), der für Suchergebnisse erforderlich ist. Niedrigere Werte liefern mehr Ergebnisse, die jedoch möglicherweise weniger relevant sind. Höhere Werte liefern weniger, aber relevantere Ergebnisse.",
+		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)"
 	},
 	"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.",
@@ -528,7 +557,6 @@
 			"name": "Marketplace aktivieren",
 			"description": "Wenn aktiviert, kannst du MCP und benutzerdefinierte Modi aus dem Marketplace installieren und verwalten."
 		},
-
 		"MULTI_FILE_APPLY_DIFF": {
 			"name": "Gleichzeitige Dateibearbeitungen aktivieren",
 			"description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage bearbeiten. Wenn deaktiviert, muss Roo Dateien einzeln bearbeiten. Das Deaktivieren kann helfen, wenn du mit weniger fähigen Modellen arbeitest oder mehr Kontrolle über Dateiänderungen haben möchtest."

+ 36 - 7
webview-ui/src/i18n/locales/en/settings.json

@@ -38,9 +38,14 @@
 	},
 	"codeIndex": {
 		"title": "Codebase Indexing",
+		"description": "Configure codebase indexing settings to enable semantic search of your project. <0>Learn more</0>",
+		"statusTitle": "Status",
 		"enableLabel": "Enable Codebase Indexing",
 		"enableDescription": "<0>Codebase Indexing</0> is an experimental feature that creates a semantic search index of your project using AI embeddings. This enables Roo Code to better understand and navigate large codebases by finding relevant code based on meaning rather than just keywords.",
+		"settingsTitle": "Indexing Settings",
+		"disabledMessage": "Codebase indexing is currently disabled. Enable it in the global settings to configure indexing options.",
 		"providerLabel": "Embeddings Provider",
+		"embedderProviderLabel": "Embedder Provider",
 		"selectProviderPlaceholder": "Select provider",
 		"openaiProvider": "OpenAI",
 		"ollamaProvider": "Ollama",
@@ -48,17 +53,25 @@
 		"geminiApiKeyLabel": "API Key:",
 		"geminiApiKeyPlaceholder": "Enter your Gemini API key",
 		"openaiCompatibleProvider": "OpenAI Compatible",
-		"openaiKeyLabel": "OpenAI Key:",
-		"openaiCompatibleBaseUrlLabel": "Base URL:",
-		"openaiCompatibleApiKeyLabel": "API Key:",
-		"openaiCompatibleModelDimensionLabel": "Embedding Dimension:",
-		"openaiCompatibleModelDimensionPlaceholder": "e.g., 1536",
-		"openaiCompatibleModelDimensionDescription": "The embedding dimension (output size) for your model. Check your provider's documentation for this value. Common values: 384, 768, 1536, 3072.",
+		"openAiKeyLabel": "OpenAI API Key",
+		"openAiKeyPlaceholder": "Enter your OpenAI API key",
+		"openAiCompatibleBaseUrlLabel": "Base URL",
+		"openAiCompatibleApiKeyLabel": "API Key",
+		"openAiCompatibleApiKeyPlaceholder": "Enter your API key",
+		"openAiCompatibleModelDimensionLabel": "Embedding Dimension:",
+		"modelDimensionLabel": "Model Dimension",
+		"openAiCompatibleModelDimensionPlaceholder": "e.g., 1536",
+		"openAiCompatibleModelDimensionDescription": "The embedding dimension (output size) for your model. Check your provider's documentation for this value. Common values: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
+		"modelPlaceholder": "Enter model name",
+		"selectModel": "Select a model",
 		"selectModelPlaceholder": "Select model",
 		"ollamaUrlLabel": "Ollama URL:",
+		"ollamaBaseUrlLabel": "Ollama Base URL",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Key:",
+		"qdrantApiKeyLabel": "Qdrant API Key",
+		"qdrantApiKeyPlaceholder": "Enter your Qdrant API key (optional)",
 		"advancedConfigLabel": "Advanced Configuration",
 		"searchMinScoreLabel": "Search Score Threshold",
 		"searchMinScoreDescription": "Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.",
@@ -71,7 +84,23 @@
 			"description": "This action cannot be undone. This will permanently delete your codebase index data.",
 			"cancelButton": "Cancel",
 			"confirmButton": "Clear Data"
-		}
+		},
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Failed to save settings",
+		"modelDimensions": "({{dimension}} dimensions)",
+		"saveSuccess": "Settings saved successfully",
+		"saving": "Saving...",
+		"saveSettings": "Save",
+		"indexingStatuses": {
+			"standby": "Standby",
+			"indexing": "Indexing",
+			"indexed": "Indexed",
+			"error": "Error"
+		},
+		"close": "Close"
 	},
 	"autoApprove": {
 		"description": "Allow Roo to automatically perform operations without requiring approval. Enable these settings only if you fully trust the AI and understand the associated security risks.",

+ 42 - 13
webview-ui/src/i18n/locales/es/settings.json

@@ -38,9 +38,14 @@
 	},
 	"codeIndex": {
 		"title": "Indexación de código",
+		"description": "Configura los ajustes de indexación de código para habilitar búsqueda semántica en tu proyecto. <0>Más información</0>",
+		"statusTitle": "Estado",
 		"enableLabel": "Habilitar indexación de código",
 		"enableDescription": "<0>La indexación de código</0> es una función experimental que crea un índice de búsqueda semántica de tu proyecto usando embeddings de IA. Esto permite a Roo Code entender mejor y navegar grandes bases de código encontrando código relevante basado en significado en lugar de solo palabras clave.",
+		"settingsTitle": "Configuración de indexación",
+		"disabledMessage": "La indexación de código está actualmente deshabilitada. Habilítala en la configuración global para configurar las opciones de indexación.",
 		"providerLabel": "Proveedor de embeddings",
+		"embedderProviderLabel": "Proveedor de embedder",
 		"selectProviderPlaceholder": "Seleccionar proveedor",
 		"openaiProvider": "OpenAI",
 		"ollamaProvider": "Ollama",
@@ -48,30 +53,54 @@
 		"geminiApiKeyLabel": "Clave API:",
 		"geminiApiKeyPlaceholder": "Introduce tu clave de API de Gemini",
 		"openaiCompatibleProvider": "Compatible con OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL base:",
-		"openaiCompatibleApiKeyLabel": "Clave API:",
-		"openaiCompatibleModelDimensionLabel": "Dimensión de Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "ej., 1536",
-		"openaiCompatibleModelDimensionDescription": "La dimensión de embedding (tamaño de salida) para tu modelo. Consulta la documentación de tu proveedor para este valor. Valores comunes: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Clave de OpenAI:",
+		"openAiKeyLabel": "Clave API de OpenAI",
+		"openAiKeyPlaceholder": "Introduce tu clave API de OpenAI",
+		"openAiCompatibleBaseUrlLabel": "URL base",
+		"openAiCompatibleApiKeyLabel": "Clave API",
+		"openAiCompatibleApiKeyPlaceholder": "Introduce tu clave API",
+		"openAiCompatibleModelDimensionLabel": "Dimensión de Embedding:",
+		"modelDimensionLabel": "Dimensión del modelo",
+		"openAiCompatibleModelDimensionPlaceholder": "ej., 1536",
+		"openAiCompatibleModelDimensionDescription": "La dimensión de embedding (tamaño de salida) para tu modelo. Consulta la documentación de tu proveedor para este valor. Valores comunes: 384, 768, 1536, 3072.",
 		"modelLabel": "Modelo",
+		"modelPlaceholder": "Introduce el nombre del modelo",
+		"selectModel": "Seleccionar un modelo",
 		"selectModelPlaceholder": "Seleccionar modelo",
 		"ollamaUrlLabel": "URL de Ollama:",
+		"ollamaBaseUrlLabel": "URL base de Ollama",
 		"qdrantUrlLabel": "URL de Qdrant",
 		"qdrantKeyLabel": "Clave de Qdrant:",
-		"advancedConfigLabel": "Configuración avanzada",
-		"searchMinScoreLabel": "Umbral de puntuación de búsqueda",
-		"searchMinScoreDescription": "Puntuación mínima de similitud (0.0-1.0) requerida para los resultados de búsqueda. Valores más bajos devuelven más resultados pero pueden ser menos relevantes. Valores más altos devuelven menos resultados pero más relevantes.",
-		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
-		"startIndexingButton": "Iniciar indexación",
-		"clearIndexDataButton": "Borrar datos de índice",
+		"qdrantApiKeyLabel": "Clave API de Qdrant",
+		"qdrantApiKeyPlaceholder": "Introduce tu clave API de Qdrant (opcional)",
+		"startIndexingButton": "Iniciar",
+		"clearIndexDataButton": "Borrar índice",
 		"unsavedSettingsMessage": "Por favor guarda tus ajustes antes de iniciar el proceso de indexación.",
 		"clearDataDialog": {
 			"title": "¿Estás seguro?",
 			"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"
-		}
+		},
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Error al guardar la configuración",
+		"modelDimensions": "({{dimension}} dimensiones)",
+		"saveSuccess": "Configuración guardada exitosamente",
+		"saving": "Guardando...",
+		"saveSettings": "Guardar",
+		"indexingStatuses": {
+			"standby": "En espera",
+			"indexing": "Indexando",
+			"indexed": "Indexado",
+			"error": "Error"
+		},
+		"close": "Cerrar",
+		"advancedConfigLabel": "Configuración avanzada",
+		"searchMinScoreLabel": "Umbral de puntuación de búsqueda",
+		"searchMinScoreDescription": "Puntuación mínima de similitud (0.0-1.0) requerida para los resultados de búsqueda. Valores más bajos devuelven más resultados pero pueden ser menos relevantes. Valores más altos devuelven menos resultados pero más relevantes.",
+		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)"
 	},
 	"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.",

+ 42 - 14
webview-ui/src/i18n/locales/fr/settings.json

@@ -38,9 +38,14 @@
 	},
 	"codeIndex": {
 		"title": "Indexation de la base de code",
+		"description": "Configurez les paramètres d'indexation de la base de code pour activer la recherche sémantique dans votre projet. <0>En savoir plus</0>",
+		"statusTitle": "Statut",
 		"enableLabel": "Activer l'indexation de la base de code",
 		"enableDescription": "<0>L'indexation de la base de code</0> est une fonctionnalité expérimentale qui crée un index de recherche sémantique de votre projet en utilisant des embeddings IA. Cela permet à Roo Code de mieux comprendre et naviguer dans de grandes bases de code en trouvant du code pertinent basé sur le sens plutôt que seulement sur des mots-clés.",
+		"settingsTitle": "Paramètres d'indexation",
+		"disabledMessage": "L'indexation de la base de code est actuellement désactivée. Activez-la dans les paramètres globaux pour configurer les options d'indexation.",
 		"providerLabel": "Fournisseur d'embeddings",
+		"embedderProviderLabel": "Fournisseur d'embedder",
 		"selectProviderPlaceholder": "Sélectionner un fournisseur",
 		"openaiProvider": "OpenAI",
 		"ollamaProvider": "Ollama",
@@ -48,30 +53,54 @@
 		"geminiApiKeyLabel": "Clé API :",
 		"geminiApiKeyPlaceholder": "Entrez votre clé API Gemini",
 		"openaiCompatibleProvider": "Compatible OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL de base :",
-		"openaiCompatibleApiKeyLabel": "Clé API :",
-		"openaiCompatibleModelDimensionLabel": "Dimension d'Embedding :",
-		"openaiCompatibleModelDimensionPlaceholder": "ex., 1536",
-		"openaiCompatibleModelDimensionDescription": "La dimension d'embedding (taille de sortie) pour votre modèle. Consultez la documentation de votre fournisseur pour cette valeur. Valeurs courantes : 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Clé OpenAI :",
+		"openAiKeyLabel": "Clé API OpenAI",
+		"openAiKeyPlaceholder": "Entrez votre clé API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "URL de base",
+		"openAiCompatibleApiKeyLabel": "Clé API",
+		"openAiCompatibleApiKeyPlaceholder": "Entrez votre clé API",
+		"openAiCompatibleModelDimensionLabel": "Dimension d'Embedding :",
+		"modelDimensionLabel": "Dimension du modèle",
+		"openAiCompatibleModelDimensionPlaceholder": "ex., 1536",
+		"openAiCompatibleModelDimensionDescription": "La dimension d'embedding (taille de sortie) pour votre modèle. Consultez la documentation de votre fournisseur pour cette valeur. Valeurs courantes : 384, 768, 1536, 3072.",
 		"modelLabel": "Modèle",
+		"modelPlaceholder": "Entrez le nom du modèle",
+		"selectModel": "Sélectionner un modèle",
 		"selectModelPlaceholder": "Sélectionner un modèle",
 		"ollamaUrlLabel": "URL Ollama :",
+		"ollamaBaseUrlLabel": "URL de base Ollama",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Clé Qdrant :",
-		"advancedConfigLabel": "Configuration avancée",
-		"searchMinScoreLabel": "Seuil de score de recherche",
-		"searchMinScoreDescription": "Score de similarité minimum (0.0-1.0) requis pour les résultats de recherche. Des valeurs plus faibles renvoient plus de résultats mais peuvent être moins pertinents. Des valeurs plus élevées renvoient moins de résultats mais plus pertinents.",
-		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)",
-		"startIndexingButton": "Démarrer l'indexation",
-		"clearIndexDataButton": "Effacer les données d'index",
+		"qdrantApiKeyLabel": "Clé API Qdrant",
+		"qdrantApiKeyPlaceholder": "Entrez votre clé API Qdrant (optionnel)",
+		"startIndexingButton": "Démarrer",
+		"clearIndexDataButton": "Effacer l'index",
 		"unsavedSettingsMessage": "Merci d'enregistrer tes paramètres avant de démarrer le processus d'indexation.",
 		"clearDataDialog": {
 			"title": "Êtes-vous sûr ?",
 			"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"
-		}
+		},
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Échec de la sauvegarde des paramètres",
+		"modelDimensions": "({{dimension}} dimensions)",
+		"saveSuccess": "Paramètres sauvegardés avec succès",
+		"saving": "Sauvegarde...",
+		"saveSettings": "Sauvegarder",
+		"indexingStatuses": {
+			"standby": "En attente",
+			"indexing": "Indexation",
+			"indexed": "Indexé",
+			"error": "Erreur"
+		},
+		"close": "Fermer",
+		"advancedConfigLabel": "Configuration avancée",
+		"searchMinScoreLabel": "Seuil de score de recherche",
+		"searchMinScoreDescription": "Score de similarité minimum (0.0-1.0) requis pour les résultats de recherche. Des valeurs plus faibles renvoient plus de résultats mais peuvent être moins pertinents. Des valeurs plus élevées renvoient moins de résultats mais plus pertinents.",
+		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)"
 	},
 	"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.",
@@ -528,7 +557,6 @@
 			"name": "Activer le Marketplace",
 			"description": "Lorsque cette option est activée, tu peux installer des MCP et des modes personnalisés depuis le Marketplace."
 		},
-
 		"MULTI_FILE_APPLY_DIFF": {
 			"name": "Activer les éditions de fichiers concurrentes",
 			"description": "Lorsque cette option est activée, Roo peut éditer plusieurs fichiers en une seule requête. Lorsqu'elle est désactivée, Roo doit éditer les fichiers un par un. Désactiver cette option peut aider lorsque tu travailles avec des modèles moins capables ou lorsque tu veux plus de contrôle sur les modifications de fichiers."

+ 42 - 13
webview-ui/src/i18n/locales/hi/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API कुंजी:",
 		"geminiApiKeyPlaceholder": "अपना जेमिनी एपीआई कुंजी दर्ज करें",
 		"openaiCompatibleProvider": "OpenAI संगत",
-		"openaiCompatibleBaseUrlLabel": "आधार URL:",
-		"openaiCompatibleApiKeyLabel": "API कुंजी:",
-		"openaiCompatibleModelDimensionLabel": "एम्बेडिंग आयाम:",
-		"openaiCompatibleModelDimensionPlaceholder": "उदा., 1536",
-		"openaiCompatibleModelDimensionDescription": "आपके मॉडल के लिए एम्बेडिंग आयाम (आउटपुट साइज)। इस मान के लिए अपने प्रदाता के दस्तावेज़ीकरण की जांच करें। सामान्य मान: 384, 768, 1536, 3072।",
-		"openaiKeyLabel": "OpenAI कुंजी:",
+		"openAiKeyLabel": "OpenAI API कुंजी",
+		"openAiKeyPlaceholder": "अपना OpenAI API कुंजी दर्ज करें",
+		"openAiCompatibleBaseUrlLabel": "आधार URL",
+		"openAiCompatibleApiKeyLabel": "API कुंजी",
+		"openAiCompatibleApiKeyPlaceholder": "अपना API कुंजी दर्ज करें",
+		"openAiCompatibleModelDimensionLabel": "एम्बेडिंग आयाम:",
+		"modelDimensionLabel": "मॉडल आयाम",
+		"openAiCompatibleModelDimensionPlaceholder": "उदा., 1536",
+		"openAiCompatibleModelDimensionDescription": "आपके मॉडल के लिए एम्बेडिंग आयाम (आउटपुट साइज)। इस मान के लिए अपने प्रदाता के दस्तावेज़ीकरण की जांच करें। सामान्य मान: 384, 768, 1536, 3072।",
 		"modelLabel": "मॉडल",
 		"selectModelPlaceholder": "मॉडल चुनें",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant कुंजी:",
-		"advancedConfigLabel": "उन्नत कॉन्फ़िगरेशन",
-		"searchMinScoreLabel": "खोज स्कोर थ्रेसहोल्ड",
-		"searchMinScoreDescription": "खोज परिणामों के लिए आवश्यक न्यूनतम समानता स्कोर (0.0-1.0)। कम मान अधिक परिणाम लौटाते हैं लेकिन कम प्रासंगिक हो सकते हैं। उच्च मान कम लेकिन अधिक प्रासंगिक परिणाम लौटाते हैं।",
-		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)",
-		"startIndexingButton": "इंडेक्सिंग शुरू करें",
-		"clearIndexDataButton": "इंडेक्स डेटा साफ़ करें",
+		"startIndexingButton": "शुरू करें",
+		"clearIndexDataButton": "इंडेक्स साफ़ करें",
 		"unsavedSettingsMessage": "इंडेक्सिंग प्रक्रिया शुरू करने से पहले कृपया अपनी सेटिंग्स सहेजें।",
 		"clearDataDialog": {
 			"title": "क्या आप सुनिश्चित हैं?",
 			"description": "यह क्रिया पूर्ववत नहीं की जा सकती। यह आपके कोडबेस इंडेक्स डेटा को स्थायी रूप से हटा देगी।",
 			"cancelButton": "रद्द करें",
 			"confirmButton": "डेटा साफ़ करें"
-		}
+		},
+		"description": "अपने प्रोजेक्ट की सिमेंटिक खोज को सक्षम करने के लिए कोडबेस इंडेक्सिंग सेटिंग्स कॉन्फ़िगर करें। <0>और जानें</0>",
+		"statusTitle": "स्थिति",
+		"settingsTitle": "इंडेक्सिंग सेटिंग्स",
+		"disabledMessage": "कोडबेस इंडेक्सिंग वर्तमान में अक्षम है। इंडेक्सिंग विकल्पों को कॉन्फ़िगर करने के लिए इसे ग्लोबल सेटिंग्स में सक्षम करें।",
+		"embedderProviderLabel": "एम्बेडर प्रदाता",
+		"modelPlaceholder": "मॉडल नाम दर्ज करें",
+		"selectModel": "एक मॉडल चुनें",
+		"ollamaBaseUrlLabel": "Ollama आधार URL",
+		"qdrantApiKeyLabel": "Qdrant API कुंजी",
+		"qdrantApiKeyPlaceholder": "अपनी Qdrant API कुंजी दर्ज करें (वैकल्पिक)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "सेटिंग्स सहेजने में विफल",
+		"modelDimensions": "({{dimension}} आयाम)",
+		"saveSuccess": "सेटिंग्स सफलतापूर्वक सहेजी गईं",
+		"saving": "सहेज रहे हैं...",
+		"saveSettings": "सहेजें",
+		"indexingStatuses": {
+			"standby": "स्टैंडबाई",
+			"indexing": "इंडेक्सिंग",
+			"indexed": "इंडेक्स किया गया",
+			"error": "त्रुटि"
+		},
+		"close": "बंद करें",
+		"advancedConfigLabel": "उन्नत कॉन्फ़िगरेशन",
+		"searchMinScoreLabel": "खोज स्कोर थ्रेसहोल्ड",
+		"searchMinScoreDescription": "खोज परिणामों के लिए आवश्यक न्यूनतम समानता स्कोर (0.0-1.0)। कम मान अधिक परिणाम लौटाते हैं लेकिन कम प्रासंगिक हो सकते हैं। उच्च मान कम लेकिन अधिक प्रासंगिक परिणाम लौटाते हैं।",
+		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)"
 	},
 	"autoApprove": {
 		"description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।",

+ 42 - 13
webview-ui/src/i18n/locales/id/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API Key:",
 		"geminiApiKeyPlaceholder": "Masukkan kunci API Gemini Anda",
 		"openaiCompatibleProvider": "OpenAI Compatible",
-		"openaiKeyLabel": "OpenAI Key:",
-		"openaiCompatibleBaseUrlLabel": "Base URL:",
-		"openaiCompatibleApiKeyLabel": "API Key:",
-		"openaiCompatibleModelDimensionLabel": "Dimensi Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "misalnya, 1536",
-		"openaiCompatibleModelDimensionDescription": "Dimensi embedding (ukuran output) untuk model kamu. Periksa dokumentasi provider kamu untuk nilai ini. Nilai umum: 384, 768, 1536, 3072.",
+		"openAiKeyLabel": "OpenAI API Key",
+		"openAiKeyPlaceholder": "Masukkan kunci API OpenAI kamu",
+		"openAiCompatibleBaseUrlLabel": "Base URL",
+		"openAiCompatibleApiKeyLabel": "API Key",
+		"openAiCompatibleApiKeyPlaceholder": "Masukkan kunci API kamu",
+		"openAiCompatibleModelDimensionLabel": "Dimensi Embedding:",
+		"modelDimensionLabel": "Dimensi Model",
+		"openAiCompatibleModelDimensionPlaceholder": "misalnya, 1536",
+		"openAiCompatibleModelDimensionDescription": "Dimensi embedding (ukuran output) untuk model kamu. Periksa dokumentasi provider kamu untuk nilai ini. Nilai umum: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
 		"selectModelPlaceholder": "Pilih model",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Key:",
-		"advancedConfigLabel": "Konfigurasi Lanjutan",
-		"searchMinScoreLabel": "Ambang Batas Skor Pencarian",
-		"searchMinScoreDescription": "Skor kesamaan minimum (0.0-1.0) yang diperlukan untuk hasil pencarian. Nilai yang lebih rendah mengembalikan lebih banyak hasil tetapi mungkin kurang relevan. Nilai yang lebih tinggi mengembalikan lebih sedikit hasil tetapi lebih relevan.",
-		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)",
-		"startIndexingButton": "Mulai Pengindeksan",
-		"clearIndexDataButton": "Hapus Data Indeks",
+		"startIndexingButton": "Mulai",
+		"clearIndexDataButton": "Hapus Indeks",
 		"unsavedSettingsMessage": "Silakan simpan pengaturan kamu sebelum memulai proses pengindeksan.",
 		"clearDataDialog": {
 			"title": "Apakah kamu yakin?",
 			"description": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus data indeks codebase kamu secara permanen.",
 			"cancelButton": "Batal",
 			"confirmButton": "Hapus Data"
-		}
+		},
+		"description": "Konfigurasi pengaturan pengindeksan codebase untuk mengaktifkan pencarian semantik proyek kamu. <0>Pelajari lebih lanjut</0>",
+		"statusTitle": "Status",
+		"settingsTitle": "Pengaturan Pengindeksan",
+		"disabledMessage": "Pengindeksan codebase saat ini dinonaktifkan. Aktifkan di pengaturan global untuk mengkonfigurasi opsi pengindeksan.",
+		"embedderProviderLabel": "Provider Embedder",
+		"modelPlaceholder": "Masukkan nama model",
+		"selectModel": "Pilih model",
+		"ollamaBaseUrlLabel": "URL Dasar Ollama",
+		"qdrantApiKeyLabel": "Kunci API Qdrant",
+		"qdrantApiKeyPlaceholder": "Masukkan kunci API Qdrant kamu (opsional)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Gagal menyimpan pengaturan",
+		"modelDimensions": "({{dimension}} dimensi)",
+		"saveSuccess": "Pengaturan berhasil disimpan",
+		"saving": "Menyimpan...",
+		"saveSettings": "Simpan",
+		"indexingStatuses": {
+			"standby": "Siaga",
+			"indexing": "Mengindeks",
+			"indexed": "Terindeks",
+			"error": "Error"
+		},
+		"close": "Tutup",
+		"advancedConfigLabel": "Konfigurasi Lanjutan",
+		"searchMinScoreLabel": "Ambang Batas Skor Pencarian",
+		"searchMinScoreDescription": "Skor kesamaan minimum (0.0-1.0) yang diperlukan untuk hasil pencarian. Nilai yang lebih rendah mengembalikan lebih banyak hasil tetapi mungkin kurang relevan. Nilai yang lebih tinggi mengembalikan lebih sedikit hasil tetapi lebih relevan.",
+		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/it/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Chiave API:",
 		"geminiApiKeyPlaceholder": "Inserisci la tua chiave API Gemini",
 		"openaiCompatibleProvider": "Compatibile con OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL di base:",
-		"openaiCompatibleApiKeyLabel": "Chiave API:",
-		"openaiCompatibleModelDimensionLabel": "Dimensione Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "es., 1536",
-		"openaiCompatibleModelDimensionDescription": "La dimensione dell'embedding (dimensione di output) per il tuo modello. Controlla la documentazione del tuo provider per questo valore. Valori comuni: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Chiave OpenAI:",
+		"openAiKeyLabel": "Chiave API OpenAI",
+		"openAiKeyPlaceholder": "Inserisci la tua chiave API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "URL di base",
+		"openAiCompatibleApiKeyLabel": "Chiave API",
+		"openAiCompatibleApiKeyPlaceholder": "Inserisci la tua chiave API",
+		"openAiCompatibleModelDimensionLabel": "Dimensione Embedding:",
+		"modelDimensionLabel": "Dimensione del modello",
+		"openAiCompatibleModelDimensionPlaceholder": "es., 1536",
+		"openAiCompatibleModelDimensionDescription": "La dimensione dell'embedding (dimensione di output) per il tuo modello. Controlla la documentazione del tuo provider per questo valore. Valori comuni: 384, 768, 1536, 3072.",
 		"modelLabel": "Modello",
 		"selectModelPlaceholder": "Seleziona modello",
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Chiave Qdrant:",
-		"advancedConfigLabel": "Configurazione avanzata",
-		"searchMinScoreLabel": "Soglia punteggio di ricerca",
-		"searchMinScoreDescription": "Punteggio minimo di somiglianza (0.0-1.0) richiesto per i risultati della ricerca. Valori più bassi restituiscono più risultati ma potrebbero essere meno pertinenti. Valori più alti restituiscono meno risultati ma più pertinenti.",
-		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)",
-		"startIndexingButton": "Avvia indicizzazione",
-		"clearIndexDataButton": "Cancella dati indice",
+		"startIndexingButton": "Avvia",
+		"clearIndexDataButton": "Cancella indice",
 		"unsavedSettingsMessage": "Per favore salva le tue impostazioni prima di avviare il processo di indicizzazione.",
 		"clearDataDialog": {
 			"title": "Sei sicuro?",
 			"description": "Questa azione non può essere annullata. Eliminerà permanentemente i dati di indice del tuo codice.",
 			"cancelButton": "Annulla",
 			"confirmButton": "Cancella dati"
-		}
+		},
+		"description": "Configura le impostazioni di indicizzazione del codebase per abilitare la ricerca semantica del tuo progetto. <0>Scopri di più</0>",
+		"statusTitle": "Stato",
+		"settingsTitle": "Impostazioni di indicizzazione",
+		"disabledMessage": "L'indicizzazione del codebase è attualmente disabilitata. Abilitala nelle impostazioni globali per configurare le opzioni di indicizzazione.",
+		"embedderProviderLabel": "Provider Embedder",
+		"modelPlaceholder": "Inserisci il nome del modello",
+		"selectModel": "Seleziona un modello",
+		"ollamaBaseUrlLabel": "URL base Ollama",
+		"qdrantApiKeyLabel": "Chiave API Qdrant",
+		"qdrantApiKeyPlaceholder": "Inserisci la tua chiave API Qdrant (opzionale)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Impossibile salvare le impostazioni",
+		"modelDimensions": "({{dimension}} dimensioni)",
+		"saveSuccess": "Impostazioni salvate con successo",
+		"saving": "Salvataggio...",
+		"saveSettings": "Salva",
+		"indexingStatuses": {
+			"standby": "In attesa",
+			"indexing": "Indicizzazione",
+			"indexed": "Indicizzato",
+			"error": "Errore"
+		},
+		"close": "Chiudi",
+		"advancedConfigLabel": "Configurazione avanzata",
+		"searchMinScoreLabel": "Soglia punteggio di ricerca",
+		"searchMinScoreDescription": "Punteggio minimo di somiglianza (0.0-1.0) richiesto per i risultati della ricerca. Valori più bassi restituiscono più risultati ma potrebbero essere meno pertinenti. Valori più alti restituiscono meno risultati ma più pertinenti.",
+		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/ja/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "APIキー:",
 		"geminiApiKeyPlaceholder": "Gemini APIキーを入力してください",
 		"openaiCompatibleProvider": "OpenAI互換",
-		"openaiCompatibleBaseUrlLabel": "ベースURL:",
-		"openaiCompatibleApiKeyLabel": "APIキー:",
-		"openaiCompatibleModelDimensionLabel": "埋め込みディメンション:",
-		"openaiCompatibleModelDimensionPlaceholder": "例:1536",
-		"openaiCompatibleModelDimensionDescription": "モデルの埋め込みディメンション(出力サイズ)。この値についてはプロバイダーのドキュメントを確認してください。一般的な値:384、768、1536、3072。",
-		"openaiKeyLabel": "OpenAIキー:",
+		"openAiKeyLabel": "OpenAI APIキー",
+		"openAiKeyPlaceholder": "OpenAI APIキーを入力してください",
+		"openAiCompatibleBaseUrlLabel": "ベースURL",
+		"openAiCompatibleApiKeyLabel": "APIキー",
+		"openAiCompatibleApiKeyPlaceholder": "APIキーを入力してください",
+		"openAiCompatibleModelDimensionLabel": "埋め込みディメンション:",
+		"modelDimensionLabel": "モデルディメンション",
+		"openAiCompatibleModelDimensionPlaceholder": "例:1536",
+		"openAiCompatibleModelDimensionDescription": "モデルの埋め込みディメンション(出力サイズ)。この値についてはプロバイダーのドキュメントを確認してください。一般的な値:384、768、1536、3072。",
 		"modelLabel": "モデル",
 		"selectModelPlaceholder": "モデルを選択",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrantキー:",
-		"advancedConfigLabel": "詳細設定",
-		"searchMinScoreLabel": "検索スコアのしきい値",
-		"searchMinScoreDescription": "検索結果に必要な最小類似度スコア(0.0-1.0)。値を低くするとより多くの結果が返されますが、関連性が低くなる可能性があります。値を高くすると返される結果は少なくなりますが、より関連性が高くなります。",
-		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット",
-		"startIndexingButton": "インデックス作成を開始",
-		"clearIndexDataButton": "インデックスデータをクリア",
+		"startIndexingButton": "開始",
+		"clearIndexDataButton": "インデックスクリア",
 		"unsavedSettingsMessage": "インデックス作成プロセスを開始する前に設定を保存してください。",
 		"clearDataDialog": {
 			"title": "本当によろしいですか?",
 			"description": "この操作は元に戻せません。コードベースのインデックスデータが完全に削除されます。",
 			"cancelButton": "キャンセル",
 			"confirmButton": "データをクリア"
-		}
+		},
+		"description": "プロジェクトのセマンティック検索を有効にするためのコードベースインデックス設定を構成します。<0>詳細はこちら</0>",
+		"statusTitle": "ステータス",
+		"settingsTitle": "インデックス設定",
+		"disabledMessage": "コードベースインデックスは現在無効になっています。グローバル設定で有効にしてインデックスオプションを構成してください。",
+		"embedderProviderLabel": "エンベッダープロバイダー",
+		"modelPlaceholder": "モデル名を入力",
+		"selectModel": "モデルを選択",
+		"ollamaBaseUrlLabel": "Ollama ベースURL",
+		"qdrantApiKeyLabel": "Qdrant APIキー",
+		"qdrantApiKeyPlaceholder": "Qdrant APIキーを入力(オプション)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "設定の保存に失敗しました",
+		"modelDimensions": "({{dimension}} 次元)",
+		"saveSuccess": "設定が正常に保存されました",
+		"saving": "保存中...",
+		"saveSettings": "保存",
+		"indexingStatuses": {
+			"standby": "スタンバイ",
+			"indexing": "インデックス中",
+			"indexed": "インデックス済み",
+			"error": "エラー"
+		},
+		"close": "閉じる",
+		"advancedConfigLabel": "詳細設定",
+		"searchMinScoreLabel": "検索スコアのしきい値",
+		"searchMinScoreDescription": "検索結果に必要な最小類似度スコア(0.0-1.0)。値を低くするとより多くの結果が返されますが、関連性が低くなる可能性があります。値を高くすると返される結果は少なくなりますが、より関連性が高くなります。",
+		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット"
 	},
 	"autoApprove": {
 		"description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。",

+ 42 - 13
webview-ui/src/i18n/locales/ko/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API 키:",
 		"geminiApiKeyPlaceholder": "Gemini API 키를 입력하세요",
 		"openaiCompatibleProvider": "OpenAI 호환",
-		"openaiCompatibleBaseUrlLabel": "기본 URL:",
-		"openaiCompatibleApiKeyLabel": "API 키:",
-		"openaiCompatibleModelDimensionLabel": "임베딩 차원:",
-		"openaiCompatibleModelDimensionPlaceholder": "예: 1536",
-		"openaiCompatibleModelDimensionDescription": "모델의 임베딩 차원(출력 크기)입니다. 이 값에 대해서는 제공업체의 문서를 확인하세요. 일반적인 값: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "OpenAI 키:",
+		"openAiKeyLabel": "OpenAI API 키",
+		"openAiKeyPlaceholder": "OpenAI API 키를 입력하세요",
+		"openAiCompatibleBaseUrlLabel": "기본 URL",
+		"openAiCompatibleApiKeyLabel": "API 키",
+		"openAiCompatibleApiKeyPlaceholder": "API 키를 입력하세요",
+		"openAiCompatibleModelDimensionLabel": "임베딩 차원:",
+		"modelDimensionLabel": "모델 차원",
+		"openAiCompatibleModelDimensionPlaceholder": "예: 1536",
+		"openAiCompatibleModelDimensionDescription": "모델의 임베딩 차원(출력 크기)입니다. 이 값에 대해서는 제공업체의 문서를 확인하세요. 일반적인 값: 384, 768, 1536, 3072.",
 		"modelLabel": "모델",
 		"selectModelPlaceholder": "모델 선택",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 키:",
-		"advancedConfigLabel": "고급 구성",
-		"searchMinScoreLabel": "검색 점수 임계값",
-		"searchMinScoreDescription": "검색 결과에 필요한 최소 유사도 점수(0.0-1.0). 값이 낮을수록 더 많은 결과가 반환되지만 관련성이 떨어질 수 있습니다. 값이 높을수록 결과는 적지만 관련성이 높은 결과가 반환됩니다.",
-		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정",
-		"startIndexingButton": "인덱싱 시작",
-		"clearIndexDataButton": "인덱스 데이터 지우기",
+		"startIndexingButton": "시작",
+		"clearIndexDataButton": "인덱스 지우기",
 		"unsavedSettingsMessage": "인덱싱 프로세스를 시작하기 전에 설정을 저장해 주세요.",
 		"clearDataDialog": {
 			"title": "확실합니까?",
 			"description": "이 작업은 취소할 수 없습니다. 코드베이스 인덱스 데이터가 영구적으로 삭제됩니다.",
 			"cancelButton": "취소",
 			"confirmButton": "데이터 지우기"
-		}
+		},
+		"description": "프로젝트의 시맨틱 검색을 활성화하기 위한 코드베이스 인덱싱 설정을 구성합니다. <0>자세히 알아보기</0>",
+		"statusTitle": "상태",
+		"settingsTitle": "인덱싱 설정",
+		"disabledMessage": "코드베이스 인덱싱이 현재 비활성화되어 있습니다. 인덱싱 옵션을 구성하려면 전역 설정에서 활성화하세요.",
+		"embedderProviderLabel": "임베더 제공자",
+		"modelPlaceholder": "모델 이름을 입력하세요",
+		"selectModel": "모델 선택",
+		"ollamaBaseUrlLabel": "Ollama 기본 URL",
+		"qdrantApiKeyLabel": "Qdrant API 키",
+		"qdrantApiKeyPlaceholder": "Qdrant API 키를 입력하세요 (선택사항)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "설정을 저장하지 못했습니다",
+		"modelDimensions": "({{dimension}} 차원)",
+		"saveSuccess": "설정이 성공적으로 저장되었습니다",
+		"saving": "저장 중...",
+		"saveSettings": "저장",
+		"indexingStatuses": {
+			"standby": "대기",
+			"indexing": "인덱싱 중",
+			"indexed": "인덱싱됨",
+			"error": "오류"
+		},
+		"close": "닫기",
+		"advancedConfigLabel": "고급 구성",
+		"searchMinScoreLabel": "검색 점수 임계값",
+		"searchMinScoreDescription": "검색 결과에 필요한 최소 유사도 점수(0.0-1.0). 값이 낮을수록 더 많은 결과가 반환되지만 관련성이 떨어질 수 있습니다. 값이 높을수록 결과는 적지만 관련성이 높은 결과가 반환됩니다.",
+		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정"
 	},
 	"autoApprove": {
 		"description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.",

+ 42 - 13
webview-ui/src/i18n/locales/nl/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API-sleutel:",
 		"geminiApiKeyPlaceholder": "Voer uw Gemini API-sleutel in",
 		"openaiCompatibleProvider": "OpenAI-compatibel",
-		"openaiCompatibleBaseUrlLabel": "Basis-URL:",
-		"openaiCompatibleApiKeyLabel": "API-sleutel:",
-		"openaiCompatibleModelDimensionLabel": "Embedding Dimensie:",
-		"openaiCompatibleModelDimensionPlaceholder": "bijv., 1536",
-		"openaiCompatibleModelDimensionDescription": "De embedding dimensie (uitvoergrootte) voor uw model. Controleer de documentatie van uw provider voor deze waarde. Veelvoorkomende waarden: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "OpenAI-sleutel:",
+		"openAiKeyLabel": "OpenAI API-sleutel",
+		"openAiKeyPlaceholder": "Voer uw OpenAI API-sleutel in",
+		"openAiCompatibleBaseUrlLabel": "Basis-URL",
+		"openAiCompatibleApiKeyLabel": "API-sleutel",
+		"openAiCompatibleApiKeyPlaceholder": "Voer uw API-sleutel in",
+		"openAiCompatibleModelDimensionLabel": "Embedding Dimensie:",
+		"modelDimensionLabel": "Model Dimensie",
+		"openAiCompatibleModelDimensionPlaceholder": "bijv., 1536",
+		"openAiCompatibleModelDimensionDescription": "De embedding dimensie (uitvoergrootte) voor uw model. Controleer de documentatie van uw provider voor deze waarde. Veelvoorkomende waarden: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
 		"selectModelPlaceholder": "Selecteer model",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant-sleutel:",
-		"advancedConfigLabel": "Geavanceerde configuratie",
-		"searchMinScoreLabel": "Zoekscore drempel",
-		"searchMinScoreDescription": "Minimale overeenkomstscore (0.0-1.0) vereist voor zoekresultaten. Lagere waarden leveren meer resultaten op, maar zijn mogelijk minder relevant. Hogere waarden leveren minder, maar relevantere resultaten op.",
-		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
-		"startIndexingButton": "Indexering starten",
-		"clearIndexDataButton": "Indexgegevens wissen",
+		"startIndexingButton": "Start",
+		"clearIndexDataButton": "Index wissen",
 		"unsavedSettingsMessage": "Sla je instellingen op voordat je het indexeringsproces start.",
 		"clearDataDialog": {
 			"title": "Weet je het zeker?",
 			"description": "Deze actie kan niet ongedaan worden gemaakt. Dit zal je codebase-indexgegevens permanent verwijderen.",
 			"cancelButton": "Annuleren",
 			"confirmButton": "Gegevens wissen"
-		}
+		},
+		"description": "Configureer codebase-indexeringsinstellingen om semantisch zoeken voor je project in te schakelen. <0>Meer informatie</0>",
+		"statusTitle": "Status",
+		"settingsTitle": "Indexeringsinstellingen",
+		"disabledMessage": "Codebase-indexering is momenteel uitgeschakeld. Schakel het in de algemene instellingen in om indexeringsopties te configureren.",
+		"embedderProviderLabel": "Embedder Provider",
+		"modelPlaceholder": "Voer modelnaam in",
+		"selectModel": "Selecteer een model",
+		"ollamaBaseUrlLabel": "Ollama Basis-URL",
+		"qdrantApiKeyLabel": "Qdrant API-sleutel",
+		"qdrantApiKeyPlaceholder": "Voer je Qdrant API-sleutel in (optioneel)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Kan instellingen niet opslaan",
+		"modelDimensions": "({{dimension}} dimensies)",
+		"saveSuccess": "Instellingen succesvol opgeslagen",
+		"saving": "Opslaan...",
+		"saveSettings": "Opslaan",
+		"indexingStatuses": {
+			"standby": "Stand-by",
+			"indexing": "Indexeren",
+			"indexed": "Geïndexeerd",
+			"error": "Fout"
+		},
+		"close": "Sluiten",
+		"advancedConfigLabel": "Geavanceerde configuratie",
+		"searchMinScoreLabel": "Zoekscore drempel",
+		"searchMinScoreDescription": "Minimale overeenkomstscore (0.0-1.0) vereist voor zoekresultaten. Lagere waarden leveren meer resultaten op, maar zijn mogelijk minder relevant. Hogere waarden leveren minder, maar relevantere resultaten op.",
+		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/pl/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Klucz API:",
 		"geminiApiKeyPlaceholder": "Wprowadź swój klucz API Gemini",
 		"openaiCompatibleProvider": "Kompatybilny z OpenAI",
-		"openaiCompatibleBaseUrlLabel": "Bazowy URL:",
-		"openaiCompatibleApiKeyLabel": "Klucz API:",
-		"openaiCompatibleModelDimensionLabel": "Wymiar Embeddingu:",
-		"openaiCompatibleModelDimensionPlaceholder": "np., 1536",
-		"openaiCompatibleModelDimensionDescription": "Wymiar embeddingu (rozmiar wyjściowy) dla twojego modelu. Sprawdź dokumentację swojego dostawcy, aby uzyskać tę wartość. Typowe wartości: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Klucz OpenAI:",
+		"openAiKeyLabel": "Klucz API OpenAI",
+		"openAiKeyPlaceholder": "Wprowadź swój klucz API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "Bazowy URL",
+		"openAiCompatibleApiKeyLabel": "Klucz API",
+		"openAiCompatibleApiKeyPlaceholder": "Wprowadź swój klucz API",
+		"openAiCompatibleModelDimensionLabel": "Wymiar Embeddingu:",
+		"modelDimensionLabel": "Wymiar modelu",
+		"openAiCompatibleModelDimensionPlaceholder": "np., 1536",
+		"openAiCompatibleModelDimensionDescription": "Wymiar embeddingu (rozmiar wyjściowy) dla twojego modelu. Sprawdź dokumentację swojego dostawcy, aby uzyskać tę wartość. Typowe wartości: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
 		"selectModelPlaceholder": "Wybierz model",
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Klucz Qdrant:",
-		"advancedConfigLabel": "Konfiguracja zaawansowana",
-		"searchMinScoreLabel": "Próg wyniku wyszukiwania",
-		"searchMinScoreDescription": "Minimalny wynik podobieństwa (0.0-1.0) wymagany dla wyników wyszukiwania. Niższe wartości zwracają więcej wyników, ale mogą być mniej trafne. Wyższe wartości zwracają mniej wyników, ale bardziej trafnych.",
-		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
-		"startIndexingButton": "Rozpocznij indeksowanie",
-		"clearIndexDataButton": "Wyczyść dane indeksu",
+		"startIndexingButton": "Rozpocznij",
+		"clearIndexDataButton": "Wyczyść indeks",
 		"unsavedSettingsMessage": "Zapisz swoje ustawienia przed rozpoczęciem procesu indeksowania.",
 		"clearDataDialog": {
 			"title": "Czy jesteś pewien?",
 			"description": "Tej akcji nie można cofnąć. Spowoduje to trwałe usunięcie danych indeksu Twojego kodu.",
 			"cancelButton": "Anuluj",
 			"confirmButton": "Wyczyść dane"
-		}
+		},
+		"description": "Skonfiguruj ustawienia indeksowania bazy kodu, aby włączyć wyszukiwanie semantyczne w swoim projekcie. <0>Dowiedz się więcej</0>",
+		"statusTitle": "Status",
+		"settingsTitle": "Ustawienia indeksowania",
+		"disabledMessage": "Indeksowanie bazy kodu jest obecnie wyłączone. Włącz je w ustawieniach globalnych, aby skonfigurować opcje indeksowania.",
+		"embedderProviderLabel": "Dostawca Embeddera",
+		"modelPlaceholder": "Wprowadź nazwę modelu",
+		"selectModel": "Wybierz model",
+		"ollamaBaseUrlLabel": "Bazowy URL Ollama",
+		"qdrantApiKeyLabel": "Klucz API Qdrant",
+		"qdrantApiKeyPlaceholder": "Wprowadź swój klucz API Qdrant (opcjonalnie)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Nie udało się zapisać ustawień",
+		"modelDimensions": "({{dimension}} wymiarów)",
+		"saveSuccess": "Ustawienia zapisane pomyślnie",
+		"saving": "Zapisywanie...",
+		"saveSettings": "Zapisz",
+		"indexingStatuses": {
+			"standby": "Gotowość",
+			"indexing": "Indeksowanie",
+			"indexed": "Zaindeksowane",
+			"error": "Błąd"
+		},
+		"close": "Zamknij",
+		"advancedConfigLabel": "Konfiguracja zaawansowana",
+		"searchMinScoreLabel": "Próg wyniku wyszukiwania",
+		"searchMinScoreDescription": "Minimalny wynik podobieństwa (0.0-1.0) wymagany dla wyników wyszukiwania. Niższe wartości zwracają więcej wyników, ale mogą być mniej trafne. Wyższe wartości zwracają mniej wyników, ale bardziej trafnych.",
+		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Chave de API:",
 		"geminiApiKeyPlaceholder": "Digite sua chave de API do Gemini",
 		"openaiCompatibleProvider": "Compatível com OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL Base:",
-		"openaiCompatibleApiKeyLabel": "Chave de API:",
-		"openaiCompatibleModelDimensionLabel": "Dimensão de Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "ex., 1536",
-		"openaiCompatibleModelDimensionDescription": "A dimensão de embedding (tamanho de saída) para seu modelo. Verifique a documentação do seu provedor para este valor. Valores comuns: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Chave OpenAI:",
+		"openAiKeyLabel": "Chave de API OpenAI",
+		"openAiKeyPlaceholder": "Digite sua chave de API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "URL Base",
+		"openAiCompatibleApiKeyLabel": "Chave de API",
+		"openAiCompatibleApiKeyPlaceholder": "Digite sua chave de API",
+		"openAiCompatibleModelDimensionLabel": "Dimensão de Embedding:",
+		"modelDimensionLabel": "Dimensão do Modelo",
+		"openAiCompatibleModelDimensionPlaceholder": "ex., 1536",
+		"openAiCompatibleModelDimensionDescription": "A dimensão de embedding (tamanho de saída) para seu modelo. Verifique a documentação do seu provedor para este valor. Valores comuns: 384, 768, 1536, 3072.",
 		"modelLabel": "Modelo",
 		"selectModelPlaceholder": "Selecionar modelo",
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Chave Qdrant:",
-		"advancedConfigLabel": "Configuração Avançada",
-		"searchMinScoreLabel": "Limite de pontuação de busca",
-		"searchMinScoreDescription": "Pontuação mínima de similaridade (0.0-1.0) necessária para os resultados da busca. Valores mais baixos retornam mais resultados, mas podem ser menos relevantes. Valores mais altos retornam menos resultados, mas mais relevantes.",
-		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)",
-		"startIndexingButton": "Iniciar Indexação",
-		"clearIndexDataButton": "Limpar Dados de Índice",
+		"startIndexingButton": "Iniciar",
+		"clearIndexDataButton": "Limpar Índice",
 		"unsavedSettingsMessage": "Por favor, salve suas configurações antes de iniciar o processo de indexação.",
 		"clearDataDialog": {
 			"title": "Tem certeza?",
 			"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"
-		}
+		},
+		"description": "Configure as configurações de indexação da base de código para habilitar a pesquisa semântica do seu projeto. <0>Saiba mais</0>",
+		"statusTitle": "Status",
+		"settingsTitle": "Configurações de Indexação",
+		"disabledMessage": "A indexação da base de código está atualmente desativada. Ative-a nas configurações globais para configurar as opções de indexação.",
+		"embedderProviderLabel": "Provedor de Embedder",
+		"modelPlaceholder": "Insira o nome do modelo",
+		"selectModel": "Selecione um modelo",
+		"ollamaBaseUrlLabel": "URL Base do Ollama",
+		"qdrantApiKeyLabel": "Chave da API Qdrant",
+		"qdrantApiKeyPlaceholder": "Insira sua chave da API Qdrant (opcional)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Falha ao salvar configurações",
+		"modelDimensions": "({{dimension}} dimensões)",
+		"saveSuccess": "Configurações salvas com sucesso",
+		"saving": "Salvando...",
+		"saveSettings": "Salvar",
+		"indexingStatuses": {
+			"standby": "Em espera",
+			"indexing": "Indexando",
+			"indexed": "Indexado",
+			"error": "Erro"
+		},
+		"close": "Fechar",
+		"advancedConfigLabel": "Configuração Avançada",
+		"searchMinScoreLabel": "Limite de pontuação de busca",
+		"searchMinScoreDescription": "Pontuação mínima de similaridade (0.0-1.0) necessária para os resultados da busca. Valores mais baixos retornam mais resultados, mas podem ser menos relevantes. Valores mais altos retornam menos resultados, mas mais relevantes.",
+		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/ru/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Ключ API:",
 		"geminiApiKeyPlaceholder": "Введите свой API-ключ Gemini",
 		"openaiCompatibleProvider": "OpenAI-совместимый",
-		"openaiCompatibleBaseUrlLabel": "Базовый URL:",
-		"openaiCompatibleApiKeyLabel": "Ключ API:",
-		"openaiCompatibleModelDimensionLabel": "Размерность эмбеддинга:",
-		"openaiCompatibleModelDimensionPlaceholder": "напр., 1536",
-		"openaiCompatibleModelDimensionDescription": "Размерность эмбеддинга (размер выходных данных) для вашей модели. Проверьте документацию вашего провайдера для этого значения. Распространенные значения: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Ключ OpenAI:",
+		"openAiKeyLabel": "Ключ API OpenAI",
+		"openAiKeyPlaceholder": "Введите ваш ключ API OpenAI",
+		"openAiCompatibleBaseUrlLabel": "Базовый URL",
+		"openAiCompatibleApiKeyLabel": "Ключ API",
+		"openAiCompatibleApiKeyPlaceholder": "Введите ваш ключ API",
+		"openAiCompatibleModelDimensionLabel": "Размерность эмбеддинга:",
+		"modelDimensionLabel": "Размерность модели",
+		"openAiCompatibleModelDimensionPlaceholder": "напр., 1536",
+		"openAiCompatibleModelDimensionDescription": "Размерность эмбеддинга (размер выходных данных) для вашей модели. Проверьте документацию вашего провайдера для этого значения. Распространенные значения: 384, 768, 1536, 3072.",
 		"modelLabel": "Модель",
 		"selectModelPlaceholder": "Выберите модель",
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Ключ Qdrant:",
-		"advancedConfigLabel": "Расширенная конфигурация",
-		"searchMinScoreLabel": "Порог оценки поиска",
-		"searchMinScoreDescription": "Минимальный балл сходства (0.0-1.0), необходимый для результатов поиска. Более низкие значения возвращают больше результатов, но они могут быть менее релевантными. Более высокие значения возвращают меньше результатов, но более релевантных.",
-		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)",
-		"startIndexingButton": "Начать индексацию",
-		"clearIndexDataButton": "Очистить данные индекса",
+		"startIndexingButton": "Начать",
+		"clearIndexDataButton": "Очистить индекс",
 		"unsavedSettingsMessage": "Пожалуйста, сохрани настройки перед запуском процесса индексации.",
 		"clearDataDialog": {
 			"title": "Вы уверены?",
 			"description": "Это действие нельзя отменить. Оно навсегда удалит данные индекса вашей кодовой базы.",
 			"cancelButton": "Отмена",
 			"confirmButton": "Очистить данные"
-		}
+		},
+		"description": "Настройте параметры индексации кодовой базы для включения семантического поиска в вашем проекте. <0>Узнать больше</0>",
+		"statusTitle": "Статус",
+		"settingsTitle": "Настройки индексации",
+		"disabledMessage": "Индексация кодовой базы в настоящее время отключена. Включите ее в глобальных настройках для настройки параметров индексации.",
+		"embedderProviderLabel": "Провайдер эмбеддера",
+		"modelPlaceholder": "Введите название модели",
+		"selectModel": "Выберите модель",
+		"ollamaBaseUrlLabel": "Базовый URL Ollama",
+		"qdrantApiKeyLabel": "API-ключ Qdrant",
+		"qdrantApiKeyPlaceholder": "Введите ваш API-ключ Qdrant (необязательно)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Не удалось сохранить настройки",
+		"modelDimensions": "({{dimension}} измерений)",
+		"saveSuccess": "Настройки успешно сохранены",
+		"saving": "Сохранение...",
+		"saveSettings": "Сохранить",
+		"indexingStatuses": {
+			"standby": "Ожидание",
+			"indexing": "Индексация",
+			"indexed": "Проиндексировано",
+			"error": "Ошибка"
+		},
+		"close": "Закрыть",
+		"advancedConfigLabel": "Расширенная конфигурация",
+		"searchMinScoreLabel": "Порог оценки поиска",
+		"searchMinScoreDescription": "Минимальный балл сходства (0.0-1.0), необходимый для результатов поиска. Более низкие значения возвращают больше результатов, но они могут быть менее релевантными. Более высокие значения возвращают меньше результатов, но более релевантных.",
+		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)"
 	},
 	"autoApprove": {
 		"description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.",

+ 42 - 13
webview-ui/src/i18n/locales/tr/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API Anahtarı:",
 		"geminiApiKeyPlaceholder": "Gemini API anahtarınızı girin",
 		"openaiCompatibleProvider": "OpenAI Uyumlu",
-		"openaiCompatibleBaseUrlLabel": "Temel URL:",
-		"openaiCompatibleApiKeyLabel": "API Anahtarı:",
-		"openaiCompatibleModelDimensionLabel": "Gömme Boyutu:",
-		"openaiCompatibleModelDimensionPlaceholder": "örn., 1536",
-		"openaiCompatibleModelDimensionDescription": "Modeliniz için gömme boyutu (çıktı boyutu). Bu değer için sağlayıcınızın belgelerine bakın. Yaygın değerler: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "OpenAI Anahtarı:",
+		"openAiKeyLabel": "OpenAI API Anahtarı",
+		"openAiKeyPlaceholder": "OpenAI API anahtarınızı girin",
+		"openAiCompatibleBaseUrlLabel": "Temel URL",
+		"openAiCompatibleApiKeyLabel": "API Anahtarı",
+		"openAiCompatibleApiKeyPlaceholder": "API anahtarınızı girin",
+		"openAiCompatibleModelDimensionLabel": "Gömme Boyutu:",
+		"modelDimensionLabel": "Model Boyutu",
+		"openAiCompatibleModelDimensionPlaceholder": "örn., 1536",
+		"openAiCompatibleModelDimensionDescription": "Modeliniz için gömme boyutu (çıktı boyutu). Bu değer için sağlayıcınızın belgelerine bakın. Yaygın değerler: 384, 768, 1536, 3072.",
 		"modelLabel": "Model",
 		"selectModelPlaceholder": "Model seç",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Anahtarı:",
-		"advancedConfigLabel": "Gelişmiş Yapılandırma",
-		"searchMinScoreLabel": "Arama Skoru Eşiği",
-		"searchMinScoreDescription": "Arama sonuçları için gereken minimum benzerlik puanı (0.0-1.0). Düşük değerler daha fazla sonuç döndürür ancak daha az alakalı olabilir. Yüksek değerler daha az ancak daha alakalı sonuçlar döndürür.",
-		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)",
-		"startIndexingButton": "İndekslemeyi Başlat",
-		"clearIndexDataButton": "İndeks Verilerini Temizle",
+		"startIndexingButton": "Başlat",
+		"clearIndexDataButton": "İndeks Temizle",
 		"unsavedSettingsMessage": "İndeksleme işlemini başlatmadan önce lütfen ayarlarını kaydet.",
 		"clearDataDialog": {
 			"title": "Emin misiniz?",
 			"description": "Bu işlem geri alınamaz. Bu, kod tabanı indeks verilerinizi kalıcı olarak silecektir.",
 			"cancelButton": "İptal",
 			"confirmButton": "Verileri Temizle"
-		}
+		},
+		"description": "Projenizin anlamsal aramasını etkinleştirmek için kod tabanı indeksleme ayarlarını yapılandırın. <0>Daha fazla bilgi</0>",
+		"statusTitle": "Durum",
+		"settingsTitle": "İndeksleme Ayarları",
+		"disabledMessage": "Kod tabanı indeksleme şu anda devre dışı. İndeksleme seçeneklerini yapılandırmak için genel ayarlarda etkinleştirin.",
+		"embedderProviderLabel": "Gömücü Sağlayıcı",
+		"modelPlaceholder": "Model adını girin",
+		"selectModel": "Bir model seçin",
+		"ollamaBaseUrlLabel": "Ollama Temel URL",
+		"qdrantApiKeyLabel": "Qdrant API Anahtarı",
+		"qdrantApiKeyPlaceholder": "Qdrant API anahtarınızı girin (isteğe bağlı)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Ayarlar kaydedilemedi",
+		"modelDimensions": "({{dimension}} boyut)",
+		"saveSuccess": "Ayarlar başarıyla kaydedildi",
+		"saving": "Kaydediliyor...",
+		"saveSettings": "Kaydet",
+		"indexingStatuses": {
+			"standby": "Bekleme",
+			"indexing": "İndeksleniyor",
+			"indexed": "İndekslendi",
+			"error": "Hata"
+		},
+		"close": "Kapat",
+		"advancedConfigLabel": "Gelişmiş Yapılandırma",
+		"searchMinScoreLabel": "Arama Skoru Eşiği",
+		"searchMinScoreDescription": "Arama sonuçları için gereken minimum benzerlik puanı (0.0-1.0). Düşük değerler daha fazla sonuç döndürür ancak daha az alakalı olabilir. Yüksek değerler daha az ancak daha alakalı sonuçlar döndürür.",
+		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/vi/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "Khóa API:",
 		"geminiApiKeyPlaceholder": "Nhập khóa API Gemini của bạn",
 		"openaiCompatibleProvider": "Tương thích OpenAI",
-		"openaiCompatibleBaseUrlLabel": "URL cơ sở:",
-		"openaiCompatibleApiKeyLabel": "Khóa API:",
-		"openaiCompatibleModelDimensionLabel": "Kích thước Embedding:",
-		"openaiCompatibleModelDimensionPlaceholder": "vd., 1536",
-		"openaiCompatibleModelDimensionDescription": "Kích thước embedding (kích thước đầu ra) cho mô hình của bạn. Kiểm tra tài liệu của nhà cung cấp để biết giá trị này. Giá trị phổ biến: 384, 768, 1536, 3072.",
-		"openaiKeyLabel": "Khóa OpenAI:",
+		"openAiKeyLabel": "Khóa API OpenAI",
+		"openAiKeyPlaceholder": "Nhập khóa API OpenAI của bạn",
+		"openAiCompatibleBaseUrlLabel": "URL cơ sở",
+		"openAiCompatibleApiKeyLabel": "Khóa API",
+		"openAiCompatibleApiKeyPlaceholder": "Nhập khóa API của bạn",
+		"openAiCompatibleModelDimensionLabel": "Kích thước Embedding:",
+		"modelDimensionLabel": "Kích thước mô hình",
+		"openAiCompatibleModelDimensionPlaceholder": "vd., 1536",
+		"openAiCompatibleModelDimensionDescription": "Kích thước embedding (kích thước đầu ra) cho mô hình của bạn. Kiểm tra tài liệu của nhà cung cấp để biết giá trị này. Giá trị phổ biến: 384, 768, 1536, 3072.",
 		"modelLabel": "Mô hình",
 		"selectModelPlaceholder": "Chọn mô hình",
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Khóa Qdrant:",
-		"advancedConfigLabel": "Cấu hình nâng cao",
-		"searchMinScoreLabel": "Ngưỡng điểm tìm kiếm",
-		"searchMinScoreDescription": "Điểm tương đồng tối thiểu (0.0-1.0) cần thiết cho kết quả tìm kiếm. Giá trị thấp hơn trả về nhiều kết quả hơn nhưng có thể kém liên quan hơn. Giá trị cao hơn trả về ít kết quả hơn nhưng có liên quan hơn.",
-		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
-		"startIndexingButton": "Bắt đầu lập chỉ mục",
-		"clearIndexDataButton": "Xóa dữ liệu chỉ mục",
+		"startIndexingButton": "Bắt đầu",
+		"clearIndexDataButton": "Xóa chỉ mục",
 		"unsavedSettingsMessage": "Vui lòng lưu cài đặt của bạn trước khi bắt đầu quá trình lập chỉ mục.",
 		"clearDataDialog": {
 			"title": "Bạn có chắc không?",
 			"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"
-		}
+		},
+		"description": "Cấu hình cài đặt lập chỉ mục mã nguồn để kích hoạt tìm kiếm ngữ nghĩa cho dự án của bạn. <0>Tìm hiểu thêm</0>",
+		"statusTitle": "Trạng thái",
+		"settingsTitle": "Cài đặt lập chỉ mục",
+		"disabledMessage": "Lập chỉ mục mã nguồn hiện đang bị tắt. Bật nó trong cài đặt chung để cấu hình các tùy chọn lập chỉ mục.",
+		"embedderProviderLabel": "Nhà cung cấp Embedder",
+		"modelPlaceholder": "Nhập tên mô hình",
+		"selectModel": "Chọn một mô hình",
+		"ollamaBaseUrlLabel": "URL cơ sở Ollama",
+		"qdrantApiKeyLabel": "Khóa API Qdrant",
+		"qdrantApiKeyPlaceholder": "Nhập khóa API Qdrant của bạn (tùy chọn)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "Không thể lưu cài đặt",
+		"modelDimensions": "({{dimension}} chiều)",
+		"saveSuccess": "Cài đặt đã được lưu thành công",
+		"saving": "Đang lưu...",
+		"saveSettings": "Lưu",
+		"indexingStatuses": {
+			"standby": "Chờ",
+			"indexing": "Đang lập chỉ mục",
+			"indexed": "Đã lập chỉ mục",
+			"error": "Lỗi"
+		},
+		"close": "Đóng",
+		"advancedConfigLabel": "Cấu hình nâng cao",
+		"searchMinScoreLabel": "Ngưỡng điểm tìm kiếm",
+		"searchMinScoreDescription": "Điểm tương đồng tối thiểu (0.0-1.0) cần thiết cho kết quả tìm kiếm. Giá trị thấp hơn trả về nhiều kết quả hơn nhưng có thể kém liên quan hơn. Giá trị cao hơn trả về ít kết quả hơn nhưng có liên quan hơn.",
+		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)"
 	},
 	"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.",

+ 42 - 13
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -38,9 +38,14 @@
 	},
 	"codeIndex": {
 		"title": "代码库索引",
+		"description": "配置代码库索引设置以启用项目的语义搜索。<0>了解更多</0>",
+		"statusTitle": "状态",
 		"enableLabel": "启用代码库索引",
 		"enableDescription": "<0>代码库索引</0>是一个实验性功能,使用 AI 嵌入为您的项目创建语义搜索索引。这使 Roo Code 能够通过基于含义而非仅仅关键词来查找相关代码,从而更好地理解和导航大型代码库。",
+		"settingsTitle": "索引设置",
+		"disabledMessage": "代码库索引当前已禁用。在全局设置中启用它以配置索引选项。",
 		"providerLabel": "嵌入提供商",
+		"embedderProviderLabel": "嵌入器提供商",
 		"selectProviderPlaceholder": "选择提供商",
 		"openaiProvider": "OpenAI",
 		"ollamaProvider": "Ollama",
@@ -48,30 +53,54 @@
 		"geminiApiKeyLabel": "API 密钥:",
 		"geminiApiKeyPlaceholder": "输入您的Gemini API密钥",
 		"openaiCompatibleProvider": "OpenAI 兼容",
-		"openaiCompatibleBaseUrlLabel": "基础 URL:",
-		"openaiCompatibleApiKeyLabel": "API 密钥:",
-		"openaiCompatibleModelDimensionLabel": "嵌入维度:",
-		"openaiCompatibleModelDimensionPlaceholder": "例如,1536",
-		"openaiCompatibleModelDimensionDescription": "模型的嵌入维度(输出大小)。请查阅您的提供商文档获取此值。常见值:384、768、1536、3072。",
-		"openaiKeyLabel": "OpenAI 密钥:",
+		"openAiKeyLabel": "OpenAI API 密钥",
+		"openAiKeyPlaceholder": "输入你的 OpenAI API 密钥",
+		"openAiCompatibleBaseUrlLabel": "基础 URL",
+		"openAiCompatibleApiKeyLabel": "API 密钥",
+		"openAiCompatibleApiKeyPlaceholder": "输入你的 API 密钥",
+		"openAiCompatibleModelDimensionLabel": "嵌入维度:",
+		"modelDimensionLabel": "模型维度",
+		"openAiCompatibleModelDimensionPlaceholder": "例如,1536",
+		"openAiCompatibleModelDimensionDescription": "模型的嵌入维度(输出大小)。请查阅您的提供商文档获取此值。常见值:384、768、1536、3072。",
 		"modelLabel": "模型",
+		"modelPlaceholder": "输入模型名称",
+		"selectModel": "选择模型",
 		"selectModelPlaceholder": "选择模型",
 		"ollamaUrlLabel": "Ollama URL:",
+		"ollamaBaseUrlLabel": "Ollama 基础 URL",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 密钥:",
-		"advancedConfigLabel": "高级配置",
-		"searchMinScoreLabel": "搜索分数阈值",
-		"searchMinScoreDescription": "搜索结果所需的最低相似度分数(0.0-1.0)。较低的值返回更多结果,但可能不太相关。较高的值返回较少但更相关的结果。",
-		"searchMinScoreResetTooltip": "恢复默认值 (0.4)",
-		"startIndexingButton": "开始索引",
-		"clearIndexDataButton": "清除索引数据",
+		"qdrantApiKeyLabel": "Qdrant API 密钥",
+		"qdrantApiKeyPlaceholder": "输入你的 Qdrant API 密钥(可选)",
+		"startIndexingButton": "开始",
+		"clearIndexDataButton": "清除索引",
 		"unsavedSettingsMessage": "请先保存设置再开始索引过程。",
 		"clearDataDialog": {
 			"title": "确定要继续吗?",
 			"description": "此操作无法撤消。这将永久删除您的代码库索引数据。",
 			"cancelButton": "取消",
 			"confirmButton": "清除数据"
-		}
+		},
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "保存设置失败",
+		"modelDimensions": "({{dimension}} 维度)",
+		"saveSuccess": "设置保存成功",
+		"saving": "保存中...",
+		"saveSettings": "保存",
+		"indexingStatuses": {
+			"standby": "待机",
+			"indexing": "索引中",
+			"indexed": "已索引",
+			"error": "错误"
+		},
+		"close": "关闭",
+		"advancedConfigLabel": "高级配置",
+		"searchMinScoreLabel": "搜索分数阈值",
+		"searchMinScoreDescription": "搜索结果所需的最低相似度分数(0.0-1.0)。较低的值返回更多结果,但可能不太相关。较高的值返回较少但更相关的结果。",
+		"searchMinScoreResetTooltip": "恢复默认值 (0.4)"
 	},
 	"autoApprove": {
 		"description": "允许 Roo 自动执行操作而无需批准。只有在您完全信任 AI 并了解相关安全风险的情况下才启用这些设置。",

+ 42 - 13
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -48,30 +48,59 @@
 		"geminiApiKeyLabel": "API 金鑰:",
 		"geminiApiKeyPlaceholder": "輸入您的Gemini API金鑰",
 		"openaiCompatibleProvider": "OpenAI 相容",
-		"openaiCompatibleBaseUrlLabel": "基礎 URL:",
-		"openaiCompatibleApiKeyLabel": "API 金鑰:",
-		"openaiCompatibleModelDimensionLabel": "嵌入維度:",
-		"openaiCompatibleModelDimensionPlaceholder": "例如,1536",
-		"openaiCompatibleModelDimensionDescription": "模型的嵌入維度(輸出大小)。請查閱您的提供商文件獲取此值。常見值:384、768、1536、3072。",
-		"openaiKeyLabel": "OpenAI 金鑰:",
+		"openAiKeyLabel": "OpenAI API 金鑰",
+		"openAiKeyPlaceholder": "輸入您的 OpenAI API 金鑰",
+		"openAiCompatibleBaseUrlLabel": "基礎 URL",
+		"openAiCompatibleApiKeyLabel": "API 金鑰",
+		"openAiCompatibleApiKeyPlaceholder": "輸入您的 API 金鑰",
+		"openAiCompatibleModelDimensionLabel": "嵌入維度:",
+		"modelDimensionLabel": "模型維度",
+		"openAiCompatibleModelDimensionPlaceholder": "例如,1536",
+		"openAiCompatibleModelDimensionDescription": "模型的嵌入維度(輸出大小)。請查閱您的提供商文件獲取此值。常見值:384、768、1536、3072。",
 		"modelLabel": "模型",
 		"selectModelPlaceholder": "選擇模型",
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 金鑰:",
-		"advancedConfigLabel": "進階設定",
-		"searchMinScoreLabel": "搜尋分數閾值",
-		"searchMinScoreDescription": "搜尋結果所需的最低相似度分數(0.0-1.0)。較低的值會傳回更多結果,但可能較不相關。較高的值會傳回較少但更相關的結果。",
-		"searchMinScoreResetTooltip": "重設為預設值 (0.4)",
-		"startIndexingButton": "開始索引",
-		"clearIndexDataButton": "清除索引資料",
+		"startIndexingButton": "開始",
+		"clearIndexDataButton": "清除索引",
 		"unsavedSettingsMessage": "請先儲存設定再開始索引程序。",
 		"clearDataDialog": {
 			"title": "確定要繼續嗎?",
 			"description": "此操作無法復原。這將永久刪除您的程式碼庫索引資料。",
 			"cancelButton": "取消",
 			"confirmButton": "清除資料"
-		}
+		},
+		"description": "設定程式碼庫索引設定以啟用專案的語意搜尋。<0>了解更多</0>",
+		"statusTitle": "狀態",
+		"settingsTitle": "索引設定",
+		"disabledMessage": "程式碼庫索引目前已停用。請在全域設定中啟用以設定索引選項。",
+		"embedderProviderLabel": "嵌入器提供者",
+		"modelPlaceholder": "輸入模型名稱",
+		"selectModel": "選擇模型",
+		"ollamaBaseUrlLabel": "Ollama 基礎 URL",
+		"qdrantApiKeyLabel": "Qdrant API 金鑰",
+		"qdrantApiKeyPlaceholder": "輸入您的 Qdrant API 金鑰(選用)",
+		"ollamaUrlPlaceholder": "http://localhost:11434",
+		"openAiCompatibleBaseUrlPlaceholder": "https://api.example.com",
+		"modelDimensionPlaceholder": "1536",
+		"qdrantUrlPlaceholder": "http://localhost:6333",
+		"saveError": "無法儲存設定",
+		"modelDimensions": "({{dimension}} 維度)",
+		"saveSuccess": "設定已成功儲存",
+		"saving": "儲存中...",
+		"saveSettings": "儲存",
+		"indexingStatuses": {
+			"standby": "待命",
+			"indexing": "索引中",
+			"indexed": "已索引",
+			"error": "錯誤"
+		},
+		"close": "關閉",
+		"advancedConfigLabel": "進階設定",
+		"searchMinScoreLabel": "搜尋分數閾值",
+		"searchMinScoreDescription": "搜尋結果所需的最低相似度分數(0.0-1.0)。較低的值會傳回更多結果,但可能較不相關。較高的值會傳回較少但更相關的結果。",
+		"searchMinScoreResetTooltip": "重設為預設值 (0.4)"
 	},
 	"autoApprove": {
 		"description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。",