Merge branch 'RooCodeInc:main' into main

This commit is contained in:
Murilo Pires 2025-07-04 12:35:40 -03:00 committed by GitHub
commit ef61905497
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1851 additions and 2080 deletions

View file

@ -0,0 +1,5 @@
---
roo-code: minor
---
Add copy prompt button to task actions. Based on [@vultrnerd's feedback](https://github.com/Kilo-Org/kilocode/discussions/850).

View file

@ -11,7 +11,6 @@ on:
jobs:
publish-nightly:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read # No tags pushed → read is enough.

View file

@ -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)]))
}

View file

@ -145,7 +145,7 @@ export async function applyDiffToolLegacy(
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(diffResult.content, true)
await cline.diffViewProvider.scrollToFirstDiff()
cline.diffViewProvider.scrollToFirstDiff()
// Check if file is write-protected
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false

View file

@ -159,7 +159,12 @@ Expected structure:
</args>
Original error: ${errorMessage}`
throw new Error(detailedError)
cline.consecutiveMistakeCount++
cline.recordToolError("apply_diff")
TelemetryService.instance.captureDiffApplicationError(cline.taskId, cline.consecutiveMistakeCount)
await cline.say("diff_error", `Failed to parse apply_diff XML: ${errorMessage}`)
pushToolResult(detailedError)
return
}
} else if (legacyPath && typeof legacyDiffContent === "string") {
// Handle legacy parameters (old way)
@ -505,7 +510,7 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(originalContent!, true)
await cline.diffViewProvider.scrollToFirstDiff()
cline.diffViewProvider.scrollToFirstDiff()
// For batch operations, we've already gotten approval
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false

View file

@ -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()

View file

@ -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 {
if (provider.codeIndexManager) {
await provider.codeIndexManager.handleExternalSettingsChange()
// 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,
}
// If now configured and enabled, start indexing automatically
// 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.handleSettingsChange()
// 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()
}
}
} catch (error) {
provider.log(
`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`,
)
}
await provider.postStateToWebview()
// Send success response
await provider.postMessageToWebview({
type: "codeIndexSettingsSaved",
success: true,
settings: globalStateConfig,
})
// Update webview state
await provider.postStateToWebview()
} catch (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",
})
}
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!

View file

@ -122,7 +122,7 @@ export class DiffViewProvider {
}
// Place cursor at the beginning of the diff editor to keep it out of
// the way of the stream animation.
// the way of the stream animation, but do this without stealing focus
const beginningOfDocument = new vscode.Position(0, 0)
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
@ -137,7 +137,7 @@ export class DiffViewProvider {
// Update decorations.
this.activeLineController.setActiveLine(endLine)
this.fadedOverlayController.updateOverlayAfterLine(endLine, document.lineCount)
// Scroll to the current line.
// Scroll to the current line without stealing focus.
const ranges = this.activeDiffEditor?.visibleRanges
if (ranges && ranges.length > 0 && ranges[0].start.line < endLine && ranges[0].end.line > endLine) {
this.scrollEditorToLine(endLine)
@ -504,7 +504,7 @@ export class DiffViewProvider {
// Pre-open the file as a text document to ensure it doesn't open in preview mode
// This fixes issues with files that have custom editor associations (like markdown preview)
vscode.window
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active })
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
.then(() => {
// Execute the diff command after ensuring the file is open as text
return vscode.commands.executeCommand(
@ -552,7 +552,7 @@ export class DiffViewProvider {
for (const part of diffs) {
if (part.added || part.removed) {
// Found the first diff, scroll to it.
// Found the first diff, scroll to it without stealing focus.
this.activeDiffEditor.revealRange(
new vscode.Range(lineCount, 0, lineCount, 0),
vscode.TextEditorRevealType.InCenter,

View file

@ -176,7 +176,7 @@ describe("DiffViewProvider", () => {
// Mock showTextDocument to track when it's called
vi.mocked(vscode.window.showTextDocument).mockImplementation(async (uri, options) => {
callOrder.push("showTextDocument")
expect(options).toEqual({ preview: false, viewColumn: vscode.ViewColumn.Active })
expect(options).toEqual({ preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
return mockEditor as any
})
@ -208,10 +208,10 @@ describe("DiffViewProvider", () => {
// Verify that showTextDocument was called before executeCommand
expect(callOrder).toEqual(["showTextDocument", "executeCommand"])
// Verify that showTextDocument was called with preview: false
// Verify that showTextDocument was called with preview: false and preserveFocus: true
expect(vscode.window.showTextDocument).toHaveBeenCalledWith(
expect.objectContaining({ fsPath: `${mockCwd}/test.md` }),
{ preview: false, viewColumn: vscode.ViewColumn.Active },
{ preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true },
)
// Verify that the diff command was executed

View file

@ -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

View file

@ -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()
})
})
})

View file

@ -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,59 +240,53 @@ 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)) {
return true
}
// Authentication changes
if (this.embedderProvider === "openai") {
const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
if (prevOpenAiKey !== currentOpenAiKey) {
return true
}
}
if (this.embedderProvider === "ollama") {
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
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
) {
return true
}
}
if (this.embedderProvider === "gemini") {
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
if (prevGeminiApiKey !== currentGeminiApiKey) {
return true
}
}
// Qdrant configuration changes
// 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
}
if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
return true
}
if (
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey
) {
return true
}
// Check for OpenAI Compatible modelDimension changes
if (this.embedderProvider === "openai-compatible" || prevProvider === "openai-compatible") {
if (prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension) {
return true
}
}
if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
return true
}
// Vector dimension changes (still important for compatibility)
if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) {
return true
}
}
return false

View file

@ -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()
}
}
}

View file

@ -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({

View file

@ -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<

View file

@ -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({

View file

@ -0,0 +1,640 @@
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 { useRooPortal } from "@src/components/ui/hooks/useRooPortal"
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) : []
}
const portalContainer = useRooPortal("roo-portal")
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}
container={portalContainer}>
<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>
)
}

View file

@ -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(

View file

@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"
import type { HistoryItem } from "@roo-code/types"
import { vscode } from "@/utils/vscode"
import { useCopyToClipboard } from "@/utils/clipboard"
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
import { IconButton } from "./IconButton"
@ -18,6 +19,7 @@ interface TaskActionsProps {
export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
const { t } = useTranslation()
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
return (
<div className="flex flex-row gap-1">
@ -28,6 +30,14 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
disabled={buttonsDisabled}
onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
/>
{item?.task && (
<IconButton
iconClass={showCopyFeedback ? "codicon-check" : "codicon-copy"}
title={t("history:copyPrompt")}
disabled={buttonsDisabled}
onClick={(e) => copyWithFeedback(item.task, e)}
/>
)}
{!!item?.size && item.size > 0 && (
<>
<div className="flex items-center">

View file

@ -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()
})

View file

@ -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>
)}
</>
)
}

View file

@ -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,31 @@ 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) => {
const newEnabledState = e.target.checked
if (setCachedStateField && codebaseIndexConfig) {
setCachedStateField("codebaseIndexConfig", {
...codebaseIndexConfig,
codebaseIndexEnabled: newEnabledState,
})
}
}}>
<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>
)

View file

@ -320,7 +320,9 @@ 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 })
if (codebaseIndexConfig) {
vscode.postMessage({ type: "codebaseIndexEnabled", bool: codebaseIndexConfig.codebaseIndexEnabled })
}
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
setChangeDetected(false)
}
@ -689,12 +691,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}
/>
)}

View file

@ -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()
})
})
})

View file

@ -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.",

View file

@ -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."

View file

@ -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.",

View file

@ -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.",

View file

@ -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."

View file

@ -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 पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।",

View file

@ -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.",

View file

@ -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.",

View file

@ -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を完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。",

View file

@ -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를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.",

View file

@ -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.",

View file

@ -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.",

View file

@ -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.",

View file

@ -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 автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.",

View file

@ -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.",

View file

@ -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.",

View file

@ -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 并了解相关安全风险的情况下才启用这些设置。",

View file

@ -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 並了解相關安全風險時啟用這些設定。",

View file

@ -6,6 +6,14 @@ import { mentionRegex } from "@roo/context-mentions"
import { escapeSpaces } from "./path-mentions"
/**
* Gets the description for a mode, prioritizing description > whenToUse > roleDefinition
* and taking only the first line
*/
function getModeDescription(mode: ModeConfig): string {
return (mode.description || mode.whenToUse || mode.roleDefinition).split("\n")[0]
}
export interface SearchResult {
path: string
type: "file" | "folder"
@ -137,13 +145,13 @@ export function getContextMenuOptions(
type: ContextMenuOptionType.Mode,
value: result.item.original.slug,
label: result.item.original.name,
description: (result.item.original.whenToUse || result.item.original.roleDefinition).split("\n")[0],
description: getModeDescription(result.item.original),
}))
: modes.map((mode) => ({
type: ContextMenuOptionType.Mode,
value: mode.slug,
label: mode.name,
description: (mode.description || mode.whenToUse || mode.roleDefinition).split("\n")[0],
description: getModeDescription(mode),
}))
return matchingModes.length > 0 ? matchingModes : [{ type: ContextMenuOptionType.NoResults }]