import { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Settings, Save, RotateCcw, Eye, EyeOff, CheckCircle2, AlertCircle, Info, Key, Zap, Globe, Database } from "lucide-react"; import { toast } from "sonner"; // LLM 提供商配置 const LLM_PROVIDERS = [ { value: 'gemini', label: 'Google Gemini', icon: '🔵', category: 'international' }, { value: 'openai', label: 'OpenAI GPT', icon: '🟢', category: 'international' }, { value: 'claude', label: 'Anthropic Claude', icon: '🟣', category: 'international' }, { value: 'deepseek', label: 'DeepSeek', icon: '🔷', category: 'international' }, { value: 'qwen', label: '阿里云通义千问', icon: '🟠', category: 'domestic' }, { value: 'zhipu', label: '智谱AI (GLM)', icon: '🔴', category: 'domestic' }, { value: 'moonshot', label: 'Moonshot (Kimi)', icon: '🌙', category: 'domestic' }, { value: 'baidu', label: '百度文心一言', icon: '🔵', category: 'domestic' }, { value: 'minimax', label: 'MiniMax', icon: '⚡', category: 'domestic' }, { value: 'doubao', label: '字节豆包', icon: '🎯', category: 'domestic' }, { value: 'ollama', label: 'Ollama 本地模型', icon: '🖥️', category: 'local' }, ]; // 默认模型配置 const DEFAULT_MODELS = { gemini: 'gemini-2.5-flash', openai: 'gpt-4o-mini', claude: 'claude-3-5-sonnet-20241022', qwen: 'qwen-turbo', deepseek: 'deepseek-chat', zhipu: 'glm-4-flash', moonshot: 'moonshot-v1-8k', baidu: 'ERNIE-3.5-8K', minimax: 'abab6.5-chat', doubao: 'doubao-pro-32k', ollama: 'llama3', }; interface SystemConfigData { // LLM 配置 llmProvider: string; llmApiKey: string; llmModel: string; llmBaseUrl: string; llmTimeout: number; llmTemperature: number; llmMaxTokens: number; // 平台专用配置 geminiApiKey: string; openaiApiKey: string; claudeApiKey: string; qwenApiKey: string; deepseekApiKey: string; zhipuApiKey: string; moonshotApiKey: string; baiduApiKey: string; minimaxApiKey: string; doubaoApiKey: string; ollamaBaseUrl: string; // GitHub 配置 githubToken: string; // 分析配置 maxAnalyzeFiles: number; llmConcurrency: number; llmGapMs: number; outputLanguage: string; } const STORAGE_KEY = 'xcodereviewer_runtime_config'; export function SystemConfig() { const [config, setConfig] = useState({ llmProvider: 'gemini', llmApiKey: '', llmModel: '', llmBaseUrl: '', llmTimeout: 150000, llmTemperature: 0.2, llmMaxTokens: 4096, geminiApiKey: '', openaiApiKey: '', claudeApiKey: '', qwenApiKey: '', deepseekApiKey: '', zhipuApiKey: '', moonshotApiKey: '', baiduApiKey: '', minimaxApiKey: '', doubaoApiKey: '', ollamaBaseUrl: 'http://localhost:11434/v1', githubToken: '', maxAnalyzeFiles: 40, llmConcurrency: 2, llmGapMs: 500, outputLanguage: 'zh-CN', }); const [showApiKeys, setShowApiKeys] = useState>({}); const [hasChanges, setHasChanges] = useState(false); const [configSource, setConfigSource] = useState<'runtime' | 'build'>('build'); // 加载配置 useEffect(() => { loadConfig(); }, []); const loadConfig = () => { try { // 尝试从 localStorage 加载运行时配置 const savedConfig = localStorage.getItem(STORAGE_KEY); if (savedConfig) { const parsedConfig = JSON.parse(savedConfig); setConfig(parsedConfig); setConfigSource('runtime'); toast.success("已加载运行时配置"); } else { // 使用构建时配置 loadFromEnv(); setConfigSource('build'); } } catch (error) { console.error('Failed to load config:', error); loadFromEnv(); } }; const loadFromEnv = () => { // 从环境变量加载(构建时配置) const envConfig: SystemConfigData = { llmProvider: import.meta.env.VITE_LLM_PROVIDER || 'gemini', llmApiKey: import.meta.env.VITE_LLM_API_KEY || '', llmModel: import.meta.env.VITE_LLM_MODEL || '', llmBaseUrl: import.meta.env.VITE_LLM_BASE_URL || '', llmTimeout: Number(import.meta.env.VITE_LLM_TIMEOUT) || 150000, llmTemperature: Number(import.meta.env.VITE_LLM_TEMPERATURE) || 0.2, llmMaxTokens: Number(import.meta.env.VITE_LLM_MAX_TOKENS) || 4096, geminiApiKey: import.meta.env.VITE_GEMINI_API_KEY || '', openaiApiKey: import.meta.env.VITE_OPENAI_API_KEY || '', claudeApiKey: import.meta.env.VITE_CLAUDE_API_KEY || '', qwenApiKey: import.meta.env.VITE_QWEN_API_KEY || '', deepseekApiKey: import.meta.env.VITE_DEEPSEEK_API_KEY || '', zhipuApiKey: import.meta.env.VITE_ZHIPU_API_KEY || '', moonshotApiKey: import.meta.env.VITE_MOONSHOT_API_KEY || '', baiduApiKey: import.meta.env.VITE_BAIDU_API_KEY || '', minimaxApiKey: import.meta.env.VITE_MINIMAX_API_KEY || '', doubaoApiKey: import.meta.env.VITE_DOUBAO_API_KEY || '', ollamaBaseUrl: import.meta.env.VITE_OLLAMA_BASE_URL || 'http://localhost:11434/v1', githubToken: import.meta.env.VITE_GITHUB_TOKEN || '', maxAnalyzeFiles: Number(import.meta.env.VITE_MAX_ANALYZE_FILES) || 40, llmConcurrency: Number(import.meta.env.VITE_LLM_CONCURRENCY) || 2, llmGapMs: Number(import.meta.env.VITE_LLM_GAP_MS) || 500, outputLanguage: import.meta.env.VITE_OUTPUT_LANGUAGE || 'zh-CN', }; setConfig(envConfig); }; const saveConfig = () => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); setHasChanges(false); setConfigSource('runtime'); toast.success("配置已保存!刷新页面后生效"); // 提示用户刷新页面 setTimeout(() => { if (window.confirm("配置已保存。是否立即刷新页面使配置生效?")) { window.location.reload(); } }, 1000); } catch (error) { console.error('Failed to save config:', error); toast.error("保存配置失败"); } }; const resetConfig = () => { if (window.confirm("确定要重置为构建时配置吗?这将清除所有运行时配置。")) { localStorage.removeItem(STORAGE_KEY); loadFromEnv(); setHasChanges(false); setConfigSource('build'); toast.success("已重置为构建时配置"); } }; const updateConfig = (key: keyof SystemConfigData, value: any) => { setConfig(prev => ({ ...prev, [key]: value })); setHasChanges(true); }; const toggleShowApiKey = (field: string) => { setShowApiKeys(prev => ({ ...prev, [field]: !prev[field] })); }; const getCurrentApiKey = () => { const provider = config.llmProvider.toLowerCase(); const keyMap: Record = { gemini: config.geminiApiKey, openai: config.openaiApiKey, claude: config.claudeApiKey, qwen: config.qwenApiKey, deepseek: config.deepseekApiKey, zhipu: config.zhipuApiKey, moonshot: config.moonshotApiKey, baidu: config.baiduApiKey, minimax: config.minimaxApiKey, doubao: config.doubaoApiKey, ollama: 'ollama', }; return config.llmApiKey || keyMap[provider] || ''; }; const isConfigured = getCurrentApiKey() !== ''; return (
{/* 配置状态提示 */}
当前配置来源: {configSource === 'runtime' ? ( 运行时配置 ) : ( 构建时配置 )} {isConfigured ? ( LLM 已配置 ) : ( 未配置 LLM )}
{hasChanges && ( )} {configSource === 'runtime' && ( )}
LLM 配置 平台密钥 分析参数 其他配置 {/* LLM 基础配置 */} LLM 提供商配置 选择和配置大语言模型服务
updateConfig('llmApiKey', e.target.value)} placeholder="留空则使用平台专用 API Key" />

