Indexing field validation (#5483)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com>
This commit is contained in:
Murilo Pires 2025-07-09 00:15:37 -03:00 committed by GitHub
parent a2815e2026
commit fa60a31578
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1487 additions and 511 deletions

View file

@ -127,7 +127,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
expect(result.error).toBe("embeddings:ollama.serviceNotRunning")
})
it("should fail validation when tags endpoint returns 404", async () => {
@ -141,9 +141,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe(
"Ollama service is not running at http://localhost:11434. Please start Ollama first.",
)
expect(result.error).toBe("embeddings:ollama.serviceNotRunning")
})
it("should fail validation when tags endpoint returns other error", async () => {
@ -157,7 +155,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Ollama service is unavailable at http://localhost:11434. HTTP status: 500")
expect(result.error).toBe("embeddings:ollama.serviceUnavailable")
})
it("should fail validation when model does not exist", async () => {
@ -176,9 +174,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe(
"Model 'nomic-embed-text' not found. Available models: llama2:latest, mistral:latest",
)
expect(result.error).toBe("embeddings:ollama.modelNotFound")
})
it("should fail validation when model exists but doesn't support embeddings", async () => {
@ -205,7 +201,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Model 'nomic-embed-text' is not embedding capable")
expect(result.error).toBe("embeddings:ollama.modelNotEmbeddingCapable")
})
it("should handle ECONNREFUSED errors", async () => {
@ -214,7 +210,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
expect(result.error).toBe("embeddings:ollama.serviceNotRunning")
})
it("should handle ENOTFOUND errors", async () => {
@ -223,7 +219,7 @@ describe("CodeIndexOllamaEmbedder", () => {
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Ollama host not found: http://localhost:11434")
expect(result.error).toBe("embeddings:ollama.hostNotFound")
})
it("should handle generic network errors", async () => {

View file

@ -56,6 +56,11 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
try {
// Note: Standard Ollama API uses 'prompt' for single text, not 'input' for array.
// Implementing based on user's specific request structure.
// Add timeout to prevent indefinite hanging
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
const response = await fetch(url, {
method: "POST",
headers: {
@ -65,7 +70,9 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
model: modelToUse,
input: processedTexts, // Using 'input' as requested
}),
signal: controller.signal,
})
clearTimeout(timeoutId)
if (!response.ok) {
let errorBody = t("embeddings:ollama.couldNotReadErrorBody")
@ -97,6 +104,16 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
} catch (error: any) {
// Log the original error for debugging purposes
console.error("Ollama embedding failed:", error)
// Handle specific error types with better messages
if (error.name === "AbortError") {
throw new Error(t("embeddings:validation.connectionFailed"))
} else if (error.message?.includes("fetch failed") || error.code === "ECONNREFUSED") {
throw new Error(t("embeddings:ollama.serviceNotRunning", { baseUrl: this.baseUrl }))
} else if (error.code === "ENOTFOUND") {
throw new Error(t("embeddings:ollama.hostNotFound", { baseUrl: this.baseUrl }))
}
// Re-throw a more specific error for the caller
throw new Error(t("embeddings:ollama.embeddingFailed", { message: error.message }))
}
@ -129,12 +146,12 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
if (modelsResponse.status === 404) {
return {
valid: false,
error: t("embeddings:errors.ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
error: t("embeddings:ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
}
}
return {
valid: false,
error: t("embeddings:errors.ollama.serviceUnavailable", {
error: t("embeddings:ollama.serviceUnavailable", {
baseUrl: this.baseUrl,
status: modelsResponse.status,
}),
@ -159,8 +176,8 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
const availableModels = models.map((m: any) => m.name).join(", ")
return {
valid: false,
error: t("embeddings:errors.ollama.modelNotFound", {
model: this.defaultModelId,
error: t("embeddings:ollama.modelNotFound", {
modelId: this.defaultModelId,
availableModels,
}),
}
@ -189,7 +206,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
if (!testResponse.ok) {
return {
valid: false,
error: t("embeddings:errors.ollama.modelNotEmbedding", { model: this.defaultModelId }),
error: t("embeddings:ollama.modelNotEmbeddingCapable", { modelId: this.defaultModelId }),
}
}
@ -199,21 +216,26 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
{
beforeStandardHandling: (error: any) => {
// Handle Ollama-specific connection errors
if (error?.message === "ECONNREFUSED") {
// Check for fetch failed errors which indicate Ollama is not running
if (
error?.message?.includes("fetch failed") ||
error?.code === "ECONNREFUSED" ||
error?.message?.includes("ECONNREFUSED")
) {
return {
valid: false,
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
error: t("embeddings:ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
}
} else if (error?.message === "ENOTFOUND") {
} else if (error?.code === "ENOTFOUND" || error?.message?.includes("ENOTFOUND")) {
return {
valid: false,
error: t("embeddings:errors.ollama.hostNotFound", { baseUrl: this.baseUrl }),
error: t("embeddings:ollama.hostNotFound", { baseUrl: this.baseUrl }),
}
} else if (error?.name === "AbortError") {
// Handle timeout
return {
valid: false,
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
error: t("embeddings:validation.connectionFailed"),
}
}
// Let standard handling take over

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,375 @@
import React from "react"
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import { describe, it, expect, vi, beforeEach } from "vitest"
import { CodeIndexPopover } from "../CodeIndexPopover"
// Mock the vscode API
vi.mock("@src/utils/vscode", () => ({
vscode: {
postMessage: vi.fn(),
},
}))
// Mock the extension state context
vi.mock("@src/context/ExtensionStateContext", () => ({
useExtensionState: vi.fn(),
}))
// Mock the translation context
vi.mock("@src/i18n/TranslationContext", () => ({
useAppTranslation: () => ({ t: vi.fn((key: string) => key) }),
}))
// Mock react-i18next
vi.mock("react-i18next", () => ({
Trans: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}))
// Mock the doc links utility
vi.mock("@src/utils/docLinks", () => ({
buildDocLink: vi.fn(() => "https://docs.roocode.com"),
}))
// Mock the portal hook
vi.mock("@src/components/ui/hooks/useRooPortal", () => ({
useRooPortal: () => ({ portalContainer: document.body }),
}))
// Mock Radix UI components to avoid portal issues
vi.mock("@src/components/ui", () => ({
Popover: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
PopoverContent: ({ children }: { children: React.ReactNode }) => <div role="dialog">{children}</div>,
PopoverTrigger: ({ 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, value }: { children: React.ReactNode; value: string }) => (
<div role="option" data-value={value}>
{children}
</div>
),
SelectTrigger: ({ children }: { children: React.ReactNode }) => <div role="combobox">{children}</div>,
SelectValue: ({ placeholder }: { placeholder?: string }) => <span>{placeholder}</span>,
AlertDialog: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogAction: ({ children }: { children: React.ReactNode }) => <button>{children}</button>,
AlertDialogCancel: ({ children }: { children: React.ReactNode }) => <button>{children}</button>,
AlertDialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogFooter: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
AlertDialogTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
Slider: ({ value, onValueChange }: { value: number[]; onValueChange: (value: number[]) => void }) => (
<input type="range" value={value[0]} onChange={(e) => onValueChange([parseInt(e.target.value)])} />
),
StandardTooltip: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
cn: (...classes: string[]) => classes.join(" "),
}))
// Mock VSCode web components to behave like regular HTML inputs
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeTextField: ({ value, onInput, placeholder, className, ...rest }: any) => (
<input
type="text"
value={value || ""}
onChange={(e) => onInput && onInput(e)}
placeholder={placeholder}
className={className}
aria-label="Text field"
{...rest}
/>
),
VSCodeButton: ({ children, onClick, ...rest }: any) => (
<button onClick={onClick} {...rest}>
{children}
</button>
),
VSCodeDropdown: ({ value, onChange, children, className, ...rest }: any) => (
<select
value={value || ""}
onChange={(e) => onChange && onChange(e)}
className={className}
role="combobox"
{...rest}>
{children}
</select>
),
VSCodeOption: ({ value, children, ...rest }: any) => (
<option value={value} {...rest}>
{children}
</option>
),
VSCodeLink: ({ href, children, ...rest }: any) => (
<a href={href} {...rest}>
{children}
</a>
),
}))
// Helper function to simulate input on form elements
const simulateInput = (element: Element, value: string) => {
// Now that we're mocking VSCode components as regular HTML inputs,
// we can use standard fireEvent.change
fireEvent.change(element, { target: { value } })
}
describe("CodeIndexPopover Validation", () => {
let mockUseExtensionState: any
beforeEach(async () => {
vi.clearAllMocks()
// Get the mocked function
const { useExtensionState } = await import("@src/context/ExtensionStateContext")
mockUseExtensionState = vi.mocked(useExtensionState)
// Setup default extension state
mockUseExtensionState.mockReturnValue({
codebaseIndexConfig: {
codebaseIndexEnabled: false,
codebaseIndexQdrantUrl: "",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
codebaseIndexSearchMaxResults: 10,
codebaseIndexSearchMinScore: 0.7,
codebaseIndexOpenAiCompatibleBaseUrl: "",
codebaseIndexEmbedderModelDimension: undefined,
},
codebaseIndexModels: {
openai: [{ dimension: 1536 }],
},
})
})
const renderComponent = () => {
return render(
<CodeIndexPopover indexingStatus={{ systemStatus: "idle", message: "", processedItems: 0, totalItems: 0 }}>
<button>Test Trigger</button>
</CodeIndexPopover>,
)
}
const openPopover = async () => {
const trigger = screen.getByText("Test Trigger")
fireEvent.click(trigger)
// Wait for popover to open
await waitFor(() => {
expect(screen.getByRole("dialog")).toBeInTheDocument()
})
}
const expandSetupSection = async () => {
const setupButton = screen.getByText("settings:codeIndex.setupConfigLabel")
fireEvent.click(setupButton)
// Wait for section to expand - look for vscode-text-field elements
await waitFor(() => {
const textFields = screen.getAllByLabelText("Text field")
expect(textFields.length).toBeGreaterThan(0)
})
}
describe("OpenAI Provider Validation", () => {
it("should show validation error when OpenAI API key is empty", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// First, make a change to enable the save button by modifying the Qdrant URL
const qdrantUrlField = screen.getByPlaceholderText(/settings:codeIndex.qdrantUrlPlaceholder/i)
fireEvent.change(qdrantUrlField, { target: { value: "http://localhost:6333" } })
// Wait for the save button to become enabled
await waitFor(() => {
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
expect(saveButton).not.toBeDisabled()
})
// Now clear the OpenAI API key to create a validation error
const apiKeyField = screen.getByPlaceholderText(/settings:codeIndex.openAiKeyPlaceholder/i)
fireEvent.change(apiKeyField, { target: { value: "" } })
// Click the save button to trigger validation
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
fireEvent.click(saveButton)
// Should show specific field error
await waitFor(() => {
expect(screen.getByText("settings:codeIndex.validation.openaiApiKeyRequired")).toBeInTheDocument()
})
})
it("should show validation error when model is not selected", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// First, make a change to enable the save button
const qdrantUrlField = screen.getByPlaceholderText(/settings:codeIndex.qdrantUrlPlaceholder/i)
fireEvent.change(qdrantUrlField, { target: { value: "http://localhost:6333" } })
// Set API key but leave model empty
const apiKeyField = screen.getByPlaceholderText(/settings:codeIndex.openAiKeyPlaceholder/i)
fireEvent.change(apiKeyField, { target: { value: "test-api-key" } })
// Wait for the save button to become enabled
await waitFor(() => {
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
expect(saveButton).not.toBeDisabled()
})
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText("settings:codeIndex.validation.modelSelectionRequired")).toBeInTheDocument()
})
})
})
describe("Qdrant URL Validation", () => {
it("should show validation error when Qdrant URL is empty", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// First, make a change to enable the save button by setting API key
const apiKeyField = screen.getByPlaceholderText(/settings:codeIndex.openAiKeyPlaceholder/i)
fireEvent.change(apiKeyField, { target: { value: "test-api-key" } })
// Clear the Qdrant URL to create validation error
const qdrantUrlField = screen.getByPlaceholderText(/settings:codeIndex.qdrantUrlPlaceholder/i)
fireEvent.change(qdrantUrlField, { target: { value: "" } })
// Wait for the save button to become enabled
await waitFor(() => {
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
expect(saveButton).not.toBeDisabled()
})
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText("settings:codeIndex.validation.invalidQdrantUrl")).toBeInTheDocument()
})
})
it("should show validation error when Qdrant URL is invalid", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// First, make a change to enable the save button by setting API key
const apiKeyField = screen.getByPlaceholderText(/settings:codeIndex.openAiKeyPlaceholder/i)
fireEvent.change(apiKeyField, { target: { value: "test-api-key" } })
// Set invalid Qdrant URL
const qdrantUrlField = screen.getByPlaceholderText(/settings:codeIndex.qdrantUrlPlaceholder/i)
fireEvent.change(qdrantUrlField, { target: { value: "invalid-url" } })
// Wait for the save button to become enabled
await waitFor(() => {
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
expect(saveButton).not.toBeDisabled()
})
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText("settings:codeIndex.validation.invalidQdrantUrl")).toBeInTheDocument()
})
})
})
describe("Common Field Validation", () => {
it("should not show validation error for optional Qdrant API key", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// Set required fields to make form valid
const qdrantUrlField = screen.getByPlaceholderText(/settings:codeIndex.qdrantUrlPlaceholder/i)
fireEvent.change(qdrantUrlField, { target: { value: "http://localhost:6333" } })
const apiKeyField = screen.getByPlaceholderText(/settings:codeIndex.openAiKeyPlaceholder/i)
fireEvent.change(apiKeyField, { target: { value: "test-api-key" } })
// Select a model - this is required (get the select element specifically)
const modelSelect = screen.getAllByRole("combobox").find((el) => el.tagName === "SELECT")
if (modelSelect) {
fireEvent.change(modelSelect, { target: { value: "0" } })
}
// Leave Qdrant API key empty (it's optional)
const qdrantApiKeyField = screen.getByPlaceholderText(/settings:codeIndex.qdrantApiKeyPlaceholder/i)
fireEvent.change(qdrantApiKeyField, { target: { value: "" } })
// Wait for the save button to become enabled
await waitFor(() => {
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
expect(saveButton).not.toBeDisabled()
})
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
fireEvent.click(saveButton)
// Should not show validation errors since Qdrant API key is optional
})
it("should clear validation errors when fields are corrected", async () => {
renderComponent()
await openPopover()
await expandSetupSection()
// First make an invalid change to enable the save button and trigger validation
const textFields = screen.getAllByLabelText("Text field")
const qdrantField = textFields.find((field) =>
field.getAttribute("placeholder")?.toLowerCase().includes("qdrant"),
)
if (qdrantField) {
simulateInput(qdrantField, "invalid-url") // Invalid URL to trigger validation
}
// Wait for save button to be enabled
const saveButton = screen.getByText("settings:codeIndex.saveSettings")
await waitFor(() => {
expect(saveButton).not.toBeDisabled()
})
// Click save to trigger validation errors
fireEvent.click(saveButton)
// Now fix the errors with valid values
const apiKeyField = textFields.find(
(field) =>
field.getAttribute("placeholder")?.toLowerCase().includes("openai") ||
field.getAttribute("placeholder")?.toLowerCase().includes("key"),
)
// Set valid Qdrant URL
if (qdrantField) {
simulateInput(qdrantField, "http://localhost:6333")
}
// Set API key
if (apiKeyField) {
simulateInput(apiKeyField, "test-api-key")
}
// Select a model - this is required (get the select element specifically)
const modelSelect = screen.getAllByRole("combobox").find((el) => el.tagName === "SELECT")
if (modelSelect) {
fireEvent.change(modelSelect, { target: { value: "0" } })
}
// Try to save again
fireEvent.click(saveButton)
// Validation errors should be cleared (specific field errors are checked elsewhere)
})
})
})

