import { useState, useCallback, useEffect, useRef } from "react"; interface UseModelStateProps { settingsConfig: string; 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" | "ANTHROPIC_DEFAULT_FABLE_MODEL" | "ANTHROPIC_DEFAULT_FABLE_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 */ function parseModelsFromConfig(settingsConfig: string) { 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 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); // 回填链镜像运行时映射链(fable → opus → default),保证 UI 展示 // 与代理实际转发的模型一致。 const fable = typeof env.ANTHROPIC_DEFAULT_FABLE_MODEL === "string" ? env.ANTHROPIC_DEFAULT_FABLE_MODEL : opus; const fableName = typeof env.ANTHROPIC_DEFAULT_FABLE_MODEL_NAME === "string" ? env.ANTHROPIC_DEFAULT_FABLE_MODEL_NAME : stripClaudeOneMMarker(fable); return { model, haiku, haikuName, sonnet, sonnetName, opus, opusName, fable, fableName, }; } catch { return { model: "", haiku: "", haikuName: "", sonnet: "", sonnetName: "", opus: "", opusName: "", fable: "", fableName: "", }; } } /** * 管理模型选择状态 * 支持 ANTHROPIC_MODEL 和各类型默认模型 */ export function useModelState({ settingsConfig, onConfigChange, }: UseModelStateProps) { 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 [defaultSonnetModel, setDefaultSonnetModel] = useState(initial.sonnet); const [defaultSonnetModelName, setDefaultSonnetModelName] = useState( initial.sonnetName, ); const [defaultOpusModel, setDefaultOpusModel] = useState(initial.opus); const [defaultOpusModelName, setDefaultOpusModelName] = useState( initial.opusName, ); const [defaultFableModel, setDefaultFableModel] = useState(initial.fable); const [defaultFableModelName, setDefaultFableModelName] = useState( initial.fableName, ); const isUserEditingRef = useRef(false); const lastConfigRef = useRef(settingsConfig); const latestConfigRef = useRef(settingsConfig); latestConfigRef.current = settingsConfig; // 仅在 settingsConfig 外部变化时同步(表单加载 / 切换预设); // 用户正在编辑时 (isUserEditingRef) 跳过一次以避免回填覆盖。 useEffect(() => { if (lastConfigRef.current === settingsConfig) { return; } if (isUserEditingRef.current) { isUserEditingRef.current = false; lastConfigRef.current = settingsConfig; return; } lastConfigRef.current = settingsConfig; 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); setDefaultFableModel(parsed.fable); setDefaultFableModelName(parsed.fableName); }, [settingsConfig]); const handleModelChange = useCallback( (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); if (field === "ANTHROPIC_DEFAULT_FABLE_MODEL") setDefaultFableModel(value); if (field === "ANTHROPIC_DEFAULT_FABLE_MODEL_NAME") setDefaultFableModelName(value); try { const currentConfig = latestConfigRef.current ? JSON.parse(latestConfigRef.current) : { env: {} }; if (!currentConfig.env) currentConfig.env = {}; const env = currentConfig.env as Record; // 新键仅写入;旧键不再写入 const trimmed = value.trim(); if (trimmed) { env[field] = trimmed; } else { delete env[field]; } // 删除旧键 delete env["ANTHROPIC_SMALL_FAST_MODEL"]; const updatedConfig = JSON.stringify(currentConfig, null, 2); latestConfigRef.current = updatedConfig; onConfigChange(updatedConfig); } catch (err) { console.error("Failed to update model config:", err); } }, [onConfigChange], ); return { claudeModel, setClaudeModel, defaultHaikuModel, setDefaultHaikuModel, defaultHaikuModelName, setDefaultHaikuModelName, defaultSonnetModel, setDefaultSonnetModel, defaultSonnetModelName, setDefaultSonnetModelName, defaultOpusModel, setDefaultOpusModel, defaultOpusModelName, setDefaultOpusModelName, defaultFableModel, setDefaultFableModel, defaultFableModelName, setDefaultFableModelName, handleModelChange, }; }