mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-21 00:21:25 +00:00
191 lines
6.9 KiB
TypeScript
191 lines
6.9 KiB
TypeScript
import React, { useMemo } from "react"
|
|
import { VSCodeCheckbox, VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
|
import { IMAGE_GENERATION_MODELS, type ImageGenerationProvider, getImageGenerationProvider } from "@roo-code/types"
|
|
import { useAppTranslation } from "@/i18n/TranslationContext"
|
|
|
|
interface ImageGenerationSettingsProps {
|
|
enabled: boolean
|
|
onChange: (enabled: boolean) => void
|
|
imageGenerationProvider?: ImageGenerationProvider
|
|
openRouterImageApiKey?: string
|
|
openRouterImageGenerationSelectedModel?: string
|
|
setImageGenerationProvider: (provider: ImageGenerationProvider) => void
|
|
setOpenRouterImageApiKey: (apiKey: string) => void
|
|
setImageGenerationSelectedModel: (model: string) => void
|
|
}
|
|
|
|
export const ImageGenerationSettings = ({
|
|
enabled,
|
|
onChange,
|
|
imageGenerationProvider,
|
|
openRouterImageApiKey,
|
|
openRouterImageGenerationSelectedModel,
|
|
setImageGenerationProvider,
|
|
setOpenRouterImageApiKey,
|
|
setImageGenerationSelectedModel,
|
|
}: ImageGenerationSettingsProps) => {
|
|
const { t } = useAppTranslation()
|
|
|
|
// Use shared utility for backwards compatibility logic
|
|
const currentProvider = getImageGenerationProvider(
|
|
imageGenerationProvider,
|
|
!!openRouterImageGenerationSelectedModel,
|
|
)
|
|
|
|
const availableModels = useMemo(() => {
|
|
return IMAGE_GENERATION_MODELS.filter((model) => model.provider === currentProvider)
|
|
}, [currentProvider])
|
|
|
|
// Derive the current model value - either from props or first available
|
|
const currentModel = useMemo(() => {
|
|
// If we have a stored model, verify it exists for the current provider
|
|
// (check both value and provider since some models have duplicate values)
|
|
if (openRouterImageGenerationSelectedModel) {
|
|
// Find a model that matches BOTH the value AND the current provider
|
|
const modelInfo = IMAGE_GENERATION_MODELS.find(
|
|
(m) => m.value === openRouterImageGenerationSelectedModel && m.provider === currentProvider,
|
|
)
|
|
if (modelInfo) {
|
|
return openRouterImageGenerationSelectedModel
|
|
}
|
|
}
|
|
// Otherwise use first available model for current provider
|
|
return availableModels[0]?.value || IMAGE_GENERATION_MODELS[0].value
|
|
}, [openRouterImageGenerationSelectedModel, availableModels, currentProvider])
|
|
|
|
// Handle provider changes
|
|
const handleProviderChange = (value: string) => {
|
|
const newProvider = value as ImageGenerationProvider
|
|
setImageGenerationProvider(newProvider)
|
|
|
|
// Smart model selection when switching providers:
|
|
// 1. If current model exists for new provider (same model name), keep it
|
|
// 2. Otherwise, switch to first available model for new provider
|
|
const providerModels = IMAGE_GENERATION_MODELS.filter((m) => m.provider === newProvider)
|
|
if (providerModels.length > 0) {
|
|
// Check if current model exists for new provider
|
|
const currentModelForNewProvider = providerModels.find(
|
|
(m) => m.value === openRouterImageGenerationSelectedModel,
|
|
)
|
|
if (currentModelForNewProvider) {
|
|
// Current model exists for new provider, keep it
|
|
// No need to call setImageGenerationSelectedModel since the value doesn't change
|
|
} else {
|
|
// Current model doesn't exist for new provider, switch to first available
|
|
setImageGenerationSelectedModel(providerModels[0].value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle API key changes
|
|
const handleApiKeyChange = (value: string) => {
|
|
setOpenRouterImageApiKey(value)
|
|
}
|
|
|
|
// Handle model selection changes
|
|
const handleModelChange = (value: string) => {
|
|
setImageGenerationSelectedModel(value)
|
|
}
|
|
|
|
const requiresApiKey = currentProvider === "openrouter"
|
|
const isConfigured = !requiresApiKey || (requiresApiKey && openRouterImageApiKey)
|
|
|
|
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">
|
|
{/* Provider Selection */}
|
|
<div>
|
|
<label className="block font-medium mb-1">
|
|
{t("settings:experimental.IMAGE_GENERATION.providerLabel")}
|
|
</label>
|
|
<VSCodeDropdown
|
|
value={currentProvider}
|
|
onChange={(e: any) => handleProviderChange(e.target.value)}
|
|
className="w-full">
|
|
<VSCodeOption value="roo" className="py-2 px-3">
|
|
Roo Code Cloud
|
|
</VSCodeOption>
|
|
<VSCodeOption value="openrouter" className="py-2 px-3">
|
|
OpenRouter
|
|
</VSCodeOption>
|
|
</VSCodeDropdown>
|
|
<p className="text-vscode-descriptionForeground text-xs mt-1">
|
|
{t("settings:experimental.IMAGE_GENERATION.providerDescription")}
|
|
</p>
|
|
</div>
|
|
|
|
{/* API Key Configuration (only for OpenRouter) */}
|
|
{currentProvider === "openrouter" && (
|
|
<div>
|
|
<label className="block font-medium mb-1">
|
|
{t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyLabel")}
|
|
</label>
|
|
<VSCodeTextField
|
|
value={openRouterImageApiKey || ""}
|
|
onInput={(e: any) => handleApiKeyChange(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={currentModel}
|
|
onChange={(e: any) => handleModelChange(e.target.value)}
|
|
className="w-full">
|
|
{availableModels.map((model) => (
|
|
<VSCodeOption key={model.value} value={model.value} className="py-2 px-3">
|
|
{model.label}
|
|
</VSCodeOption>
|
|
))}
|
|
</VSCodeDropdown>
|
|
<p className="text-vscode-descriptionForeground text-xs mt-1">
|
|
{t("settings:experimental.IMAGE_GENERATION.modelSelectionDescription")}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Status Message */}
|
|
{enabled && !isConfigured && (
|
|
<div className="p-2 bg-vscode-editorWarning-background text-vscode-editorWarning-foreground rounded text-sm">
|
|
{t("settings:experimental.IMAGE_GENERATION.warningMissingKey")}
|
|
</div>
|
|
)}
|
|
|
|
{enabled && isConfigured && (
|
|
<div className="p-2 bg-vscode-editorInfo-background text-vscode-editorInfo-foreground rounded text-sm">
|
|
{t("settings:experimental.IMAGE_GENERATION.successConfigured")}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|