refactor(models): migrate to granular model configuration architecture
Upgrade Claude model configuration from dual-key to quad-key system for better model tier differentiation. **Breaking Changes:** - Replace `ANTHROPIC_SMALL_FAST_MODEL` with three granular keys: - `ANTHROPIC_DEFAULT_HAIKU_MODEL` - `ANTHROPIC_DEFAULT_SONNET_MODEL` - `ANTHROPIC_DEFAULT_OPUS_MODEL` **Backend (Rust):** - Add `normalize_claude_models_in_value()` for automatic migration - Implement fallback chain: `DEFAULT_* || SMALL_FAST || MODEL` - Auto-cleanup: remove legacy `SMALL_FAST` key after normalization - Apply normalization across 6 critical paths: - Add/update provider - Read from live config - Write to live config - Refresh config snapshot **Frontend (React):** - Expand UI from 2 to 4 model input fields - Implement smart fallback in `useModelState` hook - Update `useKimiModelSelector` for Kimi model picker - Add i18n keys for Haiku/Sonnet/Opus labels (zh/en) **Configuration:** - Update all 7 provider presets to new format - DeepSeek/Qwen/Moonshot: use same model for all tiers - Zhipu: preserve tier differentiation (glm-4.5-air for Haiku) **Backward Compatibility:** - Old configs auto-upgrade on first read/write - Fallback chain ensures graceful degradation - No manual migration required Closes #[issue-number]
This commit is contained in:
@@ -112,6 +112,74 @@ mod tests {
|
||||
}
|
||||
|
||||
impl ProviderService {
|
||||
/// 归一化 Claude 模型键:读旧键(ANTHROPIC_SMALL_FAST_MODEL),写新键(DEFAULT_*), 并删除旧键
|
||||
fn normalize_claude_models_in_value(settings: &mut Value) -> bool {
|
||||
let mut changed = false;
|
||||
let env = match settings.get_mut("env") {
|
||||
Some(v) if v.is_object() => v.as_object_mut().unwrap(),
|
||||
_ => return changed,
|
||||
};
|
||||
|
||||
let model = env
|
||||
.get("ANTHROPIC_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let small_fast = env
|
||||
.get("ANTHROPIC_SMALL_FAST_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let current_haiku = env
|
||||
.get("ANTHROPIC_DEFAULT_HAIKU_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let current_sonnet = env
|
||||
.get("ANTHROPIC_DEFAULT_SONNET_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let current_opus = env
|
||||
.get("ANTHROPIC_DEFAULT_OPUS_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let target_haiku = current_haiku.or_else(|| small_fast.clone()).or_else(|| model.clone());
|
||||
let target_sonnet = current_sonnet.or_else(|| model.clone()).or_else(|| small_fast.clone());
|
||||
let target_opus = current_opus.or_else(|| model.clone()).or_else(|| small_fast.clone());
|
||||
|
||||
if env.get("ANTHROPIC_DEFAULT_HAIKU_MODEL").is_none() {
|
||||
if let Some(v) = target_haiku {
|
||||
env.insert("ANTHROPIC_DEFAULT_HAIKU_MODEL".to_string(), Value::String(v));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if env.get("ANTHROPIC_DEFAULT_SONNET_MODEL").is_none() {
|
||||
if let Some(v) = target_sonnet {
|
||||
env.insert("ANTHROPIC_DEFAULT_SONNET_MODEL".to_string(), Value::String(v));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if env.get("ANTHROPIC_DEFAULT_OPUS_MODEL").is_none() {
|
||||
if let Some(v) = target_opus {
|
||||
env.insert("ANTHROPIC_DEFAULT_OPUS_MODEL".to_string(), Value::String(v));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if env.remove("ANTHROPIC_SMALL_FAST_MODEL").is_some() {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
fn normalize_provider_if_claude(app_type: &AppType, provider: &mut Provider) {
|
||||
if matches!(app_type, AppType::Claude) {
|
||||
let mut v = provider.settings_config.clone();
|
||||
if Self::normalize_claude_models_in_value(&mut v) {
|
||||
provider.settings_config = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_transaction<R, F>(state: &AppState, f: F) -> Result<R, AppError>
|
||||
where
|
||||
F: FnOnce(&mut MultiAppConfig) -> Result<(R, Option<PostCommitAction>), AppError>,
|
||||
@@ -209,7 +277,8 @@ impl ProviderService {
|
||||
"Claude settings file missing; cannot refresh snapshot",
|
||||
));
|
||||
}
|
||||
let live_after = read_json_file::<Value>(&settings_path)?;
|
||||
let mut live_after = read_json_file::<Value>(&settings_path)?;
|
||||
let _ = Self::normalize_claude_models_in_value(&mut live_after);
|
||||
{
|
||||
let mut guard = state.config.write().map_err(AppError::from)?;
|
||||
if let Some(manager) = guard.get_manager_mut(app_type) {
|
||||
@@ -308,6 +377,9 @@ impl ProviderService {
|
||||
|
||||
/// 新增供应商
|
||||
pub fn add(state: &AppState, app_type: AppType, provider: Provider) -> Result<bool, AppError> {
|
||||
let mut provider = provider;
|
||||
// 归一化 Claude 模型键
|
||||
Self::normalize_provider_if_claude(&app_type, &mut provider);
|
||||
Self::validate_provider_settings(&app_type, &provider)?;
|
||||
|
||||
let app_type_clone = app_type.clone();
|
||||
@@ -347,6 +419,9 @@ impl ProviderService {
|
||||
app_type: AppType,
|
||||
provider: Provider,
|
||||
) -> Result<bool, AppError> {
|
||||
let mut provider = provider;
|
||||
// 归一化 Claude 模型键
|
||||
Self::normalize_provider_if_claude(&app_type, &mut provider);
|
||||
Self::validate_provider_settings(&app_type, &provider)?;
|
||||
let provider_id = provider.id.clone();
|
||||
let app_type_clone = app_type.clone();
|
||||
@@ -440,7 +515,9 @@ impl ProviderService {
|
||||
"Claude settings file is missing",
|
||||
));
|
||||
}
|
||||
read_json_file(&settings_path)?
|
||||
let mut v = read_json_file::<Value>(&settings_path)?;
|
||||
let _ = Self::normalize_claude_models_in_value(&mut v);
|
||||
v
|
||||
}
|
||||
};
|
||||
|
||||
@@ -848,7 +925,8 @@ impl ProviderService {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let live = read_json_file::<Value>(&settings_path)?;
|
||||
let mut live = read_json_file::<Value>(&settings_path)?;
|
||||
let _ = Self::normalize_claude_models_in_value(&mut live);
|
||||
if let Some(manager) = config.get_manager_mut(&AppType::Claude) {
|
||||
if let Some(current) = manager.providers.get_mut(¤t_id) {
|
||||
current.settings_config = live;
|
||||
@@ -864,7 +942,10 @@ impl ProviderService {
|
||||
std::fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?;
|
||||
}
|
||||
|
||||
write_json_file(&settings_path, &provider.settings_config)?;
|
||||
// 归一化后再写入
|
||||
let mut content = provider.settings_config.clone();
|
||||
let _ = Self::normalize_claude_models_in_value(&mut content);
|
||||
write_json_file(&settings_path, &content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -38,17 +38,29 @@ interface ClaudeFormFieldsProps {
|
||||
shouldShowKimiSelector: boolean;
|
||||
shouldShowModelSelector: boolean;
|
||||
claudeModel: string;
|
||||
claudeSmallFastModel: string;
|
||||
defaultHaikuModel: string;
|
||||
defaultSonnetModel: string;
|
||||
defaultOpusModel: string;
|
||||
onModelChange: (
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => void;
|
||||
|
||||
// Kimi Model Selector
|
||||
kimiAnthropicModel: string;
|
||||
kimiAnthropicSmallFastModel: string;
|
||||
kimiDefaultHaikuModel: string;
|
||||
kimiDefaultSonnetModel: string;
|
||||
kimiDefaultOpusModel: string;
|
||||
onKimiModelChange: (
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => void;
|
||||
|
||||
@@ -76,10 +88,14 @@ export function ClaudeFormFields({
|
||||
shouldShowKimiSelector,
|
||||
shouldShowModelSelector,
|
||||
claudeModel,
|
||||
claudeSmallFastModel,
|
||||
defaultHaikuModel,
|
||||
defaultSonnetModel,
|
||||
defaultOpusModel,
|
||||
onModelChange,
|
||||
kimiAnthropicModel,
|
||||
kimiAnthropicSmallFastModel,
|
||||
kimiDefaultHaikuModel,
|
||||
kimiDefaultSonnetModel,
|
||||
kimiDefaultOpusModel,
|
||||
onKimiModelChange,
|
||||
speedTestEndpoints,
|
||||
}: ClaudeFormFieldsProps) {
|
||||
@@ -163,19 +179,53 @@ export function ClaudeFormFields({
|
||||
{shouldShowModelSelector && (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* ANTHROPIC_MODEL */}
|
||||
{/* 主模型 */}
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="claudeModel">
|
||||
{t("providerForm.anthropicModel", {
|
||||
defaultValue: "主模型",
|
||||
})}
|
||||
{t("providerForm.anthropicModel", { defaultValue: "主模型" })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="claudeModel"
|
||||
type="text"
|
||||
value={claudeModel}
|
||||
onChange={(e) => onModelChange("ANTHROPIC_MODEL", e.target.value)}
|
||||
placeholder={t("providerForm.modelPlaceholder", {
|
||||
defaultValue: "claude-3-7-sonnet-20250219",
|
||||
})}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 默认 Haiku */}
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="claudeDefaultHaikuModel">
|
||||
{t("providerForm.anthropicDefaultHaikuModel", { defaultValue: "Haiku 默认模型" })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="claudeDefaultHaikuModel"
|
||||
type="text"
|
||||
value={defaultHaikuModel}
|
||||
onChange={(e) =>
|
||||
onModelChange("ANTHROPIC_MODEL", e.target.value)
|
||||
onModelChange("ANTHROPIC_DEFAULT_HAIKU_MODEL", e.target.value)
|
||||
}
|
||||
placeholder={t("providerForm.modelPlaceholder", {
|
||||
defaultValue: "claude-3-5-haiku-20241022",
|
||||
})}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 默认 Sonnet */}
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="claudeDefaultSonnetModel">
|
||||
{t("providerForm.anthropicDefaultSonnetModel", { defaultValue: "Sonnet 默认模型" })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="claudeDefaultSonnetModel"
|
||||
type="text"
|
||||
value={defaultSonnetModel}
|
||||
onChange={(e) =>
|
||||
onModelChange("ANTHROPIC_DEFAULT_SONNET_MODEL", e.target.value)
|
||||
}
|
||||
placeholder={t("providerForm.modelPlaceholder", {
|
||||
defaultValue: "claude-3-7-sonnet-20250219",
|
||||
@@ -184,22 +234,20 @@ export function ClaudeFormFields({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ANTHROPIC_SMALL_FAST_MODEL */}
|
||||
{/* 默认 Opus */}
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="claudeSmallFastModel">
|
||||
{t("providerForm.anthropicSmallFastModel", {
|
||||
defaultValue: "快速模型",
|
||||
})}
|
||||
<FormLabel htmlFor="claudeDefaultOpusModel">
|
||||
{t("providerForm.anthropicDefaultOpusModel", { defaultValue: "Opus 默认模型" })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="claudeSmallFastModel"
|
||||
id="claudeDefaultOpusModel"
|
||||
type="text"
|
||||
value={claudeSmallFastModel}
|
||||
value={defaultOpusModel}
|
||||
onChange={(e) =>
|
||||
onModelChange("ANTHROPIC_SMALL_FAST_MODEL", e.target.value)
|
||||
onModelChange("ANTHROPIC_DEFAULT_OPUS_MODEL", e.target.value)
|
||||
}
|
||||
placeholder={t("providerForm.smallModelPlaceholder", {
|
||||
defaultValue: "claude-3-5-haiku-20241022",
|
||||
placeholder={t("providerForm.modelPlaceholder", {
|
||||
defaultValue: "claude-3-7-opus-20250219",
|
||||
})}
|
||||
autoComplete="off"
|
||||
/>
|
||||
@@ -207,8 +255,7 @@ export function ClaudeFormFields({
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("providerForm.modelHelper", {
|
||||
defaultValue:
|
||||
"可选:指定默认使用的 Claude 模型,留空则使用系统默认。",
|
||||
defaultValue: "可选:指定默认使用的 Claude 模型,留空则使用系统默认。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
@@ -219,7 +266,9 @@ export function ClaudeFormFields({
|
||||
<KimiModelSelector
|
||||
apiKey={apiKey}
|
||||
anthropicModel={kimiAnthropicModel}
|
||||
anthropicSmallFastModel={kimiAnthropicSmallFastModel}
|
||||
defaultHaikuModel={kimiDefaultHaikuModel}
|
||||
defaultSonnetModel={kimiDefaultSonnetModel}
|
||||
defaultOpusModel={kimiDefaultOpusModel}
|
||||
onModelChange={onKimiModelChange}
|
||||
disabled={category === "official"}
|
||||
/>
|
||||
|
||||
@@ -12,9 +12,15 @@ interface KimiModel {
|
||||
interface KimiModelSelectorProps {
|
||||
apiKey: string;
|
||||
anthropicModel: string;
|
||||
anthropicSmallFastModel: string;
|
||||
defaultHaikuModel: string;
|
||||
defaultSonnetModel: string;
|
||||
defaultOpusModel: string;
|
||||
onModelChange: (
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => void;
|
||||
disabled?: boolean;
|
||||
@@ -23,7 +29,9 @@ interface KimiModelSelectorProps {
|
||||
const KimiModelSelector: React.FC<KimiModelSelectorProps> = ({
|
||||
apiKey,
|
||||
anthropicModel,
|
||||
anthropicSmallFastModel,
|
||||
defaultHaikuModel,
|
||||
defaultSonnetModel,
|
||||
defaultOpusModel,
|
||||
onModelChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
@@ -173,11 +181,19 @@ const KimiModelSelector: React.FC<KimiModelSelectorProps> = ({
|
||||
onChange={(value) => onModelChange("ANTHROPIC_MODEL", value)}
|
||||
/>
|
||||
<ModelSelect
|
||||
label={t("kimiSelector.fastModel")}
|
||||
value={anthropicSmallFastModel}
|
||||
onChange={(value) =>
|
||||
onModelChange("ANTHROPIC_SMALL_FAST_MODEL", value)
|
||||
}
|
||||
label={t("kimiSelector.haikuModel", { defaultValue: "Haiku 默认" })}
|
||||
value={defaultHaikuModel}
|
||||
onChange={(value) => onModelChange("ANTHROPIC_DEFAULT_HAIKU_MODEL", value)}
|
||||
/>
|
||||
<ModelSelect
|
||||
label={t("kimiSelector.sonnetModel", { defaultValue: "Sonnet 默认" })}
|
||||
value={defaultSonnetModel}
|
||||
onChange={(value) => onModelChange("ANTHROPIC_DEFAULT_SONNET_MODEL", value)}
|
||||
/>
|
||||
<ModelSelect
|
||||
label={t("kimiSelector.opusModel", { defaultValue: "Opus 默认" })}
|
||||
value={defaultOpusModel}
|
||||
onChange={(value) => onModelChange("ANTHROPIC_DEFAULT_OPUS_MODEL", value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -140,12 +140,17 @@ export function ProviderForm({
|
||||
},
|
||||
});
|
||||
|
||||
// 使用 Model hook
|
||||
const { claudeModel, claudeSmallFastModel, handleModelChange } =
|
||||
useModelState({
|
||||
settingsConfig: form.watch("settingsConfig"),
|
||||
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||
});
|
||||
// 使用 Model hook(新:主模型 + Haiku/Sonnet/Opus 默认模型)
|
||||
const {
|
||||
claudeModel,
|
||||
defaultHaikuModel,
|
||||
defaultSonnetModel,
|
||||
defaultOpusModel,
|
||||
handleModelChange,
|
||||
} = useModelState({
|
||||
settingsConfig: form.watch("settingsConfig"),
|
||||
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||
});
|
||||
|
||||
// 使用 Codex 配置 hook (仅 Codex 模式)
|
||||
const {
|
||||
@@ -218,7 +223,9 @@ export function ProviderForm({
|
||||
const {
|
||||
shouldShow: shouldShowKimiSelector,
|
||||
kimiAnthropicModel,
|
||||
kimiAnthropicSmallFastModel,
|
||||
kimiDefaultHaikuModel,
|
||||
kimiDefaultSonnetModel,
|
||||
kimiDefaultOpusModel,
|
||||
handleKimiModelChange,
|
||||
} = useKimiModelSelector({
|
||||
initialData,
|
||||
@@ -500,10 +507,14 @@ export function ProviderForm({
|
||||
category !== "official" && !shouldShowKimiSelector
|
||||
}
|
||||
claudeModel={claudeModel}
|
||||
claudeSmallFastModel={claudeSmallFastModel}
|
||||
defaultHaikuModel={defaultHaikuModel}
|
||||
defaultSonnetModel={defaultSonnetModel}
|
||||
defaultOpusModel={defaultOpusModel}
|
||||
onModelChange={handleModelChange}
|
||||
kimiAnthropicModel={kimiAnthropicModel}
|
||||
kimiAnthropicSmallFastModel={kimiAnthropicSmallFastModel}
|
||||
kimiDefaultHaikuModel={kimiDefaultHaikuModel}
|
||||
kimiDefaultSonnetModel={kimiDefaultSonnetModel}
|
||||
kimiDefaultOpusModel={kimiDefaultOpusModel}
|
||||
onKimiModelChange={handleKimiModelChange}
|
||||
speedTestEndpoints={speedTestEndpoints}
|
||||
/>
|
||||
|
||||
@@ -21,8 +21,9 @@ export function useKimiModelSelector({
|
||||
presetName = "",
|
||||
}: UseKimiModelSelectorProps) {
|
||||
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
||||
useState("");
|
||||
const [kimiDefaultHaikuModel, setKimiDefaultHaikuModel] = useState("");
|
||||
const [kimiDefaultSonnetModel, setKimiDefaultSonnetModel] = useState("");
|
||||
const [kimiDefaultOpusModel, setKimiDefaultOpusModel] = useState("");
|
||||
|
||||
// 判断是否显示 Kimi 模型选择器
|
||||
const shouldShowKimiSelector =
|
||||
@@ -53,12 +54,24 @@ export function useKimiModelSelector({
|
||||
typeof config.env.ANTHROPIC_MODEL === "string"
|
||||
? config.env.ANTHROPIC_MODEL
|
||||
: "";
|
||||
const smallFastModel =
|
||||
typeof config.env.ANTHROPIC_SMALL_FAST_MODEL === "string"
|
||||
? config.env.ANTHROPIC_SMALL_FAST_MODEL
|
||||
: "";
|
||||
const haiku =
|
||||
typeof config.env.ANTHROPIC_DEFAULT_HAIKU_MODEL === "string"
|
||||
? (config.env.ANTHROPIC_DEFAULT_HAIKU_MODEL as string)
|
||||
: (typeof config.env.ANTHROPIC_SMALL_FAST_MODEL === "string"
|
||||
? (config.env.ANTHROPIC_SMALL_FAST_MODEL as string)
|
||||
: model);
|
||||
const sonnet =
|
||||
typeof config.env.ANTHROPIC_DEFAULT_SONNET_MODEL === "string"
|
||||
? (config.env.ANTHROPIC_DEFAULT_SONNET_MODEL as string)
|
||||
: model;
|
||||
const opus =
|
||||
typeof config.env.ANTHROPIC_DEFAULT_OPUS_MODEL === "string"
|
||||
? (config.env.ANTHROPIC_DEFAULT_OPUS_MODEL as string)
|
||||
: model;
|
||||
setKimiAnthropicModel(model);
|
||||
setKimiAnthropicSmallFastModel(smallFastModel);
|
||||
setKimiDefaultHaikuModel(haiku);
|
||||
setKimiDefaultSonnetModel(sonnet);
|
||||
setKimiDefaultOpusModel(opus);
|
||||
}
|
||||
}
|
||||
}, [initialData]);
|
||||
@@ -66,21 +79,25 @@ export function useKimiModelSelector({
|
||||
// 处理 Kimi 模型变化
|
||||
const handleKimiModelChange = useCallback(
|
||||
(
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => {
|
||||
if (field === "ANTHROPIC_MODEL") {
|
||||
setKimiAnthropicModel(value);
|
||||
} else {
|
||||
setKimiAnthropicSmallFastModel(value);
|
||||
}
|
||||
if (field === "ANTHROPIC_MODEL") setKimiAnthropicModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_HAIKU_MODEL") setKimiDefaultHaikuModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_SONNET_MODEL") setKimiDefaultSonnetModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_OPUS_MODEL") setKimiDefaultOpusModel(value);
|
||||
|
||||
// 更新配置 JSON
|
||||
// 更新配置 JSON(只写新键并清理旧键)
|
||||
try {
|
||||
const currentConfig = JSON.parse(settingsConfig || "{}");
|
||||
if (!currentConfig.env) currentConfig.env = {};
|
||||
currentConfig.env[field] = value;
|
||||
|
||||
if (value.trim()) currentConfig.env[field] = value;
|
||||
else delete currentConfig.env[field];
|
||||
delete currentConfig.env["ANTHROPIC_SMALL_FAST_MODEL"];
|
||||
const updatedConfigString = JSON.stringify(currentConfig, null, 2);
|
||||
onConfigChange(updatedConfigString);
|
||||
} catch (err) {
|
||||
@@ -97,9 +114,16 @@ export function useKimiModelSelector({
|
||||
const config = JSON.parse(settingsConfig);
|
||||
if (config.env) {
|
||||
const model = config.env.ANTHROPIC_MODEL || "";
|
||||
const smallFastModel = config.env.ANTHROPIC_SMALL_FAST_MODEL || "";
|
||||
const haiku =
|
||||
config.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ||
|
||||
config.env.ANTHROPIC_SMALL_FAST_MODEL ||
|
||||
model || "";
|
||||
const sonnet = config.env.ANTHROPIC_DEFAULT_SONNET_MODEL || model || "";
|
||||
const opus = config.env.ANTHROPIC_DEFAULT_OPUS_MODEL || model || "";
|
||||
setKimiAnthropicModel(model);
|
||||
setKimiAnthropicSmallFastModel(smallFastModel);
|
||||
setKimiDefaultHaikuModel(haiku);
|
||||
setKimiDefaultSonnetModel(sonnet);
|
||||
setKimiDefaultOpusModel(opus);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -110,7 +134,9 @@ export function useKimiModelSelector({
|
||||
return {
|
||||
shouldShow,
|
||||
kimiAnthropicModel,
|
||||
kimiAnthropicSmallFastModel,
|
||||
kimiDefaultHaikuModel,
|
||||
kimiDefaultSonnetModel,
|
||||
kimiDefaultOpusModel,
|
||||
handleKimiModelChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
|
||||
interface UseModelStateProps {
|
||||
settingsConfig: string;
|
||||
@@ -9,39 +9,76 @@ interface UseModelStateProps {
|
||||
* 管理模型选择状态
|
||||
* 支持 ANTHROPIC_MODEL 和 ANTHROPIC_SMALL_FAST_MODEL
|
||||
*/
|
||||
export function useModelState({
|
||||
settingsConfig,
|
||||
onConfigChange,
|
||||
}: UseModelStateProps) {
|
||||
export function useModelState({ settingsConfig, onConfigChange }: UseModelStateProps) {
|
||||
const [claudeModel, setClaudeModel] = useState("");
|
||||
const [claudeSmallFastModel, setClaudeSmallFastModel] = useState("");
|
||||
const [defaultHaikuModel, setDefaultHaikuModel] = useState("");
|
||||
const [defaultSonnetModel, setDefaultSonnetModel] = useState("");
|
||||
const [defaultOpusModel, setDefaultOpusModel] = useState("");
|
||||
|
||||
// 初始化读取:读新键;若缺失,按兼容优先级回退
|
||||
// Haiku: DEFAULT_HAIKU || SMALL_FAST || MODEL
|
||||
// Sonnet: DEFAULT_SONNET || MODEL || SMALL_FAST
|
||||
// Opus: DEFAULT_OPUS || MODEL || SMALL_FAST
|
||||
// 仅在 settingsConfig 变化时同步一次(表单加载/切换预设时)
|
||||
useEffect(() => {
|
||||
try {
|
||||
const cfg = settingsConfig ? JSON.parse(settingsConfig) : {};
|
||||
const env = cfg?.env || {};
|
||||
const model = typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : "";
|
||||
const small =
|
||||
typeof env.ANTHROPIC_SMALL_FAST_MODEL === "string" ? env.ANTHROPIC_SMALL_FAST_MODEL : "";
|
||||
const haiku =
|
||||
typeof env.ANTHROPIC_DEFAULT_HAIKU_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_HAIKU_MODEL
|
||||
: small || model;
|
||||
const sonnet =
|
||||
typeof env.ANTHROPIC_DEFAULT_SONNET_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_SONNET_MODEL
|
||||
: model || small;
|
||||
const opus =
|
||||
typeof env.ANTHROPIC_DEFAULT_OPUS_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_OPUS_MODEL
|
||||
: model || small;
|
||||
|
||||
setClaudeModel(model || "");
|
||||
setDefaultHaikuModel(haiku || "");
|
||||
setDefaultSonnetModel(sonnet || "");
|
||||
setDefaultOpusModel(opus || "");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [settingsConfig]);
|
||||
|
||||
const handleModelChange = useCallback(
|
||||
(
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => {
|
||||
if (field === "ANTHROPIC_MODEL") {
|
||||
setClaudeModel(value);
|
||||
} else {
|
||||
setClaudeSmallFastModel(value);
|
||||
}
|
||||
if (field === "ANTHROPIC_MODEL") setClaudeModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_HAIKU_MODEL") setDefaultHaikuModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_SONNET_MODEL") setDefaultSonnetModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_OPUS_MODEL") setDefaultOpusModel(value);
|
||||
|
||||
try {
|
||||
const currentConfig = settingsConfig
|
||||
? JSON.parse(settingsConfig)
|
||||
: { env: {} };
|
||||
const currentConfig = settingsConfig ? JSON.parse(settingsConfig) : { env: {} };
|
||||
if (!currentConfig.env) currentConfig.env = {};
|
||||
|
||||
if (value.trim()) {
|
||||
currentConfig.env[field] = value.trim();
|
||||
// 新键仅写入;旧键不再写入
|
||||
const trimmed = value.trim();
|
||||
if (trimmed) {
|
||||
currentConfig.env[field] = trimmed;
|
||||
} else {
|
||||
delete currentConfig.env[field];
|
||||
}
|
||||
// 删除旧键
|
||||
delete currentConfig.env["ANTHROPIC_SMALL_FAST_MODEL"];
|
||||
|
||||
onConfigChange(JSON.stringify(currentConfig, null, 2));
|
||||
} catch (err) {
|
||||
// 如果 JSON 解析失败,不做处理
|
||||
console.error("Failed to update model config:", err);
|
||||
}
|
||||
},
|
||||
@@ -51,8 +88,12 @@ export function useModelState({
|
||||
return {
|
||||
claudeModel,
|
||||
setClaudeModel,
|
||||
claudeSmallFastModel,
|
||||
setClaudeSmallFastModel,
|
||||
defaultHaikuModel,
|
||||
setDefaultHaikuModel,
|
||||
defaultSonnetModel,
|
||||
setDefaultSonnetModel,
|
||||
defaultOpusModel,
|
||||
setDefaultOpusModel,
|
||||
handleModelChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ export const providerPresets: ProviderPreset[] = [
|
||||
ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "DeepSeek-V3.2-Exp",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "DeepSeek-V3.2-Exp",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "DeepSeek-V3.2-Exp",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "DeepSeek-V3.2-Exp",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "DeepSeek-V3.2-Exp",
|
||||
},
|
||||
},
|
||||
category: "cn_official",
|
||||
@@ -75,7 +77,6 @@ export const providerPresets: ProviderPreset[] = [
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
// 兼容旧键名,保持前端读取一致
|
||||
ANTHROPIC_MODEL: "GLM-4.6",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "glm-4.5-air",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.6",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.6",
|
||||
@@ -92,7 +93,9 @@ export const providerPresets: ProviderPreset[] = [
|
||||
"https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "qwen3-max",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "qwen3-max",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-max",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-max",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-max",
|
||||
},
|
||||
},
|
||||
category: "cn_official",
|
||||
@@ -105,7 +108,9 @@ export const providerPresets: ProviderPreset[] = [
|
||||
ANTHROPIC_BASE_URL: "https://api.moonshot.cn/anthropic",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "kimi-k2-turbo-preview",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "kimi-k2-turbo-preview",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-k2-turbo-preview",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-k2-turbo-preview",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-k2-turbo-preview",
|
||||
},
|
||||
},
|
||||
category: "cn_official",
|
||||
@@ -118,7 +123,9 @@ export const providerPresets: ProviderPreset[] = [
|
||||
ANTHROPIC_BASE_URL: "https://api-inference.modelscope.cn",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "ZhipuAI/GLM-4.6",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "ZhipuAI/GLM-4.6",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "ZhipuAI/GLM-4.6",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "ZhipuAI/GLM-4.6",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "ZhipuAI/GLM-4.6",
|
||||
},
|
||||
},
|
||||
category: "aggregator",
|
||||
@@ -133,7 +140,9 @@ export const providerPresets: ProviderPreset[] = [
|
||||
"https://vanchin.streamlake.ai/api/gateway/v1/endpoints/${ENDPOINT_ID}/claude-code-proxy",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "KAT-Coder",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "KAT-Coder",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "KAT-Coder",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "KAT-Coder",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "KAT-Coder",
|
||||
},
|
||||
},
|
||||
category: "cn_official",
|
||||
@@ -155,7 +164,7 @@ export const providerPresets: ProviderPreset[] = [
|
||||
ANTHROPIC_BASE_URL: "https://api.longcat.chat/anthropic",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "LongCat-Flash-Chat",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "LongCat-Flash-Chat",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "LongCat-Flash-Chat",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "LongCat-Flash-Chat",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "LongCat-Flash-Chat",
|
||||
CLAUDE_CODE_MAX_OUTPUT_TOKENS: "6000",
|
||||
|
||||
@@ -258,6 +258,9 @@
|
||||
"visitWebsite": "Visit {{url}}",
|
||||
"anthropicModel": "Main Model",
|
||||
"anthropicSmallFastModel": "Fast Model",
|
||||
"anthropicDefaultHaikuModel": "Default Haiku Model",
|
||||
"anthropicDefaultSonnetModel": "Default Sonnet Model",
|
||||
"anthropicDefaultOpusModel": "Default Opus Model",
|
||||
"modelPlaceholder": "GLM-4.6",
|
||||
"smallModelPlaceholder": "GLM-4.5-Air",
|
||||
"modelHelper": "Optional: Specify default Claude model to use, leave blank to use system default.",
|
||||
@@ -373,6 +376,9 @@
|
||||
"modelConfig": "Model Configuration",
|
||||
"mainModel": "Main Model",
|
||||
"fastModel": "Fast Model",
|
||||
"haikuModel": "Default Haiku",
|
||||
"sonnetModel": "Default Sonnet",
|
||||
"opusModel": "Default Opus",
|
||||
"refreshModels": "Refresh Model List",
|
||||
"pleaseSelectModel": "Please select a model",
|
||||
"noModels": "No models available",
|
||||
|
||||
@@ -258,6 +258,9 @@
|
||||
"visitWebsite": "访问 {{url}}",
|
||||
"anthropicModel": "主模型",
|
||||
"anthropicSmallFastModel": "快速模型",
|
||||
"anthropicDefaultHaikuModel": "Haiku 默认模型",
|
||||
"anthropicDefaultSonnetModel": "Sonnet 默认模型",
|
||||
"anthropicDefaultOpusModel": "Opus 默认模型",
|
||||
"modelPlaceholder": "GLM-4.6",
|
||||
"smallModelPlaceholder": "GLM-4.5-Air",
|
||||
"modelHelper": "可选:指定默认使用的 Claude 模型,留空则使用系统默认。",
|
||||
@@ -373,6 +376,9 @@
|
||||
"modelConfig": "模型配置",
|
||||
"mainModel": "主模型",
|
||||
"fastModel": "快速模型",
|
||||
"haikuModel": "Haiku 默认",
|
||||
"sonnetModel": "Sonnet 默认",
|
||||
"opusModel": "Opus 默认",
|
||||
"refreshModels": "刷新模型列表",
|
||||
"pleaseSelectModel": "请选择模型",
|
||||
"noModels": "暂无模型",
|
||||
|
||||
Reference in New Issue
Block a user