如果设置,将优先使用此 API Key;否则使用下方对应平台的专用 API Key

updateConfig('llmModel', e.target.value)} placeholder={`默认:${DEFAULT_MODELS[config.llmProvider as keyof typeof DEFAULT_MODELS] || '自动'}`} />

留空使用默认模型

updateConfig('llmBaseUrl', e.target.value)} placeholder="例如:https://api.example.com/v1" />

💡 使用 API 中转站?在这里填入中转站地址

查看常见 API 中转示例

OpenAI 兼容格式:

• https://your-proxy.com/v1

• https://api.openai-proxy.org/v1

其他中转格式:

• https://your-api-gateway.com/openai

• https://custom-endpoint.com/api

⚠️ 确保中转站支持你选择的 LLM 平台

updateConfig('llmTimeout', Number(e.target.value))} />
updateConfig('llmTemperature', Number(e.target.value))} />
updateConfig('llmMaxTokens', Number(e.target.value))} />
{/* 平台专用密钥 */}

配置各平台的 API Key,方便快速切换。如果设置了通用 API Key,将优先使用通用配置。

💡 使用 API 中转站的用户注意:这里填入的应该是中转站提供的 API Key,而不是官方 Key。 中转站地址请在「LLM 配置」标签页的「API 基础 URL」中填写。