View file

@ -98,6 +98,21 @@
"error": "Error"
},
"close": "Tancar",
"validation": {
"invalidQdrantUrl": "URL de Qdrant no vàlida",
"invalidOllamaUrl": "URL d'Ollama no vàlida",
"invalidBaseUrl": "URL de base no vàlida",
"qdrantUrlRequired": "Cal una URL de Qdrant",
"openaiApiKeyRequired": "Cal una clau d'API d'OpenAI",
"modelSelectionRequired": "Cal seleccionar un model",
"apiKeyRequired": "Cal una clau d'API",
"modelIdRequired": "Cal un ID de model",
"modelDimensionRequired": "Cal una dimensió de model",
"geminiApiKeyRequired": "Cal una clau d'API de Gemini",
"ollamaBaseUrlRequired": "Cal una URL base d'Ollama",
"baseUrlRequired": "Cal una URL base",
"modelDimensionMinValue": "La dimensió del model ha de ser superior a 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Fehler"
},
"close": "Schließen",
"validation": {
"invalidQdrantUrl": "Ungültige Qdrant-URL",
"invalidOllamaUrl": "Ungültige Ollama-URL",
"invalidBaseUrl": "Ungültige Basis-URL",
"qdrantUrlRequired": "Qdrant-URL ist erforderlich",
"openaiApiKeyRequired": "OpenAI-API-Schlüssel ist erforderlich",
"modelSelectionRequired": "Modellauswahl ist erforderlich",
"apiKeyRequired": "API-Schlüssel ist erforderlich",
"modelIdRequired": "Modell-ID ist erforderlich",
"modelDimensionRequired": "Modellabmessung ist erforderlich",
"geminiApiKeyRequired": "Gemini-API-Schlüssel ist erforderlich",
"ollamaBaseUrlRequired": "Ollama-Basis-URL ist erforderlich",
"baseUrlRequired": "Basis-URL ist erforderlich",
"modelDimensionMinValue": "Modellabmessung muss größer als 0 sein"
},
"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.",

