Преглед изворни кода

Merge pull request #2221 from Ffinnis/add-canceling-indexing

Add cancel indexing
Kevin van Dijk пре 4 месеци
родитељ
комит
aa02c62f1e
53 измењених фајлова са 538 додато и 48 уклоњено
  1. 5 0
      .changeset/flat-chicken-jump.md
  2. 32 0
      src/core/webview/webviewMessageHandler.ts
  3. 2 1
      src/i18n/locales/ar/embeddings.json
  4. 2 1
      src/i18n/locales/ca/embeddings.json
  5. 2 1
      src/i18n/locales/cs/embeddings.json
  6. 2 1
      src/i18n/locales/de/embeddings.json
  7. 2 1
      src/i18n/locales/en/embeddings.json
  8. 2 1
      src/i18n/locales/es/embeddings.json
  9. 2 1
      src/i18n/locales/fr/embeddings.json
  10. 2 1
      src/i18n/locales/hi/embeddings.json
  11. 2 1
      src/i18n/locales/id/embeddings.json
  12. 2 1
      src/i18n/locales/it/embeddings.json
  13. 2 1
      src/i18n/locales/ja/embeddings.json
  14. 2 1
      src/i18n/locales/ko/embeddings.json
  15. 2 1
      src/i18n/locales/nl/embeddings.json
  16. 2 1
      src/i18n/locales/pl/embeddings.json
  17. 2 1
      src/i18n/locales/pt-BR/embeddings.json
  18. 2 1
      src/i18n/locales/ru/embeddings.json
  19. 2 1
      src/i18n/locales/th/embeddings.json
  20. 2 1
      src/i18n/locales/tr/embeddings.json
  21. 2 1
      src/i18n/locales/uk/embeddings.json
  22. 2 1
      src/i18n/locales/vi/embeddings.json
  23. 2 1
      src/i18n/locales/zh-CN/embeddings.json
  24. 2 1
      src/i18n/locales/zh-TW/embeddings.json
  25. 14 0
      src/services/code-index/manager.ts
  26. 48 0
      src/services/code-index/orchestrator.ts
  27. 89 3
      src/services/code-index/processors/scanner.ts
  28. 1 0
      src/shared/WebviewMessage.ts
  29. 21 0
      webview-ui/src/components/chat/CodeIndexPopover.tsx
  30. 217 0
      webview-ui/src/components/chat/__tests__/CodeIndexPopover.spec.tsx
  31. 1 1
      webview-ui/src/components/kilocodeMcp/McpView.tsx
  32. 3 1
      webview-ui/src/i18n/locales/ar/settings.json
  33. 3 1
      webview-ui/src/i18n/locales/ca/settings.json
  34. 3 1
      webview-ui/src/i18n/locales/cs/settings.json
  35. 3 1
      webview-ui/src/i18n/locales/de/settings.json
  36. 3 1
      webview-ui/src/i18n/locales/en/settings.json
  37. 3 1
      webview-ui/src/i18n/locales/es/settings.json
  38. 3 1
      webview-ui/src/i18n/locales/fr/settings.json
  39. 3 1
      webview-ui/src/i18n/locales/hi/settings.json
  40. 3 1
      webview-ui/src/i18n/locales/id/settings.json
  41. 3 1
      webview-ui/src/i18n/locales/it/settings.json
  42. 3 1
      webview-ui/src/i18n/locales/ja/settings.json
  43. 3 1
      webview-ui/src/i18n/locales/ko/settings.json
  44. 3 1
      webview-ui/src/i18n/locales/nl/settings.json
  45. 3 1
      webview-ui/src/i18n/locales/pl/settings.json
  46. 3 1
      webview-ui/src/i18n/locales/pt-BR/settings.json
  47. 3 1
      webview-ui/src/i18n/locales/ru/settings.json
  48. 3 1
      webview-ui/src/i18n/locales/th/settings.json
  49. 3 1
      webview-ui/src/i18n/locales/tr/settings.json
  50. 3 1
      webview-ui/src/i18n/locales/uk/settings.json
  51. 3 1
      webview-ui/src/i18n/locales/vi/settings.json
  52. 3 1
      webview-ui/src/i18n/locales/zh-CN/settings.json
  53. 3 1
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 5 - 0
.changeset/flat-chicken-jump.md

@@ -0,0 +1,5 @@
+---
+"kilo-code": minor
+---
+
+Add ability to cancel code indexing process

+ 32 - 0
src/core/webview/webviewMessageHandler.ts

@@ -3110,6 +3110,38 @@ export const webviewMessageHandler = async (
 			}
 			break
 		}
+		// kilocode_change start
+		case "cancelIndexing": {
+			try {
+				const manager = provider.getCurrentWorkspaceCodeIndexManager()
+				if (!manager) {
+					provider.postMessageToWebview({
+						type: "indexingStatusUpdate",
+						values: {
+							systemStatus: "Error",
+							message: t("embeddings:orchestrator.indexingRequiresWorkspace"),
+							processedItems: 0,
+							totalItems: 0,
+							currentItemUnit: "items",
+						},
+					})
+					provider.log("Cannot cancel indexing: No workspace folder open")
+					return
+				}
+				if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
+					manager.cancelIndexing()
+					// Immediately reflect updated status to UI
+					provider.postMessageToWebview({
+						type: "indexingStatusUpdate",
+						values: manager.getCurrentStatus(),
+					})
+				}
+			} catch (error) {
+				provider.log(`Error canceling indexing: ${error instanceof Error ? error.message : String(error)}`)
+			}
+			break
+		}
+		// kilocode_change end
 		case "clearIndexData": {
 			try {
 				const manager = provider.getCurrentWorkspaceCodeIndexManager()

+ 2 - 1
src/i18n/locales/ar/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "تم إيقاف مراقب الملفات.",
 		"failedDuringInitialScan": "فشل أثناء المسح الأولي: {{errorMessage}}",
 		"unknownError": "خطأ غير معروف",
-		"indexingRequiresWorkspace": "الفهرسة تتطلب مجلد مشروع مفتوح"
+		"indexingRequiresWorkspace": "الفهرسة تتطلب مجلد مشروع مفتوح",
+		"indexingCancelled": "تم إلغاء الفهرسة"
 	}
 }

+ 2 - 1
src/i18n/locales/ca/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Monitor de fitxers aturat.",
 		"failedDuringInitialScan": "Ha fallat durant l'escaneig inicial: {{errorMessage}}",
 		"unknownError": "Error desconegut",
-		"indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta"
+		"indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta",
+		"indexingCancelled": "S'ha cancel·lat la indexació"
 	}
 }

