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:
Jason
2025-11-02 18:02:22 +08:00
parent 2ebe34810c
commit 4811aa2dcd
9 changed files with 336 additions and 91 deletions

View File

@@ -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(&current_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(())
}

View File

@@ -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"}
/>

View File

@@ -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>

View File

@@ -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}
/>

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "暂无模型",