View file

@ -104,7 +104,22 @@
"indexed": "Indexed",
"error": "Error"
},
"close": "Close"
"close": "Close",
"validation": {
"qdrantUrlRequired": "Qdrant URL is required",
"invalidQdrantUrl": "Invalid Qdrant URL",
"invalidOllamaUrl": "Invalid Ollama URL",
"invalidBaseUrl": "Invalid base URL",
"openaiApiKeyRequired": "OpenAI API key is required",
"modelSelectionRequired": "Model selection is required",
"apiKeyRequired": "API key is required",
"modelIdRequired": "Model ID is required",
"modelDimensionRequired": "Model dimension is required",
"geminiApiKeyRequired": "Gemini API key is required",
"ollamaBaseUrlRequired": "Ollama base URL is required",
"baseUrlRequired": "Base URL is required",
"modelDimensionMinValue": "Model dimension must be greater than 0"
}
},
"autoApprove": {
"description": "Allow Roo to automatically perform operations without requiring approval. Enable these settings only if you fully trust the AI and understand the associated security risks.",

View file

@ -98,6 +98,21 @@
"error": "Error"
},
"close": "Cerrar",
"validation": {
"invalidQdrantUrl": "URL de Qdrant no válida",
"invalidOllamaUrl": "URL de Ollama no válida",
"invalidBaseUrl": "URL base no válida",
"qdrantUrlRequired": "Se requiere la URL de Qdrant",
"openaiApiKeyRequired": "Se requiere la clave API de OpenAI",
"modelSelectionRequired": "Se requiere la selección de un modelo",
"apiKeyRequired": "Se requiere la clave API",
"modelIdRequired": "Se requiere el ID del modelo",
"modelDimensionRequired": "Se requiere la dimensión del modelo",
"geminiApiKeyRequired": "Se requiere la clave API de Gemini",
"ollamaBaseUrlRequired": "Se requiere la URL base de Ollama",
"baseUrlRequired": "Se requiere la URL base",
"modelDimensionMinValue": "La dimensión del modelo debe ser mayor que 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Erreur"
},
"close": "Fermer",
"validation": {
"invalidQdrantUrl": "URL Qdrant invalide",
"invalidOllamaUrl": "URL Ollama invalide",
"invalidBaseUrl": "URL de base invalide",
"qdrantUrlRequired": "L'URL Qdrant est requise",
"openaiApiKeyRequired": "La clé API OpenAI est requise",
"modelSelectionRequired": "La sélection du modèle est requise",
"apiKeyRequired": "La clé API est requise",
"modelIdRequired": "L'ID du modèle est requis",
"modelDimensionRequired": "La dimension du modèle est requise",
"geminiApiKeyRequired": "La clé API Gemini est requise",
"ollamaBaseUrlRequired": "L'URL de base Ollama est requise",
"baseUrlRequired": "L'URL de base est requise",
"modelDimensionMinValue": "La dimension du modèle doit être supérieure à 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "त्रुटि"
},
"close": "बंद करें",
"validation": {
"invalidQdrantUrl": "अमान्य Qdrant URL",
"invalidOllamaUrl": "अमान्य Ollama URL",
"invalidBaseUrl": "अमान्य बेस URL",
"qdrantUrlRequired": "Qdrant URL आवश्यक है",
"openaiApiKeyRequired": "OpenAI API कुंजी आवश्यक है",
"modelSelectionRequired": "मॉडल चयन आवश्यक है",
"apiKeyRequired": "API कुंजी आवश्यक है",
"modelIdRequired": "मॉडल आईडी आवश्यक है",
"modelDimensionRequired": "मॉडल आयाम आवश्यक है",
"geminiApiKeyRequired": "Gemini API कुंजी आवश्यक है",
"ollamaBaseUrlRequired": "Ollama आधार URL आवश्यक है",
"baseUrlRequired": "आधार URL आवश्यक है",
"modelDimensionMinValue": "मॉडल आयाम 0 से बड़ा होना चाहिए"
},
"advancedConfigLabel": "उन्नत कॉन्फ़िगरेशन",
"searchMinScoreLabel": "खोज स्कोर थ्रेसहोल्ड",
"searchMinScoreDescription": "खोज परिणामों के लिए आवश्यक न्यूनतम समानता स्कोर (0.0-1.0)। कम मान अधिक परिणाम लौटाते हैं लेकिन कम प्रासंगिक हो सकते हैं। उच्च मान कम लेकिन अधिक प्रासंगिक परिणाम लौटाते हैं।",

View file

@ -98,6 +98,21 @@
"error": "Error"
},
"close": "Tutup",
"validation": {
"invalidQdrantUrl": "URL Qdrant tidak valid",
"invalidOllamaUrl": "URL Ollama tidak valid",
"invalidBaseUrl": "URL dasar tidak valid",
"qdrantUrlRequired": "URL Qdrant diperlukan",
"openaiApiKeyRequired": "Kunci API OpenAI diperlukan",
"modelSelectionRequired": "Pemilihan model diperlukan",
"apiKeyRequired": "Kunci API diperlukan",
"modelIdRequired": "ID Model diperlukan",
"modelDimensionRequired": "Dimensi model diperlukan",
"geminiApiKeyRequired": "Kunci API Gemini diperlukan",
"ollamaBaseUrlRequired": "URL dasar Ollama diperlukan",
"baseUrlRequired": "URL dasar diperlukan",
"modelDimensionMinValue": "Dimensi model harus lebih besar dari 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Errore"
},
"close": "Chiudi",
"validation": {
"invalidQdrantUrl": "URL Qdrant non valido",
"invalidOllamaUrl": "URL Ollama non valido",
"invalidBaseUrl": "URL di base non valido",
"qdrantUrlRequired": "È richiesto l'URL di Qdrant",
"openaiApiKeyRequired": "È richiesta la chiave API di OpenAI",
"modelSelectionRequired": "È richiesta la selezione del modello",
"apiKeyRequired": "È richiesta la chiave API",
"modelIdRequired": "È richiesto l'ID del modello",
"modelDimensionRequired": "È richiesta la dimensione del modello",
"geminiApiKeyRequired": "È richiesta la chiave API Gemini",
"ollamaBaseUrlRequired": "È richiesto l'URL di base di Ollama",
"baseUrlRequired": "È richiesto l'URL di base",
"modelDimensionMinValue": "La dimensione del modello deve essere maggiore di 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "エラー"
},
"close": "閉じる",
"validation": {
"invalidQdrantUrl": "無効なQdrant URL",
"invalidOllamaUrl": "無効なOllama URL",
"invalidBaseUrl": "無効なベースURL",
"qdrantUrlRequired": "Qdrant URL が必要です",
"openaiApiKeyRequired": "OpenAI APIキーが必要です",
"modelSelectionRequired": "モデルの選択が必要です",
"apiKeyRequired": "APIキーが必要です",
"modelIdRequired": "モデルIDが必要です",
"modelDimensionRequired": "モデルの次元が必要です",
"geminiApiKeyRequired": "Gemini APIキーが必要です",
"ollamaBaseUrlRequired": "OllamaのベースURLが必要です",
"baseUrlRequired": "ベースURLが必要です",
"modelDimensionMinValue": "モデルの次元は0より大きくなければなりません"
},
"advancedConfigLabel": "詳細設定",
"searchMinScoreLabel": "検索スコアのしきい値",
"searchMinScoreDescription": "検索結果に必要な最小類似度スコア0.0-1.0)。値を低くするとより多くの結果が返されますが、関連性が低くなる可能性があります。値を高くすると返される結果は少なくなりますが、より関連性が高くなります。",