+ 2 - 1
src/i18n/locales/cs/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Pozorovatel souborů byl zastaven.",
 		"failedDuringInitialScan": "Selhalo během úvodního skenování: {{errorMessage}}",
 		"unknownError": "Neznámá chyba",
-		"indexingRequiresWorkspace": "Indexování vyžaduje otevřenou složku workspace"
+		"indexingRequiresWorkspace": "Indexování vyžaduje otevřenou složku workspace",
+		"indexingCancelled": "Indexování bylo zrušeno"
 	}
 }

+ 2 - 1
src/i18n/locales/de/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Datei-Watcher gestoppt.",
 		"failedDuringInitialScan": "Fehler während des ersten Scans: {{errorMessage}}",
 		"unknownError": "Unbekannter Fehler",
-		"indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner"
+		"indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner",
+		"indexingCancelled": "Indizierung abgebrochen"
 	}
 }

+ 2 - 1
src/i18n/locales/en/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "File watcher stopped.",
 		"failedDuringInitialScan": "Failed during initial scan: {{errorMessage}}",
 		"unknownError": "Unknown error",
-		"indexingRequiresWorkspace": "Indexing requires an open workspace folder"
+		"indexingRequiresWorkspace": "Indexing requires an open workspace folder",
+		"indexingCancelled": "Indexing cancelled"
 	}
 }

+ 2 - 1
src/i18n/locales/es/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Monitor de archivos detenido.",
 		"failedDuringInitialScan": "Falló durante el escaneo inicial: {{errorMessage}}",
 		"unknownError": "Error desconocido",
-		"indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta"
+		"indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta",
+		"indexingCancelled": "Indexación cancelada"
 	}
 }

+ 2 - 1
src/i18n/locales/fr/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Surveillant de fichiers arrêté.",
 		"failedDuringInitialScan": "Échec lors du scan initial : {{errorMessage}}",
 		"unknownError": "Erreur inconnue",
-		"indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace"
+		"indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace",
+		"indexingCancelled": "Indexation annulée"
 	}
 }

+ 2 - 1
src/i18n/locales/hi/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "फाइल वॉचर रुक गया।",
 		"failedDuringInitialScan": "प्रारंभिक स्कैन के दौरान असफल: {{errorMessage}}",
 		"unknownError": "अज्ञात त्रुटि",
-		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है"
+		"indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है",
+		"indexingCancelled": "इंडेक्सिंग रद्द की गई"
 	}
 }

+ 2 - 1
src/i18n/locales/id/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Pemantau file dihentikan.",
 		"failedDuringInitialScan": "Gagal selama pemindaian awal: {{errorMessage}}",
 		"unknownError": "Kesalahan tidak diketahui",
-		"indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka"
+		"indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka",
+		"indexingCancelled": "Pengindeksan dibatalkan"
 	}
 }

+ 2 - 1
src/i18n/locales/it/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Monitoraggio file fermato.",
 		"failedDuringInitialScan": "Fallito durante la scansione iniziale: {{errorMessage}}",
 		"unknownError": "Errore sconosciuto",
-		"indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta"
+		"indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta",
+		"indexingCancelled": "Indicizzazione annullata"
 	}
 }

+ 2 - 1
src/i18n/locales/ja/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "ファイルウォッチャーが停止されました。",
 		"failedDuringInitialScan": "初期スキャン中に失敗しました:{{errorMessage}}",
 		"unknownError": "不明なエラー",
-		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です"
+		"indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です",
+		"indexingCancelled": "インデックス作成をキャンセルしました"
 	}
 }

+ 2 - 1
src/i18n/locales/ko/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "파일 감시자가 중지되었습니다.",
 		"failedDuringInitialScan": "초기 스캔 중 실패: {{errorMessage}}",
 		"unknownError": "알 수 없는 오류",
-		"indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다"
+		"indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다",
+		"indexingCancelled": "인덱싱이 취소되었습니다"
 	}
 }

+ 2 - 1
src/i18n/locales/nl/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Bestandsmonitor gestopt.",
 		"failedDuringInitialScan": "Mislukt tijdens initiële scan: {{errorMessage}}",
 		"unknownError": "Onbekende fout",
-		"indexingRequiresWorkspace": "Indexering vereist een geopende workspace map"
+		"indexingRequiresWorkspace": "Indexering vereist een geopende workspace map",
+		"indexingCancelled": "Indexering geannuleerd"
 	}
 }

+ 2 - 1
src/i18n/locales/pl/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Monitor plików zatrzymany.",
 		"failedDuringInitialScan": "Niepowodzenie podczas początkowego skanowania: {{errorMessage}}",
 		"unknownError": "Nieznany błąd",
-		"indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace"
+		"indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace",
+		"indexingCancelled": "Indeksowanie anulowano"
 	}
 }

+ 2 - 1
src/i18n/locales/pt-BR/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Monitor de arquivos parado.",
 		"failedDuringInitialScan": "Falhou durante a varredura inicial: {{errorMessage}}",
 		"unknownError": "Erro desconhecido",
-		"indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta"
+		"indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta",
+		"indexingCancelled": "Indexação cancelada"
 	}
 }

+ 2 - 1
src/i18n/locales/ru/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Наблюдатель файлов остановлен.",
 		"failedDuringInitialScan": "Ошибка во время первоначального сканирования: {{errorMessage}}",
 		"unknownError": "Неизвестная ошибка",
-		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства"
+		"indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства",
+		"indexingCancelled": "Индексация отменена"
 	}
 }

+ 2 - 1
src/i18n/locales/th/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "หยุดตัวติดตามไฟล์แล้ว",
 		"failedDuringInitialScan": "ล้มเหลวระหว่างการสแกนเริ่มต้น: {{errorMessage}}",
 		"unknownError": "ข้อผิดพลาดที่ไม่ทราบสาเหตุ",
-		"indexingRequiresWorkspace": "การจัดทำดัชนีต้องการโฟลเดอร์พื้นที่ทำงานที่เปิดอยู่"
+		"indexingRequiresWorkspace": "การจัดทำดัชนีต้องการโฟลเดอร์พื้นที่ทำงานที่เปิดอยู่",
+		"indexingCancelled": "ยกเลิกการจัดทำดัชนีแล้ว"
 	}
 }

+ 2 - 1
src/i18n/locales/tr/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Dosya izleyici durduruldu.",
 		"failedDuringInitialScan": "İlk tarama sırasında başarısız: {{errorMessage}}",
 		"unknownError": "Bilinmeyen hata",
