mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 12:22:09 +08:00
feat(gemini): add config.json editor and common config functionality
Implements dual-editor pattern for Gemini providers, following the Codex architecture:
- Environment variables (.env format) editor
- Extended configuration (config.json) editor with common config support
New Components:
- GeminiConfigSections: Separate sections for env and config editing
- GeminiCommonConfigModal: Modal for editing common config snippets
New Hooks:
- useGeminiConfigState: Manages env/config separation and conversion
- Converts between .env string format and JSON object
- Validates JSON config structure
- Extracts API Key and Base URL from env
- useGeminiCommonConfig: Handles common config snippets
- Deep merge algorithm for combining configs
- Remove common config logic for toggling off
- localStorage persistence for snippets
Features:
- Format buttons for both env and config editors
- Common config toggle with deep merge/remove
- Error validation and display
- Auto-open modal on common config errors
Configuration Structure:
{
"env": {
"GOOGLE_GEMINI_BASE_URL": "https://...",
"GEMINI_API_KEY": "sk-...",
"GEMINI_MODEL": "gemini-2.5-pro"
},
"config": {
"timeout": 30000,
"maxRetries": 3
}
}
This brings Gemini providers to feature parity with Claude and Codex.
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
|
||||
interface UseGeminiConfigStateProps {
|
||||
initialData?: {
|
||||
settingsConfig?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理 Gemini 配置状态
|
||||
* Gemini 配置包含两部分:env (环境变量) 和 config (扩展配置 JSON)
|
||||
*/
|
||||
export function useGeminiConfigState({
|
||||
initialData,
|
||||
}: UseGeminiConfigStateProps) {
|
||||
const [geminiEnv, setGeminiEnvState] = useState("");
|
||||
const [geminiConfig, setGeminiConfigState] = useState("");
|
||||
const [geminiApiKey, setGeminiApiKey] = useState("");
|
||||
const [geminiBaseUrl, setGeminiBaseUrl] = useState("");
|
||||
const [envError, setEnvError] = useState("");
|
||||
const [configError, setConfigError] = useState("");
|
||||
|
||||
// 将 JSON env 对象转换为 .env 格式字符串
|
||||
const envObjToString = useCallback(
|
||||
(envObj: Record<string, unknown>): string => {
|
||||
const lines: string[] = [];
|
||||
if (typeof envObj.GOOGLE_GEMINI_BASE_URL === "string") {
|
||||
lines.push(`GOOGLE_GEMINI_BASE_URL=${envObj.GOOGLE_GEMINI_BASE_URL}`);
|
||||
}
|
||||
if (typeof envObj.GEMINI_API_KEY === "string") {
|
||||
lines.push(`GEMINI_API_KEY=${envObj.GEMINI_API_KEY}`);
|
||||
}
|
||||
if (typeof envObj.GEMINI_MODEL === "string") {
|
||||
lines.push(`GEMINI_MODEL=${envObj.GEMINI_MODEL}`);
|
||||
}
|
||||
return lines.join("\n");
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 将 .env 格式字符串转换为 JSON env 对象
|
||||
const envStringToObj = useCallback(
|
||||
(envString: string): Record<string, string> => {
|
||||
const env: Record<string, string> = {};
|
||||
const lines = envString.split("\n");
|
||||
lines.forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) return;
|
||||
const equalIndex = trimmed.indexOf("=");
|
||||
if (equalIndex > 0) {
|
||||
const key = trimmed.substring(0, equalIndex).trim();
|
||||
const value = trimmed.substring(equalIndex + 1).trim();
|
||||
env[key] = value;
|
||||
}
|
||||
});
|
||||
return env;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 初始化 Gemini 配置(编辑模式)
|
||||
useEffect(() => {
|
||||
if (!initialData) return;
|
||||
|
||||
const config = initialData.settingsConfig;
|
||||
if (typeof config === "object" && config !== null) {
|
||||
// 设置 env
|
||||
const env = (config as any).env || {};
|
||||
setGeminiEnvState(envObjToString(env));
|
||||
|
||||
// 设置 config
|
||||
const configObj = (config as any).config || {};
|
||||
setGeminiConfigState(JSON.stringify(configObj, null, 2));
|
||||
|
||||
// 提取 API Key 和 Base URL
|
||||
if (typeof env.GEMINI_API_KEY === "string") {
|
||||
setGeminiApiKey(env.GEMINI_API_KEY);
|
||||
}
|
||||
if (typeof env.GOOGLE_GEMINI_BASE_URL === "string") {
|
||||
setGeminiBaseUrl(env.GOOGLE_GEMINI_BASE_URL);
|
||||
}
|
||||
}
|
||||
}, [initialData, envObjToString]);
|
||||
|
||||
// 从 geminiEnv 中提取并同步 API Key 和 Base URL
|
||||
useEffect(() => {
|
||||
const envObj = envStringToObj(geminiEnv);
|
||||
const extractedKey = envObj.GEMINI_API_KEY || "";
|
||||
const extractedBaseUrl = envObj.GOOGLE_GEMINI_BASE_URL || "";
|
||||
|
||||
if (extractedKey !== geminiApiKey) {
|
||||
setGeminiApiKey(extractedKey);
|
||||
}
|
||||
if (extractedBaseUrl !== geminiBaseUrl) {
|
||||
setGeminiBaseUrl(extractedBaseUrl);
|
||||
}
|
||||
}, [geminiEnv, envStringToObj]);
|
||||
|
||||
// 验证 Gemini Config JSON
|
||||
const validateGeminiConfig = useCallback((value: string): string => {
|
||||
if (!value.trim()) return ""; // 空值允许
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return "";
|
||||
}
|
||||
return "Config must be a JSON object";
|
||||
} catch {
|
||||
return "Invalid JSON format";
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 设置 env
|
||||
const setGeminiEnv = useCallback((value: string) => {
|
||||
setGeminiEnvState(value);
|
||||
// .env 格式较宽松,不做严格校验
|
||||
setEnvError("");
|
||||
}, []);
|
||||
|
||||
// 设置 config (支持函数更新)
|
||||
const setGeminiConfig = useCallback(
|
||||
(value: string | ((prev: string) => string)) => {
|
||||
const newValue =
|
||||
typeof value === "function" ? value(geminiConfig) : value;
|
||||
setGeminiConfigState(newValue);
|
||||
setConfigError(validateGeminiConfig(newValue));
|
||||
},
|
||||
[geminiConfig, validateGeminiConfig],
|
||||
);
|
||||
|
||||
// 处理 Gemini API Key 输入并写回 env
|
||||
const handleGeminiApiKeyChange = useCallback(
|
||||
(key: string) => {
|
||||
const trimmed = key.trim();
|
||||
setGeminiApiKey(trimmed);
|
||||
|
||||
const envObj = envStringToObj(geminiEnv);
|
||||
envObj.GEMINI_API_KEY = trimmed;
|
||||
const newEnv = envObjToString(envObj);
|
||||
setGeminiEnv(newEnv);
|
||||
},
|
||||
[geminiEnv, envStringToObj, envObjToString, setGeminiEnv],
|
||||
);
|
||||
|
||||
// 处理 Gemini Base URL 变化
|
||||
const handleGeminiBaseUrlChange = useCallback(
|
||||
(url: string) => {
|
||||
const sanitized = url.trim().replace(/\/+$/, "");
|
||||
setGeminiBaseUrl(sanitized);
|
||||
|
||||
const envObj = envStringToObj(geminiEnv);
|
||||
envObj.GOOGLE_GEMINI_BASE_URL = sanitized;
|
||||
const newEnv = envObjToString(envObj);
|
||||
setGeminiEnv(newEnv);
|
||||
},
|
||||
[geminiEnv, envStringToObj, envObjToString, setGeminiEnv],
|
||||
);
|
||||
|
||||
// 处理 env 变化
|
||||
const handleGeminiEnvChange = useCallback(
|
||||
(value: string) => {
|
||||
setGeminiEnv(value);
|
||||
},
|
||||
[setGeminiEnv],
|
||||
);
|
||||
|
||||
// 处理 config 变化
|
||||
const handleGeminiConfigChange = useCallback(
|
||||
(value: string) => {
|
||||
setGeminiConfig(value);
|
||||
},
|
||||
[setGeminiConfig],
|
||||
);
|
||||
|
||||
// 重置配置(用于预设切换)
|
||||
const resetGeminiConfig = useCallback(
|
||||
(env: Record<string, unknown>, config: Record<string, unknown>) => {
|
||||
const envString = envObjToString(env);
|
||||
const configString = JSON.stringify(config, null, 2);
|
||||
|
||||
setGeminiEnv(envString);
|
||||
setGeminiConfig(configString);
|
||||
|
||||
// 提取 API Key 和 Base URL
|
||||
if (typeof env.GEMINI_API_KEY === "string") {
|
||||
setGeminiApiKey(env.GEMINI_API_KEY);
|
||||
} else {
|
||||
setGeminiApiKey("");
|
||||
}
|
||||
|
||||
if (typeof env.GOOGLE_GEMINI_BASE_URL === "string") {
|
||||
setGeminiBaseUrl(env.GOOGLE_GEMINI_BASE_URL);
|
||||
} else {
|
||||
setGeminiBaseUrl("");
|
||||
}
|
||||
},
|
||||
[envObjToString, setGeminiEnv, setGeminiConfig],
|
||||
);
|
||||
|
||||
return {
|
||||
geminiEnv,
|
||||
geminiConfig,
|
||||
geminiApiKey,
|
||||
geminiBaseUrl,
|
||||
envError,
|
||||
configError,
|
||||
setGeminiEnv,
|
||||
setGeminiConfig,
|
||||
handleGeminiApiKeyChange,
|
||||
handleGeminiBaseUrlChange,
|
||||
handleGeminiEnvChange,
|
||||
handleGeminiConfigChange,
|
||||
resetGeminiConfig,
|
||||
envStringToObj,
|
||||
envObjToString,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user