Kaynağa Gözat

feat: add image generation tool with OpenRouter integration (#7474)

Co-authored-by: Matt Rubens <[email protected]>
Co-authored-by: cte <[email protected]>
Daniel 4 ay önce
ebeveyn
işleme
2092fb1a50
77 değiştirilmiş dosya ile 1218 ekleme ve 29 silme
  1. 1 1
      packages/types/npm/package.metadata.json
  2. 7 1
      packages/types/src/experiment.ts
  3. 7 0
      packages/types/src/provider-settings.ts
  4. 1 0
      packages/types/src/tool.ts
  5. 9 9
      pnpm-lock.yaml
  6. 128 0
      src/api/providers/openrouter.ts
  7. 6 0
      src/core/assistant-message/presentAssistantMessage.ts
  8. 20 0
      src/core/prompts/tools/generate-image.ts
  9. 8 0
      src/core/prompts/tools/index.ts
  10. 193 0
      src/core/tools/generateImageTool.ts
  11. 1 1
      src/package.json
  12. 3 0
      src/shared/ExtensionMessage.ts
  13. 3 0
      src/shared/__tests__/experiments.spec.ts
  14. 2 0
      src/shared/experiments.ts
  15. 8 1
      src/shared/tools.ts
  16. 41 0
      webview-ui/src/components/chat/ChatRow.tsx
  17. 2 0
      webview-ui/src/components/chat/ChatView.tsx
  18. 15 0
      webview-ui/src/components/common/ImageBlock.tsx
  19. 240 0
      webview-ui/src/components/common/ImageViewer.tsx
  20. 18 0
      webview-ui/src/components/settings/ExperimentalSettings.tsx
  21. 122 0
      webview-ui/src/components/settings/ImageGenerationSettings.tsx
  22. 6 1
      webview-ui/src/components/settings/SettingsView.tsx
  23. 2 0
      webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  24. 5 1
      webview-ui/src/i18n/locales/ca/chat.json
  25. 5 0
      webview-ui/src/i18n/locales/ca/common.json
  26. 11 0
      webview-ui/src/i18n/locales/ca/settings.json
  27. 5 1
      webview-ui/src/i18n/locales/de/chat.json
  28. 5 0
      webview-ui/src/i18n/locales/de/common.json
  29. 11 0
      webview-ui/src/i18n/locales/de/settings.json
  30. 4 0
      webview-ui/src/i18n/locales/en/chat.json
  31. 5 0
      webview-ui/src/i18n/locales/en/common.json
  32. 11 0
      webview-ui/src/i18n/locales/en/settings.json
  33. 5 1
      webview-ui/src/i18n/locales/es/chat.json
  34. 5 0
      webview-ui/src/i18n/locales/es/common.json
  35. 11 0
      webview-ui/src/i18n/locales/es/settings.json
  36. 5 1
      webview-ui/src/i18n/locales/fr/chat.json
  37. 5 0
      webview-ui/src/i18n/locales/fr/common.json
  38. 11 0
      webview-ui/src/i18n/locales/fr/settings.json
  39. 5 1
      webview-ui/src/i18n/locales/hi/chat.json
  40. 5 0
      webview-ui/src/i18n/locales/hi/common.json
  41. 11 0
      webview-ui/src/i18n/locales/hi/settings.json
  42. 4 0
      webview-ui/src/i18n/locales/id/chat.json
  43. 5 0
      webview-ui/src/i18n/locales/id/common.json
  44. 11 0
      webview-ui/src/i18n/locales/id/settings.json
  45. 5 1
      webview-ui/src/i18n/locales/it/chat.json
  46. 5 0
      webview-ui/src/i18n/locales/it/common.json
  47. 11 0
      webview-ui/src/i18n/locales/it/settings.json
  48. 5 1
      webview-ui/src/i18n/locales/ja/chat.json
  49. 5 0
      webview-ui/src/i18n/locales/ja/common.json
  50. 11 0
      webview-ui/src/i18n/locales/ja/settings.json
  51. 5 1
      webview-ui/src/i18n/locales/ko/chat.json
  52. 5 0
      webview-ui/src/i18n/locales/ko/common.json
  53. 11 0
      webview-ui/src/i18n/locales/ko/settings.json
  54. 5 1
      webview-ui/src/i18n/locales/nl/chat.json
  55. 5 0
      webview-ui/src/i18n/locales/nl/common.json
  56. 11 0
      webview-ui/src/i18n/locales/nl/settings.json
  57. 5 1
      webview-ui/src/i18n/locales/pl/chat.json
  58. 5 0
      webview-ui/src/i18n/locales/pl/common.json
  59. 11 0
      webview-ui/src/i18n/locales/pl/settings.json
  60. 5 1
      webview-ui/src/i18n/locales/pt-BR/chat.json
  61. 5 0
      webview-ui/src/i18n/locales/pt-BR/common.json
  62. 11 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  63. 5 1
      webview-ui/src/i18n/locales/ru/chat.json
  64. 5 0
      webview-ui/src/i18n/locales/ru/common.json
  65. 11 0
      webview-ui/src/i18n/locales/ru/settings.json
  66. 5 1
      webview-ui/src/i18n/locales/tr/chat.json
  67. 5 0
      webview-ui/src/i18n/locales/tr/common.json
  68. 11 0
      webview-ui/src/i18n/locales/tr/settings.json
  69. 5 1
      webview-ui/src/i18n/locales/vi/chat.json
  70. 5 0
      webview-ui/src/i18n/locales/vi/common.json
  71. 11 0
      webview-ui/src/i18n/locales/vi/settings.json
  72. 5 1
      webview-ui/src/i18n/locales/zh-CN/chat.json
  73. 5 0
      webview-ui/src/i18n/locales/zh-CN/common.json
  74. 11 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  75. 4 0
      webview-ui/src/i18n/locales/zh-TW/chat.json
  76. 5 0
      webview-ui/src/i18n/locales/zh-TW/common.json
  77. 11 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 1 - 1
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",

+ 7 - 1
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<typeof experimentsSchema>

+ 7 - 0
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({

+ 1 - 0
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)

+ 9 - 9
pnpm-lock.yaml

@@ -584,8 +584,8 @@ importers:
         specifier: ^1.14.0
         version: 1.14.0([email protected])
       '@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/[email protected]5.0':
-    resolution: {integrity: sha512-bRPQ6Zc3u5IqbDb0Vzj5+E5zFiq0tHRLICkZfwi3hyHvAiX9HxzNOboyR/HJhD+pr5xBizTDrJmccfA3RwrEBA==}
+  '@roo-code/[email protected]9.0':
+    resolution: {integrity: sha512-fXN0mdkd5GezpVrCspe6atUkwvSk5D4wF80g+lc8E3aPVqEAozoI97kHNulRChGlBw7UIdd5xxbr1Z8Jtn+S/Q==}
 
-  '@roo-code/[email protected]1.0':
-    resolution: {integrity: sha512-YJdFc6aYfaZ8EN08KbWaKLehRr1dcN3G3CzDjpppb08iehSEUZMycax/ryP5/G4vl34HTdtzyHNMboDen5ElUg==}
+  '@roo-code/[email protected]3.0':
+    resolution: {integrity: sha512-pX8ftkDq1CySBbkUTIW9/QEG52ttFT/kl0ID286l0L3W22wpGRUct6PCedNI9kLDM4s5sxaUeZx7b3rUChikkw==}
 
   '@sec-ant/[email protected]':
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -12732,9 +12732,9 @@ snapshots:
   '@rollup/[email protected]':
     optional: true
 
-  '@roo-code/[email protected]5.0':
+  '@roo-code/[email protected]9.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/[email protected]1.0':
+  '@roo-code/[email protected]3.0':
     dependencies:
       zod: 3.25.76
 

+ 128 - 0
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<ImageGenerationResult> {
+		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",
+			}
+		}
+	}
 }