-		"indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir"
+		"indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir",
+		"indexingCancelled": "İndeksleme iptal edildi"
 	}
 }

+ 2 - 1
src/i18n/locales/uk/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Спостерігач файлів зупинено.",
 		"failedDuringInitialScan": "Не вдалося під час початкового сканування: {{errorMessage}}",
 		"unknownError": "Невідома помилка",
-		"indexingRequiresWorkspace": "Індексування вимагає відкритої папки робочого простору"
+		"indexingRequiresWorkspace": "Індексування вимагає відкритої папки робочого простору",
+		"indexingCancelled": "Індексацію скасовано"
 	}
 }

+ 2 - 1
src/i18n/locales/vi/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "Trình theo dõi tệp đã dừng.",
 		"failedDuringInitialScan": "Thất bại trong quá trình quét ban đầu: {{errorMessage}}",
 		"unknownError": "Lỗi không xác định",
-		"indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở"
+		"indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở",
+		"indexingCancelled": "Đã hủy lập chỉ mục"
 	}
 }

+ 2 - 1
src/i18n/locales/zh-CN/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "文件监控已停止。",
 		"failedDuringInitialScan": "初始扫描失败:{{errorMessage}}",
 		"unknownError": "未知错误",
-		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹"
+		"indexingRequiresWorkspace": "索引需要打开的工作区文件夹",
+		"indexingCancelled": "索引已取消"
 	}
 }

+ 2 - 1
src/i18n/locales/zh-TW/embeddings.json

@@ -61,6 +61,7 @@
 		"fileWatcherStopped": "檔案監控已停止。",
 		"failedDuringInitialScan": "初始掃描失敗:{{errorMessage}}",
 		"unknownError": "未知錯誤",
-		"indexingRequiresWorkspace": "索引需要開啟的工作區資料夾"
+		"indexingRequiresWorkspace": "索引需要開啟的工作區資料夾",
+		"indexingCancelled": "已取消索引"
 	}
 }

+ 14 - 0
src/services/code-index/manager.ts

@@ -203,6 +203,20 @@ export class CodeIndexManager {
 		}
 	}
 
