feat: replace text inputs with dropdown selects for OpenClaw agent model config

- Add useOpenClawModelOptions hook to aggregate models from all configured OpenClaw providers
- Replace read-only primary model display with a searchable Select dropdown
- Replace comma-separated fallback text input with add/remove Select rows
- Filter out already-selected models from fallback options
- Show "(not configured)" marker for values whose provider has been deleted
- Unify terminology: rename "主模型/Primary Model" to "默认模型/Default Model"
This commit is contained in:
Jason
2026-03-04 15:48:00 +08:00
parent 7d4ffa9872
commit 4e0f9d9552
5 changed files with 259 additions and 37 deletions
@@ -0,0 +1,62 @@
import { useMemo } from "react";
import { useProvidersQuery } from "@/lib/query/queries";
import type { OpenClawProviderConfig } from "@/types";
export interface ModelOption {
value: string; // "providerId/modelId"
label: string; // "Provider Name / Model Name"
}
export function useOpenClawModelOptions(): {
options: ModelOption[];
isLoading: boolean;
} {
const { data: providersData, isLoading } = useProvidersQuery("openclaw");
const options = useMemo<ModelOption[]>(() => {
const allProviders = providersData?.providers;
if (!allProviders) return [];
const dedupedOptions = new Map<string, string>();
for (const [providerKey, provider] of Object.entries(allProviders)) {
let config: OpenClawProviderConfig;
try {
config =
typeof provider.settingsConfig === "string"
? (JSON.parse(provider.settingsConfig) as OpenClawProviderConfig)
: (provider.settingsConfig as OpenClawProviderConfig);
} catch {
continue;
}
const models = config.models;
if (!Array.isArray(models)) continue;
const providerDisplayName =
typeof provider.name === "string" && provider.name.trim()
? provider.name
: providerKey;
for (const model of models) {
if (!model.id) continue;
const value = `${providerKey}/${model.id}`;
const modelDisplayName =
typeof model.name === "string" && model.name.trim()
? model.name
: model.id;
const label = `${providerDisplayName} / ${modelDisplayName}`;
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"));
}, [providersData?.providers]);
return { options, isLoading };
}