From 32ce45d8bc1105be2cc829173a7cc205ce392572 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 8 Jul 2025 13:45:38 -0400 Subject: [PATCH 1/6] Add a hint about using todos for complicated tasks (#5473) --- src/core/environment/reminder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/environment/reminder.ts b/src/core/environment/reminder.ts index eb1b39dfb5..6edb24364d 100644 --- a/src/core/environment/reminder.ts +++ b/src/core/environment/reminder.ts @@ -5,7 +5,7 @@ import { TodoItem, TodoStatus } from "@roo-code/types" */ export function formatReminderSection(todoList?: TodoItem[]): string { if (!todoList || todoList.length === 0) { - return "" + return "You have not created a todo list yet. Create one with `update_todo_list` if your task is complicated or involves multiple steps." } const statusMap: Record = { pending: "Pending", From 0606c56bf1aab0818297b20d7b2ba40252ff4cd9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 8 Jul 2025 13:59:48 -0400 Subject: [PATCH 2/6] Make the default architect prompt create a todo list (#5472) --- .../architect-mode-prompt.snap | 14 ++- .../mcp-server-creation-disabled.snap | 14 ++- .../mcp-server-creation-enabled.snap | 14 ++- .../partial-reads-enabled.snap | 14 ++- .../consistent-system-prompt.snap | 14 ++- .../with-computer-use-support.snap | 14 ++- .../with-diff-enabled-false.snap | 14 ++- .../system-prompt/with-diff-enabled-true.snap | 14 ++- .../with-diff-enabled-undefined.snap | 14 ++- .../with-different-viewport-size.snap | 14 ++- .../system-prompt/with-mcp-hub-provided.snap | 14 ++- .../system-prompt/with-undefined-mcp-hub.snap | 14 ++- .../__tests__/get-prompt-component.spec.ts | 88 +++++++++++++++++ src/core/prompts/system.ts | 23 +++-- .../modes-empty-prompt-component.spec.ts | 97 +++++++++++++++++++ src/shared/__tests__/modes.spec.ts | 7 +- src/shared/modes.ts | 24 +++-- src/utils/__tests__/object.spec.ts | 38 ++++++++ src/utils/object.ts | 18 ++++ 19 files changed, 395 insertions(+), 68 deletions(-) create mode 100644 src/core/prompts/__tests__/get-prompt-component.spec.ts create mode 100644 src/shared/__tests__/modes-empty-prompt-component.spec.ts create mode 100644 src/utils/__tests__/object.spec.ts create mode 100644 src/utils/object.ts diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index 31b49ec682..ea5f8b7338 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -549,13 +549,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index b2b0abfad4..84fc71ee25 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -611,13 +611,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 0eecf0482e..43126e40c1 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -617,13 +617,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 731f4b29a7..5da7e33bdb 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -554,13 +554,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index 31b49ec682..ea5f8b7338 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -549,13 +549,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index fda93c1c77..29afbda0ad 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -605,13 +605,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index 31b49ec682..ea5f8b7338 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -549,13 +549,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 8db4d0b377..36af7d8687 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -637,13 +637,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index 31b49ec682..ea5f8b7338 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -549,13 +549,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index 44d5b58327..68a333d092 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -605,13 +605,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 0eecf0482e..43126e40c1 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -617,13 +617,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index 31b49ec682..ea5f8b7338 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -549,13 +549,19 @@ Mode-specific Instructions: 2. You should also ask the user clarifying questions to get a better understanding of the task. -3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. +3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be: + - Specific and actionable + - Listed in logical execution order + - Focused on a single, well-defined outcome + - Clear enough that another mode could execute it independently -4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. -5. Use the switch_mode tool to request that the user switch to another mode to implement the solution. +5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture. -**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.** +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. + +**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.** Rules: # Rules from .clinerules-architect: diff --git a/src/core/prompts/__tests__/get-prompt-component.spec.ts b/src/core/prompts/__tests__/get-prompt-component.spec.ts new file mode 100644 index 0000000000..7c4229b9c6 --- /dev/null +++ b/src/core/prompts/__tests__/get-prompt-component.spec.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest" +import { getPromptComponent } from "../system" +import type { CustomModePrompts } from "@roo-code/types" + +describe("getPromptComponent", () => { + it("should return undefined for empty objects", () => { + const customModePrompts: CustomModePrompts = { + architect: {}, + } + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toBeUndefined() + }) + + it("should return the component for objects with any properties", () => { + const customModePrompts: CustomModePrompts = { + architect: { + foo: "bar", + baz: 123, + } as any, + } + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toEqual({ foo: "bar", baz: 123 }) + }) + + it("should return undefined for missing mode", () => { + const customModePrompts: CustomModePrompts = {} + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toBeUndefined() + }) + + it("should return undefined when customModePrompts is undefined", () => { + const result = getPromptComponent(undefined, "architect") + expect(result).toBeUndefined() + }) + + it.each([ + ["roleDefinition", { roleDefinition: "Test role" }], + ["customInstructions", { customInstructions: "Test instructions" }], + ["whenToUse", { whenToUse: "Test when to use" }], + ["description", { description: "Test description" }], + ])("should return the component when it has %s", (property, component) => { + const customModePrompts: CustomModePrompts = { + architect: component, + } + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toEqual(component) + }) + + it("should return the component when it has multiple properties", () => { + const customModePrompts: CustomModePrompts = { + architect: { + roleDefinition: "Test role", + customInstructions: "Test instructions", + whenToUse: "Test when to use", + description: "Test description", + }, + } + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toEqual({ + roleDefinition: "Test role", + customInstructions: "Test instructions", + whenToUse: "Test when to use", + description: "Test description", + }) + }) + + it("should return the component when it has both relevant and irrelevant properties", () => { + const customModePrompts: CustomModePrompts = { + architect: { + roleDefinition: "Test role", + foo: "bar", + baz: 123, + } as any, + } + + const result = getPromptComponent(customModePrompts, "architect") + expect(result).toEqual({ + roleDefinition: "Test role", + foo: "bar", + baz: 123, + }) + }) +}) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index be3b91f146..bfc12930a0 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -6,6 +6,7 @@ import type { ModeConfig, PromptComponent, CustomModePrompts, TodoItem } from "@ import { Mode, modes, defaultModeSlug, getModeBySlug, getGroupName, getModeSelection } from "../../shared/modes" import { DiffStrategy } from "../../shared/tools" import { formatLanguage } from "../../shared/language" +import { isEmpty } from "../../utils/object" import { McpHub } from "../../services/mcp/McpHub" import { CodeIndexManager } from "../../services/code-index/manager" @@ -26,6 +27,19 @@ import { markdownFormattingSection, } from "./sections" +// Helper function to get prompt component, filtering out empty objects +export function getPromptComponent( + customModePrompts: CustomModePrompts | undefined, + mode: string, +): PromptComponent | undefined { + const component = customModePrompts?.[mode] + // Return undefined if component is empty + if (isEmpty(component)) { + return undefined + } + return component +} + async function generatePrompt( context: vscode.ExtensionContext, cwd: string, @@ -129,13 +143,6 @@ export const SYSTEM_PROMPT = async ( throw new Error("Extension context is required for generating system prompt") } - const getPromptComponent = (value: unknown) => { - if (typeof value === "object" && value !== null) { - return value as PromptComponent - } - return undefined - } - // Try to load custom system prompt from file const variablesForPrompt: PromptVariables = { workspace: cwd, @@ -147,7 +154,7 @@ export const SYSTEM_PROMPT = async ( const fileCustomSystemPrompt = await loadSystemPromptFile(cwd, mode, variablesForPrompt) // Check if it's a custom mode - const promptComponent = getPromptComponent(customModePrompts?.[mode]) + const promptComponent = getPromptComponent(customModePrompts, mode) // Get full mode config from custom modes or fall back to built-in modes const currentMode = getModeBySlug(mode, customModes) || modes.find((m) => m.slug === mode) || modes[0] diff --git a/src/shared/__tests__/modes-empty-prompt-component.spec.ts b/src/shared/__tests__/modes-empty-prompt-component.spec.ts new file mode 100644 index 0000000000..5af93cb5d9 --- /dev/null +++ b/src/shared/__tests__/modes-empty-prompt-component.spec.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from "vitest" +import { getModeSelection, modes } from "../modes" +import type { PromptComponent } from "@roo-code/types" + +describe("getModeSelection with empty promptComponent", () => { + it("should use built-in mode instructions when promptComponent is undefined", () => { + const architectMode = modes.find((m) => m.slug === "architect")! + + // Test with undefined promptComponent (which is what getPromptComponent returns for empty objects) + const result = getModeSelection("architect", undefined, []) + + // Should use built-in mode values + expect(result.roleDefinition).toBe(architectMode.roleDefinition) + expect(result.baseInstructions).toBe(architectMode.customInstructions) + expect(result.baseInstructions).toContain("Do some information gathering") + }) + + it("should use built-in mode instructions when promptComponent is null", () => { + const debugMode = modes.find((m) => m.slug === "debug")! + + // Test with null promptComponent + const result = getModeSelection("debug", null as any, []) + + // Should use built-in mode values + expect(result.roleDefinition).toBe(debugMode.roleDefinition) + expect(result.baseInstructions).toBe(debugMode.customInstructions) + expect(result.baseInstructions).toContain("Reflect on 5-7 different possible sources") + }) + + it("should use promptComponent when it has actual content", () => { + // Test with promptComponent that has actual content + const validPromptComponent: PromptComponent = { + roleDefinition: "Custom role", + customInstructions: "Custom instructions", + } + const result = getModeSelection("architect", validPromptComponent, []) + + // Should use promptComponent values + expect(result.roleDefinition).toBe("Custom role") + expect(result.baseInstructions).toBe("Custom instructions") + }) + + it("should merge promptComponent with built-in mode when it has partial content", () => { + const architectMode = modes.find((m) => m.slug === "architect")! + + // Test with promptComponent that only has customInstructions + const partialPromptComponent: PromptComponent = { + customInstructions: "Only custom instructions", + } + const result = getModeSelection("architect", partialPromptComponent, []) + + // Should merge: use promptComponent's customInstructions but fall back to built-in roleDefinition + expect(result.roleDefinition).toBe(architectMode.roleDefinition) // Falls back to built-in + expect(result.baseInstructions).toBe("Only custom instructions") // Uses promptComponent + }) + + it("should merge promptComponent with built-in mode when it only has roleDefinition", () => { + const debugMode = modes.find((m) => m.slug === "debug")! + + // Test with promptComponent that only has roleDefinition + const partialPromptComponent: PromptComponent = { + roleDefinition: "Custom debug role", + } + const result = getModeSelection("debug", partialPromptComponent, []) + + // Should merge: use promptComponent's roleDefinition but fall back to built-in customInstructions + expect(result.roleDefinition).toBe("Custom debug role") // Uses promptComponent + expect(result.baseInstructions).toBe(debugMode.customInstructions) // Falls back to built-in + }) + + it("should handle promptComponent with both roleDefinition and customInstructions", () => { + // Test with promptComponent that has both properties + const fullPromptComponent: PromptComponent = { + roleDefinition: "Full custom role", + customInstructions: "Full custom instructions", + } + const result = getModeSelection("architect", fullPromptComponent, []) + + // Should use promptComponent values for both + expect(result.roleDefinition).toBe("Full custom role") + expect(result.baseInstructions).toBe("Full custom instructions") + }) + + it("should fall back to default mode when built-in mode is not found", () => { + const defaultMode = modes[0] // First mode is the default + + // Test with non-existent mode + const partialPromptComponent: PromptComponent = { + customInstructions: "Custom instructions for unknown mode", + } + const result = getModeSelection("non-existent-mode", partialPromptComponent, []) + + // Should merge with default mode + expect(result.roleDefinition).toBe(defaultMode.roleDefinition) // Falls back to default mode + expect(result.baseInstructions).toBe("Custom instructions for unknown mode") // Uses promptComponent + }) +}) diff --git a/src/shared/__tests__/modes.spec.ts b/src/shared/__tests__/modes.spec.ts index acf7da84c8..6166c2445c 100644 --- a/src/shared/__tests__/modes.spec.ts +++ b/src/shared/__tests__/modes.spec.ts @@ -443,10 +443,11 @@ describe("getModeSelection", () => { expect(selection.baseInstructions).toBe(newCustomMode.customInstructions) }) - test("should return empty strings if slug does not exist in custom, prompt, or built-in modes", () => { + test("should fall back to default mode if slug does not exist in custom, prompt, or built-in modes", () => { const selection = getModeSelection("non-existent-mode", undefined, customModesList) - expect(selection.roleDefinition).toBe("") - expect(selection.baseInstructions).toBe("") + const defaultMode = modes[0] // First mode is the default + expect(selection.roleDefinition).toBe(defaultMode.roleDefinition) + expect(selection.baseInstructions).toBe(defaultMode.customInstructions || "") }) test("customMode's properties are used if customMode exists, ignoring promptComponent's properties", () => { diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 23efdc45f7..f876aa56fa 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -72,7 +72,7 @@ export const modes: readonly ModeConfig[] = [ description: "Plan and design before implementation", groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], customInstructions: - "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer.\n\n4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.\n\n5. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Do not provide time estimates for how long tasks will take to complete. Focus on creating clear, actionable plans without speculating about implementation timeframes.**", + "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. Include Mermaid diagrams if they help clarify complex workflows or system architecture.\n\n6. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", }, { slug: "code", @@ -183,23 +183,29 @@ export function findModeBySlug(slug: string, modes: readonly ModeConfig[] | unde /** * Get the mode selection based on the provided mode slug, prompt component, and custom modes. * If a custom mode is found, it takes precedence over the built-in modes. - * If no custom mode is found, the built-in mode is used. + * If no custom mode is found, the built-in mode is used with partial merging from promptComponent. * If neither is found, the default mode is used. */ export function getModeSelection(mode: string, promptComponent?: PromptComponent, customModes?: ModeConfig[]) { const customMode = findModeBySlug(mode, customModes) const builtInMode = findModeBySlug(mode, modes) - const modeToUse = customMode || promptComponent || builtInMode + // If we have a custom mode, use it entirely + if (customMode) { + return { + roleDefinition: customMode.roleDefinition || "", + baseInstructions: customMode.customInstructions || "", + description: customMode.description || "", + } + } - const roleDefinition = modeToUse?.roleDefinition || "" - const baseInstructions = modeToUse?.customInstructions || "" - const description = (customMode || builtInMode)?.description || "" + // Otherwise, use built-in mode as base and merge with promptComponent + const baseMode = builtInMode || modes[0] // fallback to default mode return { - roleDefinition, - baseInstructions, - description, + roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition || "", + baseInstructions: promptComponent?.customInstructions || baseMode.customInstructions || "", + description: baseMode.description || "", } } diff --git a/src/utils/__tests__/object.spec.ts b/src/utils/__tests__/object.spec.ts new file mode 100644 index 0000000000..ce9d9f5cc7 --- /dev/null +++ b/src/utils/__tests__/object.spec.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from "vitest" +import { isEmpty } from "../object" + +describe("isEmpty", () => { + describe("should return true for empty values", () => { + it.each([ + ["empty object", {}], + ["empty array", []], + ["null", null], + ["undefined", undefined], + ["string", "string"], + ["number", 123], + ["boolean true", true], + ["boolean false", false], + ])("%s", (_, value) => { + expect(isEmpty(value)).toBe(true) + }) + }) + + describe("should return false for non-empty values", () => { + it.each([ + ["object with properties", { a: 1 }], + ["object with multiple properties", { a: 1, b: 2 }], + ["array with one item", [1]], + ["array with multiple items", [1, 2, 3]], + ])("%s", (_, value) => { + expect(isEmpty(value)).toBe(false) + }) + }) + + it("should handle objects with null prototype", () => { + const obj = Object.create(null) + expect(isEmpty(obj)).toBe(true) + + obj.prop = "value" + expect(isEmpty(obj)).toBe(false) + }) +}) diff --git a/src/utils/object.ts b/src/utils/object.ts new file mode 100644 index 0000000000..1aec4a1309 --- /dev/null +++ b/src/utils/object.ts @@ -0,0 +1,18 @@ +/** + * Check if an object is empty (has no own enumerable properties) + * @param obj The object to check + * @returns true if the object is empty, false otherwise + */ +export function isEmpty(obj: unknown): boolean { + if (!obj || typeof obj !== "object") { + return true + } + + // Check if it's an array + if (Array.isArray(obj)) { + return obj.length === 0 + } + + // Check if it's an object with no own properties + return Object.keys(obj).length === 0 +} From c551e17327b732b1f35234b7a527ebe85a9aea3a Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:43:03 -0500 Subject: [PATCH 3/6] refactor: remove legacy codebaseIndexOpenAiCompatibleModelDimension property (#5459) --- .../config/__tests__/importExport.spec.ts | 26 ++++------ src/core/webview/webviewMessageHandler.ts | 2 +- src/i18n/locales/ca/embeddings.json | 11 +++++ src/i18n/locales/de/embeddings.json | 11 +++++ src/i18n/locales/en/embeddings.json | 11 +++++ src/i18n/locales/es/embeddings.json | 11 +++++ src/i18n/locales/fr/embeddings.json | 11 +++++ src/i18n/locales/hi/embeddings.json | 11 +++++ src/i18n/locales/id/embeddings.json | 11 +++++ src/i18n/locales/it/embeddings.json | 11 +++++ src/i18n/locales/ja/embeddings.json | 11 +++++ src/i18n/locales/ko/embeddings.json | 11 +++++ src/i18n/locales/nl/embeddings.json | 11 +++++ src/i18n/locales/pl/embeddings.json | 11 +++++ src/i18n/locales/pt-BR/embeddings.json | 11 +++++ src/i18n/locales/ru/embeddings.json | 11 +++++ src/i18n/locales/tr/embeddings.json | 11 +++++ src/i18n/locales/vi/embeddings.json | 11 +++++ src/i18n/locales/zh-CN/embeddings.json | 11 +++++ src/i18n/locales/zh-TW/embeddings.json | 11 +++++ .../__tests__/config-manager.spec.ts | 27 +++++----- .../__tests__/service-factory.spec.ts | 44 ++++++++--------- src/services/code-index/config-manager.ts | 49 ++++++++++++++----- src/services/code-index/interfaces/config.ts | 5 +- src/services/code-index/service-factory.ts | 39 +++++++-------- src/shared/WebviewMessage.ts | 2 +- .../src/components/chat/CodeIndexPopover.tsx | 14 +++--- 27 files changed, 308 insertions(+), 98 deletions(-) diff --git a/src/core/config/__tests__/importExport.spec.ts b/src/core/config/__tests__/importExport.spec.ts index 052bfc77f8..361d6b23b0 100644 --- a/src/core/config/__tests__/importExport.spec.ts +++ b/src/core/config/__tests__/importExport.spec.ts @@ -628,7 +628,7 @@ describe("importExport", () => { codebaseIndexEmbedderBaseUrl: "http://localhost:11434", // Wrong URL from Ollama // OpenAI Compatible settings are now stored directly in codebaseIndexConfig codebaseIndexOpenAiCompatibleBaseUrl: "https://custom-openai-api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1536, + codebaseIndexEmbedderModelDimension: 1536, }, } @@ -673,7 +673,7 @@ describe("importExport", () => { codebaseIndexEmbedderBaseUrl: "", // OpenAI Compatible settings are now stored directly in codebaseIndexConfig codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 768, + codebaseIndexEmbedderModelDimension: 768, }, } @@ -688,9 +688,7 @@ describe("importExport", () => { const exportedData = (safeWriteJson as Mock).mock.calls[0][1] // Settings are now exported as-is from codebaseIndexConfig - expect( - exportedData.globalSettings.codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension, - ).toBe(768) + expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexEmbedderModelDimension).toBe(768) expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl).toBe( "https://api.example.com/v1", ) @@ -731,7 +729,7 @@ describe("importExport", () => { codebaseIndexEmbedderBaseUrl: "http://localhost:11434", // Wrong URL from Ollama // OpenAI Compatible settings are now stored directly in codebaseIndexConfig codebaseIndexOpenAiCompatibleBaseUrl: "https://openai-compatible.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1536, + codebaseIndexEmbedderModelDimension: 1536, }, } @@ -749,9 +747,7 @@ describe("importExport", () => { expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl).toBe( "https://openai-compatible.example.com/v1", ) - expect( - exportedData.globalSettings.codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension, - ).toBe(1536) + expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexEmbedderModelDimension).toBe(1536) // The generic embedder base URL is still there expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexEmbedderBaseUrl).toBe( "http://localhost:11434", @@ -921,7 +917,6 @@ describe("importExport", () => { codebaseIndexEmbedderModelDimension: 1536, // OpenAI Compatible settings are now stored directly here codebaseIndexOpenAiCompatibleBaseUrl: "https://imported-url.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1536, }, }, }) @@ -956,7 +951,7 @@ describe("importExport", () => { expect.objectContaining({ codebaseIndexConfig: expect.objectContaining({ codebaseIndexOpenAiCompatibleBaseUrl: "https://imported-url.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1536, + codebaseIndexEmbedderModelDimension: 1536, }), }), ) @@ -1105,7 +1100,6 @@ describe("importExport", () => { codebaseIndexEmbedderModelDimension: testModelDimension, // OpenAI Compatible settings are now stored directly in codebaseIndexConfig codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: testModelDimension, }, } @@ -1160,7 +1154,7 @@ describe("importExport", () => { // Step 9: Verify that the model dimension was preserved exactly in global settings const importedGlobalSettings = mockContextProxy.setValues.mock.calls[0][0] - expect(importedGlobalSettings.codebaseIndexConfig?.codebaseIndexOpenAiCompatibleModelDimension).toBe( + expect(importedGlobalSettings.codebaseIndexConfig?.codebaseIndexEmbedderModelDimension).toBe( testModelDimension, ) expect(importedGlobalSettings.codebaseIndexConfig?.codebaseIndexOpenAiCompatibleBaseUrl).toBe( @@ -1198,7 +1192,7 @@ describe("importExport", () => { codebaseIndexEmbedderBaseUrl: "https://api.example.com/v1", // OpenAI Compatible settings are now stored directly in codebaseIndexConfig codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: testModelDimension, // 0 is a valid value + codebaseIndexEmbedderModelDimension: testModelDimension, // 0 is a valid value }, } @@ -1219,7 +1213,7 @@ describe("importExport", () => { // Verify the exported data includes the model dimension even when it's 0 const exportedData = (safeWriteJson as Mock).mock.calls[0][1] - expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension).toBe(0) + expect(exportedData.globalSettings.codebaseIndexConfig.codebaseIndexEmbedderModelDimension).toBe(0) // Test import roundtrip const exportedFileContent = JSON.stringify(exportedData) @@ -1247,7 +1241,7 @@ describe("importExport", () => { // Verify that model dimension 0 was preserved in global settings const setValuesCall = mockContextProxy.setValues.mock.calls[0][0] - expect(setValuesCall.codebaseIndexConfig?.codebaseIndexOpenAiCompatibleModelDimension).toBe(0) + expect(setValuesCall.codebaseIndexConfig?.codebaseIndexEmbedderModelDimension).toBe(0) }) it("should handle missing model dimension gracefully", async () => { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 219e52974e..08dbeabfc1 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1967,8 +1967,8 @@ export const webviewMessageHandler = async ( codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider, codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl, codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId, + codebaseIndexEmbedderModelDimension: settings.codebaseIndexEmbedderModelDimension, // Generic dimension codebaseIndexOpenAiCompatibleBaseUrl: settings.codebaseIndexOpenAiCompatibleBaseUrl, - codebaseIndexOpenAiCompatibleModelDimension: settings.codebaseIndexOpenAiCompatibleModelDimension, codebaseIndexSearchMaxResults: settings.codebaseIndexSearchMaxResults, codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore, } diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 35be4089d4..1d3c5f2476 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -37,5 +37,16 @@ "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ó." + }, + "serviceFactory": { + "openAiConfigMissing": "Falta la configuració d'OpenAI per crear l'embedder", + "ollamaConfigMissing": "Falta la configuració d'Ollama per crear l'embedder", + "openAiCompatibleConfigMissing": "Falta la configuració compatible amb OpenAI per crear l'embedder", + "geminiConfigMissing": "Falta la configuració de Gemini per crear l'embedder", + "invalidEmbedderType": "Tipus d'embedder configurat no vàlid: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Assegura't que la 'Dimensió d'incrustació' estigui configurada correctament als paràmetres del proveïdor compatible amb OpenAI.", + "vectorDimensionNotDetermined": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Comprova els perfils del model o la configuració.", + "qdrantUrlMissing": "Falta l'URL de Qdrant per crear l'emmagatzematge de vectors", + "codeIndexingNotConfigured": "No es poden crear serveis: La indexació de codi no està configurada correctament" } } diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index 7d96ddb511..f4abcb3e15 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "OpenAI-Konfiguration fehlt für die Erstellung des Embedders", + "ollamaConfigMissing": "Ollama-Konfiguration fehlt für die Erstellung des Embedders", + "openAiCompatibleConfigMissing": "OpenAI-kompatible Konfiguration fehlt für die Erstellung des Embedders", + "geminiConfigMissing": "Gemini-Konfiguration fehlt für die Erstellung des Embedders", + "invalidEmbedderType": "Ungültiger Embedder-Typ konfiguriert: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Stelle sicher, dass die 'Embedding-Dimension' in den OpenAI-kompatiblen Anbietereinstellungen korrekt eingestellt ist.", + "vectorDimensionNotDetermined": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Überprüfe die Modellprofile oder Konfiguration.", + "qdrantUrlMissing": "Qdrant-URL fehlt für die Erstellung des Vektorspeichers", + "codeIndexingNotConfigured": "Kann keine Dienste erstellen: Code-Indizierung ist nicht richtig konfiguriert" } } diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index 012b2323cf..b2ed2a64c4 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "OpenAI configuration missing for embedder creation", + "ollamaConfigMissing": "Ollama configuration missing for embedder creation", + "openAiCompatibleConfigMissing": "OpenAI Compatible configuration missing for embedder creation", + "geminiConfigMissing": "Gemini configuration missing for embedder creation", + "invalidEmbedderType": "Invalid embedder type configured: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.", + "vectorDimensionNotDetermined": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Check model profiles or configuration.", + "qdrantUrlMissing": "Qdrant URL missing for vector store creation", + "codeIndexingNotConfigured": "Cannot create services: Code indexing is not properly configured" } } diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index 5fa46f9c45..8cb0dd7f1f 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Falta la configuración de OpenAI para crear el incrustador", + "ollamaConfigMissing": "Falta la configuración de Ollama para crear el incrustador", + "openAiCompatibleConfigMissing": "Falta la configuración compatible con OpenAI para crear el incrustador", + "geminiConfigMissing": "Falta la configuración de Gemini para crear el incrustador", + "invalidEmbedderType": "Tipo de incrustador configurado inválido: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Asegúrate de que la 'Dimensión de incrustación' esté configurada correctamente en los ajustes del proveedor compatible con OpenAI.", + "vectorDimensionNotDetermined": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Verifica los perfiles del modelo o la configuración.", + "qdrantUrlMissing": "Falta la URL de Qdrant para crear el almacén de vectores", + "codeIndexingNotConfigured": "No se pueden crear servicios: La indexación de código no está configurada correctamente" } } diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index b6ef0d8786..23d70650e8 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Configuration OpenAI manquante pour la création de l'embedder", + "ollamaConfigMissing": "Configuration Ollama manquante pour la création de l'embedder", + "openAiCompatibleConfigMissing": "Configuration compatible OpenAI manquante pour la création de l'embedder", + "geminiConfigMissing": "Configuration Gemini manquante pour la création de l'embedder", + "invalidEmbedderType": "Type d'embedder configuré invalide : {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Assure-toi que la 'Dimension d'embedding' est correctement définie dans les paramètres du fournisseur compatible OpenAI.", + "vectorDimensionNotDetermined": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Vérifie les profils du modèle ou la configuration.", + "qdrantUrlMissing": "URL Qdrant manquante pour la création du stockage de vecteurs", + "codeIndexingNotConfigured": "Impossible de créer les services : L'indexation du code n'est pas correctement configurée" } } diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index 5ec34e5624..e6a0aa2bbc 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "अमान्य बेस यूआरएल। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।", "invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।", "invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।" + }, + "serviceFactory": { + "openAiConfigMissing": "एम्बेडर बनाने के लिए OpenAI कॉन्फ़िगरेशन गायब है", + "ollamaConfigMissing": "एम्बेडर बनाने के लिए Ollama कॉन्फ़िगरेशन गायब है", + "openAiCompatibleConfigMissing": "एम्बेडर बनाने के लिए OpenAI संगत कॉन्फ़िगरेशन गायब है", + "geminiConfigMissing": "एम्बेडर बनाने के लिए Gemini कॉन्फ़िगरेशन गायब है", + "invalidEmbedderType": "अमान्य एम्बेडर प्रकार कॉन्फ़िगर किया गया: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। कृपया सुनिश्चित करें कि OpenAI-संगत प्रदाता सेटिंग्स में 'एम्बेडिंग आयाम' सही तरीके से सेट है।", + "vectorDimensionNotDetermined": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। मॉडल प्रोफ़ाइल या कॉन्फ़िगरेशन की जांच करें।", + "qdrantUrlMissing": "वेक्टर स्टोर बनाने के लिए Qdrant URL गायब है", + "codeIndexingNotConfigured": "सेवाएं नहीं बना सकते: कोड इंडेक्सिंग ठीक से कॉन्फ़िगर नहीं है" } } diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index 0082ec8dcf..5ba042e7ef 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Konfigurasi OpenAI tidak ada untuk membuat embedder", + "ollamaConfigMissing": "Konfigurasi Ollama tidak ada untuk membuat embedder", + "openAiCompatibleConfigMissing": "Konfigurasi yang kompatibel dengan OpenAI tidak ada untuk membuat embedder", + "geminiConfigMissing": "Konfigurasi Gemini tidak ada untuk membuat embedder", + "invalidEmbedderType": "Tipe embedder yang dikonfigurasi tidak valid: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Pastikan 'Dimensi Embedding' diatur dengan benar di pengaturan penyedia yang kompatibel dengan OpenAI.", + "vectorDimensionNotDetermined": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Periksa profil model atau konfigurasi.", + "qdrantUrlMissing": "URL Qdrant tidak ada untuk membuat penyimpanan vektor", + "codeIndexingNotConfigured": "Tidak dapat membuat layanan: Pengindeksan kode tidak dikonfigurasi dengan benar" } } diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index 19e6af332f..38f502ae78 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Configurazione OpenAI mancante per la creazione dell'embedder", + "ollamaConfigMissing": "Configurazione Ollama mancante per la creazione dell'embedder", + "openAiCompatibleConfigMissing": "Configurazione compatibile con OpenAI mancante per la creazione dell'embedder", + "geminiConfigMissing": "Configurazione Gemini mancante per la creazione dell'embedder", + "invalidEmbedderType": "Tipo di embedder configurato non valido: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Assicurati che la 'Dimensione di embedding' sia impostata correttamente nelle impostazioni del provider compatibile con OpenAI.", + "vectorDimensionNotDetermined": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Controlla i profili del modello o la configurazione.", + "qdrantUrlMissing": "URL Qdrant mancante per la creazione dello storage vettoriale", + "codeIndexingNotConfigured": "Impossibile creare i servizi: L'indicizzazione del codice non è configurata correttamente" } } diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index fcf426a14c..817287cff9 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "無効なベースURLです。URL構成を確認してください。", "invalidModel": "無効なモデルです。モデル構成を確認してください。", "invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。" + }, + "serviceFactory": { + "openAiConfigMissing": "エンベッダー作成のためのOpenAI設定がありません", + "ollamaConfigMissing": "エンベッダー作成のためのOllama設定がありません", + "openAiCompatibleConfigMissing": "エンベッダー作成のためのOpenAI互換設定がありません", + "geminiConfigMissing": "エンベッダー作成のためのGemini設定がありません", + "invalidEmbedderType": "無効なエンベッダータイプが設定されています: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。OpenAI互換プロバイダー設定で「埋め込み次元」が正しく設定されていることを確認してください。", + "vectorDimensionNotDetermined": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。モデルプロファイルまたは設定を確認してください。", + "qdrantUrlMissing": "ベクターストア作成のためのQdrant URLがありません", + "codeIndexingNotConfigured": "サービスを作成できません: コードインデックスが正しく設定されていません" } } diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 16d119c959..272ac74cc7 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "잘못된 기본 URL입니다. URL 구성을 확인하세요.", "invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.", "invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요." + }, + "serviceFactory": { + "openAiConfigMissing": "임베더 생성을 위한 OpenAI 구성이 누락되었습니다", + "ollamaConfigMissing": "임베더 생성을 위한 Ollama 구성이 누락되었습니다", + "openAiCompatibleConfigMissing": "임베더 생성을 위한 OpenAI 호환 구성이 누락되었습니다", + "geminiConfigMissing": "임베더 생성을 위한 Gemini 구성이 누락되었습니다", + "invalidEmbedderType": "잘못된 임베더 유형이 구성되었습니다: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. OpenAI 호환 프로바이더 설정에서 '임베딩 차원'이 올바르게 설정되어 있는지 확인하세요.", + "vectorDimensionNotDetermined": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. 모델 프로필 또는 구성을 확인하세요.", + "qdrantUrlMissing": "벡터 저장소 생성을 위한 Qdrant URL이 누락되었습니다", + "codeIndexingNotConfigured": "서비스를 생성할 수 없습니다: 코드 인덱싱이 올바르게 구성되지 않았습니다" } } diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index 9eeb5a04ea..055d27b607 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "Ongeldige basis-URL. Controleer je URL-configuratie.", "invalidModel": "Ongeldig model. Controleer je modelconfiguratie.", "invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie." + }, + "serviceFactory": { + "openAiConfigMissing": "OpenAI-configuratie ontbreekt voor het maken van embedder", + "ollamaConfigMissing": "Ollama-configuratie ontbreekt voor het maken van embedder", + "openAiCompatibleConfigMissing": "OpenAI-compatibele configuratie ontbreekt voor het maken van embedder", + "geminiConfigMissing": "Gemini-configuratie ontbreekt voor het maken van embedder", + "invalidEmbedderType": "Ongeldig embedder-type geconfigureerd: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Zorg ervoor dat de 'Embedding Dimensie' correct is ingesteld in de OpenAI-compatibele provider-instellingen.", + "vectorDimensionNotDetermined": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Controleer modelprofielen of configuratie.", + "qdrantUrlMissing": "Qdrant URL ontbreekt voor het maken van vectoropslag", + "codeIndexingNotConfigured": "Kan geen services maken: Code-indexering is niet correct geconfigureerd" } } diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index dd10c1ec4c..874fcf7e00 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -37,5 +37,16 @@ "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ę." + }, + "serviceFactory": { + "openAiConfigMissing": "Brak konfiguracji OpenAI do utworzenia embeddera", + "ollamaConfigMissing": "Brak konfiguracji Ollama do utworzenia embeddera", + "openAiCompatibleConfigMissing": "Brak konfiguracji kompatybilnej z OpenAI do utworzenia embeddera", + "geminiConfigMissing": "Brak konfiguracji Gemini do utworzenia embeddera", + "invalidEmbedderType": "Skonfigurowano nieprawidłowy typ embeddera: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Upewnij się, że 'Wymiar osadzania' jest poprawnie ustawiony w ustawieniach dostawcy kompatybilnego z OpenAI.", + "vectorDimensionNotDetermined": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Sprawdź profile modelu lub konfigurację.", + "qdrantUrlMissing": "Brak adresu URL Qdrant do utworzenia magazynu wektorów", + "codeIndexingNotConfigured": "Nie można utworzyć usług: Indeksowanie kodu nie jest poprawnie skonfigurowane" } } diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index ec1db07113..741a10423e 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Configuração do OpenAI ausente para criação do embedder", + "ollamaConfigMissing": "Configuração do Ollama ausente para criação do embedder", + "openAiCompatibleConfigMissing": "Configuração compatível com OpenAI ausente para criação do embedder", + "geminiConfigMissing": "Configuração do Gemini ausente para criação do embedder", + "invalidEmbedderType": "Tipo de embedder configurado inválido: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Certifique-se de que a 'Dimensão de Embedding' esteja configurada corretamente nas configurações do provedor compatível com OpenAI.", + "vectorDimensionNotDetermined": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Verifique os perfis do modelo ou a configuração.", + "qdrantUrlMissing": "URL do Qdrant ausente para criação do armazenamento de vetores", + "codeIndexingNotConfigured": "Não é possível criar serviços: A indexação de código não está configurada corretamente" } } diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 931dca5bbd..78a21872c6 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "Неверный базовый URL. Проверьте конфигурацию URL.", "invalidModel": "Неверная модель. Проверьте конфигурацию модели.", "invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию." + }, + "serviceFactory": { + "openAiConfigMissing": "Отсутствует конфигурация OpenAI для создания эмбеддера", + "ollamaConfigMissing": "Отсутствует конфигурация Ollama для создания эмбеддера", + "openAiCompatibleConfigMissing": "Отсутствует конфигурация, совместимая с OpenAI, для создания эмбеддера", + "geminiConfigMissing": "Отсутствует конфигурация Gemini для создания эмбеддера", + "invalidEmbedderType": "Настроен недопустимый тип эмбеддера: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Убедитесь, что 'Размерность эмбеддинга' правильно установлена в настройках провайдера, совместимого с OpenAI.", + "vectorDimensionNotDetermined": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Проверьте профили модели или конфигурацию.", + "qdrantUrlMissing": "Отсутствует URL Qdrant для создания векторного хранилища", + "codeIndexingNotConfigured": "Невозможно создать сервисы: Индексация кода не настроена должным образом" } } diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index 8ff94b0dae..411bad5eb3 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Gömücü oluşturmak için OpenAI yapılandırması eksik", + "ollamaConfigMissing": "Gömücü oluşturmak için Ollama yapılandırması eksik", + "openAiCompatibleConfigMissing": "Gömücü oluşturmak için OpenAI uyumlu yapılandırması eksik", + "geminiConfigMissing": "Gömücü oluşturmak için Gemini yapılandırması eksik", + "invalidEmbedderType": "Geçersiz gömücü türü yapılandırıldı: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. OpenAI uyumlu sağlayıcı ayarlarında 'Gömme Boyutu'nun doğru ayarlandığından emin ol.", + "vectorDimensionNotDetermined": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. Model profillerini veya yapılandırmayı kontrol et.", + "qdrantUrlMissing": "Vektör deposu oluşturmak için Qdrant URL'si eksik", + "codeIndexingNotConfigured": "Hizmetler oluşturulamıyor: Kod indeksleme düzgün yapılandırılmamış" } } diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index 5988219aed..c645199046 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -37,5 +37,16 @@ "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." + }, + "serviceFactory": { + "openAiConfigMissing": "Thiếu cấu hình OpenAI để tạo embedder", + "ollamaConfigMissing": "Thiếu cấu hình Ollama để tạo embedder", + "openAiCompatibleConfigMissing": "Thiếu cấu hình tương thích OpenAI để tạo embedder", + "geminiConfigMissing": "Thiếu cấu hình Gemini để tạo embedder", + "invalidEmbedderType": "Loại embedder được cấu hình không hợp lệ: {{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Hãy đảm bảo 'Kích thước Embedding' được cài đặt đúng trong cài đặt nhà cung cấp tương thích OpenAI.", + "vectorDimensionNotDetermined": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Kiểm tra hồ sơ mô hình hoặc cấu hình.", + "qdrantUrlMissing": "Thiếu URL Qdrant để tạo kho lưu trữ vector", + "codeIndexingNotConfigured": "Không thể tạo dịch vụ: Lập chỉ mục mã không được cấu hình đúng cách" } } diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index 68d41a2f4c..8eb4ae4d2d 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "基础 URL 无效。请检查您的 URL 配置。", "invalidModel": "模型无效。请检查您的模型配置。", "invalidResponse": "嵌入服务响应无效。请检查您的配置。" + }, + "serviceFactory": { + "openAiConfigMissing": "创建嵌入器缺少 OpenAI 配置", + "ollamaConfigMissing": "创建嵌入器缺少 Ollama 配置", + "openAiCompatibleConfigMissing": "创建嵌入器缺少 OpenAI 兼容配置", + "geminiConfigMissing": "创建嵌入器缺少 Gemini 配置", + "invalidEmbedderType": "配置的嵌入器类型无效:{{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请确保在 OpenAI 兼容提供商设置中正确设置了「嵌入维度」。", + "vectorDimensionNotDetermined": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请检查模型配置文件或配置。", + "qdrantUrlMissing": "创建向量存储缺少 Qdrant URL", + "codeIndexingNotConfigured": "无法创建服务:代码索引未正确配置" } } diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 2b9967a930..7bd4dfeba3 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -37,5 +37,16 @@ "invalidBaseUrl": "無效的基礎 URL。請檢查您的 URL 組態。", "invalidModel": "無效的模型。請檢查您的模型組態。", "invalidResponse": "內嵌服務回應無效。請檢查您的組態。" + }, + "serviceFactory": { + "openAiConfigMissing": "建立嵌入器缺少 OpenAI 設定", + "ollamaConfigMissing": "建立嵌入器缺少 Ollama 設定", + "openAiCompatibleConfigMissing": "建立嵌入器缺少 OpenAI 相容設定", + "geminiConfigMissing": "建立嵌入器缺少 Gemini 設定", + "invalidEmbedderType": "設定的嵌入器類型無效:{{embedderProvider}}", + "vectorDimensionNotDeterminedOpenAiCompatible": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請確保在 OpenAI 相容提供商設定中正確設定了「嵌入維度」。", + "vectorDimensionNotDetermined": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請檢查模型設定檔或設定。", + "qdrantUrlMissing": "建立向量儲存缺少 Qdrant URL", + "codeIndexingNotConfigured": "無法建立服務:程式碼索引未正確設定" } } diff --git a/src/services/code-index/__tests__/config-manager.spec.ts b/src/services/code-index/__tests__/config-manager.spec.ts index 994f228e52..4571c9011a 100644 --- a/src/services/code-index/__tests__/config-manager.spec.ts +++ b/src/services/code-index/__tests__/config-manager.spec.ts @@ -135,7 +135,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1024, + codebaseIndexEmbedderModelDimension: 1024, } mockContextProxy.getGlobalState.mockImplementation((key: string) => { if (key === "codebaseIndexConfig") return mockGlobalState @@ -153,12 +153,12 @@ describe("CodeIndexConfigManager", () => { isConfigured: true, embedderProvider: "openai-compatible", modelId: "custom-model", + modelDimension: 1024, openAiOptions: { openAiNativeApiKey: "" }, ollamaOptions: { ollamaBaseUrl: "" }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", - modelDimension: 1024, }, qdrantUrl: "http://qdrant.local", qdrantApiKey: "test-qdrant-key", @@ -213,7 +213,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: "invalid-dimension", // Invalid type + codebaseIndexEmbedderModelDimension: "invalid-dimension", // Invalid type } mockContextProxy.getGlobalState.mockImplementation((key: string) => { if (key === "codebaseIndexConfig") return mockGlobalState @@ -231,13 +231,14 @@ describe("CodeIndexConfigManager", () => { isConfigured: true, embedderProvider: "openai-compatible", modelId: "custom-model", + modelDimension: undefined, // Invalid dimension is converted to undefined openAiOptions: { openAiNativeApiKey: "" }, ollamaOptions: { ollamaBaseUrl: "" }, openAiCompatibleOptions: { baseUrl: "https://api.example.com/v1", apiKey: "test-openai-compatible-key", - modelDimension: "invalid-dimension", }, + geminiOptions: undefined, qdrantUrl: "http://qdrant.local", qdrantApiKey: "test-qdrant-key", searchMinScore: 0.4, @@ -533,7 +534,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1024, + codebaseIndexEmbedderModelDimension: 1024, } } return undefined @@ -554,7 +555,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 2048, + codebaseIndexEmbedderModelDimension: 2048, } } return undefined @@ -573,10 +574,10 @@ describe("CodeIndexConfigManager", () => { codebaseIndexQdrantUrl: "http://qdrant.local", codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", + codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", + codebaseIndexEmbedderModelDimension: 1024, } } - if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1" - if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024 return undefined }) setupSecretMocks({ @@ -594,11 +595,11 @@ describe("CodeIndexConfigManager", () => { codebaseIndexQdrantUrl: "http://qdrant.local", codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", + codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", + codebaseIndexEmbedderModelDimension: 1024, codebaseIndexSearchMinScore: 0.5, // Changed unrelated setting } } - if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1" - if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024 return undefined }) @@ -637,7 +638,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1024, + codebaseIndexEmbedderModelDimension: 1024, } } return undefined @@ -657,7 +658,7 @@ describe("CodeIndexConfigManager", () => { codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "custom-model", codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", - codebaseIndexOpenAiCompatibleModelDimension: 1024, + codebaseIndexEmbedderModelDimension: 1024, } } return undefined @@ -808,10 +809,10 @@ describe("CodeIndexConfigManager", () => { codebaseIndexQdrantUrl: "http://qdrant.local", codebaseIndexEmbedderProvider: "openai-compatible", codebaseIndexEmbedderModelId: "nomic-embed-code", + codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1", // No codebaseIndexSearchMinScore } } - if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1" return undefined }) mockContextProxy.getSecret.mockImplementation((key: string) => { diff --git a/src/services/code-index/__tests__/service-factory.spec.ts b/src/services/code-index/__tests__/service-factory.spec.ts index c2b56a0463..65932225eb 100644 --- a/src/services/code-index/__tests__/service-factory.spec.ts +++ b/src/services/code-index/__tests__/service-factory.spec.ts @@ -146,7 +146,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow("OpenAI configuration missing for embedder creation") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.openAiConfigMissing") }) it("should throw error when Ollama base URL is missing", () => { @@ -161,7 +161,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow("Ollama configuration missing for embedder creation") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.ollamaConfigMissing") }) it("should pass model ID to OpenAI Compatible embedder when using OpenAI Compatible provider", () => { @@ -224,9 +224,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow( - "OpenAI Compatible configuration missing for embedder creation", - ) + expect(() => factory.createEmbedder()).toThrow("serviceFactory.openAiCompatibleConfigMissing") }) it("should throw error when OpenAI Compatible API key is missing", () => { @@ -242,9 +240,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow( - "OpenAI Compatible configuration missing for embedder creation", - ) + expect(() => factory.createEmbedder()).toThrow("serviceFactory.openAiCompatibleConfigMissing") }) it("should throw error when OpenAI Compatible options are missing", () => { @@ -257,9 +253,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow( - "OpenAI Compatible configuration missing for embedder creation", - ) + expect(() => factory.createEmbedder()).toThrow("serviceFactory.openAiCompatibleConfigMissing") }) it("should create GeminiEmbedder when using Gemini provider", () => { @@ -290,7 +284,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow("Gemini configuration missing for embedder creation") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.geminiConfigMissing") }) it("should throw error when Gemini options are missing", () => { @@ -302,7 +296,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow("Gemini configuration missing for embedder creation") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.geminiConfigMissing") }) it("should throw error for invalid embedder provider", () => { @@ -314,7 +308,7 @@ describe("CodeIndexServiceFactory", () => { mockConfigManager.getConfig.mockReturnValue(testConfig as any) // Act & Assert - expect(() => factory.createEmbedder()).toThrow("Invalid embedder type configured: invalid-provider") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.invalidEmbedderType") }) }) @@ -406,8 +400,10 @@ describe("CodeIndexServiceFactory", () => { const testConfig = { embedderProvider: "openai-compatible", modelId: testModelId, + modelDimension: manualDimension, openAiCompatibleOptions: { - modelDimension: manualDimension, + baseUrl: "https://api.example.com/v1", + apiKey: "test-api-key", }, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -463,8 +459,10 @@ describe("CodeIndexServiceFactory", () => { const testConfig = { embedderProvider: "openai-compatible", modelId: testModelId, + modelDimension: 0, // Invalid dimension openAiCompatibleOptions: { - modelDimension: 0, // Invalid dimension + baseUrl: "https://api.example.com/v1", + apiKey: "test-api-key", }, qdrantUrl: "http://localhost:6333", qdrantApiKey: "test-key", @@ -474,7 +472,7 @@ describe("CodeIndexServiceFactory", () => { // Act & Assert expect(() => factory.createVectorStore()).toThrow( - "Could not determine vector dimension for model 'custom-model' with provider 'openai-compatible'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.", + "serviceFactory.vectorDimensionNotDeterminedOpenAiCompatible", ) }) @@ -496,7 +494,7 @@ describe("CodeIndexServiceFactory", () => { // Act & Assert expect(() => factory.createVectorStore()).toThrow( - "Could not determine vector dimension for model 'unknown-model' with provider 'openai-compatible'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.", + "serviceFactory.vectorDimensionNotDeterminedOpenAiCompatible", ) }) @@ -560,9 +558,7 @@ describe("CodeIndexServiceFactory", () => { mockGetModelDimension.mockReturnValue(undefined) // Act & Assert - expect(() => factory.createVectorStore()).toThrow( - "Could not determine vector dimension for model 'unknown-model' with provider 'openai'. Check model profiles or configuration.", - ) + expect(() => factory.createVectorStore()).toThrow("serviceFactory.vectorDimensionNotDetermined") }) it("should throw error when Qdrant URL is missing", () => { @@ -577,7 +573,7 @@ describe("CodeIndexServiceFactory", () => { mockGetModelDimension.mockReturnValue(1536) // Act & Assert - expect(() => factory.createVectorStore()).toThrow("Qdrant URL missing for vector store creation") + expect(() => factory.createVectorStore()).toThrow("serviceFactory.qdrantUrlMissing") }) }) @@ -747,7 +743,7 @@ describe("CodeIndexServiceFactory", () => { await expect(async () => { const embedder = factory.createEmbedder() await factory.validateEmbedder(embedder) - }).rejects.toThrow("OpenAI configuration missing for embedder creation") + }).rejects.toThrow("serviceFactory.openAiConfigMissing") }) it("should return error for unknown embedder provider", async () => { @@ -760,7 +756,7 @@ describe("CodeIndexServiceFactory", () => { // Act & Assert // This should throw when trying to create the embedder - expect(() => factory.createEmbedder()).toThrow("Invalid embedder type configured: unknown-provider") + expect(() => factory.createEmbedder()).toThrow("serviceFactory.invalidEmbedderType") }) }) }) diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 26ea0e2a48..8e2b8e082d 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -13,9 +13,10 @@ export class CodeIndexConfigManager { private isEnabled: boolean = false private embedderProvider: EmbedderProvider = "openai" private modelId?: string + private modelDimension?: number private openAiOptions?: ApiHandlerOptions private ollamaOptions?: ApiHandlerOptions - private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number } + private openAiCompatibleOptions?: { baseUrl: string; apiKey: string } private geminiOptions?: { apiKey: string } private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string @@ -65,9 +66,6 @@ export class CodeIndexConfigManager { // Fix: Read OpenAI Compatible settings from the correct location within codebaseIndexConfig const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? "" const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? "" - const openAiCompatibleModelDimension = codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension as - | number - | undefined const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? "" // Update instance variables with configuration @@ -76,6 +74,23 @@ export class CodeIndexConfigManager { this.qdrantApiKey = qdrantApiKey ?? "" this.searchMinScore = codebaseIndexSearchMinScore this.searchMaxResults = codebaseIndexSearchMaxResults + + // Validate and set model dimension + const rawDimension = codebaseIndexConfig.codebaseIndexEmbedderModelDimension + if (rawDimension !== undefined && rawDimension !== null) { + const dimension = Number(rawDimension) + if (!isNaN(dimension) && dimension > 0) { + this.modelDimension = dimension + } else { + console.warn( + `Invalid codebaseIndexEmbedderModelDimension value: ${rawDimension}. Must be a positive number.`, + ) + this.modelDimension = undefined + } + } else { + this.modelDimension = undefined + } + this.openAiOptions = { openAiNativeApiKey: openAiKey } // Set embedder provider with support for openai-compatible @@ -100,7 +115,6 @@ export class CodeIndexConfigManager { ? { baseUrl: openAiCompatibleBaseUrl, apiKey: openAiCompatibleApiKey, - modelDimension: openAiCompatibleModelDimension, } : undefined @@ -117,6 +131,7 @@ export class CodeIndexConfigManager { isConfigured: boolean embedderProvider: EmbedderProvider modelId?: string + modelDimension?: number openAiOptions?: ApiHandlerOptions ollamaOptions?: ApiHandlerOptions openAiCompatibleOptions?: { baseUrl: string; apiKey: string } @@ -133,11 +148,11 @@ export class CodeIndexConfigManager { configured: this.isConfigured(), embedderProvider: this.embedderProvider, modelId: this.modelId, + modelDimension: this.modelDimension, openAiKey: this.openAiOptions?.openAiNativeApiKey ?? "", ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl ?? "", openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "", openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "", - openAiCompatibleModelDimension: this.openAiCompatibleOptions?.modelDimension, geminiApiKey: this.geminiOptions?.apiKey ?? "", qdrantUrl: this.qdrantUrl ?? "", qdrantApiKey: this.qdrantApiKey ?? "", @@ -158,6 +173,7 @@ export class CodeIndexConfigManager { isConfigured: this.isConfigured(), embedderProvider: this.embedderProvider, modelId: this.modelId, + modelDimension: this.modelDimension, openAiOptions: this.openAiOptions, ollamaOptions: this.ollamaOptions, openAiCompatibleOptions: this.openAiCompatibleOptions, @@ -225,7 +241,7 @@ export class CodeIndexConfigManager { const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? "" const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? "" const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? "" - const prevOpenAiCompatibleModelDimension = prev?.openAiCompatibleModelDimension + const prevModelDimension = prev?.modelDimension const prevGeminiApiKey = prev?.geminiApiKey ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" @@ -257,7 +273,7 @@ export class CodeIndexConfigManager { const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? "" const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? "" const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? "" - const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension + const currentModelDimension = this.modelDimension const currentGeminiApiKey = this.geminiOptions?.apiKey ?? "" const currentQdrantUrl = this.qdrantUrl ?? "" const currentQdrantApiKey = this.qdrantApiKey ?? "" @@ -277,11 +293,9 @@ export class CodeIndexConfigManager { return true } - // Check for OpenAI Compatible modelDimension changes - if (this.embedderProvider === "openai-compatible" || prevProvider === "openai-compatible") { - if (prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension) { - return true - } + // Check for model dimension changes (generic for all providers) + if (prevModelDimension !== currentModelDimension) { + return true } if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) { @@ -332,6 +346,7 @@ export class CodeIndexConfigManager { isConfigured: this.isConfigured(), embedderProvider: this.embedderProvider, modelId: this.modelId, + modelDimension: this.modelDimension, openAiOptions: this.openAiOptions, ollamaOptions: this.ollamaOptions, openAiCompatibleOptions: this.openAiCompatibleOptions, @@ -381,6 +396,14 @@ export class CodeIndexConfigManager { return this.modelId } + /** + * Gets the current model dimension being used for embeddings. + * Returns the explicitly configured dimension or undefined if not set. + */ + public get currentModelDimension(): number | undefined { + return this.modelDimension + } + /** * Gets the configured minimum search score based on user setting, model-specific threshold, or fallback. * Priority: 1) User setting, 2) Model-specific threshold, 3) Default DEFAULT_SEARCH_MIN_SCORE constant. diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index 0600f29c2a..f4eb55c183 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -9,9 +9,10 @@ export interface CodeIndexConfig { isConfigured: boolean embedderProvider: EmbedderProvider modelId?: string + modelDimension?: number // Generic dimension property for all providers openAiOptions?: ApiHandlerOptions ollamaOptions?: ApiHandlerOptions - openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number } + openAiCompatibleOptions?: { baseUrl: string; apiKey: string } geminiOptions?: { apiKey: string } qdrantUrl?: string qdrantApiKey?: string @@ -27,11 +28,11 @@ export type PreviousConfigSnapshot = { configured: boolean embedderProvider: EmbedderProvider modelId?: string + modelDimension?: number // Generic dimension property openAiKey?: string ollamaBaseUrl?: string openAiCompatibleBaseUrl?: string openAiCompatibleApiKey?: string - openAiCompatibleModelDimension?: number geminiApiKey?: string qdrantUrl?: string qdrantApiKey?: string diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index a9c84481a6..818dafb497 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -10,6 +10,7 @@ import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces import { CodeIndexConfigManager } from "./config-manager" import { CacheManager } from "./cache-manager" import { Ignore } from "ignore" +import { t } from "../../i18n" /** * Factory class responsible for creating and configuring code indexing service dependencies. @@ -33,7 +34,7 @@ export class CodeIndexServiceFactory { const apiKey = config.openAiOptions?.openAiNativeApiKey if (!apiKey) { - throw new Error("OpenAI configuration missing for embedder creation") + throw new Error(t("embeddings:serviceFactory.openAiConfigMissing")) } return new OpenAiEmbedder({ ...config.openAiOptions, @@ -41,7 +42,7 @@ export class CodeIndexServiceFactory { }) } else if (provider === "ollama") { if (!config.ollamaOptions?.ollamaBaseUrl) { - throw new Error("Ollama configuration missing for embedder creation") + throw new Error(t("embeddings:serviceFactory.ollamaConfigMissing")) } return new CodeIndexOllamaEmbedder({ ...config.ollamaOptions, @@ -49,7 +50,7 @@ export class CodeIndexServiceFactory { }) } else if (provider === "openai-compatible") { if (!config.openAiCompatibleOptions?.baseUrl || !config.openAiCompatibleOptions?.apiKey) { - throw new Error("OpenAI Compatible configuration missing for embedder creation") + throw new Error(t("embeddings:serviceFactory.openAiCompatibleConfigMissing")) } return new OpenAICompatibleEmbedder( config.openAiCompatibleOptions.baseUrl, @@ -58,12 +59,14 @@ export class CodeIndexServiceFactory { ) } else if (provider === "gemini") { if (!config.geminiOptions?.apiKey) { - throw new Error("Gemini configuration missing for embedder creation") + throw new Error(t("embeddings:serviceFactory.geminiConfigMissing")) } return new GeminiEmbedder(config.geminiOptions.apiKey) } - throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`) + throw new Error( + t("embeddings:serviceFactory.invalidEmbedderType", { embedderProvider: config.embedderProvider }), + ) } /** @@ -96,33 +99,29 @@ export class CodeIndexServiceFactory { let vectorSize: number | undefined - if (provider === "openai-compatible") { - if (config.openAiCompatibleOptions?.modelDimension && config.openAiCompatibleOptions.modelDimension > 0) { - vectorSize = config.openAiCompatibleOptions.modelDimension - } else { - // Fallback if not provided or invalid in openAiCompatibleOptions - vectorSize = getModelDimension(provider, modelId) - } + // First check if a manual dimension is provided (works for all providers) + if (config.modelDimension && config.modelDimension > 0) { + vectorSize = config.modelDimension } else if (provider === "gemini") { // Gemini's text-embedding-004 has a fixed dimension of 768 vectorSize = 768 } else { + // Fall back to model-specific dimension from profiles vectorSize = getModelDimension(provider, modelId) } - if (vectorSize === undefined) { - let errorMessage = `Could not determine vector dimension for model '${modelId}' with provider '${provider}'. ` + if (vectorSize === undefined || vectorSize <= 0) { if (provider === "openai-compatible") { - errorMessage += `Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.` + throw new Error( + t("embeddings:serviceFactory.vectorDimensionNotDeterminedOpenAiCompatible", { modelId, provider }), + ) } else { - errorMessage += `Check model profiles or configuration.` + throw new Error(t("embeddings:serviceFactory.vectorDimensionNotDetermined", { modelId, provider })) } - throw new Error(errorMessage) } if (!config.qdrantUrl) { - // This check remains important - throw new Error("Qdrant URL missing for vector store creation") + throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing")) } // Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?) @@ -170,7 +169,7 @@ export class CodeIndexServiceFactory { fileWatcher: IFileWatcher } { if (!this.configManager.isFeatureConfigured) { - throw new Error("Cannot create services: Code indexing is not properly configured") + throw new Error(t("embeddings:serviceFactory.codeIndexingNotConfigured")) } const embedder = this.createEmbedder() diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 312b7ceb11..fa9fb67310 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -241,8 +241,8 @@ export interface WebviewMessage { codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini" codebaseIndexEmbedderBaseUrl?: string codebaseIndexEmbedderModelId: string + codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers codebaseIndexOpenAiCompatibleBaseUrl?: string - codebaseIndexOpenAiCompatibleModelDimension?: number codebaseIndexSearchMaxResults?: number codebaseIndexSearchMinScore?: number diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 6c9ea1c204..22b209bcd8 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -51,6 +51,7 @@ interface LocalCodeIndexSettings { codebaseIndexEmbedderProvider: EmbedderProvider codebaseIndexEmbedderBaseUrl?: string codebaseIndexEmbedderModelId: string + codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers codebaseIndexSearchMaxResults?: number codebaseIndexSearchMinScore?: number @@ -59,7 +60,6 @@ interface LocalCodeIndexSettings { codeIndexQdrantApiKey?: string codebaseIndexOpenAiCompatibleBaseUrl?: string codebaseIndexOpenAiCompatibleApiKey?: string - codebaseIndexOpenAiCompatibleModelDimension?: number codebaseIndexGeminiApiKey?: string } @@ -85,13 +85,13 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexEmbedderProvider: "openai", codebaseIndexEmbedderBaseUrl: "", codebaseIndexEmbedderModelId: "", + codebaseIndexEmbedderModelDimension: undefined, codebaseIndexSearchMaxResults: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS, codebaseIndexSearchMinScore: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE, codeIndexOpenAiKey: "", codeIndexQdrantApiKey: "", codebaseIndexOpenAiCompatibleBaseUrl: "", codebaseIndexOpenAiCompatibleApiKey: "", - codebaseIndexOpenAiCompatibleModelDimension: undefined, codebaseIndexGeminiApiKey: "", }) @@ -115,6 +115,8 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai", codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "", codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "", + codebaseIndexEmbedderModelDimension: + codebaseIndexConfig.codebaseIndexEmbedderModelDimension || undefined, codebaseIndexSearchMaxResults: codebaseIndexConfig.codebaseIndexSearchMaxResults ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS, codebaseIndexSearchMinScore: @@ -123,8 +125,6 @@ export const CodeIndexPopover: React.FC = ({ codeIndexQdrantApiKey: "", codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "", codebaseIndexOpenAiCompatibleApiKey: "", - codebaseIndexOpenAiCompatibleModelDimension: - codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension || undefined, codebaseIndexGeminiApiKey: "", } setInitialSettings(settings) @@ -509,12 +509,10 @@ export const CodeIndexPopover: React.FC = ({ {t("settings:codeIndex.modelDimensionLabel")} { const value = e.target.value ? parseInt(e.target.value) : undefined - updateSetting("codebaseIndexOpenAiCompatibleModelDimension", value) + updateSetting("codebaseIndexEmbedderModelDimension", value) }} placeholder={t("settings:codeIndex.modelDimensionPlaceholder")} className="w-full" From 150d71256f583b68e0024b4f31a330f50c567c86 Mon Sep 17 00:00:00 2001 From: Murilo Pires <50873657+MuriloFP@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:19:02 -0300 Subject: [PATCH 4/6] Chat index UI enhancements (#5451) Co-authored-by: Daniel Riccio --- .../src/components/chat/ChatTextArea.tsx | 55 +- .../src/components/chat/CodeIndexPopover.tsx | 535 ++++++++++-------- .../components/chat/IndexingStatusBadge.tsx | 14 +- webview-ui/src/i18n/locales/ca/settings.json | 1 + webview-ui/src/i18n/locales/de/settings.json | 97 ++-- webview-ui/src/i18n/locales/en/settings.json | 1 + webview-ui/src/i18n/locales/es/settings.json | 1 + webview-ui/src/i18n/locales/fr/settings.json | 1 + webview-ui/src/i18n/locales/hi/settings.json | 1 + webview-ui/src/i18n/locales/id/settings.json | 1 + webview-ui/src/i18n/locales/it/settings.json | 1 + webview-ui/src/i18n/locales/ja/settings.json | 1 + webview-ui/src/i18n/locales/ko/settings.json | 1 + webview-ui/src/i18n/locales/nl/settings.json | 1 + webview-ui/src/i18n/locales/pl/settings.json | 1 + .../src/i18n/locales/pt-BR/settings.json | 1 + webview-ui/src/i18n/locales/ru/settings.json | 1 + webview-ui/src/i18n/locales/tr/settings.json | 1 + webview-ui/src/i18n/locales/vi/settings.json | 1 + .../src/i18n/locales/zh-CN/settings.json | 1 + .../src/i18n/locales/zh-TW/settings.json | 1 + 21 files changed, 402 insertions(+), 316 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 51279062d2..e44caedb7c 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -943,10 +943,11 @@ const ChatTextArea = forwardRef( "resize-none", "overflow-x-hidden", "overflow-y-auto", - "pr-2", + "pr-9", "flex-none flex-grow", "z-[2]", "scrollbar-none", + "scrollbar-hide", )} onScroll={() => updateHighlights()} /> @@ -961,24 +962,31 @@ const ChatTextArea = forwardRef( )} +
+ +
+ +
+ +
+ {!inputValue && (
+ className="absolute left-2 z-30 pr-9 flex items-center h-8" + style={{ bottom: "0.25rem", color: "var(--vscode-tab-inactiveForeground)" }}> {placeholderBottomText}
)} @@ -1134,25 +1142,12 @@ const ChatTextArea = forwardRef(
{codebaseIndexConfig?.codebaseIndexEnabled && } - -
diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 22b209bcd8..7479115081 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -72,6 +72,7 @@ export const CodeIndexPopover: React.FC = ({ const { codebaseIndexConfig, codebaseIndexModels } = useExtensionState() const [open, setOpen] = useState(false) const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false) + const [isSetupSettingsOpen, setIsSetupSettingsOpen] = useState(false) const [indexingStatus, setIndexingStatus] = useState(externalIndexingStatus) @@ -304,7 +305,7 @@ export const CodeIndexPopover: React.FC = ({ {children} = ({ collisionPadding={16} avoidCollisions={true} container={portalContainer}> -
-

{t("settings:codeIndex.title")}

-

+

+
+

{t("settings:codeIndex.title")}

+
+

= ({

-
+
{/* Status Section */}

{t("settings:codeIndex.statusTitle")}

@@ -357,233 +360,297 @@ export const CodeIndexPopover: React.FC = ({ )}
- {/* Embedder Provider Section */} -
- - -
+ {/* Setup Settings Disclosure */} +
+ - {/* Provider-specific settings */} - {currentSettings.codebaseIndexEmbedderProvider === "openai" && ( - <> -
- - updateSetting("codeIndexOpenAiKey", e.target.value)} - placeholder={t("settings:codeIndex.openAiKeyPlaceholder")} - className="w-full" - /> + {isSetupSettingsOpen && ( +
+ {/* Embedder Provider Section */} +
+ + +
+ + {/* Provider-specific settings */} + {currentSettings.codebaseIndexEmbedderProvider === "openai" && ( + <> +
+ + + updateSetting("codeIndexOpenAiKey", e.target.value) + } + placeholder={t("settings:codeIndex.openAiKeyPlaceholder")} + className="w-full" + /> +
+ +
+ + + updateSetting("codebaseIndexEmbedderModelId", e.target.value) + } + className="w-full"> + + {t("settings:codeIndex.selectModel")} + + {getAvailableModels().map((modelId) => { + const model = + codebaseIndexModels?.[ + currentSettings.codebaseIndexEmbedderProvider + ]?.[modelId] + return ( + + {modelId}{" "} + {model + ? t("settings:codeIndex.modelDimensions", { + dimension: model.dimension, + }) + : ""} + + ) + })} + +
+ + )} + + {currentSettings.codebaseIndexEmbedderProvider === "ollama" && ( + <> +
+ + + updateSetting("codebaseIndexEmbedderBaseUrl", e.target.value) + } + placeholder={t("settings:codeIndex.ollamaUrlPlaceholder")} + className="w-full" + /> +
+ +
+ + + updateSetting("codebaseIndexEmbedderModelId", e.target.value) + } + className="w-full"> + + {t("settings:codeIndex.selectModel")} + + {getAvailableModels().map((modelId) => { + const model = + codebaseIndexModels?.[ + currentSettings.codebaseIndexEmbedderProvider + ]?.[modelId] + return ( + + {modelId}{" "} + {model + ? t("settings:codeIndex.modelDimensions", { + dimension: model.dimension, + }) + : ""} + + ) + })} + +
+ + )} + + {currentSettings.codebaseIndexEmbedderProvider === "openai-compatible" && ( + <> +
+ + + updateSetting( + "codebaseIndexOpenAiCompatibleBaseUrl", + e.target.value, + ) + } + placeholder={t("settings:codeIndex.openAiCompatibleBaseUrlPlaceholder")} + className="w-full" + /> +
+ +
+ + + updateSetting("codebaseIndexOpenAiCompatibleApiKey", e.target.value) + } + placeholder={t("settings:codeIndex.openAiCompatibleApiKeyPlaceholder")} + className="w-full" + /> +
+ +
+ + + updateSetting("codebaseIndexEmbedderModelId", e.target.value) + } + placeholder={t("settings:codeIndex.modelPlaceholder")} + className="w-full" + /> +
+ +
+ + { + const value = e.target.value ? parseInt(e.target.value) : undefined + updateSetting("codebaseIndexEmbedderModelDimension", value) + }} + placeholder={t("settings:codeIndex.modelDimensionPlaceholder")} + className="w-full" + /> +
+ + )} + + {currentSettings.codebaseIndexEmbedderProvider === "gemini" && ( + <> +
+ + + updateSetting("codebaseIndexGeminiApiKey", e.target.value) + } + placeholder={t("settings:codeIndex.geminiApiKeyPlaceholder")} + className="w-full" + /> +
+ +
+ + + updateSetting("codebaseIndexEmbedderModelId", e.target.value) + } + className="w-full"> + + {t("settings:codeIndex.selectModel")} + + {getAvailableModels().map((modelId) => { + const model = + codebaseIndexModels?.[ + currentSettings.codebaseIndexEmbedderProvider + ]?.[modelId] + return ( + + {modelId}{" "} + {model + ? t("settings:codeIndex.modelDimensions", { + dimension: model.dimension, + }) + : ""} + + ) + })} + +
+ + )} + + {/* Qdrant Settings */} +
+ + updateSetting("codebaseIndexQdrantUrl", e.target.value)} + placeholder={t("settings:codeIndex.qdrantUrlPlaceholder")} + className="w-full" + /> +
+ +
+ + updateSetting("codeIndexQdrantApiKey", e.target.value)} + placeholder={t("settings:codeIndex.qdrantApiKeyPlaceholder")} + className="w-full" + /> +
- -
- - updateSetting("codebaseIndexEmbedderModelId", e.target.value)} - className="w-full"> - {t("settings:codeIndex.selectModel")} - {getAvailableModels().map((modelId) => { - const model = - codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[ - modelId - ] - return ( - - {modelId}{" "} - {model - ? t("settings:codeIndex.modelDimensions", { - dimension: model.dimension, - }) - : ""} - - ) - })} - -
- - )} - - {currentSettings.codebaseIndexEmbedderProvider === "ollama" && ( - <> -
- - updateSetting("codebaseIndexEmbedderBaseUrl", e.target.value)} - placeholder={t("settings:codeIndex.ollamaUrlPlaceholder")} - className="w-full" - /> -
- -
- - updateSetting("codebaseIndexEmbedderModelId", e.target.value)} - className="w-full"> - {t("settings:codeIndex.selectModel")} - {getAvailableModels().map((modelId) => { - const model = - codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[ - modelId - ] - return ( - - {modelId}{" "} - {model - ? t("settings:codeIndex.modelDimensions", { - dimension: model.dimension, - }) - : ""} - - ) - })} - -
- - )} - - {currentSettings.codebaseIndexEmbedderProvider === "openai-compatible" && ( - <> -
- - - updateSetting("codebaseIndexOpenAiCompatibleBaseUrl", e.target.value) - } - placeholder={t("settings:codeIndex.openAiCompatibleBaseUrlPlaceholder")} - className="w-full" - /> -
- -
- - - updateSetting("codebaseIndexOpenAiCompatibleApiKey", e.target.value) - } - placeholder={t("settings:codeIndex.openAiCompatibleApiKeyPlaceholder")} - className="w-full" - /> -
- -
- - updateSetting("codebaseIndexEmbedderModelId", e.target.value)} - placeholder={t("settings:codeIndex.modelPlaceholder")} - className="w-full" - /> -
- -
- - { - const value = e.target.value ? parseInt(e.target.value) : undefined - updateSetting("codebaseIndexEmbedderModelDimension", value) - }} - placeholder={t("settings:codeIndex.modelDimensionPlaceholder")} - className="w-full" - /> -
- - )} - - {currentSettings.codebaseIndexEmbedderProvider === "gemini" && ( - <> -
- - updateSetting("codebaseIndexGeminiApiKey", e.target.value)} - placeholder={t("settings:codeIndex.geminiApiKeyPlaceholder")} - className="w-full" - /> -
- -
- - updateSetting("codebaseIndexEmbedderModelId", e.target.value)} - className="w-full"> - {t("settings:codeIndex.selectModel")} - {getAvailableModels().map((modelId) => { - const model = - codebaseIndexModels?.[currentSettings.codebaseIndexEmbedderProvider]?.[ - modelId - ] - return ( - - {modelId}{" "} - {model - ? t("settings:codeIndex.modelDimensions", { - dimension: model.dimension, - }) - : ""} - - ) - })} - -
- - )} - - {/* Qdrant Settings */} -
- - updateSetting("codebaseIndexQdrantUrl", e.target.value)} - placeholder={t("settings:codeIndex.qdrantUrlPlaceholder")} - className="w-full" - /> -
- -
- - updateSetting("codeIndexQdrantApiKey", e.target.value)} - placeholder={t("settings:codeIndex.qdrantApiKeyPlaceholder")} - className="w-full" - /> + )}
{/* Advanced Settings Disclosure */} @@ -594,11 +661,13 @@ export const CodeIndexPopover: React.FC = ({ aria-expanded={isAdvancedSettingsOpen}> - {t("settings:codeIndex.advancedConfigLabel")} + + {t("settings:codeIndex.advancedConfigLabel")} + {isAdvancedSettingsOpen && ( -
+
{/* Search Score Threshold Slider */}
@@ -691,7 +760,7 @@ export const CodeIndexPopover: React.FC = ({
{/* Action Buttons */} -
+
{(indexingStatus.systemStatus === "Error" || indexingStatus.systemStatus === "Standby") && ( = ({ className onMouseEnter={handleMouseEnterButton} onMouseLeave={handleMouseLeaveButton} className={cn( - "flex items-center justify-center w-7 h-7 rounded-md", - "bg-transparent hover:bg-vscode-list-hoverBackground", - "cursor-pointer transition-all duration-200", - "opacity-85 hover:opacity-100 relative", + "relative inline-flex items-center justify-center", + "bg-transparent border-none p-1.5", + "rounded-md min-w-[28px] min-h-[28px]", + "opacity-85 text-vscode-foreground", + "transition-all duration-150", + "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", + "active:bg-[rgba(255,255,255,0.1)]", + className, )} aria-label={getTooltipText()}> - {/* Status dot */} Mehr erfahren" + "description": "Verzögerung in Millisekunden, die nach der Befehlsausführung hinzugefügt wird. Die Standardeinstellung von 0 deaktiviert die Verzögerung vollständig. Dies kann dazu beitragen, dass die Befehlsausgabe in Terminals mit Timing-Problemen vollständig erfasst wird. In den meisten Terminals wird dies durch Setzen von `PROMPT_COMMAND='sleep N'` und Powershell fügt `start-sleep` am Ende jedes Befehls hinzu. Ursprünglich war dies eine Lösung für VSCode-Bug#237208 und ist möglicherweise nicht mehr erforderlich. <0>Mehr erfahren" }, "compressProgressBar": { "label": "Fortschrittsbalken-Ausgabe komprimieren", @@ -501,72 +502,72 @@ }, "zshP10k": { "label": "Powerlevel10k-Integration aktivieren", - "description": "Wenn aktiviert, wird POWERLEVEL9K_TERM_SHELL_INTEGRATION=true gesetzt, um die Shell-Integrationsfunktionen von Powerlevel10k zu aktivieren. <0>Mehr erfahren" + "description": "Wenn aktiviert, wird POWERLEVEL9K_INSTANT_PROMPT=quiet gesetzt, um die Powerlevel10k-Integration zu aktivieren. Dies kann die Leistung verbessern, indem der Prompt sofort angezeigt wird. <0>Mehr erfahren" }, "zdotdir": { - "label": "ZDOTDIR-Behandlung aktivieren", - "description": "Erstellt bei Aktivierung ein temporäres Verzeichnis für ZDOTDIR, um die zsh-Shell-Integration korrekt zu handhaben. Dies stellt sicher, dass die VSCode-Shell-Integration mit zsh funktioniert und dabei deine zsh-Konfiguration erhalten bleibt. <0>Mehr erfahren" + "label": "ZDOTDIR Handhabung aktivieren", + "description": "Wenn aktiviert, wird ein temporäres Verzeichnis für ZDOTDIR erstellt, um die Zsh-Shell-Integration ordnungsgemäß zu handhaben. Dies stellt sicher, dass die VSCode-Shell-Integration mit Zsh korrekt funktioniert, während deine Zsh-Konfiguration erhalten bleibt. <0>Mehr erfahren" }, "inheritEnv": { - "label": "Umgebungsvariablen übernehmen", - "description": "Wenn aktiviert, übernimmt das Terminal Umgebungsvariablen vom übergeordneten VSCode-Prozess, wie z.B. in Benutzerprofilen definierte Shell-Integrationseinstellungen. Dies schaltet direkt die globale VSCode-Einstellung `terminal.integrated.inheritEnv` um. <0>Mehr erfahren" + "label": "Umgebungsvariablen erben", + "description": "Wenn aktiviert, erbt das Terminal Umgebungsvariablen aus dem übergeordneten Prozess von VSCode, wie z.B. benutzerdefinierte Shell-Integrationseinstellungen. Dies schaltet direkt die globale VSCode-Einstellung `terminal.integrated.inheritEnv` um. <0>Mehr erfahren" } }, "advanced": { "diff": { "label": "Bearbeitung durch Diffs aktivieren", - "description": "Wenn aktiviert, kann Roo Dateien schneller bearbeiten und lehnt automatisch gekürzte vollständige Dateischreibvorgänge ab. Funktioniert am besten mit dem neuesten Claude 3.7 Sonnet-Modell.", + "description": "Wenn aktiviert, kann Roo Dateien schneller bearbeiten und lehnt automatisch abgeschnittene vollständige Dateischreibvorgänge ab. Funktioniert am besten mit dem neuesten Claude 3.7 Sonnet-Modell.", "strategy": { "label": "Diff-Strategie", "options": { - "standard": "Standard (Einzelner Block)", - "multiBlock": "Experimentell: Multi-Block-Diff", - "unified": "Experimentell: Vereinheitlichter Diff" + "standard": "Standard (Einzelblock)", + "multiBlock": "Experimentell: Mehrblock-Diff", + "unified": "Experimentell: Einheitliches Diff" }, "descriptions": { - "standard": "Die Standard-Diff-Strategie wendet Änderungen auf einen einzelnen Codeblock gleichzeitig an.", - "unified": "Die vereinheitlichte Diff-Strategie verwendet mehrere Ansätze zum Anwenden von Diffs und wählt den besten Ansatz aus.", - "multiBlock": "Die Multi-Block-Diff-Strategie ermöglicht die Aktualisierung mehrerer Codeblöcke in einer Datei in einer Anfrage." + "standard": "Die Standard-Diff-Strategie wendet Änderungen jeweils auf einen einzelnen Codeblock an.", + "unified": "Die einheitliche Diff-Strategie wendet mehrere Ansätze zur Anwendung von Diffs an und wählt den besten Ansatz.", + "multiBlock": "Die Mehrblock-Diff-Strategie ermöglicht das Aktualisieren mehrerer Codeblöcke in einer Datei in einer Anfrage." } }, "matchPrecision": { - "label": "Übereinstimmungsgenauigkeit", - "description": "Dieser Schieberegler steuert, wie genau Codeabschnitte beim Anwenden von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen flexiblere Übereinstimmungen, erhöhen aber das Risiko falscher Ersetzungen. Verwende Werte unter 100% mit äußerster Vorsicht." + "label": "Übereinstimmungspräzision", + "description": "Dieser Schieberegler steuert, wie genau Codeabschnitte bei der Anwendung von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen eine flexiblere Übereinstimmung, erhöhen aber das Risiko falscher Ersetzungen. Verwenden Sie Werte unter 100 % mit äußerster Vorsicht." } } }, "experimental": { "DIFF_STRATEGY_UNIFIED": { "name": "Experimentelle einheitliche Diff-Strategie verwenden", - "description": "Aktiviert die experimentelle einheitliche Diff-Strategie. Diese Strategie könnte die Anzahl der durch Modellfehler verursachten Wiederholungen reduzieren, kann aber unerwartetes Verhalten oder falsche Bearbeitungen verursachen. Nur aktivieren, wenn du die Risiken verstehst und bereit bist, alle Änderungen sorgfältig zu überprüfen." + "description": "Aktiviere die experimentelle einheitliche Diff-Strategie. Diese Strategie könnte die Anzahl der durch Modellfehler verursachten Wiederholungsversuche reduzieren, kann aber zu unerwartetem Verhalten oder falschen Bearbeitungen führen. Aktiviere sie nur, wenn du die Risiken verstehst und bereit bist, alle Änderungen sorgfältig zu überprüfen." }, "SEARCH_AND_REPLACE": { "name": "Experimentelles Such- und Ersetzungswerkzeug verwenden", - "description": "Aktiviert das experimentelle Such- und Ersetzungswerkzeug, das Roo ermöglicht, mehrere Instanzen eines Suchbegriffs in einer Anfrage zu ersetzen." + "description": "Aktiviere das experimentelle Such- und Ersetzungswerkzeug, mit dem Roo mehrere Instanzen eines Suchbegriffs in einer Anfrage ersetzen kann." }, "INSERT_BLOCK": { - "name": "Experimentelles Inhalts-Einfüge-Werkzeug verwenden", - "description": "Aktiviert das experimentelle Inhalts-Einfüge-Werkzeug, das Roo ermöglicht, Inhalte an bestimmten Zeilennummern einzufügen, ohne einen Diff erstellen zu müssen." + "name": "Experimentelles Inhalts-Einfügewerkzeug verwenden", + "description": "Aktiviere das experimentelle Inhalts-Einfügewerkzeug, mit dem Roo Inhalte an bestimmten Zeilennummern einfügen kann, ohne einen Diff erstellen zu müssen." }, "POWER_STEERING": { - "name": "Experimentellen \"Servolenkung\"-Modus verwenden", - "description": "Wenn aktiviert, wird Roo das Modell häufiger an die Details seiner aktuellen Modusdefinition erinnern. Dies führt zu einer stärkeren Einhaltung von Rollendefinitionen und benutzerdefinierten Anweisungen, verwendet aber mehr Tokens pro Nachricht." - }, - "MULTI_SEARCH_AND_REPLACE": { - "name": "Experimentelles Multi-Block-Diff-Werkzeug verwenden", - "description": "Wenn aktiviert, verwendet Roo das Multi-Block-Diff-Werkzeug. Dies versucht, mehrere Codeblöcke in der Datei in einer Anfrage zu aktualisieren." + "name": "Experimentellen \"Power Steering\"-Modus verwenden", + "description": "Wenn aktiviert, erinnert Roo das Modell häufiger an die Details seiner aktuellen Modusdefinition. Dies führt zu einer stärkeren Einhaltung von Rollendefinitionen und benutzerdefinierten Anweisungen, verbraucht aber mehr Token pro Nachricht." }, "CONCURRENT_FILE_READS": { "name": "Gleichzeitiges Lesen von Dateien aktivieren", - "description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage lesen. Wenn deaktiviert, muss Roo Dateien nacheinander lesen. Das Deaktivieren kann helfen, wenn du mit weniger leistungsfähigen Modellen arbeitest oder mehr Kontrolle über den Dateizugriff möchtest." + "description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage lesen. Wenn deaktiviert, muss Roo Dateien einzeln lesen. Das Deaktivieren kann hilfreich sein, wenn mit weniger fähigen Modellen gearbeitet wird oder wenn du mehr Kontrolle über den Dateizugriff haben möchtest." + }, + "MULTI_SEARCH_AND_REPLACE": { + "name": "Experimentelles Multi-Block-Diff-Tool verwenden", + "description": "Wenn aktiviert, wird Roo das Multi-Block-Diff-Tool verwenden. Dies wird versuchen, mehrere Codeblöcke in der Datei in einer Anfrage zu aktualisieren." }, "MARKETPLACE": { - "name": "Marketplace aktivieren", - "description": "Wenn aktiviert, kannst du MCP und benutzerdefinierte Modi aus dem Marketplace installieren und verwalten." + "name": "Marktplatz aktivieren", + "description": "Wenn aktiviert, können Sie MCPs und benutzerdefinierte Modi aus dem Marketplace installieren." }, "MULTI_FILE_APPLY_DIFF": { "name": "Gleichzeitige Dateibearbeitungen aktivieren", - "description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage bearbeiten. Wenn deaktiviert, muss Roo Dateien einzeln bearbeiten. Das Deaktivieren kann helfen, wenn du mit weniger fähigen Modellen arbeitest oder mehr Kontrolle über Dateiänderungen haben möchtest." + "description": "Wenn aktiviert, kann Roo mehrere Dateien in einer einzigen Anfrage bearbeiten. Wenn deaktiviert, muss Roo Dateien einzeln bearbeiten. Das Deaktivieren kann hilfreich sein, wenn mit weniger fähigen Modellen gearbeitet wird oder wenn du mehr Kontrolle über Dateiänderungen haben möchtest." } }, "promptCaching": { @@ -575,16 +576,16 @@ }, "temperature": { "useCustom": "Benutzerdefinierte Temperatur verwenden", - "description": "Steuert die Zufälligkeit in den Antworten des Modells.", + "description": "Steuert die Zufälligkeit der Modellantworten.", "rangeDescription": "Höhere Werte machen die Ausgabe zufälliger, niedrigere Werte machen sie deterministischer." }, "modelInfo": { "supportsImages": "Unterstützt Bilder", "noImages": "Unterstützt keine Bilder", - "supportsComputerUse": "Unterstützt Computer-Nutzung", - "noComputerUse": "Unterstützt keine Computer-Nutzung", - "supportsPromptCache": "Unterstützt Prompt-Caching", - "noPromptCache": "Unterstützt kein Prompt-Caching", + "supportsComputerUse": "Unterstützt Computernutzung", + "noComputerUse": "Unterstützt keine Computernutzung", + "supportsPromptCache": "Unterstützt Prompt-Cache", + "noPromptCache": "Unterstützt keinen Prompt-Cache", "maxOutput": "Maximale Ausgabe", "inputPrice": "Eingabepreis", "outputPrice": "Ausgabepreis", @@ -592,27 +593,27 @@ "cacheWritesPrice": "Cache-Schreibpreis", "enableStreaming": "Streaming aktivieren", "enableR1Format": "R1-Modellparameter aktivieren", - "enableR1FormatTips": "Muss aktiviert werden, wenn R1-Modelle wie QWQ verwendet werden, um 400-Fehler zu vermeiden", + "enableR1FormatTips": "Muss bei Verwendung von R1-Modellen wie QWQ aktiviert werden, um 400er-Fehler zu vermeiden", "useAzure": "Azure verwenden", "azureApiVersion": "Azure API-Version festlegen", "gemini": { "freeRequests": "* Kostenlos bis zu {{count}} Anfragen pro Minute. Danach hängt die Abrechnung von der Prompt-Größe ab.", - "pricingDetails": "Weitere Informationen finden Sie in den Preisdetails.", + "pricingDetails": "Weitere Informationen finden Sie unter Preisdetails.", "billingEstimate": "* Die Abrechnung ist eine Schätzung - die genauen Kosten hängen von der Prompt-Größe ab." } }, "modelPicker": { - "automaticFetch": "Die Erweiterung ruft automatisch die neueste Liste der verfügbaren Modelle von {{serviceName}} ab. Wenn du dir nicht sicher bist, welches Modell du wählen sollst, funktioniert Roo Code am besten mit {{defaultModelId}}. Du kannst auch nach \"free\" suchen, um derzeit verfügbare kostenlose Optionen zu finden.", + "automaticFetch": "Die Erweiterung ruft automatisch die neueste Liste der auf {{serviceName}} verfügbaren Modelle ab. Wenn du dir nicht sicher bist, welches Modell du wählen sollst, funktioniert Roo Code am besten mit {{defaultModelId}}. Du kannst auch versuchen, nach \"kostenlos\" zu suchen, um die derzeit verfügbaren kostenlosen Optionen zu finden.", "label": "Modell", "searchPlaceholder": "Suchen", "noMatchFound": "Keine Übereinstimmung gefunden", "useCustomModel": "Benutzerdefiniert verwenden: {{modelId}}" }, "footer": { - "feedback": "Wenn du Fragen oder Feedback hast, kannst du gerne ein Issue auf github.com/RooCodeInc/Roo-Code öffnen oder reddit.com/r/RooCode oder discord.gg/roocode beitreten", + "feedback": "Wenn du Fragen oder Feedback hast, kannst du gerne ein Issue auf github.com/RooCodeInc/Roo-Code eröffnen oder reddit.com/r/RooCode oder discord.gg/roocode beitreten", "telemetry": { "label": "Anonyme Fehler- und Nutzungsberichte zulassen", - "description": "Helfen Sie, Roo Code zu verbessern, indem Sie anonyme Nutzungsdaten und Fehlerberichte senden. Es werden niemals Code, Prompts oder persönliche Informationen gesendet. Weitere Details finden Sie in unserer Datenschutzrichtlinie." + "description": "Hilf mit, Roo Code zu verbessern, indem du anonyme Nutzungsdaten und Fehlerberichte sendest. Es werden niemals Code, Prompts oder persönliche Informationen gesendet (es sei denn, du verbindest dich mit Roo Code Cloud). Weitere Einzelheiten findest du in unserer Datenschutzrichtlinie." }, "settings": { "import": "Importieren", @@ -621,19 +622,19 @@ } }, "thinkingBudget": { - "maxTokens": "Maximale Tokens", - "maxThinkingTokens": "Maximale Thinking-Tokens" + "maxTokens": "Max Tokens", + "maxThinkingTokens": "Max Thinking Tokens" }, "validation": { "apiKey": "Du musst einen gültigen API-Schlüssel angeben.", - "awsRegion": "Du musst eine Region für Amazon Bedrock auswählen.", + "awsRegion": "Du musst eine Region für die Verwendung mit Amazon Bedrock auswählen.", "googleCloud": "Du musst eine gültige Google Cloud Projekt-ID und Region angeben.", "modelId": "Du musst eine gültige Modell-ID angeben.", - "modelSelector": "Du musst einen gültigen Modell-Selektor angeben.", - "openAi": "Du musst eine gültige Basis-URL, API-Schlüssel und Modell-ID angeben.", + "modelSelector": "Du musst einen gültigen Modellselektor angeben.", + "openAi": "Du musst eine gültige Basis-URL, einen API-Schlüssel und eine Modell-ID angeben.", "arn": { - "invalidFormat": "Ungültiges ARN-Format. Bitte überprüfen Sie die Formatanforderungen.", - "regionMismatch": "Warnung: Die Region in deiner ARN ({{arnRegion}}) stimmt nicht mit deiner ausgewählten Region ({{region}}) überein. Dies kann zu Zugriffsproblemen führen. Der Provider wird die Region aus der ARN verwenden." + "invalidFormat": "Ungültiges ARN-Format. Bitte überprüfe die Formatanforderungen.", + "regionMismatch": "Warnung: Die Region in deiner ARN ({{arnRegion}}) stimmt nicht mit deiner ausgewählten Region ({{region}}) überein. Dies kann zu Zugriffsproblemen führen. Der Anbieter wird die Region aus der ARN verwenden." }, "modelAvailability": "Die von dir angegebene Modell-ID ({{modelId}}) ist nicht verfügbar. Bitte wähle ein anderes Modell.", "providerNotAllowed": "Anbieter '{{provider}}' ist von deiner Organisation nicht erlaubt", @@ -646,7 +647,7 @@ "accessKey": "Zugriffsschlüssel eingeben...", "secretKey": "Geheimschlüssel eingeben...", "sessionToken": "Sitzungstoken eingeben...", - "credentialsJson": "Anmeldedaten-JSON eingeben...", + "credentialsJson": "Anmeldeinformationen JSON eingeben...", "keyFilePath": "Schlüsseldateipfad eingeben...", "projectId": "Projekt-ID eingeben...", "customArn": "ARN eingeben (z.B. arn:aws:bedrock:us-east-1:123456789012:foundation-model/my-model)", @@ -674,5 +675,5 @@ "useCustomArn": "Benutzerdefinierte ARN verwenden..." }, "includeMaxOutputTokens": "Maximale Ausgabe-Tokens einbeziehen", - "includeMaxOutputTokensDescription": "Sende den Parameter für maximale Ausgabe-Tokens in API-Anfragen. Einige Anbieter unterstützen dies möglicherweise nicht." + "includeMaxOutputTokensDescription": "Senden Sie den Parameter für maximale Ausgabe-Tokens in API-Anfragen. Einige Anbieter unterstützen dies möglicherweise nicht." } diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index ea7191f224..8b30f87a7e 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -72,6 +72,7 @@ "qdrantKeyLabel": "Qdrant Key:", "qdrantApiKeyLabel": "Qdrant API Key", "qdrantApiKeyPlaceholder": "Enter your Qdrant API key (optional)", + "setupConfigLabel": "Setup", "advancedConfigLabel": "Advanced Configuration", "searchMinScoreLabel": "Search Score Threshold", "searchMinScoreDescription": "Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index d6cebea206..14aa34278d 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -72,6 +72,7 @@ "qdrantKeyLabel": "Clave de Qdrant:", "qdrantApiKeyLabel": "Clave API de Qdrant", "qdrantApiKeyPlaceholder": "Introduce tu clave API de Qdrant (opcional)", + "setupConfigLabel": "Configuración", "startIndexingButton": "Iniciar", "clearIndexDataButton": "Borrar índice", "unsavedSettingsMessage": "Por favor guarda tus ajustes antes de iniciar el proceso de indexación.", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 281fb705fc..d645235dbe 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -72,6 +72,7 @@ "qdrantKeyLabel": "Clé Qdrant :", "qdrantApiKeyLabel": "Clé API Qdrant", "qdrantApiKeyPlaceholder": "Entrez votre clé API Qdrant (optionnel)", + "setupConfigLabel": "Configuration", "startIndexingButton": "Démarrer", "clearIndexDataButton": "Effacer l'index", "unsavedSettingsMessage": "Merci d'enregistrer tes paramètres avant de démarrer le processus d'indexation.", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 4b8132768f..20bb0b0e70 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama आधार URL", "qdrantApiKeyLabel": "Qdrant API कुंजी", "qdrantApiKeyPlaceholder": "अपनी Qdrant API कुंजी दर्ज करें (वैकल्पिक)", + "setupConfigLabel": "सेटअप", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index ac1454ab7b..a8adf2e626 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "URL Dasar Ollama", "qdrantApiKeyLabel": "Kunci API Qdrant", "qdrantApiKeyPlaceholder": "Masukkan kunci API Qdrant kamu (opsional)", + "setupConfigLabel": "Pengaturan", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 7627486863..48b9e324b0 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "URL base Ollama", "qdrantApiKeyLabel": "Chiave API Qdrant", "qdrantApiKeyPlaceholder": "Inserisci la tua chiave API Qdrant (opzionale)", + "setupConfigLabel": "Impostazione", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index f779749cd4..666d650bc4 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama ベースURL", "qdrantApiKeyLabel": "Qdrant APIキー", "qdrantApiKeyPlaceholder": "Qdrant APIキーを入力(オプション)", + "setupConfigLabel": "設定", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 2ea16f7834..a1618952ef 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama 기본 URL", "qdrantApiKeyLabel": "Qdrant API 키", "qdrantApiKeyPlaceholder": "Qdrant API 키를 입력하세요 (선택사항)", + "setupConfigLabel": "설정", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 92b00cf3ff..2fb43272ce 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama Basis-URL", "qdrantApiKeyLabel": "Qdrant API-sleutel", "qdrantApiKeyPlaceholder": "Voer je Qdrant API-sleutel in (optioneel)", + "setupConfigLabel": "Instellen", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 34e0ac52ef..3988da53c2 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Bazowy URL Ollama", "qdrantApiKeyLabel": "Klucz API Qdrant", "qdrantApiKeyPlaceholder": "Wprowadź swój klucz API Qdrant (opcjonalnie)", + "setupConfigLabel": "Konfiguracja", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index e696ab0f77..d3553c0c7b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "URL Base do Ollama", "qdrantApiKeyLabel": "Chave da API Qdrant", "qdrantApiKeyPlaceholder": "Insira sua chave da API Qdrant (opcional)", + "setupConfigLabel": "Configuração", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 3e6a7a9d76..16483222a3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Базовый URL Ollama", "qdrantApiKeyLabel": "API-ключ Qdrant", "qdrantApiKeyPlaceholder": "Введите ваш API-ключ Qdrant (необязательно)", + "setupConfigLabel": "Настройка", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index c0a3b8f501..91ccceb08e 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama Temel URL", "qdrantApiKeyLabel": "Qdrant API Anahtarı", "qdrantApiKeyPlaceholder": "Qdrant API anahtarınızı girin (isteğe bağlı)", + "setupConfigLabel": "Kurulum", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index e553f5d0c2..3c9aad3d9f 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "URL cơ sở Ollama", "qdrantApiKeyLabel": "Khóa API Qdrant", "qdrantApiKeyPlaceholder": "Nhập khóa API Qdrant của bạn (tùy chọn)", + "setupConfigLabel": "Cài đặt", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index d8d875c85f..4e90ec3771 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -72,6 +72,7 @@ "qdrantKeyLabel": "Qdrant 密钥:", "qdrantApiKeyLabel": "Qdrant API 密钥", "qdrantApiKeyPlaceholder": "输入你的 Qdrant API 密钥(可选)", + "setupConfigLabel": "设置", "startIndexingButton": "开始", "clearIndexDataButton": "清除索引", "unsavedSettingsMessage": "请先保存设置再开始索引过程。", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index f08cb0dbba..5d55596e3d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -81,6 +81,7 @@ "ollamaBaseUrlLabel": "Ollama 基礎 URL", "qdrantApiKeyLabel": "Qdrant API 金鑰", "qdrantApiKeyPlaceholder": "輸入您的 Qdrant API 金鑰(選用)", + "setupConfigLabel": "設定", "ollamaUrlPlaceholder": "http://localhost:11434", "openAiCompatibleBaseUrlPlaceholder": "https://api.example.com", "modelDimensionPlaceholder": "1536", From e9dca0acb9087e25d467624ccd71a8db8f2c0567 Mon Sep 17 00:00:00 2001 From: Roomote Bot Date: Tue, 8 Jul 2025 12:33:03 -0700 Subject: [PATCH 5/6] Mark test snapshot files as linguist-generated (#5474) Co-authored-by: Matt Rubens --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitattributes b/.gitattributes index e8eeb58b23..02ddd6b634 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ demo.gif filter=lfs diff=lfs merge=lfs -text assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text src/assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text + +# Test snapshot files - mark as linguist-generated to exclude from GitHub language statistics +*.snap linguist-generated=true From d116dbf0f189c56f6e22934ac9e3138dc0e085c9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 8 Jul 2025 15:49:23 -0400 Subject: [PATCH 6/6] Only show share modal from sharebutton (#5475) --- .../src/components/chat/ShareButton.tsx | 22 +++++++-- .../chat/__tests__/TaskActions.spec.tsx | 46 +++++++++++++++++-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/webview-ui/src/components/chat/ShareButton.tsx b/webview-ui/src/components/chat/ShareButton.tsx index 20442ba76b..7c90e72039 100644 --- a/webview-ui/src/components/chat/ShareButton.tsx +++ b/webview-ui/src/components/chat/ShareButton.tsx @@ -35,20 +35,32 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { const { t } = useTranslation() const { sharingEnabled, cloudIsAuthenticated, cloudUserInfo } = useExtensionState() const wasUnauthenticatedRef = useRef(false) + const initiatedAuthFromThisButtonRef = useRef(false) // Track authentication state changes to auto-open popover after login useEffect(() => { if (!cloudIsAuthenticated || !sharingEnabled) { wasUnauthenticatedRef.current = true } else if (wasUnauthenticatedRef.current && cloudIsAuthenticated && sharingEnabled) { - // User just authenticated, send telemetry, close modal, and open the popover - telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_SUCCESS) - setConnectModalOpen(false) - setShareDropdownOpen(true) + // Only open dropdown if auth was initiated from this button + if (initiatedAuthFromThisButtonRef.current) { + // User just authenticated from this share button, send telemetry, close modal, and open the popover + telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_SUCCESS) + setConnectModalOpen(false) + setShareDropdownOpen(true) + initiatedAuthFromThisButtonRef.current = false // Reset the flag + } wasUnauthenticatedRef.current = false } }, [cloudIsAuthenticated, sharingEnabled]) + // Cleanup effect to reset flag on unmount + useEffect(() => { + return () => { + initiatedAuthFromThisButtonRef.current = false + } + }, []) + // Listen for share success messages from the extension useEffect(() => { const handleMessage = (event: MessageEvent) => { @@ -92,6 +104,8 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { // Send telemetry for connect to cloud action telemetryClient.capture(TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED) + // Mark that authentication was initiated from this button + initiatedAuthFromThisButtonRef.current = true vscode.postMessage({ type: "rooCloudSignIn" }) setShareDropdownOpen(false) setConnectModalOpen(false) diff --git a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx index 2d9ea0cac1..5db6287747 100644 --- a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx @@ -265,7 +265,7 @@ describe("TaskActions", () => { expect(screen.queryByText("Connect to Cloud")).not.toBeInTheDocument() }) - it("automatically opens popover when user becomes authenticated", () => { + it("does not automatically open popover when user becomes authenticated from elsewhere", () => { // Start with unauthenticated state mockUseExtensionState.mockReturnValue({ sharingEnabled: false, @@ -277,7 +277,7 @@ describe("TaskActions", () => { // Verify popover is not open initially expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() - // Simulate user becoming authenticated + // Simulate user becoming authenticated (e.g., from AccountView) mockUseExtensionState.mockReturnValue({ sharingEnabled: true, cloudIsAuthenticated: true, @@ -288,7 +288,47 @@ describe("TaskActions", () => { rerender() - // Verify popover automatically opens and shows sharing options + // Verify popover does NOT automatically open when auth happens from elsewhere + expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() + expect(screen.queryByText("Share Publicly")).not.toBeInTheDocument() + }) + + it("automatically opens popover when user authenticates from share button", () => { + // Start with unauthenticated state + mockUseExtensionState.mockReturnValue({ + sharingEnabled: false, + cloudIsAuthenticated: false, + } as any) + + const { rerender } = render() + + // Click share button to open connect modal + const buttons = screen.getAllByRole("button") + const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) + expect(shareButton).toBeDefined() + fireEvent.click(shareButton!) + + // Click connect button to initiate authentication + const connectButton = screen.getByText("Connect") + fireEvent.click(connectButton) + + // Verify rooCloudSignIn message was sent + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "rooCloudSignIn", + }) + + // Simulate user becoming authenticated after clicking connect from share button + mockUseExtensionState.mockReturnValue({ + sharingEnabled: true, + cloudIsAuthenticated: true, + cloudUserInfo: { + organizationName: "Test Organization", + }, + } as any) + + rerender() + + // Verify popover automatically opens when auth was initiated from share button expect(screen.getByText("Share with Organization")).toBeInTheDocument() expect(screen.getByText("Share Publicly")).toBeInTheDocument() })