View file

@ -98,6 +98,21 @@
"error": "오류"
},
"close": "닫기",
"validation": {
"invalidQdrantUrl": "잘못된 Qdrant URL",
"invalidOllamaUrl": "잘못된 Ollama URL",
"invalidBaseUrl": "잘못된 기본 URL",
"qdrantUrlRequired": "Qdrant URL이 필요합니다",
"openaiApiKeyRequired": "OpenAI API 키가 필요합니다",
"modelSelectionRequired": "모델 선택이 필요합니다",
"apiKeyRequired": "API 키가 필요합니다",
"modelIdRequired": "모델 ID가 필요합니다",
"modelDimensionRequired": "모델 차원이 필요합니다",
"geminiApiKeyRequired": "Gemini API 키가 필요합니다",
"ollamaBaseUrlRequired": "Ollama 기본 URL이 필요합니다",
"baseUrlRequired": "기본 URL이 필요합니다",
"modelDimensionMinValue": "모델 차원은 0보다 커야 합니다"
},
"advancedConfigLabel": "고급 구성",
"searchMinScoreLabel": "검색 점수 임계값",
"searchMinScoreDescription": "검색 결과에 필요한 최소 유사도 점수(0.0-1.0). 값이 낮을수록 더 많은 결과가 반환되지만 관련성이 떨어질 수 있습니다. 값이 높을수록 결과는 적지만 관련성이 높은 결과가 반환됩니다.",

