fix: prevent UI freeze during code indexing by implementing worker threads and throttling (#4188)

This commit is contained in:
hannesrudolph 2025-07-02 14:32:11 -06:00
parent 1be6fce1a6
commit ace9510bcd
31 changed files with 2011 additions and 20 deletions

View file

@ -1810,6 +1810,17 @@ export const webviewMessageHandler = async (
}
break
}
case "cancelIndexing": {
try {
const manager = provider.codeIndexManager!
if (manager.isFeatureEnabled) {
await manager.cancelIndexing()
}
} catch (error) {
provider.log(`Error cancelling indexing: ${error instanceof Error ? error.message : String(error)}`)
}
break
}
case "clearIndexData": {
try {
const manager = provider.codeIndexManager!

View file

@ -0,0 +1,304 @@
import { describe, it, expect, vi, beforeEach, afterEach, vitest } from "vitest"
import { DirectoryScanner } from "../processors/scanner"
import { WorkerPool } from "../workers/worker-pool"
import * as vscode from "vscode"
import * as fs from "fs/promises"
import { listFiles } from "../../glob/list-files"
import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController"
// Mock dependencies
vitest.mock("vscode")
vitest.mock("fs/promises", () => ({
stat: vitest.fn(),
readFile: vitest.fn(),
}))
vitest.mock("../workers/worker-pool")
vitest.mock("../../glob/list-files")
vitest.mock("../../../core/ignore/RooIgnoreController", () => ({
RooIgnoreController: vitest.fn().mockImplementation(() => ({
initialize: vitest.fn().mockResolvedValue(undefined),
filterPaths: vitest.fn().mockImplementation((paths) => paths),
})),
}))
describe("DirectoryScanner Cancellation", () => {
let scanner: DirectoryScanner
let mockWorkerPool: any
let mockEmbedder: any
let mockVectorStore: any
let mockCodeParser: any
let mockCacheManager: any
let mockIgnore: any
let abortController: AbortController
beforeEach(() => {
// Mock worker pool
mockWorkerPool = {
execute: vi.fn().mockResolvedValue({
content: "file content",
hash: "abc123",
}),
shutdown: vi.fn().mockResolvedValue(undefined),
}
vi.mocked(WorkerPool).mockImplementation(() => mockWorkerPool)
// Mock dependencies
mockEmbedder = {
createEmbeddings: vi.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]] }),
}
mockVectorStore = {
upsertPoints: vi.fn().mockResolvedValue(undefined),
deletePointsByFilePath: vi.fn().mockResolvedValue(undefined),
deletePointsByMultipleFilePaths: vi.fn().mockResolvedValue(undefined),
}
mockCodeParser = {
parseFile: vi.fn().mockResolvedValue([
{
content: "test code",
file_path: "/test/file.ts",
start_line: 1,
end_line: 10,
},
]),
}
mockCacheManager = {
getHash: vi.fn().mockReturnValue(null),
updateHash: vi.fn().mockResolvedValue(undefined),
deleteHash: vi.fn().mockResolvedValue(undefined),
getAllHashes: vi.fn().mockReturnValue({}),
}
mockIgnore = {
ignores: vi.fn().mockReturnValue(false),
}
// Mock listFiles - returns [files[], hasMore: boolean]
vi.mocked(listFiles).mockResolvedValue([["file1.ts", "file2.ts", "file3.ts"], false])
// RooIgnoreController is already mocked in the module mock above
// Mock file system
vi.mocked(fs.stat).mockResolvedValue({
isDirectory: () => false,
isFile: () => true,
size: 1000,
} as any)
// Mock vscode.workspace.fs
vi.mocked(vscode.workspace.fs.readFile).mockResolvedValue(Buffer.from("file content") as any)
// Create scanner
scanner = new DirectoryScanner(mockEmbedder, mockVectorStore, mockCodeParser, mockCacheManager, mockIgnore)
abortController = new AbortController()
})
afterEach(() => {
vi.clearAllMocks()
})
it("should stop processing when signal is aborted", async () => {
// Mock multiple files
vi.mocked(listFiles).mockResolvedValue([["file1.ts", "file2.ts", "file3.ts", "file4.ts", "file5.ts"], false])
// Track processing
let processedCount = 0
// Mock worker pool to simulate slower processing and check abort signal
mockWorkerPool.execute.mockImplementation(async () => {
processedCount++
// Abort after processing 2 files
if (processedCount === 2) {
// Abort immediately
abortController.abort()
}
// Simulate processing delay
await new Promise((resolve) => setTimeout(resolve, 10))
// Check if aborted
if (abortController.signal.aborted) {
throw new Error("Indexing cancelled")
}
return {
content: "file content",
hash: "abc123",
}
})
// Start scanning
const scanPromise = scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Should throw cancellation error
await expect(scanPromise).rejects.toThrow("Indexing cancelled")
// Should have started processing but not completed all files
expect(processedCount).toBeGreaterThan(0)
expect(processedCount).toBeLessThan(5)
})
it("should throw error when cancelled during file processing", async () => {
// Mock multiple files to ensure processing takes time
vi.mocked(listFiles).mockResolvedValue([["file1.ts", "file2.ts", "file3.ts"], false])
// Make worker pool check abort signal
let callCount = 0
mockWorkerPool.execute.mockImplementation(async (task: any) => {
callCount++
// Process first file normally
if (callCount === 1) {
await new Promise((resolve) => setTimeout(resolve, 10))
return {
content: "file content",
hash: "abc123",
}
}
// Abort immediately on second file
abortController.abort()
// Wait a bit then check signal
await new Promise((resolve) => setTimeout(resolve, 5))
if (abortController.signal.aborted) {
throw new Error("Indexing cancelled")
}
return {
content: "file content",
hash: "abc123",
}
})
// Start scanning
const scanPromise = scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Should reject with cancellation error
await expect(scanPromise).rejects.toThrow("Indexing cancelled")
// Should have attempted to process at least one file
expect(callCount).toBeGreaterThan(0)
})
it("should clean up worker pool on disposal", async () => {
// Scan without cancelling
await scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Dispose scanner
await scanner.dispose()
// Worker pool should be shut down
expect(mockWorkerPool.shutdown).toHaveBeenCalled()
})
it("should complete successfully if not cancelled", async () => {
// Mock simple file structure
vi.mocked(listFiles).mockResolvedValue([["file1.ts", "file2.ts"], false])
// Scan without cancelling
const result = await scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Should complete successfully
expect(result.codeBlocks).toHaveLength(2)
expect(result.stats.processed).toBe(2)
expect(result.stats.skipped).toBe(0)
// Should have parsed both files
expect(mockCodeParser.parseFile).toHaveBeenCalledTimes(2)
})
it("should handle cancellation during batch processing", async () => {
// Mock many files to trigger batch processing (BATCH_SEGMENT_THRESHOLD is 50)
const manyFiles = Array(60)
.fill(null)
.map((_, i) => `file${i}.ts`)
vi.mocked(listFiles).mockResolvedValue([manyFiles, false])
// Track embedding calls
let embeddingCallCount = 0
let shouldAbort = false
mockEmbedder.createEmbeddings.mockImplementation(async (texts: string[]) => {
embeddingCallCount++
// First batch should succeed, second should be cancelled
if (embeddingCallCount === 1) {
// Let first batch complete
return {
embeddings: texts.map(() => [0.1, 0.2, 0.3]),
}
} else {
// Simulate delay for second batch
await new Promise((resolve) => setTimeout(resolve, 100))
// Check abort signal
if (shouldAbort || abortController.signal.aborted) {
throw new Error("Indexing cancelled")
}
return {
embeddings: texts.map(() => [0.1, 0.2, 0.3]),
}
}
})
// Start scanning
const scanPromise = scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Abort after first batch completes
setTimeout(() => {
shouldAbort = true
abortController.abort()
}, 50)
// Should complete successfully since cancellation happens after processing
const result = await scanPromise
// Should have processed files
expect(result.codeBlocks.length).toBeGreaterThan(0)
expect(embeddingCallCount).toBeGreaterThanOrEqual(1)
})
it("should respect abort signal in listFiles", async () => {
// Make listFiles check abort signal
vi.mocked(listFiles).mockImplementation(async () => {
// Check abort signal
if (abortController.signal.aborted) {
throw new Error("Indexing cancelled")
}
// Simulate delay
await new Promise((resolve) => setTimeout(resolve, 100))
return [["file1.ts", "file2.ts"], false]
})
// Start scanning
const scanPromise = scanner.scanDirectory("/test/workspace", undefined, undefined, undefined, {
signal: abortController.signal,
})
// Abort quickly
setTimeout(() => abortController.abort(), 50)
// Should reject
await expect(scanPromise).rejects.toThrow("Indexing cancelled")
// Should not have reached file parsing
expect(mockCodeParser.parseFile).not.toHaveBeenCalled()
})
})

View file