+ 6 - 0
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

+ 20 - 0
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:
+<generate_image>
+<prompt>Your image description here</prompt>
+<path>path/to/save/image.png</path>
+</generate_image>
+
+Example: Requesting to generate a sunset image
+<generate_image>
+<prompt>A beautiful sunset over mountains with vibrant orange and purple colors</prompt>
+<path>images/sunset.png</path>
+</generate_image>`
+}

+ 8 - 0
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, (args: ToolArgs) => 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,
 }

+ 193 - 0
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
+	}
+}

+ 1 - 1
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:^",

+ 3 - 0
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.

+ 3 - 0
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)
 		})

+ 2 - 0
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<string, ExperimentId>
 
 type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPERIMENT_IDS>>>
@@ -18,6 +19,7 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
 	MULTI_FILE_APPLY_DIFF: { enabled: false },
 	POWER_STEERING: { enabled: false },
 	PREVENT_FOCUS_DISRUPTION: { enabled: false },
+	IMAGE_GENERATION: { enabled: false },
 }
 
 export const experimentDefault = Object.fromEntries(

+ 8 - 1
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<Pick<Record<ToolParamName, string>, "use_regex" | "ignore_case" | "start_line" | "end_line">>
 }
 
+export interface GenerateImageToolUse extends ToolUse {
+	name: "generate_image"
+	params: Partial<Pick<Record<ToolParamName, string>, "prompt" | "path">>
+}
+
 // Define tool group configuration
 export type ToolGroupConfig = {
 	tools: readonly string[]
@@ -190,6 +196,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
 	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<ToolGroup, ToolGroupConfig> = {
 		],
 	},
 	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"],

+ 41 - 0
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 = ({
 						</div>
 					</>
 				)
+			case "generateImage":
+				return (
+					<>
+						<div style={headerStyle}>
+							{tool.isProtected ? (
+								<span
+									className="codicon codicon-lock"
+									style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
+								/>
+							) : (
+								toolIcon("file-media")
+							)}
+							<span style={{ fontWeight: "bold" }}>
+								{message.type === "ask"
+									? tool.isProtected
+										? t("chat:fileOperations.wantsToGenerateImageProtected")
+										: tool.isOutsideWorkspace
+											? t("chat:fileOperations.wantsToGenerateImageOutsideWorkspace")
+											: t("chat:fileOperations.wantsToGenerateImage")
+									: t("chat:fileOperations.didGenerateImage")}
+							</span>
+						</div>
+						{message.type === "ask" && (
+							<CodeAccordian
+								path={tool.path}
+								code={tool.content}
+								language="text"
+								isExpanded={isExpanded}
+								onToggleExpand={handleToggleExpand}
+							/>
+						)}
+					</>
+				)
 			default:
 				return null
 		}
@@ -1002,6 +1036,13 @@ export const ChatRowContent = ({
 					return (
 						<div>
 							<Markdown markdown={message.text} partial={message.partial} />
+							{message.images && message.images.length > 0 && (
+								<div style={{ marginTop: "10px" }}>
+									{message.images.map((image, index) => (
+										<ImageBlock key={index} imageData={image} />
+									))}
+								</div>
+							)}
 						</div>
 					)
 				case "user_feedback":

+ 2 - 0
webview-ui/src/components/chat/ChatView.tsx

@@ -325,6 +325,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 								case "appliedDiff":
 								case "newFileCreated":
 								case "insertContent":
+								case "generateImage":
 									setPrimaryButtonText(t("chat:save.title"))
 									setSecondaryButtonText(t("chat:reject.title"))
 									break
@@ -1047,6 +1048,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 				"newFileCreated",
 				"searchAndReplace",
 				"insertContent",
+				"generateImage",
 			].includes(tool.tool)
 		}
 

+ 15 - 0
webview-ui/src/components/common/ImageBlock.tsx

@@ -0,0 +1,15 @@
+import React from "react"
+import { ImageViewer } from "./ImageViewer"
+
+interface ImageBlockProps {
+	imageData: string
+	path?: string
+}
+
+export default function ImageBlock({ imageData, path }: ImageBlockProps) {
+	return (
+		<div className="my-2">
+			<ImageViewer imageData={imageData} path={path} alt="AI Generated Image" showControls={true} />
+		</div>
+	)
+}

+ 240 - 0
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 (
+		<>
+			<div
+				className={`relative w-full ${className}`}
+				onMouseEnter={handleMouseEnter}
+				onMouseLeave={handleMouseLeave}>
+				<img
+					src={imageData}
+					alt={alt}
+					className="w-full h-auto rounded cursor-pointer"
+					onClick={handleOpenInEditor}
+					style={{
+						maxHeight: "400px",
+						objectFit: "contain",
+						backgroundColor: "var(--vscode-editor-background)",
+					}}
+				/>
+				{path && <div className="mt-1 text-xs text-vscode-descriptionForeground">{path}</div>}
+				{showControls && isHovering && (
+					<div className="absolute bottom-2 right-2 flex gap-1 bg-vscode-editor-background/90 rounded p-0.5 z-10 opacity-100 transition-opacity duration-200 ease-in-out">
+						<MermaidActionButtons
+							onZoom={handleZoom}
+							onCopy={handleCopy}
+							onSave={handleSave}
+							onViewCode={() => {}} // Not applicable for images
+							copyFeedback={copyFeedback}
+						/>
+					</div>
+				)}
+			</div>
+
+			<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
+				<div className="flex justify-between items-center border-b border-vscode-editorGroup-border">
+					<div className="flex gap-0">
+						<TabButton
+							icon="file-media"
+							label={t("common:image.tabs.view")}
+							isActive={true}
+							onClick={() => {}}
+						/>
+					</div>
+
+					<div className="pr-3">
+						<StandardTooltip content={t("common:mermaid.buttons.close")}>
+							<IconButton icon="close" onClick={() => setShowModal(false)} />
+						</StandardTooltip>
+					</div>
+				</div>
+				<div
+					className="flex-1 p-4 pb-[60px] overflow-auto flex items-center justify-center"
+					onWheel={handleWheel}>
+					<div
+						style={{
+							transform: `scale(${zoomLevel}) translate(${dragPosition.x}px, ${dragPosition.y}px)`,
+							transformOrigin: "center center",
+							transition: isDragging ? "none" : "transform 0.1s ease",
+							cursor: isDragging ? "grabbing" : "grab",
+						}}
+						onMouseDown={(e) => {
+							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)}>
+						<img
+							src={imageData}
+							alt={alt}
+							style={{
+								maxWidth: "90vw",
+								maxHeight: "80vh",
+								objectFit: "contain",
+							}}
+						/>
+					</div>
+					<div className="absolute bottom-4 left-4 bg-vscode-editor-background border border-vscode-editorGroup-border rounded px-2 py-1 text-xs text-vscode-descriptionForeground pointer-events-none opacity-80">
+						{Math.round(zoomLevel * 100)}%
+					</div>
+				</div>
+				<div className="absolute bottom-0 right-0 left-0 p-3 flex items-center justify-end gap-2 bg-vscode-editor-background border-t border-vscode-editorGroup-border rounded-b">
+					<ZoomControls
+						zoomLevel={zoomLevel}
+						zoomInTitle={t("common:mermaid.buttons.zoomIn")}
+						zoomOutTitle={t("common:mermaid.buttons.zoomOut")}
+						useContinuousZoom={true}
+						adjustZoom={adjustZoom}
+						zoomInStep={0.2}
+						zoomOutStep={-0.2}
+					/>
+					{path && (
+						<StandardTooltip content={t("common:mermaid.buttons.copy")}>
+							<IconButton icon={copyFeedback ? "check" : "copy"} onClick={handleCopy} />
+						</StandardTooltip>
+					)}
+					<StandardTooltip content={t("common:mermaid.buttons.save")}>
+						<IconButton icon="save" onClick={handleSave} />
+					</StandardTooltip>
+				</div>
+			</Modal>
+		</>
+	)
+}

+ 18 - 0
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<HTMLDivElement> & {
 	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 (
+								<ImageGenerationSettings
+									key={config[0]}
+									enabled={experiments[EXPERIMENT_IDS.IMAGE_GENERATION] ?? false}
+									onChange={(enabled) =>
+										setExperimentEnabled(EXPERIMENT_IDS.IMAGE_GENERATION, enabled)
+									}
+									apiConfiguration={apiConfiguration}
+									setApiConfigurationField={setApiConfigurationField}
+								/>
+							)
+						}
 						return (
 							<ExperimentalFeature
 								key={config[0]}

+ 122 - 0
webview-ui/src/components/settings/ImageGenerationSettings.tsx

@@ -0,0 +1,122 @@
+import React, { useState, useEffect } from "react"
+import { VSCodeCheckbox, VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+import type { ProviderSettings } from "@roo-code/types"
+
+interface ImageGenerationSettingsProps {
+	enabled: boolean
+	onChange: (enabled: boolean) => void
+	apiConfiguration: ProviderSettings
+	setApiConfigurationField: <K extends keyof ProviderSettings>(
+		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 (
+		<div className="space-y-4">
+			<div>
+				<div className="flex items-center gap-2">
+					<VSCodeCheckbox checked={enabled} onChange={(e: any) => onChange(e.target.checked)}>
+						<span className="font-medium">{t("settings:experimental.IMAGE_GENERATION.name")}</span>
+					</VSCodeCheckbox>
+				</div>
+				<p className="text-vscode-descriptionForeground text-sm mt-0">
+					{t("settings:experimental.IMAGE_GENERATION.description")}
+				</p>
+			</div>
+
+			{enabled && (
+				<div className="ml-2 space-y-3">
+					{/* API Key Configuration */}
+					<div>
+						<label className="block font-medium mb-1">
+							{t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyLabel")}
+						</label>
+						<VSCodeTextField
+							value={openRouterApiKey}
+							onInput={(e: any) => setOpenRouterApiKey(e.target.value)}
+							placeholder={t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder")}
+							className="w-full"
+							type="password"
+						/>
+						<p className="text-vscode-descriptionForeground text-xs mt-1">
+							{t("settings:experimental.IMAGE_GENERATION.getApiKeyText")}{" "}
+							<a
+								href="https://openrouter.ai/keys"
+								target="_blank"
+								rel="noopener noreferrer"
+								className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground">
+								openrouter.ai/keys
+							</a>
+						</p>
+					</div>
+
+					{/* Model Selection */}
+					<div>
+						<label className="block font-medium mb-1">
+							{t("settings:experimental.IMAGE_GENERATION.modelSelectionLabel")}
+						</label>
+						<VSCodeDropdown
+							value={selectedModel}
+							onChange={(e: any) => setSelectedModel(e.target.value)}
+							className="w-full">
+							{IMAGE_GENERATION_MODELS.map((model) => (
+								<VSCodeOption key={model.value} value={model.value}>
+									{model.label}
+								</VSCodeOption>
+							))}
+						</VSCodeDropdown>
+						<p className="text-vscode-descriptionForeground text-xs mt-1">
+							{t("settings:experimental.IMAGE_GENERATION.modelSelectionDescription")}
+						</p>
+					</div>
+
+					{/* Status Message */}
+					{enabled && !openRouterApiKey && (
+						<div className="p-2 bg-vscode-editorWarning-background text-vscode-editorWarning-foreground rounded text-sm">
+							{t("settings:experimental.IMAGE_GENERATION.warningMissingKey")}
+						</div>
+					)}
+
+					{enabled && openRouterApiKey && (
+						<div className="p-2 bg-vscode-editorInfo-background text-vscode-editorInfo-foreground rounded text-sm">
+							{t("settings:experimental.IMAGE_GENERATION.successConfigured")}
+						</div>
+					)}
+				</div>
+			)}
+		</div>
+	)
+}

+ 6 - 1
webview-ui/src/components/settings/SettingsView.tsx

@@ -718,7 +718,12 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 
 					{/* Experimental Section */}
 					{activeTab === "experimental" && (
-						<ExperimentalSettings setExperimentEnabled={setExperimentEnabled} experiments={experiments} />
+						<ExperimentalSettings
+							setExperimentEnabled={setExperimentEnabled}
+							experiments={experiments}
+							apiConfiguration={apiConfiguration}
+							setApiConfigurationField={setApiConfigurationField}
+						/>
 					)}
 
 					{/* Language Section */}

+ 2 - 0
webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx

@@ -230,6 +230,7 @@ describe("mergeExtensionState", () => {
 				multiFileApplyDiff: true,
 				preventFocusDisruption: false,
 				newTaskRequireTodos: false,
+				imageGeneration: false,
 			} as Record<ExperimentId, boolean>,
 		}
 
@@ -248,6 +249,7 @@ describe("mergeExtensionState", () => {
 			multiFileApplyDiff: true,
 			preventFocusDisruption: false,
 			newTaskRequireTodos: false,
+			imageGeneration: false,
 		})
 	})
 })

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 4 - 0
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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"

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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 इस निर्देशिका में शीर्ष स्तर की फ़ाइलें देखना चाहता है:",

+ 5 - 0
webview-ui/src/i18n/locales/hi/common.json

@@ -48,6 +48,11 @@
 			"copyError": "इमेज कॉपी करने में त्रुटि"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "चित्र"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "अमान्य डेटा URI फॉर्मेट",

+ 11 - 0
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": {

+ 4 - 0
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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はこのディレクトリのトップレベルファイルを表示したい:",

+ 5 - 0
webview-ui/src/i18n/locales/ja/common.json

@@ -48,6 +48,11 @@
 			"copyError": "画像のコピーエラー"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "画像"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "無効なデータURI形式",

+ 11 - 0
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": {

+ 5 - 1
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가 이 디렉토리의 최상위 파일을 보고 싶어합니다:",

+ 5 - 0
webview-ui/src/i18n/locales/ko/common.json

@@ -48,6 +48,11 @@
 			"copyError": "이미지 복사 오류"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "이미지"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "잘못된 데이터 URI 형식",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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",

+ 11 - 0
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": {

+ 5 - 1
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 хочет просмотреть файлы верхнего уровня в этой директории:",

+ 5 - 0
webview-ui/src/i18n/locales/ru/common.json

@@ -48,6 +48,11 @@
 			"copyError": "Ошибка копирования изображения"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "Изображение"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "Неверный формат URI данных",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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ı",

+ 11 - 0
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": {

+ 5 - 1
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:",

+ 5 - 0
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ệ",

+ 11 - 0
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": {

+ 5 - 1
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": "需要查看目录文件列表:",

+ 5 - 0
webview-ui/src/i18n/locales/zh-CN/common.json

@@ -48,6 +48,11 @@
 			"copyError": "复制图片时出错"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "图像"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "无效的数据 URI 格式",

+ 11 - 0
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": {

+ 4 - 0
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 已在此檔案執行搜尋和取代:",

+ 5 - 0
webview-ui/src/i18n/locales/zh-TW/common.json

@@ -48,6 +48,11 @@
 			"copyError": "複製圖片時發生錯誤"
 		}
 	},
+	"image": {
+		"tabs": {
+			"view": "圖像"
+		}
+	},
 	"file": {
 		"errors": {
 			"invalidDataUri": "無效的資料 URI 格式",

+ 11 - 0
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": {