View file

@ -98,6 +98,21 @@
"error": "Fout"
},
"close": "Sluiten",
"validation": {
"invalidQdrantUrl": "Ongeldige Qdrant URL",
"invalidOllamaUrl": "Ongeldige Ollama URL",
"invalidBaseUrl": "Ongeldige basis-URL",
"qdrantUrlRequired": "Qdrant URL is vereist",
"openaiApiKeyRequired": "OpenAI API-sleutel is vereist",
"modelSelectionRequired": "Modelselectie is vereist",
"apiKeyRequired": "API-sleutel is vereist",
"modelIdRequired": "Model-ID is vereist",
"modelDimensionRequired": "Modelafmeting is vereist",
"geminiApiKeyRequired": "Gemini API-sleutel is vereist",
"ollamaBaseUrlRequired": "Ollama basis-URL is vereist",
"baseUrlRequired": "Basis-URL is vereist",
"modelDimensionMinValue": "Modelafmeting moet groter zijn dan 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Błąd"
},
"close": "Zamknij",
"validation": {
"invalidQdrantUrl": "Nieprawidłowy URL Qdrant",
"invalidOllamaUrl": "Nieprawidłowy URL Ollama",
"invalidBaseUrl": "Nieprawidłowy podstawowy URL",
"qdrantUrlRequired": "Wymagany jest URL Qdrant",
"openaiApiKeyRequired": "Wymagany jest klucz API OpenAI",
"modelSelectionRequired": "Wymagany jest wybór modelu",
"apiKeyRequired": "Wymagany jest klucz API",
"modelIdRequired": "Wymagane jest ID modelu",
"modelDimensionRequired": "Wymagany jest wymiar modelu",
"geminiApiKeyRequired": "Wymagany jest klucz API Gemini",
"ollamaBaseUrlRequired": "Wymagany jest bazowy adres URL Ollama",
"baseUrlRequired": "Wymagany jest bazowy adres URL",
"modelDimensionMinValue": "Wymiar modelu musi być większy niż 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Erro"
},
"close": "Fechar",
"validation": {
"invalidQdrantUrl": "URL do Qdrant inválida",
"invalidOllamaUrl": "URL do Ollama inválida",
"invalidBaseUrl": "URL base inválida",
"qdrantUrlRequired": "A URL do Qdrant é obrigatória",
"openaiApiKeyRequired": "A chave de API da OpenAI é obrigatória",
"modelSelectionRequired": "A seleção do modelo é obrigatória",
"apiKeyRequired": "A chave de API é obrigatória",
"modelIdRequired": "O ID do modelo é obrigatório",
"modelDimensionRequired": "A dimensão do modelo é obrigatória",
"geminiApiKeyRequired": "A chave de API do Gemini é obrigatória",
"ollamaBaseUrlRequired": "A URL base do Ollama é obrigatória",
"baseUrlRequired": "A URL base é obrigatória",
"modelDimensionMinValue": "A dimensão do modelo deve ser maior que 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Ошибка"
},
"close": "Закрыть",
"validation": {
"invalidQdrantUrl": "Неверный URL Qdrant",
"invalidOllamaUrl": "Неверный URL Ollama",
"invalidBaseUrl": "Неверный базовый URL",
"qdrantUrlRequired": "Требуется URL Qdrant",
"openaiApiKeyRequired": "Требуется ключ API OpenAI",
"modelSelectionRequired": "Требуется выбор модели",
"apiKeyRequired": "Требуется ключ API",
"modelIdRequired": "Требуется идентификатор модели",
"modelDimensionRequired": "Требуется размерность модели",
"geminiApiKeyRequired": "Требуется ключ API Gemini",
"ollamaBaseUrlRequired": "Требуется базовый URL Ollama",
"baseUrlRequired": "Требуется базовый URL",
"modelDimensionMinValue": "Размерность модели должна быть больше 0"
},
"advancedConfigLabel": "Расширенная конфигурация",
"searchMinScoreLabel": "Порог оценки поиска",
"searchMinScoreDescription": "Минимальный балл сходства (0.0-1.0), необходимый для результатов поиска. Более низкие значения возвращают больше результатов, но они могут быть менее релевантными. Более высокие значения возвращают меньше результатов, но более релевантных.",