+	// kilocode_change start
+	/**
+	 * Cancel any active indexing activity immediately.
+	 */
+	public cancelIndexing(): void {
+		if (!this.isFeatureEnabled) {
+			return
+		}
+		if (this._orchestrator) {
+			this._orchestrator.cancelIndexing()
+		}
+	}
+	// kilocode_change end
+
 	/**
 	 * Recovers from error state by clearing the error and resetting internal state.
 	 * This allows the manager to be re-initialized after a recoverable error.

+ 48 - 0
src/services/code-index/orchestrator.ts

@@ -15,6 +15,7 @@ import { t } from "../../i18n"
 export class CodeIndexOrchestrator {
 	private _fileWatcherSubscriptions: vscode.Disposable[] = []
 	private _isProcessing: boolean = false
+	private _cancelRequested: boolean = false // kilocode_change
 
 	constructor(
 		private readonly configManager: CodeIndexConfigManager,
@@ -120,6 +121,7 @@ export class CodeIndexOrchestrator {
 			return
 		}
 
+		this._cancelRequested = false // kilocode_change
 		this._isProcessing = true
 		this.stateManager.setSystemState("Indexing", "Initializing services...")
 
@@ -130,6 +132,14 @@ export class CodeIndexOrchestrator {
 				await this.cacheManager.clearCacheFile()
 			}
 
+			// kilocode_change start
+			if (this._cancelRequested) {
+				this._isProcessing = false
+				this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingCancelled"))
+				return
+			}
+			// kilocode_change end
+
 			this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...")
 
 			let cumulativeBlocksIndexed = 0
@@ -137,11 +147,13 @@ export class CodeIndexOrchestrator {
 			let batchErrors: Error[] = []
 
 			const handleFileParsed = (fileBlockCount: number) => {
+				if (this._cancelRequested) return // kilocode_change
 				cumulativeBlocksFoundSoFar += fileBlockCount
 				this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
 			}
 
 			const handleBlocksIndexed = (indexedCount: number) => {
+				if (this._cancelRequested) return // kilocode_change
 				cumulativeBlocksIndexed += indexedCount
 				this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar)
 			}
@@ -163,6 +175,16 @@ export class CodeIndexOrchestrator {
 				throw new Error("Scan failed, is scanner initialized?")
 			}
 
+			// kilocode_change start
+			if (this._cancelRequested || this.scanner.isCancelled) {
+				this._isProcessing = false
+				if (this.stateManager.state !== "Error") {
+					this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingCancelled"))
+				}
+				return
+			}
+			// kilocode_change end
+
 			const { stats } = result
 
 			// Check if any blocks were actually indexed successfully
@@ -249,6 +271,32 @@ export class CodeIndexOrchestrator {
 		this._isProcessing = false
 	}
 
+	// kilocode_change start
+	/**
+	 * Gracefully cancels any ongoing indexing work.
+	 * - Stops the watcher if active
+	 * - Signals the scanner to cancel pending/ongoing work
+	 * - Updates UI state to reflect cancellation
+	 */
+	public cancelIndexing(): void {
+		// Mark cancellation so progress callbacks and subsequent steps are ignored
+		this._cancelRequested = true
+
+		// Best-effort cancel of any ongoing initial scan/batch work
+		// (DirectoryScanner will no-op quickly on next checks)
+		this.scanner.cancel()
+
+		// Ensure the watcher is stopped if it had started
+		this.stopWatcher()
+
+		// Reflect cancellation to the UI
+		this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingCancelled"))
+
+		// Clear processing flag
+		this._isProcessing = false
+	}
+	// kilocode_change end
+
 	/**
 	 * Clears all index data by stopping the watcher, clearing the vector store,
 	 * and resetting the cache file.

+ 89 - 3
src/services/code-index/processors/scanner.ts

@@ -32,6 +32,7 @@ import { sanitizeErrorMessage } from "../shared/validation-helpers"
 import { Package } from "../../../shared/package"
 
 export class DirectoryScanner implements IDirectoryScanner {
+	private _cancelled = false // kilocode_change
 	private readonly batchSegmentThreshold: number
 
 	constructor(
@@ -58,6 +59,21 @@ export class DirectoryScanner implements IDirectoryScanner {
 		}
 	}
 
+	// kilocode_change start
+	/**
+	 * Request cooperative cancellation of any in-flight scanning work.
+	 * The scanDirectory and batch operations periodically check this flag
+	 * and will exit as soon as practical.
+	 */
+	public cancel(): void {
+		this._cancelled = true
+	}
+
+	public get isCancelled(): boolean {
+		return this._cancelled
+	}
+	// kilocode_change end
+
 	/**
 	 * Recursively scans a directory for code blocks in supported files.
 	 * @param directoryPath The directory to scan
@@ -72,6 +88,11 @@ export class DirectoryScanner implements IDirectoryScanner {
 		onBlocksIndexed?: (indexedCount: number) => void,
 		onFileParsed?: (fileBlockCount: number) => void,
 	): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
+		// kilocode_change start
+		// reset cooperative cancel flag on new full scan
+		this._cancelled = false
+		// kilocode_change end
+
 		const directoryPath = directory
 		// Capture workspace context at scan start
 		const scanWorkspace = getWorkspacePathForContext(directoryPath)
@@ -126,9 +147,22 @@ export class DirectoryScanner implements IDirectoryScanner {
 		// Process all files in parallel with concurrency control
 		const parsePromises = supportedPaths.map((filePath) =>
 			parseLimiter(async () => {
+				// kilocode_change start
+				// Early exit if cancellation requested
+				if (this._cancelled) {
+					return
+				}
+				// kilocode_change end
+
 				try {
 					// Check file size
 					const stats = await stat(filePath)
+					// kilocode_change start
+					if (this._cancelled) {
+						return
+					}
+					// kilocode_change end
+
 					if (stats.size > MAX_FILE_SIZE_BYTES) {
 						skippedCount++ // Skip large files
 						return
@@ -139,6 +173,12 @@ export class DirectoryScanner implements IDirectoryScanner {
 						.readFile(vscode.Uri.file(filePath))
 						.then((buffer) => Buffer.from(buffer).toString("utf-8"))
 
+					// kilocode_change start
+					if (this._cancelled) {
+						return
+					}
+					// kilocode_change end
+
 					// Calculate current hash
 					const currentFileHash = createHash("sha256").update(content).digest("hex")
 					processedFiles.add(filePath)
@@ -154,6 +194,13 @@ 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 })
+
+					// kilocode_change start
+					if (this._cancelled) {
+						return
+					}
+					// kilocode_change end
+
 					const fileBlockCount = blocks.length
 					onFileParsed?.(fileBlockCount)
 					processedCount++
@@ -163,10 +210,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 						// Add to batch accumulators
 						let addedBlocksFromFile = false
 						for (const block of blocks) {
+							if (this._cancelled) break // kilocode_change
 							const trimmedContent = block.content.trim()
 							if (trimmedContent) {
 								const release = await mutex.acquire()
 								try {
+									// kilocode_change start
+									if (this._cancelled) {
+										// Abort adding more items if cancelled
+										break
+									}
+									// kilocode_change end
 									currentBatchBlocks.push(block)
 									currentBatchTexts.push(trimmedContent)
 									addedBlocksFromFile = true
@@ -175,10 +229,17 @@ export class DirectoryScanner implements IDirectoryScanner {
 									if (currentBatchBlocks.length >= this.batchSegmentThreshold) {
 										// Wait if we've reached the maximum pending batches
 										while (pendingBatchCount >= MAX_PENDING_BATCHES) {
+											if (this._cancelled) break // kilocode_change
 											// Wait for at least one batch to complete
 											await Promise.race(activeBatchPromises)
 										}
 
+										// kilocode_change start
+										if (this._cancelled) {
+											break
+										}
+										// kilocode_change end
+
 										// Copy current batch data and clear accumulators
 										const batchBlocks = [...currentBatchBlocks]
 										const batchTexts = [...currentBatchTexts]
@@ -258,7 +319,8 @@ export class DirectoryScanner implements IDirectoryScanner {
 		await Promise.all(parsePromises)
 
 		// Process any remaining items in batch
-		if (currentBatchBlocks.length > 0) {
+		// kilocode_change: add !this._cancelled
+		if (!this._cancelled && currentBatchBlocks.length > 0) {
 			const release = await mutex.acquire()
 			try {
 				// Copy current batch data and clear accumulators
@@ -288,8 +350,20 @@ export class DirectoryScanner implements IDirectoryScanner {
 			}
 		}
 
-		// Wait for all batch processing to complete
-		await Promise.all(activeBatchPromises)
+		// kilocode_change start
+		// Short-circuit if cancelled before handling deletions
+		if (this._cancelled) {
+			return {
+				stats: {
+					processed: processedCount,
+					skipped: skippedCount,
+				},
+				totalBlockCount,
+			}
+		} else {
+			await Promise.all(activeBatchPromises)
+		}
+		// kilocode_change end
 
 		// Handle deleted files
 		const oldHashes = this.cacheManager.getAllHashes()
@@ -355,6 +429,9 @@ export class DirectoryScanner implements IDirectoryScanner {
 		onBlocksIndexed?: (indexedCount: number) => void,
 	): Promise<void> {
 		// kilocode_change start
+		// Respect cooperative cancellation
+		if (this._cancelled || batchBlocks.length === 0) return
+
 		if (batchBlocks.length === 0) {
 			console.debug("[DirectoryScanner] Skipping empty batch processing")
 			return
@@ -371,7 +448,10 @@ export class DirectoryScanner implements IDirectoryScanner {
 
 		while (attempts < MAX_BATCH_RETRIES && !success) {
 			attempts++
+
 			// kilocode_change start
+			if (this._cancelled) return
+
 			console.debug(
 				`[DirectoryScanner] Processing batch attempt ${attempts}/${MAX_BATCH_RETRIES} for ${batchBlocks.length} blocks`,
 			)
@@ -432,7 +512,10 @@ export class DirectoryScanner implements IDirectoryScanner {
 				// --- End Deletion Step ---
 
 				// Create embeddings for batch
+				if (this._cancelled) return // kilocode_change
+
 				console.debug(`[DirectoryScanner] Creating embeddings for ${batchTexts.length} texts`) // kilocode_change
+
 				const { embeddings } = await this.embedder.createEmbeddings(batchTexts)
 				console.debug(`[DirectoryScanner] Successfully created ${embeddings.length} embeddings`) // kilocode_change
 
@@ -459,7 +542,10 @@ export class DirectoryScanner implements IDirectoryScanner {
 				console.debug(`[DirectoryScanner] Prepared ${points.length} points for Qdrant`) // kilocode_change
 
 				// Upsert points to Qdrant
+				if (this._cancelled) return // kilocode_change
+
 				console.debug("[DirectoryScanner] Starting Qdrant upsert") // kilocode_change
+
 				await this.qdrantClient.upsertPoints(points)
 				console.debug("[DirectoryScanner] Completed Qdrant upsert") // kilocode_change
 				onBlocksIndexed?.(batchBlocks.length)

+ 1 - 0
src/shared/WebviewMessage.ts

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

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

@@ -482,6 +482,17 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 	// Use the shared ESC key handler hook - respects unsaved changes logic
 	useEscapeKey(open, handlePopoverClose)
 
+	// kilocode_change start
+	const handleCancelIndexing = useCallback(() => {
+		// Optimistically update UI while backend cancels
+		setIndexingStatus((prev) => ({
+			...prev,
+			message: t("settings:codeIndex.cancelling"),
+		}))
+		vscode.postMessage({ type: "cancelIndexing" })
+	}, [t])
+	// kilocode_change end
+
 	const handleSaveSettings = () => {
 		// Validate settings before saving
 		if (!validateSettings()) {
@@ -1294,6 +1305,16 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 						{/* Action Buttons */}
 						<div className="flex items-center justify-between gap-2 pt-6">
 							<div className="flex gap-2">
