diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 4f9bf512c..c5f6abef7 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -166,10 +166,8 @@ impl ProviderService { if matches!(app_type, AppType::OpenCode) { // OMO providers use exclusive mode and write to dedicated config file. if provider.category.as_deref() == Some("omo") { - state - .db - .set_omo_provider_current(app_type.as_str(), &provider.id)?; - crate::services::OmoService::write_config_to_file(state)?; + // Do not auto-enable newly added OMO providers. + // Users must explicitly switch/apply an OMO provider to activate it. return Ok(true); } write_live_snapshot(&app_type, &provider)?; diff --git a/src/components/providers/forms/OmoFormFields.tsx b/src/components/providers/forms/OmoFormFields.tsx index f31013311..a98545c60 100644 --- a/src/components/providers/forms/OmoFormFields.tsx +++ b/src/components/providers/forms/OmoFormFields.tsx @@ -5,6 +5,13 @@ import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Plus, Trash2, @@ -34,6 +41,8 @@ const ADVANCED_PLACEHOLDER = `{ }`; interface OmoFormFieldsProps { + modelOptions: Array<{ value: string; label: string }>; + modelVariantsMap?: Record; agents: Record>; onAgentsChange: (agents: Record>) => void; categories: Record>; @@ -49,15 +58,20 @@ type BuiltinModelDef = Pick< OmoAgentDef | OmoCategoryDef, "key" | "display" | "descZh" | "descEn" | "recommended" >; +type ModelOption = { value: string; label: string }; const BUILTIN_AGENT_KEYS = new Set(OMO_BUILTIN_AGENTS.map((a) => a.key)); const BUILTIN_CATEGORY_KEYS = new Set(OMO_BUILTIN_CATEGORIES.map((c) => c.key)); +const EMPTY_MODEL_VALUE = "__cc_switch_omo_model_empty__"; +const UNAVAILABLE_MODEL_VALUE = "__cc_switch_omo_model_unavailable__"; +const EMPTY_VARIANT_VALUE = "__cc_switch_omo_variant_empty__"; +const UNAVAILABLE_VARIANT_VALUE = "__cc_switch_omo_variant_unavailable__"; function getAdvancedStr(config: Record | undefined): string { if (!config) return ""; const adv: Record = {}; for (const [k, v] of Object.entries(config)) { - if (k !== "model") adv[k] = v; + if (k !== "model" && k !== "variant") adv[k] = v; } return Object.keys(adv).length > 0 ? JSON.stringify(adv, null, 2) : ""; } @@ -96,6 +110,8 @@ function mergeCustomModelsIntoStore( } export function OmoFormFields({ + modelOptions, + modelVariantsMap = {}, agents, onAgentsChange, categories, @@ -158,6 +174,144 @@ export function OmoFormFields({ [categories, onCategoriesChange], ); + const buildEffectiveModelOptions = useCallback( + (currentModel: string): ModelOption[] => { + if (!currentModel) return modelOptions; + if (modelOptions.some((item) => item.value === currentModel)) { + return modelOptions; + } + return [ + { + value: currentModel, + label: isZh + ? `${currentModel}(当前值,未启用)` + : `${currentModel} (current value, not enabled)`, + }, + ...modelOptions, + ]; + }, + [isZh, modelOptions], + ); + + const resolveRecommendedModel = useCallback( + (recommended?: string): string | undefined => { + if (!recommended || modelOptions.length === 0) return undefined; + + const exact = modelOptions.find((item) => item.value === recommended); + if (exact) return exact.value; + + const bySuffix = modelOptions.find((item) => + item.value.endsWith(`/${recommended}`), + ); + return bySuffix?.value; + }, + [modelOptions], + ); + + const renderModelSelect = ( + currentModel: string, + onChange: (value: string) => void, + placeholder?: string, + ) => { + const options = buildEffectiveModelOptions(currentModel); + return ( + + ); + }; + + const buildEffectiveVariantOptions = useCallback( + (currentModel: string, currentVariant: string): string[] => { + const variantKeys = modelVariantsMap[currentModel] || []; + if (!currentVariant || variantKeys.includes(currentVariant)) { + return variantKeys; + } + return [currentVariant, ...variantKeys]; + }, + [modelVariantsMap], + ); + + const renderVariantSelect = ( + currentModel: string, + currentVariant: string, + onChange: (value: string) => void, + ) => { + const variantOptions = buildEffectiveVariantOptions( + currentModel, + currentVariant, + ); + const hasModel = Boolean(currentModel); + const firstIsUnavailable = + Boolean(currentVariant) && + !(modelVariantsMap[currentModel] || []).includes(currentVariant); + + return ( + + ); + }; + const handleModelChange = ( key: string, model: string, @@ -165,12 +319,25 @@ export function OmoFormFields({ setter: (v: Record>) => void, ) => { if (model.trim()) { - setter({ ...store, [key]: { ...store[key], model } }); + const nextEntry: Record = { + ...(store[key] || {}), + model, + }; + const currentVariant = + typeof nextEntry.variant === "string" ? nextEntry.variant : ""; + if (currentVariant) { + const validVariants = modelVariantsMap[model] || []; + if (!validVariants.includes(currentVariant)) { + delete nextEntry.variant; + } + } + setter({ ...store, [key]: nextEntry }); } else { const existing = store[key]; if (existing) { const adv = { ...existing }; delete adv.model; + delete adv.variant; if (Object.keys(adv).length > 0) { setter({ ...store, [key]: adv }); } else { @@ -182,6 +349,31 @@ export function OmoFormFields({ } }; + const handleVariantChange = ( + key: string, + variant: string, + store: Record>, + setter: (v: Record>) => void, + ) => { + const existing = store[key]; + if (variant.trim()) { + setter({ ...store, [key]: { ...existing, variant } }); + return; + } + + if (!existing) return; + const nextEntry = { ...existing }; + delete nextEntry.variant; + if (Object.keys(nextEntry).length > 0) { + setter({ ...store, [key]: nextEntry }); + return; + } + + const next = { ...store }; + delete next[key]; + setter(next); + }; + const handleAdvancedChange = ( key: string, rawJson: string, @@ -189,9 +381,16 @@ export function OmoFormFields({ setter: (v: Record>) => void, ): boolean => { const currentModel = (store[key]?.model as string) || ""; + const currentVariant = (store[key]?.variant as string) || ""; if (!rawJson.trim()) { - if (currentModel) { - setter({ ...store, [key]: { model: currentModel } }); + if (currentModel || currentVariant) { + setter({ + ...store, + [key]: { + ...(currentModel ? { model: currentModel } : {}), + ...(currentVariant ? { variant: currentVariant } : {}), + }, + }); } else { const next = { ...store }; delete next[key]; @@ -206,11 +405,15 @@ export function OmoFormFields({ parsed !== null && !Array.isArray(parsed) ) { + const parsedAdvanced = { ...(parsed as Record) }; + delete parsedAdvanced.model; + delete parsedAdvanced.variant; setter({ ...store, [key]: { ...(currentModel ? { model: currentModel } : {}), - ...parsed, + ...(currentVariant ? { variant: currentVariant } : {}), + ...parsedAdvanced, }, }); return true; @@ -300,7 +503,7 @@ export function OmoFormFields({ } }} placeholder={ADVANCED_PLACEHOLDER} - className="font-mono text-xs min-h-[80px]" + className="font-mono text-xs min-h-[130px] py-3" /> {showHint && (

@@ -313,12 +516,22 @@ export function OmoFormFields({ ); const handleFillAllRecommended = () => { + if (modelOptions.length === 0) { + toast.warning( + isZh + ? "当前没有可用的已启用模型,请先启用并配置 OpenCode 模型" + : "No enabled models available. Configure and enable OpenCode models first.", + ); + return; + } + const updatedAgents = { ...agents }; for (const agentDef of OMO_BUILTIN_AGENTS) { - if (agentDef.recommended && !updatedAgents[agentDef.key]?.model) { + const recommendedValue = resolveRecommendedModel(agentDef.recommended); + if (recommendedValue && !updatedAgents[agentDef.key]?.model) { updatedAgents[agentDef.key] = { ...updatedAgents[agentDef.key], - model: agentDef.recommended, + model: recommendedValue, }; } } @@ -326,10 +539,11 @@ export function OmoFormFields({ const updatedCategories = { ...categories }; for (const catDef of OMO_BUILTIN_CATEGORIES) { - if (catDef.recommended && !updatedCategories[catDef.key]?.model) { + const recommendedValue = resolveRecommendedModel(catDef.recommended); + if (recommendedValue && !updatedCategories[catDef.key]?.model) { updatedCategories[catDef.key] = { ...updatedCategories[catDef.key], - model: catDef.recommended, + model: recommendedValue, }; } } @@ -399,6 +613,7 @@ export function OmoFormFields({ const key = def.key; const currentModel = (store[key]?.model as string) || ""; + const currentVariant = (store[key]?.variant as string) || ""; const advStr = getAdvancedStr(store[key]); const draftValue = drafts[key] ?? advStr; const isExpanded = expanded[key] ?? false; @@ -412,14 +627,14 @@ export function OmoFormFields({ {isZh ? def.descZh : def.descEn} - - handleModelChange(key, e.target.value, store, setter) - } - placeholder={def.recommended || "model-name"} - className="flex-1 h-8 text-sm" - /> + {renderModelSelect( + currentModel, + (value) => handleModelChange(key, value, store, setter), + def.recommended, + )} + {renderVariantSelect(currentModel, currentVariant, (value) => + handleVariantChange(key, value, store, setter), + )}