View file

@ -98,6 +98,21 @@
"error": "Hata"
},
"close": "Kapat",
"validation": {
"invalidQdrantUrl": "Geçersiz Qdrant URL'si",
"invalidOllamaUrl": "Geçersiz Ollama URL'si",
"invalidBaseUrl": "Geçersiz temel URL'si",
"qdrantUrlRequired": "Qdrant URL'si gereklidir",
"openaiApiKeyRequired": "OpenAI API anahtarı gereklidir",
"modelSelectionRequired": "Model seçimi gereklidir",
"apiKeyRequired": "API anahtarı gereklidir",
"modelIdRequired": "Model kimliği gereklidir",
"modelDimensionRequired": "Model boyutu gereklidir",
"geminiApiKeyRequired": "Gemini API anahtarı gereklidir",
"ollamaBaseUrlRequired": "Ollama temel URL'si gereklidir",
"baseUrlRequired": "Temel URL'si gereklidir",
"modelDimensionMinValue": "Model boyutu 0'dan büyük olmalıdır"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "Lỗi"
},
"close": "Đóng",
"validation": {
"invalidQdrantUrl": "URL Qdrant không hợp lệ",
"invalidOllamaUrl": "URL Ollama không hợp lệ",
"invalidBaseUrl": "URL cơ sở không hợp lệ",
"qdrantUrlRequired": "Yêu cầu URL Qdrant",
"openaiApiKeyRequired": "Yêu cầu khóa API OpenAI",
"modelSelectionRequired": "Yêu cầu chọn mô hình",
"apiKeyRequired": "Yêu cầu khóa API",
"modelIdRequired": "Yêu cầu ID mô hình",
"modelDimensionRequired": "Yêu cầu kích thước mô hình",
"geminiApiKeyRequired": "Yêu cầu khóa API Gemini",
"ollamaBaseUrlRequired": "Yêu cầu URL cơ sở Ollama",
"baseUrlRequired": "Yêu cầu URL cơ sở",
"modelDimensionMinValue": "Kích thước mô hình phải lớn hơn 0"
},
"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.",