+								{/* kilocode_change start */}
+								{currentSettings.codebaseIndexEnabled && indexingStatus.systemStatus === "Indexing" && (
+									<VSCodeButton
+										appearance="secondary"
+										onClick={handleCancelIndexing}
+										disabled={saveStatus === "saving"}>
+										{t("settings:codeIndex.cancelIndexingButton")}
+									</VSCodeButton>
+								)}
+								{/* kilocode_change end */}
 								{currentSettings.codebaseIndexEnabled &&
 									(indexingStatus.systemStatus === "Error" ||
 										indexingStatus.systemStatus === "Standby") && (

+ 217 - 0
webview-ui/src/components/chat/__tests__/CodeIndexPopover.spec.tsx

@@ -0,0 +1,217 @@
+import { render, screen, fireEvent } from "@/utils/test-utils"
+import React from "react"
+import type { IndexingStatus } from "@roo/ExtensionMessage"
+import { CodeIndexPopover } from "../CodeIndexPopover"
+import { vscode } from "@src/utils/vscode"
+
+// Mock external dependencies
+vi.mock("@src/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+const t = (key: string) => {
+	const translations: Record<string, string> = {
+		"settings:codeIndex.title": "Codebase Indexing",
+		"settings:codeIndex.cancelIndexingButton": "Cancel Indexing",
+		"settings:codeIndex.cancelling": "Cancelling...",
+		"settings:codeIndex.indexingStatuses.indexing": "Indexing",
+		"settings:codeIndex.indexingStatuses.standby": "Standby",
+		"settings:codeIndex.description": "settings:codeIndex.description",
+		"settings:codeIndex.enableLabel": "Enable",
+		"settings:codeIndex.statusTitle": "Status",
+		"settings:codeIndex.setupConfigLabel": "Setup",
+		"settings:codeIndex.advancedConfigLabel": "Advanced",
+	}
+	return translations[key] || key
+}
+
+const mockUseAppTranslation = { t }
+vi.mock("@src/i18n/TranslationContext", () => ({
+	useAppTranslation: () => mockUseAppTranslation,
+}))
+
+vi.mock("react-i18next", () => ({
+	Trans: ({ i18nKey, children }: { i18nKey?: string; children?: React.ReactNode }) => (
+		<>{i18nKey ? <span>{i18nKey}</span> : children}</>
+	),
+}))
+
+const mockExtensionState = {
+	codebaseIndexConfig: {
+		codebaseIndexEnabled: true,
+		codebaseIndexQdrantUrl: "http://localhost:6333",
+		codebaseIndexEmbedderProvider: "openai",
+		codebaseIndexEmbedderModelId: "text-embedding-3-small",
+		codebaseIndexSearchMaxResults: 5,
+		codebaseIndexSearchMinScore: 0.5,
+	},
+	codebaseIndexModels: {
+		openai: {
+			"text-embedding-3-small": { dimension: 1536 },
+			"text-embedding-ada-002": { dimension: 1536 },
+		},
+	},
+	cwd: "/test/workspace",
+}
+vi.mock("@src/context/ExtensionStateContext", () => ({
+	useExtensionState: () => mockExtensionState,
+}))
+
+// Mock UI components
+vi.mock("@vscode/webview-ui-toolkit/react", () => ({
+	VSCodeButton: ({
+		children,
+		onClick,
+		...props
+	}: {
+		children: React.ReactNode
+		onClick?: () => void
+		[key: string]: any
+	}) => (
+		<button onClick={onClick} {...props}>
+			{children}
+		</button>
+	),
+	VSCodeTextField: ({
+		onInput,
+		value,
+		...props
+	}: {
+		onInput?: (e: any) => void
+		value?: string
+		[key: string]: any
+	}) => <input value={value || ""} onChange={onInput} {...props} />,
+	VSCodeDropdown: ({ children, ...props }: { children: React.ReactNode; [key: string]: any }) => (
+		<select {...props}>{children}</select>
+	),
+	VSCodeOption: ({ children, value, ...props }: { children: React.ReactNode; value: string; [key: string]: any }) => (
+		<option value={value} {...props}>
+			{children}
+		</option>
+	),
+	VSCodeLink: ({ children, href, ...props }: { children: React.ReactNode; href?: string; [key: string]: any }) => (
+		<a href={href} {...props}>
+			{children}
+		</a>
+	),
+	VSCodeCheckbox: ({
+		children,
+		checked,
+		onChange,
+		...props
+	}: {
+		children: React.ReactNode
+		checked?: boolean
+		onChange?: (e: any) => void
+		[key: string]: any
+	}) => (
+		<label {...props}>
+			<input type="checkbox" checked={checked || false} onChange={onChange} />
+			{children}
+		</label>
+	),
+}))
+
+vi.mock("@src/components/ui", () => ({
+	Popover: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	PopoverTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	PopoverContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	Select: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	SelectContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	SelectItem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	SelectTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	SelectValue: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	AlertDialog: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	AlertDialogTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	AlertDialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
+	AlertDialogAction: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => (
+		<button onClick={onClick}>{children}</button>
+	),
+	AlertDialogCancel: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => (
+		<button onClick={onClick}>{children}</button>
+	),
+	AlertDialogDescription: ({ children }: { children: React.ReactNode }) => <p>{children}</p>,
+	AlertDialogFooter: ({ children }: { children: React.ReactNode }) => <footer>{children}</footer>,
+	AlertDialogHeader: ({ children }: { children: React.ReactNode }) => <header>{children}</header>,
+	AlertDialogTitle: ({ children }: { children: React.ReactNode }) => <h4>{children}</h4>,
+	Slider: () => <div>Slider</div>,
+	StandardTooltip: ({ children, content }: { children: React.ReactNode; content: string }) => (
+		<div title={content}>{children}</div>
+	),
+}))
+
+vi.mock("@radix-ui/react-progress", () => ({
+	Root: ({ children, ...props }: { children: React.ReactNode; [key: string]: any }) => (
+		<div {...props}>{children}</div>
+	),
+	Indicator: (props: any) => <div {...props} />,
+}))
+
+vi.mock("lucide-react", () => ({
+	AlertTriangle: (props: any) => <span {...props}>Alert</span>,
+}))
+
+vi.mock("@src/components/ui/hooks/useRooPortal", () => ({
+	useRooPortal: () => document.body,
+}))
+
+vi.mock("@src/hooks/useEscapeKey", () => ({
+	useEscapeKey: vi.fn(),
+}))
+
+vi.mock("@src/utils/docLinks", () => ({
+	buildDocLink: (path: string) => `https://docs.example.com/${path}`,
+}))
+
+describe("CodeIndexPopover", () => {
+	const initialIndexingStatus: IndexingStatus = {
+		systemStatus: "Standby",
+		message: "",
+		processedItems: 0,
+		totalItems: 0,
+		currentItemUnit: "items",
+	}
+
+	it("should render the popover window content", () => {
+		render(
+			<CodeIndexPopover indexingStatus={initialIndexingStatus}>
+				<button>Open Popover</button>
+			</CodeIndexPopover>,
+		)
+
+		// With the simplified mock, the content is always rendered.
+		expect(screen.getByText("Codebase Indexing")).toBeInTheDocument()
+		expect(screen.getByText("settings:codeIndex.description")).toBeInTheDocument()
+		expect(screen.getByText("Enable")).toBeInTheDocument()
+		expect(screen.getByText("Status")).toBeInTheDocument()
+		expect(screen.getByText("Setup")).toBeInTheDocument()
+		expect(screen.getByText("Advanced")).toBeInTheDocument()
+	})
+
+	it("should send cancelIndexing message when cancel button is clicked", async () => {
+		const indexingStatus: IndexingStatus = {
+			systemStatus: "Indexing",
+			message: "Processing...",
+			processedItems: 50,
+			totalItems: 100,
+			currentItemUnit: "items",
+		}
+
+		render(
+			<CodeIndexPopover indexingStatus={indexingStatus}>
+				<button>Open Popover</button>
+			</CodeIndexPopover>,
+		)
+
+		const cancelButton = screen.getByText("Cancel Indexing")
+		fireEvent.click(cancelButton)
+
+		expect(vscode.postMessage).toHaveBeenCalledWith({ type: "cancelIndexing" })
+
+		// Check for optimistic UI update for the message
+		const statusMessage = await screen.findByText(/Cancelling.../i)
+		expect(statusMessage).toBeInTheDocument()
+	})
+})

+ 1 - 1
webview-ui/src/components/kilocodeMcp/McpView.tsx

@@ -62,7 +62,7 @@ const McpView = () => {
 						{activeTab === "marketplace" && (
 							<MarketplaceView hideHeader targetTab="mcp" stateManager={marketplaceStateManager} />
 						)}
-						{activeTab === "installed" && <RooMcpView hideHeader onDone={() => { }} />}
+						{activeTab === "installed" && <RooMcpView hideHeader onDone={() => {}} />}
 					</div>
 				</div>
 			</Section>

+ 3 - 1
webview-ui/src/i18n/locales/ar/settings.json

@@ -139,7 +139,9 @@
 			"ollamaBaseUrlRequired": "رابط Ollama الأساسي مطلوب",
 			"baseUrlRequired": "الرابط الأساسي مطلوب",
 			"modelDimensionMinValue": "بُعد النموذج يجب أن يكون أكبر من 0"
-		}
+		},
+		"cancelling": "جارٍ الإلغاء...",
+		"cancelIndexingButton": "إلغاء الفهرسة"
 	},
 	"autoApproval": {
 		"title": "الموافقة التلقائية",

+ 3 - 1
webview-ui/src/i18n/locales/ca/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)",
 		"searchMaxResultsLabel": "Màxim de resultats de cerca",
 		"searchMaxResultsDescription": "Nombre màxim de resultats de cerca a retornar quan es consulta l'índex de la base de codi. Els valors més alts proporcionen més context però poden incloure resultats menys rellevants.",
