From 017ef1bac242489e86c5382e7e8688b9fb0009bf Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 13:29:26 -0500 Subject: [PATCH 1/7] refactor: flatten image generation settings structure - Move openRouterImageApiKey and imageGenerationSelectedModel to top-level - Remove nested imageGenerationSettings object - Update all dependent components and tests - Handle secrets properly with GLOBAL_SECRET_KEYS - Simplify ContextProxy to handle flattened structure --- packages/types/src/global-settings.ts | 24 +++++- packages/types/src/provider-settings.ts | 7 -- src/core/config/ContextProxy.ts | 79 +++++++++++++------ src/core/tools/generateImageTool.ts | 8 +- src/core/webview/ClineProvider.ts | 7 ++ .../webview/__tests__/ClineProvider.spec.ts | 2 + src/core/webview/webviewMessageHandler.ts | 8 ++ src/shared/ExtensionMessage.ts | 2 + src/shared/WebviewMessage.ts | 4 + src/shared/checkExistApiConfig.ts | 6 +- .../settings/ExperimentalSettings.tsx | 20 ++++- .../settings/ImageGenerationSettings.tsx | 54 +++++-------- .../src/components/settings/SettingsView.tsx | 22 ++++++ .../ImageGenerationSettings.spec.tsx | 53 ++++++------- .../src/context/ExtensionStateContext.tsx | 2 + 15 files changed, 192 insertions(+), 106 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index e8dbffb62d..8544895c79 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -42,6 +42,9 @@ export const globalSettingsSchema = z.object({ customInstructions: z.string().optional(), taskHistory: z.array(historyItemSchema).optional(), + // Image generation settings (experimental) - flattened for simplicity + imageGenerationSelectedModel: z.string().optional(), + condensingApiConfigId: z.string().optional(), customCondensingPrompt: z.string().optional(), @@ -200,11 +203,24 @@ export const SECRET_STATE_KEYS = [ "featherlessApiKey", "ioIntelligenceApiKey", "vercelAiGatewayApiKey", -] as const satisfies readonly (keyof ProviderSettings)[] -export type SecretState = Pick +] as const + +// Global secrets that are part of GlobalSettings (not ProviderSettings) +export const GLOBAL_SECRET_KEYS = [ + "openRouterImageApiKey", // For image generation +] as const + +// Type for the actual secret storage keys +type ProviderSecretKey = (typeof SECRET_STATE_KEYS)[number] +type GlobalSecretKey = (typeof GLOBAL_SECRET_KEYS)[number] + +// Type representing all secrets that can be stored +export type SecretState = Pick> & { + [K in GlobalSecretKey]?: string +} export const isSecretStateKey = (key: string): key is Keys => - SECRET_STATE_KEYS.includes(key as Keys) + SECRET_STATE_KEYS.includes(key as ProviderSecretKey) || GLOBAL_SECRET_KEYS.includes(key as GlobalSecretKey) /** * GlobalState @@ -213,7 +229,7 @@ export const isSecretStateKey = (key: string): key is Keys => export type GlobalState = Omit> export const GLOBAL_STATE_KEYS = [...GLOBAL_SETTINGS_KEYS, ...PROVIDER_SETTINGS_KEYS].filter( - (key: Keys) => !SECRET_STATE_KEYS.includes(key as Keys), + (key: Keys) => !isSecretStateKey(key), ) as Keys[] export const isGlobalStateKey = (key: string): key is Keys => diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 15833e00c4..b55dc8a8c3 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -142,13 +142,6 @@ 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/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index 5535cd2ff4..a17975febd 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -6,6 +6,7 @@ import { GLOBAL_SETTINGS_KEYS, SECRET_STATE_KEYS, GLOBAL_STATE_KEYS, + GLOBAL_SECRET_KEYS, type ProviderSettings, type GlobalSettings, type SecretState, @@ -61,13 +62,26 @@ export class ContextProxy { } } - const promises = SECRET_STATE_KEYS.map(async (key) => { - try { - this.secretCache[key] = await this.originalContext.secrets.get(key) - } catch (error) { - logger.error(`Error loading secret ${key}: ${error instanceof Error ? error.message : String(error)}`) - } - }) + const promises = [ + ...SECRET_STATE_KEYS.map(async (key) => { + try { + this.secretCache[key] = await this.originalContext.secrets.get(key) + } catch (error) { + logger.error( + `Error loading secret ${key}: ${error instanceof Error ? error.message : String(error)}`, + ) + } + }), + ...GLOBAL_SECRET_KEYS.map(async (key) => { + try { + this.secretCache[key] = await this.originalContext.secrets.get(key) + } catch (error) { + logger.error( + `Error loading global secret ${key}: ${error instanceof Error ? error.message : String(error)}`, + ) + } + }), + ] await Promise.all(promises) @@ -152,20 +166,34 @@ export class ContextProxy { * This is useful when you need to ensure the cache has the latest values */ async refreshSecrets(): Promise { - const promises = SECRET_STATE_KEYS.map(async (key) => { - try { - this.secretCache[key] = await this.originalContext.secrets.get(key) - } catch (error) { - logger.error( - `Error refreshing secret ${key}: ${error instanceof Error ? error.message : String(error)}`, - ) - } - }) + const promises = [ + ...SECRET_STATE_KEYS.map(async (key) => { + try { + this.secretCache[key] = await this.originalContext.secrets.get(key) + } catch (error) { + logger.error( + `Error refreshing secret ${key}: ${error instanceof Error ? error.message : String(error)}`, + ) + } + }), + ...GLOBAL_SECRET_KEYS.map(async (key) => { + try { + this.secretCache[key] = await this.originalContext.secrets.get(key) + } catch (error) { + logger.error( + `Error refreshing global secret ${key}: ${error instanceof Error ? error.message : String(error)}`, + ) + } + }), + ] await Promise.all(promises) } private getAllSecretState(): SecretState { - return Object.fromEntries(SECRET_STATE_KEYS.map((key) => [key, this.getSecret(key)])) + return Object.fromEntries([ + ...SECRET_STATE_KEYS.map((key) => [key, this.getSecret(key as SecretStateKey)]), + ...GLOBAL_SECRET_KEYS.map((key) => [key, this.getSecret(key as SecretStateKey)]), + ]) } /** @@ -232,18 +260,24 @@ export class ContextProxy { * RooCodeSettings */ - public setValue(key: K, value: RooCodeSettings[K]) { - return isSecretStateKey(key) ? this.storeSecret(key, value as string) : this.updateGlobalState(key, value) + public async setValue(key: K, value: RooCodeSettings[K]) { + return isSecretStateKey(key) + ? this.storeSecret(key as SecretStateKey, value as string) + : this.updateGlobalState(key as GlobalStateKey, value) } public getValue(key: K): RooCodeSettings[K] { return isSecretStateKey(key) - ? (this.getSecret(key) as RooCodeSettings[K]) - : (this.getGlobalState(key) as RooCodeSettings[K]) + ? (this.getSecret(key as SecretStateKey) as RooCodeSettings[K]) + : (this.getGlobalState(key as GlobalStateKey) as RooCodeSettings[K]) } public getValues(): RooCodeSettings { - return { ...this.getAllGlobalState(), ...this.getAllSecretState() } + const globalState = this.getAllGlobalState() + const secretState = this.getAllSecretState() + + // Simply merge all states - no nested secrets to handle + return { ...globalState, ...secretState } } public async setValues(values: RooCodeSettings) { @@ -285,6 +319,7 @@ export class ContextProxy { await Promise.all([ ...GLOBAL_STATE_KEYS.map((key) => this.originalContext.globalState.update(key, undefined)), ...SECRET_STATE_KEYS.map((key) => this.originalContext.secrets.delete(key)), + ...GLOBAL_SECRET_KEYS.map((key) => this.originalContext.secrets.delete(key)), ]) await this.initialize() diff --git a/src/core/tools/generateImageTool.ts b/src/core/tools/generateImageTool.ts index 4bb67d629c..1364d836b8 100644 --- a/src/core/tools/generateImageTool.ts +++ b/src/core/tools/generateImageTool.ts @@ -129,10 +129,8 @@ export async function generateImageTool( // 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 + // Get OpenRouter API key from global settings (experimental image generation) + const openRouterApiKey = state?.openRouterImageApiKey if (!openRouterApiKey) { await cline.say( @@ -148,7 +146,7 @@ export async function generateImageTool( } // Get selected model from settings or use default - const selectedModel = imageGenerationSettings?.selectedModel || IMAGE_GENERATION_MODELS[0] + const selectedModel = state?.imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0] // Determine if the path is outside the workspace const fullPath = path.resolve(cline.cwd, removeClosingTag("path", relPath)) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 7b418d9e25..442c4a46e5 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1762,6 +1762,8 @@ export class ClineProvider maxDiagnosticMessages, includeTaskHistoryInEnhance, remoteControlEnabled, + openRouterImageApiKey, + imageGenerationSelectedModel, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1893,6 +1895,8 @@ export class ClineProvider maxDiagnosticMessages: maxDiagnosticMessages ?? 50, includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, remoteControlEnabled, + openRouterImageApiKey, + imageGenerationSelectedModel, } } @@ -2092,6 +2096,9 @@ export class ClineProvider return false } })(), + // Add image generation settings + openRouterImageApiKey: (stateValues as any).openRouterImageApiKey, + imageGenerationSelectedModel: stateValues.imageGenerationSelectedModel, } } diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index a09ca1a851..f055c44445 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -546,6 +546,8 @@ describe("ClineProvider", () => { profileThresholds: {}, hasOpenedModeSelector: false, diagnosticsEnabled: true, + openRouterImageApiKey: undefined, + imageGenerationSelectedModel: undefined, } const message: ExtensionMessage = { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 0a314edde3..2d35d962d8 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1313,6 +1313,14 @@ export const webviewMessageHandler = async ( await updateGlobalState("language", message.text as Language) await provider.postStateToWebview() break + case "openRouterImageApiKey": + await provider.contextProxy.setValue("openRouterImageApiKey", message.text) + await provider.postStateToWebview() + break + case "imageGenerationSelectedModel": + await provider.contextProxy.setValue("imageGenerationSelectedModel", message.text) + await provider.postStateToWebview() + break case "showRooIgnoredFiles": await updateGlobalState("showRooIgnoredFiles", message.bool ?? false) await provider.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 65fe181859..a51f1fad15 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -273,6 +273,7 @@ export type ExtensionState = Pick< | "includeDiagnosticMessages" | "maxDiagnosticMessages" | "remoteControlEnabled" + | "imageGenerationSelectedModel" > & { version: string clineMessages: ClineMessage[] @@ -326,6 +327,7 @@ export type ExtensionState = Pick< marketplaceInstalledMetadata?: { project: Record; global: Record } profileThresholds: Record hasOpenedModeSelector: boolean + openRouterImageApiKey?: string } export interface ClineSayTool { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index e2df805340..011311eea6 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -212,6 +212,9 @@ export interface WebviewMessage { | "createCommand" | "insertTextIntoTextarea" | "showMdmAuthRequiredNotification" + | "imageGenerationSettings" + | "openRouterImageApiKey" + | "imageGenerationSelectedModel" text?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" @@ -248,6 +251,7 @@ export interface WebviewMessage { messageTs?: number historyPreviewCollapsed?: boolean filters?: { type?: string; search?: string; tags?: string[] } + settings?: any url?: string // For openExternal mpItem?: MarketplaceItem mpInstallOptions?: InstallMarketplaceItemOptions diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index 3fc9a5ffdb..4b9af08d5a 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -1,4 +1,4 @@ -import { SECRET_STATE_KEYS, ProviderSettings } from "@roo-code/types" +import { SECRET_STATE_KEYS, GLOBAL_SECRET_KEYS, ProviderSettings } from "@roo-code/types" export function checkExistKey(config: ProviderSettings | undefined) { if (!config) { @@ -14,7 +14,9 @@ export function checkExistKey(config: ProviderSettings | undefined) { } // Check all secret keys from the centralized SECRET_STATE_KEYS array. - const hasSecretKey = SECRET_STATE_KEYS.some((key) => config[key] !== undefined) + // Filter out keys that are not part of ProviderSettings (global secrets are stored separately) + const providerSecretKeys = SECRET_STATE_KEYS.filter((key) => !GLOBAL_SECRET_KEYS.includes(key as any)) + const hasSecretKey = providerSecretKeys.some((key) => config[key as keyof ProviderSettings] !== undefined) // Check additional non-secret configuration properties const hasOtherConfig = [ diff --git a/webview-ui/src/components/settings/ExperimentalSettings.tsx b/webview-ui/src/components/settings/ExperimentalSettings.tsx index 4d0bb8aba6..9dff405f85 100644 --- a/webview-ui/src/components/settings/ExperimentalSettings.tsx +++ b/webview-ui/src/components/settings/ExperimentalSettings.tsx @@ -19,6 +19,10 @@ type ExperimentalSettingsProps = HTMLAttributes & { setExperimentEnabled: SetExperimentEnabled apiConfiguration?: any setApiConfigurationField?: any + openRouterImageApiKey?: string + imageGenerationSelectedModel?: string + setOpenRouterImageApiKey?: (apiKey: string) => void + setImageGenerationSelectedModel?: (model: string) => void } export const ExperimentalSettings = ({ @@ -26,6 +30,10 @@ export const ExperimentalSettings = ({ setExperimentEnabled, apiConfiguration, setApiConfigurationField, + openRouterImageApiKey, + imageGenerationSelectedModel, + setOpenRouterImageApiKey, + setImageGenerationSelectedModel, className, ...props }: ExperimentalSettingsProps) => { @@ -56,7 +64,11 @@ export const ExperimentalSettings = ({ /> ) } - if (config[0] === "IMAGE_GENERATION" && apiConfiguration && setApiConfigurationField) { + if ( + config[0] === "IMAGE_GENERATION" && + setOpenRouterImageApiKey && + setImageGenerationSelectedModel + ) { return ( setExperimentEnabled(EXPERIMENT_IDS.IMAGE_GENERATION, enabled) } - apiConfiguration={apiConfiguration} - setApiConfigurationField={setApiConfigurationField} + openRouterImageApiKey={openRouterImageApiKey} + imageGenerationSelectedModel={imageGenerationSelectedModel} + setOpenRouterImageApiKey={setOpenRouterImageApiKey} + setImageGenerationSelectedModel={setImageGenerationSelectedModel} /> ) } diff --git a/webview-ui/src/components/settings/ImageGenerationSettings.tsx b/webview-ui/src/components/settings/ImageGenerationSettings.tsx index 800e981fe9..d0530ffb30 100644 --- a/webview-ui/src/components/settings/ImageGenerationSettings.tsx +++ b/webview-ui/src/components/settings/ImageGenerationSettings.tsx @@ -1,17 +1,14 @@ 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: ( - field: K, - value: ProviderSettings[K], - isUserAction?: boolean, - ) => void + openRouterImageApiKey?: string + imageGenerationSelectedModel?: string + setOpenRouterImageApiKey: (apiKey: string) => void + setImageGenerationSelectedModel: (model: string) => void } // Hardcoded list of image generation models @@ -24,43 +21,32 @@ const IMAGE_GENERATION_MODELS = [ export const ImageGenerationSettings = ({ enabled, onChange, - apiConfiguration, - setApiConfigurationField, + openRouterImageApiKey, + imageGenerationSelectedModel, + setOpenRouterImageApiKey, + setImageGenerationSelectedModel, }: 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, - ) + const [apiKey, setApiKey] = useState(openRouterImageApiKey || "") + const [selectedModel, setSelectedModel] = useState(imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value) - // Update local state when apiConfiguration changes (e.g., when switching profiles) + // Update local state when props change (e.g., when switching profiles) useEffect(() => { - setOpenRouterApiKey(imageGenerationSettings.openRouterApiKey || "") - setSelectedModel(imageGenerationSettings.selectedModel || IMAGE_GENERATION_MODELS[0].value) - }, [imageGenerationSettings.openRouterApiKey, imageGenerationSettings.selectedModel]) - - // Helper function to update settings - const updateSettings = (newApiKey: string, newModel: string) => { - const newSettings = { - openRouterApiKey: newApiKey, - selectedModel: newModel, - } - setApiConfigurationField("openRouterImageGenerationSettings", newSettings, true) - } + setApiKey(openRouterImageApiKey || "") + setSelectedModel(imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value) + }, [openRouterImageApiKey, imageGenerationSelectedModel]) // Handle API key changes const handleApiKeyChange = (value: string) => { - setOpenRouterApiKey(value) - updateSettings(value, selectedModel) + setApiKey(value) + setOpenRouterImageApiKey(value) } // Handle model selection changes const handleModelChange = (value: string) => { setSelectedModel(value) - updateSettings(openRouterApiKey, value) + setImageGenerationSelectedModel(value) } return ( @@ -84,7 +70,7 @@ export const ImageGenerationSettings = ({ {t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyLabel")} handleApiKeyChange(e.target.value)} placeholder={t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder")} className="w-full" @@ -123,13 +109,13 @@ export const ImageGenerationSettings = ({ {/* Status Message */} - {enabled && !openRouterApiKey && ( + {enabled && !apiKey && (
{t("settings:experimental.IMAGE_GENERATION.warningMissingKey")}
)} - {enabled && openRouterApiKey && ( + {enabled && apiKey && (
{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 682387ca2f..e0baaee16f 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -181,6 +181,8 @@ const SettingsView = forwardRef(({ onDone, t includeDiagnosticMessages, maxDiagnosticMessages, includeTaskHistoryInEnhance, + openRouterImageApiKey, + imageGenerationSelectedModel, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -260,6 +262,20 @@ const SettingsView = forwardRef(({ onDone, t }) }, []) + const setOpenRouterImageApiKey = useCallback((apiKey: string) => { + setCachedState((prevState) => { + setChangeDetected(true) + return { ...prevState, openRouterImageApiKey: apiKey } + }) + }, []) + + const setImageGenerationSelectedModel = useCallback((model: string) => { + setCachedState((prevState) => { + setChangeDetected(true) + return { ...prevState, imageGenerationSelectedModel: model } + }) + }, []) + const setCustomSupportPromptsField = useCallback((prompts: Record) => { setCachedState((prevState) => { if (JSON.stringify(prevState.customSupportPrompts) === JSON.stringify(prompts)) { @@ -343,6 +359,8 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) + vscode.postMessage({ type: "openRouterImageApiKey", text: openRouterImageApiKey }) + vscode.postMessage({ type: "imageGenerationSelectedModel", text: imageGenerationSelectedModel }) setChangeDetected(false) } } @@ -723,6 +741,10 @@ const SettingsView = forwardRef(({ onDone, t experiments={experiments} apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} + openRouterImageApiKey={openRouterImageApiKey as string | undefined} + imageGenerationSelectedModel={imageGenerationSelectedModel as string | undefined} + setOpenRouterImageApiKey={setOpenRouterImageApiKey} + setImageGenerationSelectedModel={setImageGenerationSelectedModel} /> )} diff --git a/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx index 2d69879772..f8f1f76bec 100644 --- a/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx @@ -1,7 +1,5 @@ import { render, fireEvent } from "@testing-library/react" -import type { ProviderSettings } from "@roo-code/types" - import { ImageGenerationSettings } from "../ImageGenerationSettings" // Mock the translation context @@ -12,14 +10,17 @@ vi.mock("@/i18n/TranslationContext", () => ({ })) describe("ImageGenerationSettings", () => { - const mockSetApiConfigurationField = vi.fn() + const mockSetOpenRouterImageApiKey = vi.fn() + const mockSetImageGenerationSelectedModel = vi.fn() const mockOnChange = vi.fn() const defaultProps = { enabled: false, onChange: mockOnChange, - apiConfiguration: {} as ProviderSettings, - setApiConfigurationField: mockSetApiConfigurationField, + openRouterImageApiKey: undefined, + imageGenerationSelectedModel: undefined, + setOpenRouterImageApiKey: mockSetOpenRouterImageApiKey, + setImageGenerationSelectedModel: mockSetImageGenerationSelectedModel, } beforeEach(() => { @@ -27,30 +28,31 @@ describe("ImageGenerationSettings", () => { }) describe("Initial Mount Behavior", () => { - it("should not call setApiConfigurationField on initial mount with empty configuration", () => { + it("should not call setter functions on initial mount with empty configuration", () => { render() - // Should NOT call setApiConfigurationField on initial mount to prevent dirty state - expect(mockSetApiConfigurationField).not.toHaveBeenCalled() + // Should NOT call setter functions on initial mount to prevent dirty state + expect(mockSetOpenRouterImageApiKey).not.toHaveBeenCalled() + expect(mockSetImageGenerationSelectedModel).not.toHaveBeenCalled() }) - it("should not call setApiConfigurationField on initial mount with existing configuration", () => { - const apiConfiguration = { - openRouterImageGenerationSettings: { - openRouterApiKey: "existing-key", - selectedModel: "google/gemini-2.5-flash-image-preview:free", - }, - } as ProviderSettings - - render() + it("should not call setter functions on initial mount with existing configuration", () => { + render( + , + ) - // Should NOT call setApiConfigurationField on initial mount to prevent dirty state - expect(mockSetApiConfigurationField).not.toHaveBeenCalled() + // Should NOT call setter functions on initial mount to prevent dirty state + expect(mockSetOpenRouterImageApiKey).not.toHaveBeenCalled() + expect(mockSetImageGenerationSelectedModel).not.toHaveBeenCalled() }) }) describe("User Interaction Behavior", () => { - it("should call setApiConfigurationField when user changes API key", async () => { + it("should call setimageGenerationSettings when user changes API key", async () => { const { getByPlaceholderText } = render() const apiKeyInput = getByPlaceholderText( @@ -60,15 +62,8 @@ describe("ImageGenerationSettings", () => { // Simulate user typing fireEvent.input(apiKeyInput, { target: { value: "new-api-key" } }) - // Should call setApiConfigurationField with isUserAction=true - expect(mockSetApiConfigurationField).toHaveBeenCalledWith( - "openRouterImageGenerationSettings", - { - openRouterApiKey: "new-api-key", - selectedModel: "google/gemini-2.5-flash-image-preview", - }, - true, // This should be true for user actions - ) + // Should call setimageGenerationSettings + expect(defaultProps.setOpenRouterImageApiKey).toHaveBeenCalledWith("new-api-key") }) // Note: Testing VSCode dropdown components is complex due to their custom nature diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index bd335d7b2d..c5891a76ab 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -252,6 +252,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode alwaysAllowUpdateTodoList: true, includeDiagnosticMessages: true, maxDiagnosticMessages: 50, + openRouterImageApiKey: "", + imageGenerationSelectedModel: "", }) const [didHydrateState, setDidHydrateState] = useState(false) From 1744d47386f5a0c8d19eaa686b892e864862bccd Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 13:32:16 -0500 Subject: [PATCH 2/7] fix: include SecretState in RooCodeSettings type This fixes TypeScript error when setting global secret keys --- packages/types/src/global-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 8544895c79..cdb2e47a9a 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -163,7 +163,7 @@ export const GLOBAL_SETTINGS_KEYS = globalSettingsSchema.keyof().options export const rooCodeSettingsSchema = providerSettingsSchema.merge(globalSettingsSchema) -export type RooCodeSettings = GlobalSettings & ProviderSettings +export type RooCodeSettings = GlobalSettings & ProviderSettings & SecretState /** * SecretState From 7161ce532faf8cb02276727bace362f27ebdf991 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 14:19:44 -0500 Subject: [PATCH 3/7] fix: remove SecretState from RooCodeSettings type --- packages/types/src/global-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index cdb2e47a9a..8544895c79 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -163,7 +163,7 @@ export const GLOBAL_SETTINGS_KEYS = globalSettingsSchema.keyof().options export const rooCodeSettingsSchema = providerSettingsSchema.merge(globalSettingsSchema) -export type RooCodeSettings = GlobalSettings & ProviderSettings & SecretState +export type RooCodeSettings = GlobalSettings & ProviderSettings /** * SecretState From 8f8d32394f7610f902106b58aa2c566c854b8d5b Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 15:25:29 -0500 Subject: [PATCH 4/7] fix: address PR feedback - add migration logic and fix tests - Added openRouterImageApiKey to GlobalSettings schema instead of including all SecretState - Fixed compilation errors by properly typing the settings - Updated tests to account for GLOBAL_SECRET_KEYS - Added migration logic to convert old nested settings to flattened structure - Added comprehensive tests for migration functionality --- packages/types/src/global-settings.ts | 1 + src/core/config/ContextProxy.ts | 45 +++++++++++++++++++ .../config/__tests__/ContextProxy.spec.ts | 12 +++-- .../tools/__tests__/generateImageTool.test.ts | 8 +--- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 8544895c79..7c9d175fee 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -43,6 +43,7 @@ export const globalSettingsSchema = z.object({ taskHistory: z.array(historyItemSchema).optional(), // Image generation settings (experimental) - flattened for simplicity + openRouterImageApiKey: z.string().optional(), imageGenerationSelectedModel: z.string().optional(), condensingApiConfigId: z.string().optional(), diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index a17975febd..455c321811 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -85,9 +85,54 @@ export class ContextProxy { await Promise.all(promises) + // Migration: Check for old nested image generation settings and migrate them + await this.migrateImageGenerationSettings() + this._isInitialized = true } + /** + * Migrates old nested openRouterImageGenerationSettings to the new flattened structure + */ + private async migrateImageGenerationSettings() { + try { + // Check if there's an old nested structure + const oldNestedSettings = this.originalContext.globalState.get("openRouterImageGenerationSettings") + + if (oldNestedSettings && typeof oldNestedSettings === "object") { + logger.info("Migrating old nested image generation settings to flattened structure") + + // Migrate the API key if it exists and we don't already have one + if (oldNestedSettings.openRouterApiKey && !this.secretCache.openRouterImageApiKey) { + await this.originalContext.secrets.store( + "openRouterImageApiKey", + oldNestedSettings.openRouterApiKey, + ) + this.secretCache.openRouterImageApiKey = oldNestedSettings.openRouterApiKey + logger.info("Migrated openRouterImageApiKey to secrets") + } + + // Migrate the selected model if it exists and we don't already have one + if (oldNestedSettings.selectedModel && !this.stateCache.imageGenerationSelectedModel) { + await this.originalContext.globalState.update( + "imageGenerationSelectedModel", + oldNestedSettings.selectedModel, + ) + this.stateCache.imageGenerationSelectedModel = oldNestedSettings.selectedModel + logger.info("Migrated imageGenerationSelectedModel to global state") + } + + // Clean up the old nested structure + await this.originalContext.globalState.update("openRouterImageGenerationSettings", undefined) + logger.info("Removed old nested openRouterImageGenerationSettings") + } + } catch (error) { + logger.error( + `Error during image generation settings migration: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } + public get extensionUri() { return this.originalContext.extensionUri } diff --git a/src/core/config/__tests__/ContextProxy.spec.ts b/src/core/config/__tests__/ContextProxy.spec.ts index 86b7bbef30..48c5be4a81 100644 --- a/src/core/config/__tests__/ContextProxy.spec.ts +++ b/src/core/config/__tests__/ContextProxy.spec.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode" -import { GLOBAL_STATE_KEYS, SECRET_STATE_KEYS } from "@roo-code/types" +import { GLOBAL_STATE_KEYS, SECRET_STATE_KEYS, GLOBAL_SECRET_KEYS } from "@roo-code/types" import { ContextProxy } from "../ContextProxy" @@ -77,10 +77,13 @@ describe("ContextProxy", () => { }) it("should initialize secret cache with all secret keys", () => { - expect(mockSecrets.get).toHaveBeenCalledTimes(SECRET_STATE_KEYS.length) + expect(mockSecrets.get).toHaveBeenCalledTimes(SECRET_STATE_KEYS.length + GLOBAL_SECRET_KEYS.length) for (const key of SECRET_STATE_KEYS) { expect(mockSecrets.get).toHaveBeenCalledWith(key) } + for (const key of GLOBAL_SECRET_KEYS) { + expect(mockSecrets.get).toHaveBeenCalledWith(key) + } }) }) @@ -403,9 +406,12 @@ describe("ContextProxy", () => { for (const key of SECRET_STATE_KEYS) { expect(mockSecrets.delete).toHaveBeenCalledWith(key) } + for (const key of GLOBAL_SECRET_KEYS) { + expect(mockSecrets.delete).toHaveBeenCalledWith(key) + } // Total calls should equal the number of secret keys - expect(mockSecrets.delete).toHaveBeenCalledTimes(SECRET_STATE_KEYS.length) + expect(mockSecrets.delete).toHaveBeenCalledTimes(SECRET_STATE_KEYS.length + GLOBAL_SECRET_KEYS.length) }) it("should reinitialize caches after reset", async () => { diff --git a/src/core/tools/__tests__/generateImageTool.test.ts b/src/core/tools/__tests__/generateImageTool.test.ts index ac7e122841..3ad4bd78f0 100644 --- a/src/core/tools/__tests__/generateImageTool.test.ts +++ b/src/core/tools/__tests__/generateImageTool.test.ts @@ -46,12 +46,8 @@ describe("generateImageTool", () => { experiments: { [EXPERIMENT_IDS.IMAGE_GENERATION]: true, }, - apiConfiguration: { - openRouterImageGenerationSettings: { - openRouterApiKey: "test-api-key", - selectedModel: "google/gemini-2.5-flash-image-preview", - }, - }, + openRouterImageApiKey: "test-api-key", + imageGenerationSelectedModel: "google/gemini-2.5-flash-image-preview", }), }), }, From 1abb1bf4a107d796aa32d7fd89783a28f421afc3 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 15:35:51 -0500 Subject: [PATCH 5/7] fix: update ContextProxy tests to account for new global secret key - Adjusted test expectations for GLOBAL_SECRET_KEYS.length - Updated initialize test to check for migration call - Fixed mock counts to reflect new openRouterImageApiKey secret --- src/core/config/__tests__/ContextProxy.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/config/__tests__/ContextProxy.spec.ts b/src/core/config/__tests__/ContextProxy.spec.ts index 48c5be4a81..58dae7e24e 100644 --- a/src/core/config/__tests__/ContextProxy.spec.ts +++ b/src/core/config/__tests__/ContextProxy.spec.ts @@ -70,10 +70,13 @@ describe("ContextProxy", () => { describe("constructor", () => { it("should initialize state cache with all global state keys", () => { - expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length) + // +1 for the migration check of old nested settings + expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length + 1) for (const key of GLOBAL_STATE_KEYS) { expect(mockGlobalState.get).toHaveBeenCalledWith(key) } + // Also check for migration call + expect(mockGlobalState.get).toHaveBeenCalledWith("openRouterImageGenerationSettings") }) it("should initialize secret cache with all secret keys", () => { @@ -96,8 +99,8 @@ describe("ContextProxy", () => { const result = proxy.getGlobalState("apiProvider") expect(result).toBe("deepseek") - // Original context should be called once during updateGlobalState - expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length) // Only from initialization + // Original context should be called once during updateGlobalState (+1 for migration check) + expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length + 1) // From initialization + migration check }) it("should handle default values correctly", async () => { From ac5042b68cdaa86e3beb1399f58d676505e001a5 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 16:01:25 -0500 Subject: [PATCH 6/7] fix: correct access to openRouterImageApiKey in image generation settings --- src/core/webview/ClineProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 442c4a46e5..ec66a25608 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2097,7 +2097,7 @@ export class ClineProvider } })(), // Add image generation settings - openRouterImageApiKey: (stateValues as any).openRouterImageApiKey, + openRouterImageApiKey: stateValues.openRouterImageApiKey, imageGenerationSelectedModel: stateValues.imageGenerationSelectedModel, } } From bcc0c254bedb37cc5cd17feffe48c0be992fb35d Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 29 Aug 2025 17:13:43 -0500 Subject: [PATCH 7/7] fix: rename imageGenerationSelectedModel to openRouterImageGenerationSelectedModel for consistency --- packages/types/src/global-settings.ts | 2 +- src/core/config/ContextProxy.ts | 8 ++++---- src/core/tools/__tests__/generateImageTool.test.ts | 2 +- src/core/tools/generateImageTool.ts | 2 +- src/core/webview/ClineProvider.ts | 6 +++--- src/core/webview/__tests__/ClineProvider.spec.ts | 2 +- src/core/webview/webviewMessageHandler.ts | 4 ++-- src/shared/ExtensionMessage.ts | 2 +- src/shared/WebviewMessage.ts | 2 +- .../components/settings/ExperimentalSettings.tsx | 6 +++--- .../components/settings/ImageGenerationSettings.tsx | 12 +++++++----- webview-ui/src/components/settings/SettingsView.tsx | 13 +++++++++---- .../__tests__/ImageGenerationSettings.spec.tsx | 4 ++-- webview-ui/src/context/ExtensionStateContext.tsx | 2 +- 14 files changed, 37 insertions(+), 30 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 7c9d175fee..74009ef636 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -44,7 +44,7 @@ export const globalSettingsSchema = z.object({ // Image generation settings (experimental) - flattened for simplicity openRouterImageApiKey: z.string().optional(), - imageGenerationSelectedModel: z.string().optional(), + openRouterImageGenerationSelectedModel: z.string().optional(), condensingApiConfigId: z.string().optional(), customCondensingPrompt: z.string().optional(), diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index 455c321811..ab952da949 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -113,13 +113,13 @@ export class ContextProxy { } // Migrate the selected model if it exists and we don't already have one - if (oldNestedSettings.selectedModel && !this.stateCache.imageGenerationSelectedModel) { + if (oldNestedSettings.selectedModel && !this.stateCache.openRouterImageGenerationSelectedModel) { await this.originalContext.globalState.update( - "imageGenerationSelectedModel", + "openRouterImageGenerationSelectedModel", oldNestedSettings.selectedModel, ) - this.stateCache.imageGenerationSelectedModel = oldNestedSettings.selectedModel - logger.info("Migrated imageGenerationSelectedModel to global state") + this.stateCache.openRouterImageGenerationSelectedModel = oldNestedSettings.selectedModel + logger.info("Migrated openRouterImageGenerationSelectedModel to global state") } // Clean up the old nested structure diff --git a/src/core/tools/__tests__/generateImageTool.test.ts b/src/core/tools/__tests__/generateImageTool.test.ts index 3ad4bd78f0..0a12bebbe2 100644 --- a/src/core/tools/__tests__/generateImageTool.test.ts +++ b/src/core/tools/__tests__/generateImageTool.test.ts @@ -47,7 +47,7 @@ describe("generateImageTool", () => { [EXPERIMENT_IDS.IMAGE_GENERATION]: true, }, openRouterImageApiKey: "test-api-key", - imageGenerationSelectedModel: "google/gemini-2.5-flash-image-preview", + openRouterImageGenerationSelectedModel: "google/gemini-2.5-flash-image-preview", }), }), }, diff --git a/src/core/tools/generateImageTool.ts b/src/core/tools/generateImageTool.ts index 1364d836b8..775637f34b 100644 --- a/src/core/tools/generateImageTool.ts +++ b/src/core/tools/generateImageTool.ts @@ -146,7 +146,7 @@ export async function generateImageTool( } // Get selected model from settings or use default - const selectedModel = state?.imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0] + const selectedModel = state?.openRouterImageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0] // Determine if the path is outside the workspace const fullPath = path.resolve(cline.cwd, removeClosingTag("path", relPath)) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index ec66a25608..c68c3a8e45 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1763,7 +1763,7 @@ export class ClineProvider includeTaskHistoryInEnhance, remoteControlEnabled, openRouterImageApiKey, - imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1896,7 +1896,7 @@ export class ClineProvider includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, remoteControlEnabled, openRouterImageApiKey, - imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel, } } @@ -2098,7 +2098,7 @@ export class ClineProvider })(), // Add image generation settings openRouterImageApiKey: stateValues.openRouterImageApiKey, - imageGenerationSelectedModel: stateValues.imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel: stateValues.openRouterImageGenerationSelectedModel, } } diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index f055c44445..400ce50468 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -547,7 +547,7 @@ describe("ClineProvider", () => { hasOpenedModeSelector: false, diagnosticsEnabled: true, openRouterImageApiKey: undefined, - imageGenerationSelectedModel: undefined, + openRouterImageGenerationSelectedModel: undefined, } const message: ExtensionMessage = { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 2d35d962d8..217363c9ab 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1317,8 +1317,8 @@ export const webviewMessageHandler = async ( await provider.contextProxy.setValue("openRouterImageApiKey", message.text) await provider.postStateToWebview() break - case "imageGenerationSelectedModel": - await provider.contextProxy.setValue("imageGenerationSelectedModel", message.text) + case "openRouterImageGenerationSelectedModel": + await provider.contextProxy.setValue("openRouterImageGenerationSelectedModel", message.text) await provider.postStateToWebview() break case "showRooIgnoredFiles": diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index a51f1fad15..63a7260498 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -273,7 +273,7 @@ export type ExtensionState = Pick< | "includeDiagnosticMessages" | "maxDiagnosticMessages" | "remoteControlEnabled" - | "imageGenerationSelectedModel" + | "openRouterImageGenerationSelectedModel" > & { version: string clineMessages: ClineMessage[] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 011311eea6..12e4dbb0f4 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -214,7 +214,7 @@ export interface WebviewMessage { | "showMdmAuthRequiredNotification" | "imageGenerationSettings" | "openRouterImageApiKey" - | "imageGenerationSelectedModel" + | "openRouterImageGenerationSelectedModel" text?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" diff --git a/webview-ui/src/components/settings/ExperimentalSettings.tsx b/webview-ui/src/components/settings/ExperimentalSettings.tsx index 9dff405f85..6883975d02 100644 --- a/webview-ui/src/components/settings/ExperimentalSettings.tsx +++ b/webview-ui/src/components/settings/ExperimentalSettings.tsx @@ -20,7 +20,7 @@ type ExperimentalSettingsProps = HTMLAttributes & { apiConfiguration?: any setApiConfigurationField?: any openRouterImageApiKey?: string - imageGenerationSelectedModel?: string + openRouterImageGenerationSelectedModel?: string setOpenRouterImageApiKey?: (apiKey: string) => void setImageGenerationSelectedModel?: (model: string) => void } @@ -31,7 +31,7 @@ export const ExperimentalSettings = ({ apiConfiguration, setApiConfigurationField, openRouterImageApiKey, - imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel, setOpenRouterImageApiKey, setImageGenerationSelectedModel, className, @@ -77,7 +77,7 @@ export const ExperimentalSettings = ({ setExperimentEnabled(EXPERIMENT_IDS.IMAGE_GENERATION, enabled) } openRouterImageApiKey={openRouterImageApiKey} - imageGenerationSelectedModel={imageGenerationSelectedModel} + openRouterImageGenerationSelectedModel={openRouterImageGenerationSelectedModel} setOpenRouterImageApiKey={setOpenRouterImageApiKey} setImageGenerationSelectedModel={setImageGenerationSelectedModel} /> diff --git a/webview-ui/src/components/settings/ImageGenerationSettings.tsx b/webview-ui/src/components/settings/ImageGenerationSettings.tsx index d0530ffb30..c31f31e316 100644 --- a/webview-ui/src/components/settings/ImageGenerationSettings.tsx +++ b/webview-ui/src/components/settings/ImageGenerationSettings.tsx @@ -6,7 +6,7 @@ interface ImageGenerationSettingsProps { enabled: boolean onChange: (enabled: boolean) => void openRouterImageApiKey?: string - imageGenerationSelectedModel?: string + openRouterImageGenerationSelectedModel?: string setOpenRouterImageApiKey: (apiKey: string) => void setImageGenerationSelectedModel: (model: string) => void } @@ -22,20 +22,22 @@ export const ImageGenerationSettings = ({ enabled, onChange, openRouterImageApiKey, - imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel, setOpenRouterImageApiKey, setImageGenerationSelectedModel, }: ImageGenerationSettingsProps) => { const { t } = useAppTranslation() const [apiKey, setApiKey] = useState(openRouterImageApiKey || "") - const [selectedModel, setSelectedModel] = useState(imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value) + const [selectedModel, setSelectedModel] = useState( + openRouterImageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value, + ) // Update local state when props change (e.g., when switching profiles) useEffect(() => { setApiKey(openRouterImageApiKey || "") - setSelectedModel(imageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value) - }, [openRouterImageApiKey, imageGenerationSelectedModel]) + setSelectedModel(openRouterImageGenerationSelectedModel || IMAGE_GENERATION_MODELS[0].value) + }, [openRouterImageApiKey, openRouterImageGenerationSelectedModel]) // Handle API key changes const handleApiKeyChange = (value: string) => { diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index e0baaee16f..f680f3e5fe 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -182,7 +182,7 @@ const SettingsView = forwardRef(({ onDone, t maxDiagnosticMessages, includeTaskHistoryInEnhance, openRouterImageApiKey, - imageGenerationSelectedModel, + openRouterImageGenerationSelectedModel, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -272,7 +272,7 @@ const SettingsView = forwardRef(({ onDone, t const setImageGenerationSelectedModel = useCallback((model: string) => { setCachedState((prevState) => { setChangeDetected(true) - return { ...prevState, imageGenerationSelectedModel: model } + return { ...prevState, openRouterImageGenerationSelectedModel: model } }) }, []) @@ -360,7 +360,10 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) vscode.postMessage({ type: "openRouterImageApiKey", text: openRouterImageApiKey }) - vscode.postMessage({ type: "imageGenerationSelectedModel", text: imageGenerationSelectedModel }) + vscode.postMessage({ + type: "openRouterImageGenerationSelectedModel", + text: openRouterImageGenerationSelectedModel, + }) setChangeDetected(false) } } @@ -742,7 +745,9 @@ const SettingsView = forwardRef(({ onDone, t apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} openRouterImageApiKey={openRouterImageApiKey as string | undefined} - imageGenerationSelectedModel={imageGenerationSelectedModel as string | undefined} + openRouterImageGenerationSelectedModel={ + openRouterImageGenerationSelectedModel as string | undefined + } setOpenRouterImageApiKey={setOpenRouterImageApiKey} setImageGenerationSelectedModel={setImageGenerationSelectedModel} /> diff --git a/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx index f8f1f76bec..cadd8f83e0 100644 --- a/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx @@ -18,7 +18,7 @@ describe("ImageGenerationSettings", () => { enabled: false, onChange: mockOnChange, openRouterImageApiKey: undefined, - imageGenerationSelectedModel: undefined, + openRouterImageGenerationSelectedModel: undefined, setOpenRouterImageApiKey: mockSetOpenRouterImageApiKey, setImageGenerationSelectedModel: mockSetImageGenerationSelectedModel, } @@ -41,7 +41,7 @@ describe("ImageGenerationSettings", () => { , ) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index c5891a76ab..cbaf6fa045 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -253,7 +253,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode includeDiagnosticMessages: true, maxDiagnosticMessages: 50, openRouterImageApiKey: "", - imageGenerationSelectedModel: "", + openRouterImageGenerationSelectedModel: "", }) const [didHydrateState, setDidHydrateState] = useState(false)