diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index cc7d35e54965..46978350d6a5 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.62.0", + "version": "1.63.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 5424121d6709..5d5610daa10b 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -6,7 +6,12 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js" * ExperimentId */ -export const experimentIds = ["powerSteering", "multiFileApplyDiff", "preventFocusDisruption"] as const +export const experimentIds = [ + "powerSteering", + "multiFileApplyDiff", + "preventFocusDisruption", + "imageGeneration", +] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -20,6 +25,7 @@ export const experimentsSchema = z.object({ powerSteering: z.boolean().optional(), multiFileApplyDiff: z.boolean().optional(), preventFocusDisruption: z.boolean().optional(), + imageGeneration: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index b55dc8a8c3ad..15833e00c4dc 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -142,6 +142,13 @@ const openRouterSchema = baseProviderSettingsSchema.extend({ openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), openRouterUseMiddleOutTransform: z.boolean().optional(), + // Image generation settings (experimental) + openRouterImageGenerationSettings: z + .object({ + openRouterApiKey: z.string().optional(), + selectedModel: z.string().optional(), + }) + .optional(), }) const bedrockSchema = apiModelIdProviderModelSchema.extend({ diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 7a3fd2119996..c31f63df7648 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -34,6 +34,7 @@ export const toolNames = [ "fetch_instructions", "codebase_search", "update_todo_list", + "generate_image", ] as const export const toolNamesSchema = z.enum(toolNames) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8555a377ad24..3e012472905a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,8 +584,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.25.0 - version: 0.25.0 + specifier: ^0.29.0 + version: 0.29.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3346,11 +3346,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.25.0': - resolution: {integrity: sha512-bRPQ6Zc3u5IqbDb0Vzj5+E5zFiq0tHRLICkZfwi3hyHvAiX9HxzNOboyR/HJhD+pr5xBizTDrJmccfA3RwrEBA==} + '@roo-code/cloud@0.29.0': + resolution: {integrity: sha512-fXN0mdkd5GezpVrCspe6atUkwvSk5D4wF80g+lc8E3aPVqEAozoI97kHNulRChGlBw7UIdd5xxbr1Z8Jtn+S/Q==} - '@roo-code/types@1.61.0': - resolution: {integrity: sha512-YJdFc6aYfaZ8EN08KbWaKLehRr1dcN3G3CzDjpppb08iehSEUZMycax/ryP5/G4vl34HTdtzyHNMboDen5ElUg==} + '@roo-code/types@1.63.0': + resolution: {integrity: sha512-pX8ftkDq1CySBbkUTIW9/QEG52ttFT/kl0ID286l0L3W22wpGRUct6PCedNI9kLDM4s5sxaUeZx7b3rUChikkw==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -12732,9 +12732,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.25.0': + '@roo-code/cloud@0.29.0': dependencies: - '@roo-code/types': 1.61.0 + '@roo-code/types': 1.63.0 ioredis: 5.6.1 jwt-decode: 4.0.0 p-wait-for: 5.0.2 @@ -12745,7 +12745,7 @@ snapshots: - supports-color - utf-8-validate - '@roo-code/types@1.61.0': + '@roo-code/types@1.63.0': dependencies: zod: 3.25.76 diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 6565daa238bf..349e32ced38e 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -26,6 +26,33 @@ import { DEFAULT_HEADERS } from "./constants" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler } from "../index" +// Image generation types +interface ImageGenerationResponse { + choices?: Array<{ + message?: { + content?: string + images?: Array<{ + type?: string + image_url?: { + url?: string + } + }> + } + }> + error?: { + message?: string + type?: string + code?: string + } +} + +export interface ImageGenerationResult { + success: boolean + imageData?: string + imageFormat?: string + error?: string +} + // Add custom interface for OpenRouter params. type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { transforms?: string[] @@ -242,4 +269,105 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH const completion = response as OpenAI.Chat.ChatCompletion return completion.choices[0]?.message?.content || "" } + + /** + * Generate an image using OpenRouter's image generation API + * @param prompt The text prompt for image generation + * @param model The model to use for generation + * @param apiKey The OpenRouter API key (must be explicitly provided) + * @returns The generated image data and format, or an error + */ + async generateImage(prompt: string, model: string, apiKey: string): Promise { + if (!apiKey) { + return { + success: false, + error: "OpenRouter API key is required for image generation", + } + } + + try { + const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + "HTTP-Referer": "https://github.com/RooVetGit/Roo-Code", + "X-Title": "Roo Code", + }, + body: JSON.stringify({ + model, + messages: [ + { + role: "user", + content: prompt, + }, + ], + modalities: ["image", "text"], + }), + }) + + if (!response.ok) { + const errorText = await response.text() + let errorMessage = `Failed to generate image: ${response.status} ${response.statusText}` + try { + const errorJson = JSON.parse(errorText) + if (errorJson.error?.message) { + errorMessage = `Failed to generate image: ${errorJson.error.message}` + } + } catch { + // Use default error message + } + return { + success: false, + error: errorMessage, + } + } + + const result: ImageGenerationResponse = await response.json() + + if (result.error) { + return { + success: false, + error: `Failed to generate image: ${result.error.message}`, + } + } + + // Extract the generated image from the response + const images = result.choices?.[0]?.message?.images + if (!images || images.length === 0) { + return { + success: false, + error: "No image was generated in the response", + } + } + + const imageData = images[0]?.image_url?.url + if (!imageData) { + return { + success: false, + error: "Invalid image data in response", + } + } + + // Extract base64 data from data URL + const base64Match = imageData.match(/^data:image\/(png|jpeg|jpg);base64,(.+)$/) + if (!base64Match) { + return { + success: false, + error: "Invalid image format received", + } + } + + return { + success: true, + imageData: imageData, + imageFormat: base64Match[1], + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : "Unknown error occurred", + } + } + } } diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index a8b90728b1ee..af1c57a5eef2 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -28,6 +28,7 @@ import { attemptCompletionTool } from "../tools/attemptCompletionTool" import { newTaskTool } from "../tools/newTaskTool" import { updateTodoListTool } from "../tools/updateTodoListTool" +import { generateImageTool } from "../tools/generateImageTool" import { formatResponse } from "../prompts/responses" import { validateToolUse } from "../tools/validateToolUse" @@ -221,6 +222,8 @@ export async function presentAssistantMessage(cline: Task) { const modeName = getModeBySlug(mode, customModes)?.name ?? mode return `[${block.name} in ${modeName} mode: '${message}']` } + case "generate_image": + return `[${block.name} for '${block.params.path}']` } } @@ -546,6 +549,9 @@ export async function presentAssistantMessage(cline: Task) { askFinishSubTaskApproval, ) break + case "generate_image": + await generateImageTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } break diff --git a/src/core/prompts/tools/generate-image.ts b/src/core/prompts/tools/generate-image.ts new file mode 100644 index 000000000000..786976522840 --- /dev/null +++ b/src/core/prompts/tools/generate-image.ts @@ -0,0 +1,20 @@ +import { ToolArgs } from "./types" + +export function getGenerateImageDescription(args: ToolArgs): string { + return `## generate_image +Description: Request to generate an image using AI models through OpenRouter API. This tool creates images from text prompts and saves them to the specified path. +Parameters: +- prompt: (required) The text prompt describing the image to generate +- path: (required) The file path where the generated image should be saved (relative to the current workspace directory ${args.cwd}). The tool will automatically add the appropriate image extension if not provided. +Usage: + +Your image description here +path/to/save/image.png + + +Example: Requesting to generate a sunset image + +A beautiful sunset over mountains with vibrant orange and purple colors +images/sunset.png +` +} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 3eb112d27098..8b4e90733ca0 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -25,6 +25,7 @@ import { getSwitchModeDescription } from "./switch-mode" import { getNewTaskDescription } from "./new-task" import { getCodebaseSearchDescription } from "./codebase-search" import { getUpdateTodoListDescription } from "./update-todo-list" +import { getGenerateImageDescription } from "./generate-image" import { CodeIndexManager } from "../../../services/code-index/manager" // Map of tool names to their description functions @@ -56,6 +57,7 @@ const toolDescriptionMap: Record string | undefined> apply_diff: (args) => args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "", update_todo_list: (args) => getUpdateTodoListDescription(args), + generate_image: (args) => getGenerateImageDescription(args), } export function getToolDescriptionsForMode( @@ -129,6 +131,11 @@ export function getToolDescriptionsForMode( tools.delete("update_todo_list") } + // Conditionally exclude generate_image if experiment is not enabled + if (!experiments?.imageGeneration) { + tools.delete("generate_image") + } + // Map tool descriptions for allowed tools const descriptions = Array.from(tools).map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] @@ -164,4 +171,5 @@ export { getInsertContentDescription, getSearchAndReplaceDescription, getCodebaseSearchDescription, + getGenerateImageDescription, } diff --git a/src/core/tools/generateImageTool.ts b/src/core/tools/generateImageTool.ts new file mode 100644 index 000000000000..97c29e0a6216 --- /dev/null +++ b/src/core/tools/generateImageTool.ts @@ -0,0 +1,193 @@ +import path from "path" +import fs from "fs/promises" +import * as vscode from "vscode" +import { Task } from "../task/Task" +import { formatResponse } from "../prompts/responses" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { fileExistsAtPath } from "../../utils/fs" +import { getReadablePath } from "../../utils/path" +import { isPathOutsideWorkspace } from "../../utils/pathUtils" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { safeWriteJson } from "../../utils/safeWriteJson" +import { OpenRouterHandler } from "../../api/providers/openrouter" + +// Hardcoded list of image generation models for now +const IMAGE_GENERATION_MODELS = [ + "google/gemini-2.5-flash-image-preview", + // Add more models as they become available +] + +export async function generateImageTool( + cline: Task, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const prompt: string | undefined = block.params.prompt + const relPath: string | undefined = block.params.path + + // Check if the experiment is enabled + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const isImageGenerationEnabled = experiments.isEnabled(state?.experiments ?? {}, EXPERIMENT_IDS.IMAGE_GENERATION) + + if (!isImageGenerationEnabled) { + pushToolResult( + formatResponse.toolError( + "Image generation is an experimental feature that must be enabled in settings. Please enable 'Image Generation' in the Experimental Settings section.", + ), + ) + return + } + + if (block.partial && (!prompt || !relPath)) { + // Wait for complete parameters + return + } + + if (!prompt) { + cline.consecutiveMistakeCount++ + cline.recordToolError("generate_image") + pushToolResult(await cline.sayAndCreateMissingParamError("generate_image", "prompt")) + return + } + + if (!relPath) { + cline.consecutiveMistakeCount++ + cline.recordToolError("generate_image") + pushToolResult(await cline.sayAndCreateMissingParamError("generate_image", "path")) + return + } + + // Validate access permissions + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + return + } + + // Check if file is write-protected + const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false + + // Get OpenRouter API key from experimental settings ONLY (no fallback to profile) + const apiConfiguration = state?.apiConfiguration + const imageGenerationSettings = apiConfiguration?.openRouterImageGenerationSettings + const openRouterApiKey = imageGenerationSettings?.openRouterApiKey + + if (!openRouterApiKey) { + await cline.say( + "error", + "OpenRouter API key is required for image generation. Please configure it in the Image Generation experimental settings.", + ) + pushToolResult( + formatResponse.toolError( + "OpenRouter API key is required for image generation. Please configure it in the Image Generation experimental settings.", + ), + ) + return + } + + // Get selected model from settings or use default + const selectedModel = imageGenerationSettings?.selectedModel || IMAGE_GENERATION_MODELS[0] + + // Determine if the path is outside the workspace + const fullPath = path.resolve(cline.cwd, removeClosingTag("path", relPath)) + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + + const sharedMessageProps = { + tool: "generateImage" as const, + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + content: prompt, + isOutsideWorkspace, + isProtected: isWriteProtected, + } + + try { + if (!block.partial) { + cline.consecutiveMistakeCount = 0 + + // Ask for approval before generating the image + const approvalMessage = JSON.stringify({ + ...sharedMessageProps, + content: prompt, + }) + + const didApprove = await askApproval("tool", approvalMessage, undefined, isWriteProtected) + + if (!didApprove) { + return + } + + // Create a temporary OpenRouter handler with minimal options + const openRouterHandler = new OpenRouterHandler({} as any) + + // Call the generateImage method with the explicit API key + const result = await openRouterHandler.generateImage(prompt, selectedModel, openRouterApiKey) + + if (!result.success) { + await cline.say("error", result.error || "Failed to generate image") + pushToolResult(formatResponse.toolError(result.error || "Failed to generate image")) + return + } + + if (!result.imageData) { + const errorMessage = "No image data received" + await cline.say("error", errorMessage) + pushToolResult(formatResponse.toolError(errorMessage)) + return + } + + // Extract base64 data from data URL + const base64Match = result.imageData.match(/^data:image\/(png|jpeg|jpg);base64,(.+)$/) + if (!base64Match) { + const errorMessage = "Invalid image format received" + await cline.say("error", errorMessage) + pushToolResult(formatResponse.toolError(errorMessage)) + return + } + + const imageFormat = base64Match[1] + const base64Data = base64Match[2] + + // Ensure the file has the correct extension + let finalPath = relPath + if (!finalPath.match(/\.(png|jpg|jpeg)$/i)) { + finalPath = `${finalPath}.${imageFormat === "jpeg" ? "jpg" : imageFormat}` + } + + // Convert base64 to buffer + const imageBuffer = Buffer.from(base64Data, "base64") + + // Create directory if it doesn't exist + const absolutePath = path.resolve(cline.cwd, finalPath) + const directory = path.dirname(absolutePath) + await fs.mkdir(directory, { recursive: true }) + + // Write the image file + await fs.writeFile(absolutePath, imageBuffer) + + // Track file creation + if (finalPath) { + await cline.fileContextTracker.trackFileContext(finalPath, "roo_edited") + } + + cline.didEditFile = true + + // Display the generated image in the chat using a text message with the image + await cline.say("text", getReadablePath(cline.cwd, finalPath), [result.imageData]) + + // Record successful tool usage + cline.recordToolUsage("generate_image") + + pushToolResult(formatResponse.toolResult(getReadablePath(cline.cwd, finalPath))) + + return + } + } catch (error) { + await handleError("generating image", error) + return + } +} diff --git a/src/package.json b/src/package.json index dc4524b8c193..8d806d2d3006 100644 --- a/src/package.json +++ b/src/package.json @@ -429,7 +429,7 @@ "@mistralai/mistralai": "^1.9.18", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.25.0", + "@roo-code/cloud": "^0.29.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 18b3cee5205d..88121876358b 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -343,6 +343,8 @@ export interface ClineSayTool { | "finishTask" | "searchAndReplace" | "insertContent" + | "generateImage" + | "imageGenerated" path?: string diff?: string content?: string @@ -379,6 +381,7 @@ export interface ClineSayTool { }> }> question?: string + imageData?: string // Base64 encoded image data for generated images } // Must keep in sync with system prompt. diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 607c1e0b04e8..d805a1954846 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -29,6 +29,7 @@ describe("experiments", () => { powerSteering: false, multiFileApplyDiff: false, preventFocusDisruption: false, + imageGeneration: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -38,6 +39,7 @@ describe("experiments", () => { powerSteering: true, multiFileApplyDiff: false, preventFocusDisruption: false, + imageGeneration: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -47,6 +49,7 @@ describe("experiments", () => { powerSteering: false, multiFileApplyDiff: false, preventFocusDisruption: false, + imageGeneration: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 548b55f68c7d..b84d87150393 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -4,6 +4,7 @@ export const EXPERIMENT_IDS = { MULTI_FILE_APPLY_DIFF: "multiFileApplyDiff", POWER_STEERING: "powerSteering", PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption", + IMAGE_GENERATION: "imageGeneration", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -18,6 +19,7 @@ export const experimentConfigsMap: Record = { MULTI_FILE_APPLY_DIFF: { enabled: false }, POWER_STEERING: { enabled: false }, PREVENT_FOCUS_DISRUPTION: { enabled: false }, + IMAGE_GENERATION: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 047c2fe35158..f15e8ef4c939 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -65,6 +65,7 @@ export const toolParamNames = [ "query", "args", "todos", + "prompt", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -164,6 +165,11 @@ export interface SearchAndReplaceToolUse extends ToolUse { Partial, "use_regex" | "ignore_case" | "start_line" | "end_line">> } +export interface GenerateImageToolUse extends ToolUse { + name: "generate_image" + params: Partial, "prompt" | "path">> +} + // Define tool group configuration export type ToolGroupConfig = { tools: readonly string[] @@ -190,6 +196,7 @@ export const TOOL_DISPLAY_NAMES: Record = { search_and_replace: "search and replace", codebase_search: "codebase search", update_todo_list: "update todo list", + generate_image: "generate images", } as const // Define available tool groups. @@ -205,7 +212,7 @@ export const TOOL_GROUPS: Record = { ], }, edit: { - tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace"], + tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace", "generate_image"], }, browser: { tools: ["browser_action"], diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 4413eab33c80..ad33ae9187f4 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -25,6 +25,7 @@ import CodeBlock from "../common/CodeBlock" import MarkdownBlock from "../common/MarkdownBlock" import { ReasoningBlock } from "./ReasoningBlock" import Thumbnails from "../common/Thumbnails" +import ImageBlock from "../common/ImageBlock" import McpResourceRow from "../mcp/McpResourceRow" @@ -790,6 +791,39 @@ export const ChatRowContent = ({ ) + case "generateImage": + return ( + <> +
+ {tool.isProtected ? ( + + ) : ( + toolIcon("file-media") + )} + + {message.type === "ask" + ? tool.isProtected + ? t("chat:fileOperations.wantsToGenerateImageProtected") + : tool.isOutsideWorkspace + ? t("chat:fileOperations.wantsToGenerateImageOutsideWorkspace") + : t("chat:fileOperations.wantsToGenerateImage") + : t("chat:fileOperations.didGenerateImage")} + +
+ {message.type === "ask" && ( + + )} + + ) default: return null } @@ -1002,6 +1036,13 @@ export const ChatRowContent = ({ return (
+ {message.images && message.images.length > 0 && ( +
+ {message.images.map((image, index) => ( + + ))} +
+ )}
) case "user_feedback": diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 7cca453580b0..85840267fe1b 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -325,6 +325,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction + + + ) +} diff --git a/webview-ui/src/components/common/ImageViewer.tsx b/webview-ui/src/components/common/ImageViewer.tsx new file mode 100644 index 000000000000..bb2f6791a4ad --- /dev/null +++ b/webview-ui/src/components/common/ImageViewer.tsx @@ -0,0 +1,240 @@ +import { useState, useCallback } from "react" +import { useCopyToClipboard } from "@src/utils/clipboard" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { vscode } from "@src/utils/vscode" +import { MermaidActionButtons } from "./MermaidActionButtons" +import { Modal } from "./Modal" +import { TabButton } from "./TabButton" +import { IconButton } from "./IconButton" +import { ZoomControls } from "./ZoomControls" +import { StandardTooltip } from "@/components/ui" + +const MIN_ZOOM = 0.5 +const MAX_ZOOM = 20 + +export interface ImageViewerProps { + imageData: string // base64 data URL or regular URL + alt?: string + path?: string + showControls?: boolean + className?: string +} + +export function ImageViewer({ + imageData, + alt = "Generated image", + path, + showControls = true, + className = "", +}: ImageViewerProps) { + const [showModal, setShowModal] = useState(false) + const [zoomLevel, setZoomLevel] = useState(1) + const [copyFeedback, setCopyFeedback] = useState(false) + const [isHovering, setIsHovering] = useState(false) + const [isDragging, setIsDragging] = useState(false) + const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }) + const { copyWithFeedback } = useCopyToClipboard() + const { t } = useAppTranslation() + + /** + * Opens a modal with the image for zooming + */ + const handleZoom = async (e: React.MouseEvent) => { + e.stopPropagation() + setShowModal(true) + setZoomLevel(1) + setDragPosition({ x: 0, y: 0 }) + } + + /** + * Copies the image path to clipboard + */ + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation() + + try { + const textToCopy = path || imageData + await copyWithFeedback(textToCopy, e) + + // Show feedback + setCopyFeedback(true) + setTimeout(() => setCopyFeedback(false), 2000) + } catch (err) { + console.error("Error copying:", err instanceof Error ? err.message : String(err)) + } + } + + /** + * Saves the image as a file + */ + const handleSave = async (e: React.MouseEvent) => { + e.stopPropagation() + + try { + // Send message to VSCode to save the image + vscode.postMessage({ + type: "saveImage", + dataUri: imageData, + }) + } catch (error) { + console.error("Error saving image:", error) + } + } + + /** + * Opens the image in VS Code's image viewer + */ + const handleOpenInEditor = (e: React.MouseEvent) => { + e.stopPropagation() + vscode.postMessage({ + type: "openImage", + text: imageData, + }) + } + + /** + * Adjust zoom level in the modal + */ + const adjustZoom = (amount: number) => { + setZoomLevel((prev) => { + const newZoom = prev + amount + return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom)) + }) + } + + /** + * Handle wheel event for zooming with scroll wheel + */ + const handleWheel = useCallback((e: React.WheelEvent) => { + e.preventDefault() + e.stopPropagation() + + // Determine zoom direction and amount + // Negative deltaY means scrolling up (zoom in), positive means scrolling down (zoom out) + const delta = e.deltaY > 0 ? -0.2 : 0.2 + adjustZoom(delta) + }, []) + + /** + * Handle mouse enter event for image container + */ + const handleMouseEnter = () => { + setIsHovering(true) + } + + /** + * Handle mouse leave event for image container + */ + const handleMouseLeave = () => { + setIsHovering(false) + } + + return ( + <> +
+ {alt} + {path &&
{path}
} + {showControls && isHovering && ( +
+ {}} // Not applicable for images + copyFeedback={copyFeedback} + /> +
+ )} +
+ + setShowModal(false)}> +
+
+ {}} + /> +
+ +
+ + setShowModal(false)} /> + +
+
+
+
{ + setIsDragging(true) + e.preventDefault() + }} + onMouseMove={(e) => { + if (isDragging) { + setDragPosition((prev) => ({ + x: prev.x + e.movementX / zoomLevel, + y: prev.y + e.movementY / zoomLevel, + })) + } + }} + onMouseUp={() => setIsDragging(false)} + onMouseLeave={() => setIsDragging(false)}> + {alt} +
+
+ {Math.round(zoomLevel * 100)}% +
+
+
+ + {path && ( + + + + )} + + + +
+
+ + ) +} diff --git a/webview-ui/src/components/settings/ExperimentalSettings.tsx b/webview-ui/src/components/settings/ExperimentalSettings.tsx index 53801232ecc8..4d0bb8aba6ab 100644 --- a/webview-ui/src/components/settings/ExperimentalSettings.tsx +++ b/webview-ui/src/components/settings/ExperimentalSettings.tsx @@ -12,15 +12,20 @@ import { SetExperimentEnabled } from "./types" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" import { ExperimentalFeature } from "./ExperimentalFeature" +import { ImageGenerationSettings } from "./ImageGenerationSettings" type ExperimentalSettingsProps = HTMLAttributes & { experiments: Experiments setExperimentEnabled: SetExperimentEnabled + apiConfiguration?: any + setApiConfigurationField?: any } export const ExperimentalSettings = ({ experiments, setExperimentEnabled, + apiConfiguration, + setApiConfigurationField, className, ...props }: ExperimentalSettingsProps) => { @@ -51,6 +56,19 @@ export const ExperimentalSettings = ({ /> ) } + if (config[0] === "IMAGE_GENERATION" && apiConfiguration && setApiConfigurationField) { + return ( + + setExperimentEnabled(EXPERIMENT_IDS.IMAGE_GENERATION, enabled) + } + apiConfiguration={apiConfiguration} + setApiConfigurationField={setApiConfigurationField} + /> + ) + } return ( void + apiConfiguration: ProviderSettings + setApiConfigurationField: ( + field: K, + value: ProviderSettings[K], + isUserAction?: boolean, + ) => void +} + +// Hardcoded list of image generation models +const IMAGE_GENERATION_MODELS = [ + { value: "google/gemini-2.5-flash-image-preview", label: "Gemini 2.5 Flash Image Preview" }, + // Add more models as they become available +] + +export const ImageGenerationSettings = ({ + enabled, + onChange, + apiConfiguration, + setApiConfigurationField, +}: ImageGenerationSettingsProps) => { + const { t } = useAppTranslation() + + // Get image generation settings from apiConfiguration + const imageGenerationSettings = apiConfiguration?.openRouterImageGenerationSettings || {} + const [openRouterApiKey, setOpenRouterApiKey] = useState(imageGenerationSettings.openRouterApiKey || "") + const [selectedModel, setSelectedModel] = useState( + imageGenerationSettings.selectedModel || IMAGE_GENERATION_MODELS[0].value, + ) + + // Update parent state when local state changes + useEffect(() => { + const newSettings = { + openRouterApiKey, + selectedModel, + } + setApiConfigurationField("openRouterImageGenerationSettings", newSettings) + }, [openRouterApiKey, selectedModel, setApiConfigurationField]) + + return ( +
+
+
+ onChange(e.target.checked)}> + {t("settings:experimental.IMAGE_GENERATION.name")} + +
+