{[ { key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方:https://makersuite.google.com/app/apikey | 或使用中转站 Key' }, { key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方:https://platform.openai.com/api-keys | 或使用中转站 Key' }, { key: 'claudeApiKey', label: 'Claude API Key', icon: '🟣', hint: '官方:https://console.anthropic.com/ | 或使用中转站 Key' }, { key: 'qwenApiKey', label: '通义千问 API Key', icon: '🟠', hint: '官方:https://dashscope.console.aliyun.com/ | 或使用中转站 Key' }, { key: 'deepseekApiKey', label: 'DeepSeek API Key', icon: '🔷', hint: '官方:https://platform.deepseek.com/ | 或使用中转站 Key' }, { key: 'zhipuApiKey', label: '智谱AI API Key', icon: '🔴', hint: '官方:https://open.bigmodel.cn/ | 或使用中转站 Key' }, { key: 'moonshotApiKey', label: 'Moonshot API Key', icon: '🌙', hint: '官方:https://platform.moonshot.cn/ | 或使用中转站 Key' }, { key: 'baiduApiKey', label: '百度文心 API Key', icon: '🔵', hint: '官方格式:API_KEY:SECRET_KEY | 或使用中转站 Key' }, { key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方:https://www.minimaxi.com/ | 或使用中转站 Key' }, { key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方:https://console.volcengine.com/ark | 或使用中转站 Key' }, ].map(({ key, label, icon, hint }) => ( {icon} {label} {hint}
updateConfig(key as keyof SystemConfigData, e.target.value)} placeholder={`输入 ${label}`} />
))} 🖥️ Ollama 基础 URL 本地 Ollama 服务的 API 端点 updateConfig('ollamaBaseUrl', e.target.value)} placeholder="http://localhost:11434/v1" />
{/* 分析参数配置 */} 代码分析参数 调整代码分析的行为和性能
updateConfig('maxAnalyzeFiles', Number(e.target.value))} />

单次分析任务最多处理的文件数量

updateConfig('llmConcurrency', Number(e.target.value))} />

同时发送给 LLM 的请求数量(降低可避免速率限制)

updateConfig('llmGapMs', Number(e.target.value))} />

每个 LLM 请求之间的延迟时间

{/* 其他配置 */} GitHub 集成 配置 GitHub Personal Access Token 以访问私有仓库
updateConfig('githubToken', e.target.value)} placeholder="ghp_xxxxxxxxxxxx" />

获取:https://github.com/settings/tokens

配置说明

运行时配置

配置保存在浏览器 localStorage 中,刷新页面后立即生效。 可以在不重新构建 Docker 镜像的情况下修改配置。

配置优先级

运行时配置 > 构建时配置。如果设置了运行时配置,将覆盖构建时的环境变量。

安全提示

API Keys 存储在浏览器本地,其他网站无法访问。但清除浏览器数据会删除所有配置。

{/* 底部操作按钮 */} {hasChanges && (
)}
); }