diff --git a/apps/web-roo-code/src/app/pricing/page.tsx b/apps/web-roo-code/src/app/pricing/page.tsx index a46b5c67cc5..6851b47b6a1 100644 --- a/apps/web-roo-code/src/app/pricing/page.tsx +++ b/apps/web-roo-code/src/app/pricing/page.tsx @@ -291,11 +291,7 @@ export default function PricingPage() {
  • To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)
  • To pay for AI model inference costs ( - + varies by model ) diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index fe985d7ec89..76415a77975 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -35,7 +35,7 @@ - [简体中文](../zh-CN/README.md) - [繁體中文](../zh-TW/README.md) - ... - + --- diff --git a/packages/types/src/followup.ts b/packages/types/src/followup.ts index 1a5424cd11e..4d7047665f2 100644 --- a/packages/types/src/followup.ts +++ b/packages/types/src/followup.ts @@ -8,6 +8,8 @@ import { z } from "zod" export interface FollowUpData { /** The question being asked by the LLM */ question?: string + /** Array of questions being asked by the LLM */ + questions?: FollowUpQuestion[] /** Array of suggested answers that the user can select */ suggest?: Array } @@ -22,6 +24,12 @@ export interface SuggestionItem { mode?: string } +/** + * Type definition for a follow-up question + * Can be a simple string or an object with text and options + */ +export type FollowUpQuestion = string | { text: string; options?: string[] } + /** * Zod schema for SuggestionItem */ @@ -30,11 +38,23 @@ export const suggestionItemSchema = z.object({ mode: z.string().optional(), }) +/** + * Zod schema for FollowUpQuestion + */ +export const followUpQuestionSchema = z.union([ + z.string(), + z.object({ + text: z.string(), + options: z.array(z.string()).optional(), + }), +]) + /** * Zod schema for FollowUpData */ export const followUpDataSchema = z.object({ question: z.string().optional(), + questions: z.array(followUpQuestionSchema).optional(), suggest: z.array(suggestionItemSchema).optional(), }) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index ddb62206097..e5489fd6b2f 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -223,6 +223,18 @@ export const globalSettingsSchema = z.object({ lastTaskExportPath: z.string().optional(), lastImageSavePath: z.string().optional(), + /** + * Whether to show multiple questions one by one or all at once. + * @default false (all at once) + */ + showQuestionsOneByOne: z.boolean().optional(), + + /** + * Whether to highlight the task header in the chat view. + * @default false + */ + taskHeaderHighlightEnabled: z.boolean().optional(), + /** * Path to worktree to auto-open after switching workspaces. * Used by the worktree feature to open the Roo Code sidebar in a new window. @@ -391,6 +403,8 @@ export const EVALS_SETTINGS: RooCodeSettings = { mode: "code", // "architect", customModes: [], + showQuestionsOneByOne: false, + taskHeaderHighlightEnabled: false, } export const EVALS_TIMEOUT = 5 * 60 * 1_000 diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index da1d214cdbb..f2cddee8192 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -285,6 +285,7 @@ export type ExtensionState = Pick< | "ttsSpeed" | "soundEnabled" | "soundVolume" + | "taskHeaderHighlightEnabled" | "terminalOutputPreviewSize" | "terminalShellIntegrationTimeout" | "terminalShellIntegrationDisabled" @@ -313,6 +314,7 @@ export type ExtensionState = Pick< | "enterBehavior" | "includeCurrentTime" | "includeCurrentCost" + | "showQuestionsOneByOne" | "maxGitStatusFiles" | "requestDelaySeconds" | "showWorktreesInHomeScreen" diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index e7b0067dd92..6b5310b7455 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -473,9 +473,9 @@ export class NativeToolCallParser { break case "ask_followup_question": - if (partialArgs.question !== undefined || partialArgs.follow_up !== undefined) { + if (partialArgs.questions !== undefined || partialArgs.follow_up !== undefined) { nativeArgs = { - question: partialArgs.question, + questions: Array.isArray(partialArgs.questions) ? partialArgs.questions : undefined, follow_up: Array.isArray(partialArgs.follow_up) ? partialArgs.follow_up : undefined, } } @@ -816,9 +816,9 @@ export class NativeToolCallParser { break case "ask_followup_question": - if (args.question !== undefined && args.follow_up !== undefined) { + if (args.questions !== undefined || args.follow_up !== undefined) { nativeArgs = { - question: args.question, + questions: Array.isArray(args.questions) ? args.questions : undefined, follow_up: args.follow_up, } as NativeArgsFor } diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts index 7316884984f..a2fffde1216 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts @@ -49,6 +49,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () = closeBrowser: vi.fn().mockResolvedValue(undefined), }, recordToolUsage: vi.fn(), + recordToolError: vi.fn(), + sayAndCreateMissingParamError: vi.fn().mockResolvedValue("mock error message"), toolRepetitionDetector: { check: vi.fn().mockReturnValue({ allowExecution: true }), }, @@ -85,8 +87,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () = type: "tool_use", id: toolCallId, // ID indicates native tool calling name: "ask_followup_question", - params: { question: "What do you see?" }, - nativeArgs: { question: "What do you see?", follow_up: [] }, + params: { questions: ["What do you see?"] }, + nativeArgs: { questions: ["What do you see?"], follow_up: [] }, }, ] @@ -138,8 +140,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () = type: "tool_use", id: toolCallId, name: "ask_followup_question", - params: { question: "What is your name?" }, - nativeArgs: { question: "What is your name?", follow_up: [] }, + params: { questions: ["What is your name?"] }, + nativeArgs: { questions: ["What is your name?"], follow_up: [] }, }, ] diff --git a/src/core/prompts/tools/native-tools/ask_followup_question.ts b/src/core/prompts/tools/native-tools/ask_followup_question.ts index b0591206ade..45465962926 100644 --- a/src/core/prompts/tools/native-tools/ask_followup_question.ts +++ b/src/core/prompts/tools/native-tools/ask_followup_question.ts @@ -3,16 +3,26 @@ import type OpenAI from "openai" const ASK_FOLLOWUP_QUESTION_DESCRIPTION = `Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. Parameters: -- question: (required) A clear, specific question addressing the information needed +- questions: (required) A list of questions to ask. Each question can be a simple string or an object with "text" and "options" for multiple choice. - follow_up: (required) A list of 2-4 suggested answers. Suggestions must be complete, actionable answers without placeholders. Optionally include mode to switch modes (code/architect/etc.) Example: Asking for file path -{ "question": "What is the path to the frontend-config.json file?", "follow_up": [{ "text": "./src/frontend-config.json", "mode": null }, { "text": "./config/frontend-config.json", "mode": null }, { "text": "./frontend-config.json", "mode": null }] } +{ "questions": ["What is the path to the frontend-config.json file?"], "follow_up": [{ "text": "./src/frontend-config.json", "mode": null }, { "text": "./config/frontend-config.json", "mode": null }, { "text": "./frontend-config.json", "mode": null }] } + +Example: Asking with multiple questions and choices +{ + "questions": [ + { "text": "Which framework are you using?", "options": ["React", "Vue", "Svelte", "Other"] }, + "What is your project name?", + { "text": "Include telemetry?", "options": ["Yes", "No"] } + ], + "follow_up": [{ "text": "I've answered the questions", "mode": null }] +} Example: Asking with mode switch -{ "question": "Would you like me to implement this feature?", "follow_up": [{ "text": "Yes, implement it now", "mode": "code" }, { "text": "No, just plan it out", "mode": "architect" }] }` +{ "questions": ["Would you like me to implement this feature?"], "follow_up": [{ "text": "Yes, implement it now", "mode": "code" }, { "text": "No, just plan it out", "mode": "architect" }] }` -const QUESTION_PARAMETER_DESCRIPTION = `Clear, specific question that captures the missing information you need` +const QUESTIONS_PARAMETER_DESCRIPTION = `List of questions to ask. Each question can be a string or an object with "text" and "options" for multiple choice.` const FOLLOW_UP_PARAMETER_DESCRIPTION = `Required list of 2-4 suggested responses; each suggestion must be a complete, actionable answer and may include a mode switch` @@ -29,9 +39,29 @@ export default { parameters: { type: "object", properties: { - question: { - type: "string", - description: QUESTION_PARAMETER_DESCRIPTION, + questions: { + type: "array", + items: { + anyOf: [ + { + type: "string", + }, + { + type: "object", + properties: { + text: { type: "string" }, + options: { + type: "array", + items: { type: "string" }, + }, + }, + required: ["text", "options"], + additionalProperties: false, + }, + ], + }, + description: QUESTIONS_PARAMETER_DESCRIPTION, + minItems: 1, }, follow_up: { type: "array", @@ -55,7 +85,7 @@ export default { maxItems: 4, }, }, - required: ["question", "follow_up"], + required: ["questions", "follow_up"], additionalProperties: false, }, }, diff --git a/src/core/tools/AskFollowupQuestionTool.ts b/src/core/tools/AskFollowupQuestionTool.ts index 010a6240f1e..8533e9c7b34 100644 --- a/src/core/tools/AskFollowupQuestionTool.ts +++ b/src/core/tools/AskFollowupQuestionTool.ts @@ -9,8 +9,13 @@ interface Suggestion { mode?: string } +interface Question { + text: string + options?: string[] +} + interface AskFollowupQuestionParams { - question: string + questions: Array follow_up: Suggestion[] } @@ -18,26 +23,26 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { readonly name = "ask_followup_question" as const async execute(params: AskFollowupQuestionParams, task: Task, callbacks: ToolCallbacks): Promise { - const { question, follow_up } = params + const { questions, follow_up } = params const { handleError, pushToolResult } = callbacks try { - if (!question) { + if (!questions || questions.length === 0) { task.consecutiveMistakeCount++ task.recordToolError("ask_followup_question") task.didToolFailInCurrentTurn = true - pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "question")) + pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "questions")) return } // Transform follow_up suggestions to the format expected by task.ask - const follow_up_json = { - question, - suggest: follow_up.map((s) => ({ answer: s.text, mode: s.mode })), + const followup_json = { + questions, + suggest: follow_up?.map((s) => ({ answer: s.text, mode: s.mode })) || [], } task.consecutiveMistakeCount = 0 - const { text, images } = await task.ask("followup", JSON.stringify(follow_up_json), false) + const { text, images } = await task.ask("followup", JSON.stringify(followup_json), false) await task.say("user_feedback", text ?? "", images) pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) } catch (error) { @@ -46,11 +51,17 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { } override async handlePartial(task: Task, block: ToolUse<"ask_followup_question">): Promise { - const question: string | undefined = block.nativeArgs?.question ?? block.params.question + // Get question from params or nativeArgs + const questions = block.nativeArgs?.questions ?? [] + const firstQuestion = questions[0] + const multiQuestionText = typeof firstQuestion === "string" ? firstQuestion : firstQuestion?.text + const singleQuestionText = (block.nativeArgs as any)?.question ?? block.params.question + + const questionText = multiQuestionText ?? singleQuestionText // During partial streaming, only show the question to avoid displaying raw JSON - // The full JSON with suggestions will be sent when the tool call is complete (!block.partial) - await task.ask("followup", question ?? "", block.partial).catch(() => {}) + // The full JSON with suggestions will be sent when the tool call is complete + await task.ask("followup", questionText ?? "", block.partial).catch(() => {}) } } diff --git a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts index e13f639ba00..858b03706f3 100644 --- a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts +++ b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts @@ -29,7 +29,7 @@ describe("askFollowupQuestionTool", () => { question: "What would you like to do?", }, nativeArgs: { - question: "What would you like to do?", + questions: ["What would you like to do?"], follow_up: [{ text: "Option 1" }, { text: "Option 2" }], }, partial: false, @@ -56,7 +56,7 @@ describe("askFollowupQuestionTool", () => { question: "What would you like to do?", }, nativeArgs: { - question: "What would you like to do?", + questions: ["What would you like to do?"], follow_up: [ { text: "Write code", mode: "code" }, { text: "Debug issue", mode: "debug" }, @@ -88,7 +88,7 @@ describe("askFollowupQuestionTool", () => { question: "What would you like to do?", }, nativeArgs: { - question: "What would you like to do?", + questions: ["What would you like to do?"], follow_up: [{ text: "Regular option" }, { text: "Plan architecture", mode: "architect" }], }, partial: false, @@ -109,18 +109,75 @@ describe("askFollowupQuestionTool", () => { ) }) + it("should handle multiple questions in native protocol", async () => { + const block: ToolUse<"ask_followup_question"> = { + type: "tool_use", + name: "ask_followup_question", + params: {}, + nativeArgs: { + questions: ["Question A", "Question B"], + follow_up: [{ text: "Okay", mode: "code" }], + }, + partial: false, + } + + await askFollowupQuestionTool.handle(mockCline, block, { + askApproval: vi.fn(), + handleError: vi.fn(), + pushToolResult: mockPushToolResult, + }) + + expect(mockCline.ask).toHaveBeenCalledWith( + "followup", + expect.stringContaining('"questions":["Question A","Question B"]'), + false, + ) + }) + + it("should handle multiple-choice questions in native protocol", async () => { + const block: ToolUse<"ask_followup_question"> = { + type: "tool_use", + name: "ask_followup_question", + params: {}, + nativeArgs: { + questions: [ + { text: "Framework?", options: ["React", "Vue"] }, + "Project name?", + { text: "Deploy?", options: ["Yes", "No"] }, + ], + follow_up: [{ text: "Done", mode: "code" }], + }, + partial: false, + } + + await askFollowupQuestionTool.handle(mockCline, block, { + askApproval: vi.fn(), + handleError: vi.fn(), + pushToolResult: mockPushToolResult, + }) + + expect(mockCline.ask).toHaveBeenCalledWith( + "followup", + expect.stringContaining( + '"questions":[{"text":"Framework?","options":["React","Vue"]},"Project name?",{"text":"Deploy?","options":["Yes","No"]}]', + ), + false, + ) + }) + describe("handlePartial with native protocol", () => { - it("should only send question during partial streaming to avoid raw JSON display", async () => { + it("should only send first question during partial streaming to avoid raw JSON display", async () => { const block: ToolUse<"ask_followup_question"> = { type: "tool_use", name: "ask_followup_question", - params: { - question: "What would you like to do?", - }, + params: {}, partial: true, nativeArgs: { - question: "What would you like to do?", - follow_up: [{ text: "Option 1", mode: "code" }, { text: "Option 2" }], + questions: ["What would you like to do?"], + follow_up: [ + { text: "Option 1", mode: "code" }, + { text: "Option 2", mode: "architect" }, + ], }, } @@ -130,18 +187,20 @@ describe("askFollowupQuestionTool", () => { pushToolResult: mockPushToolResult, }) - // During partial streaming, only the question should be sent (not JSON with suggestions) + // During partial streaming, only the first question should be sent (not JSON with suggestions) expect(mockCline.ask).toHaveBeenCalledWith("followup", "What would you like to do?", true) }) - it("should handle partial with question from params", async () => { + it("should handle partial with multiple questions", async () => { const block: ToolUse<"ask_followup_question"> = { type: "tool_use", name: "ask_followup_question", - params: { - question: "Choose wisely", - }, + params: {}, partial: true, + nativeArgs: { + questions: ["Question 1", "Question 2"], + follow_up: [], + }, } await askFollowupQuestionTool.handle(mockCline, block, { @@ -150,7 +209,8 @@ describe("askFollowupQuestionTool", () => { pushToolResult: mockPushToolResult, }) - expect(mockCline.ask).toHaveBeenCalledWith("followup", "Choose wisely", true) + // Should show first question during streaming + expect(mockCline.ask).toHaveBeenCalledWith("followup", "Question 1", true) }) }) @@ -160,34 +220,33 @@ describe("askFollowupQuestionTool", () => { NativeToolCallParser.clearRawChunkState() }) - it("should build nativeArgs with question and follow_up during streaming", () => { + it("should build nativeArgs with questions and follow_up during streaming", () => { // Start a streaming tool call NativeToolCallParser.startStreamingToolCall("call_123", "ask_followup_question") // Simulate streaming JSON chunks - const chunk1 = '{"question":"What would you like?","follow_up":[{"text":"Option 1","mode":"code"}' + const chunk1 = '{"questions":["What would you like?"],"follow_up":[{"text":"Option 1","mode":"code"}' const result1 = NativeToolCallParser.processStreamingChunk("call_123", chunk1) expect(result1).not.toBeNull() expect(result1?.name).toBe("ask_followup_question") - expect(result1?.params.question).toBe("What would you like?") expect(result1?.nativeArgs).toBeDefined() // Use type assertion to access the specific fields const nativeArgs = result1?.nativeArgs as { - question: string + questions: string[] follow_up?: Array<{ text: string; mode?: string }> } - expect(nativeArgs?.question).toBe("What would you like?") + expect(nativeArgs?.questions).toEqual(["What would you like?"]) // partial-json should parse the incomplete array expect(nativeArgs?.follow_up).toBeDefined() }) - it("should finalize with complete nativeArgs", () => { + it("should finalize with complete nativeArgs including complex questions", () => { NativeToolCallParser.startStreamingToolCall("call_456", "ask_followup_question") // Add complete JSON const completeJson = - '{"question":"Choose an option","follow_up":[{"text":"Yes","mode":"code"},{"text":"No","mode":null}]}' + '{"questions":[{"text":"Framework?","options":["React","Vue"]},"Name?"],"follow_up":[{"text":"Yes","mode":"code"},{"text":"No","mode":"architect"}]}' NativeToolCallParser.processStreamingChunk("call_456", completeJson) const result = NativeToolCallParser.finalizeStreamingToolCall("call_456") @@ -199,10 +258,10 @@ describe("askFollowupQuestionTool", () => { // Type guard: regular tools have type 'tool_use', MCP tools have type 'mcp_tool_use' if (result?.type === "tool_use") { expect(result.nativeArgs).toEqual({ - question: "Choose an option", + questions: [{ text: "Framework?", options: ["React", "Vue"] }, "Name?"], follow_up: [ { text: "Yes", mode: "code" }, - { text: "No", mode: null }, + { text: "No", mode: "architect" }, ], }) } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index cef6ce94707..8a44766897f 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2078,6 +2078,7 @@ export class ClineProvider checkpointTimeout, taskHistory, soundVolume, + taskHeaderHighlightEnabled, browserViewportSize, screenshotQuality, remoteBrowserHost, @@ -2142,6 +2143,7 @@ export class ClineProvider featureRoomoteControlEnabled, isBrowserSessionActive, lockApiConfigAcrossModes, + showQuestionsOneByOne, } = await this.getState() let cloudOrganizations: CloudOrganizationMembership[] = [] @@ -2260,6 +2262,7 @@ export class ClineProvider historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, enterBehavior: enterBehavior ?? "send", + taskHeaderHighlightEnabled: taskHeaderHighlightEnabled ?? false, cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, cloudAuthSkipModel: this.context.globalState.get("roo-auth-skip-model") ?? false, @@ -2298,6 +2301,7 @@ export class ClineProvider includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, + showQuestionsOneByOne: showQuestionsOneByOne ?? false, maxGitStatusFiles: maxGitStatusFiles ?? 0, taskSyncEnabled, remoteControlEnabled, @@ -2501,6 +2505,8 @@ export class ClineProvider historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, enterBehavior: stateValues.enterBehavior ?? "send", + taskHeaderHighlightEnabled: stateValues.taskHeaderHighlightEnabled ?? false, + showQuestionsOneByOne: stateValues.showQuestionsOneByOne ?? false, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 570f55c4f2f..931b9c3e19e 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -7,6 +7,7 @@ import type { ToolName, BrowserActionParams, GenerateImageParams, + FollowUpQuestion, } from "@roo-code/types" export type ToolResponse = string | Array @@ -72,6 +73,8 @@ export const toolParamNames = [ "old_string", // search_replace and edit_file parameter "new_string", // search_replace and edit_file parameter "expected_replacements", // edit_file parameter for multiple occurrences + "expected_replacements", // edit_file parameter for multiple occurrences + "questions", // ask_followup_question parameter for multiple questions "artifact_id", // read_command_output parameter "search", // read_command_output parameter for grep-like search "offset", // read_command_output and read_file parameter @@ -108,8 +111,8 @@ export type NativeToolArgs = { list_files: { path: string; recursive?: boolean } new_task: { mode: string; message: string; todos?: string } ask_followup_question: { - question: string - follow_up: Array<{ text: string; mode?: string }> + questions: FollowUpQuestion[] + follow_up?: Array<{ text: string; mode?: string }> } browser_action: BrowserActionParams codebase_search: { query: string; path?: string } @@ -235,7 +238,7 @@ export interface AccessMcpResourceToolUse extends ToolUse<"access_mcp_resource"> export interface AskFollowupQuestionToolUse extends ToolUse<"ask_followup_question"> { name: "ask_followup_question" - params: Partial, "question" | "follow_up">> + params: Partial, "question" | "follow_up" | "questions">> } export interface AttemptCompletionToolUse extends ToolUse<"attempt_completion"> { diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index b4342f2edfc..cf1c69c8fc1 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -39,6 +39,7 @@ import McpResourceRow from "../mcp/McpResourceRow" import { Mention } from "./Mention" import { CheckpointSaved } from "./checkpoints/CheckpointSaved" import { FollowUpSuggest } from "./FollowUpSuggest" +import { MultiQuestionHandler } from "./MultiQuestionHandler" import { BatchFilePermission } from "./BatchFilePermission" import { BatchListFilesPermission } from "./BatchListFilesPermission" import { BatchDiffApproval } from "./BatchDiffApproval" @@ -1738,17 +1739,28 @@ export const ChatRowContent = ({ )}
    - - + {followUpData?.questions && followUpData.questions.length > 0 ? ( + { + onSuggestionClick?.({ answer: response }) + }} + /> + ) : ( + <> + + + + )}
    ) diff --git a/webview-ui/src/components/chat/MultiQuestionHandler.tsx b/webview-ui/src/components/chat/MultiQuestionHandler.tsx new file mode 100644 index 00000000000..dea9da65d0c --- /dev/null +++ b/webview-ui/src/components/chat/MultiQuestionHandler.tsx @@ -0,0 +1,178 @@ +import { useState, useEffect } from "react" +import { Button, AutosizeTextarea } from "@/components/ui" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { FollowUpQuestion } from "@roo-code/types" + +interface MultiQuestionHandlerProps { + questions: FollowUpQuestion[] + onSendResponse: (response: string) => void +} + +interface QuestionItemProps { + question: FollowUpQuestion + title: string + textValue: string + selectedOption?: string + onTextChange: (value: string) => void + onOptionClick: (option: string) => void +} + +const QuestionItem = ({ + question, + title, + textValue, + selectedOption, + onTextChange, + onOptionClick, +}: QuestionItemProps) => { + const { t } = useAppTranslation() + const qText = typeof question === "string" ? question : question.text + const options = typeof question === "string" ? undefined : question.options + + return ( +
    +
    {title}
    +
    {qText}
    + {options && options.length > 0 && ( +
    + {options.map((option, idx) => ( + + ))} +
    + )} + onTextChange(e.target.value)} + minHeight={21} + maxHeight={200} + placeholder={t("chat:questions.typeAnswer")} + className="w-full py-2 pl-3 pr-3 rounded border border-transparent" + /> +
    + ) +} + +export const MultiQuestionHandler = ({ questions, onSendResponse }: MultiQuestionHandlerProps) => { + const { t } = useAppTranslation() + const { showQuestionsOneByOne } = useExtensionState() + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0) + const [selectedOptions, setSelectedOptions] = useState<(string | undefined)[]>( + new Array(questions.length).fill(undefined), + ) + const [textAnswers, setTextAnswers] = useState(new Array(questions.length).fill("")) + const [oneByOneInputValue, setOneByOneInputValue] = useState("") + + useEffect(() => { + if (showQuestionsOneByOne) { + setOneByOneInputValue(textAnswers[currentQuestionIndex] || "") + } + }, [currentQuestionIndex, textAnswers, showQuestionsOneByOne]) + + const updateTextAnswer = (index: number, value: string) => { + const next = [...textAnswers] + next[index] = value + setTextAnswers(next) + } + + const handleNext = () => { + updateTextAnswer(currentQuestionIndex, oneByOneInputValue) + if (currentQuestionIndex < questions.length - 1) setCurrentQuestionIndex(currentQuestionIndex + 1) + } + + const handlePrevious = () => { + updateTextAnswer(currentQuestionIndex, oneByOneInputValue) + if (currentQuestionIndex > 0) setCurrentQuestionIndex(currentQuestionIndex - 1) + } + + const handleOptionClick = (index: number, option: string) => { + setSelectedOptions((prev) => { + const next = [...prev] + next[index] = next[index] === option ? undefined : option + return next + }) + } + + const handleFinish = () => { + let finalAnswers = textAnswers + if (showQuestionsOneByOne) { + finalAnswers = [...textAnswers] + finalAnswers[currentQuestionIndex] = oneByOneInputValue + } + + const combined = questions + .map((q, i) => { + const qText = typeof q === "string" ? q : q.text + const text = finalAnswers[i].trim() + const option = selectedOptions[i] + const answer = option && text ? `${option}: ${text}` : option || text || "(skipped)" + return `Question: ${qText}\nAnswer: ${answer}` + }) + .join("\n\n") + onSendResponse(combined) + } + + if (showQuestionsOneByOne) { + return ( +
    + handleOptionClick(currentQuestionIndex, opt)} + /> +
    + {currentQuestionIndex > 0 && ( + + )} + +
    +
    + ) + } + + return ( +
    + {questions.map((q, i) => ( + updateTextAnswer(i, val)} + onOptionClick={(opt) => handleOptionClick(i, opt)} + /> + ))} +
    + +
    +
    + ) +} diff --git a/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx b/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx new file mode 100644 index 00000000000..d18163e139c --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx @@ -0,0 +1,242 @@ +import { render, screen, fireEvent } from "@/utils/test-utils" +import { vi, describe, it, expect, beforeEach } from "vitest" +import TranslationProvider from "@src/i18n/TranslationContext" +import { MultiQuestionHandler } from "../MultiQuestionHandler" + +// Mock ExtensionStateContext +vi.mock("@src/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + language: "en", + showQuestionsOneByOne: true, + }), +})) + +// Mock react-i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + i18n: { + t: (key: string, options?: Record) => { + // Mock specific translations used in tests + if (key === "chat:questions.questionNumberOfTotal" && options) { + return `Question ${options.current} of ${options.total}` + } + if (key === "chat:questions.questionNumber" && options) { + return `Question ${options.number}` + } + if (key === "chat:questions.typeAnswer") return "Type your answer..." + if (key === "chat:questions.previous") return "Previous" + if (key === "chat:questions.next") return "Next" + if (key === "chat:questions.finish") return "Finish" + return key + }, + changeLanguage: vi.fn(), + }, + }), +})) + +// Mock translations +vi.mock("@src/i18n/setup", () => ({ + default: { + t: (key: string, options?: Record) => { + // Mock specific translations used in tests + if (key === "chat:questions.questionNumberOfTotal" && options) { + return `Question ${options.current} of ${options.total}` + } + if (key === "chat:questions.questionNumber" && options) { + return `Question ${options.number}` + } + if (key === "chat:questions.typeAnswer") return "Type your answer..." + if (key === "chat:questions.previous") return "Previous" + if (key === "chat:questions.next") return "Next" + if (key === "chat:questions.finish") return "Finish" + return key + }, + changeLanguage: vi.fn(), + }, + loadTranslations: vi.fn(), +})) + +const TestWrapper = ({ children }: { children: React.ReactNode }) => ( + {children} +) + +describe("MultiQuestionHandler", () => { + const mockOnSendResponse = vi.fn() + + beforeEach(() => { + mockOnSendResponse.mockClear() + }) + + it("should render single question correctly", () => { + const questions = ["What is your name?"] + render( + + + , + ) + + expect(screen.getByText("Question 1 of 1")).toBeInTheDocument() + expect(screen.getByText("What is your name?")).toBeInTheDocument() + expect(screen.getByText("Finish")).toBeInTheDocument() + }) + + it("should render multiple questions with navigation", () => { + const questions = ["What is your name?", "What is your age?"] + render( + + + , + ) + + expect(screen.getByText("Question 1 of 2")).toBeInTheDocument() + expect(screen.getByText("What is your name?")).toBeInTheDocument() + expect(screen.getByText("Next")).toBeInTheDocument() + expect(screen.queryByText("Previous")).not.toBeInTheDocument() + expect(screen.queryByText("Finish")).not.toBeInTheDocument() + }) + + it("should navigate between questions without sending responses", () => { + const questions = ["What is your name?", "What is your age?"] + render( + + + , + ) + + const textarea = screen.getByPlaceholderText("Type your answer...") + fireEvent.change(textarea, { target: { value: "John" } }) + + const nextButton = screen.getByText("Next") + fireEvent.click(nextButton) + + expect(screen.getByText("Question 2 of 2")).toBeInTheDocument() + expect(screen.getByText("What is your age?")).toBeInTheDocument() + expect(mockOnSendResponse).not.toHaveBeenCalled() + + const previousButton = screen.getByText("Previous") + fireEvent.click(previousButton) + + expect(screen.getByText("Question 1 of 2")).toBeInTheDocument() + expect(screen.getByText("What is your name?")).toBeInTheDocument() + expect(textarea).toHaveValue("John") + expect(mockOnSendResponse).not.toHaveBeenCalled() + }) + + it("should only send response when Finish is clicked", () => { + const questions = ["What is your name?", "What is your age?"] + render( + + + , + ) + + const textarea = screen.getByPlaceholderText("Type your answer...") + fireEvent.change(textarea, { target: { value: "John" } }) + + const nextButton = screen.getByText("Next") + fireEvent.click(nextButton) + + fireEvent.change(textarea, { target: { value: "25" } }) + + const finishButton = screen.getByText("Finish") + fireEvent.click(finishButton) + + expect(mockOnSendResponse).toHaveBeenCalledTimes(1) + expect(mockOnSendResponse).toHaveBeenCalledWith( + "Question: What is your name?\nAnswer: John\n\nQuestion: What is your age?\nAnswer: 25", + ) + }) + + it("should handle skipped questions", () => { + const questions = ["What is your name?", "What is your age?"] + render( + + + , + ) + + const nextButton = screen.getByText("Next") + fireEvent.click(nextButton) // Skip first question + + const textarea = screen.getByPlaceholderText("Type your answer...") + fireEvent.change(textarea, { target: { value: "25" } }) + + const finishButton = screen.getByText("Finish") + fireEvent.click(finishButton) + + expect(mockOnSendResponse).toHaveBeenCalledWith( + "Question: What is your name?\nAnswer: (skipped)\n\nQuestion: What is your age?\nAnswer: 25", + ) + }) + + it("should preserve answers when navigating back and forth", () => { + const questions = ["Q1", "Q2", "Q3"] + render( + + + , + ) + + const textarea = screen.getByPlaceholderText("Type your answer...") + + // Answer Q1 + fireEvent.change(textarea, { target: { value: "A1" } }) + fireEvent.click(screen.getByText("Next")) + + // Answer Q2 + fireEvent.change(textarea, { target: { value: "A2" } }) + fireEvent.click(screen.getByText("Next")) + + // Go back to Q1 + fireEvent.click(screen.getByText("Previous")) + fireEvent.click(screen.getByText("Previous")) + + expect(textarea).toHaveValue("A1") + + // Go to Q2 + fireEvent.click(screen.getByText("Next")) + expect(textarea).toHaveValue("A2") + + expect(mockOnSendResponse).not.toHaveBeenCalled() + }) + + it("should handle options correctly without copying to text area", () => { + const questions = [{ text: "Color?", options: ["Red", "Blue"] }] + render( + + + , + ) + + const redButton = screen.getByText("Red") + fireEvent.click(redButton) + + const textarea = screen.getByPlaceholderText("Type your answer...") + expect(textarea).toHaveValue("") + + const finishButton = screen.getByText("Finish") + fireEvent.click(finishButton) + + expect(mockOnSendResponse).toHaveBeenCalledWith("Question: Color?\nAnswer: Red") + }) + + it("should handle both option and text answer", () => { + const questions = [{ text: "Color?", options: ["Red", "Blue"] }] + render( + + + , + ) + + const redButton = screen.getByText("Red") + fireEvent.click(redButton) + + const textarea = screen.getByPlaceholderText("Type your answer...") + fireEvent.change(textarea, { target: { value: "very dark" } }) + + const finishButton = screen.getByText("Finish") + fireEvent.click(finishButton) + + expect(mockOnSendResponse).toHaveBeenCalledWith("Question: Color?\nAnswer: Red: very dark") + }) +}) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index eb57b4e0009..dbac92538c5 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -212,6 +212,8 @@ const SettingsView = forwardRef(({ onDone, t includeCurrentTime, includeCurrentCost, maxGitStatusFiles, + showQuestionsOneByOne, + taskHeaderHighlightEnabled, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -430,6 +432,7 @@ const SettingsView = forwardRef(({ onDone, t enterBehavior: enterBehavior ?? "send", includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, + showQuestionsOneByOne: showQuestionsOneByOne ?? false, maxGitStatusFiles: maxGitStatusFiles ?? 0, profileThresholds, imageGenerationProvider, @@ -921,6 +924,8 @@ const SettingsView = forwardRef(({ onDone, t )} diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index a3488dc59e1..9f65c350c6c 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -1,4 +1,5 @@ import { HTMLAttributes, useMemo } from "react" +import { ExtensionStateContextType } from "@/context/ExtensionStateContext" import { useAppTranslation } from "@/i18n/TranslationContext" import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { telemetryClient } from "@/utils/TelemetryClient" @@ -7,17 +8,20 @@ import { SetCachedStateField } from "./types" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" import { SearchableSetting } from "./SearchableSetting" -import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { reasoningBlockCollapsed: boolean enterBehavior: "send" | "newline" + showQuestionsOneByOne: boolean + taskHeaderHighlightEnabled: boolean setCachedStateField: SetCachedStateField } export const UISettings = ({ reasoningBlockCollapsed, enterBehavior, + showQuestionsOneByOne, + taskHeaderHighlightEnabled, setCachedStateField, ...props }: UISettingsProps) => { @@ -48,6 +52,19 @@ export const UISettings = ({ }) } + const handleShowQuestionsOneByOneChange = (value: boolean) => { + setCachedStateField("showQuestionsOneByOne", value) + + // Track telemetry event + telemetryClient.capture("ui_settings_show_questions_one_by_one_changed", { + enabled: value, + }) + } + + const handleTaskHeaderHighlightChange = (enabled: boolean) => { + setCachedStateField("taskHeaderHighlightEnabled", enabled) + } + return (
    {t("settings:sections.ui")} @@ -91,6 +108,42 @@ export const UISettings = ({
    + + {/* Show Questions One By One Setting */} + +
    + handleShowQuestionsOneByOneChange(e.target.checked)} + data-testid="show-questions-one-by-one-checkbox"> + {t("settings:ui.showQuestionsOneByOne.label")} + +
    + {t("settings:ui.showQuestionsOneByOne.description")} +
    +
    +
    + + {/* Task Header Highlight Setting */} + +
    + handleTaskHeaderHighlightChange(e.target.checked)} + data-testid="task-header-highlight-checkbox"> + {t("settings:ui.taskHeaderHighlight.label")} + +
    + {t("settings:ui.taskHeaderHighlight.description")} +
    +
    +
    diff --git a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx index 2a21a410b38..90d1ae5d23b 100644 --- a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx @@ -6,6 +6,8 @@ describe("UISettings", () => { const defaultProps = { reasoningBlockCollapsed: false, enterBehavior: "send" as const, + showQuestionsOneByOne: false, + taskHeaderHighlightEnabled: false, setCachedStateField: vi.fn(), } diff --git a/webview-ui/src/components/ui/autosize-textarea.tsx b/webview-ui/src/components/ui/autosize-textarea.tsx index d23f7a43600..26c660f6e56 100644 --- a/webview-ui/src/components/ui/autosize-textarea.tsx +++ b/webview-ui/src/components/ui/autosize-textarea.tsx @@ -91,7 +91,7 @@ export const AutosizeTextarea = React.forwardRef void includeCurrentCost?: boolean setIncludeCurrentCost: (value: boolean) => void + showQuestionsOneByOne?: boolean + setShowQuestionsOneByOne: (value: boolean) => void showWorktreesInHomeScreen: boolean setShowWorktreesInHomeScreen: (value: boolean) => void + taskHeaderHighlightEnabled: boolean + setTaskHeaderHighlightEnabled: (value: boolean) => void } export const ExtensionStateContext = createContext(undefined) @@ -264,6 +268,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode openRouterImageGenerationSelectedModel: "", includeCurrentTime: true, includeCurrentCost: true, + showQuestionsOneByOne: false, + taskHeaderHighlightEnabled: false, lockApiConfigAcrossModes: false, }) @@ -288,6 +294,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const [prevCloudIsAuthenticated, setPrevCloudIsAuthenticated] = useState(false) const [includeCurrentTime, setIncludeCurrentTime] = useState(true) const [includeCurrentCost, setIncludeCurrentCost] = useState(true) + const [showQuestionsOneByOne, setShowQuestionsOneByOne] = useState(false) const setListApiConfigMeta = useCallback( (value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), @@ -333,6 +340,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode if ((newState as any).includeCurrentCost !== undefined) { setIncludeCurrentCost((newState as any).includeCurrentCost) } + // Update showQuestionsOneByOne if present in state message + if ((newState as any).showQuestionsOneByOne !== undefined) { + setShowQuestionsOneByOne((newState as any).showQuestionsOneByOne) + } // Handle marketplace data if present in state message if (newState.marketplaceItems !== undefined) { setMarketplaceItems(newState.marketplaceItems) @@ -610,6 +621,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setIncludeCurrentTime, includeCurrentCost, setIncludeCurrentCost, + showQuestionsOneByOne, + setShowQuestionsOneByOne, + taskHeaderHighlightEnabled: state.taskHeaderHighlightEnabled ?? false, + setTaskHeaderHighlightEnabled: (value) => + setState((prevState) => ({ ...prevState, taskHeaderHighlightEnabled: value })), showWorktreesInHomeScreen: state.showWorktreesInHomeScreen ?? true, setShowWorktreesInHomeScreen: (value) => setState((prevState) => ({ ...prevState, showWorktreesInHomeScreen: value })), diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 54602475754..d3a4f2b7c95 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Veure tasca" }, "questions": { - "hasQuestion": "Roo té una pregunta" + "hasQuestion": "Roo té una pregunta", + "questionNumber": "Pregunta {{number}}", + "questionNumberOfTotal": "Pregunta {{current}} de {{total}}", + "typeAnswer": "Escriu la teva resposta aquí...", + "previous": "Anterior", + "next": "Següent", + "finish": "Finalitzar" }, "taskCompleted": "Tasca completada", "modelResponseIncomplete": "Resposta del model incompleta", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index a04fd202cc4..294d8eed7a4 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Requereix {{primaryMod}}+Intro per enviar missatges", "description": "Quan estigui activat, has de prémer {{primaryMod}}+Intro per enviar missatges en lloc de només Intro" + }, + "showQuestionsOneByOne": { + "label": "Mostra les preguntes d'una en una", + "description": "Quan està activat, les preguntes del model es mostraran una a una." + }, + "taskHeaderHighlight": { + "label": "Destaca l'encapçalament de la tasca", + "description": "Quan està activat, l'encapçalament de la tasca es destacarà per ajudar-te a centrar-te en la tasca actual." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index fe00c912787..175d98a624e 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Aufgabe anzeigen" }, "questions": { - "hasQuestion": "Roo hat eine Frage" + "hasQuestion": "Roo hat eine Frage", + "questionNumber": "Frage {{number}}", + "questionNumberOfTotal": "Frage {{current}} von {{total}}", + "typeAnswer": "Gib deine Antwort hier ein...", + "previous": "Zurück", + "next": "Weiter", + "finish": "Beenden" }, "taskCompleted": "Aufgabe abgeschlossen", "modelResponseIncomplete": "Modellantwort unvollständig", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8f565ba2048..d7f79ae2713 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "{{primaryMod}}+Enter zum Senden erfordern", "description": "Wenn aktiviert, musst du {{primaryMod}}+Enter drücken, um Nachrichten zu senden, anstatt nur Enter" + }, + "showQuestionsOneByOne": { + "label": "Fragen einzeln anzeigen", + "description": "Wenn aktiviert, werden Fragen des Modells nacheinander angezeigt." + }, + "taskHeaderHighlight": { + "label": "Task-Header hervorheben", + "description": "Wenn aktiviert, wird der Task-Header hervorgehoben, um dich auf die aktuelle Aufgabe zu konzentrieren." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 2a0cc4a54ce..50cd98bf7f8 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -311,7 +311,13 @@ "goToSubtask": "View task" }, "questions": { - "hasQuestion": "Roo has a question" + "hasQuestion": "Roo has a question", + "questionNumber": "Question {{number}}", + "questionNumberOfTotal": "Question {{current}} of {{total}}", + "typeAnswer": "Type your answer here...", + "previous": "Previous", + "next": "Next", + "finish": "Finish" }, "taskCompleted": "Task Completed", "error": "Error", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 9f727255205..470df200879 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -164,6 +164,14 @@ "requireCtrlEnterToSend": { "label": "Require {{primaryMod}}+Enter to send messages", "description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter" + }, + "showQuestionsOneByOne": { + "label": "Show questions one by one", + "description": "When enabled, questions from the model will be displayed one at a time." + }, + "taskHeaderHighlight": { + "label": "Highlight task header", + "description": "When enabled, the task header will be highlighted to help you focus on the current task." } }, "prompts": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 56f3c5404b3..d5690663dde 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Ver tarea" }, "questions": { - "hasQuestion": "Roo tiene una pregunta" + "hasQuestion": "Roo tiene una pregunta", + "questionNumber": "Pregunta {{number}}", + "questionNumberOfTotal": "Pregunta {{current}} de {{total}}", + "typeAnswer": "Escribe tu respuesta aquí...", + "previous": "Anterior", + "next": "Siguiente", + "finish": "Finalizar" }, "taskCompleted": "Tarea completada", "modelResponseIncomplete": "Respuesta del modelo incompleta", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 55977aae8dc..ddf4dfc5c9e 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Requerir {{primaryMod}}+Enter para enviar mensajes", "description": "Cuando está activado, debes presionar {{primaryMod}}+Enter para enviar mensajes en lugar de solo Enter" + }, + "showQuestionsOneByOne": { + "label": "Mostrar preguntas de una en una", + "description": "Cuando está activado, las preguntas del modelo se mostrarán una a una." + }, + "taskHeaderHighlight": { + "label": "Resaltar el encabezado de la tarea", + "description": "Cuando está activado, el encabezado de la tarea se resaltará para ayudarte a concentrarte en la tarea actual." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index f838e1de27f..081590d1857 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Afficher la tâche" }, "questions": { - "hasQuestion": "Roo a une question" + "hasQuestion": "Roo a une question", + "questionNumber": "Question {{number}}", + "questionNumberOfTotal": "Question {{current}} sur {{total}}", + "typeAnswer": "Tapez votre réponse ici...", + "previous": "Précédent", + "next": "Suivant", + "finish": "Terminer" }, "taskCompleted": "Tâche terminée", "modelResponseIncomplete": "Réponse du modèle incomplète", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 1423704dd8b..1c712dc1c5d 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Exiger {{primaryMod}}+Entrée pour envoyer les messages", "description": "Lorsqu'activé, tu dois appuyer sur {{primaryMod}}+Entrée pour envoyer des messages au lieu de simplement Entrée" + }, + "showQuestionsOneByOne": { + "label": "Afficher les questions une par une", + "description": "Lorsqu'elles sont activées, les questions du modèle s'affichent une à une." + }, + "taskHeaderHighlight": { + "label": "Mettre en évidence l'en-tête de la tâche", + "description": "Lorsqu'il est activé, l'en-tête de la tâche sera mis en évidence pour vous aider à vous concentrer sur la tâche en cours." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 0a91af01308..ed1360f070d 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "कार्य देखें" }, "questions": { - "hasQuestion": "Roo का एक प्रश्न है" + "hasQuestion": "Roo का एक प्रश्न है", + "questionNumber": "प्रश्न {{number}}", + "questionNumberOfTotal": "{{total}} में से प्रश्न {{current}}", + "typeAnswer": "अपना उत्तर यहाँ टाइप करें...", + "previous": "पिछला", + "next": "अगला", + "finish": "समाप्त" }, "taskCompleted": "कार्य पूरा हुआ", "modelResponseIncomplete": "मॉडल प्रतिक्रिया अपूर्ण", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2932913f37c..ee9d1cad721 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "संदेश भेजने के लिए {{primaryMod}}+Enter की आवश्यकता है", "description": "जब सक्षम हो, तो आपको केवल Enter के बजाय संदेश भेजने के लिए {{primaryMod}}+Enter दबाना होगा" + }, + "showQuestionsOneByOne": { + "label": "एक-एक करके प्रश्न दिखाएं", + "description": "सक्षम होने पर, मॉडल के प्रश्न एक-एक करके प्रदर्शित किए जाएंगे।" + }, + "taskHeaderHighlight": { + "label": "टास्क हेडर को हाइलाइट करें", + "description": "सक्षम होने पर, वर्तमान टास्क पर ध्यान केंद्रित करने में आपकी मदद करने के लिए टास्क हेडर को हाइलाइट किया जाएगा।" } }, "skills": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 7adbd06edd7..15f259b1038 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -320,7 +320,13 @@ "goToSubtask": "Lihat tugas" }, "questions": { - "hasQuestion": "Roo punya pertanyaan" + "hasQuestion": "Roo punya pertanyaan", + "questionNumber": "Pertanyaan {{number}}", + "questionNumberOfTotal": "Pertanyaan {{current}} dari {{total}}", + "typeAnswer": "Ketik jawaban Anda di sini...", + "previous": "Sebelumnya", + "next": "Berikutnya", + "finish": "Selesai" }, "taskCompleted": "Tugas Selesai", "error": "Error", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 08e8c7ccec9..3c7a7f95c61 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Memerlukan {{primaryMod}}+Enter untuk mengirim pesan", "description": "Ketika diaktifkan, kamu harus menekan {{primaryMod}}+Enter untuk mengirim pesan alih-alih hanya Enter" + }, + "showQuestionsOneByOne": { + "label": "Tampilkan pertanyaan satu per satu", + "description": "Jika diaktifkan, pertanyaan dari model akan ditampilkan satu per satu." + }, + "taskHeaderHighlight": { + "label": "Sorot header tugas", + "description": "Jika diaktifkan, header tugas akan disorot untuk membantu Anda fokus pada tugas saat ini." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 461cc905565..be8714f8c72 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Visualizza attività" }, "questions": { - "hasQuestion": "Roo ha una domanda" + "hasQuestion": "Roo ha una domanda", + "questionNumber": "Domanda {{number}}", + "questionNumberOfTotal": "Domanda {{current}} di {{total}}", + "typeAnswer": "Scrivi la tua risposta qui...", + "previous": "Precedente", + "next": "Successivo", + "finish": "Fine" }, "taskCompleted": "Attività completata", "modelResponseIncomplete": "Risposta del modello incompleta", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 7ee641c3b3a..dcbfb5c80ec 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Richiedi {{primaryMod}}+Invio per inviare messaggi", "description": "Quando abilitato, devi premere {{primaryMod}}+Invio per inviare messaggi invece di solo Invio" + }, + "showQuestionsOneByOne": { + "label": "Mostra domande una per una", + "description": "Se abilitato, le domande del modello verranno visualizzate una alla volta." + }, + "taskHeaderHighlight": { + "label": "Evidenzia intestazione attività", + "description": "Se abilitato, l'intestazione dell'attività verrà evidenziata per aiutarti a concentrarti sull'attività corrente." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 7d0957a5328..d340ddaa00e 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "タスクを表示" }, "questions": { - "hasQuestion": "Rooは質問があります" + "hasQuestion": "Rooは質問があります", + "questionNumber": "質問 {{number}}", + "questionNumberOfTotal": "質問 {{current}} / {{total}}", + "typeAnswer": "ここに回答を入力してください...", + "previous": "前へ", + "next": "次へ", + "finish": "完了" }, "taskCompleted": "タスク完了", "modelResponseIncomplete": "モデルの応答が不完全", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 787d3db2189..b2594ba6c15 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "メッセージを送信するには{{primaryMod}}+Enterが必要", "description": "有効にすると、Enterだけでなく{{primaryMod}}+Enterを押してメッセージを送信する必要があります" + }, + "showQuestionsOneByOne": { + "label": "質問を1つずつ表示", + "description": "有効にすると、モデルからの質問が1つずつ表示されます。" + }, + "taskHeaderHighlight": { + "label": "タスクヘッダーを強調表示", + "description": "有効にすると、現在のタスクに集中しやすいようにタスクヘッダーが強調表示されます。" } }, "skills": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index d61781d79ed..dd039740e23 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "작업 보기" }, "questions": { - "hasQuestion": "Roo에게 질문이 있습니다" + "hasQuestion": "Roo에게 질문이 있습니다", + "questionNumber": "질문 {{number}}", + "questionNumberOfTotal": "질문 {{current}} / {{total}}", + "typeAnswer": "여기에 답변을 입력하세요...", + "previous": "이전", + "next": "다음", + "finish": "완료" }, "taskCompleted": "작업 완료", "modelResponseIncomplete": "모델 응답 불완전", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 6c4a91f9c4a..041261a4e9f 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "메시지를 보내려면 {{primaryMod}}+Enter가 필요", "description": "활성화하면 Enter만으로는 안 되고 {{primaryMod}}+Enter를 눌러야 메시지를 보낼 수 있습니다" + }, + "showQuestionsOneByOne": { + "label": "질문을 하나씩 표시", + "description": "활성화하면 모델의 질문이 하나씩 표시됩니다." + }, + "taskHeaderHighlight": { + "label": "작업 헤더 강조 표시", + "description": "활성화하면 현재 작업에 집중할 수 있도록 작업 헤더가 강조 표시됩니다." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 5d924ea97bd..36f6ca5b254 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -264,7 +264,13 @@ "goToSubtask": "Taak weergeven" }, "questions": { - "hasQuestion": "Roo heeft een vraag" + "hasQuestion": "Roo heeft een vraag", + "questionNumber": "Vraag {{number}}", + "questionNumberOfTotal": "Vraag {{current}} van {{total}}", + "typeAnswer": "Typ hier je antwoord...", + "previous": "Vorige", + "next": "Volgende", + "finish": "Voltooien" }, "taskCompleted": "Taak voltooid", "error": "Fout", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 3cf63857167..51215f42604 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Vereist {{primaryMod}}+Enter om berichten te versturen", "description": "Wanneer ingeschakeld, moet je {{primaryMod}}+Enter indrukken om berichten te versturen in plaats van alleen Enter" + }, + "showQuestionsOneByOne": { + "label": "Vragen één voor één weergeven", + "description": "Indien ingeschakeld, worden vragen van het model één voor één weergegeven." + }, + "taskHeaderHighlight": { + "label": "Taakkoptekst markeren", + "description": "Indien ingeschakeld, wordt de taakkoptekst gemarkeerd om je te helpen focussen op de huidige taak." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 93e0601b169..9f4d8111d4c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Wyświetl zadanie" }, "questions": { - "hasQuestion": "Roo ma pytanie" + "hasQuestion": "Roo ma pytanie", + "questionNumber": "Pytanie {{number}}", + "questionNumberOfTotal": "Pytanie {{current}} z {{total}}", + "typeAnswer": "Wpisz swoją odpowiedź tutaj...", + "previous": "Poprzednie", + "next": "Następne", + "finish": "Zakończ" }, "taskCompleted": "Zadanie zakończone", "modelResponseIncomplete": "Odpowiedź modelu niekompletna", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 7dc5dfa7661..f3c5d51b09f 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Wymagaj {{primaryMod}}+Enter do wysyłania wiadomości", "description": "Po włączeniu musisz nacisnąć {{primaryMod}}+Enter, aby wysłać wiadomości, zamiast tylko Enter" + }, + "showQuestionsOneByOne": { + "label": "Pokaż pytania jedno po drugim", + "description": "Po włączeniu pytania z modelu będą wyświetlane jedno po drugim." + }, + "taskHeaderHighlight": { + "label": "Wyróżnij nagłówek zadania", + "description": "Po włączeniu nagłówek zadania zostanie wyróżniony, aby pomóc Ci skupić się na bieżącym zadaniu." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index bf4c1e09fd7..a3f7f17265e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -269,7 +269,13 @@ "goToSubtask": "Ver tarefa" }, "questions": { - "hasQuestion": "Roo tem uma pergunta" + "hasQuestion": "Roo tem uma pergunta", + "questionNumber": "Pergunta {{number}}", + "questionNumberOfTotal": "Pergunta {{current}} de {{total}}", + "typeAnswer": "Digite sua resposta aqui...", + "previous": "Anterior", + "next": "Próximo", + "finish": "Concluir" }, "taskCompleted": "Tarefa concluída", "modelResponseIncomplete": "Resposta do modelo incompleta", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index af1e68e8240..63ea1ac6e0b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Requer {{primaryMod}}+Enter para enviar mensagens", "description": "Quando ativado, você deve pressionar {{primaryMod}}+Enter para enviar mensagens em vez de apenas Enter" + }, + "showQuestionsOneByOne": { + "label": "Mostrar perguntas uma por uma", + "description": "Quando ativado, as perguntas do modelo serão exibidas uma por uma." + }, + "taskHeaderHighlight": { + "label": "Destacar cabeçalho da tarefa", + "description": "Quando ativado, o cabeçalho da tarefa será destacado para ajudar você a se concentrar na tarefa atual." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 9a5245f91ed..924fbbe136d 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -265,7 +265,13 @@ "goToSubtask": "Просмотреть задачу" }, "questions": { - "hasQuestion": "У Roo есть вопрос" + "hasQuestion": "У Roo есть вопрос", + "questionNumber": "Вопрос {{number}}", + "questionNumberOfTotal": "Вопрос {{current}} из {{total}}", + "typeAnswer": "Введите ваш ответ здесь...", + "previous": "Назад", + "next": "Далее", + "finish": "Завершить" }, "taskCompleted": "Задача завершена", "error": "Ошибка", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 94e9a78ff51..d729d30f45a 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Требовать {{primaryMod}}+Enter для отправки сообщений", "description": "Если включено, необходимо нажать {{primaryMod}}+Enter для отправки сообщений вместо простого Enter" + }, + "showQuestionsOneByOne": { + "label": "Показывать вопросы по одному", + "description": "Если включено, вопросы от модели будут отображаться по одному." + }, + "taskHeaderHighlight": { + "label": "Выделять заголовок задачи", + "description": "Если включено, заголовок задачи будет выделен, чтобы помочь вам сосредоточиться на текущей задаче." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index a2fe5f3a4cf..fa6b36eba4b 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -270,7 +270,13 @@ "goToSubtask": "Görevi görüntüle" }, "questions": { - "hasQuestion": "Roo'nun bir sorusu var" + "hasQuestion": "Roo'nun bir sorusu var", + "questionNumber": "Soru {{number}}", + "questionNumberOfTotal": "{{total}} sorudan {{current}}. soru", + "typeAnswer": "Cevabınızı buraya yazın...", + "previous": "Önceki", + "next": "Sonraki", + "finish": "Bitir" }, "taskCompleted": "Görev Tamamlandı", "modelResponseIncomplete": "Model yanıtı eksik", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index df219f179d0..da482e94626 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Mesaj göndermek için {{primaryMod}}+Enter gerekli", "description": "Etkinleştirildiğinde, sadece Enter yerine mesaj göndermek için {{primaryMod}}+Enter'a basmalısınız" + }, + "showQuestionsOneByOne": { + "label": "Soruları tek tek göster", + "description": "Etkinleştirildiğinde, modelden gelen sorular tek tek görüntülenecektir." + }, + "taskHeaderHighlight": { + "label": "Görev başlığını vurgula", + "description": "Etkinleştirildiğinde, mevcut göreve odaklanmanıza yardımcı olmak için görev başlığı vurgulanacaktır." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 514a3a2db1c..6e23b93ef58 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -270,7 +270,13 @@ "goToSubtask": "Xem nhiệm vụ" }, "questions": { - "hasQuestion": "Roo có một câu hỏi" + "hasQuestion": "Roo có một câu hỏi", + "questionNumber": "Câu hỏi {{number}}", + "questionNumberOfTotal": "Câu hỏi {{current}} trên {{total}}", + "typeAnswer": "Nhập câu trả lời của bạn vào đây...", + "previous": "Trước", + "next": "Tiếp", + "finish": "Hoàn thành" }, "taskCompleted": "Nhiệm vụ hoàn thành", "modelResponseIncomplete": "Phản hồi của mô hình không đầy đủ", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 56641811731..9ddc139f8e7 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "Yêu cầu {{primaryMod}}+Enter để gửi tin nhắn", "description": "Khi được bật, bạn phải nhấn {{primaryMod}}+Enter để gửi tin nhắn thay vì chỉ nhấn Enter" + }, + "showQuestionsOneByOne": { + "label": "Hiển thị từng câu hỏi một", + "description": "Khi được bật, các câu hỏi từ mô hình sẽ được hiển thị từng câu một." + }, + "taskHeaderHighlight": { + "label": "Làm nổi bật tiêu đề nhiệm vụ", + "description": "Khi được bật, tiêu đề nhiệm vụ sẽ được làm nổi bật để giúp bạn tập trung vào nhiệm vụ hiện tại." } }, "skills": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index ea31595757c..42f9fcf41bb 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -270,7 +270,13 @@ "goToSubtask": "查看任务" }, "questions": { - "hasQuestion": "Roo有一个问题" + "hasQuestion": "Roo有一个问题", + "questionNumber": "问题 {{number}}", + "questionNumberOfTotal": "第 {{current}} / {{total}} 个问题", + "typeAnswer": "在此输入您的回答...", + "previous": "上一个", + "next": "下一个", + "finish": "完成" }, "taskCompleted": "任务完成", "modelResponseIncomplete": "模型响应不完整", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 4c2eef0efbb..91502e58955 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -1025,6 +1025,14 @@ "requireCtrlEnterToSend": { "label": "需要 {{primaryMod}}+Enter 发送消息", "description": "启用后,必须按 {{primaryMod}}+Enter 发送消息,而不仅仅是 Enter" + }, + "showQuestionsOneByOne": { + "label": "逐个显示问题", + "description": "启用后,模型的问题将逐个显示。" + }, + "taskHeaderHighlight": { + "label": "高亮任务页眉", + "description": "启用后,任务页眉将被高亮显示,以帮助您专注于当前任务。" } }, "skills": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index d53c1d3441d..faddaf3fe61 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -314,7 +314,13 @@ "goToSubtask": "查看工作" }, "questions": { - "hasQuestion": "Roo 有一個問題" + "hasQuestion": "Roo 有一個問題", + "questionNumber": "問題 {{number}}", + "questionNumberOfTotal": "第 {{current}} / {{total}} 個問題", + "typeAnswer": "在此輸入您的回答...", + "previous": "上一個", + "next": "下一個", + "finish": "完成" }, "taskCompleted": "工作完成", "error": "錯誤", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index cf27a25498c..22858a799a6 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -111,6 +111,14 @@ "requireCtrlEnterToSend": { "label": "需要 {{primaryMod}}+Enter 傳送訊息", "description": "啟用後,必須按 {{primaryMod}}+Enter 傳送訊息,而不只是 Enter" + }, + "showQuestionsOneByOne": { + "label": "逐個顯示問題", + "description": "啟用後,模型的問題將逐個顯示。" + }, + "taskHeaderHighlight": { + "label": "高亮任務頁眉", + "description": "啟用後,任務頁眉將被高亮顯示,以幫助您專注於當前任務。" } }, "prompts": {