mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Merge branch 'RooCodeInc:main' into main
This commit is contained in:
commit
00a0b631f7
61 changed files with 3351 additions and 330 deletions
|
|
@ -313,25 +313,6 @@ describe("getEnvironmentDetails", () => {
|
|||
expect(mockInactiveTerminal.getCurrentWorkingDirectory).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should include warning when file writing is not allowed", async () => {
|
||||
;(isToolAllowedForMode as Mock).mockReturnValue(false)
|
||||
;(getModeBySlug as Mock).mockImplementation((slug: string) => {
|
||||
if (slug === "code") {
|
||||
return { name: "💻 Code" }
|
||||
}
|
||||
|
||||
if (slug === defaultModeSlug) {
|
||||
return { name: "Default Mode" }
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
|
||||
expect(result).toContain("NOTE: You are currently in '💻 Code' mode, which does not allow write operations")
|
||||
})
|
||||
|
||||
it("should include experiment-specific details when Power Steering is enabled", async () => {
|
||||
mockState.experiments = { [EXPERIMENT_IDS.POWER_STEERING]: true }
|
||||
;(experiments.isEnabled as Mock).mockReturnValue(true)
|
||||
|
|
|
|||
|
|
@ -233,16 +233,6 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
|
|||
}
|
||||
}
|
||||
|
||||
// Add warning if not in code mode.
|
||||
if (
|
||||
!isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], { apply_diff: cline.diffEnabled }) &&
|
||||
!isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: cline.diffEnabled })
|
||||
) {
|
||||
const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode
|
||||
const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug
|
||||
details += `\n\nNOTE: You are currently in '${currentModeName}' mode, which does not allow write operations. To write files, the user will need to switch to a mode that supports file writing, such as '${defaultModeName}' mode.`
|
||||
}
|
||||
|
||||
if (includeFileDetails) {
|
||||
details += `\n\n# Current Workspace Directory (${cline.cwd.toPosix()}) Files\n`
|
||||
const isDesktop = arePathsEqual(cline.cwd, path.join(os.homedir(), "Desktop"))
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,9 +6,16 @@ import pWaitFor from "p-wait-for"
|
|||
import * as vscode from "vscode"
|
||||
import * as yaml from "yaml"
|
||||
|
||||
import { type Language, type ProviderSettings, type GlobalState, TelemetryEventName } from "@roo-code/types"
|
||||
import {
|
||||
type Language,
|
||||
type ProviderSettings,
|
||||
type GlobalState,
|
||||
type ClineMessage,
|
||||
TelemetryEventName,
|
||||
} from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { type ApiMessage } from "../task-persistence/apiMessages"
|
||||
|
||||
import { ClineProvider } from "./ClineProvider"
|
||||
import { changeLanguage, t } from "../../i18n"
|
||||
|
|
@ -58,6 +65,200 @@ export const webviewMessageHandler = async (
|
|||
const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
|
||||
await provider.contextProxy.setValue(key, value)
|
||||
|
||||
/**
|
||||
* Shared utility to find message indices based on timestamp
|
||||
*/
|
||||
const findMessageIndices = (messageTs: number, currentCline: any) => {
|
||||
const timeCutoff = messageTs - 1000 // 1 second buffer before the message
|
||||
const messageIndex = currentCline.clineMessages.findIndex((msg: ClineMessage) => msg.ts && msg.ts >= timeCutoff)
|
||||
const apiConversationHistoryIndex = currentCline.apiConversationHistory.findIndex(
|
||||
(msg: ApiMessage) => msg.ts && msg.ts >= timeCutoff,
|
||||
)
|
||||
return { messageIndex, apiConversationHistoryIndex }
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes just the target message, preserving messages after the next user message
|
||||
*/
|
||||
const removeMessagesJustThis = async (
|
||||
currentCline: any,
|
||||
messageIndex: number,
|
||||
apiConversationHistoryIndex: number,
|
||||
) => {
|
||||
// Find the next user message first
|
||||
const nextUserMessage = currentCline.clineMessages
|
||||
.slice(messageIndex + 1)
|
||||
.find((msg: ClineMessage) => msg.type === "say" && msg.say === "user_feedback")
|
||||
|
||||
// Handle UI messages
|
||||
if (nextUserMessage) {
|
||||
// Find absolute index of next user message
|
||||
const nextUserMessageIndex = currentCline.clineMessages.findIndex(
|
||||
(msg: ClineMessage) => msg === nextUserMessage,
|
||||
)
|
||||
|
||||
// Keep messages before current message and after next user message
|
||||
await currentCline.overwriteClineMessages([
|
||||
...currentCline.clineMessages.slice(0, messageIndex),
|
||||
...currentCline.clineMessages.slice(nextUserMessageIndex),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current message
|
||||
await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex))
|
||||
}
|
||||
|
||||
// Handle API messages
|
||||
if (apiConversationHistoryIndex !== -1) {
|
||||
if (nextUserMessage && nextUserMessage.ts) {
|
||||
// Keep messages before current API message and after next user message
|
||||
await currentCline.overwriteApiConversationHistory([
|
||||
...currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
...currentCline.apiConversationHistory.filter(
|
||||
(msg: ApiMessage) => msg.ts && msg.ts >= nextUserMessage.ts,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current API message
|
||||
await currentCline.overwriteApiConversationHistory(
|
||||
currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the target message and all subsequent messages
|
||||
*/
|
||||
const removeMessagesThisAndSubsequent = async (
|
||||
currentCline: any,
|
||||
messageIndex: number,
|
||||
apiConversationHistoryIndex: number,
|
||||
) => {
|
||||
// Delete this message and all that follow
|
||||
await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex))
|
||||
|
||||
if (apiConversationHistoryIndex !== -1) {
|
||||
await currentCline.overwriteApiConversationHistory(
|
||||
currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles message deletion operations with user confirmation
|
||||
*/
|
||||
const handleDeleteOperation = async (messageTs: number): Promise<void> => {
|
||||
const options = [
|
||||
t("common:confirmation.delete_just_this_message"),
|
||||
t("common:confirmation.delete_this_and_subsequent"),
|
||||
]
|
||||
|
||||
const answer = await vscode.window.showInformationMessage(
|
||||
t("common:confirmation.delete_message"),
|
||||
{ modal: true },
|
||||
...options,
|
||||
)
|
||||
|
||||
// Only proceed if user selected one of the options and we have a current cline
|
||||
if (answer && options.includes(answer) && provider.getCurrentCline()) {
|
||||
const currentCline = provider.getCurrentCline()!
|
||||
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
try {
|
||||
const { historyItem } = await provider.getTaskWithId(currentCline.taskId)
|
||||
|
||||
// Check which option the user selected
|
||||
if (answer === options[0]) {
|
||||
// Delete just this message
|
||||
await removeMessagesJustThis(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
} else if (answer === options[1]) {
|
||||
// Delete this message and all subsequent
|
||||
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
}
|
||||
|
||||
// Initialize with history item after deletion
|
||||
await provider.initClineWithHistoryItem(historyItem)
|
||||
} catch (error) {
|
||||
console.error("Error in delete message:", error)
|
||||
vscode.window.showErrorMessage(
|
||||
`Error deleting message: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles message editing operations with user confirmation
|
||||
*/
|
||||
const handleEditOperation = async (messageTs: number, editedContent: string): Promise<void> => {
|
||||
const options = [
|
||||
t("common:confirmation.edit_this_and_delete_subsequent"),
|
||||
t("common:confirmation.edit_just_this_message"),
|
||||
]
|
||||
|
||||
const answer = await vscode.window.showInformationMessage(
|
||||
t("common:confirmation.edit_message"),
|
||||
{ modal: true },
|
||||
...options,
|
||||
)
|
||||
|
||||
// Only proceed if user selected one of the options and we have a current cline
|
||||
if (answer && options.includes(answer) && provider.getCurrentCline()) {
|
||||
const currentCline = provider.getCurrentCline()!
|
||||
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
try {
|
||||
// Check which option the user selected
|
||||
if (answer === options[0]) {
|
||||
// Edit this message and delete subsequent
|
||||
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
} else if (answer === options[1]) {
|
||||
// Edit just this message
|
||||
await removeMessagesJustThis(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
}
|
||||
|
||||
// Process the edited message as a regular user message
|
||||
// This will add it to the conversation and trigger an AI response
|
||||
webviewMessageHandler(provider, {
|
||||
type: "askResponse",
|
||||
askResponse: "messageResponse",
|
||||
text: editedContent,
|
||||
})
|
||||
|
||||
// Don't initialize with history item for edit operations
|
||||
// The webviewMessageHandler will handle the conversation state
|
||||
} catch (error) {
|
||||
console.error("Error in edit message:", error)
|
||||
vscode.window.showErrorMessage(
|
||||
`Error editing message: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles message modification operations (delete or edit) with confirmation dialog
|
||||
* @param messageTs Timestamp of the message to operate on
|
||||
* @param operation Type of operation ('delete' or 'edit')
|
||||
* @param editedContent New content for edit operations
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
const handleMessageModificationsOperation = async (
|
||||
messageTs: number,
|
||||
operation: "delete" | "edit",
|
||||
editedContent?: string,
|
||||
): Promise<void> => {
|
||||
if (operation === "delete") {
|
||||
await handleDeleteOperation(messageTs)
|
||||
} else if (operation === "edit" && editedContent) {
|
||||
await handleEditOperation(messageTs, editedContent)
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case "webviewDidLaunch":
|
||||
// Load custom modes first
|
||||
|
|
@ -989,108 +1190,19 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
case "deleteMessage": {
|
||||
const answer = await vscode.window.showInformationMessage(
|
||||
t("common:confirmation.delete_message"),
|
||||
{ modal: true },
|
||||
t("common:confirmation.just_this_message"),
|
||||
t("common:confirmation.this_and_subsequent"),
|
||||
)
|
||||
|
||||
if (provider.getCurrentCline() && typeof message.value === "number" && message.value) {
|
||||
await handleMessageModificationsOperation(message.value, "delete")
|
||||
}
|
||||
break
|
||||
}
|
||||
case "submitEditedMessage": {
|
||||
if (
|
||||
(answer === t("common:confirmation.just_this_message") ||
|
||||
answer === t("common:confirmation.this_and_subsequent")) &&
|
||||
provider.getCurrentCline() &&
|
||||
typeof message.value === "number" &&
|
||||
message.value
|
||||
message.value &&
|
||||
message.editedMessageContent
|
||||
) {
|
||||
const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete
|
||||
|
||||
const messageIndex = provider
|
||||
.getCurrentCline()!
|
||||
.clineMessages.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
|
||||
|
||||
const apiConversationHistoryIndex = provider
|
||||
.getCurrentCline()
|
||||
?.apiConversationHistory.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
const { historyItem } = await provider.getTaskWithId(provider.getCurrentCline()!.taskId)
|
||||
|
||||
if (answer === t("common:confirmation.just_this_message")) {
|
||||
// Find the next user message first
|
||||
const nextUserMessage = provider
|
||||
.getCurrentCline()!
|
||||
.clineMessages.slice(messageIndex + 1)
|
||||
.find((msg) => msg.type === "say" && msg.say === "user_feedback")
|
||||
|
||||
// Handle UI messages
|
||||
if (nextUserMessage) {
|
||||
// Find absolute index of next user message
|
||||
const nextUserMessageIndex = provider
|
||||
.getCurrentCline()!
|
||||
.clineMessages.findIndex((msg) => msg === nextUserMessage)
|
||||
|
||||
// Keep messages before current message and after next user message
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteClineMessages([
|
||||
...provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
|
||||
...provider.getCurrentCline()!.clineMessages.slice(nextUserMessageIndex),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current message
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteClineMessages(
|
||||
provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle API messages
|
||||
if (apiConversationHistoryIndex !== -1) {
|
||||
if (nextUserMessage && nextUserMessage.ts) {
|
||||
// Keep messages before current API message and after next user message
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteApiConversationHistory([
|
||||
...provider
|
||||
.getCurrentCline()!
|
||||
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
...provider
|
||||
.getCurrentCline()!
|
||||
.apiConversationHistory.filter(
|
||||
(msg) => msg.ts && msg.ts >= nextUserMessage.ts,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current API message
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteApiConversationHistory(
|
||||
provider
|
||||
.getCurrentCline()!
|
||||
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (answer === t("common:confirmation.this_and_subsequent")) {
|
||||
// Delete this message and all that follow
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteClineMessages(provider.getCurrentCline()!.clineMessages.slice(0, messageIndex))
|
||||
if (apiConversationHistoryIndex !== -1) {
|
||||
await provider
|
||||
.getCurrentCline()!
|
||||
.overwriteApiConversationHistory(
|
||||
provider
|
||||
.getCurrentCline()!
|
||||
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await provider.initClineWithHistoryItem(historyItem)
|
||||
}
|
||||
await handleMessageModificationsOperation(message.value, "edit", message.editedMessageContent)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -1843,8 +1955,12 @@ export const webviewMessageHandler = async (
|
|||
const settings = message.codeIndexSettings
|
||||
|
||||
try {
|
||||
// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
|
||||
// Check if embedder provider has changed
|
||||
const currentConfig = getGlobalState("codebaseIndexConfig") || {}
|
||||
const embedderProviderChanged =
|
||||
currentConfig.codebaseIndexEmbedderProvider !== settings.codebaseIndexEmbedderProvider
|
||||
|
||||
// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
|
||||
const globalStateConfig = {
|
||||
...currentConfig,
|
||||
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
|
||||
|
|
@ -1880,23 +1996,7 @@ export const webviewMessageHandler = async (
|
|||
)
|
||||
}
|
||||
|
||||
// Verify secrets are actually stored
|
||||
const storedOpenAiKey = provider.contextProxy.getSecret("codeIndexOpenAiKey")
|
||||
|
||||
// Notify code index manager of changes
|
||||
if (provider.codeIndexManager) {
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
|
||||
// Auto-start indexing if now enabled and configured
|
||||
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
|
||||
if (!provider.codeIndexManager.isInitialized) {
|
||||
await provider.codeIndexManager.initialize(provider.contextProxy)
|
||||
}
|
||||
provider.codeIndexManager.startIndexing()
|
||||
}
|
||||
}
|
||||
|
||||
// Send success response
|
||||
// Send success response first - settings are saved regardless of validation
|
||||
await provider.postMessageToWebview({
|
||||
type: "codeIndexSettingsSaved",
|
||||
success: true,
|
||||
|
|
@ -1905,6 +2005,61 @@ export const webviewMessageHandler = async (
|
|||
|
||||
// Update webview state
|
||||
await provider.postStateToWebview()
|
||||
|
||||
// Then handle validation and initialization
|
||||
if (provider.codeIndexManager) {
|
||||
// If embedder provider changed, perform proactive validation
|
||||
if (embedderProviderChanged) {
|
||||
try {
|
||||
// Force handleSettingsChange which will trigger validation
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
} catch (error) {
|
||||
// Validation failed - the error state is already set by handleSettingsChange
|
||||
provider.log(
|
||||
`Embedder validation failed after provider change: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
// Send validation error to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: provider.codeIndexManager.getCurrentStatus(),
|
||||
})
|
||||
// Exit early - don't try to start indexing with invalid configuration
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// No provider change, just handle settings normally
|
||||
try {
|
||||
await provider.codeIndexManager.handleSettingsChange()
|
||||
} catch (error) {
|
||||
// Log but don't fail - settings are saved
|
||||
provider.log(
|
||||
`Settings change handling error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a bit more to ensure everything is ready
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Auto-start indexing if now enabled and configured
|
||||
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
|
||||
if (!provider.codeIndexManager.isInitialized) {
|
||||
try {
|
||||
await provider.codeIndexManager.initialize(provider.contextProxy)
|
||||
provider.log(`Code index manager initialized after settings save`)
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Code index initialization failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
// Send error status to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: provider.codeIndexManager.getCurrentStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
provider.log(`Error saving code index settings: ${error.message || error}`)
|
||||
await provider.postMessageToWebview({
|
||||
|
|
|
|||
|
|
@ -23,8 +23,11 @@
|
|||
"delete_config_profile": "Estàs segur que vols eliminar aquest perfil de configuració?",
|
||||
"delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest mode {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}",
|
||||
"delete_message": "Què vols eliminar?",
|
||||
"just_this_message": "Només aquest missatge",
|
||||
"this_and_subsequent": "Aquest i tots els missatges posteriors"
|
||||
"edit_message": "Eliminar tots els missatges després d'aquest?",
|
||||
"delete_just_this_message": "Només aquest missatge",
|
||||
"edit_just_this_message": "No, només editar aquest",
|
||||
"delete_this_and_subsequent": "Aquest i tots els missatges posteriors",
|
||||
"edit_this_and_delete_subsequent": "Sí"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Format d'URI de dades no vàlid",
|
||||
|
|
@ -112,6 +115,11 @@
|
|||
"remove": "Eliminar",
|
||||
"keep": "Mantenir"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Desar",
|
||||
"cancel": "Cancel·lar",
|
||||
"edit": "Editar"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Error de tasca: Ha estat aturada i cancel·lada per l'usuari.",
|
||||
"deleted": "Fallada de tasca: Ha estat aturada i eliminada per l'usuari.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "No s'ha pogut llegir el cos de l'error",
|
||||
"requestFailed": "La sol·licitud de l'API d'Ollama ha fallat amb l'estat {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estructura de resposta no vàlida de l'API d'Ollama: no s'ha trobat la matriu \"embeddings\" o no és una matriu.",
|
||||
"embeddingFailed": "La incrustació d'Ollama ha fallat: {{message}}"
|
||||
"embeddingFailed": "La incrustació d'Ollama ha fallat: {{message}}",
|
||||
"serviceNotRunning": "El servei d'Ollama no s'està executant a {{baseUrl}}",
|
||||
"serviceUnavailable": "El servei d'Ollama no està disponible (estat: {{status}})",
|
||||
"modelNotFound": "No s'ha trobat el model d'Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "El model d'Ollama no és capaç de fer incrustacions: {{modelId}}",
|
||||
"hostNotFound": "No s'ha trobat l'amfitrió d'Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error desconegut en processar el fitxer {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Ha fallat l'autenticació. Comproveu la vostra clau d'API a la configuració.",
|
||||
"connectionFailed": "No s'ha pogut connectar al servei d'incrustació. Comproveu la vostra configuració de connexió i assegureu-vos que el servei estigui funcionant.",
|
||||
"modelNotAvailable": "El model especificat no està disponible. Comproveu la vostra configuració de model.",
|
||||
"configurationError": "Configuració d'incrustació no vàlida. Reviseu la vostra configuració.",
|
||||
"serviceUnavailable": "El servei d'incrustació no està disponible. Assegureu-vos que estigui funcionant i sigui accessible.",
|
||||
"invalidEndpoint": "Punt final d'API no vàlid. Comproveu la vostra configuració d'URL.",
|
||||
"invalidEmbedderConfig": "Configuració d'incrustació no vàlida. Comproveu la vostra configuració.",
|
||||
"invalidApiKey": "Clau d'API no vàlida. Comproveu la vostra configuració de clau d'API.",
|
||||
"invalidBaseUrl": "URL base no vàlida. Comproveu la vostra configuració d'URL.",
|
||||
"invalidModel": "Model no vàlid. Comproveu la vostra configuració de model.",
|
||||
"invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Möchtest du dieses Konfigurationsprofil wirklich löschen?",
|
||||
"delete_custom_mode_with_rules": "Bist du sicher, dass du diesen {scope}-Modus löschen möchtest?\n\nDadurch wird auch der zugehörige Regelordner unter folgender Adresse gelöscht:\n{rulesFolderPath}",
|
||||
"delete_message": "Was möchtest du löschen?",
|
||||
"just_this_message": "Nur diese Nachricht",
|
||||
"this_and_subsequent": "Diese und alle nachfolgenden Nachrichten"
|
||||
"edit_message": "Alle Nachrichten nach dieser löschen?",
|
||||
"delete_just_this_message": "Nur diese Nachricht",
|
||||
"edit_just_this_message": "Nein, nur diese bearbeiten",
|
||||
"delete_this_and_subsequent": "Diese und alle nachfolgenden Nachrichten",
|
||||
"edit_this_and_delete_subsequent": "Ja"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Ungültiges Daten-URI-Format",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Entfernen",
|
||||
"keep": "Behalten"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und abgebrochen.",
|
||||
"deleted": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und gelöscht.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Fehlerinhalt konnte nicht gelesen werden",
|
||||
"requestFailed": "Ollama API-Anfrage fehlgeschlagen mit Status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ungültige Antwortstruktur von Ollama API: \"embeddings\" Array nicht gefunden oder kein Array.",
|
||||
"embeddingFailed": "Ollama Einbettung fehlgeschlagen: {{message}}"
|
||||
"embeddingFailed": "Ollama Einbettung fehlgeschlagen: {{message}}",
|
||||
"serviceNotRunning": "Ollama-Dienst wird unter {{baseUrl}} nicht ausgeführt",
|
||||
"serviceUnavailable": "Ollama-Dienst ist nicht verfügbar (Status: {{status}})",
|
||||
"modelNotFound": "Ollama-Modell nicht gefunden: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama-Modell ist nicht für Einbettungen geeignet: {{modelId}}",
|
||||
"hostNotFound": "Ollama-Host nicht gefunden: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Unbekannter Fehler beim Verarbeiten der Datei {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfe deinen API-Schlüssel in den Einstellungen.",
|
||||
"connectionFailed": "Verbindung zum Embedder-Dienst fehlgeschlagen. Bitte überprüfe deine Verbindungseinstellungen und stelle sicher, dass der Dienst läuft.",
|
||||
"modelNotAvailable": "Das angegebene Modell ist nicht verfügbar. Bitte überprüfe deine Modellkonfiguration.",
|
||||
"configurationError": "Ungültige Embedder-Konfiguration. Bitte überprüfe deine Einstellungen.",
|
||||
"serviceUnavailable": "Der Embedder-Dienst ist nicht verfügbar. Bitte stelle sicher, dass er läuft und erreichbar ist.",
|
||||
"invalidEndpoint": "Ungültiger API-Endpunkt. Bitte überprüfe deine URL-Konfiguration.",
|
||||
"invalidEmbedderConfig": "Ungültige Embedder-Konfiguration. Bitte überprüfe deine Einstellungen.",
|
||||
"invalidApiKey": "Ungültiger API-Schlüssel. Bitte überprüfe deine API-Schlüssel-Konfiguration.",
|
||||
"invalidBaseUrl": "Ungültige Basis-URL. Bitte überprüfe deine URL-Konfiguration.",
|
||||
"invalidModel": "Ungültiges Modell. Bitte überprüfe deine Modellkonfiguration.",
|
||||
"invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Are you sure you want to delete this configuration profile?",
|
||||
"delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}",
|
||||
"delete_message": "What would you like to delete?",
|
||||
"just_this_message": "Just this message",
|
||||
"this_and_subsequent": "This and all subsequent messages"
|
||||
"edit_message": "Delete all messages after this one?",
|
||||
"delete_just_this_message": "Just this message",
|
||||
"edit_just_this_message": "No, just edit this one",
|
||||
"delete_this_and_subsequent": "This and all subsequent messages",
|
||||
"edit_this_and_delete_subsequent": "Yes"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Invalid data URI format",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Remove",
|
||||
"keep": "Keep"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Task error: It was stopped and canceled by the user.",
|
||||
"deleted": "Task failure: It was stopped and deleted by the user.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Could not read error body",
|
||||
"requestFailed": "Ollama API request failed with status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Invalid response structure from Ollama API: \"embeddings\" array not found or not an array.",
|
||||
"embeddingFailed": "Ollama embedding failed: {{message}}"
|
||||
"embeddingFailed": "Ollama embedding failed: {{message}}",
|
||||
"serviceNotRunning": "Ollama service is not running at {{baseUrl}}",
|
||||
"serviceUnavailable": "Ollama service is unavailable (status: {{status}})",
|
||||
"modelNotFound": "Ollama model not found: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama model is not embedding capable: {{modelId}}",
|
||||
"hostNotFound": "Ollama host not found: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Unknown error processing file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authentication failed. Please check your API key in the settings.",
|
||||
"connectionFailed": "Failed to connect to the embedder service. Please check your connection settings and ensure the service is running.",
|
||||
"modelNotAvailable": "The specified model is not available. Please check your model configuration.",
|
||||
"configurationError": "Invalid embedder configuration. Please review your settings.",
|
||||
"serviceUnavailable": "The embedder service is not available. Please ensure it is running and accessible.",
|
||||
"invalidEndpoint": "Invalid API endpoint. Please check your URL configuration.",
|
||||
"invalidEmbedderConfig": "Invalid embedder configuration. Please check your settings.",
|
||||
"invalidApiKey": "Invalid API key. Please check your API key configuration.",
|
||||
"invalidBaseUrl": "Invalid base URL. Please check your URL configuration.",
|
||||
"invalidModel": "Invalid model. Please check your model configuration.",
|
||||
"invalidResponse": "Invalid response from embedder service. Please check your configuration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "¿Estás seguro de que deseas eliminar este perfil de configuración?",
|
||||
"delete_custom_mode_with_rules": "¿Estás seguro de que quieres eliminar este modo {scope}?\n\nEsto también eliminará la carpeta de reglas asociada en:\n{rulesFolderPath}",
|
||||
"delete_message": "¿Qué deseas eliminar?",
|
||||
"just_this_message": "Solo este mensaje",
|
||||
"this_and_subsequent": "Este y todos los mensajes posteriores"
|
||||
"edit_message": "¿Eliminar todos los mensajes posteriores a este?",
|
||||
"delete_just_this_message": "Solo este mensaje",
|
||||
"edit_just_this_message": "No, solo editar este",
|
||||
"delete_this_and_subsequent": "Este y todos los mensajes posteriores",
|
||||
"edit_this_and_delete_subsequent": "Sí"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Formato de URI de datos no válido",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Eliminar",
|
||||
"keep": "Mantener"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"edit": "Editar"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Error de tarea: Fue detenida y cancelada por el usuario.",
|
||||
"deleted": "Fallo de tarea: Fue detenida y eliminada por el usuario.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "No se pudo leer el cuerpo del error",
|
||||
"requestFailed": "La solicitud de la API de Ollama falló con estado {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estructura de respuesta inválida de la API de Ollama: array \"embeddings\" no encontrado o no es un array.",
|
||||
"embeddingFailed": "Incrustación de Ollama falló: {{message}}"
|
||||
"embeddingFailed": "Incrustación de Ollama falló: {{message}}",
|
||||
"serviceNotRunning": "El servicio Ollama no se está ejecutando en {{baseUrl}}",
|
||||
"serviceUnavailable": "El servicio Ollama no está disponible (estado: {{status}})",
|
||||
"modelNotFound": "No se encuentra el modelo Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "El modelo Ollama no es capaz de realizar incrustaciones: {{modelId}}",
|
||||
"hostNotFound": "No se encuentra el host de Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error desconocido procesando archivo {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Error de autenticación. Comprueba tu clave de API en los ajustes.",
|
||||
"connectionFailed": "Error al conectar con el servicio de embedder. Comprueba los ajustes de conexión y asegúrate de que el servicio esté funcionando.",
|
||||
"modelNotAvailable": "El modelo especificado no está disponible. Comprueba la configuración de tu modelo.",
|
||||
"configurationError": "Configuración de embedder no válida. Revisa tus ajustes.",
|
||||
"serviceUnavailable": "El servicio de embedder no está disponible. Asegúrate de que esté funcionando y sea accesible.",
|
||||
"invalidEndpoint": "Punto de conexión de API no válido. Comprueba la configuración de tu URL.",
|
||||
"invalidEmbedderConfig": "Configuración de embedder no válida. Comprueba tus ajustes.",
|
||||
"invalidApiKey": "Clave de API no válida. Comprueba la configuración de tu clave de API.",
|
||||
"invalidBaseUrl": "URL base no válida. Comprueba la configuración de tu URL.",
|
||||
"invalidModel": "Modelo no válido. Comprueba la configuración de tu modelo.",
|
||||
"invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Êtes-vous sûr de vouloir supprimer ce profil de configuration ?",
|
||||
"delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce mode {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}",
|
||||
"delete_message": "Que souhaitez-vous supprimer ?",
|
||||
"just_this_message": "Uniquement ce message",
|
||||
"this_and_subsequent": "Ce message et tous les messages suivants"
|
||||
"edit_message": "Supprimer tous les messages après celui-ci ?",
|
||||
"delete_just_this_message": "Uniquement ce message",
|
||||
"edit_just_this_message": "Non, modifier uniquement celui-ci",
|
||||
"delete_this_and_subsequent": "Ce message et tous les messages suivants",
|
||||
"edit_this_and_delete_subsequent": "Oui"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Format d'URI de données invalide",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Supprimer",
|
||||
"keep": "Conserver"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"edit": "Modifier"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Erreur de tâche : Elle a été arrêtée et annulée par l'utilisateur.",
|
||||
"deleted": "Échec de la tâche : Elle a été arrêtée et supprimée par l'utilisateur.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Impossible de lire le corps de l'erreur",
|
||||
"requestFailed": "Échec de la requête API Ollama avec le statut {{status}} {{statusText}} : {{errorBody}}",
|
||||
"invalidResponseStructure": "Structure de réponse invalide de l'API Ollama : tableau \"embeddings\" non trouvé ou n'est pas un tableau.",
|
||||
"embeddingFailed": "Échec de l'embedding Ollama : {{message}}"
|
||||
"embeddingFailed": "Échec de l'embedding Ollama : {{message}}",
|
||||
"serviceNotRunning": "Le service Ollama n'est pas en cours d'exécution sur {{baseUrl}}",
|
||||
"serviceUnavailable": "Le service Ollama est indisponible (statut : {{status}})",
|
||||
"modelNotFound": "Modèle Ollama introuvable : {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Le modèle Ollama n'est pas capable d'intégrer : {{modelId}}",
|
||||
"hostNotFound": "Hôte Ollama introuvable : {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Erreur inconnue lors du traitement du fichier {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Échec de l'authentification. Veuillez vérifier votre clé API dans les paramètres.",
|
||||
"connectionFailed": "Échec de la connexion au service d'embedding. Veuillez vérifier vos paramètres de connexion et vous assurer que le service est en cours d'exécution.",
|
||||
"modelNotAvailable": "Le modèle spécifié n'est pas disponible. Veuillez vérifier la configuration de votre modèle.",
|
||||
"configurationError": "Configuration de l'embedder invalide. Veuillez vérifier vos paramètres.",
|
||||
"serviceUnavailable": "Le service d'embedding n'est pas disponible. Veuillez vous assurer qu'il est en cours d'exécution et accessible.",
|
||||
"invalidEndpoint": "Point de terminaison d'API invalide. Veuillez vérifier votre configuration d'URL.",
|
||||
"invalidEmbedderConfig": "Configuration de l'embedder invalide. Veuillez vérifier vos paramètres.",
|
||||
"invalidApiKey": "Clé API invalide. Veuillez vérifier votre configuration de clé API.",
|
||||
"invalidBaseUrl": "URL de base invalide. Veuillez vérifier votre configuration d'URL.",
|
||||
"invalidModel": "Modèle invalide. Veuillez vérifier votre configuration de modèle.",
|
||||
"invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "क्या आप वाकई इस कॉन्फ़िगरेशन प्रोफ़ाइल को हटाना चाहते हैं?",
|
||||
"delete_custom_mode_with_rules": "क्या आप वाकई इस {scope} मोड को हटाना चाहते हैं?\n\nयह संबंधित नियम फ़ोल्डर को भी यहाँ हटा देगा:\n{rulesFolderPath}",
|
||||
"delete_message": "आप क्या हटाना चाहते हैं?",
|
||||
"just_this_message": "सिर्फ यह संदेश",
|
||||
"this_and_subsequent": "यह और सभी बाद के संदेश"
|
||||
"edit_message": "इसके बाद के सभी संदेशों को हटाएं?",
|
||||
"delete_just_this_message": "सिर्फ यह संदेश",
|
||||
"edit_just_this_message": "नहीं, केवल इसे संपादित करें",
|
||||
"delete_this_and_subsequent": "यह और सभी बाद के संदेश",
|
||||
"edit_this_and_delete_subsequent": "हां"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "अमान्य डेटा URI फॉर्मेट",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "हटाएं",
|
||||
"keep": "रखें"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "सहेजें",
|
||||
"cancel": "रद्द करें",
|
||||
"edit": "संपादित करें"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "टास्क त्रुटि: इसे उपयोगकर्ता द्वारा रोका और रद्द किया गया था।",
|
||||
"deleted": "टास्क विफलता: इसे उपयोगकर्ता द्वारा रोका और हटाया गया था।",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "त्रुटि सामग्री पढ़ नहीं सका",
|
||||
"requestFailed": "Ollama API अनुरोध स्थिति {{status}} {{statusText}} के साथ विफल: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API से अमान्य प्रतिक्रिया संरचना: \"embeddings\" सरणी नहीं मिली या सरणी नहीं है।",
|
||||
"embeddingFailed": "Ollama एम्बेडिंग विफल: {{message}}"
|
||||
"embeddingFailed": "Ollama एम्बेडिंग विफल: {{message}}",
|
||||
"serviceNotRunning": "ओलामा सेवा {{baseUrl}} पर नहीं चल रही है",
|
||||
"serviceUnavailable": "ओलामा सेवा अनुपलब्ध है (स्थिति: {{status}})",
|
||||
"modelNotFound": "ओलामा मॉडल नहीं मिला: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "ओलामा मॉडल एम्बेडिंग में सक्षम नहीं है: {{modelId}}",
|
||||
"hostNotFound": "ओलामा होस्ट नहीं मिला: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "फ़ाइल {{filePath}} प्रसंस्करण में अज्ञात त्रुटि",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "प्रमाणीकरण विफल। कृपया सेटिंग्स में अपनी एपीआई कुंजी जांचें।",
|
||||
"connectionFailed": "एम्बेडर सेवा से कनेक्ट करने में विफल। कृपया अपनी कनेक्शन सेटिंग्स जांचें और सुनिश्चित करें कि सेवा चल रही है।",
|
||||
"modelNotAvailable": "निर्दिष्ट मॉडल उपलब्ध नहीं है। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
|
||||
"configurationError": "अमान्य एम्बेडर कॉन्फ़िगरेशन। कृपया अपनी सेटिंग्स की समीक्षा करें।",
|
||||
"serviceUnavailable": "एम्बेडर सेवा उपलब्ध नहीं है। कृपया सुनिश्चित करें कि यह चल रहा है और पहुंच योग्य है।",
|
||||
"invalidEndpoint": "अमान्य एपीआई एंडपॉइंट। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidEmbedderConfig": "अमान्य एम्बेडर कॉन्फ़िगरेशन। कृपया अपनी सेटिंग्स जांचें।",
|
||||
"invalidApiKey": "अमान्य एपीआई कुंजी। कृपया अपनी एपीआई कुंजी कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidBaseUrl": "अमान्य बेस यूआरएल। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
|
||||
"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Apakah kamu yakin ingin menghapus profil konfigurasi ini?",
|
||||
"delete_custom_mode_with_rules": "Anda yakin ingin menghapus mode {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}",
|
||||
"delete_message": "Apa yang ingin kamu hapus?",
|
||||
"just_this_message": "Hanya pesan ini",
|
||||
"this_and_subsequent": "Ini dan semua pesan selanjutnya"
|
||||
"edit_message": "Hapus semua pesan setelah ini?",
|
||||
"delete_just_this_message": "Hanya pesan ini",
|
||||
"edit_just_this_message": "Tidak, hanya edit yang ini",
|
||||
"delete_this_and_subsequent": "Ini dan semua pesan selanjutnya",
|
||||
"edit_this_and_delete_subsequent": "Ya"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Format data URI tidak valid",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Hapus",
|
||||
"keep": "Simpan"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Simpan",
|
||||
"cancel": "Batal",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Error tugas: Dihentikan dan dibatalkan oleh pengguna.",
|
||||
"deleted": "Kegagalan tugas: Dihentikan dan dihapus oleh pengguna.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Tidak dapat membaca body error",
|
||||
"requestFailed": "Permintaan API Ollama gagal dengan status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Struktur respons tidak valid dari API Ollama: array \"embeddings\" tidak ditemukan atau bukan array.",
|
||||
"embeddingFailed": "Embedding Ollama gagal: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama gagal: {{message}}",
|
||||
"serviceNotRunning": "Layanan Ollama tidak berjalan di {{baseUrl}}",
|
||||
"serviceUnavailable": "Layanan Ollama tidak tersedia (status: {{status}})",
|
||||
"modelNotFound": "Model Ollama tidak ditemukan: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Model Ollama tidak mampu melakukan embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama tidak ditemukan: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Error tidak dikenal saat memproses file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Autentikasi gagal. Silakan periksa kunci API Anda di pengaturan.",
|
||||
"connectionFailed": "Gagal terhubung ke layanan embedder. Silakan periksa pengaturan koneksi Anda dan pastikan layanan berjalan.",
|
||||
"modelNotAvailable": "Model yang ditentukan tidak tersedia. Silakan periksa konfigurasi model Anda.",
|
||||
"configurationError": "Konfigurasi embedder tidak valid. Harap tinjau pengaturan Anda.",
|
||||
"serviceUnavailable": "Layanan embedder tidak tersedia. Pastikan layanan tersebut berjalan dan dapat diakses.",
|
||||
"invalidEndpoint": "Endpoint API tidak valid. Silakan periksa konfigurasi URL Anda.",
|
||||
"invalidEmbedderConfig": "Konfigurasi embedder tidak valid. Silakan periksa pengaturan Anda.",
|
||||
"invalidApiKey": "Kunci API tidak valid. Silakan periksa konfigurasi kunci API Anda.",
|
||||
"invalidBaseUrl": "URL dasar tidak valid. Silakan periksa konfigurasi URL Anda.",
|
||||
"invalidModel": "Model tidak valid. Silakan periksa konfigurasi model Anda.",
|
||||
"invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Sei sicuro di voler eliminare questo profilo di configurazione?",
|
||||
"delete_custom_mode_with_rules": "Sei sicuro di voler eliminare questa modalità {scope}?\n\nQuesto eliminerà anche la cartella delle regole associata in:\n{rulesFolderPath}",
|
||||
"delete_message": "Cosa desideri eliminare?",
|
||||
"just_this_message": "Solo questo messaggio",
|
||||
"this_and_subsequent": "Questo e tutti i messaggi successivi"
|
||||
"edit_message": "Eliminare tutti i messaggi dopo questo?",
|
||||
"delete_just_this_message": "Solo questo messaggio",
|
||||
"edit_just_this_message": "No, modifica solo questo",
|
||||
"delete_this_and_subsequent": "Questo e tutti i messaggi successivi",
|
||||
"edit_this_and_delete_subsequent": "Sì"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Formato URI dati non valido",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Rimuovi",
|
||||
"keep": "Mantieni"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Salva",
|
||||
"cancel": "Annulla",
|
||||
"edit": "Modifica"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Errore attività: È stata interrotta e annullata dall'utente.",
|
||||
"deleted": "Fallimento attività: È stata interrotta ed eliminata dall'utente.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Impossibile leggere il corpo dell'errore",
|
||||
"requestFailed": "Richiesta API Ollama fallita con stato {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Struttura di risposta non valida dall'API Ollama: array \"embeddings\" non trovato o non è un array.",
|
||||
"embeddingFailed": "Embedding Ollama fallito: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama fallito: {{message}}",
|
||||
"serviceNotRunning": "Il servizio Ollama non è in esecuzione su {{baseUrl}}",
|
||||
"serviceUnavailable": "Il servizio Ollama non è disponibile (stato: {{status}})",
|
||||
"modelNotFound": "Modello Ollama non trovato: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Il modello Ollama non è in grado di eseguire l'embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama non trovato: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Errore sconosciuto nell'elaborazione del file {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Autenticazione fallita. Controlla la tua chiave API nelle impostazioni.",
|
||||
"connectionFailed": "Connessione al servizio di embedder fallita. Controlla le impostazioni di connessione e assicurati che il servizio sia in esecuzione.",
|
||||
"modelNotAvailable": "Il modello specificato non è disponibile. Controlla la configurazione del tuo modello.",
|
||||
"configurationError": "Configurazione dell'embedder non valida. Rivedi le tue impostazioni.",
|
||||
"serviceUnavailable": "Il servizio di embedder non è disponibile. Assicurati che sia in esecuzione e accessibile.",
|
||||
"invalidEndpoint": "Endpoint API non valido. Controlla la configurazione del tuo URL.",
|
||||
"invalidEmbedderConfig": "Configurazione dell'embedder non valida. Controlla le tue impostazioni.",
|
||||
"invalidApiKey": "Chiave API non valida. Controlla la configurazione della tua chiave API.",
|
||||
"invalidBaseUrl": "URL di base non valido. Controlla la configurazione del tuo URL.",
|
||||
"invalidModel": "Modello non valido. Controlla la configurazione del tuo modello.",
|
||||
"invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "この設定プロファイルを削除してもよろしいですか?",
|
||||
"delete_custom_mode_with_rules": "この{scope}モードを削除してもよろしいですか?\n\nこれにより、関連するルールフォルダも次の場所で削除されます:\n{rulesFolderPath}",
|
||||
"delete_message": "何を削除しますか?",
|
||||
"just_this_message": "このメッセージのみ",
|
||||
"this_and_subsequent": "これ以降のすべてのメッセージ"
|
||||
"edit_message": "これ以降のメッセージをすべて削除しますか?",
|
||||
"delete_just_this_message": "このメッセージのみ",
|
||||
"edit_just_this_message": "いいえ、これだけを編集",
|
||||
"delete_this_and_subsequent": "これ以降のすべてのメッセージ",
|
||||
"edit_this_and_delete_subsequent": "はい"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "データURIフォーマットが無効です",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "削除",
|
||||
"keep": "保持"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "保存",
|
||||
"cancel": "キャンセル",
|
||||
"edit": "編集"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "タスクエラー:ユーザーによって停止およびキャンセルされました。",
|
||||
"deleted": "タスク失敗:ユーザーによって停止および削除されました。",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "エラー本文を読み取れませんでした",
|
||||
"requestFailed": "Ollama APIリクエストが失敗しました。ステータス {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama APIからの無効な応答構造:\"embeddings\"配列が見つからないか、配列ではありません。",
|
||||
"embeddingFailed": "Ollama埋め込みが失敗しました:{{message}}"
|
||||
"embeddingFailed": "Ollama埋め込みが失敗しました:{{message}}",
|
||||
"serviceNotRunning": "Ollamaサービスは{{baseUrl}}で実行されていません",
|
||||
"serviceUnavailable": "Ollamaサービスは利用できません(ステータス:{{status}})",
|
||||
"modelNotFound": "Ollamaモデルが見つかりません:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollamaモデルは埋め込みに対応していません:{{modelId}}",
|
||||
"hostNotFound": "Ollamaホストが見つかりません:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "ファイル{{filePath}}の処理中に不明なエラーが発生しました",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "認証に失敗しました。設定でAPIキーを確認してください。",
|
||||
"connectionFailed": "エンベッダーサービスへの接続に失敗しました。接続設定を確認し、サービスが実行されていることを確認してください。",
|
||||
"modelNotAvailable": "指定されたモデルは利用できません。モデル構成を確認してください。",
|
||||
"configurationError": "無効なエンベッダー構成です。設定を確認してください。",
|
||||
"serviceUnavailable": "エンベッダーサービスは利用できません。実行中でアクセス可能であることを確認してください。",
|
||||
"invalidEndpoint": "無効なAPIエンドポイントです。URL構成を確認してください。",
|
||||
"invalidEmbedderConfig": "無効なエンベッダー構成です。設定を確認してください。",
|
||||
"invalidApiKey": "無効なAPIキーです。APIキー構成を確認してください。",
|
||||
"invalidBaseUrl": "無効なベースURLです。URL構成を確認してください。",
|
||||
"invalidModel": "無効なモデルです。モデル構成を確認してください。",
|
||||
"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "이 구성 프로필을 삭제하시겠습니까?",
|
||||
"delete_custom_mode_with_rules": "이 {scope} 모드를 삭제하시겠습니까?\n\n이렇게 하면 연결된 규칙 폴더도 다음 위치에서 삭제됩니다:\n{rulesFolderPath}",
|
||||
"delete_message": "무엇을 삭제하시겠습니까?",
|
||||
"just_this_message": "이 메시지만",
|
||||
"this_and_subsequent": "이 메시지와 모든 후속 메시지"
|
||||
"edit_message": "이 메시지 이후의 모든 메시지를 삭제하시겠습니까?",
|
||||
"delete_just_this_message": "이 메시지만",
|
||||
"edit_just_this_message": "아니요, 이것만 편집",
|
||||
"delete_this_and_subsequent": "이 메시지와 모든 후속 메시지",
|
||||
"edit_this_and_delete_subsequent": "예"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "잘못된 데이터 URI 형식",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "제거",
|
||||
"keep": "유지"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "저장",
|
||||
"cancel": "취소",
|
||||
"edit": "편집"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "작업 오류: 사용자에 의해 중지 및 취소되었습니다.",
|
||||
"deleted": "작업 실패: 사용자에 의해 중지 및 삭제되었습니다.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "오류 본문을 읽을 수 없습니다",
|
||||
"requestFailed": "Ollama API 요청이 실패했습니다. 상태 {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API에서 잘못된 응답 구조: \"embeddings\" 배열을 찾을 수 없거나 배열이 아닙니다.",
|
||||
"embeddingFailed": "Ollama 임베딩 실패: {{message}}"
|
||||
"embeddingFailed": "Ollama 임베딩 실패: {{message}}",
|
||||
"serviceNotRunning": "Ollama 서비스가 {{baseUrl}}에서 실행되고 있지 않습니다",
|
||||
"serviceUnavailable": "Ollama 서비스를 사용할 수 없습니다 (상태: {{status}})",
|
||||
"modelNotFound": "Ollama 모델을 찾을 수 없습니다: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 모델은 임베딩이 불가능합니다: {{modelId}}",
|
||||
"hostNotFound": "Ollama 호스트를 찾을 수 없습니다: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "파일 {{filePath}} 처리 중 알 수 없는 오류",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "인증에 실패했습니다. 설정에서 API 키를 확인하세요.",
|
||||
"connectionFailed": "임베더 서비스에 연결하지 못했습니다. 연결 설정을 확인하고 서비스가 실행 중인지 확인하세요.",
|
||||
"modelNotAvailable": "지정된 모델을 사용할 수 없습니다. 모델 구성을 확인하세요.",
|
||||
"configurationError": "잘못된 임베더 구성입니다. 설정을 검토하세요.",
|
||||
"serviceUnavailable": "임베더 서비스를 사용할 수 없습니다. 실행 중이고 액세스 가능한지 확인하세요.",
|
||||
"invalidEndpoint": "잘못된 API 엔드포인트입니다. URL 구성을 확인하세요.",
|
||||
"invalidEmbedderConfig": "잘못된 임베더 구성입니다. 설정을 확인하세요.",
|
||||
"invalidApiKey": "잘못된 API 키입니다. API 키 구성을 확인하세요.",
|
||||
"invalidBaseUrl": "잘못된 기본 URL입니다. URL 구성을 확인하세요.",
|
||||
"invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.",
|
||||
"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Weet je zeker dat je dit configuratieprofiel wilt verwijderen?",
|
||||
"delete_custom_mode_with_rules": "Weet je zeker dat je deze {scope}-modus wilt verwijderen?\n\nDit verwijdert ook de bijbehorende regelsmap op:\n{rulesFolderPath}",
|
||||
"delete_message": "Wat wil je verwijderen?",
|
||||
"just_this_message": "Alleen dit bericht",
|
||||
"this_and_subsequent": "Dit en alle volgende berichten"
|
||||
"delete_just_this_message": "Alleen dit bericht",
|
||||
"delete_this_and_subsequent": "Dit en alle volgende berichten",
|
||||
"edit_message": "Alle berichten na dit bericht verwijderen?",
|
||||
"edit_just_this_message": "Nee, alleen dit bericht bewerken",
|
||||
"edit_this_and_delete_subsequent": "Ja"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Ongeldig data-URI-formaat",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Verwijderen",
|
||||
"keep": "Behouden"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Opslaan",
|
||||
"cancel": "Annuleren",
|
||||
"edit": "Bewerken"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Taakfout: gestopt en geannuleerd door gebruiker.",
|
||||
"deleted": "Taakfout: gestopt en verwijderd door gebruiker.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Kon foutinhoud niet lezen",
|
||||
"requestFailed": "Ollama API-verzoek mislukt met status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ongeldige responsstructuur van Ollama API: \"embeddings\" array niet gevonden of is geen array.",
|
||||
"embeddingFailed": "Ollama insluiting mislukt: {{message}}"
|
||||
"embeddingFailed": "Ollama insluiting mislukt: {{message}}",
|
||||
"serviceNotRunning": "Ollama-service draait niet op {{baseUrl}}",
|
||||
"serviceUnavailable": "Ollama-service is niet beschikbaar (status: {{status}})",
|
||||
"modelNotFound": "Ollama-model niet gevonden: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama-model is niet in staat tot insluiten: {{modelId}}",
|
||||
"hostNotFound": "Ollama-host niet gevonden: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Onbekende fout bij verwerken van bestand {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Authenticatie mislukt. Controleer je API-sleutel in de instellingen.",
|
||||
"connectionFailed": "Verbinding met de embedder-service mislukt. Controleer je verbindingsinstellingen en zorg ervoor dat de service draait.",
|
||||
"modelNotAvailable": "Het opgegeven model is niet beschikbaar. Controleer je modelconfiguratie.",
|
||||
"configurationError": "Ongeldige embedder-configuratie. Controleer je instellingen.",
|
||||
"serviceUnavailable": "De embedder-service is niet beschikbaar. Zorg ervoor dat deze draait en toegankelijk is.",
|
||||
"invalidEndpoint": "Ongeldig API-eindpunt. Controleer je URL-configuratie.",
|
||||
"invalidEmbedderConfig": "Ongeldige embedder-configuratie. Controleer je instellingen.",
|
||||
"invalidApiKey": "Ongeldige API-sleutel. Controleer je API-sleutelconfiguratie.",
|
||||
"invalidBaseUrl": "Ongeldige basis-URL. Controleer je URL-configuratie.",
|
||||
"invalidModel": "Ongeldig model. Controleer je modelconfiguratie.",
|
||||
"invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Czy na pewno chcesz usunąć ten profil konfiguracyjny?",
|
||||
"delete_custom_mode_with_rules": "Czy na pewno chcesz usunąć ten tryb {scope}?\n\nSpowoduje to również usunięcie powiązanego folderu reguł pod adresem:\n{rulesFolderPath}",
|
||||
"delete_message": "Co chcesz usunąć?",
|
||||
"just_this_message": "Tylko tę wiadomość",
|
||||
"this_and_subsequent": "Tę i wszystkie kolejne wiadomości"
|
||||
"delete_just_this_message": "Tylko tę wiadomość",
|
||||
"delete_this_and_subsequent": "Tę i wszystkie kolejne wiadomości",
|
||||
"edit_message": "Usunąć wszystkie wiadomości po tej?",
|
||||
"edit_just_this_message": "Nie, tylko edytuj tę wiadomość",
|
||||
"edit_this_and_delete_subsequent": "Tak"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Nieprawidłowy format URI danych",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Usuń",
|
||||
"keep": "Zachowaj"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Zapisz",
|
||||
"cancel": "Anuluj",
|
||||
"edit": "Edytuj"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Błąd zadania: Zostało zatrzymane i anulowane przez użytkownika.",
|
||||
"deleted": "Niepowodzenie zadania: Zostało zatrzymane i usunięte przez użytkownika.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Nie można odczytać treści błędu",
|
||||
"requestFailed": "Żądanie API Ollama nie powiodło się ze statusem {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Nieprawidłowa struktura odpowiedzi z API Ollama: tablica \"embeddings\" nie została znaleziona lub nie jest tablicą.",
|
||||
"embeddingFailed": "Osadzenie Ollama nie powiodło się: {{message}}"
|
||||
"embeddingFailed": "Osadzenie Ollama nie powiodło się: {{message}}",
|
||||
"serviceNotRunning": "Usługa Ollama nie jest uruchomiona pod adresem {{baseUrl}}",
|
||||
"serviceUnavailable": "Usługa Ollama jest niedostępna (status: {{status}})",
|
||||
"modelNotFound": "Nie znaleziono modelu Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Model Ollama nie jest zdolny do osadzania: {{modelId}}",
|
||||
"hostNotFound": "Nie znaleziono hosta Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Nieznany błąd podczas przetwarzania pliku {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Uwierzytelnianie nie powiodło się. Sprawdź swój klucz API w ustawieniach.",
|
||||
"connectionFailed": "Nie udało się połączyć z usługą embeddera. Sprawdź ustawienia połączenia i upewnij się, że usługa jest uruchomiona.",
|
||||
"modelNotAvailable": "Określony model jest niedostępny. Sprawdź konfigurację modelu.",
|
||||
"configurationError": "Nieprawidłowa konfiguracja embeddera. Sprawdź swoje ustawienia.",
|
||||
"serviceUnavailable": "Usługa embeddera jest niedostępna. Upewnij się, że jest uruchomiona i dostępna.",
|
||||
"invalidEndpoint": "Nieprawidłowy punkt końcowy API. Sprawdź konfigurację adresu URL.",
|
||||
"invalidEmbedderConfig": "Nieprawidłowa konfiguracja embeddera. Sprawdź swoje ustawienia.",
|
||||
"invalidApiKey": "Nieprawidłowy klucz API. Sprawdź konfigurację klucza API.",
|
||||
"invalidBaseUrl": "Nieprawidłowy podstawowy adres URL. Sprawdź konfigurację adresu URL.",
|
||||
"invalidModel": "Nieprawidłowy model. Sprawdź konfigurację modelu.",
|
||||
"invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,11 @@
|
|||
"delete_config_profile": "Tem certeza de que deseja excluir este perfil de configuração?",
|
||||
"delete_custom_mode_with_rules": "Tem certeza de que deseja excluir este modo {scope}?\n\nIsso também excluirá a pasta de regras associada em:\n{rulesFolderPath}",
|
||||
"delete_message": "O que você gostaria de excluir?",
|
||||
"just_this_message": "Apenas esta mensagem",
|
||||
"this_and_subsequent": "Esta e todas as mensagens subsequentes"
|
||||
"delete_just_this_message": "Apenas esta mensagem",
|
||||
"delete_this_and_subsequent": "Esta e todas as mensagens subsequentes",
|
||||
"edit_message": "Excluir todas as mensagens após esta?",
|
||||
"edit_just_this_message": "Não, apenas editar esta",
|
||||
"edit_this_and_delete_subsequent": "Sim"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Formato de URI de dados inválido",
|
||||
|
|
@ -112,6 +115,11 @@
|
|||
"remove": "Remover",
|
||||
"keep": "Manter"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"edit": "Editar"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Erro na tarefa: Foi interrompida e cancelada pelo usuário.",
|
||||
"deleted": "Falha na tarefa: Foi interrompida e excluída pelo usuário.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Não foi possível ler o corpo do erro",
|
||||
"requestFailed": "Solicitação da API Ollama falhou com status {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Estrutura de resposta inválida da API Ollama: array \"embeddings\" não encontrado ou não é um array.",
|
||||
"embeddingFailed": "Embedding Ollama falhou: {{message}}"
|
||||
"embeddingFailed": "Embedding Ollama falhou: {{message}}",
|
||||
"serviceNotRunning": "O serviço Ollama não está em execução em {{baseUrl}}",
|
||||
"serviceUnavailable": "O serviço Ollama não está disponível (status: {{status}})",
|
||||
"modelNotFound": "Modelo Ollama não encontrado: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "O modelo Ollama não é capaz de embedding: {{modelId}}",
|
||||
"hostNotFound": "Host Ollama não encontrado: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Erro desconhecido ao processar arquivo {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Falha na autenticação. Verifique sua chave de API nas configurações.",
|
||||
"connectionFailed": "Falha ao conectar ao serviço do embedder. Verifique suas configurações de conexão e garanta que o serviço esteja em execução.",
|
||||
"modelNotAvailable": "O modelo especificado não está disponível. Verifique a configuração do seu modelo.",
|
||||
"configurationError": "Configuração do embedder inválida. Revise suas configurações.",
|
||||
"serviceUnavailable": "O serviço do embedder não está disponível. Garanta que ele esteja em execução e acessível.",
|
||||
"invalidEndpoint": "Endpoint de API inválido. Verifique sua configuração de URL.",
|
||||
"invalidEmbedderConfig": "Configuração do embedder inválida. Verifique suas configurações.",
|
||||
"invalidApiKey": "Chave de API inválida. Verifique sua configuração de chave de API.",
|
||||
"invalidBaseUrl": "URL base inválida. Verifique sua configuração de URL.",
|
||||
"invalidModel": "Modelo inválido. Verifique a configuração do seu modelo.",
|
||||
"invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Вы уверены, что хотите удалить этот профиль конфигурации?",
|
||||
"delete_custom_mode_with_rules": "Вы уверены, что хотите удалить этот режим {scope}?\n\nЭто также приведет к удалению соответствующей папки правил по адресу:\n{rulesFolderPath}",
|
||||
"delete_message": "Что вы хотите удалить?",
|
||||
"just_this_message": "Только это сообщение",
|
||||
"this_and_subsequent": "Это и все последующие сообщения"
|
||||
"delete_just_this_message": "Только это сообщение",
|
||||
"delete_this_and_subsequent": "Это и все последующие сообщения",
|
||||
"edit_message": "Удалить все сообщения после этого?",
|
||||
"edit_just_this_message": "Нет, только редактировать это",
|
||||
"edit_this_and_delete_subsequent": "Да"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Неверный формат URI данных",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Удалить",
|
||||
"keep": "Оставить"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"edit": "Редактировать"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Ошибка задачи: Она была остановлена и отменена пользователем.",
|
||||
"deleted": "Сбой задачи: Она была остановлена и удалена пользователем.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Не удалось прочитать тело ошибки",
|
||||
"requestFailed": "Запрос к API Ollama не удался со статусом {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Неверная структура ответа от API Ollama: массив \"embeddings\" не найден или не является массивом.",
|
||||
"embeddingFailed": "Вложение Ollama не удалось: {{message}}"
|
||||
"embeddingFailed": "Вложение Ollama не удалось: {{message}}",
|
||||
"serviceNotRunning": "Сервис Ollama не запущен по адресу {{baseUrl}}",
|
||||
"serviceUnavailable": "Сервис Ollama недоступен (статус: {{status}})",
|
||||
"modelNotFound": "Модель Ollama не найдена: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Модель Ollama не способна к вложению: {{modelId}}",
|
||||
"hostNotFound": "Хост Ollama не найден: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Неизвестная ошибка при обработке файла {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Ошибка аутентификации. Проверьте свой ключ API в настройках.",
|
||||
"connectionFailed": "Не удалось подключиться к службе эмбеддера. Проверьте настройки подключения и убедитесь, что служба запущена.",
|
||||
"modelNotAvailable": "Указанная модель недоступна. Проверьте конфигурацию модели.",
|
||||
"configurationError": "Неверная конфигурация эмбеддера. Проверьте свои настройки.",
|
||||
"serviceUnavailable": "Служба эмбеддера недоступна. Убедитесь, что она запущена и доступна.",
|
||||
"invalidEndpoint": "Неверная конечная точка API. Проверьте конфигурацию URL.",
|
||||
"invalidEmbedderConfig": "Неверная конфигурация эмбеддера. Проверьте свои настройки.",
|
||||
"invalidApiKey": "Неверный ключ API. Проверьте конфигурацию ключа API.",
|
||||
"invalidBaseUrl": "Неверный базовый URL. Проверьте конфигурацию URL.",
|
||||
"invalidModel": "Неверная модель. Проверьте конфигурацию модели.",
|
||||
"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Bu yapılandırma profilini silmek istediğinizden emin misiniz?",
|
||||
"delete_custom_mode_with_rules": "Bu {scope} modunu silmek istediğinizden emin misiniz?\n\nBu işlem, ilişkili kurallar klasörünü de şu konumdan silecektir:\n{rulesFolderPath}",
|
||||
"delete_message": "Neyi silmek istersiniz?",
|
||||
"just_this_message": "Sadece bu mesajı",
|
||||
"this_and_subsequent": "Bu ve sonraki tüm mesajları"
|
||||
"delete_just_this_message": "Sadece bu mesajı",
|
||||
"delete_this_and_subsequent": "Bu ve sonraki tüm mesajları",
|
||||
"edit_message": "Bu mesajdan sonraki tüm mesajlar silinsin mi?",
|
||||
"edit_just_this_message": "Hayır, sadece bunu düzenle",
|
||||
"edit_this_and_delete_subsequent": "Evet"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Geçersiz veri URI formatı",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Kaldır",
|
||||
"keep": "Koru"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Kaydet",
|
||||
"cancel": "İptal",
|
||||
"edit": "Düzenle"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Görev hatası: Kullanıcı tarafından durduruldu ve iptal edildi.",
|
||||
"deleted": "Görev başarısız: Kullanıcı tarafından durduruldu ve silindi.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Hata gövdesi okunamadı",
|
||||
"requestFailed": "Ollama API isteği {{status}} {{statusText}} durumuyla başarısız oldu: {{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API'den geçersiz yanıt yapısı: \"embeddings\" dizisi bulunamadı veya dizi değil.",
|
||||
"embeddingFailed": "Ollama gömülmesi başarısız oldu: {{message}}"
|
||||
"embeddingFailed": "Ollama gömülmesi başarısız oldu: {{message}}",
|
||||
"serviceNotRunning": "Ollama hizmeti {{baseUrl}} adresinde çalışmıyor",
|
||||
"serviceUnavailable": "Ollama hizmeti kullanılamıyor (durum: {{status}})",
|
||||
"modelNotFound": "Ollama modeli bulunamadı: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama modeli gömme yeteneğine sahip değil: {{modelId}}",
|
||||
"hostNotFound": "Ollama ana bilgisayarı bulunamadı: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "{{filePath}} dosyası işlenirken bilinmeyen hata",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Kimlik doğrulama başarısız oldu. Lütfen ayarlardan API anahtarınızı kontrol edin.",
|
||||
"connectionFailed": "Gömücü hizmetine bağlanılamadı. Lütfen bağlantı ayarlarınızı kontrol edin ve hizmetin çalıştığından emin olun.",
|
||||
"modelNotAvailable": "Belirtilen model mevcut değil. Lütfen model yapılandırmanızı kontrol edin.",
|
||||
"configurationError": "Geçersiz gömücü yapılandırması. Lütfen ayarlarınızı gözden geçirin.",
|
||||
"serviceUnavailable": "Gömücü hizmeti mevcut değil. Lütfen çalıştığından ve erişilebilir olduğundan emin olun.",
|
||||
"invalidEndpoint": "Geçersiz API uç noktası. Lütfen URL yapılandırmanızı kontrol edin.",
|
||||
"invalidEmbedderConfig": "Geçersiz gömücü yapılandırması. Lütfen ayarlarınızı kontrol edin.",
|
||||
"invalidApiKey": "Geçersiz API anahtarı. Lütfen API anahtarı yapılandırmanızı kontrol edin.",
|
||||
"invalidBaseUrl": "Geçersiz temel URL. Lütfen URL yapılandırmanızı kontrol edin.",
|
||||
"invalidModel": "Geçersiz model. Lütfen model yapılandırmanızı kontrol edin.",
|
||||
"invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "Bạn có chắc chắn muốn xóa hồ sơ cấu hình này không?",
|
||||
"delete_custom_mode_with_rules": "Bạn có chắc chắn muốn xóa chế độ {scope} này không?\n\nThao tác này cũng sẽ xóa thư mục quy tắc liên quan tại:\n{rulesFolderPath}",
|
||||
"delete_message": "Bạn muốn xóa gì?",
|
||||
"just_this_message": "Chỉ tin nhắn này",
|
||||
"this_and_subsequent": "Tin nhắn này và tất cả tin nhắn tiếp theo"
|
||||
"delete_just_this_message": "Chỉ tin nhắn này",
|
||||
"delete_this_and_subsequent": "Tin nhắn này và tất cả tin nhắn tiếp theo",
|
||||
"edit_message": "Xóa tất cả tin nhắn sau tin nhắn này?",
|
||||
"edit_just_this_message": "Không, chỉ chỉnh sửa tin nhắn này",
|
||||
"edit_this_and_delete_subsequent": "Có"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Định dạng URI dữ liệu không hợp lệ",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "Xóa",
|
||||
"keep": "Giữ"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Lưu",
|
||||
"cancel": "Hủy",
|
||||
"edit": "Chỉnh sửa"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "Lỗi nhiệm vụ: Nó đã bị dừng và hủy bởi người dùng.",
|
||||
"deleted": "Nhiệm vụ thất bại: Nó đã bị dừng và xóa bởi người dùng.",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "Không thể đọc nội dung lỗi",
|
||||
"requestFailed": "Yêu cầu API Ollama thất bại với trạng thái {{status}} {{statusText}}: {{errorBody}}",
|
||||
"invalidResponseStructure": "Cấu trúc phản hồi không hợp lệ từ API Ollama: không tìm thấy mảng \"embeddings\" hoặc không phải là mảng.",
|
||||
"embeddingFailed": "Nhúng Ollama thất bại: {{message}}"
|
||||
"embeddingFailed": "Nhúng Ollama thất bại: {{message}}",
|
||||
"serviceNotRunning": "Dịch vụ Ollama không chạy tại {{baseUrl}}",
|
||||
"serviceUnavailable": "Dịch vụ Ollama không khả dụng (trạng thái: {{status}})",
|
||||
"modelNotFound": "Không tìm thấy mô hình Ollama: {{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Mô hình Ollama không có khả năng nhúng: {{modelId}}",
|
||||
"hostNotFound": "Không tìm thấy máy chủ Ollama: {{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "Lỗi không xác định khi xử lý tệp {{filePath}}",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "Xác thực không thành công. Vui lòng kiểm tra khóa API của bạn trong cài đặt.",
|
||||
"connectionFailed": "Không thể kết nối với dịch vụ nhúng. Vui lòng kiểm tra cài đặt kết nối của bạn và đảm bảo dịch vụ đang chạy.",
|
||||
"modelNotAvailable": "Mô hình được chỉ định không có sẵn. Vui lòng kiểm tra cấu hình mô hình của bạn.",
|
||||
"configurationError": "Cấu hình nhúng không hợp lệ. Vui lòng xem lại cài đặt của bạn.",
|
||||
"serviceUnavailable": "Dịch vụ nhúng không có sẵn. Vui lòng đảm bảo nó đang chạy và có thể truy cập được.",
|
||||
"invalidEndpoint": "Điểm cuối API không hợp lệ. Vui lòng kiểm tra cấu hình URL của bạn.",
|
||||
"invalidEmbedderConfig": "Cấu hình nhúng không hợp lệ. Vui lòng kiểm tra cài đặt của bạn.",
|
||||
"invalidApiKey": "Khóa API không hợp lệ. Vui lòng kiểm tra cấu hình khóa API của bạn.",
|
||||
"invalidBaseUrl": "URL cơ sở không hợp lệ. Vui lòng kiểm tra cấu hình URL của bạn.",
|
||||
"invalidModel": "Mô hình không hợp lệ. Vui lòng kiểm tra cấu hình mô hình của bạn.",
|
||||
"invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "您确定要删除此配置文件吗?",
|
||||
"delete_custom_mode_with_rules": "您确定要删除此 {scope} 模式吗?\n\n这也将删除位于以下位置的关联规则文件夹:\n{rulesFolderPath}",
|
||||
"delete_message": "您想删除什么?",
|
||||
"just_this_message": "仅此消息",
|
||||
"this_and_subsequent": "此消息及所有后续消息"
|
||||
"edit_message": "删除此消息后的所有消息?",
|
||||
"delete_just_this_message": "仅此消息",
|
||||
"edit_just_this_message": "不,仅编辑此消息",
|
||||
"delete_this_and_subsequent": "此消息及所有后续消息",
|
||||
"edit_this_and_delete_subsequent": "是"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_mcp_config": "项目MCP配置格式无效",
|
||||
|
|
@ -113,6 +116,11 @@
|
|||
"remove": "删除",
|
||||
"keep": "保留"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"edit": "编辑"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "任务错误:它已被用户停止并取消。",
|
||||
"deleted": "任务失败:它已被用户停止并删除。",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "无法读取错误内容",
|
||||
"requestFailed": "Ollama API 请求失败,状态码 {{status}} {{statusText}}:{{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API 响应结构无效:未找到 \"embeddings\" 数组或不是数组。",
|
||||
"embeddingFailed": "Ollama 嵌入失败:{{message}}"
|
||||
"embeddingFailed": "Ollama 嵌入失败:{{message}}",
|
||||
"serviceNotRunning": "Ollama 服务未在 {{baseUrl}} 运行",
|
||||
"serviceUnavailable": "Ollama 服务不可用(状态:{{status}})",
|
||||
"modelNotFound": "未找到 Ollama 模型:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 模型不具备嵌入能力:{{modelId}}",
|
||||
"hostNotFound": "未找到 Ollama 主机:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "处理文件 {{filePath}} 时出现未知错误",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "身份验证失败。请在设置中检查您的 API 密钥。",
|
||||
"connectionFailed": "连接嵌入器服务失败。请检查您的连接设置并确保服务正在运行。",
|
||||
"modelNotAvailable": "指定的模型不可用。请检查您的模型配置。",
|
||||
"configurationError": "嵌入器配置无效。请查看您的设置。",
|
||||
"serviceUnavailable": "嵌入器服务不可用。请确保它正在运行且可访问。",
|
||||
"invalidEndpoint": "API 端点无效。请检查您的 URL 配置。",
|
||||
"invalidEmbedderConfig": "嵌入器配置无效。请检查您的设置。",
|
||||
"invalidApiKey": "API 密钥无效。请检查您的 API 密钥配置。",
|
||||
"invalidBaseUrl": "基础 URL 无效。请检查您的 URL 配置。",
|
||||
"invalidModel": "模型无效。请检查您的模型配置。",
|
||||
"invalidResponse": "嵌入服务响应无效。请检查您的配置。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,11 @@
|
|||
"delete_config_profile": "您確定要刪除此設定檔案嗎?",
|
||||
"delete_custom_mode_with_rules": "您確定要刪除此 {scope} 模式嗎?\n\n這也將刪除位於以下位置的關聯規則資料夾:\n{rulesFolderPath}",
|
||||
"delete_message": "您想刪除哪些內容?",
|
||||
"just_this_message": "僅這則訊息",
|
||||
"this_and_subsequent": "這則訊息及所有後續訊息"
|
||||
"edit_message": "刪除此訊息後的所有訊息?",
|
||||
"delete_just_this_message": "僅這則訊息",
|
||||
"edit_just_this_message": "否,僅編輯此訊息",
|
||||
"delete_this_and_subsequent": "這則訊息及所有後續訊息",
|
||||
"edit_this_and_delete_subsequent": "是"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "資料 URI 格式無效",
|
||||
|
|
@ -108,6 +111,11 @@
|
|||
"remove": "刪除",
|
||||
"keep": "保留"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "儲存",
|
||||
"cancel": "取消",
|
||||
"edit": "編輯"
|
||||
},
|
||||
"tasks": {
|
||||
"canceled": "工作錯誤:它已被使用者停止並取消。",
|
||||
"deleted": "工作失敗:它已被使用者停止並刪除。",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@
|
|||
"couldNotReadErrorBody": "無法讀取錯誤內容",
|
||||
"requestFailed": "Ollama API 請求失敗,狀態碼 {{status}} {{statusText}}:{{errorBody}}",
|
||||
"invalidResponseStructure": "Ollama API 回應結構無效:未找到 \"embeddings\" 陣列或不是陣列。",
|
||||
"embeddingFailed": "Ollama 內嵌失敗:{{message}}"
|
||||
"embeddingFailed": "Ollama 內嵌失敗:{{message}}",
|
||||
"serviceNotRunning": "Ollama 服務未在 {{baseUrl}} 執行",
|
||||
"serviceUnavailable": "Ollama 服務不可用(狀態:{{status}})",
|
||||
"modelNotFound": "找不到 Ollama 模型:{{modelId}}",
|
||||
"modelNotEmbeddingCapable": "Ollama 模型不具備內嵌能力:{{modelId}}",
|
||||
"hostNotFound": "找不到 Ollama 主機:{{baseUrl}}"
|
||||
},
|
||||
"scanner": {
|
||||
"unknownErrorProcessingFile": "處理檔案 {{filePath}} 時發生未知錯誤",
|
||||
|
|
@ -19,5 +24,18 @@
|
|||
},
|
||||
"vectorStore": {
|
||||
"qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}"
|
||||
},
|
||||
"validation": {
|
||||
"authenticationFailed": "驗證失敗。請在設定中檢查您的 API 金鑰。",
|
||||
"connectionFailed": "連線至內嵌服務失敗。請檢查您的連線設定並確保服務正在執行。",
|
||||
"modelNotAvailable": "指定的模型不可用。請檢查您的模型組態。",
|
||||
"configurationError": "無效的內嵌程式組態。請檢閱您的設定。",
|
||||
"serviceUnavailable": "內嵌服務不可用。請確保它正在執行且可存取。",
|
||||
"invalidEndpoint": "無效的 API 端點。請檢查您的 URL 組態。",
|
||||
"invalidEmbedderConfig": "無效的內嵌程式組態。請檢查您的設定。",
|
||||
"invalidApiKey": "無效的 API 金鑰。請檢查您的 API 金鑰組態。",
|
||||
"invalidBaseUrl": "無效的基礎 URL。請檢查您的 URL 組態。",
|
||||
"invalidModel": "無效的模型。請檢查您的模型組態。",
|
||||
"invalidResponse": "內嵌服務回應無效。請檢查您的組態。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@
|
|||
"vsix": "mkdirp ../bin && vsce package --no-dependencies --out ../bin",
|
||||
"publish:marketplace": "vsce publish --no-dependencies && ovsx publish --no-dependencies",
|
||||
"watch:bundle": "pnpm bundle --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"watch:tsc": "cd .. && tsc --noEmit --watch --project src/tsconfig.json",
|
||||
"clean": "rimraf README.md CHANGELOG.md LICENSE dist mock .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import { CodeIndexManager } from "../manager"
|
||||
import { CodeIndexServiceFactory } from "../service-factory"
|
||||
import type { MockedClass } from "vitest"
|
||||
|
||||
// Mock vscode module
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
|
|
@ -21,10 +25,12 @@ vi.mock("../state-manager", () => ({
|
|||
onProgressUpdate: vi.fn(),
|
||||
getCurrentStatus: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
setSystemState: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
import { CodeIndexManager } from "../manager"
|
||||
vi.mock("../service-factory")
|
||||
const MockedCodeIndexServiceFactory = CodeIndexServiceFactory as MockedClass<typeof CodeIndexServiceFactory>
|
||||
|
||||
describe("CodeIndexManager - handleSettingsChange regression", () => {
|
||||
let mockContext: any
|
||||
|
|
@ -72,13 +78,63 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
// Mock a minimal config manager that simulates first-time configuration
|
||||
const mockConfigManager = {
|
||||
loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: true }),
|
||||
isFeatureConfigured: true,
|
||||
isFeatureEnabled: true,
|
||||
getConfig: vi.fn().mockReturnValue({
|
||||
isEnabled: true,
|
||||
isConfigured: true,
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: { openAiNativeApiKey: "test-key" },
|
||||
qdrantUrl: "http://localhost:6333",
|
||||
qdrantApiKey: "test-key",
|
||||
searchMinScore: 0.4,
|
||||
}),
|
||||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
|
||||
// Mock cache manager
|
||||
const mockCacheManager = {
|
||||
initialize: vi.fn(),
|
||||
clearCacheFile: vi.fn(),
|
||||
}
|
||||
;(manager as any)._cacheManager = mockCacheManager
|
||||
|
||||
// Mock the feature state to simulate valid configuration that would normally trigger restart
|
||||
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
|
||||
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
|
||||
|
||||
// Mock service factory to handle _recreateServices call
|
||||
const mockServiceFactoryInstance = {
|
||||
configManager: mockConfigManager,
|
||||
workspacePath: "/test/workspace",
|
||||
cacheManager: mockCacheManager,
|
||||
createEmbedder: vi.fn().mockReturnValue({ embedderInfo: { name: "openai" } }),
|
||||
createVectorStore: vi.fn().mockReturnValue({}),
|
||||
createDirectoryScanner: vi.fn().mockReturnValue({}),
|
||||
createFileWatcher: vi.fn().mockReturnValue({
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}),
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: { embedderInfo: { name: "openai" } },
|
||||
vectorStore: {},
|
||||
scanner: {},
|
||||
fileWatcher: {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
}),
|
||||
validateEmbedder: vi.fn().mockResolvedValue({ valid: true }),
|
||||
}
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance as any)
|
||||
|
||||
// The key test: this should NOT throw "CodeIndexManager not initialized" error
|
||||
await expect(manager.handleSettingsChange()).resolves.not.toThrow()
|
||||
|
||||
|
|
@ -105,29 +161,65 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
|
||||
// Mock cache manager
|
||||
const mockCacheManager = {
|
||||
initialize: vi.fn(),
|
||||
clearCacheFile: vi.fn(),
|
||||
}
|
||||
;(manager as any)._cacheManager = mockCacheManager
|
||||
|
||||
// Simulate an initialized manager by setting the required properties
|
||||
;(manager as any)._orchestrator = { stopWatcher: vi.fn() }
|
||||
;(manager as any)._searchService = {}
|
||||
;(manager as any)._cacheManager = {}
|
||||
|
||||
// Verify manager is considered initialized
|
||||
expect(manager.isInitialized).toBe(true)
|
||||
|
||||
// Mock the methods that would be called during restart
|
||||
const recreateServicesSpy = vi.spyOn(manager as any, "_recreateServices").mockImplementation(() => {})
|
||||
const startIndexingSpy = vi.spyOn(manager, "startIndexing").mockResolvedValue()
|
||||
|
||||
// Mock the feature state
|
||||
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
|
||||
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
|
||||
|
||||
// Mock service factory to handle _recreateServices call
|
||||
const mockServiceFactoryInstance = {
|
||||
configManager: mockConfigManager,
|
||||
workspacePath: "/test/workspace",
|
||||
cacheManager: mockCacheManager,
|
||||
createEmbedder: vi.fn().mockReturnValue({ embedderInfo: { name: "openai" } }),
|
||||
createVectorStore: vi.fn().mockReturnValue({}),
|
||||
createDirectoryScanner: vi.fn().mockReturnValue({}),
|
||||
createFileWatcher: vi.fn().mockReturnValue({
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}),
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: { embedderInfo: { name: "openai" } },
|
||||
vectorStore: {},
|
||||
scanner: {},
|
||||
fileWatcher: {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
}),
|
||||
validateEmbedder: vi.fn().mockResolvedValue({ valid: true }),
|
||||
}
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance as any)
|
||||
|
||||
// Mock the methods that would be called during restart
|
||||
const recreateServicesSpy = vi.spyOn(manager as any, "_recreateServices")
|
||||
|
||||
await manager.handleSettingsChange()
|
||||
|
||||
// Verify that the restart sequence was called
|
||||
expect(mockConfigManager.loadConfiguration).toHaveBeenCalled()
|
||||
// stopWatcher is called inside _recreateServices, which we mocked
|
||||
// _recreateServices should be called when requiresRestart is true
|
||||
expect(recreateServicesSpy).toHaveBeenCalled()
|
||||
expect(startIndexingSpy).toHaveBeenCalled()
|
||||
// Note: startIndexing is NOT called by handleSettingsChange - it's only called by initialize()
|
||||
})
|
||||
|
||||
it("should handle case when config manager is not set", async () => {
|
||||
|
|
@ -138,4 +230,135 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
|
|||
await expect(manager.handleSettingsChange()).resolves.not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("embedder validation integration", () => {
|
||||
let mockServiceFactoryInstance: any
|
||||
let mockStateManager: any
|
||||
let mockEmbedder: any
|
||||
let mockVectorStore: any
|
||||
let mockScanner: any
|
||||
let mockFileWatcher: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock service factory objects
|
||||
mockEmbedder = { embedderInfo: { name: "openai" } }
|
||||
mockVectorStore = {}
|
||||
mockScanner = {}
|
||||
mockFileWatcher = {
|
||||
onDidStartBatchProcessing: vi.fn(),
|
||||
onBatchProgressUpdate: vi.fn(),
|
||||
watch: vi.fn(),
|
||||
stopWatcher: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
}
|
||||
|
||||
// Mock service factory instance
|
||||
mockServiceFactoryInstance = {
|
||||
createServices: vi.fn().mockReturnValue({
|
||||
embedder: mockEmbedder,
|
||||
vectorStore: mockVectorStore,
|
||||
scanner: mockScanner,
|
||||
fileWatcher: mockFileWatcher,
|
||||
}),
|
||||
validateEmbedder: vi.fn(),
|
||||
}
|
||||
|
||||
// Mock the ServiceFactory constructor
|
||||
MockedCodeIndexServiceFactory.mockImplementation(() => mockServiceFactoryInstance)
|
||||
|
||||
// Mock state manager methods directly on the existing instance
|
||||
mockStateManager = (manager as any)._stateManager
|
||||
mockStateManager.setSystemState = vi.fn()
|
||||
|
||||
// Mock config manager
|
||||
const mockConfigManager = {
|
||||
loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: false }),
|
||||
isFeatureConfigured: true,
|
||||
isFeatureEnabled: true,
|
||||
getConfig: vitest.fn().mockReturnValue({
|
||||
isEnabled: true,
|
||||
isConfigured: true,
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: { openAiNativeApiKey: "test-key" },
|
||||
qdrantUrl: "http://localhost:6333",
|
||||
qdrantApiKey: "test-key",
|
||||
searchMinScore: 0.4,
|
||||
}),
|
||||
}
|
||||
;(manager as any)._configManager = mockConfigManager
|
||||
})
|
||||
|
||||
it("should validate embedder during _recreateServices when validation succeeds", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act - directly call the private method for testing
|
||||
await (manager as any)._recreateServices()
|
||||
|
||||
// Assert
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Error", expect.any(String))
|
||||
})
|
||||
|
||||
it("should set error state when embedder validation fails", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act & Assert
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow(
|
||||
"embeddings:validation.authenticationFailed",
|
||||
)
|
||||
|
||||
// Assert other expectations
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).toHaveBeenCalledWith(
|
||||
"Error",
|
||||
"embeddings:validation.authenticationFailed",
|
||||
)
|
||||
})
|
||||
|
||||
it("should set generic error state when embedder validation throws", async () => {
|
||||
// Arrange
|
||||
// Since the real service factory catches exceptions, we should mock it to resolve with an error
|
||||
mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.configurationError",
|
||||
})
|
||||
|
||||
// Act & Assert
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow(
|
||||
"embeddings:validation.configurationError",
|
||||
)
|
||||
|
||||
// Assert other expectations
|
||||
expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled()
|
||||
const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder)
|
||||
expect(mockStateManager.setSystemState).toHaveBeenCalledWith(
|
||||
"Error",
|
||||
"embeddings:validation.configurationError",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle embedder creation failure", async () => {
|
||||
// Arrange
|
||||
mockServiceFactoryInstance.createServices.mockImplementation(() => {
|
||||
throw new Error("Invalid configuration")
|
||||
})
|
||||
|
||||
// Act & Assert - should throw the error
|
||||
await expect((manager as any)._recreateServices()).rejects.toThrow("Invalid configuration")
|
||||
|
||||
// Should not attempt validation if embedder creation fails
|
||||
expect(mockServiceFactoryInstance.validateEmbedder).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -580,4 +580,187 @@ describe("CodeIndexServiceFactory", () => {
|
|||
expect(() => factory.createVectorStore()).toThrow("Qdrant URL missing for vector store creation")
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateEmbedder", () => {
|
||||
let mockEmbedderInstance: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockEmbedderInstance = {
|
||||
validateConfiguration: vitest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
it("should validate OpenAI embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return validation error from OpenAI embedder", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "invalid-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate Ollama embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "ollama",
|
||||
modelId: "nomic-embed-text",
|
||||
ollamaOptions: {
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedCodeIndexOllamaEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should validate OpenAI Compatible embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai-compatible",
|
||||
modelId: "custom-model",
|
||||
openAiCompatibleOptions: {
|
||||
baseUrl: "https://api.example.com/v1",
|
||||
apiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAICompatibleEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should validate Gemini embedder successfully", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "gemini",
|
||||
geminiOptions: {
|
||||
apiKey: "test-gemini-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedGeminiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
mockEmbedderInstance.validateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ valid: true })
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle validation exceptions", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
MockedOpenAiEmbedder.mockImplementation(() => mockEmbedderInstance)
|
||||
const networkError = new Error("Network error")
|
||||
mockEmbedderInstance.validateConfiguration.mockRejectedValue(networkError)
|
||||
|
||||
// Act
|
||||
const embedder = factory.createEmbedder()
|
||||
const result = await factory.validateEmbedder(embedder)
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "Network error",
|
||||
})
|
||||
expect(mockEmbedderInstance.validateConfiguration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return error for invalid embedder configuration", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "openai",
|
||||
modelId: "text-embedding-3-small",
|
||||
openAiOptions: {
|
||||
openAiNativeApiKey: undefined, // Missing API key
|
||||
},
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
|
||||
// Act & Assert
|
||||
// This should throw when trying to create the embedder
|
||||
await expect(async () => {
|
||||
const embedder = factory.createEmbedder()
|
||||
await factory.validateEmbedder(embedder)
|
||||
}).rejects.toThrow("OpenAI configuration missing for embedder creation")
|
||||
})
|
||||
|
||||
it("should return error for unknown embedder provider", async () => {
|
||||
// Arrange
|
||||
const testConfig = {
|
||||
embedderProvider: "unknown-provider",
|
||||
modelId: "some-model",
|
||||
}
|
||||
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
|
||||
|
||||
// Act & Assert
|
||||
// This should throw when trying to create the embedder
|
||||
expect(() => factory.createEmbedder()).toThrow("Invalid embedder type configured: unknown-provider")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -55,4 +55,54 @@ describe("GeminiEmbedder", () => {
|
|||
expect(GeminiEmbedder.dimension).toBe(768)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
let mockValidateConfiguration: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockValidateConfiguration = vitest.fn()
|
||||
MockedOpenAICompatibleEmbedder.prototype.validateConfiguration = mockValidateConfiguration
|
||||
})
|
||||
|
||||
it("should delegate validation to OpenAICompatibleEmbedder", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockResolvedValue({ valid: true })
|
||||
|
||||
// Act
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
// Assert
|
||||
expect(mockValidateConfiguration).toHaveBeenCalled()
|
||||
expect(result).toEqual({ valid: true })
|
||||
})
|
||||
|
||||
it("should pass through validation errors from OpenAICompatibleEmbedder", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockResolvedValue({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
|
||||
// Act
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
// Assert
|
||||
expect(mockValidateConfiguration).toHaveBeenCalled()
|
||||
expect(result).toEqual({
|
||||
valid: false,
|
||||
error: "embeddings:validation.authenticationFailed",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle validation exceptions", async () => {
|
||||
// Arrange
|
||||
embedder = new GeminiEmbedder("test-api-key")
|
||||
mockValidateConfiguration.mockRejectedValue(new Error("Validation failed"))
|
||||
|
||||
// Act & Assert
|
||||
await expect(embedder.validateConfiguration()).rejects.toThrow("Validation failed")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
238
src/services/code-index/embedders/__tests__/ollama.spec.ts
Normal file
238
src/services/code-index/embedders/__tests__/ollama.spec.ts
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import { vitest, describe, it, expect, beforeEach, afterEach } from "vitest"
|
||||
import type { MockedFunction } from "vitest"
|
||||
import { CodeIndexOllamaEmbedder } from "../ollama"
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vitest.fn() as MockedFunction<typeof fetch>
|
||||
|
||||
// Mock i18n
|
||||
vitest.mock("../../../../i18n", () => ({
|
||||
t: (key: string, params?: Record<string, any>) => {
|
||||
const translations: Record<string, string> = {
|
||||
"embeddings:validation.serviceUnavailable":
|
||||
"The embedder service is not available. Please ensure it is running and accessible.",
|
||||
"embeddings:validation.modelNotAvailable":
|
||||
"The specified model is not available. Please check your model configuration.",
|
||||
"embeddings:validation.connectionFailed":
|
||||
"Failed to connect to the embedder service. Please check your connection settings and ensure the service is running.",
|
||||
"embeddings:validation.configurationError": "Invalid embedder configuration. Please review your settings.",
|
||||
"embeddings:errors.ollama.serviceNotRunning":
|
||||
"Ollama service is not running at {{baseUrl}}. Please start Ollama first.",
|
||||
"embeddings:errors.ollama.serviceUnavailable":
|
||||
"Ollama service is unavailable at {{baseUrl}}. HTTP status: {{status}}",
|
||||
"embeddings:errors.ollama.modelNotFound":
|
||||
"Model '{{model}}' not found. Available models: {{availableModels}}",
|
||||
"embeddings:errors.ollama.modelNotEmbedding": "Model '{{model}}' is not embedding capable",
|
||||
"embeddings:errors.ollama.hostNotFound": "Ollama host not found: {{baseUrl}}",
|
||||
"embeddings:errors.ollama.connectionTimeout": "Connection to Ollama timed out at {{baseUrl}}",
|
||||
}
|
||||
// Handle parameter substitution
|
||||
let result = translations[key] || key
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([param, value]) => {
|
||||
result = result.replace(new RegExp(`{{${param}}}`, "g"), String(value))
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock console methods
|
||||
const consoleMocks = {
|
||||
error: vitest.spyOn(console, "error").mockImplementation(() => {}),
|
||||
}
|
||||
|
||||
describe("CodeIndexOllamaEmbedder", () => {
|
||||
let embedder: CodeIndexOllamaEmbedder
|
||||
let mockFetch: MockedFunction<typeof fetch>
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
consoleMocks.error.mockClear()
|
||||
|
||||
mockFetch = global.fetch as MockedFunction<typeof fetch>
|
||||
|
||||
embedder = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
it("should initialize with provided options", () => {
|
||||
expect(embedder.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
|
||||
it("should use default values when not provided", () => {
|
||||
const embedderWithDefaults = new CodeIndexOllamaEmbedder({})
|
||||
expect(embedderWithDefaults.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
it("should validate successfully when service is available and model exists", async () => {
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text:latest" }, { name: "llama2:latest" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock successful /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
|
||||
// Check first call (GET /api/tags)
|
||||
const firstCall = mockFetch.mock.calls[0]
|
||||
expect(firstCall[0]).toBe("http://localhost:11434/api/tags")
|
||||
expect(firstCall[1]?.method).toBe("GET")
|
||||
expect(firstCall[1]?.headers).toEqual({ "Content-Type": "application/json" })
|
||||
expect(firstCall[1]?.signal).toBeDefined() // AbortSignal for timeout
|
||||
|
||||
// Check second call (POST /api/embed)
|
||||
const secondCall = mockFetch.mock.calls[1]
|
||||
expect(secondCall[0]).toBe("http://localhost:11434/api/embed")
|
||||
expect(secondCall[1]?.method).toBe("POST")
|
||||
expect(secondCall[1]?.headers).toEqual({ "Content-Type": "application/json" })
|
||||
expect(secondCall[1]?.body).toBe(JSON.stringify({ model: "nomic-embed-text", input: ["test"] }))
|
||||
expect(secondCall[1]?.signal).toBeDefined() // AbortSignal for timeout
|
||||
})
|
||||
|
||||
it("should fail validation when service is not available", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should fail validation when tags endpoint returns 404", async () => {
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 404,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
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.",
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation when tags endpoint returns other error", async () => {
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 500,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
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")
|
||||
})
|
||||
|
||||
it("should fail validation when model does not exist", async () => {
|
||||
// Mock successful /api/tags call with different models
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "llama2:latest" }, { name: "mistral:latest" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation when model exists but doesn't support embeddings", async () => {
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock failed /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 400,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Model 'nomic-embed-text' is not embedding capable")
|
||||
})
|
||||
|
||||
it("should handle ECONNREFUSED errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Connection to Ollama timed out at http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should handle ENOTFOUND errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("ENOTFOUND"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Ollama host not found: http://localhost:11434")
|
||||
})
|
||||
|
||||
it("should handle generic network errors", async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("Network timeout"))
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Network timeout")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -882,4 +882,133 @@ describe("OpenAICompatibleEmbedder", () => {
|
|||
expect(mockCreate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
let embedder: OpenAICompatibleEmbedder
|
||||
let mockFetch: MockedFunction<typeof fetch>
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
// Reset and re-assign the global fetch mock
|
||||
global.fetch = vitest.fn()
|
||||
mockFetch = global.fetch as MockedFunction<typeof fetch>
|
||||
})
|
||||
|
||||
it("should validate successfully with valid configuration and base URL", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const mockResponse = {
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}
|
||||
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: testModelId,
|
||||
encoding_format: "base64",
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate successfully with full endpoint URL", async () => {
|
||||
const fullUrl = "https://api.example.com/v1/embeddings"
|
||||
embedder = new OpenAICompatibleEmbedder(fullUrl, testApiKey, testModelId)
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}),
|
||||
text: async () => "",
|
||||
} as any)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
fullUrl,
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
Authorization: `Bearer ${testApiKey}`,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail validation with authentication error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const authError = new Error("Invalid API key")
|
||||
;(authError as any).status = 401
|
||||
mockEmbeddingsCreate.mockRejectedValue(authError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.authenticationFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with connection error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const connectionError = new Error("ECONNREFUSED")
|
||||
mockEmbeddingsCreate.mockRejectedValue(connectionError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.connectionFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with invalid endpoint for full URL", async () => {
|
||||
const fullUrl = "https://api.example.com/v1/embeddings"
|
||||
embedder = new OpenAICompatibleEmbedder(fullUrl, testApiKey, testModelId)
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({ error: "Not found" }),
|
||||
text: async () => "Not found",
|
||||
} as any)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.invalidEndpoint")
|
||||
})
|
||||
|
||||
it("should fail validation with rate limit error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const rateLimitError = new Error("Rate limit exceeded")
|
||||
;(rateLimitError as any).status = 429
|
||||
mockEmbeddingsCreate.mockRejectedValue(rateLimitError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.serviceUnavailable")
|
||||
})
|
||||
|
||||
it("should fail validation with generic error", async () => {
|
||||
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
|
||||
|
||||
const genericError = new Error("Unknown error")
|
||||
;(genericError as any).status = 500
|
||||
mockEmbeddingsCreate.mockRejectedValue(genericError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.configurationError")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -464,4 +464,66 @@ describe("OpenAiEmbedder", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
it("should validate successfully with valid configuration", async () => {
|
||||
const mockResponse = {
|
||||
data: [{ embedding: [0.1, 0.2, 0.3] }],
|
||||
usage: { prompt_tokens: 2, total_tokens: 2 },
|
||||
}
|
||||
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: "text-embedding-3-small",
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail validation with authentication error", async () => {
|
||||
const authError = new Error("Invalid API key")
|
||||
;(authError as any).status = 401
|
||||
mockEmbeddingsCreate.mockRejectedValue(authError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.authenticationFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with rate limit error", async () => {
|
||||
const rateLimitError = new Error("Rate limit exceeded")
|
||||
;(rateLimitError as any).status = 429
|
||||
mockEmbeddingsCreate.mockRejectedValue(rateLimitError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.serviceUnavailable")
|
||||
})
|
||||
|
||||
it("should fail validation with connection error", async () => {
|
||||
const connectionError = new Error("ECONNREFUSED")
|
||||
mockEmbeddingsCreate.mockRejectedValue(connectionError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.connectionFailed")
|
||||
})
|
||||
|
||||
it("should fail validation with generic error", async () => {
|
||||
const genericError = new Error("Unknown error")
|
||||
;(genericError as any).status = 500
|
||||
mockEmbeddingsCreate.mockRejectedValue(genericError)
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:validation.configurationError")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ export class GeminiEmbedder implements IEmbedder {
|
|||
return this.openAICompatibleEmbedder.createEmbeddings(texts, GeminiEmbedder.GEMINI_MODEL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the Gemini embedder configuration by delegating to the underlying OpenAI-compatible embedder
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
// Delegate validation to the OpenAI-compatible embedder
|
||||
// The error messages will be specific to Gemini since we're using Gemini's base URL
|
||||
return this.openAICompatibleEmbedder.validateConfiguration()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this embedder
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { EmbedderInfo, EmbeddingResponse, IEmbedder } from "../interfaces"
|
|||
import { getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { MAX_ITEM_TOKENS } from "../constants"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling } from "../shared/validation-helpers"
|
||||
|
||||
/**
|
||||
* Implements the IEmbedder interface using a local Ollama instance.
|
||||
|
|
@ -101,6 +102,127 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the Ollama embedder configuration by checking service availability and model existence
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(
|
||||
async () => {
|
||||
// First check if Ollama service is running by trying to list models
|
||||
const modelsUrl = `${this.baseUrl}/api/tags`
|
||||
|
||||
// Add timeout to prevent indefinite hanging
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
|
||||
|
||||
const modelsResponse = await fetch(modelsUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!modelsResponse.ok) {
|
||||
if (modelsResponse.status === 404) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.serviceUnavailable", {
|
||||
baseUrl: this.baseUrl,
|
||||
status: modelsResponse.status,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the specific model exists
|
||||
const modelsData = await modelsResponse.json()
|
||||
const models = modelsData.models || []
|
||||
|
||||
// Check both with and without :latest suffix
|
||||
const modelExists = models.some((m: any) => {
|
||||
const modelName = m.name || ""
|
||||
return (
|
||||
modelName === this.defaultModelId ||
|
||||
modelName === `${this.defaultModelId}:latest` ||
|
||||
modelName === this.defaultModelId.replace(":latest", "")
|
||||
)
|
||||
})
|
||||
|
||||
if (!modelExists) {
|
||||
const availableModels = models.map((m: any) => m.name).join(", ")
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.modelNotFound", {
|
||||
model: this.defaultModelId,
|
||||
availableModels,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Try a test embedding to ensure the model works for embeddings
|
||||
const testUrl = `${this.baseUrl}/api/embed`
|
||||
|
||||
// Add timeout for test request too
|
||||
const testController = new AbortController()
|
||||
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
|
||||
|
||||
const testResponse = await fetch(testUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.defaultModelId,
|
||||
input: ["test"],
|
||||
}),
|
||||
signal: testController.signal,
|
||||
})
|
||||
clearTimeout(testTimeoutId)
|
||||
|
||||
if (!testResponse.ok) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.modelNotEmbedding", { model: this.defaultModelId }),
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
},
|
||||
"ollama",
|
||||
{
|
||||
beforeStandardHandling: (error: any) => {
|
||||
// Handle Ollama-specific connection errors
|
||||
if (error?.message === "ECONNREFUSED") {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
} else if (error?.message === "ENOTFOUND") {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.hostNotFound", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
} else if (error?.name === "AbortError") {
|
||||
// Handle timeout
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:errors.ollama.connectionTimeout", { baseUrl: this.baseUrl }),
|
||||
}
|
||||
}
|
||||
// Let standard handling take over
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
get embedderInfo(): EmbedderInfo {
|
||||
return {
|
||||
name: "ollama",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "../constants"
|
||||
import { getDefaultModelId, getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "../shared/validation-helpers"
|
||||
|
||||
interface EmbeddingItem {
|
||||
embedding: string | number[]
|
||||
|
|
@ -26,12 +27,6 @@ interface OpenAIEmbeddingResponse {
|
|||
* OpenAI Compatible implementation of the embedder interface with batching and rate limiting.
|
||||
* This embedder allows using any OpenAI-compatible API endpoint by specifying a custom baseURL.
|
||||
*/
|
||||
interface HttpError extends Error {
|
||||
status?: number
|
||||
response?: {
|
||||
status?: number
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenAICompatibleEmbedder implements IEmbedder {
|
||||
private embeddingsClient: OpenAI
|
||||
|
|
@ -201,14 +196,31 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
const error = new Error(`HTTP ${response.status}: ${errorText}`) as HttpError
|
||||
error.status = response.status
|
||||
if (!response || !response.ok) {
|
||||
const status = response?.status || 0
|
||||
let errorText = "No response"
|
||||
try {
|
||||
if (response && typeof response.text === "function") {
|
||||
errorText = await response.text()
|
||||
} else if (response) {
|
||||
errorText = `Error ${status}`
|
||||
}
|
||||
} catch {
|
||||
// Ignore text parsing errors
|
||||
errorText = `Error ${status}`
|
||||
}
|
||||
const error = new Error(`HTTP ${status}: ${errorText}`) as HttpError
|
||||
error.status = status || response?.status || 0
|
||||
throw error
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
try {
|
||||
return await response.json()
|
||||
} catch (e) {
|
||||
const error = new Error(`Failed to parse response JSON`) as HttpError
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -272,11 +284,11 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
},
|
||||
}
|
||||
} catch (error) {
|
||||
const httpError = error as HttpError
|
||||
const isRateLimitError = httpError?.status === 429
|
||||
const hasMoreAttempts = attempts < MAX_RETRIES - 1
|
||||
|
||||
if (isRateLimitError && hasMoreAttempts) {
|
||||
// Check if it's a rate limit error
|
||||
const httpError = error as HttpError
|
||||
if (httpError?.status === 429 && hasMoreAttempts) {
|
||||
const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
|
||||
console.warn(
|
||||
t("embeddings:rateLimitRetry", {
|
||||
|
|
@ -292,37 +304,50 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
|
|||
// Log the error for debugging
|
||||
console.error(`OpenAI Compatible embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
|
||||
|
||||
// Provide more context in the error message using robust error extraction
|
||||
let errorMessage = t("embeddings:unknownError")
|
||||
if (httpError?.message) {
|
||||
errorMessage = httpError.message
|
||||
} else if (typeof error === "string") {
|
||||
errorMessage = error
|
||||
} else if (error && typeof error === "object" && "toString" in error) {
|
||||
try {
|
||||
errorMessage = String(error)
|
||||
} catch {
|
||||
errorMessage = t("embeddings:unknownError")
|
||||
}
|
||||
}
|
||||
|
||||
const statusCode = httpError?.status || httpError?.response?.status
|
||||
|
||||
if (statusCode === 401) {
|
||||
throw new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
throw new Error(
|
||||
t("embeddings:failedWithStatus", { attempts: MAX_RETRIES, statusCode, errorMessage }),
|
||||
)
|
||||
} else {
|
||||
throw new Error(t("embeddings:failedWithError", { attempts: MAX_RETRIES, errorMessage }))
|
||||
}
|
||||
// Format and throw the error
|
||||
throw formatEmbeddingError(error, MAX_RETRIES)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the OpenAI-compatible embedder configuration by testing endpoint connectivity and API key
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(async () => {
|
||||
// Test with a minimal embedding request
|
||||
const testTexts = ["test"]
|
||||
const modelToUse = this.defaultModelId
|
||||
|
||||
let response: OpenAIEmbeddingResponse
|
||||
|
||||
if (this.isFullUrl) {
|
||||
// Test direct HTTP request for full endpoint URLs
|
||||
response = await this.makeDirectEmbeddingRequest(this.baseUrl, testTexts, modelToUse)
|
||||
} else {
|
||||
// Test using OpenAI SDK for base URLs
|
||||
response = (await this.embeddingsClient.embeddings.create({
|
||||
input: testTexts,
|
||||
model: modelToUse,
|
||||
encoding_format: "base64",
|
||||
})) as OpenAIEmbeddingResponse
|
||||
}
|
||||
|
||||
// Check if we got a valid response
|
||||
if (!response?.data || response.data.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "embeddings:validation.invalidResponse",
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}, "openai-compatible")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this embedder
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "../constants"
|
||||
import { getModelQueryPrefix } from "../../../shared/embeddingModels"
|
||||
import { t } from "../../../i18n"
|
||||
import { withValidationErrorHandling, formatEmbeddingError, HttpError } from "../shared/validation-helpers"
|
||||
|
||||
/**
|
||||
* OpenAI implementation of the embedder interface with batching and rate limiting
|
||||
|
|
@ -138,10 +139,11 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
|
|||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
const isRateLimitError = error?.status === 429
|
||||
const hasMoreAttempts = attempts < MAX_RETRIES - 1
|
||||
|
||||
if (isRateLimitError && hasMoreAttempts) {
|
||||
// Check if it's a rate limit error
|
||||
const httpError = error as HttpError
|
||||
if (httpError?.status === 429 && hasMoreAttempts) {
|
||||
const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
|
||||
console.warn(
|
||||
t("embeddings:rateLimitRetry", {
|
||||
|
|
@ -157,37 +159,38 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
|
|||
// Log the error for debugging
|
||||
console.error(`OpenAI embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
|
||||
|
||||
// Provide more context in the error message using robust error extraction
|
||||
let errorMessage = "Unknown error"
|
||||
if (error?.message) {
|
||||
errorMessage = error.message
|
||||
} else if (typeof error === "string") {
|
||||
errorMessage = error
|
||||
} else if (error && typeof error.toString === "function") {
|
||||
try {
|
||||
errorMessage = error.toString()
|
||||
} catch {
|
||||
errorMessage = "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
const statusCode = error?.status || error?.response?.status
|
||||
|
||||
if (statusCode === 401) {
|
||||
throw new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
throw new Error(
|
||||
t("embeddings:failedWithStatus", { attempts: MAX_RETRIES, statusCode, errorMessage }),
|
||||
)
|
||||
} else {
|
||||
throw new Error(t("embeddings:failedWithError", { attempts: MAX_RETRIES, errorMessage }))
|
||||
}
|
||||
// Format and throw the error
|
||||
throw formatEmbeddingError(error, MAX_RETRIES)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the OpenAI embedder configuration by attempting a minimal embedding request
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
|
||||
return withValidationErrorHandling(async () => {
|
||||
// Test with a minimal embedding request
|
||||
const response = await this.embeddingsClient.embeddings.create({
|
||||
input: ["test"],
|
||||
model: this.defaultModelId,
|
||||
})
|
||||
|
||||
// Check if we got a valid response
|
||||
if (!response.data || response.data.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: t("embeddings:openai.invalidResponseFormat"),
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}, "openai")
|
||||
}
|
||||
|
||||
get embedderInfo(): EmbedderInfo {
|
||||
return {
|
||||
name: "openai",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ export interface IEmbedder {
|
|||
* @returns Promise resolving to an EmbeddingResponse
|
||||
*/
|
||||
createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse>
|
||||
|
||||
/**
|
||||
* Validates the embedder configuration by testing connectivity and credentials.
|
||||
* @returns Promise resolving to validation result with success status and optional error message
|
||||
*/
|
||||
validateConfiguration(): Promise<{ valid: boolean; error?: string }>
|
||||
|
||||
get embedderInfo(): EmbedderInfo
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,14 @@ export class CodeIndexManager {
|
|||
return { requiresRestart }
|
||||
}
|
||||
|
||||
// 3. CacheManager Initialization
|
||||
// 3. Check if workspace is available
|
||||
const workspacePath = getWorkspacePath()
|
||||
if (!workspacePath) {
|
||||
this._stateManager.setSystemState("Standby", "No workspace folder open")
|
||||
return { requiresRestart }
|
||||
}
|
||||
|
||||
// 4. CacheManager Initialization
|
||||
if (!this._cacheManager) {
|
||||
this._cacheManager = new CacheManager(this.context, this.workspacePath)
|
||||
await this._cacheManager.initialize()
|
||||
|
|
@ -215,6 +222,9 @@ export class CodeIndexManager {
|
|||
if (this._orchestrator) {
|
||||
this.stopWatcher()
|
||||
}
|
||||
// Clear existing services to ensure clean state
|
||||
this._orchestrator = undefined
|
||||
this._searchService = undefined
|
||||
|
||||
// (Re)Initialize service factory
|
||||
this._serviceFactory = new CodeIndexServiceFactory(
|
||||
|
|
@ -224,7 +234,14 @@ export class CodeIndexManager {
|
|||
)
|
||||
|
||||
const ignoreInstance = ignore()
|
||||
const ignorePath = path.join(getWorkspacePath(), ".gitignore")
|
||||
const workspacePath = getWorkspacePath()
|
||||
|
||||
if (!workspacePath) {
|
||||
this._stateManager.setSystemState("Standby", "")
|
||||
return
|
||||
}
|
||||
|
||||
const ignorePath = path.join(workspacePath, ".gitignore")
|
||||
try {
|
||||
const content = await fs.readFile(ignorePath, "utf8")
|
||||
ignoreInstance.add(content)
|
||||
|
|
@ -241,6 +258,17 @@ export class CodeIndexManager {
|
|||
ignoreInstance,
|
||||
)
|
||||
|
||||
// Validate embedder configuration before proceeding
|
||||
const validationResult = await this._serviceFactory.validateEmbedder(embedder)
|
||||
if (!validationResult.valid) {
|
||||
// Set error state with clear message
|
||||
this._stateManager.setSystemState(
|
||||
"Error",
|
||||
validationResult.error || "Embedder configuration validation failed",
|
||||
)
|
||||
throw new Error(validationResult.error || "Invalid embedder configuration")
|
||||
}
|
||||
|
||||
// (Re)Initialize orchestrator
|
||||
this._orchestrator = new CodeIndexOrchestrator(
|
||||
this._configManager!,
|
||||
|
|
@ -259,6 +287,9 @@ export class CodeIndexManager {
|
|||
embedder,
|
||||
vectorStore,
|
||||
)
|
||||
|
||||
// Clear any error state after successful recreation
|
||||
this._stateManager.setSystemState("Standby", "")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -274,13 +305,16 @@ export class CodeIndexManager {
|
|||
const isFeatureEnabled = this.isFeatureEnabled
|
||||
const isFeatureConfigured = this.isFeatureConfigured
|
||||
|
||||
// If configuration changes require a restart and the manager is initialized, restart the service
|
||||
if (requiresRestart && isFeatureEnabled && isFeatureConfigured && this.isInitialized) {
|
||||
// Recreate services with new configuration
|
||||
await this._recreateServices()
|
||||
|
||||
// Start indexing with new services
|
||||
this.startIndexing()
|
||||
if (requiresRestart && isFeatureEnabled && isFeatureConfigured) {
|
||||
try {
|
||||
// Recreate services with new configuration
|
||||
await this._recreateServices()
|
||||
} catch (error) {
|
||||
// Error state already set in _recreateServices
|
||||
console.error("Failed to recreate services:", error)
|
||||
// Re-throw the error so the caller knows validation failed
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,31 @@ export class CodeIndexOrchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for partial failures - if a significant portion of blocks failed
|
||||
const failureRate = (cumulativeBlocksFoundSoFar - cumulativeBlocksIndexed) / cumulativeBlocksFoundSoFar
|
||||
if (batchErrors.length > 0 && failureRate > 0.1) {
|
||||
// More than 10% of blocks failed to index
|
||||
const firstError = batchErrors[0]
|
||||
throw new Error(
|
||||
`Indexing partially failed: Only ${cumulativeBlocksIndexed} of ${cumulativeBlocksFoundSoFar} blocks were indexed. ${firstError.message}`,
|
||||
)
|
||||
}
|
||||
|
||||
// CRITICAL: If there were ANY batch errors and NO blocks were successfully indexed,
|
||||
// this is a complete failure regardless of the failure rate calculation
|
||||
if (batchErrors.length > 0 && cumulativeBlocksIndexed === 0) {
|
||||
const firstError = batchErrors[0]
|
||||
throw new Error(`Indexing failed completely: ${firstError.message}`)
|
||||
}
|
||||
|
||||
// Final sanity check: If we found blocks but indexed none and somehow no errors were reported,
|
||||
// this is still a failure
|
||||
if (cumulativeBlocksFoundSoFar > 0 && cumulativeBlocksIndexed === 0) {
|
||||
throw new Error(
|
||||
"Indexing failed: No code blocks were successfully indexed despite finding files to process. This indicates a critical embedder failure.",
|
||||
)
|
||||
}
|
||||
|
||||
await this._startWatcher()
|
||||
|
||||
this.stateManager.setSystemState("Indexed", "File watcher started.")
|
||||
|
|
|
|||
|
|
@ -66,6 +66,23 @@ export class CodeIndexServiceFactory {
|
|||
throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an embedder instance to ensure it's properly configured.
|
||||
* @param embedder The embedder instance to validate
|
||||
* @returns Promise resolving to validation result
|
||||
*/
|
||||
public async validateEmbedder(embedder: IEmbedder): Promise<{ valid: boolean; error?: string }> {
|
||||
try {
|
||||
return await embedder.validateConfiguration()
|
||||
} catch (error) {
|
||||
// If validation throws an exception, preserve the original error message
|
||||
return {
|
||||
valid: false,
|
||||
error: error instanceof Error ? error.message : "embeddings:validation.configurationError",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vector store instance using the current configuration.
|
||||
*/
|
||||
|
|
|
|||
187
src/services/code-index/shared/validation-helpers.ts
Normal file
187
src/services/code-index/shared/validation-helpers.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import { t } from "../../../i18n"
|
||||
import { serializeError } from "serialize-error"
|
||||
|
||||
/**
|
||||
* HTTP error interface for embedder errors
|
||||
*/
|
||||
export interface HttpError extends Error {
|
||||
status?: number
|
||||
response?: {
|
||||
status?: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common error types that can occur during embedder validation
|
||||
*/
|
||||
export interface ValidationError {
|
||||
status?: number
|
||||
message?: string
|
||||
name?: string
|
||||
code?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps HTTP status codes to appropriate error messages
|
||||
*/
|
||||
export function getErrorMessageForStatus(status: number | undefined, embedderType: string): string | undefined {
|
||||
switch (status) {
|
||||
case 401:
|
||||
case 403:
|
||||
return "embeddings:validation.authenticationFailed"
|
||||
case 404:
|
||||
return embedderType === "openai"
|
||||
? "embeddings:validation.modelNotAvailable"
|
||||
: "embeddings:validation.invalidEndpoint"
|
||||
case 429:
|
||||
return "embeddings:validation.serviceUnavailable"
|
||||
default:
|
||||
if (status && status >= 400 && status < 600) {
|
||||
return "embeddings:validation.configurationError"
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts status code from various error formats
|
||||
*/
|
||||
export function extractStatusCode(error: any): number | undefined {
|
||||
// Direct status property
|
||||
if (error?.status) return error.status
|
||||
|
||||
// Response status property
|
||||
if (error?.response?.status) return error.response.status
|
||||
|
||||
// Extract from error message (e.g., "HTTP 404: Not Found")
|
||||
if (error?.message) {
|
||||
const match = error.message.match(/HTTP (\d+):/)
|
||||
if (match) {
|
||||
return parseInt(match[1], 10)
|
||||
}
|
||||
}
|
||||
|
||||
// Use serialize-error as fallback for complex objects
|
||||
const serialized = serializeError(error)
|
||||
if (serialized?.status) return serialized.status
|
||||
if (serialized?.response?.status) return serialized.response.status
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts error message from various error formats
|
||||
*/
|
||||
export function extractErrorMessage(error: any): string {
|
||||
if (error?.message) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error
|
||||
}
|
||||
|
||||
if (error && typeof error === "object" && "toString" in error) {
|
||||
try {
|
||||
return String(error)
|
||||
} catch {
|
||||
return "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
// Use serialize-error as fallback for complex objects
|
||||
const serialized = serializeError(error)
|
||||
if (serialized?.message) {
|
||||
return serialized.message
|
||||
}
|
||||
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard validation error handler for embedder configuration validation
|
||||
* Returns a consistent error response based on the error type
|
||||
*/
|
||||
export function handleValidationError(
|
||||
error: any,
|
||||
embedderType: string,
|
||||
customHandlers?: {
|
||||
beforeStandardHandling?: (error: any) => { valid: boolean; error: string } | undefined
|
||||
},
|
||||
): { valid: boolean; error: string } {
|
||||
// Serialize the error to ensure we have access to all properties
|
||||
const serializedError = serializeError(error)
|
||||
|
||||
// Allow custom handling first (pass original error for backward compatibility)
|
||||
if (customHandlers?.beforeStandardHandling) {
|
||||
const customResult = customHandlers.beforeStandardHandling(error)
|
||||
if (customResult) return customResult
|
||||
}
|
||||
|
||||
// Extract status code and error message from serialized error
|
||||
const statusCode = extractStatusCode(serializedError)
|
||||
const errorMessage = extractErrorMessage(serializedError)
|
||||
|
||||
// Check for status-based errors first
|
||||
const statusError = getErrorMessageForStatus(statusCode, embedderType)
|
||||
if (statusError) {
|
||||
return { valid: false, error: statusError }
|
||||
}
|
||||
|
||||
// Check for connection errors
|
||||
if (errorMessage) {
|
||||
if (
|
||||
errorMessage.includes("ENOTFOUND") ||
|
||||
errorMessage.includes("ECONNREFUSED") ||
|
||||
errorMessage.includes("ETIMEDOUT") ||
|
||||
errorMessage === "AbortError" ||
|
||||
errorMessage.includes("HTTP 0:") ||
|
||||
errorMessage === "No response"
|
||||
) {
|
||||
return { valid: false, error: "embeddings:validation.connectionFailed" }
|
||||
}
|
||||
|
||||
if (errorMessage.includes("Failed to parse response JSON")) {
|
||||
return { valid: false, error: "embeddings:validation.invalidResponse" }
|
||||
}
|
||||
}
|
||||
|
||||
// For generic errors, preserve the original error message if it's not a standard one
|
||||
if (errorMessage && errorMessage !== "Unknown error") {
|
||||
return { valid: false, error: errorMessage }
|
||||
}
|
||||
|
||||
// Fallback to generic error
|
||||
return { valid: false, error: "embeddings:validation.configurationError" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an async validation function with standard error handling
|
||||
*/
|
||||
export async function withValidationErrorHandling<T extends { valid: boolean; error?: string }>(
|
||||
validationFn: () => Promise<T>,
|
||||
embedderType: string,
|
||||
customHandlers?: Parameters<typeof handleValidationError>[2],
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
try {
|
||||
return await validationFn()
|
||||
} catch (error) {
|
||||
return handleValidationError(error, embedderType, customHandlers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an embedding error message based on the error type and context
|
||||
*/
|
||||
export function formatEmbeddingError(error: any, maxRetries: number): Error {
|
||||
const errorMessage = extractErrorMessage(error)
|
||||
const statusCode = extractStatusCode(error)
|
||||
|
||||
if (statusCode === 401) {
|
||||
return new Error(t("embeddings:authenticationFailed"))
|
||||
} else if (statusCode) {
|
||||
return new Error(t("embeddings:failedWithStatus", { attempts: maxRetries, statusCode, errorMessage }))
|
||||
} else {
|
||||
return new Error(t("embeddings:failedWithError", { attempts: maxRetries, errorMessage }))
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +110,7 @@ export interface WebviewMessage {
|
|||
| "enhancedPrompt"
|
||||
| "draggedImages"
|
||||
| "deleteMessage"
|
||||
| "submitEditedMessage"
|
||||
| "terminalOutputLineLimit"
|
||||
| "terminalShellIntegrationTimeout"
|
||||
| "terminalShellIntegrationDisabled"
|
||||
|
|
@ -193,6 +194,7 @@ export interface WebviewMessage {
|
|||
| "saveCodeIndexSettingsAtomic"
|
||||
| "requestCodeIndexSecretStatus"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
|
||||
disabled?: boolean
|
||||
dataUri?: string
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": false,
|
||||
"resolveJsonModule": true,
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ export const ChatRowContent = ({
|
|||
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
|
||||
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
|
||||
const [showCopySuccess, setShowCopySuccess] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editedContent, setEditedContent] = useState("")
|
||||
const { copyWithFeedback } = useCopyToClipboard()
|
||||
|
||||
// Memoized callback to prevent re-renders caused by inline arrow functions
|
||||
|
|
@ -118,6 +120,31 @@ export const ChatRowContent = ({
|
|||
onToggleExpand(message.ts)
|
||||
}, [onToggleExpand, message.ts])
|
||||
|
||||
// Handle edit button click
|
||||
const handleEditClick = useCallback(() => {
|
||||
setIsEditing(true)
|
||||
setEditedContent(message.text || "")
|
||||
// Edit mode is now handled entirely in the frontend
|
||||
// No need to notify the backend
|
||||
}, [message.text])
|
||||
|
||||
// Handle cancel edit
|
||||
const handleCancelEdit = useCallback(() => {
|
||||
setIsEditing(false)
|
||||
setEditedContent(message.text || "")
|
||||
}, [message.text])
|
||||
|
||||
// Handle save edit
|
||||
const handleSaveEdit = useCallback(() => {
|
||||
setIsEditing(false)
|
||||
// Send edited message to backend
|
||||
vscode.postMessage({
|
||||
type: "submitEditedMessage",
|
||||
value: message.ts,
|
||||
editedMessageContent: editedContent,
|
||||
})
|
||||
}, [message.ts, editedContent])
|
||||
|
||||
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
||||
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
|
||||
const info = safeJsonParse<ClineApiReqInfo>(message.text)
|
||||
|
|
@ -1001,23 +1028,56 @@ export const ChatRowContent = ({
|
|||
case "user_feedback":
|
||||
return (
|
||||
<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-grow px-2 py-1 wrap-anywhere">
|
||||
<Mention text={message.text} withShadow />
|
||||
{isEditing ? (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<textarea
|
||||
className="w-full p-2 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded-xs"
|
||||
value={editedContent}
|
||||
onChange={(e) => setEditedContent(e.target.value)}
|
||||
rows={5}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={handleCancelEdit}>
|
||||
{t("chat:cancel.title")}
|
||||
</Button>
|
||||
<Button variant="default" size="sm" onClick={handleSaveEdit}>
|
||||
{t("chat:save.title")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({ type: "deleteMessage", value: message.ts })
|
||||
}}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
{message.images && message.images.length > 0 && (
|
||||
) : (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-grow px-2 py-1 wrap-anywhere">
|
||||
<Mention text={message.text} withShadow />
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleEditClick()
|
||||
}}>
|
||||
<span className="codicon codicon-edit" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({ type: "deleteMessage", value: message.ts })
|
||||
}}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isEditing && message.images && message.images.length > 0 && (
|
||||
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -445,13 +445,15 @@ describe("ChatTextArea", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should navigate to previous prompt on arrow up", () => {
|
||||
it("should navigate to previous prompt on arrow up when cursor is at beginning", () => {
|
||||
const setInputValue = vi.fn()
|
||||
const { container } = render(
|
||||
<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="" />,
|
||||
)
|
||||
|
||||
const textarea = container.querySelector("textarea")!
|
||||
// Ensure cursor is at the beginning
|
||||
textarea.setSelectionRange(0, 0)
|
||||
|
||||
// Simulate arrow up key press
|
||||
fireEvent.keyDown(textarea, { key: "ArrowUp" })
|
||||
|
|
@ -755,6 +757,86 @@ describe("ChatTextArea", () => {
|
|||
fireEvent.keyDown(textarea, { key: "ArrowUp" })
|
||||
expect(setInputValue).toHaveBeenCalledWith("Message 2")
|
||||
})
|
||||
|
||||
it("should not navigate history with arrow up when cursor is not at beginning", () => {
|
||||
const setInputValue = vi.fn()
|
||||
const { container } = render(
|
||||
<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="Some text here" />,
|
||||
)
|
||||
|
||||
const textarea = container.querySelector("textarea")!
|
||||
// Set cursor to middle of text (not at beginning)
|
||||
textarea.setSelectionRange(5, 5)
|
||||
|
||||
// Clear any calls from initial render
|
||||
setInputValue.mockClear()
|
||||
|
||||
// Simulate arrow up key press
|
||||
fireEvent.keyDown(textarea, { key: "ArrowUp" })
|
||||
|
||||
// Should not navigate history, allowing default behavior (move cursor to start)
|
||||
expect(setInputValue).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should navigate history with arrow up when cursor is at beginning", () => {
|
||||
const setInputValue = vi.fn()
|
||||
const { container } = render(
|
||||
<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="Some text here" />,
|
||||
)
|
||||
|
||||
const textarea = container.querySelector("textarea")!
|
||||
// Set cursor to beginning of text
|
||||
textarea.setSelectionRange(0, 0)
|
||||
|
||||
// Clear any calls from initial render
|
||||
setInputValue.mockClear()
|
||||
|
||||
// Simulate arrow up key press
|
||||
fireEvent.keyDown(textarea, { key: "ArrowUp" })
|
||||
|
||||
// Should navigate to history since cursor is at beginning
|
||||
expect(setInputValue).toHaveBeenCalledWith("Third prompt")
|
||||
})
|
||||
|
||||
it("should navigate history with Command+Up when cursor is at beginning", () => {
|
||||
const setInputValue = vi.fn()
|
||||
const { container } = render(
|
||||
<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="Some text here" />,
|
||||
)
|
||||
|
||||
const textarea = container.querySelector("textarea")!
|
||||
// Set cursor to beginning of text
|
||||
textarea.setSelectionRange(0, 0)
|
||||
|
||||
// Clear any calls from initial render
|
||||
setInputValue.mockClear()
|
||||
|
||||
// Simulate Command+Up key press
|
||||
fireEvent.keyDown(textarea, { key: "ArrowUp", metaKey: true })
|
||||
|
||||
// Should navigate to history since cursor is at beginning (same as regular Up)
|
||||
expect(setInputValue).toHaveBeenCalledWith("Third prompt")
|
||||
})
|
||||
|
||||
it("should not navigate history with Command+Up when cursor is not at beginning", () => {
|
||||
const setInputValue = vi.fn()
|
||||
const { container } = render(
|
||||
<ChatTextArea {...defaultProps} setInputValue={setInputValue} inputValue="Some text here" />,
|
||||
)
|
||||
|
||||
const textarea = container.querySelector("textarea")!
|
||||
// Set cursor to middle of text (not at beginning)
|
||||
textarea.setSelectionRange(5, 5)
|
||||
|
||||
// Clear any calls from initial render
|
||||
setInputValue.mockClear()
|
||||
|
||||
// Simulate Command+Up key press
|
||||
fireEvent.keyDown(textarea, { key: "ArrowUp", metaKey: true })
|
||||
|
||||
// Should not navigate history, allowing default behavior (same as regular Up)
|
||||
expect(setInputValue).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -139,32 +139,8 @@ export const usePromptHistory = ({
|
|||
const isAtBeginning = selectionStart === 0 && selectionEnd === 0
|
||||
const isAtEnd = selectionStart === value.length && selectionEnd === value.length
|
||||
|
||||
// Check for modifier keys (Alt or Cmd/Ctrl)
|
||||
const hasModifier = event.altKey || event.metaKey || event.ctrlKey
|
||||
|
||||
// Handle explicit history navigation with Alt+Up/Down
|
||||
if (hasModifier && (event.key === "ArrowUp" || event.key === "ArrowDown")) {
|
||||
event.preventDefault()
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
// Save current input if starting navigation
|
||||
if (historyIndex === -1) {
|
||||
setTempInput(inputValue)
|
||||
}
|
||||
return navigateToHistory(historyIndex + 1, textarea, "start")
|
||||
} else {
|
||||
// ArrowDown
|
||||
if (historyIndex > 0) {
|
||||
return navigateToHistory(historyIndex - 1, textarea, "end")
|
||||
} else if (historyIndex === 0) {
|
||||
returnToCurrentInput(textarea, "end")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle smart navigation without modifiers
|
||||
if (!hasSelection && !hasModifier) {
|
||||
// Handle smart navigation
|
||||
if (!hasSelection) {
|
||||
// Only navigate history with UP if cursor is at the very beginning
|
||||
if (event.key === "ArrowUp" && isAtBeginning) {
|
||||
event.preventDefault()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue