mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 18:41:35 +08:00
feat(omo): add model/variant dropdowns from enabled providers
Replace model text inputs with Select dropdowns sourced from enabled OpenCode providers, add thinking-level variant selection, and prevent auto-enabling newly added OMO providers.
This commit is contained in:
@@ -166,10 +166,8 @@ impl ProviderService {
|
|||||||
if matches!(app_type, AppType::OpenCode) {
|
if matches!(app_type, AppType::OpenCode) {
|
||||||
// OMO providers use exclusive mode and write to dedicated config file.
|
// OMO providers use exclusive mode and write to dedicated config file.
|
||||||
if provider.category.as_deref() == Some("omo") {
|
if provider.category.as_deref() == Some("omo") {
|
||||||
state
|
// Do not auto-enable newly added OMO providers.
|
||||||
.db
|
// Users must explicitly switch/apply an OMO provider to activate it.
|
||||||
.set_omo_provider_current(app_type.as_str(), &provider.id)?;
|
|
||||||
crate::services::OmoService::write_config_to_file(state)?;
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
write_live_snapshot(&app_type, &provider)?;
|
write_live_snapshot(&app_type, &provider)?;
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Trash2,
|
Trash2,
|
||||||
@@ -34,6 +41,8 @@ const ADVANCED_PLACEHOLDER = `{
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
interface OmoFormFieldsProps {
|
interface OmoFormFieldsProps {
|
||||||
|
modelOptions: Array<{ value: string; label: string }>;
|
||||||
|
modelVariantsMap?: Record<string, string[]>;
|
||||||
agents: Record<string, Record<string, unknown>>;
|
agents: Record<string, Record<string, unknown>>;
|
||||||
onAgentsChange: (agents: Record<string, Record<string, unknown>>) => void;
|
onAgentsChange: (agents: Record<string, Record<string, unknown>>) => void;
|
||||||
categories: Record<string, Record<string, unknown>>;
|
categories: Record<string, Record<string, unknown>>;
|
||||||
@@ -49,15 +58,20 @@ type BuiltinModelDef = Pick<
|
|||||||
OmoAgentDef | OmoCategoryDef,
|
OmoAgentDef | OmoCategoryDef,
|
||||||
"key" | "display" | "descZh" | "descEn" | "recommended"
|
"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_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 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<string, unknown> | undefined): string {
|
function getAdvancedStr(config: Record<string, unknown> | undefined): string {
|
||||||
if (!config) return "";
|
if (!config) return "";
|
||||||
const adv: Record<string, unknown> = {};
|
const adv: Record<string, unknown> = {};
|
||||||
for (const [k, v] of Object.entries(config)) {
|
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) : "";
|
return Object.keys(adv).length > 0 ? JSON.stringify(adv, null, 2) : "";
|
||||||
}
|
}
|
||||||
@@ -96,6 +110,8 @@ function mergeCustomModelsIntoStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function OmoFormFields({
|
export function OmoFormFields({
|
||||||
|
modelOptions,
|
||||||
|
modelVariantsMap = {},
|
||||||
agents,
|
agents,
|
||||||
onAgentsChange,
|
onAgentsChange,
|
||||||
categories,
|
categories,
|
||||||
@@ -158,6 +174,144 @@ export function OmoFormFields({
|
|||||||
[categories, onCategoriesChange],
|
[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 (
|
||||||
|
<Select
|
||||||
|
value={currentModel || EMPTY_MODEL_VALUE}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
onChange(value === EMPTY_MODEL_VALUE ? "" : value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="flex-1 h-8 text-sm">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
placeholder || (isZh ? "选择已启用模型" : "Select enabled model")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="max-h-72">
|
||||||
|
<SelectItem value={EMPTY_MODEL_VALUE}>
|
||||||
|
{isZh ? "(清空)" : "(Clear)"}
|
||||||
|
</SelectItem>
|
||||||
|
{options.length === 0 ? (
|
||||||
|
<SelectItem value={UNAVAILABLE_MODEL_VALUE} disabled>
|
||||||
|
{isZh ? "暂无已启用模型" : "No enabled models"}
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
options.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Select
|
||||||
|
value={currentVariant || EMPTY_VARIANT_VALUE}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
onChange(value === EMPTY_VARIANT_VALUE ? "" : value)
|
||||||
|
}
|
||||||
|
disabled={!hasModel}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-32 h-8 text-xs shrink-0">
|
||||||
|
<SelectValue placeholder="variant" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="max-h-72">
|
||||||
|
<SelectItem value={EMPTY_VARIANT_VALUE}>
|
||||||
|
{isZh ? "(默认)" : "(Default)"}
|
||||||
|
</SelectItem>
|
||||||
|
{!hasModel ? (
|
||||||
|
<SelectItem value={UNAVAILABLE_VARIANT_VALUE} disabled>
|
||||||
|
{isZh ? "先选择模型" : "Select model first"}
|
||||||
|
</SelectItem>
|
||||||
|
) : variantOptions.length === 0 ? (
|
||||||
|
<SelectItem value={UNAVAILABLE_VARIANT_VALUE} disabled>
|
||||||
|
{isZh ? "该模型无思考等级" : "No variants for model"}
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
variantOptions.map((variant, index) => (
|
||||||
|
<SelectItem key={`${variant}-${index}`} value={variant}>
|
||||||
|
{firstIsUnavailable && index === 0
|
||||||
|
? isZh
|
||||||
|
? `${variant}(当前值,未启用)`
|
||||||
|
: `${variant} (current value, unavailable)`
|
||||||
|
: variant}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleModelChange = (
|
const handleModelChange = (
|
||||||
key: string,
|
key: string,
|
||||||
model: string,
|
model: string,
|
||||||
@@ -165,12 +319,25 @@ export function OmoFormFields({
|
|||||||
setter: (v: Record<string, Record<string, unknown>>) => void,
|
setter: (v: Record<string, Record<string, unknown>>) => void,
|
||||||
) => {
|
) => {
|
||||||
if (model.trim()) {
|
if (model.trim()) {
|
||||||
setter({ ...store, [key]: { ...store[key], model } });
|
const nextEntry: Record<string, unknown> = {
|
||||||
|
...(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 {
|
} else {
|
||||||
const existing = store[key];
|
const existing = store[key];
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const adv = { ...existing };
|
const adv = { ...existing };
|
||||||
delete adv.model;
|
delete adv.model;
|
||||||
|
delete adv.variant;
|
||||||
if (Object.keys(adv).length > 0) {
|
if (Object.keys(adv).length > 0) {
|
||||||
setter({ ...store, [key]: adv });
|
setter({ ...store, [key]: adv });
|
||||||
} else {
|
} else {
|
||||||
@@ -182,6 +349,31 @@ export function OmoFormFields({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVariantChange = (
|
||||||
|
key: string,
|
||||||
|
variant: string,
|
||||||
|
store: Record<string, Record<string, unknown>>,
|
||||||
|
setter: (v: Record<string, Record<string, unknown>>) => 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 = (
|
const handleAdvancedChange = (
|
||||||
key: string,
|
key: string,
|
||||||
rawJson: string,
|
rawJson: string,
|
||||||
@@ -189,9 +381,16 @@ export function OmoFormFields({
|
|||||||
setter: (v: Record<string, Record<string, unknown>>) => void,
|
setter: (v: Record<string, Record<string, unknown>>) => void,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const currentModel = (store[key]?.model as string) || "";
|
const currentModel = (store[key]?.model as string) || "";
|
||||||
|
const currentVariant = (store[key]?.variant as string) || "";
|
||||||
if (!rawJson.trim()) {
|
if (!rawJson.trim()) {
|
||||||
if (currentModel) {
|
if (currentModel || currentVariant) {
|
||||||
setter({ ...store, [key]: { model: currentModel } });
|
setter({
|
||||||
|
...store,
|
||||||
|
[key]: {
|
||||||
|
...(currentModel ? { model: currentModel } : {}),
|
||||||
|
...(currentVariant ? { variant: currentVariant } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const next = { ...store };
|
const next = { ...store };
|
||||||
delete next[key];
|
delete next[key];
|
||||||
@@ -206,11 +405,15 @@ export function OmoFormFields({
|
|||||||
parsed !== null &&
|
parsed !== null &&
|
||||||
!Array.isArray(parsed)
|
!Array.isArray(parsed)
|
||||||
) {
|
) {
|
||||||
|
const parsedAdvanced = { ...(parsed as Record<string, unknown>) };
|
||||||
|
delete parsedAdvanced.model;
|
||||||
|
delete parsedAdvanced.variant;
|
||||||
setter({
|
setter({
|
||||||
...store,
|
...store,
|
||||||
[key]: {
|
[key]: {
|
||||||
...(currentModel ? { model: currentModel } : {}),
|
...(currentModel ? { model: currentModel } : {}),
|
||||||
...parsed,
|
...(currentVariant ? { variant: currentVariant } : {}),
|
||||||
|
...parsedAdvanced,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@@ -300,7 +503,7 @@ export function OmoFormFields({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder={ADVANCED_PLACEHOLDER}
|
placeholder={ADVANCED_PLACEHOLDER}
|
||||||
className="font-mono text-xs min-h-[80px]"
|
className="font-mono text-xs min-h-[130px] py-3"
|
||||||
/>
|
/>
|
||||||
{showHint && (
|
{showHint && (
|
||||||
<p className="text-[10px] text-muted-foreground mt-1">
|
<p className="text-[10px] text-muted-foreground mt-1">
|
||||||
@@ -313,12 +516,22 @@ export function OmoFormFields({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleFillAllRecommended = () => {
|
const handleFillAllRecommended = () => {
|
||||||
|
if (modelOptions.length === 0) {
|
||||||
|
toast.warning(
|
||||||
|
isZh
|
||||||
|
? "当前没有可用的已启用模型,请先启用并配置 OpenCode 模型"
|
||||||
|
: "No enabled models available. Configure and enable OpenCode models first.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const updatedAgents = { ...agents };
|
const updatedAgents = { ...agents };
|
||||||
for (const agentDef of OMO_BUILTIN_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] = {
|
||||||
...updatedAgents[agentDef.key],
|
...updatedAgents[agentDef.key],
|
||||||
model: agentDef.recommended,
|
model: recommendedValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,10 +539,11 @@ export function OmoFormFields({
|
|||||||
|
|
||||||
const updatedCategories = { ...categories };
|
const updatedCategories = { ...categories };
|
||||||
for (const catDef of OMO_BUILTIN_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] = {
|
||||||
...updatedCategories[catDef.key],
|
...updatedCategories[catDef.key],
|
||||||
model: catDef.recommended,
|
model: recommendedValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,6 +613,7 @@ export function OmoFormFields({
|
|||||||
|
|
||||||
const key = def.key;
|
const key = def.key;
|
||||||
const currentModel = (store[key]?.model as string) || "";
|
const currentModel = (store[key]?.model as string) || "";
|
||||||
|
const currentVariant = (store[key]?.variant as string) || "";
|
||||||
const advStr = getAdvancedStr(store[key]);
|
const advStr = getAdvancedStr(store[key]);
|
||||||
const draftValue = drafts[key] ?? advStr;
|
const draftValue = drafts[key] ?? advStr;
|
||||||
const isExpanded = expanded[key] ?? false;
|
const isExpanded = expanded[key] ?? false;
|
||||||
@@ -412,14 +627,14 @@ export function OmoFormFields({
|
|||||||
{isZh ? def.descZh : def.descEn}
|
{isZh ? def.descZh : def.descEn}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
{renderModelSelect(
|
||||||
value={currentModel}
|
currentModel,
|
||||||
onChange={(e) =>
|
(value) => handleModelChange(key, value, store, setter),
|
||||||
handleModelChange(key, e.target.value, store, setter)
|
def.recommended,
|
||||||
}
|
)}
|
||||||
placeholder={def.recommended || "model-name"}
|
{renderVariantSelect(currentModel, currentVariant, (value) =>
|
||||||
className="flex-1 h-8 text-sm"
|
handleVariantChange(key, value, store, setter),
|
||||||
/>
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={isExpanded ? "secondary" : "ghost"}
|
variant={isExpanded ? "secondary" : "ghost"}
|
||||||
@@ -476,6 +691,10 @@ export function OmoFormFields({
|
|||||||
: "category key";
|
: "category key";
|
||||||
|
|
||||||
const key = item.key || `${emptyKeyPrefix}${index}`;
|
const key = item.key || `${emptyKeyPrefix}${index}`;
|
||||||
|
const currentVariant =
|
||||||
|
item.key && typeof store[item.key]?.variant === "string"
|
||||||
|
? (store[item.key]?.variant as string) || ""
|
||||||
|
: "";
|
||||||
const advStr = item.key ? getAdvancedStr(store[item.key]) : "";
|
const advStr = item.key ? getAdvancedStr(store[item.key]) : "";
|
||||||
const draftValue = drafts[key] ?? advStr;
|
const draftValue = drafts[key] ?? advStr;
|
||||||
const isExpanded = expanded[key] ?? false;
|
const isExpanded = expanded[key] ?? false;
|
||||||
@@ -499,12 +718,15 @@ export function OmoFormFields({
|
|||||||
placeholder={keyPlaceholder}
|
placeholder={keyPlaceholder}
|
||||||
className="w-32 shrink-0 h-8 text-sm text-primary"
|
className="w-32 shrink-0 h-8 text-sm text-primary"
|
||||||
/>
|
/>
|
||||||
<Input
|
{renderModelSelect(
|
||||||
value={item.model}
|
item.model,
|
||||||
onChange={(e) => updateCustom({ model: e.target.value })}
|
(value) => updateCustom({ model: value }),
|
||||||
placeholder="model-name"
|
"model-name",
|
||||||
className="flex-1 h-8 text-sm"
|
)}
|
||||||
/>
|
{renderVariantSelect(item.model, currentVariant, (value) => {
|
||||||
|
if (!item.key) return;
|
||||||
|
handleVariantChange(item.key, value, store, setter);
|
||||||
|
})}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={isExpanded ? "secondary" : "ghost"}
|
variant={isExpanded ? "secondary" : "ghost"}
|
||||||
@@ -687,6 +909,12 @@ export function OmoFormFields({
|
|||||||
{isZh
|
{isZh
|
||||||
? `已配置 ${configuredAgentCount} 个 Agent,${configuredCategoryCount} 个 Category · 点击 ⚙ 展开高级参数`
|
? `已配置 ${configuredAgentCount} 个 Agent,${configuredCategoryCount} 个 Category · 点击 ⚙ 展开高级参数`
|
||||||
: `${configuredAgentCount} agents, ${configuredCategoryCount} categories configured · Click ⚙ for advanced params`}
|
: `${configuredAgentCount} agents, ${configuredCategoryCount} categories configured · Click ⚙ for advanced params`}
|
||||||
|
<span className="ml-1">
|
||||||
|
·{" "}
|
||||||
|
{isZh
|
||||||
|
? `可选已启用模型 ${modelOptions.length} 个`
|
||||||
|
: `${modelOptions.length} enabled models available`}
|
||||||
|
</span>
|
||||||
{localFilePath && (
|
{localFilePath && (
|
||||||
<span className="ml-1 text-primary/70">
|
<span className="ml-1 text-primary/70">
|
||||||
· {isZh ? "来源:" : "from:"}{" "}
|
· {isZh ? "来源:" : "from:"}{" "}
|
||||||
|
|||||||
@@ -173,7 +173,11 @@ function TagListEditor({
|
|||||||
{values.length > 0 && (
|
{values.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{values.map((v, i) => (
|
{values.map((v, i) => (
|
||||||
<Badge key={`${v}-${i}`} variant="secondary" className="text-xs gap-1">
|
<Badge
|
||||||
|
key={`${v}-${i}`}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-xs gap-1"
|
||||||
|
>
|
||||||
{v}
|
{v}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
||||||
import type { AppId } from "@/lib/api";
|
import { providersApi, type AppId } from "@/lib/api";
|
||||||
import type {
|
import type {
|
||||||
ProviderCategory,
|
ProviderCategory,
|
||||||
ProviderMeta,
|
ProviderMeta,
|
||||||
@@ -579,6 +579,130 @@ export function ProviderForm({
|
|||||||
(k) => k !== providerId,
|
(k) => k !== providerId,
|
||||||
);
|
);
|
||||||
}, [opencodeProvidersData?.providers, providerId]);
|
}, [opencodeProvidersData?.providers, providerId]);
|
||||||
|
const [enabledOpencodeProviderIds, setEnabledOpencodeProviderIds] = useState<
|
||||||
|
string[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
if (!isOmoCategory) {
|
||||||
|
setEnabledOpencodeProviderIds([]);
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const ids = await providersApi.getOpenCodeLiveProviderIds();
|
||||||
|
if (active) {
|
||||||
|
setEnabledOpencodeProviderIds(ids);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load OpenCode live provider ids:", error);
|
||||||
|
if (active) {
|
||||||
|
setEnabledOpencodeProviderIds([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, [isOmoCategory]);
|
||||||
|
|
||||||
|
const omoModelOptions = useMemo(() => {
|
||||||
|
if (!isOmoCategory) return [];
|
||||||
|
|
||||||
|
const allProviders = opencodeProvidersData?.providers;
|
||||||
|
if (!allProviders) return [];
|
||||||
|
|
||||||
|
const enabledSet = new Set(enabledOpencodeProviderIds);
|
||||||
|
if (enabledSet.size === 0) return [];
|
||||||
|
|
||||||
|
const dedupedOptions = new Map<string, string>();
|
||||||
|
|
||||||
|
for (const [providerKey, provider] of Object.entries(allProviders)) {
|
||||||
|
if (provider.category === "omo" || !enabledSet.has(providerKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedConfig = parseOpencodeConfig(provider.settingsConfig);
|
||||||
|
for (const [modelId, model] of Object.entries(
|
||||||
|
parsedConfig.models || {},
|
||||||
|
)) {
|
||||||
|
const modelName =
|
||||||
|
typeof model.name === "string" && model.name.trim()
|
||||||
|
? model.name
|
||||||
|
: modelId;
|
||||||
|
const providerDisplayName =
|
||||||
|
typeof provider.name === "string" && provider.name.trim()
|
||||||
|
? provider.name
|
||||||
|
: providerKey;
|
||||||
|
const value = `${providerKey}/${modelId}`;
|
||||||
|
const label = `${providerDisplayName} / ${modelName} (${modelId})`;
|
||||||
|
if (!dedupedOptions.has(value)) {
|
||||||
|
dedupedOptions.set(value, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(dedupedOptions.entries())
|
||||||
|
.map(([value, label]) => ({ value, label }))
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label, "zh-CN"));
|
||||||
|
}, [
|
||||||
|
isOmoCategory,
|
||||||
|
opencodeProvidersData?.providers,
|
||||||
|
enabledOpencodeProviderIds,
|
||||||
|
]);
|
||||||
|
const omoModelVariantsMap = useMemo(() => {
|
||||||
|
const variantsMap: Record<string, string[]> = {};
|
||||||
|
if (!isOmoCategory) {
|
||||||
|
return variantsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allProviders = opencodeProvidersData?.providers;
|
||||||
|
if (!allProviders) {
|
||||||
|
return variantsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabledSet = new Set(enabledOpencodeProviderIds);
|
||||||
|
if (enabledSet.size === 0) {
|
||||||
|
return variantsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [providerKey, provider] of Object.entries(allProviders)) {
|
||||||
|
if (provider.category === "omo" || !enabledSet.has(providerKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedConfig = parseOpencodeConfig(provider.settingsConfig);
|
||||||
|
for (const [modelId, model] of Object.entries(
|
||||||
|
parsedConfig.models || {},
|
||||||
|
)) {
|
||||||
|
const rawVariants = model.variants;
|
||||||
|
if (
|
||||||
|
!rawVariants ||
|
||||||
|
typeof rawVariants !== "object" ||
|
||||||
|
Array.isArray(rawVariants)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const variantKeys = Object.keys(rawVariants).filter(Boolean);
|
||||||
|
if (variantKeys.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
variantsMap[`${providerKey}/${modelId}`] = variantKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantsMap;
|
||||||
|
}, [
|
||||||
|
isOmoCategory,
|
||||||
|
opencodeProvidersData?.providers,
|
||||||
|
enabledOpencodeProviderIds,
|
||||||
|
]);
|
||||||
|
|
||||||
const initialOmoSettings =
|
const initialOmoSettings =
|
||||||
appId === "opencode" && initialData?.category === "omo"
|
appId === "opencode" && initialData?.category === "omo"
|
||||||
@@ -1477,6 +1601,8 @@ export function ProviderForm({
|
|||||||
|
|
||||||
{appId === "opencode" && category === "omo" && (
|
{appId === "opencode" && category === "omo" && (
|
||||||
<OmoFormFields
|
<OmoFormFields
|
||||||
|
modelOptions={omoModelOptions}
|
||||||
|
modelVariantsMap={omoModelVariantsMap}
|
||||||
agents={omoAgents}
|
agents={omoAgents}
|
||||||
onAgentsChange={setOmoAgents}
|
onAgentsChange={setOmoAgents}
|
||||||
categories={omoCategories}
|
categories={omoCategories}
|
||||||
|
|||||||
Reference in New Issue
Block a user