View file

@ -98,6 +98,21 @@
"error": "错误"
},
"close": "关闭",
"validation": {
"invalidQdrantUrl": "无效的 Qdrant URL",
"invalidOllamaUrl": "无效的 Ollama URL",
"invalidBaseUrl": "无效的基础 URL",
"qdrantUrlRequired": "需要 Qdrant URL",
"openaiApiKeyRequired": "需要 OpenAI API 密钥",
"modelSelectionRequired": "需要选择模型",
"apiKeyRequired": "需要 API 密钥",
"modelIdRequired": "需要模型 ID",
"modelDimensionRequired": "需要模型维度",
"geminiApiKeyRequired": "需要 Gemini API 密钥",
"ollamaBaseUrlRequired": "需要 Ollama 基础 URL",
"baseUrlRequired": "需要基础 URL",
"modelDimensionMinValue": "模型维度必须大于 0"
},
"advancedConfigLabel": "高级配置",
"searchMinScoreLabel": "搜索分数阈值",
"searchMinScoreDescription": "搜索结果所需的最低相似度分数0.0-1.0)。较低的值返回更多结果,但可能不太相关。较高的值返回较少但更相关的结果。",

View file

@ -98,6 +98,21 @@
"error": "錯誤"
},
"close": "關閉",
"validation": {
"invalidQdrantUrl": "無效的 Qdrant URL",
"invalidOllamaUrl": "無效的 Ollama URL",
"invalidBaseUrl": "無效的基礎 URL",
"qdrantUrlRequired": "需要 Qdrant URL",
"openaiApiKeyRequired": "需要 OpenAI API 金鑰",
"modelSelectionRequired": "需要選擇模型",
"apiKeyRequired": "需要 API 金鑰",
"modelIdRequired": "需要模型 ID",
"modelDimensionRequired": "需要模型維度",
"geminiApiKeyRequired": "需要 Gemini API 金鑰",
"ollamaBaseUrlRequired": "需要 Ollama 基礎 URL",
"baseUrlRequired": "需要基礎 URL",
"modelDimensionMinValue": "模型維度必須大於 0"
},
"advancedConfigLabel": "進階設定",
"searchMinScoreLabel": "搜尋分數閾值",
"searchMinScoreDescription": "搜尋結果所需的最低相似度分數0.0-1.0)。較低的值會傳回更多結果,但可能較不相關。較高的值會傳回較少但更相關的結果。",