diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 296f23bc5f..935998f6d7 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -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! diff --git a/src/services/code-index/__tests__/scanner-cancellation.spec.ts b/src/services/code-index/__tests__/scanner-cancellation.spec.ts new file mode 100644 index 0000000000..bdeee083e5 --- /dev/null +++ b/src/services/code-index/__tests__/scanner-cancellation.spec.ts @@ -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() + }) +}) diff --git a/src/services/code-index/__tests__/state-manager-throttling.spec.ts b/src/services/code-index/__tests__/state-manager-throttling.spec.ts new file mode 100644 index 0000000000..fb7e514fd3 --- /dev/null +++ b/src/services/code-index/__tests__/state-manager-throttling.spec.ts @@ -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 = 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 + + 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, + }), + ) + }) +}) diff --git a/src/services/code-index/__tests__/worker-pool.spec.ts b/src/services/code-index/__tests__/worker-pool.spec.ts new file mode 100644 index 0000000000..4ed36de212 --- /dev/null +++ b/src/services/code-index/__tests__/worker-pool.spec.ts @@ -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) + } + }) + }) +}) diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index 735bcee670..5d413cffe7 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -164,12 +164,25 @@ export class CodeIndexManager { } } + /** + * Cancels the current indexing operation. + */ + public async cancelIndexing(): Promise { + if (!this.isFeatureEnabled) { + return + } + if (this._orchestrator) { + this._orchestrator.cancelIndexing() + } + } + /** * Cleans up the manager instance. */ - public dispose(): void { + public async dispose(): Promise { if (this._orchestrator) { this.stopWatcher() + await this._orchestrator.dispose() } this._stateManager.dispose() } diff --git a/src/services/code-index/orchestrator.ts b/src/services/code-index/orchestrator.ts index 0d8151b5e1..685be1ade9 100644 --- a/src/services/code-index/orchestrator.ts +++ b/src/services/code-index/orchestrator.ts @@ -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 { + this.cancelIndexing() + this.stopWatcher() + if (this.scanner && "dispose" in this.scanner) { + await (this.scanner as any).dispose() + } + } } diff --git a/src/services/code-index/processors/scanner.ts b/src/services/code-index/processors/scanner.ts index 40475636bc..fa5a358702 100644 --- a/src/services/code-index/processors/scanner.ts +++ b/src/services/code-index/processors/scanner.ts @@ -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 { 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 { + if (this.workerPool) { + await this.workerPool.shutdown() + this.workerPool = undefined + } + } } diff --git a/src/services/code-index/state-manager.ts b/src/services/code-index/state-manager.ts index 90257fdfb1..8c274d15c7 100644 --- a/src/services/code-index/state-manager.ts +++ b/src/services/code-index/state-manager.ts @@ -10,6 +10,11 @@ export class CodeIndexStateManager { private _currentItemUnit: string = "blocks" private _progressEmitter = new vscode.EventEmitter>() + // Throttling properties + private _throttleTimer?: NodeJS.Timeout + private _pendingUpdate?: ReturnType + 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() } } diff --git a/src/services/code-index/workers/embedding.worker.ts b/src/services/code-index/workers/embedding.worker.ts new file mode 100644 index 0000000000..cc129da7d7 --- /dev/null +++ b/src/services/code-index/workers/embedding.worker.ts @@ -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 { + 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) + }) +} diff --git a/src/services/code-index/workers/file-processor.worker.ts b/src/services/code-index/workers/file-processor.worker.ts new file mode 100644 index 0000000000..e0b711c2bd --- /dev/null +++ b/src/services/code-index/workers/file-processor.worker.ts @@ -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 { + 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) + }) +} diff --git a/src/services/code-index/workers/worker-pool.ts b/src/services/code-index/workers/worker-pool.ts new file mode 100644 index 0000000000..4ff35b55f4 --- /dev/null +++ b/src/services/code-index/workers/worker-pool.ts @@ -0,0 +1,171 @@ +import { Worker } from "worker_threads" +import { cpus } from "os" + +export interface WorkerTask { + type: string + [key: string]: any +} + +export interface WorkerResult { + success: boolean + data?: T + error?: string +} + +interface QueueItem { + task: WorkerTask + resolve: (value: T) => void + reject: (reason: any) => void +} + +export class WorkerPool { + private workers: Worker[] = [] + private availableWorkers: Worker[] = [] + private queue: QueueItem[] = [] + private activeWorkers = new Map>() + 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(task: WorkerTask): Promise { + 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 { + 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() + } +} diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 0f1d22c3c7..3c3609ee30 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -158,6 +158,7 @@ export interface WebviewMessage { | "condenseTaskContextRequest" | "requestIndexingStatus" | "startIndexing" + | "cancelIndexing" | "clearIndexData" | "indexingStatusUpdate" | "indexCleared" diff --git a/webview-ui/src/components/settings/CodeIndexSettings.tsx b/webview-ui/src/components/settings/CodeIndexSettings.tsx index 8aae766b8f..f3ae2cd94f 100644 --- a/webview-ui/src/components/settings/CodeIndexSettings.tsx +++ b/webview-ui/src/components/settings/CodeIndexSettings.tsx @@ -485,6 +485,13 @@ export const CodeIndexSettings: React.FC = ({ {t("settings:codeIndex.startIndexingButton")} )} + {indexingStatus.systemStatus === "Indexing" && ( + vscode.postMessage({ type: "cancelIndexing" })} + appearance="secondary"> + {t("settings:codeIndex.stopIndexingButton")} + + )} {(indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && ( diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 3fe9cd78b9..9c9ab5e4ef 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -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 é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": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index bf33512650..7d61aa80f9 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Daten löschen" } }, + "codeIndex": { + "title": "Codebasis-Indizierung", + "enableLabel": "Codebasis-Indizierung aktivieren", + "enableDescription": "<0>Codebasis-Indizierung 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": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b18f526e15..2ef4e37bc6 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -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": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 92d12b4e24..4b023cb847 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -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 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": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 7c3f38b7b3..261e7c7e02 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -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 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": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 36cfc01896..e2221c017a 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -70,6 +70,41 @@ "confirmButton": "डेटा साफ़ करें" } }, + "codeIndex": { + "title": "कोडबेस इंडेक्सिंग", + "enableLabel": "कोडबेस इंडेक्सिंग सक्षम करें", + "enableDescription": "<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": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 08a23cd30c..feb48ba3e9 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Hapus Data" } }, + "codeIndex": { + "title": "Pengindeksan Basis Kode", + "enableLabel": "Aktifkan Pengindeksan Basis Kode", + "enableDescription": "<0>Pengindeksan Basis Kode 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": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 4897212d0f..a4c1ddcd7a 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -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 è 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": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 4165726ade..2d5ee1ab6a 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -70,6 +70,41 @@ "confirmButton": "データをクリア" } }, + "codeIndex": { + "title": "コードベースのインデックス作成", + "enableLabel": "コードベースのインデックス作成を有効にする", + "enableDescription": "<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": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index e68297599c..c587e8dfa9 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -70,6 +70,41 @@ "confirmButton": "데이터 지우기" } }, + "codeIndex": { + "title": "코드베이스 인덱싱", + "enableLabel": "코드베이스 인덱싱 활성화", + "enableDescription": "<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": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 254334df54..543630edcf 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Gegevens wissen" } }, + "codeIndex": { + "title": "Codebase-indexering", + "enableLabel": "Codebase-indexering inschakelen", + "enableDescription": "<0>Codebase-indexering 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": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 02a7d0d04d..f028282c33 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Wyczyść dane" } }, + "codeIndex": { + "title": "Indeksowanie bazy kodu", + "enableLabel": "Włącz indeksowanie bazy kodu", + "enableDescription": "<0>Indeksowanie bazy kodu 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": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 8c7b6c9a24..c6cfb2dae7 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -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 é 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": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index e60b97f775..1b55f17ce1 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Очистить данные" } }, + "codeIndex": { + "title": "Индексирование кодовой базы", + "enableLabel": "Включить индексирование кодовой базы", + "enableDescription": "<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": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index c7ced157f6..d3f9602273 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -70,6 +70,41 @@ "confirmButton": "Verileri Temizle" } }, + "codeIndex": { + "title": "Kod Tabanı İndeksleme", + "enableLabel": "Kod Tabanı İndekslemeyi Etkinleştir", + "enableDescription": "<0>Kod Tabanı İndeksleme, 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": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index c82bfdd7a2..de5b62f6e6 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -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ã 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": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index d4ce96e8e4..d50959fa2f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -70,6 +70,41 @@ "confirmButton": "清除数据" } }, + "codeIndex": { + "title": "代码库索引", + "enableLabel": "启用代码库索引", + "enableDescription": "<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": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index be6ad5d80c..d8bbc5ebc4 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -70,6 +70,41 @@ "confirmButton": "清除資料" } }, + "codeIndex": { + "title": "程式碼庫索引", + "enableLabel": "啟用程式碼庫索引", + "enableDescription": "<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": {