mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 02:05:57 +08:00
feat(claude-code): role-based model mapping with display names and 1M flag
- Replace the four flat env inputs with a Sonnet/Opus/Haiku role table. Each row exposes ANTHROPIC_DEFAULT_*_MODEL plus a new display name field ANTHROPIC_DEFAULT_*_MODEL_NAME, and Sonnet/Opus gain a "Declare 1M" checkbox that toggles the [1M] suffix. - Strip the [1M] context-capability marker before forwarding non-Copilot requests upstream. Copilot keeps its existing [1m]->-1m normalization. - Claude Desktop import now consumes ANTHROPIC_DEFAULT_*_MODEL_NAME as label_override, closing the Claude Code -> Claude Desktop displayName pipeline; add_route's merge logic is shared between hashmap branches. - Unify the [1M] marker as ONE_M_CONTEXT_MARKER across claude_desktop_config and proxy::model_mapper; rename the strip helper to strip_one_m_suffix_for_upstream. - Collapse useModelState's seven duplicated useState initializers and the useEffect parse block into a single parseModelsFromConfig call. - Add tests/hooks/useModelState.test.tsx and a Claude Desktop import test covering Kimi K2 -> label_override. i18n (en/ja/zh) updated.
This commit is contained in:
@@ -5,6 +5,33 @@ interface UseModelStateProps {
|
||||
onConfigChange: (config: string) => void;
|
||||
}
|
||||
|
||||
export type ClaudeModelEnvField =
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME";
|
||||
|
||||
export const CLAUDE_ONE_M_MARKER = "[1M]";
|
||||
|
||||
export function hasClaudeOneMMarker(model: string): boolean {
|
||||
return model.trimEnd().toLowerCase().endsWith("[1m]");
|
||||
}
|
||||
|
||||
export function stripClaudeOneMMarker(model: string): string {
|
||||
const trimmedEnd = model.trimEnd();
|
||||
if (!trimmedEnd.toLowerCase().endsWith("[1m]")) return model;
|
||||
return trimmedEnd.slice(0, -CLAUDE_ONE_M_MARKER.length).trimEnd();
|
||||
}
|
||||
|
||||
export function setClaudeOneMMarker(model: string, enabled: boolean): string {
|
||||
const base = stripClaudeOneMMarker(model).trim();
|
||||
if (!base) return "";
|
||||
return enabled ? `${base}${CLAUDE_ONE_M_MARKER}` : base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse model values from settings config JSON
|
||||
*/
|
||||
@@ -22,18 +49,38 @@ function parseModelsFromConfig(settingsConfig: string) {
|
||||
typeof env.ANTHROPIC_DEFAULT_HAIKU_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_HAIKU_MODEL
|
||||
: small || model;
|
||||
const haikuName =
|
||||
typeof env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME === "string"
|
||||
? env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME
|
||||
: stripClaudeOneMMarker(haiku);
|
||||
const sonnet =
|
||||
typeof env.ANTHROPIC_DEFAULT_SONNET_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_SONNET_MODEL
|
||||
: model || small;
|
||||
const sonnetName =
|
||||
typeof env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME === "string"
|
||||
? env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME
|
||||
: stripClaudeOneMMarker(sonnet);
|
||||
const opus =
|
||||
typeof env.ANTHROPIC_DEFAULT_OPUS_MODEL === "string"
|
||||
? env.ANTHROPIC_DEFAULT_OPUS_MODEL
|
||||
: model || small;
|
||||
const opusName =
|
||||
typeof env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME === "string"
|
||||
? env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME
|
||||
: stripClaudeOneMMarker(opus);
|
||||
|
||||
return { model, haiku, sonnet, opus };
|
||||
return { model, haiku, haikuName, sonnet, sonnetName, opus, opusName };
|
||||
} catch {
|
||||
return { model: "", haiku: "", sonnet: "", opus: "" };
|
||||
return {
|
||||
model: "",
|
||||
haiku: "",
|
||||
haikuName: "",
|
||||
sonnet: "",
|
||||
sonnetName: "",
|
||||
opus: "",
|
||||
opusName: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,18 +92,19 @@ export function useModelState({
|
||||
settingsConfig,
|
||||
onConfigChange,
|
||||
}: UseModelStateProps) {
|
||||
// Initialize state by parsing config directly (fixes edit mode backfill)
|
||||
const [claudeModel, setClaudeModel] = useState(
|
||||
() => parseModelsFromConfig(settingsConfig).model,
|
||||
const initial = useState(() => parseModelsFromConfig(settingsConfig))[0];
|
||||
const [claudeModel, setClaudeModel] = useState(initial.model);
|
||||
const [defaultHaikuModel, setDefaultHaikuModel] = useState(initial.haiku);
|
||||
const [defaultHaikuModelName, setDefaultHaikuModelName] = useState(
|
||||
initial.haikuName,
|
||||
);
|
||||
const [defaultHaikuModel, setDefaultHaikuModel] = useState(
|
||||
() => parseModelsFromConfig(settingsConfig).haiku,
|
||||
const [defaultSonnetModel, setDefaultSonnetModel] = useState(initial.sonnet);
|
||||
const [defaultSonnetModelName, setDefaultSonnetModelName] = useState(
|
||||
initial.sonnetName,
|
||||
);
|
||||
const [defaultSonnetModel, setDefaultSonnetModel] = useState(
|
||||
() => parseModelsFromConfig(settingsConfig).sonnet,
|
||||
);
|
||||
const [defaultOpusModel, setDefaultOpusModel] = useState(
|
||||
() => parseModelsFromConfig(settingsConfig).opus,
|
||||
const [defaultOpusModel, setDefaultOpusModel] = useState(initial.opus);
|
||||
const [defaultOpusModelName, setDefaultOpusModelName] = useState(
|
||||
initial.opusName,
|
||||
);
|
||||
|
||||
const isUserEditingRef = useRef(false);
|
||||
@@ -65,72 +113,45 @@ export function useModelState({
|
||||
|
||||
latestConfigRef.current = settingsConfig;
|
||||
|
||||
// 初始化读取:读新键;若缺失,按兼容优先级回退
|
||||
// Haiku: DEFAULT_HAIKU || SMALL_FAST || MODEL
|
||||
// Sonnet: DEFAULT_SONNET || MODEL || SMALL_FAST
|
||||
// Opus: DEFAULT_OPUS || MODEL || SMALL_FAST
|
||||
// 仅在 settingsConfig 变化时同步一次(表单加载/切换预设时)
|
||||
// 仅在 settingsConfig 外部变化时同步(表单加载 / 切换预设);
|
||||
// 用户正在编辑时 (isUserEditingRef) 跳过一次以避免回填覆盖。
|
||||
useEffect(() => {
|
||||
if (lastConfigRef.current === settingsConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUserEditingRef.current) {
|
||||
isUserEditingRef.current = false;
|
||||
lastConfigRef.current = settingsConfig;
|
||||
return;
|
||||
}
|
||||
|
||||
lastConfigRef.current = settingsConfig;
|
||||
|
||||
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
|
||||
}
|
||||
const parsed = parseModelsFromConfig(settingsConfig);
|
||||
setClaudeModel(parsed.model);
|
||||
setDefaultHaikuModel(parsed.haiku);
|
||||
setDefaultHaikuModelName(parsed.haikuName);
|
||||
setDefaultSonnetModel(parsed.sonnet);
|
||||
setDefaultSonnetModelName(parsed.sonnetName);
|
||||
setDefaultOpusModel(parsed.opus);
|
||||
setDefaultOpusModelName(parsed.opusName);
|
||||
}, [settingsConfig]);
|
||||
|
||||
const handleModelChange = useCallback(
|
||||
(
|
||||
field:
|
||||
| "ANTHROPIC_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
|
||||
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
value: string,
|
||||
) => {
|
||||
(field: ClaudeModelEnvField, value: string) => {
|
||||
isUserEditingRef.current = true;
|
||||
|
||||
if (field === "ANTHROPIC_MODEL") setClaudeModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_HAIKU_MODEL")
|
||||
setDefaultHaikuModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME")
|
||||
setDefaultHaikuModelName(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_SONNET_MODEL")
|
||||
setDefaultSonnetModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME")
|
||||
setDefaultSonnetModelName(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_OPUS_MODEL") setDefaultOpusModel(value);
|
||||
if (field === "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME")
|
||||
setDefaultOpusModelName(value);
|
||||
|
||||
try {
|
||||
const currentConfig = latestConfigRef.current
|
||||
@@ -164,10 +185,16 @@ export function useModelState({
|
||||
setClaudeModel,
|
||||
defaultHaikuModel,
|
||||
setDefaultHaikuModel,
|
||||
defaultHaikuModelName,
|
||||
setDefaultHaikuModelName,
|
||||
defaultSonnetModel,
|
||||
setDefaultSonnetModel,
|
||||
defaultSonnetModelName,
|
||||
setDefaultSonnetModelName,
|
||||
defaultOpusModel,
|
||||
setDefaultOpusModel,
|
||||
defaultOpusModelName,
|
||||
setDefaultOpusModelName,
|
||||
handleModelChange,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user