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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user