@ -0,0 +1,237 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { CodeIndexStateManager, IndexingState } from "../state-manager"
// Mock vscode module
vi.mock("vscode", () => ({
EventEmitter: class EventEmitter {
private listeners: Map<string, Function[]> = new Map()
event = (listener: Function) => {
if (!this.listeners.has("event")) {
this.listeners.set("event", [])
}
this.listeners.get("event")!.push(listener)
return { dispose: () => {} }
}
fire(data: any) {
const eventListeners = this.listeners.get("event") || []
eventListeners.forEach((listener) => listener(data))
}
dispose() {
this.listeners.clear()
}
},
}))
describe("CodeIndexStateManager Throttling", () => {
let stateManager: CodeIndexStateManager
let progressUpdateHandler: ReturnType<typeof vi.fn>
beforeEach(() => {
vi.useFakeTimers()
stateManager = new CodeIndexStateManager()
progressUpdateHandler = vi.fn()
stateManager.onProgressUpdate(progressUpdateHandler)
})
afterEach(() => {
vi.useRealTimers()
vi.clearAllMocks()
if (stateManager) {
stateManager.dispose()
}
})
it("should throttle rapid state updates", () => {
// Make multiple rapid updates
for (let i = 0; i < 10; i++) {
stateManager.reportBlockIndexingProgress(i, 100)
}
// Should emit immediately for the first update
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
expect(progressUpdateHandler).toHaveBeenCalledWith(
expect.objectContaining({
systemStatus: "Indexing",
processedItems: 0,
totalItems: 100,
}),
)
// Advance time to trigger throttled emit
vi.advanceTimersByTime(500)
// Should emit the pending update with the latest state
expect(progressUpdateHandler).toHaveBeenCalledTimes(2)
expect(progressUpdateHandler).toHaveBeenLastCalledWith(
expect.objectContaining({
systemStatus: "Indexing",
processedItems: 9,
totalItems: 100,
}),
)
})
it("should emit updates after throttle interval", () => {
// First update
stateManager.reportBlockIndexingProgress(10, 100)
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
// Advance time to clear throttle
vi.advanceTimersByTime(500)
// Second update after throttle interval
stateManager.reportBlockIndexingProgress(20, 100)
// Should emit immediately since throttle period has passed
expect(progressUpdateHandler).toHaveBeenCalledTimes(2)
expect(progressUpdateHandler).toHaveBeenLastCalledWith(
expect.objectContaining({
processedItems: 20,
totalItems: 100,
}),
)
})
it("should batch multiple updates within throttle interval", () => {
// Multiple updates in quick succession
stateManager.setSystemState("Indexing", "Starting...")
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
stateManager.reportBlockIndexingProgress(5, 100)
stateManager.reportFileQueueProgress(1, 10, "file1.ts")
stateManager.reportBlockIndexingProgress(10, 100)
// Should not emit more during throttle period
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
// Advance time to trigger emit
vi.advanceTimersByTime(500)
// Should emit the last pending update
expect(progressUpdateHandler).toHaveBeenCalledTimes(2)
expect(progressUpdateHandler).toHaveBeenLastCalledWith(
expect.objectContaining({
systemStatus: "Indexing",
processedItems: 10,
totalItems: 100,
currentItemUnit: "blocks",
}),
)
})
it("should clear pending updates on dispose", () => {
// Make an update
stateManager.setSystemState("Indexing")
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
// Make another update that will be pending
stateManager.reportBlockIndexingProgress(5, 100)
// Dispose before throttle interval
stateManager.dispose()
// Advance time
vi.advanceTimersByTime(1000)
// Should not emit after disposal
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
})
it("should handle state transitions correctly", () => {
// Start indexing
stateManager.setSystemState("Indexing", "Starting indexing...")
expect(progressUpdateHandler).toHaveBeenCalledWith(
expect.objectContaining({
systemStatus: "Indexing",
message: "Starting indexing...",
}),
)
// Report progress
stateManager.reportBlockIndexingProgress(50, 100)
// Advance time
vi.advanceTimersByTime(500)
// Complete indexing
stateManager.setSystemState("Indexed", "Indexing complete")
expect(progressUpdateHandler).toHaveBeenLastCalledWith(
expect.objectContaining({
systemStatus: "Indexed",
message: "Indexing complete",
processedItems: 0, // Reset on state change
totalItems: 0,
}),
)
})
it("should maintain state consistency across throttled updates", () => {
// Initial state
expect(stateManager.state).toBe("Standby")
expect(stateManager.getCurrentStatus()).toEqual({
systemStatus: "Standby",
message: "",
processedItems: 0,
totalItems: 0,
currentItemUnit: "blocks",
})
// Multiple updates
stateManager.setSystemState("Indexing")
stateManager.reportBlockIndexingProgress(5, 50)
// State should be updated immediately
expect(stateManager.getCurrentStatus()).toEqual({
systemStatus: "Indexing",
message: "Indexed 5 / 50 blocks found",
processedItems: 5,
totalItems: 50,
currentItemUnit: "blocks",
})
// More updates
stateManager.reportBlockIndexingProgress(10, 50)
stateManager.reportFileQueueProgress(2, 10, "test.ts")
// Advance time to trigger emit
vi.advanceTimersByTime(500)
// Final state should reflect the last update
expect(stateManager.getCurrentStatus()).toEqual({
systemStatus: "Indexing",
message: "Processing 2 / 10 files. Current: test.ts",
processedItems: 2,
totalItems: 10,
currentItemUnit: "files",
})
})
it("should handle rapid file queue updates", () => {
const files = ["file1.ts", "file2.ts", "file3.ts", "file4.ts", "file5.ts"]
// Rapid file processing updates
files.forEach((file, index) => {
stateManager.reportFileQueueProgress(index + 1, files.length, file)
})
// Should only emit once immediately
expect(progressUpdateHandler).toHaveBeenCalledTimes(1)
// Advance time
vi.advanceTimersByTime(500)
// Should emit the final state
expect(progressUpdateHandler).toHaveBeenCalledTimes(2)
expect(progressUpdateHandler).toHaveBeenLastCalledWith(
expect.objectContaining({
message: "Finished processing 5 files from queue.",
processedItems: 5,
totalItems: 5,
}),
)
})
})

View file