-		"resetToDefault": "Restablir al valor per defecte"
+		"resetToDefault": "Restablir al valor per defecte",
+		"cancelling": "S'està cancel·lant...",
+		"cancelIndexingButton": "Cancel·la la indexació"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Pots configurar una drecera global per a aquesta configuració <SettingsLink>a les preferències del teu IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/cs/settings.json

@@ -139,7 +139,9 @@
 			"ollamaBaseUrlRequired": "Základní URL Ollama je povinné",
 			"baseUrlRequired": "Základní URL je povinné",
 			"modelDimensionMinValue": "Dimenze modelu musí být větší než 0"
-		}
+		},
+		"cancelling": "Probíhá rušení...",
+		"cancelIndexingButton": "Zrušit indexování"
 	},
 	"autoApproval": {
 		"title": "Automatické schválení",

+ 3 - 1
webview-ui/src/i18n/locales/de/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)",
 		"searchMaxResultsLabel": "Maximale Suchergebnisse",
 		"searchMaxResultsDescription": "Maximale Anzahl von Suchergebnissen, die bei der Abfrage des Codebase-Index zurückgegeben werden. Höhere Werte bieten mehr Kontext, können aber weniger relevante Ergebnisse enthalten.",
-		"resetToDefault": "Auf Standard zurücksetzen"
+		"resetToDefault": "Auf Standard zurücksetzen",
+		"cancelling": "Wird abgebrochen...",
+		"cancelIndexingButton": "Indizierung abbrechen"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Du kannst <SettingsLink>in deinen IDE-Einstellungen</SettingsLink> einen globalen Shortcut für diese Einstellung konfigurieren.",

+ 3 - 1
webview-ui/src/i18n/locales/en/settings.json

@@ -139,7 +139,9 @@
 			"ollamaBaseUrlRequired": "Ollama base URL is required",
 			"baseUrlRequired": "Base URL is required",
 			"modelDimensionMinValue": "Model dimension must be greater than 0"
