Просмотр исходного кода

feat: add user-configurable search score threshold slider for semantic search (#5027) (#5041)

Co-authored-by: Daniel Riccio <[email protected]>
Hannes Rudolph 6 месяцев назад
Родитель
Сommit
a348a2ae9a
46 измененных файлов с 630 добавлено и 41 удалено
  1. 1 0
      packages/types/src/codebase-index.ts
  2. 147 0
      src/services/code-index/__tests__/config-manager.spec.ts
  3. 18 8
      src/services/code-index/config-manager.ts
  4. 28 1
      src/services/code-index/embedders/ollama.ts
  5. 28 2
      src/services/code-index/embedders/openai-compatible.ts
  6. 28 1
      src/services/code-index/embedders/openai.ts
  7. 53 9
      src/shared/embeddingModels.ts
  8. 7 1
      webview-ui/src/components/chat/CodebaseSearchResult.tsx
  9. 80 0
      webview-ui/src/components/settings/CodeIndexSettings.tsx
  10. 132 1
      webview-ui/src/components/settings/__tests__/CodeIndexSettings.spec.tsx
  11. 2 1
      webview-ui/src/i18n/locales/ca/chat.json
  12. 4 0
      webview-ui/src/i18n/locales/ca/settings.json
  13. 2 1
      webview-ui/src/i18n/locales/de/chat.json
  14. 4 0
      webview-ui/src/i18n/locales/de/settings.json
  15. 2 1
      webview-ui/src/i18n/locales/en/chat.json
  16. 4 0
      webview-ui/src/i18n/locales/en/settings.json
  17. 2 1
      webview-ui/src/i18n/locales/es/chat.json
  18. 4 0
      webview-ui/src/i18n/locales/es/settings.json
  19. 2 1
      webview-ui/src/i18n/locales/fr/chat.json
  20. 4 0
      webview-ui/src/i18n/locales/fr/settings.json
  21. 2 1
      webview-ui/src/i18n/locales/hi/chat.json
  22. 4 0
      webview-ui/src/i18n/locales/hi/settings.json
  23. 2 1
      webview-ui/src/i18n/locales/id/chat.json
  24. 4 0
      webview-ui/src/i18n/locales/id/settings.json
  25. 2 1
      webview-ui/src/i18n/locales/it/chat.json
  26. 4 0
      webview-ui/src/i18n/locales/it/settings.json
  27. 2 1
      webview-ui/src/i18n/locales/ja/chat.json
  28. 4 0
      webview-ui/src/i18n/locales/ja/settings.json
  29. 2 1
      webview-ui/src/i18n/locales/ko/chat.json
  30. 4 0
      webview-ui/src/i18n/locales/ko/settings.json
  31. 2 1
      webview-ui/src/i18n/locales/nl/chat.json
  32. 4 0
      webview-ui/src/i18n/locales/nl/settings.json
  33. 2 1
      webview-ui/src/i18n/locales/pl/chat.json
  34. 4 0
      webview-ui/src/i18n/locales/pl/settings.json
  35. 2 1
      webview-ui/src/i18n/locales/pt-BR/chat.json
  36. 4 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  37. 2 1
      webview-ui/src/i18n/locales/ru/chat.json
  38. 4 0
      webview-ui/src/i18n/locales/ru/settings.json
  39. 2 1
      webview-ui/src/i18n/locales/tr/chat.json
  40. 4 0
      webview-ui/src/i18n/locales/tr/settings.json
  41. 2 1
      webview-ui/src/i18n/locales/vi/chat.json
  42. 4 0
      webview-ui/src/i18n/locales/vi/settings.json
  43. 2 1
      webview-ui/src/i18n/locales/zh-CN/chat.json
  44. 4 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  45. 2 1
      webview-ui/src/i18n/locales/zh-TW/chat.json
  46. 4 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 1 - 0
packages/types/src/codebase-index.ts

@@ -10,6 +10,7 @@ export const codebaseIndexConfigSchema = z.object({
 	codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible"]).optional(),
 	codebaseIndexEmbedderBaseUrl: z.string().optional(),
 	codebaseIndexEmbedderModelId: z.string().optional(),
+	codebaseIndexSearchMinScore: z.number().min(0).max(1).optional(),
 })
 
 export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>

+ 147 - 0
src/services/code-index/__tests__/config-manager.spec.ts

@@ -709,6 +709,153 @@ describe("CodeIndexConfigManager", () => {
 				const result = await configManager.loadConfiguration()
 				expect(result.requiresRestart).toBe(false)
 			})
+
+			describe("currentSearchMinScore priority system", () => {
+				it("should return user-configured score when set", async () => {
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "openai",
+						codebaseIndexEmbedderModelId: "text-embedding-3-small",
+						codebaseIndexSearchMinScore: 0.8, // User setting
+					})
+					mockContextProxy.getSecret.mockImplementation((key: string) => {
+						if (key === "codeIndexOpenAiKey") return "test-key"
+						return undefined
+					})
+
+					await configManager.loadConfiguration()
+					expect(configManager.currentSearchMinScore).toBe(0.8)
+				})
+
+				it("should fall back to model-specific threshold when user setting is undefined", async () => {
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "ollama",
+						codebaseIndexEmbedderModelId: "nomic-embed-code",
+						// No codebaseIndexSearchMinScore - user hasn't configured it
+					})
+
+					await configManager.loadConfiguration()
+					// nomic-embed-code has a specific threshold of 0.15
+					expect(configManager.currentSearchMinScore).toBe(0.15)
+				})
+
+				it("should fall back to default SEARCH_MIN_SCORE when neither user setting nor model threshold exists", async () => {
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "openai",
+						codebaseIndexEmbedderModelId: "unknown-model", // Model not in profiles
+						// No codebaseIndexSearchMinScore
+					})
+					mockContextProxy.getSecret.mockImplementation((key: string) => {
+						if (key === "codeIndexOpenAiKey") return "test-key"
+						return undefined
+					})
+
+					await configManager.loadConfiguration()
+					// Should fall back to default SEARCH_MIN_SCORE (0.4)
+					expect(configManager.currentSearchMinScore).toBe(0.4)
+				})
+
+				it("should respect user setting of 0 (edge case)", async () => {
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "ollama",
+						codebaseIndexEmbedderModelId: "nomic-embed-code",
+						codebaseIndexSearchMinScore: 0, // User explicitly sets 0
+					})
+
+					await configManager.loadConfiguration()
+					// Should return 0, not fall back to model threshold (0.15)
+					expect(configManager.currentSearchMinScore).toBe(0)
+				})
+
+				it("should use model-specific threshold with openai-compatible provider", async () => {
+					mockContextProxy.getGlobalState.mockImplementation((key: string) => {
+						if (key === "codebaseIndexConfig") {
+							return {
+								codebaseIndexEnabled: true,
+								codebaseIndexQdrantUrl: "http://qdrant.local",
+								codebaseIndexEmbedderProvider: "openai-compatible",
+								codebaseIndexEmbedderModelId: "nomic-embed-code",
+								// No codebaseIndexSearchMinScore
+							}
+						}
+						if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
+						return undefined
+					})
+					mockContextProxy.getSecret.mockImplementation((key: string) => {
+						if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
+						return undefined
+					})
+
+					await configManager.loadConfiguration()
+					// openai-compatible provider also has nomic-embed-code with 0.15 threshold
+					expect(configManager.currentSearchMinScore).toBe(0.15)
+				})
+
+				it("should use default model ID when modelId is not specified", async () => {
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "openai",
+						// No modelId specified
+						// No codebaseIndexSearchMinScore
+					})
+					mockContextProxy.getSecret.mockImplementation((key: string) => {
+						if (key === "codeIndexOpenAiKey") return "test-key"
+						return undefined
+					})
+
+					await configManager.loadConfiguration()
+					// Should use default model (text-embedding-3-small) threshold (0.4)
+					expect(configManager.currentSearchMinScore).toBe(0.4)
+				})
+
+				it("should handle priority correctly: user > model > default", async () => {
+					// Test 1: User setting takes precedence
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "ollama",
+						codebaseIndexEmbedderModelId: "nomic-embed-code", // Has 0.15 threshold
+						codebaseIndexSearchMinScore: 0.9, // User overrides
+					})
+
+					await configManager.loadConfiguration()
+					expect(configManager.currentSearchMinScore).toBe(0.9) // User setting wins
+
+					// Test 2: Model threshold when no user setting
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "ollama",
+						codebaseIndexEmbedderModelId: "nomic-embed-code",
+						// No user setting
+					})
+
+					const newManager = new CodeIndexConfigManager(mockContextProxy)
+					await newManager.loadConfiguration()
+					expect(newManager.currentSearchMinScore).toBe(0.15) // Model threshold
+
+					// Test 3: Default when neither exists
+					mockContextProxy.getGlobalState.mockReturnValue({
+						codebaseIndexEnabled: true,
+						codebaseIndexQdrantUrl: "http://qdrant.local",
+						codebaseIndexEmbedderProvider: "openai",
+						codebaseIndexEmbedderModelId: "custom-unknown-model",
+						// No user setting, unknown model
+					})
+
+					const anotherManager = new CodeIndexConfigManager(mockContextProxy)
+					await anotherManager.loadConfiguration()
+					expect(anotherManager.currentSearchMinScore).toBe(0.4) // Default
+				})
+			})
 		})
 
 		describe("empty/missing API key handling", () => {

+ 18 - 8
src/services/code-index/config-manager.ts

@@ -3,7 +3,7 @@ import { ContextProxy } from "../../core/config/ContextProxy"
 import { EmbedderProvider } from "./interfaces/manager"
 import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
 import { SEARCH_MIN_SCORE } from "./constants"
-import { getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
+import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
 
 /**
  * Manages configuration state and validation for the code indexing feature.
@@ -34,10 +34,10 @@ export class CodeIndexConfigManager {
 		const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
 			codebaseIndexEnabled: false,
 			codebaseIndexQdrantUrl: "http://localhost:6333",
-			codebaseIndexSearchMinScore: 0.4,
 			codebaseIndexEmbedderProvider: "openai",
 			codebaseIndexEmbedderBaseUrl: "",
 			codebaseIndexEmbedderModelId: "",
+			codebaseIndexSearchMinScore: undefined,
 		}
 
 		const {
@@ -46,6 +46,7 @@ export class CodeIndexConfigManager {
 			codebaseIndexEmbedderProvider,
 			codebaseIndexEmbedderBaseUrl,
 			codebaseIndexEmbedderModelId,
+			codebaseIndexSearchMinScore,
 		} = codebaseIndexConfig
 
 		const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
@@ -60,8 +61,8 @@ export class CodeIndexConfigManager {
 		this.isEnabled = codebaseIndexEnabled || false
 		this.qdrantUrl = codebaseIndexQdrantUrl
 		this.qdrantApiKey = qdrantApiKey ?? ""
+		this.searchMinScore = codebaseIndexSearchMinScore
 		this.openAiOptions = { openAiNativeApiKey: openAiKey }
-		this.searchMinScore = SEARCH_MIN_SCORE
 
 		// Set embedder provider with support for openai-compatible
 		if (codebaseIndexEmbedderProvider === "ollama") {
@@ -139,7 +140,7 @@ export class CodeIndexConfigManager {
 				openAiCompatibleOptions: this.openAiCompatibleOptions,
 				qdrantUrl: this.qdrantUrl,
 				qdrantApiKey: this.qdrantApiKey,
-				searchMinScore: this.searchMinScore,
+				searchMinScore: this.currentSearchMinScore,
 			},
 			requiresRestart,
 		}
@@ -294,7 +295,7 @@ export class CodeIndexConfigManager {
 			openAiCompatibleOptions: this.openAiCompatibleOptions,
 			qdrantUrl: this.qdrantUrl,
 			qdrantApiKey: this.qdrantApiKey,
-			searchMinScore: this.searchMinScore,
+			searchMinScore: this.currentSearchMinScore,
 		}
 	}
 
@@ -337,9 +338,18 @@ export class CodeIndexConfigManager {
 	}
 
 	/**
-	 * Gets the configured minimum search score.
+	 * Gets the configured minimum search score based on user setting, model-specific threshold, or fallback.
+	 * Priority: 1) User setting, 2) Model-specific threshold, 3) Default SEARCH_MIN_SCORE constant.
 	 */
-	public get currentSearchMinScore(): number | undefined {
-		return this.searchMinScore
+	public get currentSearchMinScore(): number {
+		// First check if user has configured a custom score threshold
+		if (this.searchMinScore !== undefined) {
+			return this.searchMinScore
+		}
+
+		// Fall back to model-specific threshold
+		const currentModelId = this.modelId ?? getDefaultModelId(this.embedderProvider)
+		const modelSpecificThreshold = getModelScoreThreshold(this.embedderProvider, currentModelId)
+		return modelSpecificThreshold ?? SEARCH_MIN_SCORE
 	}
 }

+ 28 - 1
src/services/code-index/embedders/ollama.ts

@@ -1,5 +1,7 @@
 import { ApiHandlerOptions } from "../../../shared/api"
 import { EmbedderInfo, EmbeddingResponse, IEmbedder } from "../interfaces"
+import { getModelQueryPrefix } from "../../../shared/embeddingModels"
+import { MAX_ITEM_TOKENS } from "../constants"
 import { t } from "../../../i18n"
 
 /**
@@ -25,6 +27,31 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
 		const modelToUse = model || this.defaultModelId
 		const url = `${this.baseUrl}/api/embed` // Endpoint as specified
 
+		// Apply model-specific query prefix if required
+		const queryPrefix = getModelQueryPrefix("ollama", modelToUse)
+		const processedTexts = queryPrefix
+			? texts.map((text, index) => {
+					// Prevent double-prefixing
+					if (text.startsWith(queryPrefix)) {
+						return text
+					}
+					const prefixedText = `${queryPrefix}${text}`
+					const estimatedTokens = Math.ceil(prefixedText.length / 4)
+					if (estimatedTokens > MAX_ITEM_TOKENS) {
+						console.warn(
+							t("embeddings:textWithPrefixExceedsTokenLimit", {
+								index,
+								estimatedTokens,
+								maxTokens: MAX_ITEM_TOKENS,
+							}),
+						)
+						// Return original text if adding prefix would exceed limit
+						return text
+					}
+					return prefixedText
+				})
+			: texts
+
 		try {
 			// Note: Standard Ollama API uses 'prompt' for single text, not 'input' for array.
 			// Implementing based on user's specific request structure.
@@ -35,7 +62,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
 				},
 				body: JSON.stringify({
 					model: modelToUse,
-					input: texts, // Using 'input' as requested
+					input: processedTexts, // Using 'input' as requested
 				}),
 			})
 

+ 28 - 2
src/services/code-index/embedders/openai-compatible.ts

@@ -6,7 +6,7 @@ import {
 	MAX_BATCH_RETRIES as MAX_RETRIES,
 	INITIAL_RETRY_DELAY_MS as INITIAL_DELAY_MS,
 } from "../constants"
-import { getDefaultModelId } from "../../../shared/embeddingModels"
+import { getDefaultModelId, getModelQueryPrefix } from "../../../shared/embeddingModels"
 import { t } from "../../../i18n"
 
 interface EmbeddingItem {
@@ -59,9 +59,35 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
 	 */
 	async createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse> {
 		const modelToUse = model || this.defaultModelId
+
+		// Apply model-specific query prefix if required
+		const queryPrefix = getModelQueryPrefix("openai-compatible", modelToUse)
+		const processedTexts = queryPrefix
+			? texts.map((text, index) => {
+					// Prevent double-prefixing
+					if (text.startsWith(queryPrefix)) {
+						return text
+					}
+					const prefixedText = `${queryPrefix}${text}`
+					const estimatedTokens = Math.ceil(prefixedText.length / 4)
+					if (estimatedTokens > MAX_ITEM_TOKENS) {
+						console.warn(
+							t("embeddings:textWithPrefixExceedsTokenLimit", {
+								index,
+								estimatedTokens,
+								maxTokens: MAX_ITEM_TOKENS,
+							}),
+						)
+						// Return original text if adding prefix would exceed limit
+						return text
+					}
+					return prefixedText
+				})
+			: texts
+
 		const allEmbeddings: number[][] = []
 		const usage = { promptTokens: 0, totalTokens: 0 }
-		const remainingTexts = [...texts]
+		const remainingTexts = [...processedTexts]
 
 		while (remainingTexts.length > 0) {
 			const currentBatch: string[] = []

+ 28 - 1
src/services/code-index/embedders/openai.ts

@@ -8,6 +8,7 @@ import {
 	MAX_BATCH_RETRIES as MAX_RETRIES,
 	INITIAL_RETRY_DELAY_MS as INITIAL_DELAY_MS,
 } from "../constants"
+import { getModelQueryPrefix } from "../../../shared/embeddingModels"
 import { t } from "../../../i18n"
 
 /**
@@ -36,9 +37,35 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
 	 */
 	async createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse> {
 		const modelToUse = model || this.defaultModelId
+
+		// Apply model-specific query prefix if required
+		const queryPrefix = getModelQueryPrefix("openai", modelToUse)
+		const processedTexts = queryPrefix
+			? texts.map((text, index) => {
+					// Prevent double-prefixing
+					if (text.startsWith(queryPrefix)) {
+						return text
+					}
+					const prefixedText = `${queryPrefix}${text}`
+					const estimatedTokens = Math.ceil(prefixedText.length / 4)
+					if (estimatedTokens > MAX_ITEM_TOKENS) {
+						console.warn(
+							t("embeddings:textWithPrefixExceedsTokenLimit", {
+								index,
+								estimatedTokens,
+								maxTokens: MAX_ITEM_TOKENS,
+							}),
+						)
+						// Return original text if adding prefix would exceed limit
+						return text
+					}
+					return prefixedText
+				})
+			: texts
+
 		const allEmbeddings: number[][] = []
 		const usage = { promptTokens: 0, totalTokens: 0 }
-		const remainingTexts = [...texts]
+		const remainingTexts = [...processedTexts]
 
 		while (remainingTexts.length > 0) {
 			const currentBatch: string[] = []

+ 53 - 9
src/shared/embeddingModels.ts

@@ -6,6 +6,8 @@ export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" // Add
 
 export interface EmbeddingModelProfile {
 	dimension: number
+	scoreThreshold?: number // Model-specific minimum score threshold for semantic search
+	queryPrefix?: string // Optional prefix required by the model for queries
 	// Add other model-specific properties if needed, e.g., context window size
 }
 
@@ -18,21 +20,31 @@ export type EmbeddingModelProfiles = {
 // Example profiles - expand this list as needed
 export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
 	openai: {
-		"text-embedding-3-small": { dimension: 1536 },
-		"text-embedding-3-large": { dimension: 3072 },
-		"text-embedding-ada-002": { dimension: 1536 },
+		"text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 },
+		"text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 },
+		"text-embedding-ada-002": { dimension: 1536, scoreThreshold: 0.4 },
 	},
 	ollama: {
-		"nomic-embed-text": { dimension: 768 },
-		"mxbai-embed-large": { dimension: 1024 },
-		"all-minilm": { dimension: 384 },
+		"nomic-embed-text": { dimension: 768, scoreThreshold: 0.4 },
+		"nomic-embed-code": {
+			dimension: 3584,
+			scoreThreshold: 0.15,
+			queryPrefix: "Represent this query for searching relevant code: ",
+		},
+		"mxbai-embed-large": { dimension: 1024, scoreThreshold: 0.4 },
+		"all-minilm": { dimension: 384, scoreThreshold: 0.4 },
 		// Add default Ollama model if applicable, e.g.:
 		// 'default': { dimension: 768 } // Assuming a default dimension
 	},
 	"openai-compatible": {
-		"text-embedding-3-small": { dimension: 1536 },
-		"text-embedding-3-large": { dimension: 3072 },
-		"text-embedding-ada-002": { dimension: 1536 },
+		"text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 },
+		"text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 },
+		"text-embedding-ada-002": { dimension: 1536, scoreThreshold: 0.4 },
+		"nomic-embed-code": {
+			dimension: 3584,
+			scoreThreshold: 0.15,
+			queryPrefix: "Represent this query for searching relevant code: ",
+		},
 	},
 }
 
@@ -59,6 +71,38 @@ export function getModelDimension(provider: EmbedderProvider, modelId: string):
 	return modelProfile.dimension
 }
 
+/**
+ * Retrieves the score threshold for a given provider and model ID.
+ * @param provider The embedder provider (e.g., "openai").
+ * @param modelId The specific model ID (e.g., "text-embedding-3-small").
+ * @returns The score threshold or undefined if the model is not found.
+ */
+export function getModelScoreThreshold(provider: EmbedderProvider, modelId: string): number | undefined {
+	const providerProfiles = EMBEDDING_MODEL_PROFILES[provider]
+	if (!providerProfiles) {
+		return undefined
+	}
+
+	const modelProfile = providerProfiles[modelId]
+	return modelProfile?.scoreThreshold
+}
+
+/**
+ * Retrieves the query prefix for a given provider and model ID.
+ * @param provider The embedder provider (e.g., "openai").
+ * @param modelId The specific model ID (e.g., "nomic-embed-code").
+ * @returns The query prefix or undefined if the model doesn't require one.
+ */
+export function getModelQueryPrefix(provider: EmbedderProvider, modelId: string): string | undefined {
+	const providerProfiles = EMBEDDING_MODEL_PROFILES[provider]
+	if (!providerProfiles) {
+		return undefined
+	}
+
+	const modelProfile = providerProfiles[modelId]
+	return modelProfile?.queryPrefix
+}
+
 /**
  * Gets the default *specific* embedding model ID based on the provider.
  * Does not include the provider prefix.

+ 7 - 1
webview-ui/src/components/chat/CodebaseSearchResult.tsx

@@ -1,4 +1,5 @@
 import React from "react"
+import { useTranslation } from "react-i18next"
 import { vscode } from "@src/utils/vscode"
 import { StandardTooltip } from "@/components/ui"
 
@@ -12,6 +13,8 @@ interface CodebaseSearchResultProps {
 }
 
 const CodebaseSearchResult: React.FC<CodebaseSearchResultProps> = ({ filePath, score, startLine, endLine }) => {
+	const { t } = useTranslation("chat")
+
 	const handleClick = () => {
 		console.log(filePath)
 		vscode.postMessage({
@@ -24,7 +27,7 @@ const CodebaseSearchResult: React.FC<CodebaseSearchResultProps> = ({ filePath, s
 	}
 
 	return (
-		<StandardTooltip content={`Score: ${score.toFixed(2)}`}>
+		<StandardTooltip content={t("codebaseSearch.resultTooltip", { score: score.toFixed(3) })}>
 			<div
 				onClick={handleClick}
 				className="mb-1 p-2 border border-primary rounded cursor-pointer hover:bg-secondary hover:text-white">
@@ -35,6 +38,9 @@ const CodebaseSearchResult: React.FC<CodebaseSearchResultProps> = ({ filePath, s
 					<span className="text-gray-500 truncate min-w-0 flex-1">
 						{filePath.split("/").slice(0, -1).join("/")}
 					</span>
+					<span className="text-xs text-vscode-descriptionForeground bg-vscode-badge-background px-2 py-1 rounded whitespace-nowrap ml-auto">
+						{score.toFixed(3)}
+					</span>
 				</div>
 			</div>
 		</StandardTooltip>

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

@@ -7,6 +7,7 @@ import { Trans } from "react-i18next"
 import { CodebaseIndexConfig, CodebaseIndexModels, ProviderSettings } from "@roo-code/types"
 
 import { EmbedderProvider } from "@roo/embeddingModels"
+import { SEARCH_MIN_SCORE } from "../../../../src/services/code-index/constants"
 
 import { vscode } from "@src/utils/vscode"
 import { useAppTranslation } from "@src/i18n/TranslationContext"
@@ -27,6 +28,12 @@ import {
 	AlertDialogHeader,
 	AlertDialogTitle,
 	AlertDialogTrigger,
+	Slider,
+	Button,
+	Tooltip,
+	TooltipContent,
+	TooltipProvider,
+	TooltipTrigger,
 } from "@src/components/ui"
 
 import { SetCachedStateField } from "./types"
@@ -59,6 +66,7 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
 		totalItems: 0,
 		currentItemUnit: "items",
 	})
+	const [advancedExpanded, setAdvancedExpanded] = useState(false)
 
 	// Safely calculate available models for current provider
 	const currentProvider = codebaseIndexConfig?.codebaseIndexEmbedderProvider
@@ -506,6 +514,78 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
 							</AlertDialog>
 						)}
 					</div>
+
+					{/* Advanced Configuration Section */}
+					<div className="mt-4">
+						<button
+							onClick={() => setAdvancedExpanded(!advancedExpanded)}
+							className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
+							aria-expanded={advancedExpanded}>
+							<span
+								className={`codicon codicon-${advancedExpanded ? "chevron-down" : "chevron-right"} mr-1`}></span>
+							<span>{t("settings:codeIndex.advancedConfigLabel")}</span>
+						</button>
+
+						{advancedExpanded && (
+							<div className="text-xs text-vscode-descriptionForeground mt-2 ml-5">
+								<div className="flex flex-col gap-3">
+									<div>
+										<span className="block font-medium mb-1">
+											{t("settings:codeIndex.searchMinScoreLabel")}
+										</span>
+										<div className="flex items-center gap-2">
+											<Slider
+												min={0}
+												max={1}
+												step={0.05}
+												value={[
+													codebaseIndexConfig.codebaseIndexSearchMinScore ?? SEARCH_MIN_SCORE,
+												]}
+												onValueChange={([value]) =>
+													setCachedStateField("codebaseIndexConfig", {
+														...codebaseIndexConfig,
+														codebaseIndexSearchMinScore: value,
+													})
+												}
+												data-testid="search-min-score-slider"
+												aria-label={t("settings:codeIndex.searchMinScoreLabel")}
+											/>
+											<span className="w-10">
+												{(
+													codebaseIndexConfig.codebaseIndexSearchMinScore ?? SEARCH_MIN_SCORE
+												).toFixed(2)}
+											</span>
+											<TooltipProvider>
+												<Tooltip>
+													<TooltipTrigger asChild>
+														<Button
+															variant="ghost"
+															size="sm"
+															onClick={() =>
+																setCachedStateField("codebaseIndexConfig", {
+																	...codebaseIndexConfig,
+																	codebaseIndexSearchMinScore: SEARCH_MIN_SCORE,
+																})
+															}
+															className="h-8 w-8 p-0"
+															data-testid="search-min-score-reset-button">
+															<span className="codicon codicon-debug-restart w-4 h-4" />
+														</Button>
+													</TooltipTrigger>
+													<TooltipContent>
+														<p>{t("settings:codeIndex.searchMinScoreResetTooltip")}</p>
+													</TooltipContent>
+												</Tooltip>
+											</TooltipProvider>
+										</div>
+										<div className="text-vscode-descriptionForeground text-sm mt-1">
+											{t("settings:codeIndex.searchMinScoreDescription")}
+										</div>
+									</div>
+								</div>
+							</div>
+						)}
+					</div>
 				</div>
 			)}
 		</>

+ 132 - 1
webview-ui/src/components/settings/__tests__/CodeIndexSettings.spec.tsx

@@ -43,6 +43,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
 				"settings:codeIndex.clearDataDialog.description": "This will remove all indexed data",
 				"settings:codeIndex.clearDataDialog.cancelButton": "Cancel",
 				"settings:codeIndex.clearDataDialog.confirmButton": "Confirm",
+				"settings:codeIndex.searchMinScoreLabel": "Search Score Threshold",
+				"settings:codeIndex.searchMinScoreDescription":
+					"Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.",
+				"settings:codeIndex.searchMinScoreResetTooltip": "Reset to default value (0.4)",
+				"settings:codeIndex.advancedConfigLabel": "Advanced Configuration",
 			}
 			return translations[key] || key
 		},
@@ -85,6 +90,24 @@ vi.mock("@src/components/ui", () => ({
 	AlertDialogHeader: ({ children }: any) => <div data-testid="alert-dialog-header">{children}</div>,
 	AlertDialogTitle: ({ children }: any) => <div data-testid="alert-dialog-title">{children}</div>,
 	AlertDialogTrigger: ({ children }: any) => <div data-testid="alert-dialog-trigger">{children}</div>,
+	Slider: ({ value, onValueChange, "data-testid": dataTestId }: any) => (
+		<input
+			type="range"
+			value={value[0]}
+			onChange={(e) => onValueChange && onValueChange([parseFloat(e.target.value)])}
+			data-testid={dataTestId}
+			role="slider"
+		/>
+	),
+	Button: ({ children, onClick, "data-testid": dataTestId, ...props }: any) => (
+		<button onClick={onClick} data-testid={dataTestId} {...props}>
+			{children}
+		</button>
+	),
+	Tooltip: ({ children }: any) => <div data-testid="tooltip">{children}</div>,
+	TooltipContent: ({ children }: any) => <div data-testid="tooltip-content">{children}</div>,
+	TooltipProvider: ({ children }: any) => <div data-testid="tooltip-provider">{children}</div>,
+	TooltipTrigger: ({ children }: any) => <div data-testid="tooltip-trigger">{children}</div>,
 }))
 
 vi.mock("@vscode/webview-ui-toolkit/react", () => ({
@@ -158,6 +181,7 @@ describe("CodeIndexSettings", () => {
 			codebaseIndexEmbedderProvider: "openai" as const,
 			codebaseIndexEmbedderModelId: "text-embedding-3-small",
 			codebaseIndexQdrantUrl: "http://localhost:6333",
+			codebaseIndexSearchMinScore: 0.4,
 		},
 		apiConfiguration: {
 			codeIndexOpenAiKey: "",
@@ -204,7 +228,7 @@ describe("CodeIndexSettings", () => {
 
 			expect(screen.getByText("Base URL")).toBeInTheDocument()
 			expect(screen.getByText("API Key")).toBeInTheDocument()
-			expect(screen.getAllByTestId("vscode-textfield")).toHaveLength(6) // Base URL, API Key, Embedding Dimension, Model ID, Qdrant URL, Qdrant Key
+			expect(screen.getAllByTestId("vscode-textfield")).toHaveLength(6) // Base URL, API Key, Embedding Dimension, Model ID, Qdrant URL, Qdrant Key (Search Min Score is now a slider)
 		})
 
 		it("should hide OpenAI Compatible fields when different provider is selected", () => {
@@ -817,6 +841,113 @@ describe("CodeIndexSettings", () => {
 		})
 	})
 
+	describe("Search Minimum Score Slider", () => {
+		const expandAdvancedConfig = () => {
+			const advancedButton = screen.getByRole("button", { name: /Advanced Configuration/i })
+			fireEvent.click(advancedButton)
+		}
+
+		it("should render advanced configuration toggle button", () => {
+			render(<CodeIndexSettings {...defaultProps} />)
+
+			expect(screen.getByRole("button", { name: /Advanced Configuration/i })).toBeInTheDocument()
+			expect(screen.getByText("Advanced Configuration")).toBeInTheDocument()
+		})
+
+		it("should render search minimum score slider with reset button when expanded", () => {
+			render(<CodeIndexSettings {...defaultProps} />)
+
+			expandAdvancedConfig()
+
+			expect(screen.getByTestId("search-min-score-slider")).toBeInTheDocument()
+			expect(screen.getByTestId("search-min-score-reset-button")).toBeInTheDocument()
+			expect(screen.getByText("Search Score Threshold")).toBeInTheDocument()
+		})
+
+		it("should display current search minimum score value when expanded", () => {
+			const propsWithScore = {
+				...defaultProps,
+				codebaseIndexConfig: {
+					...defaultProps.codebaseIndexConfig,
+					codebaseIndexSearchMinScore: 0.65,
+				},
+			}
+
+			render(<CodeIndexSettings {...propsWithScore} />)
+
+			expandAdvancedConfig()
+
+			expect(screen.getByText("0.65")).toBeInTheDocument()
+		})
+
+		it("should call setCachedStateField when slider value changes", () => {
+			render(<CodeIndexSettings {...defaultProps} />)
+
+			expandAdvancedConfig()
+
+			const slider = screen.getByTestId("search-min-score-slider")
+			fireEvent.change(slider, { target: { value: "0.8" } })
+
+			expect(mockSetCachedStateField).toHaveBeenCalledWith("codebaseIndexConfig", {
+				...defaultProps.codebaseIndexConfig,
+				codebaseIndexSearchMinScore: 0.8,
+			})
+		})
+
+		it("should reset to default value when reset button is clicked", () => {
+			const propsWithScore = {
+				...defaultProps,
+				codebaseIndexConfig: {
+					...defaultProps.codebaseIndexConfig,
+					codebaseIndexSearchMinScore: 0.8,
+				},
+			}
+
+			render(<CodeIndexSettings {...propsWithScore} />)
+
+			expandAdvancedConfig()
+
+			const resetButton = screen.getByTestId("search-min-score-reset-button")
+			fireEvent.click(resetButton)
+
+			expect(mockSetCachedStateField).toHaveBeenCalledWith("codebaseIndexConfig", {
+				...defaultProps.codebaseIndexConfig,
+				codebaseIndexSearchMinScore: 0.4,
+			})
+		})
+
+		it("should use default value when no score is set", () => {
+			const propsWithoutScore = {
+				...defaultProps,
+				codebaseIndexConfig: {
+					...defaultProps.codebaseIndexConfig,
+					codebaseIndexSearchMinScore: undefined,
+				},
+			}
+
+			render(<CodeIndexSettings {...propsWithoutScore} />)
+
+			expandAdvancedConfig()
+
+			expect(screen.getByText("0.40")).toBeInTheDocument()
+		})
+
+		it("should toggle advanced section visibility", () => {
+			render(<CodeIndexSettings {...defaultProps} />)
+
+			// Initially collapsed - should not see slider
+			expect(screen.queryByTestId("search-min-score-slider")).not.toBeInTheDocument()
+
+			// Expand advanced section
+			expandAdvancedConfig()
+			expect(screen.getByTestId("search-min-score-slider")).toBeInTheDocument()
+
+			// Collapse again
+			expandAdvancedConfig()
+			expect(screen.queryByTestId("search-min-score-slider")).not.toBeInTheDocument()
+		})
+	})
+
 	describe("Error Handling", () => {
 		it("should handle invalid provider gracefully", () => {
 			const propsWithInvalidProvider = {

+ 2 - 1
webview-ui/src/i18n/locales/ca/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo vol cercar a la base de codi <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo vol cercar a la base de codi <code>{{query}}</code> a <code>{{path}}</code>:",
-		"didSearch": "S'han trobat {{count}} resultat(s) per a <code>{{query}}</code>:"
+		"didSearch": "S'han trobat {{count}} resultat(s) per a <code>{{query}}</code>:",
+		"resultTooltip": "Puntuació de similitud: {{score}} (fes clic per obrir el fitxer)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/ca/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL d'Ollama:",
 		"qdrantUrlLabel": "URL de Qdrant",
 		"qdrantKeyLabel": "Clau de Qdrant:",
+		"advancedConfigLabel": "Configuració avançada",
+		"searchMinScoreLabel": "Llindar de puntuació de cerca",
+		"searchMinScoreDescription": "Puntuació mínima de similitud (0.0-1.0) requerida per als resultats de la cerca. Valors més baixos retornen més resultats però poden ser menys rellevants. Valors més alts retornen menys resultats però més rellevants.",
+		"searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)",
 		"startIndexingButton": "Iniciar indexació",
 		"clearIndexDataButton": "Esborrar dades d'índex",
 		"unsavedSettingsMessage": "Si us plau, deseu la configuració abans d'iniciar el procés d'indexació.",

+ 2 - 1
webview-ui/src/i18n/locales/de/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo möchte den Codebase nach <code>{{query}}</code> durchsuchen:",
 		"wantsToSearchWithPath": "Roo möchte den Codebase nach <code>{{query}}</code> in <code>{{path}}</code> durchsuchen:",
-		"didSearch": "{{count}} Ergebnis(se) für <code>{{query}}</code> gefunden:"
+		"didSearch": "{{count}} Ergebnis(se) für <code>{{query}}</code> gefunden:",
+		"resultTooltip": "Ähnlichkeitswert: {{score}} (klicken zum Öffnen der Datei)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/de/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama-URL:",
 		"qdrantUrlLabel": "Qdrant-URL",
 		"qdrantKeyLabel": "Qdrant-Schlüssel:",
+		"advancedConfigLabel": "Erweiterte Konfiguration",
+		"searchMinScoreLabel": "Suchergebnis-Schwellenwert",
+		"searchMinScoreDescription": "Mindestähnlichkeitswert (0.0-1.0), der für Suchergebnisse erforderlich ist. Niedrigere Werte liefern mehr Ergebnisse, die jedoch möglicherweise weniger relevant sind. Höhere Werte liefern weniger, aber relevantere Ergebnisse.",
+		"searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)",
 		"startIndexingButton": "Indexierung starten",
 		"clearIndexDataButton": "Indexdaten löschen",
 		"unsavedSettingsMessage": "Bitte speichere deine Einstellungen, bevor du den Indexierungsprozess startest.",

+ 2 - 1
webview-ui/src/i18n/locales/en/chat.json

@@ -201,7 +201,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo wants to search the codebase for <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo wants to search the codebase for <code>{{query}}</code> in <code>{{path}}</code>:",
-		"didSearch": "Found {{count}} result(s) for <code>{{query}}</code>:"
+		"didSearch": "Found {{count}} result(s) for <code>{{query}}</code>:",
+		"resultTooltip": "Similarity score: {{score}} (click to open file)"
 	},
 	"commandOutput": "Command Output",
 	"response": "Response",

+ 4 - 0
webview-ui/src/i18n/locales/en/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Key:",
+		"advancedConfigLabel": "Advanced Configuration",
+		"searchMinScoreLabel": "Search Score Threshold",
+		"searchMinScoreDescription": "Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.",
+		"searchMinScoreResetTooltip": "Reset to default value (0.4)",
 		"startIndexingButton": "Start Indexing",
 		"clearIndexDataButton": "Clear Index Data",
 		"unsavedSettingsMessage": "Please save your settings before starting the indexing process.",

+ 2 - 1
webview-ui/src/i18n/locales/es/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo quiere buscar en la base de código <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo quiere buscar en la base de código <code>{{query}}</code> en <code>{{path}}</code>:",
-		"didSearch": "Se encontraron {{count}} resultado(s) para <code>{{query}}</code>:"
+		"didSearch": "Se encontraron {{count}} resultado(s) para <code>{{query}}</code>:",
+		"resultTooltip": "Puntuación de similitud: {{score}} (haz clic para abrir el archivo)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/es/settings.json

@@ -56,6 +56,10 @@
 		"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 mínima de similitud (0.0-1.0) requerida para los resultados de búsqueda. Valores más bajos devuelven más resultados pero pueden ser menos relevantes. Valores más altos devuelven menos resultados pero más relevantes.",
+		"searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)",
 		"startIndexingButton": "Iniciar indexación",
 		"clearIndexDataButton": "Borrar datos de índice",
 		"unsavedSettingsMessage": "Por favor guarda tus ajustes antes de iniciar el proceso de indexación.",

+ 2 - 1
webview-ui/src/i18n/locales/fr/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo veut rechercher dans la base de code <code>{{query}}</code> :",
 		"wantsToSearchWithPath": "Roo veut rechercher dans la base de code <code>{{query}}</code> dans <code>{{path}}</code> :",
-		"didSearch": "{{count}} résultat(s) trouvé(s) pour <code>{{query}}</code> :"
+		"didSearch": "{{count}} résultat(s) trouvé(s) pour <code>{{query}}</code> :",
+		"resultTooltip": "Score de similarité : {{score}} (cliquer pour ouvrir le fichier)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/fr/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama :",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Clé Qdrant :",
+		"advancedConfigLabel": "Configuration avancée",
+		"searchMinScoreLabel": "Seuil de score de recherche",
+		"searchMinScoreDescription": "Score de similarité minimum (0.0-1.0) requis pour les résultats de recherche. Des valeurs plus faibles renvoient plus de résultats mais peuvent être moins pertinents. Des valeurs plus élevées renvoient moins de résultats mais plus pertinents.",
+		"searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)",
 		"startIndexingButton": "Démarrer l'indexation",
 		"clearIndexDataButton": "Effacer les données d'index",
 		"unsavedSettingsMessage": "Merci d'enregistrer tes paramètres avant de démarrer le processus d'indexation.",

+ 2 - 1
webview-ui/src/i18n/locales/hi/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo कोडबेस में <code>{{query}}</code> खोजना चाहता है:",
 		"wantsToSearchWithPath": "Roo <code>{{path}}</code> में कोडबेस में <code>{{query}}</code> खोजना चाहता है:",
-		"didSearch": "<code>{{query}}</code> के लिए {{count}} परिणाम मिले:"
+		"didSearch": "<code>{{query}}</code> के लिए {{count}} परिणाम मिले:",
+		"resultTooltip": "समानता स्कोर: {{score}} (फ़ाइल खोलने के लिए क्लिक करें)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/hi/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant कुंजी:",
+		"advancedConfigLabel": "उन्नत कॉन्फ़िगरेशन",
+		"searchMinScoreLabel": "खोज स्कोर थ्रेसहोल्ड",
+		"searchMinScoreDescription": "खोज परिणामों के लिए आवश्यक न्यूनतम समानता स्कोर (0.0-1.0)। कम मान अधिक परिणाम लौटाते हैं लेकिन कम प्रासंगिक हो सकते हैं। उच्च मान कम लेकिन अधिक प्रासंगिक परिणाम लौटाते हैं।",
+		"searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)",
 		"startIndexingButton": "इंडेक्सिंग शुरू करें",
 		"clearIndexDataButton": "इंडेक्स डेटा साफ़ करें",
 		"unsavedSettingsMessage": "इंडेक्सिंग प्रक्रिया शुरू करने से पहले कृपया अपनी सेटिंग्स सहेजें।",

+ 2 - 1
webview-ui/src/i18n/locales/id/chat.json

@@ -207,7 +207,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo ingin mencari codebase untuk <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo ingin mencari codebase untuk <code>{{query}}</code> di <code>{{path}}</code>:",
-		"didSearch": "Ditemukan {{count}} hasil untuk <code>{{query}}</code>:"
+		"didSearch": "Ditemukan {{count}} hasil untuk <code>{{query}}</code>:",
+		"resultTooltip": "Skor kemiripan: {{score}} (klik untuk membuka file)"
 	},
 	"commandOutput": "Output Perintah",
 	"response": "Respons",

+ 4 - 0
webview-ui/src/i18n/locales/id/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Key:",
+		"advancedConfigLabel": "Konfigurasi Lanjutan",
+		"searchMinScoreLabel": "Ambang Batas Skor Pencarian",
+		"searchMinScoreDescription": "Skor kesamaan minimum (0.0-1.0) yang diperlukan untuk hasil pencarian. Nilai yang lebih rendah mengembalikan lebih banyak hasil tetapi mungkin kurang relevan. Nilai yang lebih tinggi mengembalikan lebih sedikit hasil tetapi lebih relevan.",
+		"searchMinScoreResetTooltip": "Reset ke nilai default (0.4)",
 		"startIndexingButton": "Mulai Pengindeksan",
 		"clearIndexDataButton": "Hapus Data Indeks",
 		"unsavedSettingsMessage": "Silakan simpan pengaturan kamu sebelum memulai proses pengindeksan.",

+ 2 - 1
webview-ui/src/i18n/locales/it/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo vuole cercare nella base di codice <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo vuole cercare nella base di codice <code>{{query}}</code> in <code>{{path}}</code>:",
-		"didSearch": "Trovato {{count}} risultato/i per <code>{{query}}</code>:"
+		"didSearch": "Trovato {{count}} risultato/i per <code>{{query}}</code>:",
+		"resultTooltip": "Punteggio di somiglianza: {{score}} (clicca per aprire il file)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/it/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Chiave Qdrant:",
+		"advancedConfigLabel": "Configurazione avanzata",
+		"searchMinScoreLabel": "Soglia punteggio di ricerca",
+		"searchMinScoreDescription": "Punteggio minimo di somiglianza (0.0-1.0) richiesto per i risultati della ricerca. Valori più bassi restituiscono più risultati ma potrebbero essere meno pertinenti. Valori più alti restituiscono meno risultati ma più pertinenti.",
+		"searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)",
 		"startIndexingButton": "Avvia indicizzazione",
 		"clearIndexDataButton": "Cancella dati indice",
 		"unsavedSettingsMessage": "Per favore salva le tue impostazioni prima di avviare il processo di indicizzazione.",

+ 2 - 1
webview-ui/src/i18n/locales/ja/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Rooはコードベースで <code>{{query}}</code> を検索したい:",
 		"wantsToSearchWithPath": "Rooは <code>{{path}}</code> 内のコードベースで <code>{{query}}</code> を検索したい:",
-		"didSearch": "<code>{{query}}</code> の検索結果: {{count}} 件"
+		"didSearch": "<code>{{query}}</code> の検索結果: {{count}} 件",
+		"resultTooltip": "類似度スコア: {{score}} (クリックしてファイルを開く)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/ja/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrantキー:",
+		"advancedConfigLabel": "詳細設定",
+		"searchMinScoreLabel": "検索スコアのしきい値",
+		"searchMinScoreDescription": "検索結果に必要な最小類似度スコア(0.0-1.0)。値を低くするとより多くの結果が返されますが、関連性が低くなる可能性があります。値を高くすると返される結果は少なくなりますが、より関連性が高くなります。",
+		"searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット",
 		"startIndexingButton": "インデックス作成を開始",
 		"clearIndexDataButton": "インデックスデータをクリア",
 		"unsavedSettingsMessage": "インデックス作成プロセスを開始する前に設定を保存してください。",

+ 2 - 1
webview-ui/src/i18n/locales/ko/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo가 코드베이스에서 <code>{{query}}</code>을(를) 검색하고 싶어합니다:",
 		"wantsToSearchWithPath": "Roo가 <code>{{path}}</code>에서 <code>{{query}}</code>을(를) 검색하고 싶어합니다:",
-		"didSearch": "<code>{{query}}</code>에 대한 검색 결과 {{count}}개 찾음:"
+		"didSearch": "<code>{{query}}</code>에 대한 검색 결과 {{count}}개 찾음:",
+		"resultTooltip": "유사도 점수: {{score}} (클릭하여 파일 열기)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/ko/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 키:",
+		"advancedConfigLabel": "고급 구성",
+		"searchMinScoreLabel": "검색 점수 임계값",
+		"searchMinScoreDescription": "검색 결과에 필요한 최소 유사도 점수(0.0-1.0). 값이 낮을수록 더 많은 결과가 반환되지만 관련성이 떨어질 수 있습니다. 값이 높을수록 결과는 적지만 관련성이 높은 결과가 반환됩니다.",
+		"searchMinScoreResetTooltip": "기본값(0.4)으로 재설정",
 		"startIndexingButton": "인덱싱 시작",
 		"clearIndexDataButton": "인덱스 데이터 지우기",
 		"unsavedSettingsMessage": "인덱싱 프로세스를 시작하기 전에 설정을 저장해 주세요.",

+ 2 - 1
webview-ui/src/i18n/locales/nl/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo wil de codebase doorzoeken op <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo wil de codebase doorzoeken op <code>{{query}}</code> in <code>{{path}}</code>:",
-		"didSearch": "{{count}} resultaat/resultaten gevonden voor <code>{{query}}</code>:"
+		"didSearch": "{{count}} resultaat/resultaten gevonden voor <code>{{query}}</code>:",
+		"resultTooltip": "Gelijkenisscore: {{score}} (klik om bestand te openen)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/nl/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant-sleutel:",
+		"advancedConfigLabel": "Geavanceerde configuratie",
+		"searchMinScoreLabel": "Zoekscore drempel",
+		"searchMinScoreDescription": "Minimale overeenkomstscore (0.0-1.0) vereist voor zoekresultaten. Lagere waarden leveren meer resultaten op, maar zijn mogelijk minder relevant. Hogere waarden leveren minder, maar relevantere resultaten op.",
+		"searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)",
 		"startIndexingButton": "Indexering starten",
 		"clearIndexDataButton": "Indexgegevens wissen",
 		"unsavedSettingsMessage": "Sla je instellingen op voordat je het indexeringsproces start.",

+ 2 - 1
webview-ui/src/i18n/locales/pl/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo chce przeszukać bazę kodu w poszukiwaniu <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo chce przeszukać bazę kodu w poszukiwaniu <code>{{query}}</code> w <code>{{path}}</code>:",
-		"didSearch": "Znaleziono {{count}} wynik(ów) dla <code>{{query}}</code>:"
+		"didSearch": "Znaleziono {{count}} wynik(ów) dla <code>{{query}}</code>:",
+		"resultTooltip": "Wynik podobieństwa: {{score}} (kliknij, aby otworzyć plik)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/pl/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Klucz Qdrant:",
+		"advancedConfigLabel": "Konfiguracja zaawansowana",
+		"searchMinScoreLabel": "Próg wyniku wyszukiwania",
+		"searchMinScoreDescription": "Minimalny wynik podobieństwa (0.0-1.0) wymagany dla wyników wyszukiwania. Niższe wartości zwracają więcej wyników, ale mogą być mniej trafne. Wyższe wartości zwracają mniej wyników, ale bardziej trafnych.",
+		"searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)",
 		"startIndexingButton": "Rozpocznij indeksowanie",
 		"clearIndexDataButton": "Wyczyść dane indeksu",
 		"unsavedSettingsMessage": "Zapisz swoje ustawienia przed rozpoczęciem procesu indeksowania.",

+ 2 - 1
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo quer pesquisar na base de código por <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo quer pesquisar na base de código por <code>{{query}}</code> em <code>{{path}}</code>:",
-		"didSearch": "Encontrado {{count}} resultado(s) para <code>{{query}}</code>:"
+		"didSearch": "Encontrado {{count}} resultado(s) para <code>{{query}}</code>:",
+		"resultTooltip": "Pontuação de similaridade: {{score}} (clique para abrir o arquivo)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Chave Qdrant:",
+		"advancedConfigLabel": "Configuração Avançada",
+		"searchMinScoreLabel": "Limite de pontuação de busca",
+		"searchMinScoreDescription": "Pontuação mínima de similaridade (0.0-1.0) necessária para os resultados da busca. Valores mais baixos retornam mais resultados, mas podem ser menos relevantes. Valores mais altos retornam menos resultados, mas mais relevantes.",
+		"searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)",
 		"startIndexingButton": "Iniciar Indexação",
 		"clearIndexDataButton": "Limpar Dados de Índice",
 		"unsavedSettingsMessage": "Por favor, salve suas configurações antes de iniciar o processo de indexação.",

+ 2 - 1
webview-ui/src/i18n/locales/ru/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo хочет выполнить поиск в кодовой базе по <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo хочет выполнить поиск в кодовой базе по <code>{{query}}</code> в <code>{{path}}</code>:",
-		"didSearch": "Найдено {{count}} результат(ов) для <code>{{query}}</code>:"
+		"didSearch": "Найдено {{count}} результат(ов) для <code>{{query}}</code>:",
+		"resultTooltip": "Оценка схожести: {{score}} (нажмите, чтобы открыть файл)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/ru/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Ключ Qdrant:",
+		"advancedConfigLabel": "Расширенная конфигурация",
+		"searchMinScoreLabel": "Порог оценки поиска",
+		"searchMinScoreDescription": "Минимальный балл сходства (0.0-1.0), необходимый для результатов поиска. Более низкие значения возвращают больше результатов, но они могут быть менее релевантными. Более высокие значения возвращают меньше результатов, но более релевантных.",
+		"searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)",
 		"startIndexingButton": "Начать индексацию",
 		"clearIndexDataButton": "Очистить данные индекса",
 		"unsavedSettingsMessage": "Пожалуйста, сохрани настройки перед запуском процесса индексации.",

+ 2 - 1
webview-ui/src/i18n/locales/tr/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo kod tabanında <code>{{query}}</code> aramak istiyor:",
 		"wantsToSearchWithPath": "Roo <code>{{path}}</code> içinde kod tabanında <code>{{query}}</code> aramak istiyor:",
-		"didSearch": "<code>{{query}}</code> için {{count}} sonuç bulundu:"
+		"didSearch": "<code>{{query}}</code> için {{count}} sonuç bulundu:",
+		"resultTooltip": "Benzerlik puanı: {{score}} (dosyayı açmak için tıklayın)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/tr/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant Anahtarı:",
+		"advancedConfigLabel": "Gelişmiş Yapılandırma",
+		"searchMinScoreLabel": "Arama Skoru Eşiği",
+		"searchMinScoreDescription": "Arama sonuçları için gereken minimum benzerlik puanı (0.0-1.0). Düşük değerler daha fazla sonuç döndürür ancak daha az alakalı olabilir. Yüksek değerler daha az ancak daha alakalı sonuçlar döndürür.",
+		"searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)",
 		"startIndexingButton": "İndekslemeyi Başlat",
 		"clearIndexDataButton": "İndeks Verilerini Temizle",
 		"unsavedSettingsMessage": "İndeksleme işlemini başlatmadan önce lütfen ayarlarını kaydet.",

+ 2 - 1
webview-ui/src/i18n/locales/vi/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo muốn tìm kiếm trong cơ sở mã cho <code>{{query}}</code>:",
 		"wantsToSearchWithPath": "Roo muốn tìm kiếm trong cơ sở mã cho <code>{{query}}</code> trong <code>{{path}}</code>:",
-		"didSearch": "Đã tìm thấy {{count}} kết quả cho <code>{{query}}</code>:"
+		"didSearch": "Đã tìm thấy {{count}} kết quả cho <code>{{query}}</code>:",
+		"resultTooltip": "Điểm tương tự: {{score}} (nhấp để mở tệp)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/vi/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "URL Ollama:",
 		"qdrantUrlLabel": "URL Qdrant",
 		"qdrantKeyLabel": "Khóa Qdrant:",
+		"advancedConfigLabel": "Cấu hình nâng cao",
+		"searchMinScoreLabel": "Ngưỡng điểm tìm kiếm",
+		"searchMinScoreDescription": "Điểm tương đồng tối thiểu (0.0-1.0) cần thiết cho kết quả tìm kiếm. Giá trị thấp hơn trả về nhiều kết quả hơn nhưng có thể kém liên quan hơn. Giá trị cao hơn trả về ít kết quả hơn nhưng có liên quan hơn.",
+		"searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)",
 		"startIndexingButton": "Bắt đầu lập chỉ mục",
 		"clearIndexDataButton": "Xóa dữ liệu chỉ mục",
 		"unsavedSettingsMessage": "Vui lòng lưu cài đặt của bạn trước khi bắt đầu quá trình lập chỉ mục.",

+ 2 - 1
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo 需要搜索代码库: <code>{{query}}</code>",
 		"wantsToSearchWithPath": "Roo 需要在 <code>{{path}}</code> 中搜索: <code>{{query}}</code>",
-		"didSearch": "找到 {{count}} 个结果: <code>{{query}}</code>"
+		"didSearch": "找到 {{count}} 个结果: <code>{{query}}</code>",
+		"resultTooltip": "相似度评分: {{score}} (点击打开文件)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 密钥:",
+		"advancedConfigLabel": "高级配置",
+		"searchMinScoreLabel": "搜索分数阈值",
+		"searchMinScoreDescription": "搜索结果所需的最低相似度分数(0.0-1.0)。较低的值返回更多结果,但可能不太相关。较高的值返回较少但更相关的结果。",
+		"searchMinScoreResetTooltip": "恢复默认值 (0.4)",
 		"startIndexingButton": "开始索引",
 		"clearIndexDataButton": "清除索引数据",
 		"unsavedSettingsMessage": "请先保存设置再开始索引过程。",

+ 2 - 1
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -294,7 +294,8 @@
 	"codebaseSearch": {
 		"wantsToSearch": "Roo 想要搜尋程式碼庫:<code>{{query}}</code>",
 		"wantsToSearchWithPath": "Roo 想要在 <code>{{path}}</code> 中搜尋:<code>{{query}}</code>",
-		"didSearch": "找到 {{count}} 個結果:<code>{{query}}</code>"
+		"didSearch": "找到 {{count}} 個結果:<code>{{query}}</code>",
+		"resultTooltip": "相似度評分:{{score}} (點擊開啟檔案)"
 	},
 	"read-batch": {
 		"approve": {

+ 4 - 0
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -56,6 +56,10 @@
 		"ollamaUrlLabel": "Ollama URL:",
 		"qdrantUrlLabel": "Qdrant URL",
 		"qdrantKeyLabel": "Qdrant 金鑰:",
+		"advancedConfigLabel": "進階設定",
+		"searchMinScoreLabel": "搜尋分數閾值",
+		"searchMinScoreDescription": "搜尋結果所需的最低相似度分數(0.0-1.0)。較低的值會傳回更多結果,但可能較不相關。較高的值會傳回較少但更相關的結果。",
+		"searchMinScoreResetTooltip": "重設為預設值 (0.4)",
 		"startIndexingButton": "開始索引",
 		"clearIndexDataButton": "清除索引資料",
 		"unsavedSettingsMessage": "請先儲存設定再開始索引程序。",