@ -0,0 +1,360 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { WorkerPool } from "../workers/worker-pool"
import { EventEmitter } from "events"
// Mock worker_threads module
vi.mock("worker_threads")
// Mock os module
vi.mock("os", () => ({
cpus: vi.fn(() => new Array(8)),
}))
describe("WorkerPool", () => {
let pool: WorkerPool | null = null
let mockWorkers: any[] = []
let Worker: any
beforeEach(async () => {
vi.clearAllTimers()
vi.clearAllMocks()
mockWorkers = []
pool = null
// Get the mocked Worker class
const workerThreads = await import("worker_threads")
Worker = workerThreads.Worker
// Reset and re-implement the Worker mock
vi.mocked(Worker).mockClear()
vi.mocked(Worker).mockImplementation((filename: string | URL) => {
const worker = new EventEmitter()
;(worker as any).terminate = vi.fn().mockResolvedValue(undefined)
;(worker as any).postMessage = vi.fn()
;(worker as any).scriptPath = filename
mockWorkers.push(worker)
// Simulate worker ready after a short delay
setTimeout(() => worker.emit("online"), 10)
return worker as any
})
})
afterEach(async () => {
// Clean up any pools created during tests
if (pool) {
try {
await pool.shutdown()
} catch (e) {
// Ignore shutdown errors in tests
}
pool = null
}
// Clear all mocks and timers
vi.clearAllMocks()
vi.clearAllTimers()
mockWorkers = []
})
describe("initialization", () => {
it("should create workers with specified concurrency", () => {
pool = new WorkerPool("/path/to/worker.js", 4)
expect(Worker).toHaveBeenCalledTimes(4)
expect(Worker).toHaveBeenCalledWith("/path/to/worker.js")
})
it("should use default concurrency based on CPU count", async () => {
// os.cpus is already mocked to return 8 CPUs
// Create new pool with mocked CPU count
const testPool = new WorkerPool("/path/to/worker.js")
// Should create workers based on CPU count - 1
expect(Worker).toHaveBeenCalledTimes(7) // Math.max(1, 8 - 1)
expect(mockWorkers.length).toBe(7)
// Clean up
await testPool.shutdown()
})
})
describe("task execution", () => {
beforeEach(() => {
pool = new WorkerPool("/path/to/worker.js", 2)
})
it("should execute tasks and return results", async () => {
const task = { type: "process", data: "test" }
const expectedResult = { success: true, data: "processed" }
// Set up worker to respond
const resultPromise = pool!.execute(task)
// Wait for worker to be assigned
await new Promise((resolve) => setTimeout(resolve, 20))
// Simulate worker response
const worker = mockWorkers[0]
const messageHandler = worker.listeners("message")[0] as Function
messageHandler(expectedResult)
const result = await resultPromise
expect(result).toEqual("processed") // WorkerPool returns data, not the full result
expect(worker.postMessage).toHaveBeenCalledWith(task)
})
it("should handle worker errors", async () => {
const task = { type: "process", data: "test" }
const error = new Error("Worker error")
// Set up worker to error
const resultPromise = pool!.execute(task)
// Wait for worker to be assigned
await new Promise((resolve) => setTimeout(resolve, 20))
// Simulate worker error
const worker = mockWorkers[0]
worker.emit("error", error)
await expect(resultPromise).rejects.toThrow("Worker error")
})
it("should queue tasks when all workers are busy", async () => {
const tasks = [
{ type: "task1" },
{ type: "task2" },
{ type: "task3" }, // This should be queued
]
// Start all tasks
const promises = tasks.map((task) => pool!.execute(task))
// Wait for workers to be assigned
await new Promise((resolve) => setTimeout(resolve, 20))
// Only 2 workers, so first 2 tasks should be processing
expect(mockWorkers[0].postMessage).toHaveBeenCalledWith(tasks[0])
expect(mockWorkers[1].postMessage).toHaveBeenCalledWith(tasks[1])
expect(mockWorkers[0].postMessage).toHaveBeenCalledTimes(1)
expect(mockWorkers[1].postMessage).toHaveBeenCalledTimes(1)
// Complete first task
const messageHandler0 = mockWorkers[0].listeners("message")[0] as Function
messageHandler0({ success: true, data: "task1" })
// Wait for queue processing
await new Promise((resolve) => setTimeout(resolve, 10))
// Third task should now be processed by first worker
expect(mockWorkers[0].postMessage).toHaveBeenCalledTimes(2)
expect(mockWorkers[0].postMessage).toHaveBeenLastCalledWith(tasks[2])
// Complete remaining tasks
messageHandler0({ success: true, data: "task3" })
const messageHandler1 = mockWorkers[1].listeners("message")[0] as Function
messageHandler1({ success: true, data: "task2" })
const results = await Promise.all(promises)
expect(results).toEqual(["task1", "task2", "task3"])
})
it("should handle task cancellation via queue clearing on shutdown", async () => {
// Queue multiple tasks to ensure some are pending
const tasks = [
pool!.execute({ type: "task1" }),
pool!.execute({ type: "task2" }),
pool!.execute({ type: "task3" }), // This will be queued
]
// Shutdown immediately to catch queued tasks
const shutdownPromise = pool!.shutdown()
// At least the queued task should be rejected
await expect(Promise.all(tasks)).rejects.toThrow("Worker pool is shutting down")
await shutdownPromise
})
it("should restart worker on error", async () => {
const task = { type: "test" }
// Execute a task
const taskPromise = pool!.execute(task)
// Wait for worker assignment
await new Promise((resolve) => setTimeout(resolve, 20))
const originalWorker = mockWorkers[0]
const originalWorkerCount = mockWorkers.length
// Simulate worker error (which triggers replacement)
originalWorker.emit("error", new Error("Worker crashed"))
// Wait for the task to be rejected
await expect(taskPromise).rejects.toThrow("Worker crashed")
// Worker should be terminated
expect(originalWorker.terminate).toHaveBeenCalled()
// A new worker should be created to replace the failed one
expect(mockWorkers.length).toBe(originalWorkerCount + 1)
})
})
describe("shutdown", () => {
beforeEach(() => {
pool = new WorkerPool("/path/to/worker.js", 2)
})
it("should terminate all workers", async () => {
await pool!.shutdown()
expect(mockWorkers[0].terminate).toHaveBeenCalled()
expect(mockWorkers[1].terminate).toHaveBeenCalled()
})
it("should reject pending tasks on shutdown", async () => {
// Create a new pool with limited workers
const testPool = new WorkerPool("/path/to/worker.js", 1)
// Wait for workers to be ready
await new Promise((resolve) => setTimeout(resolve, 20))
// Start a task that will occupy the worker
const firstTask = testPool.execute({ type: "task1" })
// Wait a moment to ensure first task is assigned
await new Promise((resolve) => setTimeout(resolve, 10))
// Start a second task that will be queued
const secondTask = testPool.execute({ type: "task2" }).catch((e) => e)
// Immediately shutdown - this should reject the queued task
const shutdownPromise = testPool.shutdown()
// Complete the first task to allow shutdown to proceed
const worker = mockWorkers[mockWorkers.length - 1]
const messageHandler = worker.listeners("message")[0]
if (messageHandler) {
messageHandler({ success: true, data: "completed" })
}
// Wait for shutdown
await shutdownPromise
// First task should complete, second should be rejected
const firstResult = await firstTask
const secondResult = await secondTask
expect(firstResult).toBe("completed")
expect(secondResult).toBeInstanceOf(Error)
expect((secondResult as Error).message).toBe("Worker pool is shutting down")
})
it("should handle termination errors gracefully", async () => {
// Wait for workers to be created
await new Promise((resolve) => setTimeout(resolve, 20))
// Make one worker fail to terminate
mockWorkers[0].terminate.mockRejectedValue(new Error("Termination failed"))
// Shutdown should complete despite termination error
await expect(pool!.shutdown()).resolves.not.toThrow()
// Both workers should have termination attempted
expect(mockWorkers[0].terminate).toHaveBeenCalled()
expect(mockWorkers[1].terminate).toHaveBeenCalled()
})
})
describe("error handling", () => {
beforeEach(() => {
pool = new WorkerPool("/path/to/worker.js", 1)
})
it("should handle worker initialization errors", async () => {
// Execute a task first
const taskPromise = pool!.execute({ type: "test" })
// Wait for worker assignment
await new Promise((resolve) => setTimeout(resolve, 20))
// Make worker emit error
const worker = mockWorkers[0]
worker.emit("error", new Error("Init error"))
// Task should be rejected
await expect(taskPromise).rejects.toThrow("Init error")
})
it("should handle unexpected worker messages", async () => {
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
// Execute a task to activate a worker
const taskPromise = pool!.execute({ type: "test" }).catch((e) => e)
// Wait for worker assignment
await new Promise((resolve) => setTimeout(resolve, 20))
// Send unexpected message (not a WorkerResult object)
const worker = mockWorkers[0]
// Send a message that will cause an error
worker.emit("message", { success: false, error: "Unknown worker error" })
// The task should be rejected with the error
const result = await taskPromise
expect(result).toBeInstanceOf(Error)
expect((result as Error).message).toBe("Unknown worker error")
consoleWarnSpy.mockRestore()
})
})
describe("performance", () => {
it("should process tasks concurrently", async () => {
pool = new WorkerPool("/path/to/worker.js", 3)
const tasks = Array(6)
.fill(null)
.map((_, i) => ({ type: `task${i}` }))
// Start all tasks
const promises = tasks.map((task) => pool!.execute(task))
// Wait for initial assignment
await new Promise((resolve) => setTimeout(resolve, 20))
// Complete first batch of tasks
for (let i = 0; i < 3; i++) {
const worker = mockWorkers[i]
const handler = worker.listeners("message")[0] as Function
handler({ success: true, data: `result${i}` })
}
// Wait a bit for queue processing
await new Promise((resolve) => setTimeout(resolve, 10))
// Complete second batch
for (let i = 0; i < 3; i++) {
const worker = mockWorkers[i]
const handler = worker.listeners("message")[0] as Function
handler({ success: true, data: `result${i + 3}` })
}
const results = await Promise.all(promises)
// Verify all tasks completed
expect(results).toHaveLength(6)
expect(results).toEqual(["result0", "result1", "result2", "result3", "result4", "result5"])
// Verify concurrent processing (3 workers should each process 2 tasks)
for (let i = 0; i < 3; i++) {
expect(mockWorkers[i].postMessage).toHaveBeenCalledTimes(2)
}
})
})
})

View file