-		}
+		},
+		"cancelling": "Cancelling...",
+		"cancelIndexingButton": "Cancel Indexing"
 	},
 	"autoApprove": {
 		"description": "Run these actions without asking for permission. Only enable for actions you fully trust and if you understand the security risks.",

+ 3 - 1
webview-ui/src/i18n/locales/es/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
 		"searchMaxResultsLabel": "Resultados máximos de búsqueda",
 		"searchMaxResultsDescription": "Número máximo de resultados de búsqueda a devolver al consultar el índice de código. Valores más altos proporcionan más contexto pero pueden incluir resultados menos relevantes.",
-		"resetToDefault": "Restablecer al valor predeterminado"
+		"resetToDefault": "Restablecer al valor predeterminado",
+		"cancelling": "Cancelando...",
+		"cancelIndexingButton": "Cancelar la indexación"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Puedes configurar un atajo global para esta configuración <SettingsLink>en las preferencias de tu IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/fr/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)",
 		"searchMaxResultsLabel": "Résultats de recherche maximum",
 		"searchMaxResultsDescription": "Nombre maximum de résultats de recherche à retourner lors de l'interrogation de l'index de code. Des valeurs plus élevées fournissent plus de contexte mais peuvent inclure des résultats moins pertinents.",
-		"resetToDefault": "Réinitialiser par défaut"
+		"resetToDefault": "Réinitialiser par défaut",
+		"cancelling": "Annulation...",
+		"cancelIndexingButton": "Annuler l'indexation"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Vous pouvez configurer un raccourci global pour ce paramètre <SettingsLink>dans les préférences de votre IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/hi/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)",
 		"searchMaxResultsLabel": "अधिकतम खोज परिणाम",
 		"searchMaxResultsDescription": "कोडबेस इंडेक्स को क्वेरी करते समय वापस करने के लिए खोज परिणामों की अधिकतम संख्या। उच्च मान अधिक संदर्भ प्रदान करते हैं लेकिन कम प्रासंगिक परिणाम शामिल कर सकते हैं।",
-		"resetToDefault": "डिफ़ॉल्ट पर रीसेट करें"
+		"resetToDefault": "डिफ़ॉल्ट पर रीसेट करें",
+		"cancelling": "रद्द किया जा रहा है...",
+		"cancelIndexingButton": "इंडेक्सिंग रद्द करें"
 	},
 	"autoApprove": {
 		"toggleShortcut": "आप <SettingsLink>अपनी आईडीई वरीयताओं में</SettingsLink> इस सेटिंग के लिए एक वैश्विक शॉर्टकट कॉन्फ़िगर कर सकते हैं।",

+ 3 - 1
webview-ui/src/i18n/locales/id/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)",
 		"searchMaxResultsLabel": "Hasil Pencarian Maksimum",
 		"searchMaxResultsDescription": "Jumlah maksimum hasil pencarian yang dikembalikan saat melakukan query indeks basis kode. Nilai yang lebih tinggi memberikan lebih banyak konteks tetapi mungkin menyertakan hasil yang kurang relevan.",
-		"resetToDefault": "Reset ke default"
+		"resetToDefault": "Reset ke default",
+		"cancelling": "Membatalkan...",
+		"cancelIndexingButton": "Batalkan pengindeksan"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Anda dapat mengonfigurasi pintasan global untuk pengaturan ini <SettingsLink>di preferensi IDE Anda</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/it/settings.json

@@ -134,7 +134,9 @@
 		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)",
 		"searchMaxResultsLabel": "Risultati di ricerca massimi",
 		"searchMaxResultsDescription": "Numero massimo di risultati di ricerca da restituire quando si interroga l'indice del codice. Valori più alti forniscono più contesto ma possono includere risultati meno pertinenti.",
-		"resetToDefault": "Ripristina al valore predefinito"
+		"resetToDefault": "Ripristina al valore predefinito",
+		"cancelling": "Annullamento...",
+		"cancelIndexingButton": "Annulla indicizzazione"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Puoi configurare una scorciatoia globale per questa impostazione <SettingsLink>nelle preferenze del tuo IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/ja/settings.json

@@ -134,7 +134,9 @@
 		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット",
 		"searchMaxResultsLabel": "最大検索結果数",
 		"searchMaxResultsDescription": "コードベースインデックスをクエリする際に返される検索結果の最大数。値を高くするとより多くのコンテキストが提供されますが、関連性の低い結果が含まれる可能性があります。",
-		"resetToDefault": "デフォルトにリセット"
+		"resetToDefault": "デフォルトにリセット",
+		"cancelling": "キャンセル中...",
+		"cancelIndexingButton": "インデックス作成をキャンセル"
 	},
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDEの環境設定</SettingsLink>で、この設定のグローバルショートカットを設定できます。",

+ 3 - 1
webview-ui/src/i18n/locales/ko/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정",
 		"searchMaxResultsLabel": "최대 검색 결과",
 		"searchMaxResultsDescription": "코드베이스 인덱스를 쿼리할 때 반환할 최대 검색 결과 수입니다. 값이 높을수록 더 많은 컨텍스트를 제공하지만 관련성이 낮은 결과가 포함될 수 있습니다.",
-		"resetToDefault": "기본값으로 재설정"
+		"resetToDefault": "기본값으로 재설정",
+		"cancelling": "취소 중...",
+		"cancelIndexingButton": "인덱싱 취소"
 	},
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDE 환경 설정</SettingsLink>에서 이 설정에 대한 전역 바로 가기를 구성할 수 있습니다.",

+ 3 - 1
webview-ui/src/i18n/locales/nl/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
 		"searchMaxResultsLabel": "Maximum Zoekresultaten",
 		"searchMaxResultsDescription": "Maximum aantal zoekresultaten dat wordt geretourneerd bij het doorzoeken van de codebase-index. Hogere waarden bieden meer context maar kunnen minder relevante resultaten bevatten.",
-		"resetToDefault": "Reset naar standaard"
+		"resetToDefault": "Reset naar standaard",
+		"cancelling": "Bezig met annuleren...",
+		"cancelIndexingButton": "Indexering annuleren"
 	},
 	"autoApprove": {
 		"toggleShortcut": "U kunt een globale sneltoets voor deze instelling configureren <SettingsLink>in de voorkeuren van uw IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/pl/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
 		"searchMaxResultsLabel": "Maksymalna liczba wyników wyszukiwania",
 		"searchMaxResultsDescription": "Maksymalna liczba wyników wyszukiwania zwracanych podczas zapytania do indeksu bazy kodu. Wyższe wartości zapewniają więcej kontekstu, ale mogą zawierać mniej istotne wyniki.",
-		"resetToDefault": "Przywróć domyślne"
+		"resetToDefault": "Przywróć domyślne",
+		"cancelling": "Anulowanie...",
+		"cancelIndexingButton": "Anuluj indeksowanie"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Możesz skonfigurować globalny skrót dla tego ustawienia <SettingsLink>w preferencjach swojego IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)",
 		"searchMaxResultsLabel": "Resultados máximos de busca",
 		"searchMaxResultsDescription": "Número máximo de resultados de busca a retornar ao consultar o índice de código. Valores mais altos fornecem mais contexto, mas podem incluir resultados menos relevantes.",