+ {t("settings:experimental.IMAGE_GENERATION.description")} +

+
+ + {enabled && ( +
+ {/* API Key Configuration */} +
+ + setOpenRouterApiKey(e.target.value)} + placeholder={t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder")} + className="w-full" + type="password" + /> +

+ {t("settings:experimental.IMAGE_GENERATION.getApiKeyText")}{" "} + + openrouter.ai/keys + +

+
+ + {/* Model Selection */} +
+ + setSelectedModel(e.target.value)} + className="w-full"> + {IMAGE_GENERATION_MODELS.map((model) => ( + + {model.label} + + ))} + +

+ {t("settings:experimental.IMAGE_GENERATION.modelSelectionDescription")} +

+
+ + {/* Status Message */} + {enabled && !openRouterApiKey && ( +
+ {t("settings:experimental.IMAGE_GENERATION.warningMissingKey")} +
+ )} + + {enabled && openRouterApiKey && ( +
+ {t("settings:experimental.IMAGE_GENERATION.successConfigured")} +
+ )} +
+ )} +
+ ) +} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 4d55bc69c18c..682387ca2fa0 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -718,7 +718,12 @@ const SettingsView = forwardRef(({ onDone, t {/* Experimental Section */} {activeTab === "experimental" && ( - + )} {/* Language Section */} diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 36c197d3fc65..af3c726e2d6d 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -230,6 +230,7 @@ describe("mergeExtensionState", () => { multiFileApplyDiff: true, preventFocusDisruption: false, newTaskRequireTodos: false, + imageGeneration: false, } as Record, } @@ -248,6 +249,7 @@ describe("mergeExtensionState", () => { multiFileApplyDiff: true, preventFocusDisruption: false, newTaskRequireTodos: false, + imageGeneration: false, }) }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 9beddb9e276b..d56a34f7f2b3 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo vol afegir contingut al final d'aquest fitxer:", "wantsToReadAndXMore": "En Roo vol llegir aquest fitxer i {{count}} més:", "wantsToReadMultiple": "Roo vol llegir diversos fitxers:", - "wantsToApplyBatchChanges": "Roo vol aplicar canvis a múltiples fitxers:" + "wantsToApplyBatchChanges": "Roo vol aplicar canvis a múltiples fitxers:", + "wantsToGenerateImage": "Roo vol generar una imatge:", + "wantsToGenerateImageOutsideWorkspace": "Roo vol generar una imatge fora de l'espai de treball:", + "wantsToGenerateImageProtected": "Roo vol generar una imatge en una ubicació protegida:", + "didGenerateImage": "Roo ha generat una imatge:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo vol veure els fitxers de nivell superior en aquest directori:", diff --git a/webview-ui/src/i18n/locales/ca/common.json b/webview-ui/src/i18n/locales/ca/common.json index 2351c99d01bd..c056a4432876 100644 --- a/webview-ui/src/i18n/locales/ca/common.json +++ b/webview-ui/src/i18n/locales/ca/common.json @@ -48,6 +48,11 @@ "copyError": "Error copiant la imatge" } }, + "image": { + "tabs": { + "view": "Imatge" + } + }, "file": { "errors": { "invalidDataUri": "Format d'URI de dades no vàlid", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 96972b33171c..d6cef7045d36 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -729,6 +729,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Requerir la llista 'todos' per a noves tasques", "description": "Quan estigui activat, l'eina new_task requerirà que es proporcioni un paràmetre 'todos'. Això garanteix que totes les noves tasques comencin amb una llista clara d'objectius. Quan estigui desactivat (per defecte), el paràmetre 'todos' continua sent opcional per a la compatibilitat amb versions anteriors." + }, + "IMAGE_GENERATION": { + "name": "Habilitar generació d'imatges amb IA", + "description": "Quan estigui habilitat, Roo pot generar imatges a partir de prompts de text utilitzant els models de generació d'imatges d'OpenRouter. Requereix que es configuri una clau d'API d'OpenRouter.", + "openRouterApiKeyLabel": "Clau API d'OpenRouter", + "openRouterApiKeyPlaceholder": "Introdueix la teva clau API d'OpenRouter", + "getApiKeyText": "Obté la teva clau API de", + "modelSelectionLabel": "Model de generació d'imatges", + "modelSelectionDescription": "Selecciona el model per a la generació d'imatges", + "warningMissingKey": "⚠️ La clau API d'OpenRouter és necessària per a la generació d'imatges. Si us plau, configura-la a dalt.", + "successConfigured": "✓ La generació d'imatges està configurada i llesta per utilitzar" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index bc0893acd05d..9f6c98ef2f7f 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -175,7 +175,11 @@ "wantsToInsertWithLineNumber": "Roo möchte Inhalte in diese Datei in Zeile {{lineNumber}} einfügen:", "wantsToInsertAtEnd": "Roo möchte Inhalte am Ende dieser Datei anhängen:", "wantsToReadMultiple": "Roo möchte mehrere Dateien lesen:", - "wantsToApplyBatchChanges": "Roo möchte Änderungen an mehreren Dateien vornehmen:" + "wantsToApplyBatchChanges": "Roo möchte Änderungen an mehreren Dateien vornehmen:", + "wantsToGenerateImage": "Roo möchte ein Bild generieren:", + "wantsToGenerateImageOutsideWorkspace": "Roo möchte ein Bild außerhalb des Arbeitsbereichs generieren:", + "wantsToGenerateImageProtected": "Roo möchte ein Bild an einem geschützten Ort generieren:", + "didGenerateImage": "Roo hat ein Bild generiert:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo möchte die Dateien auf oberster Ebene in diesem Verzeichnis anzeigen:", diff --git a/webview-ui/src/i18n/locales/de/common.json b/webview-ui/src/i18n/locales/de/common.json index 0fec0ba96490..85137922ffd0 100644 --- a/webview-ui/src/i18n/locales/de/common.json +++ b/webview-ui/src/i18n/locales/de/common.json @@ -48,6 +48,11 @@ "copyError": "Fehler beim Kopieren des Bildes" } }, + "image": { + "tabs": { + "view": "Bild" + } + }, "file": { "errors": { "invalidDataUri": "Ungültiges Daten-URI-Format", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 58fd48d4dc11..e222810031d0 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -729,6 +729,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "'todos'-Liste für neue Aufgaben anfordern", "description": "Wenn aktiviert, erfordert das new_task-Tool die Angabe eines todos-Parameters. Dies stellt sicher, dass alle neuen Aufgaben mit einer klaren Zielliste beginnen. Wenn deaktiviert (Standard), bleibt der todos-Parameter aus Gründen der Abwärtskompatibilität optional." + }, + "IMAGE_GENERATION": { + "name": "KI-Bildgenerierung aktivieren", + "description": "Wenn aktiviert, kann Roo Bilder aus Textprompts mit OpenRouters Bildgenerierungsmodellen erstellen. Erfordert einen konfigurierten OpenRouter API-Schlüssel.", + "openRouterApiKeyLabel": "OpenRouter API-Schlüssel", + "openRouterApiKeyPlaceholder": "Gib deinen OpenRouter API-Schlüssel ein", + "getApiKeyText": "Hol dir deinen API-Schlüssel von", + "modelSelectionLabel": "Bildgenerierungsmodell", + "modelSelectionDescription": "Wähle das Modell für die Bildgenerierung aus", + "warningMissingKey": "⚠️ OpenRouter API-Schlüssel ist für Bildgenerierung erforderlich. Bitte konfiguriere ihn oben.", + "successConfigured": "✓ Bildgenerierung ist konfiguriert und einsatzbereit" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 74bb3b0c792d..a7e4b51d020a 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -182,6 +182,10 @@ "wantsToEditOutsideWorkspace": "Roo wants to edit this file outside of the workspace:", "wantsToEditProtected": "Roo wants to edit a protected configuration file:", "wantsToApplyBatchChanges": "Roo wants to apply changes to multiple files:", + "wantsToGenerateImage": "Roo wants to generate an image:", + "wantsToGenerateImageOutsideWorkspace": "Roo wants to generate an image outside of the workspace:", + "wantsToGenerateImageProtected": "Roo wants to generate an image in a protected location:", + "didGenerateImage": "Roo generated an image:", "wantsToCreate": "Roo wants to create a new file:", "wantsToSearchReplace": "Roo wants to search and replace in this file:", "didSearchReplace": "Roo performed search and replace on this file:", diff --git a/webview-ui/src/i18n/locales/en/common.json b/webview-ui/src/i18n/locales/en/common.json index b4bc816a2b40..973cb48297bf 100644 --- a/webview-ui/src/i18n/locales/en/common.json +++ b/webview-ui/src/i18n/locales/en/common.json @@ -48,6 +48,11 @@ "copyError": "Error copying image" } }, + "image": { + "tabs": { + "view": "Image" + } + }, "file": { "errors": { "invalidDataUri": "Invalid data URI format", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 139fa121504f..33fba24b8ee7 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -728,6 +728,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Require 'todos' list for new tasks", "description": "When enabled, the new_task tool will require a todos parameter to be provided. This ensures all new tasks start with a clear list of objectives. When disabled (default), the todos parameter remains optional for backward compatibility." + }, + "IMAGE_GENERATION": { + "name": "Enable AI image generation", + "description": "When enabled, Roo can generate images from text prompts using OpenRouter's image generation models. Requires an OpenRouter API key to be configured.", + "openRouterApiKeyLabel": "OpenRouter API Key", + "openRouterApiKeyPlaceholder": "Enter your OpenRouter API key", + "getApiKeyText": "Get your API key from", + "modelSelectionLabel": "Image Generation Model", + "modelSelectionDescription": "Select the model to use for image generation", + "warningMissingKey": "⚠️ OpenRouter API key is required for image generation. Please configure it above.", + "successConfigured": "✓ Image generation is configured and ready to use" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 85d36ccd777f..3cb9631a3a8b 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo quiere añadir contenido al final de este archivo:", "wantsToReadAndXMore": "Roo quiere leer este archivo y {{count}} más:", "wantsToReadMultiple": "Roo quiere leer varios archivos:", - "wantsToApplyBatchChanges": "Roo quiere aplicar cambios a múltiples archivos:" + "wantsToApplyBatchChanges": "Roo quiere aplicar cambios a múltiples archivos:", + "wantsToGenerateImage": "Roo quiere generar una imagen:", + "wantsToGenerateImageOutsideWorkspace": "Roo quiere generar una imagen fuera del espacio de trabajo:", + "wantsToGenerateImageProtected": "Roo quiere generar una imagen en una ubicación protegida:", + "didGenerateImage": "Roo generó una imagen:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo quiere ver los archivos de nivel superior en este directorio:", diff --git a/webview-ui/src/i18n/locales/es/common.json b/webview-ui/src/i18n/locales/es/common.json index a73367347068..a293008d8a88 100644 --- a/webview-ui/src/i18n/locales/es/common.json +++ b/webview-ui/src/i18n/locales/es/common.json @@ -48,6 +48,11 @@ "copyError": "Error copiando la imagen" } }, + "image": { + "tabs": { + "view": "Imagen" + } + }, "file": { "errors": { "invalidDataUri": "Formato de URI de datos inválido", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 7e81c1f0ffda..b2deeda932e5 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -729,6 +729,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Requerir lista de 'todos' para nuevas tareas", "description": "Cuando está habilitado, la herramienta new_task requerirá que se proporcione un parámetro todos. Esto asegura que todas las nuevas tareas comiencen con una lista clara de objetivos. Cuando está deshabilitado (predeterminado), el parámetro todos permanece opcional por compatibilidad con versiones anteriores." + }, + "IMAGE_GENERATION": { + "name": "Habilitar generación de imágenes con IA", + "description": "Cuando esté habilitado, Roo puede generar imágenes a partir de prompts de texto usando los modelos de generación de imágenes de OpenRouter. Requiere que se configure una clave de API de OpenRouter.", + "openRouterApiKeyLabel": "Clave API de OpenRouter", + "openRouterApiKeyPlaceholder": "Introduce tu clave API de OpenRouter", + "getApiKeyText": "Obtén tu clave API de", + "modelSelectionLabel": "Modelo de generación de imágenes", + "modelSelectionDescription": "Selecciona el modelo para la generación de imágenes", + "warningMissingKey": "⚠️ La clave API de OpenRouter es requerida para la generación de imágenes. Por favor, configúrala arriba.", + "successConfigured": "✓ La generación de imágenes está configurada y lista para usar" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index ed8e542753cd..190c1114ad7d 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -172,7 +172,11 @@ "wantsToInsertAtEnd": "Roo veut ajouter du contenu à la fin de ce fichier :", "wantsToReadAndXMore": "Roo veut lire ce fichier et {{count}} de plus :", "wantsToReadMultiple": "Roo souhaite lire plusieurs fichiers :", - "wantsToApplyBatchChanges": "Roo veut appliquer des modifications à plusieurs fichiers :" + "wantsToApplyBatchChanges": "Roo veut appliquer des modifications à plusieurs fichiers :", + "wantsToGenerateImage": "Roo veut générer une image :", + "wantsToGenerateImageOutsideWorkspace": "Roo veut générer une image en dehors de l'espace de travail :", + "wantsToGenerateImageProtected": "Roo veut générer une image dans un emplacement protégé :", + "didGenerateImage": "Roo a généré une image :" }, "instructions": { "wantsToFetch": "Roo veut récupérer des instructions détaillées pour aider à la tâche actuelle" diff --git a/webview-ui/src/i18n/locales/fr/common.json b/webview-ui/src/i18n/locales/fr/common.json index 4c4ad83bc40e..fd7f53dd976b 100644 --- a/webview-ui/src/i18n/locales/fr/common.json +++ b/webview-ui/src/i18n/locales/fr/common.json @@ -48,6 +48,11 @@ "copyError": "Erreur lors de la copie de l'image" } }, + "image": { + "tabs": { + "view": "Image" + } + }, "file": { "errors": { "invalidDataUri": "Format d'URI de données invalide", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 2b1a9b43067b..f36a1850dd9c 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -729,6 +729,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Exiger la liste 'todos' pour les nouvelles tâches", "description": "Lorsqu'il est activé, l'outil new_task exigera qu'un paramètre todos soit fourni. Cela garantit que toutes les nouvelles tâches commencent avec une liste claire d'objectifs. Lorsqu'il est désactivé (par défaut), le paramètre todos reste facultatif pour la compatibilité descendante." + }, + "IMAGE_GENERATION": { + "name": "Activer la génération d'images IA", + "description": "Lorsqu'activé, Roo peut générer des images à partir de prompts textuels en utilisant les modèles de génération d'images d'OpenRouter. Nécessite qu'une clé API OpenRouter soit configurée.", + "openRouterApiKeyLabel": "Clé API OpenRouter", + "openRouterApiKeyPlaceholder": "Entrez votre clé API OpenRouter", + "getApiKeyText": "Obtenez votre clé API depuis", + "modelSelectionLabel": "Modèle de génération d'images", + "modelSelectionDescription": "Sélectionnez le modèle pour la génération d'images", + "warningMissingKey": "⚠️ Une clé API OpenRouter est requise pour la génération d'images. Veuillez la configurer ci-dessus.", + "successConfigured": "✓ La génération d'images est configurée et prête à utiliser" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 93efe3acee7b..2de959c1ffb6 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo इस फ़ाइल के अंत में सामग्री जोड़ना चाहता है:", "wantsToReadAndXMore": "रू इस फ़ाइल को और {{count}} अन्य को पढ़ना चाहता है:", "wantsToReadMultiple": "Roo कई फ़ाइलें पढ़ना चाहता है:", - "wantsToApplyBatchChanges": "Roo कई फ़ाइलों में परिवर्तन लागू करना चाहता है:" + "wantsToApplyBatchChanges": "Roo कई फ़ाइलों में परिवर्तन लागू करना चाहता है:", + "wantsToGenerateImage": "Roo एक छवि बनाना चाहता है:", + "wantsToGenerateImageOutsideWorkspace": "Roo कार्यक्षेत्र के बाहर एक छवि बनाना चाहता है:", + "wantsToGenerateImageProtected": "Roo एक संरक्षित स्थान पर छवि बनाना चाहता है:", + "didGenerateImage": "Roo ने एक छवि बनाई:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo इस निर्देशिका में शीर्ष स्तर की फ़ाइलें देखना चाहता है:", diff --git a/webview-ui/src/i18n/locales/hi/common.json b/webview-ui/src/i18n/locales/hi/common.json index 7e809bd0a7eb..15039dc9001a 100644 --- a/webview-ui/src/i18n/locales/hi/common.json +++ b/webview-ui/src/i18n/locales/hi/common.json @@ -48,6 +48,11 @@ "copyError": "इमेज कॉपी करने में त्रुटि" } }, + "image": { + "tabs": { + "view": "चित्र" + } + }, "file": { "errors": { "invalidDataUri": "अमान्य डेटा URI फॉर्मेट", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 74d05f60d7f2..21b390aaddb9 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "नए कार्यों के लिए 'todos' सूची की आवश्यकता है", "description": "जब सक्षम किया जाता है, तो new_task टूल को todos पैरामीटर प्रदान करने की आवश्यकता होगी। यह सुनिश्चित करता है कि सभी नए कार्य स्पष्ट उद्देश्यों की सूची के साथ शुरू हों। जब अक्षम किया जाता है (डिफ़ॉल्ट), तो todos पैरामीटर पिछड़े संगतता के लिए वैकल्पिक रहता है।" + }, + "IMAGE_GENERATION": { + "name": "AI छवि निर्माण सक्षम करें", + "description": "जब सक्षम किया जाता है, तो Roo OpenRouter के छवि निर्माण मॉडल का उपयोग करके टेक्स्ट प्रॉम्प्ट से छवियां उत्पन्न कर सकता है। एक कॉन्फ़िगर किए गए OpenRouter API कुंजी की आवश्यकता होती है।", + "openRouterApiKeyLabel": "OpenRouter API कुंजी", + "openRouterApiKeyPlaceholder": "अपनी OpenRouter API कुंजी दर्ज करें", + "getApiKeyText": "अपनी API कुंजी प्राप्त करें", + "modelSelectionLabel": "छवि निर्माण मॉडल", + "modelSelectionDescription": "छवि निर्माण के लिए उपयोग करने वाला मॉडल चुनें", + "warningMissingKey": "⚠️ छवि निर्माण के लिए OpenRouter API कुंजी आवश्यक है। कृपया इसे ऊपर कॉन्फ़िगर करें।", + "successConfigured": "✓ छवि निर्माण कॉन्फ़िगर है और उपयोग के लिए तैयार है" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 63157aa9895b..1b2f1cf56f01 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -185,6 +185,10 @@ "wantsToEditOutsideWorkspace": "Roo ingin mengedit file ini di luar workspace:", "wantsToEditProtected": "Roo ingin mengedit file konfigurasi yang dilindungi:", "wantsToApplyBatchChanges": "Roo ingin menerapkan perubahan ke beberapa file:", + "wantsToGenerateImage": "Roo ingin menghasilkan gambar:", + "wantsToGenerateImageOutsideWorkspace": "Roo ingin menghasilkan gambar di luar workspace:", + "wantsToGenerateImageProtected": "Roo ingin menghasilkan gambar di lokasi yang dilindungi:", + "didGenerateImage": "Roo telah menghasilkan gambar:", "wantsToCreate": "Roo ingin membuat file baru:", "wantsToSearchReplace": "Roo ingin mencari dan mengganti di file ini:", "didSearchReplace": "Roo melakukan pencarian dan penggantian pada file ini:", diff --git a/webview-ui/src/i18n/locales/id/common.json b/webview-ui/src/i18n/locales/id/common.json index 86818bb0842c..0dac9b2987c9 100644 --- a/webview-ui/src/i18n/locales/id/common.json +++ b/webview-ui/src/i18n/locales/id/common.json @@ -48,6 +48,11 @@ "copyError": "Error menyalin gambar" } }, + "image": { + "tabs": { + "view": "Gambar" + } + }, "file": { "errors": { "invalidDataUri": "Format data URI tidak valid", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index d8fda37d002f..61c7670078a2 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -759,6 +759,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Membutuhkan daftar 'todos' untuk tugas baru", "description": "Ketika diaktifkan, alat new_task akan membutuhkan parameter todos untuk disediakan. Ini memastikan semua tugas baru dimulai dengan daftar tujuan yang jelas. Ketika dinonaktifkan (default), parameter todos tetap opsional untuk kompatibilitas mundur." + }, + "IMAGE_GENERATION": { + "name": "Aktifkan pembuatan gambar AI", + "description": "Ketika diaktifkan, Roo dapat menghasilkan gambar dari prompt teks menggunakan model pembuatan gambar OpenRouter. Memerlukan kunci API OpenRouter yang dikonfigurasi.", + "openRouterApiKeyLabel": "Kunci API OpenRouter", + "openRouterApiKeyPlaceholder": "Masukkan kunci API OpenRouter Anda", + "getApiKeyText": "Dapatkan kunci API Anda dari", + "modelSelectionLabel": "Model Pembuatan Gambar", + "modelSelectionDescription": "Pilih model untuk pembuatan gambar", + "warningMissingKey": "⚠️ Kunci API OpenRouter diperlukan untuk pembuatan gambar. Silakan konfigurasi di atas.", + "successConfigured": "✓ Pembuatan gambar dikonfigurasi dan siap digunakan" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 10c1c9826d95..e210124df0e9 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo vuole aggiungere contenuto alla fine di questo file:", "wantsToReadAndXMore": "Roo vuole leggere questo file e altri {{count}}:", "wantsToReadMultiple": "Roo vuole leggere più file:", - "wantsToApplyBatchChanges": "Roo vuole applicare modifiche a più file:" + "wantsToApplyBatchChanges": "Roo vuole applicare modifiche a più file:", + "wantsToGenerateImage": "Roo vuole generare un'immagine:", + "wantsToGenerateImageOutsideWorkspace": "Roo vuole generare un'immagine fuori dall'area di lavoro:", + "wantsToGenerateImageProtected": "Roo vuole generare un'immagine in una posizione protetta:", + "didGenerateImage": "Roo ha generato un'immagine:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo vuole visualizzare i file di primo livello in questa directory:", diff --git a/webview-ui/src/i18n/locales/it/common.json b/webview-ui/src/i18n/locales/it/common.json index 94f637ac3dd7..9ac9cbadad1e 100644 --- a/webview-ui/src/i18n/locales/it/common.json +++ b/webview-ui/src/i18n/locales/it/common.json @@ -48,6 +48,11 @@ "copyError": "Errore nella copia dell'immagine" } }, + "image": { + "tabs": { + "view": "Immagine" + } + }, "file": { "errors": { "invalidDataUri": "Formato URI dati non valido", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index eaad78925aae..3e383fc5640a 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Richiedi elenco 'todos' per nuove attività", "description": "Quando abilitato, lo strumento new_task richiederà la fornitura di un parametro todos. Ciò garantisce che tutte le nuove attività inizino con un elenco chiaro di obiettivi. Quando disabilitato (impostazione predefinita), il parametro todos rimane facoltativo per la compatibilità con le versioni precedenti." + }, + "IMAGE_GENERATION": { + "name": "Abilita generazione immagini AI", + "description": "Quando abilitato, Roo può generare immagini da prompt di testo utilizzando i modelli di generazione immagini di OpenRouter. Richiede una chiave API OpenRouter configurata.", + "openRouterApiKeyLabel": "Chiave API OpenRouter", + "openRouterApiKeyPlaceholder": "Inserisci la tua chiave API OpenRouter", + "getApiKeyText": "Ottieni la tua chiave API da", + "modelSelectionLabel": "Modello di generazione immagini", + "modelSelectionDescription": "Seleziona il modello per la generazione di immagini", + "warningMissingKey": "⚠️ La chiave API OpenRouter è richiesta per la generazione di immagini. Configurala sopra.", + "successConfigured": "✓ La generazione di immagini è configurata e pronta per l'uso" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 30fc6a2cf9d3..a5b393ebea6f 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Rooはこのファイルの末尾にコンテンツを追加したい:", "wantsToReadAndXMore": "Roo はこのファイルと他に {{count}} 個のファイルを読み込もうとしています:", "wantsToReadMultiple": "Rooは複数のファイルを読み取ろうとしています:", - "wantsToApplyBatchChanges": "Rooは複数のファイルに変更を適用したい:" + "wantsToApplyBatchChanges": "Rooは複数のファイルに変更を適用したい:", + "wantsToGenerateImage": "Rooは画像を生成したい:", + "wantsToGenerateImageOutsideWorkspace": "Rooはワークスペース外で画像を生成したい:", + "wantsToGenerateImageProtected": "Rooは保護された場所で画像を生成したい:", + "didGenerateImage": "Rooは画像を生成しました:" }, "directoryOperations": { "wantsToViewTopLevel": "Rooはこのディレクトリのトップレベルファイルを表示したい:", diff --git a/webview-ui/src/i18n/locales/ja/common.json b/webview-ui/src/i18n/locales/ja/common.json index a3f6a90a22bf..a92a3cd79a58 100644 --- a/webview-ui/src/i18n/locales/ja/common.json +++ b/webview-ui/src/i18n/locales/ja/common.json @@ -48,6 +48,11 @@ "copyError": "画像のコピーエラー" } }, + "image": { + "tabs": { + "view": "画像" + } + }, "file": { "errors": { "invalidDataUri": "無効なデータURI形式", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 07a575600060..33dfc8be35a9 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "新しいタスクには'todos'リストを必須にする", "description": "有効にすると、new_taskツールはtodosパラメータの提供が必須になります。これにより、すべての新しいタスクが明確な目的のリストで開始されることが保証されます。無効(デフォルト)の場合、下位互換性のためにtodosパラメータはオプションのままです。" + }, + "IMAGE_GENERATION": { + "name": "AI画像生成を有効にする", + "description": "有効にすると、RooはOpenRouterの画像生成モデルを使用してテキストプロンプトから画像を生成できます。OpenRouter APIキーの設定が必要です。", + "openRouterApiKeyLabel": "OpenRouter APIキー", + "openRouterApiKeyPlaceholder": "OpenRouter APIキーを入力してください", + "getApiKeyText": "APIキーを取得する場所", + "modelSelectionLabel": "画像生成モデル", + "modelSelectionDescription": "画像生成に使用するモデルを選択", + "warningMissingKey": "⚠️ 画像生成にはOpenRouter APIキーが必要です。上記で設定してください。", + "successConfigured": "✓ 画像生成が設定され、使用準備完了です" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 3fbde1f3533c..927cdfe5cf7a 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo가 이 파일의 끝에 내용을 추가하고 싶어합니다:", "wantsToReadAndXMore": "Roo가 이 파일과 {{count}}개의 파일을 더 읽으려고 합니다:", "wantsToReadMultiple": "Roo가 여러 파일을 읽으려고 합니다:", - "wantsToApplyBatchChanges": "Roo가 여러 파일에 변경 사항을 적용하고 싶어합니다:" + "wantsToApplyBatchChanges": "Roo가 여러 파일에 변경 사항을 적용하고 싶어합니다:", + "wantsToGenerateImage": "Roo가 이미지를 생성하고 싶어합니다:", + "wantsToGenerateImageOutsideWorkspace": "Roo가 워크스페이스 외부에서 이미지를 생성하고 싶어합니다:", + "wantsToGenerateImageProtected": "Roo가 보호된 위치에서 이미지를 생성하고 싶어합니다:", + "didGenerateImage": "Roo가 이미지를 생성했습니다:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo가 이 디렉토리의 최상위 파일을 보고 싶어합니다:", diff --git a/webview-ui/src/i18n/locales/ko/common.json b/webview-ui/src/i18n/locales/ko/common.json index 83d56930df4a..e8a9b7c64b97 100644 --- a/webview-ui/src/i18n/locales/ko/common.json +++ b/webview-ui/src/i18n/locales/ko/common.json @@ -48,6 +48,11 @@ "copyError": "이미지 복사 오류" } }, + "image": { + "tabs": { + "view": "이미지" + } + }, "file": { "errors": { "invalidDataUri": "잘못된 데이터 URI 형식", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 9dcfea8428e3..969da2c9ae02 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "새 작업에 'todos' 목록 필요", "description": "활성화하면 new_task 도구에 todos 매개변수를 제공해야 합니다. 이렇게 하면 모든 새 작업이 명확한 목표 목록으로 시작됩니다. 비활성화하면(기본값) 이전 버전과의 호환성을 위해 todos 매개변수는 선택 사항으로 유지됩니다." + }, + "IMAGE_GENERATION": { + "name": "AI 이미지 생성 활성화", + "description": "활성화하면 Roo는 OpenRouter의 이미지 생성 모델을 사용하여 텍스트 프롬프트에서 이미지를 생성할 수 있습니다. OpenRouter API 키 구성이 필요합니다.", + "openRouterApiKeyLabel": "OpenRouter API 키", + "openRouterApiKeyPlaceholder": "OpenRouter API 키를 입력하세요", + "getApiKeyText": "API 키를 받을 곳", + "modelSelectionLabel": "이미지 생성 모델", + "modelSelectionDescription": "이미지 생성에 사용할 모델을 선택하세요", + "warningMissingKey": "⚠️ 이미지 생성에는 OpenRouter API 키가 필요합니다. 위에서 설정해주세요.", + "successConfigured": "✓ 이미지 생성이 구성되었으며 사용할 준비가 되었습니다" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index a53f148bf268..48f9f345f9e5 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -170,7 +170,11 @@ "wantsToInsertAtEnd": "Roo wil inhoud toevoegen aan het einde van dit bestand:", "wantsToReadAndXMore": "Roo wil dit bestand en nog {{count}} andere lezen:", "wantsToReadMultiple": "Roo wil meerdere bestanden lezen:", - "wantsToApplyBatchChanges": "Roo wil wijzigingen toepassen op meerdere bestanden:" + "wantsToApplyBatchChanges": "Roo wil wijzigingen toepassen op meerdere bestanden:", + "wantsToGenerateImage": "Roo wil een afbeelding genereren:", + "wantsToGenerateImageOutsideWorkspace": "Roo wil een afbeelding genereren buiten de werkruimte:", + "wantsToGenerateImageProtected": "Roo wil een afbeelding genereren op een beschermde locatie:", + "didGenerateImage": "Roo heeft een afbeelding gegenereerd:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo wil de bovenliggende bestanden in deze map bekijken:", diff --git a/webview-ui/src/i18n/locales/nl/common.json b/webview-ui/src/i18n/locales/nl/common.json index d81570c70570..12a6c74365f1 100644 --- a/webview-ui/src/i18n/locales/nl/common.json +++ b/webview-ui/src/i18n/locales/nl/common.json @@ -48,6 +48,11 @@ "copyError": "Fout bij kopiëren van afbeelding" } }, + "image": { + "tabs": { + "view": "Afbeelding" + } + }, "file": { "errors": { "invalidDataUri": "Ongeldig data-URI-formaat", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 12a55c97198b..aa962a56ab98 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "'todos'-lijst vereisen voor nieuwe taken", "description": "Wanneer ingeschakeld, vereist de new_task-tool dat een todos-parameter wordt opgegeven. Dit zorgt ervoor dat alle nieuwe taken beginnen met een duidelijke lijst met doelstellingen. Wanneer uitgeschakeld (standaard), blijft de todos-parameter optioneel voor achterwaartse compatibiliteit." + }, + "IMAGE_GENERATION": { + "name": "AI-afbeeldingsgeneratie inschakelen", + "description": "Wanneer ingeschakeld, kan Roo afbeeldingen genereren van tekstprompts met behulp van OpenRouter's afbeeldingsgeneratiemodellen. Vereist een geconfigureerde OpenRouter API-sleutel.", + "openRouterApiKeyLabel": "OpenRouter API-sleutel", + "openRouterApiKeyPlaceholder": "Voer je OpenRouter API-sleutel in", + "getApiKeyText": "Haal je API-sleutel op van", + "modelSelectionLabel": "Afbeeldingsgeneratiemodel", + "modelSelectionDescription": "Selecteer het model voor afbeeldingsgeneratie", + "warningMissingKey": "⚠️ OpenRouter API-sleutel is vereist voor afbeeldingsgeneratie. Configureer deze hierboven.", + "successConfigured": "✓ Afbeeldingsgeneratie is geconfigureerd en klaar voor gebruik" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 391aa7b3194a..f587406fa5ce 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo chce dodać zawartość na końcu tego pliku:", "wantsToReadAndXMore": "Roo chce przeczytać ten plik i {{count}} więcej:", "wantsToReadMultiple": "Roo chce odczytać wiele plików:", - "wantsToApplyBatchChanges": "Roo chce zastosować zmiany do wielu plików:" + "wantsToApplyBatchChanges": "Roo chce zastosować zmiany do wielu plików:", + "wantsToGenerateImage": "Roo chce wygenerować obraz:", + "wantsToGenerateImageOutsideWorkspace": "Roo chce wygenerować obraz poza obszarem roboczym:", + "wantsToGenerateImageProtected": "Roo chce wygenerować obraz w chronionym miejscu:", + "didGenerateImage": "Roo wygenerował obraz:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo chce zobaczyć pliki najwyższego poziomu w tym katalogu:", diff --git a/webview-ui/src/i18n/locales/pl/common.json b/webview-ui/src/i18n/locales/pl/common.json index 77679ef7c5e7..410c8dbb9c05 100644 --- a/webview-ui/src/i18n/locales/pl/common.json +++ b/webview-ui/src/i18n/locales/pl/common.json @@ -48,6 +48,11 @@ "copyError": "Błąd kopiowania obrazu" } }, + "image": { + "tabs": { + "view": "Obraz" + } + }, "file": { "errors": { "invalidDataUri": "Nieprawidłowy format URI danych", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 92b89ffeab92..ccc3868fb8ea 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Wymagaj listy 'todos' dla nowych zadań", "description": "Gdy włączone, narzędzie new_task będzie wymagało podania parametru todos. Zapewnia to, że wszystkie nowe zadania rozpoczynają się od jasnej listy celów. Gdy wyłączone (domyślnie), parametr todos pozostaje opcjonalny dla zachowania kompatybilności wstecznej." + }, + "IMAGE_GENERATION": { + "name": "Włącz generowanie obrazów AI", + "description": "Gdy włączone, Roo może generować obrazy z promptów tekstowych używając modeli generowania obrazów OpenRouter. Wymaga skonfigurowanego klucza API OpenRouter.", + "openRouterApiKeyLabel": "Klucz API OpenRouter", + "openRouterApiKeyPlaceholder": "Wprowadź swój klucz API OpenRouter", + "getApiKeyText": "Uzyskaj swój klucz API od", + "modelSelectionLabel": "Model generowania obrazów", + "modelSelectionDescription": "Wybierz model do generowania obrazów", + "warningMissingKey": "⚠️ Klucz API OpenRouter jest wymagany do generowania obrazów. Skonfiguruj go powyżej.", + "successConfigured": "✓ Generowanie obrazów jest skonfigurowane i gotowe do użycia" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 12fa6932ae2b..c4ccac74584e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo quer adicionar conteúdo ao final deste arquivo:", "wantsToReadAndXMore": "Roo quer ler este arquivo e mais {{count}}:", "wantsToReadMultiple": "Roo deseja ler múltiplos arquivos:", - "wantsToApplyBatchChanges": "Roo quer aplicar alterações a múltiplos arquivos:" + "wantsToApplyBatchChanges": "Roo quer aplicar alterações a múltiplos arquivos:", + "wantsToGenerateImage": "Roo quer gerar uma imagem:", + "wantsToGenerateImageOutsideWorkspace": "Roo quer gerar uma imagem fora do espaço de trabalho:", + "wantsToGenerateImageProtected": "Roo quer gerar uma imagem em um local protegido:", + "didGenerateImage": "Roo gerou uma imagem:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo quer visualizar os arquivos de nível superior neste diretório:", diff --git a/webview-ui/src/i18n/locales/pt-BR/common.json b/webview-ui/src/i18n/locales/pt-BR/common.json index 3fb4273d89cc..30d9b6dc6c1c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/common.json +++ b/webview-ui/src/i18n/locales/pt-BR/common.json @@ -48,6 +48,11 @@ "copyError": "Erro ao copiar imagem" } }, + "image": { + "tabs": { + "view": "Imagem" + } + }, "file": { "errors": { "invalidDataUri": "Formato de URI de dados inválido", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 0721ba5d1881..b6674e682e77 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Exigir lista de 'todos' para novas tarefas", "description": "Quando ativado, a ferramenta new_task exigirá que um parâmetro todos seja fornecido. Isso garante que todas as novas tarefas comecem com uma lista clara de objetivos. Quando desativado (padrão), o parâmetro todos permanece opcional para compatibilidade com versões anteriores." + }, + "IMAGE_GENERATION": { + "name": "Habilitar geração de imagens com IA", + "description": "Quando habilitado, Roo pode gerar imagens a partir de prompts de texto usando os modelos de geração de imagens do OpenRouter. Requer uma chave de API do OpenRouter configurada.", + "openRouterApiKeyLabel": "Chave de API do OpenRouter", + "openRouterApiKeyPlaceholder": "Digite sua chave de API do OpenRouter", + "getApiKeyText": "Obtenha sua chave de API de", + "modelSelectionLabel": "Modelo de Geração de Imagens", + "modelSelectionDescription": "Selecione o modelo para geração de imagens", + "warningMissingKey": "⚠️ A chave de API do OpenRouter é necessária para geração de imagens. Configure-a acima.", + "successConfigured": "✓ A geração de imagens está configurada e pronta para uso" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 0288c2bc9475..9fefd4f380d0 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -170,7 +170,11 @@ "wantsToInsertAtEnd": "Roo хочет добавить содержимое в конец этого файла:", "wantsToReadAndXMore": "Roo хочет прочитать этот файл и еще {{count}}:", "wantsToReadMultiple": "Roo хочет прочитать несколько файлов:", - "wantsToApplyBatchChanges": "Roo хочет применить изменения к нескольким файлам:" + "wantsToApplyBatchChanges": "Roo хочет применить изменения к нескольким файлам:", + "wantsToGenerateImage": "Roo хочет сгенерировать изображение:", + "wantsToGenerateImageOutsideWorkspace": "Roo хочет сгенерировать изображение вне рабочего пространства:", + "wantsToGenerateImageProtected": "Roo хочет сгенерировать изображение в защищённом месте:", + "didGenerateImage": "Roo сгенерировал изображение:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo хочет просмотреть файлы верхнего уровня в этой директории:", diff --git a/webview-ui/src/i18n/locales/ru/common.json b/webview-ui/src/i18n/locales/ru/common.json index f43c2e9e8bb9..8cdb1431eb19 100644 --- a/webview-ui/src/i18n/locales/ru/common.json +++ b/webview-ui/src/i18n/locales/ru/common.json @@ -48,6 +48,11 @@ "copyError": "Ошибка копирования изображения" } }, + "image": { + "tabs": { + "view": "Изображение" + } + }, "file": { "errors": { "invalidDataUri": "Неверный формат URI данных", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 0b23982c51d9..e416777719c4 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Требовать список 'todos' для новых задач", "description": "Если включено, инструмент new_task будет требовать предоставления параметра todos. Это гарантирует, что все новые задачи начинаются с четкого списка целей. Когда отключено (по умолчанию), параметр todos остается необязательным для обратной совместимости." + }, + "IMAGE_GENERATION": { + "name": "Включить генерацию изображений ИИ", + "description": "Когда включено, Roo может генерировать изображения из текстовых запросов, используя модели генерации изображений OpenRouter. Требует настроенный API-ключ OpenRouter.", + "openRouterApiKeyLabel": "API-ключ OpenRouter", + "openRouterApiKeyPlaceholder": "Введите ваш API-ключ OpenRouter", + "getApiKeyText": "Получите ваш API-ключ от", + "modelSelectionLabel": "Модель генерации изображений", + "modelSelectionDescription": "Выберите модель для генерации изображений", + "warningMissingKey": "⚠️ API-ключ OpenRouter необходим для генерации изображений. Настройте его выше.", + "successConfigured": "✓ Генерация изображений настроена и готова к использованию" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index c8dea0d545a7..12757edab63f 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo bu dosyanın sonuna içerik eklemek istiyor:", "wantsToReadAndXMore": "Roo bu dosyayı ve {{count}} tane daha okumak istiyor:", "wantsToReadMultiple": "Roo birden fazla dosya okumak istiyor:", - "wantsToApplyBatchChanges": "Roo birden fazla dosyaya değişiklik uygulamak istiyor:" + "wantsToApplyBatchChanges": "Roo birden fazla dosyaya değişiklik uygulamak istiyor:", + "wantsToGenerateImage": "Roo bir görsel oluşturmak istiyor:", + "wantsToGenerateImageOutsideWorkspace": "Roo çalışma alanının dışında bir görsel oluşturmak istiyor:", + "wantsToGenerateImageProtected": "Roo korumalı bir konumda görsel oluşturmak istiyor:", + "didGenerateImage": "Roo bir görsel oluşturdu:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo bu dizindeki üst düzey dosyaları görüntülemek istiyor:", diff --git a/webview-ui/src/i18n/locales/tr/common.json b/webview-ui/src/i18n/locales/tr/common.json index 2f3a7c957cd1..15f13fcdd36c 100644 --- a/webview-ui/src/i18n/locales/tr/common.json +++ b/webview-ui/src/i18n/locales/tr/common.json @@ -48,6 +48,11 @@ "copyError": "Görsel kopyalama hatası" } }, + "image": { + "tabs": { + "view": "Resim" + } + }, "file": { "errors": { "invalidDataUri": "Geçersiz veri URI formatı", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 1c218bc60710..0335802ba095 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Yeni görevler için 'todos' listesi gerektir", "description": "Etkinleştirildiğinde, new_task aracı bir todos parametresi sağlanmasını gerektirir. Bu, tüm yeni görevlerin net bir hedef listesiyle başlamasını sağlar. Devre dışı bırakıldığında (varsayılan), todos parametresi geriye dönük uyumluluk için isteğe bağlı kalır." + }, + "IMAGE_GENERATION": { + "name": "AI görüntü üretimini etkinleştir", + "description": "Etkinleştirildiğinde, Roo OpenRouter'ın görüntü üretim modellerini kullanarak metin istemlerinden görüntüler üretebilir. Yapılandırılmış bir OpenRouter API anahtarı gerektirir.", + "openRouterApiKeyLabel": "OpenRouter API Anahtarı", + "openRouterApiKeyPlaceholder": "OpenRouter API anahtarınızı girin", + "getApiKeyText": "API anahtarınızı alın", + "modelSelectionLabel": "Görüntü Üretim Modeli", + "modelSelectionDescription": "Görüntü üretimi için kullanılacak modeli seçin", + "warningMissingKey": "⚠️ Görüntü üretimi için OpenRouter API anahtarı gereklidir. Lütfen yukarıda yapılandırın.", + "successConfigured": "✓ Görüntü üretimi yapılandırılmış ve kullanıma hazır" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index c4d56a71af80..2d0135d9ac5f 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "Roo muốn thêm nội dung vào cuối tệp này:", "wantsToReadAndXMore": "Roo muốn đọc tệp này và {{count}} tệp khác:", "wantsToReadMultiple": "Roo muốn đọc nhiều tệp:", - "wantsToApplyBatchChanges": "Roo muốn áp dụng thay đổi cho nhiều tệp:" + "wantsToApplyBatchChanges": "Roo muốn áp dụng thay đổi cho nhiều tệp:", + "wantsToGenerateImage": "Roo muốn tạo một hình ảnh:", + "wantsToGenerateImageOutsideWorkspace": "Roo muốn tạo hình ảnh bên ngoài không gian làm việc:", + "wantsToGenerateImageProtected": "Roo muốn tạo hình ảnh ở vị trí được bảo vệ:", + "didGenerateImage": "Roo đã tạo một hình ảnh:" }, "directoryOperations": { "wantsToViewTopLevel": "Roo muốn xem các tệp cấp cao nhất trong thư mục này:", diff --git a/webview-ui/src/i18n/locales/vi/common.json b/webview-ui/src/i18n/locales/vi/common.json index 92aa029b018c..a75e1e1f4acd 100644 --- a/webview-ui/src/i18n/locales/vi/common.json +++ b/webview-ui/src/i18n/locales/vi/common.json @@ -48,6 +48,11 @@ "copyError": "Lỗi sao chép hình ảnh" } }, + "image": { + "tabs": { + "view": "Hình ảnh" + } + }, "file": { "errors": { "invalidDataUri": "Định dạng URI dữ liệu không hợp lệ", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 46ff47483adc..92565cb85ef8 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "Yêu cầu danh sách 'todos' cho các nhiệm vụ mới", "description": "Khi được bật, công cụ new_task sẽ yêu cầu cung cấp tham số todos. Điều này đảm bảo tất cả các nhiệm vụ mới bắt đầu với một danh sách mục tiêu rõ ràng. Khi bị tắt (mặc định), tham số todos vẫn là tùy chọn để tương thích ngược." + }, + "IMAGE_GENERATION": { + "name": "Bật tạo hình ảnh AI", + "description": "Khi được bật, Roo có thể tạo hình ảnh từ lời nhắc văn bản bằng các mô hình tạo hình ảnh của OpenRouter. Yêu cầu khóa API OpenRouter được cấu hình.", + "openRouterApiKeyLabel": "Khóa API OpenRouter", + "openRouterApiKeyPlaceholder": "Nhập khóa API OpenRouter của bạn", + "getApiKeyText": "Lấy khóa API của bạn từ", + "modelSelectionLabel": "Mô hình tạo hình ảnh", + "modelSelectionDescription": "Chọn mô hình để sử dụng cho việc tạo hình ảnh", + "warningMissingKey": "⚠️ Khóa API OpenRouter là bắt buộc để tạo hình ảnh. Vui lòng cấu hình ở trên.", + "successConfigured": "✓ Tạo hình ảnh đã được cấu hình và sẵn sàng sử dụng" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index ac95f8920904..a8491a63f662 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -175,7 +175,11 @@ "wantsToInsertAtEnd": "需要在文件末尾添加内容:", "wantsToReadAndXMore": "Roo 想读取此文件以及另外 {{count}} 个文件:", "wantsToReadMultiple": "Roo 想要读取多个文件:", - "wantsToApplyBatchChanges": "Roo 想要对多个文件应用更改:" + "wantsToApplyBatchChanges": "Roo 想要对多个文件应用更改:", + "wantsToGenerateImage": "需要生成图片:", + "wantsToGenerateImageOutsideWorkspace": "需要在工作区外生成图片:", + "wantsToGenerateImageProtected": "需要在受保护位置生成图片:", + "didGenerateImage": "已生成图片:" }, "directoryOperations": { "wantsToViewTopLevel": "需要查看目录文件列表:", diff --git a/webview-ui/src/i18n/locales/zh-CN/common.json b/webview-ui/src/i18n/locales/zh-CN/common.json index 6ff0132370a8..902bd7f7e068 100644 --- a/webview-ui/src/i18n/locales/zh-CN/common.json +++ b/webview-ui/src/i18n/locales/zh-CN/common.json @@ -48,6 +48,11 @@ "copyError": "复制图片时出错" } }, + "image": { + "tabs": { + "view": "图像" + } + }, "file": { "errors": { "invalidDataUri": "无效的数据 URI 格式", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index a6aea2cd8d71..9a3879523db1 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "要求新任务提供 'todos' 列表", "description": "启用后,new_task 工具将需要提供 todos 参数。这可以确保所有新任务都以明确的目标列表开始。禁用时(默认),todos 参数保持可选,以实现向后兼容。" + }, + "IMAGE_GENERATION": { + "name": "启用 AI 图像生成", + "description": "启用后,Roo 可以使用 OpenRouter 的图像生成模型从文本提示生成图像。需要配置 OpenRouter API 密钥。", + "openRouterApiKeyLabel": "OpenRouter API 密钥", + "openRouterApiKeyPlaceholder": "输入您的 OpenRouter API 密钥", + "getApiKeyText": "获取您的 API 密钥", + "modelSelectionLabel": "图像生成模型", + "modelSelectionDescription": "选择用于图像生成的模型", + "warningMissingKey": "⚠️ 图像生成需要 OpenRouter API 密钥。请在上方配置。", + "successConfigured": "✓ 图像生成已配置完成,可以使用" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 8df11705d4cb..c01142cdfa16 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -182,6 +182,10 @@ "wantsToEditOutsideWorkspace": "Roo 想要編輯此工作區外的檔案:", "wantsToEditProtected": "Roo 想要編輯受保護的設定檔案:", "wantsToApplyBatchChanges": "Roo 想要對多個檔案套用變更:", + "wantsToGenerateImage": "Roo 想要產生圖片:", + "wantsToGenerateImageOutsideWorkspace": "Roo 想要在工作區外產生圖片:", + "wantsToGenerateImageProtected": "Roo 想要在受保護位置產生圖片:", + "didGenerateImage": "Roo 已產生圖片:", "wantsToCreate": "Roo 想要建立新檔案:", "wantsToSearchReplace": "Roo 想要在此檔案中搜尋和取代:", "didSearchReplace": "Roo 已在此檔案執行搜尋和取代:", diff --git a/webview-ui/src/i18n/locales/zh-TW/common.json b/webview-ui/src/i18n/locales/zh-TW/common.json index 9750590462b8..9497d369a5bf 100644 --- a/webview-ui/src/i18n/locales/zh-TW/common.json +++ b/webview-ui/src/i18n/locales/zh-TW/common.json @@ -48,6 +48,11 @@ "copyError": "複製圖片時發生錯誤" } }, + "image": { + "tabs": { + "view": "圖像" + } + }, "file": { "errors": { "invalidDataUri": "無效的資料 URI 格式", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 3b9680338029..b8ade3ca8bf7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -730,6 +730,17 @@ "NEW_TASK_REQUIRE_TODOS": { "name": "要求新工作提供 'todos' 列表", "description": "啟用後,new_task 工具將需要提供 todos 參數。這可以確保所有新工作都以明確的目標列表開始。停用時(預設),todos 參數保持可選,以實現向後相容。" + }, + "IMAGE_GENERATION": { + "name": "啟用 AI 圖像生成", + "description": "啟用後,Roo 可以使用 OpenRouter 的圖像生成模型從文字提示生成圖像。需要設定 OpenRouter API 金鑰。", + "openRouterApiKeyLabel": "OpenRouter API 金鑰", + "openRouterApiKeyPlaceholder": "輸入您的 OpenRouter API 金鑰", + "getApiKeyText": "取得您的 API 金鑰從", + "modelSelectionLabel": "圖像生成模型", + "modelSelectionDescription": "選擇用於圖像生成的模型", + "warningMissingKey": "⚠️ 圖像生成需要 OpenRouter API 金鑰。請在上方設定。", + "successConfigured": "✓ 圖像生成已設定完成並準備使用" } }, "promptCaching": {