@ -164,12 +164,25 @@ export class CodeIndexManager {
}
}
/**
* Cancels the current indexing operation.
*/
public async cancelIndexing(): Promise<void> {
if (!this.isFeatureEnabled) {
return
}
if (this._orchestrator) {
this._orchestrator.cancelIndexing()
}
}
/**
* Cleans up the manager instance.
*/
public dispose(): void {
public async dispose(): Promise<void> {
if (this._orchestrator) {
this.stopWatcher()
await this._orchestrator.dispose()
}
this._stateManager.dispose()
}

View file

@ -12,6 +12,7 @@ import { CacheManager } from "./cache-manager"
export class CodeIndexOrchestrator {
private _fileWatcherSubscriptions: vscode.Disposable[] = []
private _isProcessing: boolean = false
private abortController?: AbortController
constructor(
private readonly configManager: CodeIndexConfigManager,
@ -106,6 +107,7 @@ export class CodeIndexOrchestrator {
}
this._isProcessing = true
this.abortController = new AbortController()
this.stateManager.setSystemState("Indexing", "Initializing services...")
try {
@ -142,6 +144,7 @@ export class CodeIndexOrchestrator {
},
handleBlocksIndexed,
handleFileParsed,
{ signal: this.abortController.signal },
)
if (!result) {
@ -169,18 +172,39 @@ export class CodeIndexOrchestrator {
this.stateManager.setSystemState("Indexed", "File watcher started.")
} catch (error: any) {
console.error("[CodeIndexOrchestrator] Error during indexing:", error)
try {
await this.vectorStore.clearCollection()
} catch (cleanupError) {
console.error("[CodeIndexOrchestrator] Failed to clean up after error:", cleanupError)
// Check if error was due to cancellation
if (error.name === "AbortError" || error.message === "Indexing cancelled") {
this.stateManager.setSystemState("Standby", "Indexing cancelled by user")
} else {
try {
await this.vectorStore.clearCollection()
} catch (cleanupError) {
console.error("[CodeIndexOrchestrator] Failed to clean up after error:", cleanupError)
}
await this.cacheManager.clearCacheFile()
this.stateManager.setSystemState(
"Error",
`Failed during initial scan: ${error.message || "Unknown error"}`,
)
}
await this.cacheManager.clearCacheFile()
this.stateManager.setSystemState("Error", `Failed during initial scan: ${error.message || "Unknown error"}`)
this.stopWatcher()
} finally {
this._isProcessing = false
this.abortController = undefined
}
}
/**
* Cancels the current indexing operation.
*/
public cancelIndexing(): void {
if (this.abortController) {
this.abortController.abort()
this.abortController = undefined
}
}
@ -235,4 +259,15 @@ export class CodeIndexOrchestrator {
public get state(): IndexingState {
return this.stateManager.state
}
/**
* Disposes of resources used by the orchestrator.
*/
public async dispose(): Promise<void> {
this.cancelIndexing()
this.stopWatcher()
if (this.scanner && "dispose" in this.scanner) {
await (this.scanner as any).dispose()
}
}
}

View file

@ -24,15 +24,35 @@ import {
BATCH_PROCESSING_CONCURRENCY,
} from "../constants"
import { isPathInIgnoredDirectory } from "../../glob/ignore-utils"
import { WorkerPool } from "../workers/worker-pool"
export interface ScanOptions {
signal?: AbortSignal
}
export class DirectoryScanner implements IDirectoryScanner {
private workerPool?: WorkerPool
constructor(
private readonly embedder: IEmbedder,
private readonly qdrantClient: IVectorStore,
private readonly codeParser: ICodeParser,
private readonly cacheManager: CacheManager,
private readonly ignoreInstance: Ignore,
) {}
) {
// Initialize worker pool for file processing
try {
this.workerPool = new WorkerPool(
path.join(__dirname, "../workers/file-processor.worker.js"),
PARSING_CONCURRENCY,
)
} catch (error) {
console.warn(
"[DirectoryScanner] Failed to initialize worker pool, falling back to main thread processing:",
error,
)
}
}
/**
* Recursively scans a directory for code blocks in supported files.
@ -47,6 +67,7 @@ export class DirectoryScanner implements IDirectoryScanner {
onError?: (error: Error) => void,
onBlocksIndexed?: (indexedCount: number) => void,
onFileParsed?: (fileBlockCount: number) => void,
options?: ScanOptions,
): Promise<{ codeBlocks: CodeBlock[]; stats: { processed: number; skipped: number }; totalBlockCount: number }> {
const directoryPath = directory
// Get all files recursively (handles .gitignore automatically)
@ -99,6 +120,11 @@ export class DirectoryScanner implements IDirectoryScanner {
// Process all files in parallel with concurrency control
const parsePromises = supportedPaths.map((filePath) =>
parseLimiter(async () => {
// Check for cancellation
if (options?.signal?.aborted) {
throw new Error("Indexing cancelled")
}
try {
// Check file size
const stats = await stat(filePath)
@ -107,13 +133,52 @@ export class DirectoryScanner implements IDirectoryScanner {
return
}
// Read file content
const content = await vscode.workspace.fs
.readFile(vscode.Uri.file(filePath))
.then((buffer) => Buffer.from(buffer).toString("utf-8"))
let content: string
let currentFileHash: string
// Check for cancellation before processing
if (options?.signal?.aborted) {
throw new Error("Indexing cancelled")
}
// Try to use worker pool if available
if (this.workerPool) {
try {
const result = await this.workerPool.execute<{ content: string; hash: string }>({
type: "processFile",
filePath,
workspacePath: directory,
})
content = result.content
currentFileHash = result.hash
} catch (workerError) {
// Check if cancelled
if (options?.signal?.aborted) {
throw new Error("Indexing cancelled")
}
// Fallback to main thread processing
console.warn(
`[DirectoryScanner] Worker failed for ${filePath}, using main thread:`,
workerError,
)
content = await vscode.workspace.fs
.readFile(vscode.Uri.file(filePath))
.then((buffer) => Buffer.from(buffer).toString("utf-8"))
currentFileHash = createHash("sha256").update(content).digest("hex")
}
} else {
// No worker pool, use main thread
content = await vscode.workspace.fs
.readFile(vscode.Uri.file(filePath))
.then((buffer) => Buffer.from(buffer).toString("utf-8"))
currentFileHash = createHash("sha256").update(content).digest("hex")
}
// Check for cancellation after file read
if (options?.signal?.aborted) {
throw new Error("Indexing cancelled")
}
// Calculate current hash
const currentFileHash = createHash("sha256").update(content).digest("hex")
processedFiles.add(filePath)
// Check against cache
@ -126,6 +191,12 @@ export class DirectoryScanner implements IDirectoryScanner {
// File is new or changed - parse it using the injected parser function
const blocks = await this.codeParser.parseFile(filePath, { content, fileHash: currentFileHash })
// Check for cancellation after parsing
if (options?.signal?.aborted) {
throw new Error("Indexing cancelled")
}
const fileBlockCount = blocks.length
onFileParsed?.(fileBlockCount)
codeBlocks.push(...blocks)
@ -171,6 +242,7 @@ export class DirectoryScanner implements IDirectoryScanner {
batchFileInfos,
onError,
onBlocksIndexed,
options?.signal,
),
)
activeBatchPromises.push(batchPromise)
@ -185,6 +257,11 @@ export class DirectoryScanner implements IDirectoryScanner {
await this.cacheManager.updateHash(filePath, currentFileHash)
}
} catch (error) {
// Re-throw cancellation errors
if (error instanceof Error && error.message === "Indexing cancelled") {
throw error
}
console.error(`Error processing file ${filePath}:`, error)
if (onError) {
onError(
@ -198,7 +275,16 @@ export class DirectoryScanner implements IDirectoryScanner {
)
// Wait for all parsing to complete
await Promise.all(parsePromises)
try {
await Promise.all(parsePromises)
} catch (error) {
// If it's a cancellation error, propagate it
if (error instanceof Error && error.message === "Indexing cancelled") {
throw error
}
// For other errors, log and continue
console.error("[DirectoryScanner] Error during file parsing:", error)
}
// Process any remaining items in batch
if (currentBatchBlocks.length > 0) {
@ -214,7 +300,14 @@ export class DirectoryScanner implements IDirectoryScanner {
// Queue final batch processing
const batchPromise = batchLimiter(() =>
this.processBatch(batchBlocks, batchTexts, batchFileInfos, onError, onBlocksIndexed),
this.processBatch(
batchBlocks,
batchTexts,
batchFileInfos,
onError,
onBlocksIndexed,
options?.signal,
),
)
activeBatchPromises.push(batchPromise)
} finally {
@ -269,6 +362,7 @@ export class DirectoryScanner implements IDirectoryScanner {
batchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[],
onError?: (error: Error) => void,
onBlocksIndexed?: (indexedCount: number) => void,
signal?: AbortSignal,
): Promise<void> {
if (batchBlocks.length === 0) return
@ -279,6 +373,10 @@ export class DirectoryScanner implements IDirectoryScanner {
while (attempts < MAX_BATCH_RETRIES && !success) {
attempts++
try {
// Check for cancellation
if (signal?.aborted) {
throw new Error("Indexing cancelled")
}
// --- Deletion Step ---
const uniqueFilePaths = [
...new Set(
@ -361,4 +459,11 @@ export class DirectoryScanner implements IDirectoryScanner {
}
}
}
public async dispose(): Promise<void> {
if (this.workerPool) {
await this.workerPool.shutdown()
this.workerPool = undefined
}
}
}

View file

@ -10,6 +10,11 @@ export class CodeIndexStateManager {
private _currentItemUnit: string = "blocks"
private _progressEmitter = new vscode.EventEmitter<ReturnType<typeof this.getCurrentStatus>>()
// Throttling properties
private _throttleTimer?: NodeJS.Timeout
private _pendingUpdate?: ReturnType<typeof this.getCurrentStatus>
private readonly THROTTLE_INTERVAL_MS = 500
// --- Public API ---
public readonly onProgressUpdate = this._progressEmitter.event
@ -51,7 +56,7 @@ export class CodeIndexStateManager {
if (newState === "Error" && message === undefined) this._statusMessage = "An error occurred."
}
this._progressEmitter.fire(this.getCurrentStatus())
this.throttledEmit()
}
}
@ -73,7 +78,7 @@ export class CodeIndexStateManager {
// Only fire update if status, message or progress actually changed
if (oldStatus !== this._systemStatus || oldMessage !== this._statusMessage || progressChanged) {
this._progressEmitter.fire(this.getCurrentStatus())
this.throttledEmit()
}
}
}
@ -104,12 +109,33 @@ export class CodeIndexStateManager {
this._statusMessage = message
if (oldStatus !== this._systemStatus || oldMessage !== this._statusMessage || progressChanged) {
this._progressEmitter.fire(this.getCurrentStatus())
this.throttledEmit()
}
}
}
private throttledEmit(): void {
if (this._throttleTimer) {
this._pendingUpdate = this.getCurrentStatus()
return
}
this._progressEmitter.fire(this.getCurrentStatus())
this._throttleTimer = setTimeout(() => {
if (this._pendingUpdate) {
this._progressEmitter.fire(this._pendingUpdate)
this._pendingUpdate = undefined
}
this._throttleTimer = undefined
}, this.THROTTLE_INTERVAL_MS)
}
public dispose(): void {
if (this._throttleTimer) {
clearTimeout(this._throttleTimer)
this._throttleTimer = undefined
}
this._progressEmitter.dispose()
}
}

View file

@ -0,0 +1,63 @@
import { parentPort } from "worker_threads"
import { WorkerTask, WorkerResult } from "./worker-pool"
interface EmbeddingTask extends WorkerTask {
type: "generateEmbeddings"
texts: string[]
embedderConfig: any // Configuration for the embedder
}
interface EmbeddingResult {
embeddings: number[][]
}
// Note: In a real implementation, this would use the actual embedder
// For now, we'll create a placeholder that demonstrates the structure
async function generateEmbeddings(task: EmbeddingTask): Promise<EmbeddingResult> {
try {
// In production, this would:
// 1. Initialize the embedder with the config
// 2. Generate embeddings for the texts
// 3. Return the embeddings
// Placeholder: return dummy embeddings
const embeddings = task.texts.map(() => {
// Generate a dummy embedding vector
const dimension = 1536 // Common embedding dimension
return Array(dimension)
.fill(0)
.map(() => Math.random())
})
return { embeddings }
} catch (error) {
throw new Error(`Failed to generate embeddings: ${error.message}`)
}
}
// Worker message handler
if (parentPort) {
parentPort.on("message", async (task: WorkerTask) => {
let result: WorkerResult
try {
switch (task.type) {
case "generateEmbeddings": {
const data = await generateEmbeddings(task as EmbeddingTask)
result = { success: true, data }
break
}
default:
result = { success: false, error: `Unknown task type: ${task.type}` }
}
} catch (error) {
result = {
success: false,
error: error instanceof Error ? error.message : String(error),
}
}
parentPort!.postMessage(result)
})
}

View file

@ -0,0 +1,62 @@
import { parentPort } from "worker_threads"
import { readFile } from "fs/promises"
import { createHash } from "crypto"
import * as path from "path"
import { WorkerTask, WorkerResult } from "./worker-pool"
interface ProcessFileTask extends WorkerTask {
type: "processFile"
filePath: string
workspacePath: string
}
interface ProcessedFile {
filePath: string
content: string
hash: string
}
async function processFile(task: ProcessFileTask): Promise<ProcessedFile> {
try {
// Read file content
const content = await readFile(task.filePath, "utf-8")
// Calculate hash
const hash = createHash("sha256").update(content).digest("hex")
return {
filePath: task.filePath,
content,
hash,
}
} catch (error) {
throw new Error(`Failed to process file ${task.filePath}: ${error.message}`)
}
}
// Worker message handler
if (parentPort) {
parentPort.on("message", async (task: WorkerTask) => {
let result: WorkerResult
try {
switch (task.type) {
case "processFile": {
const data = await processFile(task as ProcessFileTask)
result = { success: true, data }
break
}
default:
result = { success: false, error: `Unknown task type: ${task.type}` }
}
} catch (error) {
result = {
success: false,
error: error instanceof Error ? error.message : String(error),
}
}
parentPort!.postMessage(result)
})
}

View file

@ -0,0 +1,171 @@
import { Worker } from "worker_threads"
import { cpus } from "os"
export interface WorkerTask {
type: string
[key: string]: any
}
export interface WorkerResult<T = any> {
success: boolean
data?: T
error?: string
}
interface QueueItem<T> {
task: WorkerTask
resolve: (value: T) => void
reject: (reason: any) => void
}
export class WorkerPool {
private workers: Worker[] = []
private availableWorkers: Worker[] = []
private queue: QueueItem<any>[] = []
private activeWorkers = new Map<Worker, QueueItem<any>>()
private isShuttingDown = false
constructor(
private workerScript: string,
private maxWorkers = Math.max(1, cpus().length - 1),
) {
this.initializeWorkers()
}
private initializeWorkers(): void {
for (let i = 0; i < this.maxWorkers; i++) {
const worker = new Worker(this.workerScript)
worker.on("message", (result: WorkerResult) => {
const queueItem = this.activeWorkers.get(worker)
if (queueItem) {
this.activeWorkers.delete(worker)
this.availableWorkers.push(worker)
if (result.success) {
queueItem.resolve(result.data)
} else {
queueItem.reject(new Error(result.error || "Unknown worker error"))
}
this.processQueue()
}
})
worker.on("error", (error) => {
const queueItem = this.activeWorkers.get(worker)
if (queueItem) {
this.activeWorkers.delete(worker)
queueItem.reject(error)
}
// Replace the failed worker
if (!this.isShuttingDown) {
this.replaceWorker(worker)
}
})
this.workers.push(worker)
this.availableWorkers.push(worker)
}
}
private replaceWorker(failedWorker: Worker): void {
const index = this.workers.indexOf(failedWorker)
if (index !== -1) {
try {
failedWorker.terminate()
} catch (error) {
// Ignore termination errors
}
const newWorker = new Worker(this.workerScript)
this.workers[index] = newWorker
this.availableWorkers.push(newWorker)
// Set up event handlers for the new worker
newWorker.on("message", (result: WorkerResult) => {
const queueItem = this.activeWorkers.get(newWorker)
if (queueItem) {
this.activeWorkers.delete(newWorker)
this.availableWorkers.push(newWorker)
if (result.success) {
queueItem.resolve(result.data)
} else {
queueItem.reject(new Error(result.error || "Unknown worker error"))
}
this.processQueue()
}
})
newWorker.on("error", (error) => {
const queueItem = this.activeWorkers.get(newWorker)
if (queueItem) {
this.activeWorkers.delete(newWorker)
queueItem.reject(error)
}
if (!this.isShuttingDown) {
this.replaceWorker(newWorker)
}
})
}
}
async execute<T>(task: WorkerTask): Promise<T> {
if (this.isShuttingDown) {
throw new Error("Worker pool is shutting down")
}
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject })
this.processQueue()
})
}
private processQueue(): void {
while (this.queue.length > 0 && this.availableWorkers.length > 0) {
const queueItem = this.queue.shift()!
const worker = this.availableWorkers.shift()!
this.activeWorkers.set(worker, queueItem)
worker.postMessage(queueItem.task)
}
}
async shutdown(): Promise<void> {
this.isShuttingDown = true
// Clear the queue
for (const queueItem of this.queue) {
queueItem.reject(new Error("Worker pool is shutting down"))
}
this.queue = []
// Wait for active tasks to complete
const timeout = 5000 // 5 seconds timeout
const startTime = Date.now()
while (this.activeWorkers.size > 0 && Date.now() - startTime < timeout) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
// Terminate all workers
await Promise.all(
this.workers.map(async (worker) => {
try {
await worker.terminate()
} catch (error) {
// Ignore termination errors
console.warn("[WorkerPool] Failed to terminate worker:", error)
}
}),
)
this.workers = []
this.availableWorkers = []
this.activeWorkers.clear()
}
}

View file

@ -158,6 +158,7 @@ export interface WebviewMessage {
| "condenseTaskContextRequest"
| "requestIndexingStatus"
| "startIndexing"
| "cancelIndexing"
| "clearIndexData"
| "indexingStatusUpdate"
| "indexCleared"

View file

@ -485,6 +485,13 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
{t("settings:codeIndex.startIndexingButton")}
</VSCodeButton>
)}
{indexingStatus.systemStatus === "Indexing" && (
<VSCodeButton
onClick={() => vscode.postMessage({ type: "cancelIndexing" })}
appearance="secondary">
{t("settings:codeIndex.stopIndexingButton")}
</VSCodeButton>
)}
{(indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && (
<AlertDialog>
<AlertDialogTrigger asChild>

View file

@ -70,6 +70,41 @@
"confirmButton": "Esborrar dades"
}
},
"codeIndex": {
"title": "Indexació de la base de codi",
"enableLabel": "Activa la indexació de la base de codi",
"enableDescription": "<0>La indexació de la base de codi</0> és una funció experimental que crea un índex de cerca semàntica del vostre projecte mitjançant incrustacions d'IA. Això permet que Roo Code comprengui i navegui millor per grans bases de codi trobant codi rellevant basat en el significat en lloc de només paraules clau.",
"providerLabel": "Proveïdor d'incrustacions",
"selectProviderPlaceholder": "Selecciona el proveïdor",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Compatible amb OpenAI",
"openaiKeyLabel": "Clau d'OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL base:",
"openaiCompatibleApiKeyLabel": "Clau de l'API:",
"openaiCompatibleModelDimensionLabel": "Dimensió de la incrustació:",
"openaiCompatibleModelDimensionPlaceholder": "p. ex., 1536",
"openaiCompatibleModelDimensionDescription": "La dimensió de la incrustació (mida de sortida) per al vostre model. Consulteu la documentació del vostre proveïdor per a aquest valor. Valors comuns: 384, 768, 1536, 3072.",
"modelLabel": "Model",
"selectModelPlaceholder": "Selecciona el model",
"ollamaUrlLabel": "URL d'Ollama:",
"qdrantUrlLabel": "URL de Qdrant",
"qdrantKeyLabel": "Clau de Qdrant:",
"advancedConfigLabel": "Configuració avançada",
"searchMinScoreLabel": "Llindar de puntuació de la cerca",
"searchMinScoreDescription": "Puntuació de similitud mínima (0.0-1.0) requerida per als resultats de la cerca. Els valors més baixos retornen més resultats però poden ser menys rellevants. Els valors més alts retornen menys resultats però més rellevants.",
"searchMinScoreResetTooltip": "Restableix al valor per defecte (0.4)",
"startIndexingButton": "Inicia la indexació",
"stopIndexingButton": "Atura la indexació",
"clearIndexDataButton": "Esborra les dades de l'índex",
"unsavedSettingsMessage": "Deseu la configuració abans d'iniciar el procés d'indexació.",
"clearDataDialog": {
"title": "N'estàs segur?",
"description": "Aquesta acció no es pot desfer. Això suprimirà permanentment les dades de l'índex de la vostra base de codi.",
"cancelButton": "Cancel·la",
"confirmButton": "Esborra les dades"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Daten löschen"
}
},
"codeIndex": {
"title": "Codebasis-Indizierung",
"enableLabel": "Codebasis-Indizierung aktivieren",
"enableDescription": "<0>Codebasis-Indizierung</0> ist eine experimentelle Funktion, die einen semantischen Suchindex deines Projekts mithilfe von KI-Embeddings erstellt. Dies ermöglicht es Roo Code, große Codebasen besser zu verstehen und zu navigieren, indem relevanter Code basierend auf der Bedeutung anstatt nur auf Schlüsselwörtern gefunden wird.",
"providerLabel": "Embeddings-Anbieter",
"selectProviderPlaceholder": "Anbieter auswählen",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI-kompatibel",
"openaiKeyLabel": "OpenAI-Schlüssel:",
"openaiCompatibleBaseUrlLabel": "Basis-URL:",
"openaiCompatibleApiKeyLabel": "API-Schlüssel:",
"openaiCompatibleModelDimensionLabel": "Embedding-Dimension:",
"openaiCompatibleModelDimensionPlaceholder": "z. B. 1536",
"openaiCompatibleModelDimensionDescription": "Die Embedding-Dimension (Ausgabegröße) für dein Modell. Überprüfe die Dokumentation deines Anbieters für diesen Wert. Gängige Werte: 384, 768, 1536, 3072.",
"modelLabel": "Modell",
"selectModelPlaceholder": "Modell auswählen",
"ollamaUrlLabel": "Ollama-URL:",
"qdrantUrlLabel": "Qdrant-URL",
"qdrantKeyLabel": "Qdrant-Schlüssel:",
"advancedConfigLabel": "Erweiterte Konfiguration",
"searchMinScoreLabel": "Mindest-Suchbewertung",
"searchMinScoreDescription": "Mindest-Ähnlichkeitsbewertung (0,0-1,0), die für Suchergebnisse erforderlich ist. Niedrigere Werte geben mehr Ergebnisse zurück, sind aber möglicherweise weniger relevant. Höhere Werte geben weniger, aber relevantere Ergebnisse zurück.",
"searchMinScoreResetTooltip": "Auf Standardwert (0.4) zurücksetzen",
"startIndexingButton": "Indizierung starten",
"stopIndexingButton": "Indizierung stoppen",
"clearIndexDataButton": "Indexdaten löschen",
"unsavedSettingsMessage": "Bitte speichere deine Einstellungen, bevor du den Indizierungsprozess startest.",
"clearDataDialog": {
"title": "Bist du sicher?",
"description": "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch werden deine Codebasis-Indexdaten dauerhaft gelöscht.",
"cancelButton": "Abbrechen",
"confirmButton": "Daten löschen"
}
},
"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.",
"readOnly": {

View file

@ -61,6 +61,7 @@
"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.",
"searchMinScoreResetTooltip": "Reset to default value (0.4)",
"startIndexingButton": "Start Indexing",
"stopIndexingButton": "Stop Indexing",
"clearIndexDataButton": "Clear Index Data",
"unsavedSettingsMessage": "Please save your settings before starting the indexing process.",
"clearDataDialog": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Borrar datos"
}
},
"codeIndex": {
"title": "Indexación de la base de código",
"enableLabel": "Habilitar la indexación de la base de código",
"enableDescription": "<0>La indexación de la base de código</0> es una función experimental que crea un índice de búsqueda semántica de tu proyecto utilizando incrustaciones de IA. Esto permite a Roo Code comprender y navegar mejor por grandes bases de código al encontrar código relevante basado en el significado en lugar de solo palabras clave.",
"providerLabel": "Proveedor de incrustaciones",
"selectProviderPlaceholder": "Seleccionar proveedor",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Compatible con OpenAI",
"openaiKeyLabel": "Clave de OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL base:",
"openaiCompatibleApiKeyLabel": "Clave de API:",
"openaiCompatibleModelDimensionLabel": "Dimensión de la incrustación:",
"openaiCompatibleModelDimensionPlaceholder": "p. ej., 1536",
"openaiCompatibleModelDimensionDescription": "La dimensión de la incrustación (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",
"selectModelPlaceholder": "Seleccionar modelo",
"ollamaUrlLabel": "URL 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 de similitud mínima (0.0-1.0) requerida para los resultados de búsqueda. Los valores más bajos devuelven más resultados, pero pueden ser menos relevantes. Los valores más altos devuelven menos resultados, pero más relevantes.",
"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
"startIndexingButton": "Iniciar indexación",
"stopIndexingButton": "Detener indexación",
"clearIndexDataButton": "Borrar datos del índice",
"unsavedSettingsMessage": "Guarda la configuración 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 del índice de tu base de código.",
"cancelButton": "Cancelar",
"confirmButton": "Borrar datos"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Effacer les données"
}
},
"codeIndex": {
"title": "Indexation de la base de code",
"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 à l'aide d'embeddings d'IA. Cela permet à Roo Code de mieux comprendre et de naviguer dans les grandes bases de code en trouvant du code pertinent basé sur le sens plutôt que sur de simples mots-clés.",
"providerLabel": "Fournisseur d'embeddings",
"selectProviderPlaceholder": "Sélectionner le fournisseur",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Compatible OpenAI",
"openaiKeyLabel": "Clé OpenAI :",
"openaiCompatibleBaseUrlLabel": "URL de base :",
"openaiCompatibleApiKeyLabel": "Clé API :",
"openaiCompatibleModelDimensionLabel": "Dimension de l'embedding :",
"openaiCompatibleModelDimensionPlaceholder": "ex: 1536",
"openaiCompatibleModelDimensionDescription": "La dimension de l'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",
"selectModelPlaceholder": "Sélectionner le modèle",
"ollamaUrlLabel": "URL d'Ollama :",
"qdrantUrlLabel": "URL de 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 pertinentes. 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",
"stopIndexingButton": "Arrêter l'indexation",
"clearIndexDataButton": "Effacer les données de l'index",
"unsavedSettingsMessage": "Veuillez enregistrer vos paramètres avant de démarrer le processus d'indexation.",
"clearDataDialog": {
"title": "Êtes-vous sûr ?",
"description": "Cette action est irréversible. Cela supprimera définitivement les données d'index de votre base de code.",
"cancelButton": "Annuler",
"confirmButton": "Effacer les données"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "डेटा साफ़ करें"
}
},
"codeIndex": {
"title": "कोडबेस इंडेक्सिंग",
"enableLabel": "कोडबेस इंडेक्सिंग सक्षम करें",
"enableDescription": "<0>कोडबेस इंडेक्सिंग</0> एक प्रायोगिक सुविधा है जो एआई एम्बेडिंग का उपयोग करके आपके प्रोजेक्ट का सिमेंटिक सर्च इंडेक्स बनाती है। यह रू कोड को केवल कीवर्ड के बजाय अर्थ के आधार पर प्रासंगिक कोड ढूंढकर बड़े कोडबेस को बेहतर ढंग से समझने और नेविगेट करने में सक्षम बनाता है।",
"providerLabel": "एम्बेडिंग प्रदाता",
"selectProviderPlaceholder": "प्रदाता चुनें",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI संगत",
"openaiKeyLabel": "OpenAI कुंजी:",
"openaiCompatibleBaseUrlLabel": "आधार URL:",
"openaiCompatibleApiKeyLabel": "API कुंजी:",
"openaiCompatibleModelDimensionLabel": "एम्बेडिंग आयाम:",
"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": "इंडेक्सिंग प्रारंभ करें",
"stopIndexingButton": "इंडेक्सिंग रोकें",
"clearIndexDataButton": "इंडेक्स डेटा साफ़ करें",
"unsavedSettingsMessage": "इंडेक्सिंग प्रक्रिया शुरू करने से पहले कृपया अपनी सेटिंग्स सहेजें।",
"clearDataDialog": {
"title": "क्या आप निश्चित हैं?",
"description": "यह क्रिया पूर्ववत नहीं की जा सकती। यह आपके कोडबेस इंडेक्स डेटा को स्थायी रूप से हटा देगा।",
"cancelButton": "रद्द करें",
"confirmButton": "डेटा साफ़ करें"
}
},
"autoApprove": {
"description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Hapus Data"
}
},
"codeIndex": {
"title": "Pengindeksan Basis Kode",
"enableLabel": "Aktifkan Pengindeksan Basis Kode",
"enableDescription": "<0>Pengindeksan Basis Kode</0> adalah fitur eksperimental yang membuat indeks pencarian semantik proyek Anda menggunakan penyematan AI. Ini memungkinkan Roo Code untuk lebih memahami dan menavigasi basis kode besar dengan menemukan kode yang relevan berdasarkan makna daripada hanya kata kunci.",
"providerLabel": "Penyedia Penyematan",
"selectProviderPlaceholder": "Pilih penyedia",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Kompatibel dengan OpenAI",
"openaiKeyLabel": "Kunci OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL Dasar:",
"openaiCompatibleApiKeyLabel": "Kunci API:",
"openaiCompatibleModelDimensionLabel": "Dimensi Penyematan:",
"openaiCompatibleModelDimensionPlaceholder": "misalnya, 1536",
"openaiCompatibleModelDimensionDescription": "Dimensi penyematan (ukuran keluaran) untuk model Anda. Periksa dokumentasi penyedia Anda untuk nilai ini. Nilai umum: 384, 768, 1536, 3072.",
"modelLabel": "Model",
"selectModelPlaceholder": "Pilih model",
"ollamaUrlLabel": "URL Ollama:",
"qdrantUrlLabel": "URL Qdrant",
"qdrantKeyLabel": "Kunci Qdrant:",
"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 tetapi hasil yang lebih relevan.",
"searchMinScoreResetTooltip": "Setel ulang ke nilai default (0.4)",
"startIndexingButton": "Mulai Pengindeksan",
"stopIndexingButton": "Hentikan Pengindeksan",
"clearIndexDataButton": "Hapus Data Indeks",
"unsavedSettingsMessage": "Harap simpan pengaturan Anda sebelum memulai proses pengindeksan.",
"clearDataDialog": {
"title": "Apakah Anda yakin?",
"description": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus data indeks basis kode Anda secara permanen.",
"cancelButton": "Batal",
"confirmButton": "Hapus Data"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Cancella dati"
}
},
"codeIndex": {
"title": "Indicizzazione della base di codice",
"enableLabel": "Abilita l'indicizzazione della base di codice",
"enableDescription": "<0>L'indicizzazione della base di codice</0> è una funzionalità sperimentale che crea un indice di ricerca semantica del tuo progetto utilizzando gli embedding dell'IA. Ciò consente a Roo Code di comprendere e navigare meglio in basi di codice di grandi dimensioni trovando il codice pertinente in base al significato anziché solo alle parole chiave.",
"providerLabel": "Provider di embedding",
"selectProviderPlaceholder": "Seleziona provider",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Compatibile con OpenAI",
"openaiKeyLabel": "Chiave OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL di base:",
"openaiCompatibleApiKeyLabel": "Chiave API:",
"openaiCompatibleModelDimensionLabel": "Dimensione dell'embedding:",
"openaiCompatibleModelDimensionPlaceholder": "es. 1536",
"openaiCompatibleModelDimensionDescription": "La dimensione dell'embedding (dimensione dell'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 di Ollama:",
"qdrantUrlLabel": "URL di Qdrant",
"qdrantKeyLabel": "Chiave di Qdrant:",
"advancedConfigLabel": "Configurazione avanzata",
"searchMinScoreLabel": "Soglia punteggio di ricerca",
"searchMinScoreDescription": "Punteggio di somiglianza minimo (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",
"stopIndexingButton": "Interrompi indicizzazione",
"clearIndexDataButton": "Cancella dati indice",
"unsavedSettingsMessage": "Salva le impostazioni prima di avviare il processo di indicizzazione.",
"clearDataDialog": {
"title": "Sei sicuro?",
"description": "Questa azione non può essere annullata. Eliminerà permanentemente i dati dell'indice della tua base di codice.",
"cancelButton": "Annulla",
"confirmButton": "Cancella dati"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "データをクリア"
}
},
"codeIndex": {
"title": "コードベースのインデックス作成",
"enableLabel": "コードベースのインデックス作成を有効にする",
"enableDescription": "<0>コードベースのインデックス作成</0>は、AI埋め込みを使用してプロジェクトのセマンティック検索インデックスを作成する実験的な機能です。これにより、Roo Codeはキーワードだけでなく意味に基づいて関連コードを検索することで、大規模なコードベースをよりよく理解し、ナビゲートできるようになります。",
"providerLabel": "埋め込みプロバイダー",
"selectProviderPlaceholder": "プロバイダーを選択",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI互換",
"openaiKeyLabel": "OpenAIキー",
"openaiCompatibleBaseUrlLabel": "ベースURL",
"openaiCompatibleApiKeyLabel": "APIキー",
"openaiCompatibleModelDimensionLabel": "埋め込みディメンション:",
"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": "インデックス作成を開始",
"stopIndexingButton": "インデックス作成を停止",
"clearIndexDataButton": "インデックスデータを消去",
"unsavedSettingsMessage": "インデックス作成プロセスを開始する前に設定を保存してください。",
"clearDataDialog": {
"title": "よろしいですか?",
"description": "この操作は元に戻せません。コードベースのインデックスデータが完全に削除されます。",
"cancelButton": "キャンセル",
"confirmButton": "データを消去"
}
},
"autoApprove": {
"description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "데이터 지우기"
}
},
"codeIndex": {
"title": "코드베이스 인덱싱",
"enableLabel": "코드베이스 인덱싱 활성화",
"enableDescription": "<0>코드베이스 인덱싱</0>은 AI 임베딩을 사용하여 프로젝트의 시맨틱 검색 인덱스를 생성하는 실험적인 기능입니다. 이를 통해 Roo Code는 단순히 키워드가 아닌 의미를 기반으로 관련 코드를 찾아 대규모 코드베이스를 더 잘 이해하고 탐색할 수 있습니다.",
"providerLabel": "임베딩 공급자",
"selectProviderPlaceholder": "공급자 선택",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI 호환",
"openaiKeyLabel": "OpenAI 키:",
"openaiCompatibleBaseUrlLabel": "기본 URL:",
"openaiCompatibleApiKeyLabel": "API 키:",
"openaiCompatibleModelDimensionLabel": "임베딩 차원:",
"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": "인덱싱 시작",
"stopIndexingButton": "인덱싱 중지",
"clearIndexDataButton": "인덱스 데이터 지우기",
"unsavedSettingsMessage": "인덱싱 프로세스를 시작하기 전에 설정을 저장하십시오.",
"clearDataDialog": {
"title": "확실합니까?",
"description": "이 작업은 취소할 수 없습니다. 코드베이스 인덱스 데이터가 영구적으로 삭제됩니다.",
"cancelButton": "취소",
"confirmButton": "데이터 지우기"
}
},
"autoApprove": {
"description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Gegevens wissen"
}
},
"codeIndex": {
"title": "Codebase-indexering",
"enableLabel": "Codebase-indexering inschakelen",
"enableDescription": "<0>Codebase-indexering</0> is een experimentele functie die een semantische zoekindex van je project maakt met behulp van AI-embeddings. Hierdoor kan Roo Code grote codebases beter begrijpen en navigeren door relevante code te vinden op basis van betekenis in plaats van alleen trefwoorden.",
"providerLabel": "Embeddings-provider",
"selectProviderPlaceholder": "Selecteer provider",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI-compatibel",
"openaiKeyLabel": "OpenAI-sleutel:",
"openaiCompatibleBaseUrlLabel": "Basis-URL:",
"openaiCompatibleApiKeyLabel": "API-sleutel:",
"openaiCompatibleModelDimensionLabel": "Embedding-dimensie:",
"openaiCompatibleModelDimensionPlaceholder": "bijv. 1536",
"openaiCompatibleModelDimensionDescription": "De embedding-dimensie (uitvoergrootte) for je model. Raadpleeg de documentatie van je 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": "Minimale zoekscore",
"searchMinScoreDescription": "Minimale gelijkenisscore (0,0-1,0) vereist voor zoekresultaten. Lagere waarden geven meer resultaten, maar zijn mogelijk minder relevant. Hogere waarden geven minder, maar relevantere resultaten.",
"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
"startIndexingButton": "Indexering starten",
"stopIndexingButton": "Indexering stoppen",
"clearIndexDataButton": "Indexgegevens 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"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Wyczyść dane"
}
},
"codeIndex": {
"title": "Indeksowanie bazy kodu",
"enableLabel": "Włącz indeksowanie bazy kodu",
"enableDescription": "<0>Indeksowanie bazy kodu</0> to funkcja eksperymentalna, która tworzy semantyczny indeks wyszukiwania Twojego projektu przy użyciu osadzeń AI. Umożliwia to Roo Code lepsze rozumienie i nawigację po dużych bazach kodu poprzez znajdowanie odpowiedniego kodu na podstawie znaczenia, a nie tylko słów kluczowych.",
"providerLabel": "Dostawca osadzeń",
"selectProviderPlaceholder": "Wybierz dostawcę",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Zgodny z OpenAI",
"openaiKeyLabel": "Klucz OpenAI:",
"openaiCompatibleBaseUrlLabel": "Podstawowy adres URL:",
"openaiCompatibleApiKeyLabel": "Klucz API:",
"openaiCompatibleModelDimensionLabel": "Wymiar osadzania:",
"openaiCompatibleModelDimensionPlaceholder": "np. 1536",
"openaiCompatibleModelDimensionDescription": "Wymiar osadzania (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": "Adres URL Ollama:",
"qdrantUrlLabel": "Adres 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, ale bardziej trafnych wyników.",
"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
"startIndexingButton": "Rozpocznij indeksowanie",
"stopIndexingButton": "Zatrzymaj indeksowanie",
"clearIndexDataButton": "Wyczyść dane indeksu",
"unsavedSettingsMessage": "Zapisz ustawienia przed rozpoczęciem procesu indeksowania.",
"clearDataDialog": {
"title": "Czy na pewno?",
"description": "Tej operacji nie można cofnąć. Spowoduje to trwałe usunięcie danych indeksu Twojej bazy kodu.",
"cancelButton": "Anuluj",
"confirmButton": "Wyczyść dane"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Limpar Dados"
}
},
"codeIndex": {
"title": "Indexação da base de código",
"enableLabel": "Habilitar a indexação da base de código",
"enableDescription": "<0>A indexação da base de código</0> é um recurso experimental que cria um índice de pesquisa semântica do seu projeto usando embeddings de IA. Isso permite que o Roo Code entenda e navegue melhor em grandes bases de código, encontrando código relevante com base no significado, em vez de apenas palavras-chave.",
"providerLabel": "Provedor de embeddings",
"selectProviderPlaceholder": "Selecionar provedor",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Compatível com OpenAI",
"openaiKeyLabel": "Chave da OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL base:",
"openaiCompatibleApiKeyLabel": "Chave de API:",
"openaiCompatibleModelDimensionLabel": "Dimensão do embedding:",
"openaiCompatibleModelDimensionPlaceholder": "ex: 1536",
"openaiCompatibleModelDimensionDescription": "A dimensão do embedding (tamanho da saída) para o 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 do Ollama:",
"qdrantUrlLabel": "URL do Qdrant",
"qdrantKeyLabel": "Chave do Qdrant:",
"advancedConfigLabel": "Configuração avançada",
"searchMinScoreLabel": "Limite de pontuação da pesquisa",
"searchMinScoreDescription": "Pontuação mínima de similaridade (0,0-1,0) necessária para os resultados da pesquisa. 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",
"stopIndexingButton": "Parar indexação",
"clearIndexDataButton": "Limpar dados do índice",
"unsavedSettingsMessage": "Salve suas configurações antes de iniciar o processo de indexação.",
"clearDataDialog": {
"title": "Você tem certeza?",
"description": "Esta ação não pode ser desfeita. Isso excluirá permanentemente os dados do índice da sua base de código.",
"cancelButton": "Cancelar",
"confirmButton": "Limpar dados"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Очистить данные"
}
},
"codeIndex": {
"title": "Индексирование кодовой базы",
"enableLabel": "Включить индексирование кодовой базы",
"enableDescription": "<0>Индексирование кодовой базы</0> — это экспериментальная функция, которая создает семантический поисковый индекс вашего проекта с использованием вложений ИИ. Это позволяет Roo Code лучше понимать и перемещаться по большим кодовым базам, находя релевантный код на основе значения, а не только по ключевым словам.",
"providerLabel": "Поставщик вложений",
"selectProviderPlaceholder": "Выберите поставщика",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Совместимый с OpenAI",
"openaiKeyLabel": "Ключ OpenAI:",
"openaiCompatibleBaseUrlLabel": "Базовый URL:",
"openaiCompatibleApiKeyLabel": "Ключ API:",
"openaiCompatibleModelDimensionLabel": "Размерность вложения:",
"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": "Начать индексирование",
"stopIndexingButton": "Остановить индексирование",
"clearIndexDataButton": "Очистить данные индекса",
"unsavedSettingsMessage": "Сохраните настройки перед началом процесса индексирования.",
"clearDataDialog": {
"title": "Вы уверены?",
"description": "Это действие нельзя отменить. Это приведет к безвозвратному удалению данных индекса вашей кодовой базы.",
"cancelButton": "Отмена",
"confirmButton": "Очистить данные"
}
},
"autoApprove": {
"description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Verileri Temizle"
}
},
"codeIndex": {
"title": "Kod Tabanı İndeksleme",
"enableLabel": "Kod Tabanı İndekslemeyi Etkinleştir",
"enableDescription": "<0>Kod Tabanı İndeksleme</0>, projenizin anlamsal bir arama dizinini yapay zeka gömülerini kullanarak oluşturan deneysel bir özelliktir. Bu, Roo Code'un sadece anahtar kelimelere göre değil, anlama dayalı olarak ilgili kodu bularak büyük kod tabanlarını daha iyi anlamasını ve gezinmesini sağlar.",
"providerLabel": "Gömü Sağlayıcı",
"selectProviderPlaceholder": "Sağlayıcı seçin",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI Uyumlu",
"openaiKeyLabel": "OpenAI Anahtarı:",
"openaiCompatibleBaseUrlLabel": "Temel URL:",
"openaiCompatibleApiKeyLabel": "API Anahtarı:",
"openaiCompatibleModelDimensionLabel": "Gömme Boyutu:",
"openaiCompatibleModelDimensionPlaceholder": "ör. 1536",
"openaiCompatibleModelDimensionDescription": "Modeliniz için gömme boyutu (çıktı boyutu). Bu değer için sağlayıcınızın belgelerini kontrol edin. Yaygın değerler: 384, 768, 1536, 3072.",
"modelLabel": "Model",
"selectModelPlaceholder": "Model seçin",
"ollamaUrlLabel": "Ollama URL'si:",
"qdrantUrlLabel": "Qdrant URL'si",
"qdrantKeyLabel": "Qdrant Anahtarı:",
"advancedConfigLabel": "Gelişmiş Yapılandırma",
"searchMinScoreLabel": "Arama Puanı Eşiği",
"searchMinScoreDescription": "Arama sonuçları için gereken minimum benzerlik puanı (0.0-1.0). Daha düşük değerler daha fazla sonuç döndürür ancak daha az alakalı olabilir. Daha yüksek değerler daha az ancak daha alakalı sonuçlar döndürür.",
"searchMinScoreResetTooltip": "Varsayılan değere (0.4) sıfırla",
"startIndexingButton": "İndekslemeyi Başlat",
"stopIndexingButton": "İndekslemeyi Durdur",
"clearIndexDataButton": "Dizin Verilerini Temizle",
"unsavedSettingsMessage": "İndeksleme işlemine başlamadan önce lütfen ayarlarınızı kaydedin.",
"clearDataDialog": {
"title": "Emin misiniz?",
"description": "Bu işlem geri alınamaz. Bu, kod tabanı dizin verilerinizi kalıcı olarak silecektir.",
"cancelButton": "İptal",
"confirmButton": "Verileri Temizle"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "Xóa dữ liệu"
}
},
"codeIndex": {
"title": "Chỉ mục hóa cơ sở mã",
"enableLabel": "Bật chỉ mục hóa cơ sở mã",
"enableDescription": "<0>Chỉ mục hóa cơ sở mã</0> là một tính năng thử nghiệm tạo ra một chỉ mục tìm kiếm ngữ nghĩa của dự án của bạn bằng cách sử dụng các nhúng AI. Điều này cho phép Roo Code hiểu và điều hướng các cơ sở mã lớn tốt hơn bằng cách tìm mã có liên quan dựa trên ý nghĩa thay vì chỉ từ khóa.",
"providerLabel": "Nhà cung cấp nhúng",
"selectProviderPlaceholder": "Chọn nhà cung cấp",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "Tương thích với OpenAI",
"openaiKeyLabel": "Khóa OpenAI:",
"openaiCompatibleBaseUrlLabel": "URL cơ sở:",
"openaiCompatibleApiKeyLabel": "Khóa API:",
"openaiCompatibleModelDimensionLabel": "Kích thước nhúng:",
"openaiCompatibleModelDimensionPlaceholder": "ví dụ: 1536",
"openaiCompatibleModelDimensionDescription": "Kích thước nhúng (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 của bạn để biết giá trị này. Các 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 phù hợp hơn.",
"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
"startIndexingButton": "Bắt đầu chỉ mục hóa",
"stopIndexingButton": "Dừng chỉ mục hóa",
"clearIndexDataButton": "Xóa dữ liệu 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 chỉ mục hóa.",
"clearDataDialog": {
"title": "Bạn có chắc không?",
"description": "Hành động này không thể được hoàn tác. Thao tác này sẽ xóa vĩnh viễn dữ liệu chỉ mục cơ sở mã của bạn.",
"cancelButton": "Hủy",
"confirmButton": "Xóa dữ liệu"
}
},
"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.",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "清除数据"
}
},
"codeIndex": {
"title": "代码库索引",
"enableLabel": "启用代码库索引",
"enableDescription": "<0>代码库索引</0>是一项实验性功能,它使用 AI 嵌入为你的项目创建一个语义搜索索引。这使 Roo Code 能够通过基于含义而不仅仅是关键字查找相关代码,从而更好地理解和浏览大型代码库。",
"providerLabel": "嵌入服务提供商",
"selectProviderPlaceholder": "选择提供商",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI 兼容",
"openaiKeyLabel": "OpenAI 密钥:",
"openaiCompatibleBaseUrlLabel": "基本 URL",
"openaiCompatibleApiKeyLabel": "API 密钥:",
"openaiCompatibleModelDimensionLabel": "嵌入维度:",
"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": "开始索引",
"stopIndexingButton": "停止索引",
"clearIndexDataButton": "清除索引数据",
"unsavedSettingsMessage": "在开始索引过程之前,请保存你的设置。",
"clearDataDialog": {
"title": "你确定吗?",
"description": "此操作无法撤销。这将永久删除你的代码库索引数据。",
"cancelButton": "取消",
"confirmButton": "清除数据"
}
},
"autoApprove": {
"description": "允许 Roo 自动执行操作而无需批准。只有在您完全信任 AI 并了解相关安全风险的情况下才启用这些设置。",
"readOnly": {

View file

@ -70,6 +70,41 @@
"confirmButton": "清除資料"
}
},
"codeIndex": {
"title": "程式碼庫索引",
"enableLabel": "啟用程式碼庫索引",
"enableDescription": "<0>程式碼庫索引</0>是一項實驗性功能,它使用 AI 嵌入為您的專案建立一個語意搜尋索引。這使 Roo Code 能夠透過基於意義而不僅僅是關鍵字尋找相關程式碼,從而更好地理解和瀏覽大型程式碼庫。",
"providerLabel": "嵌入服務提供者",
"selectProviderPlaceholder": "選取提供者",
"openaiProvider": "OpenAI",
"ollamaProvider": "Ollama",
"openaiCompatibleProvider": "OpenAI 相容",
"openaiKeyLabel": "OpenAI 金鑰:",
"openaiCompatibleBaseUrlLabel": "基礎 URL",
"openaiCompatibleApiKeyLabel": "API 金鑰:",
"openaiCompatibleModelDimensionLabel": "嵌入維度:",
"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": "開始索引",
"stopIndexingButton": "停止索引",
"clearIndexDataButton": "清除索引資料",
"unsavedSettingsMessage": "在開始索引程序之前,請儲存您的設定。",
"clearDataDialog": {
"title": "您確定嗎?",
"description": "此操作無法復原。這將永久刪除您的程式碼庫索引資料。",
"cancelButton": "取消",
"confirmButton": "清除資料"
}
},
"autoApprove": {
"description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。",
"readOnly": {