-		"resetToDefault": "Redefinir para o padrão"
+		"resetToDefault": "Redefinir para o padrão",
+		"cancelling": "Cancelando...",
+		"cancelIndexingButton": "Cancelar indexação"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Você pode configurar um atalho global para esta configuração <SettingsLink>nas preferências do seu IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/ru/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)",
 		"searchMaxResultsLabel": "Максимальное количество результатов поиска",
 		"searchMaxResultsDescription": "Максимальное количество результатов поиска, возвращаемых при запросе индекса кодовой базы. Более высокие значения предоставляют больше контекста, но могут включать менее релевантные результаты.",
-		"resetToDefault": "Сбросить к значению по умолчанию"
+		"resetToDefault": "Сбросить к значению по умолчанию",
+		"cancelling": "Отмена...",
+		"cancelIndexingButton": "Отменить индексацию"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Вы можете настроить глобальное сочетание клавиш для этого параметра <SettingsLink>в настройках вашей IDE</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/th/settings.json

@@ -140,7 +140,9 @@
 			"baseUrlRequired": "จำเป็นต้องมี URL พื้นฐาน",
 			"modelDimensionMinValue": "มิติโมเดลต้องมากกว่า 0",
 			"vercelAiGatewayApiKeyRequired": "จำเป็นต้องมีคีย์ API Vercel AI Gateway"
-		}
+		},
+		"cancelling": "กำลังยกเลิก...",
+		"cancelIndexingButton": "ยกเลิกการจัดทำดัชนี"
 	},
 	"autoApprove": {
 		"description": "อนุญาตให้ Kilo Code ดำเนินการโดยอัตโนมัติโดยไม่ต้องขออนุมัติ เปิดใช้งานการตั้งค่าเหล่านี้เฉพาะเมื่อคุณเชื่อถือ AI อย่างเต็มที่และเข้าใจความเสี่ยงด้านความปลอดภัยที่เกี่ยวข้อง",

+ 3 - 1
webview-ui/src/i18n/locales/tr/settings.json

@@ -134,7 +134,9 @@
 		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)",
 		"searchMaxResultsLabel": "Maksimum Arama Sonuçları",
 		"searchMaxResultsDescription": "Kod tabanı dizinini sorgularken döndürülecek maksimum arama sonucu sayısı. Daha yüksek değerler daha fazla bağlam sağlar ancak daha az alakalı sonuçlar içerebilir.",
-		"resetToDefault": "Varsayılana sıfırla"
+		"resetToDefault": "Varsayılana sıfırla",
+		"cancelling": "İptal ediliyor...",
+		"cancelIndexingButton": "İndekslemeyi iptal et"
 	},
 	"autoApprove": {
 		"toggleShortcut": "<SettingsLink>IDE tercihlerinizde</SettingsLink> bu ayar için genel bir kısayol yapılandırabilirsiniz.",

+ 3 - 1
webview-ui/src/i18n/locales/uk/settings.json

@@ -146,7 +146,9 @@
 			"baseUrlRequired": "Базовий URL є обов'язковим",
 			"modelDimensionMinValue": "Розмірність моделі повинна бути більше 0",
 			"vercelAiGatewayApiKeyRequired": "Ключ API Vercel AI Gateway є обов'язковим"
-		}
+		},
+		"cancelling": "Скасування...",
+		"cancelIndexingButton": "Скасувати індексацію"
 	},
 	"autoApprove": {
 		"description": "Дозволити Kilo Code автоматично виконувати операції без необхідності затвердження. Вмикайте ці налаштування, лише якщо ви повністю довіряєте ШІ та розумієте пов'язані з цим ризики безпеки.",

+ 3 - 1
webview-ui/src/i18n/locales/vi/settings.json

@@ -133,7 +133,9 @@
 		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
 		"searchMaxResultsLabel": "Số Kết Quả Tìm Kiếm Tối Đa",
 		"searchMaxResultsDescription": "Số lượng kết quả tìm kiếm tối đa được trả về khi truy vấn chỉ mục cơ sở mã. Giá trị cao hơn cung cấp nhiều ngữ cảnh hơn nhưng có thể bao gồm các kết quả ít liên quan hơn.",
-		"resetToDefault": "Đặt lại về mặc định"
+		"resetToDefault": "Đặt lại về mặc định",
+		"cancelling": "Đang hủy...",
+		"cancelIndexingButton": "Hủy lập chỉ mục"
 	},
 	"autoApprove": {
 		"toggleShortcut": "Bạn có thể định cấu hình một phím tắt chung cho cài đặt này <SettingsLink>trong tùy chọn IDE của bạn</SettingsLink>.",

+ 3 - 1
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -134,7 +134,9 @@
 		"searchMinScoreResetTooltip": "恢复默认值 (0.4)",
 		"searchMaxResultsLabel": "最大搜索结果数",
 		"searchMaxResultsDescription": "查询代码库索引时返回的最大搜索结果数。较高的值提供更多上下文,但可能包含相关性较低的结果。",
-		"resetToDefault": "恢复默认值"
+		"resetToDefault": "恢复默认值",
+		"cancelling": "正在取消...",
+		"cancelIndexingButton": "取消索引"
 	},
 	"autoApprove": {
 		"toggleShortcut": "您可以<SettingsLink>在 IDE 首选项中</SettingsLink>为此设置配置全局快捷方式。",

+ 3 - 1
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -134,7 +134,9 @@
 		"searchMinScoreResetTooltip": "重設為預設值 (0.4)",
 		"searchMaxResultsLabel": "最大搜尋結果數",
 		"searchMaxResultsDescription": "查詢程式碼庫索引時傳回的最大搜尋結果數。較高的值提供更多上下文,但可能包含相關性較低的結果。",
-		"resetToDefault": "重設為預設值"
+		"resetToDefault": "重設為預設值",
+		"cancelling": "正在取消...",
+		"cancelIndexingButton": "取消索引"
 	},
 	"autoApprove": {
 		"toggleShortcut": "您可以<SettingsLink>在 IDE 偏好設定中</SettingsLink>為此設定設定全域快捷鍵。",