2026-04-19-02-48-25 重构AI接口配置:多服务商底座架构、OpenAI兼容协议、动态模型切换、旧数据自动迁移
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Video, Globe, Layout, Check, Plus, X } from 'lucide-react';
|
||||
import { User, SystemSettings as ISystemSettings, Template } from '../types';
|
||||
import { User, SystemSettings as ISystemSettings, Template, DEFAULT_AI_PROVIDERS, AiProviderConfig } from '../types';
|
||||
import { storage } from '../utils/storage';
|
||||
|
||||
export default function SystemSettings() {
|
||||
@@ -11,12 +11,10 @@ export default function SystemSettings() {
|
||||
const [settings, setSettings] = useState<ISystemSettings & { frameMode?: 'uniform' | 'keep' }>({
|
||||
frameCount: 12,
|
||||
framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
|
||||
apiEndpoint: '',
|
||||
apiKey: '',
|
||||
kimiApiKey: '',
|
||||
kimiApiEndpoint: '',
|
||||
defaultTemplate: '',
|
||||
frameMode: 'uniform'
|
||||
frameMode: 'uniform',
|
||||
activeAiProvider: 'kimi',
|
||||
aiProviders: { ...DEFAULT_AI_PROVIDERS }
|
||||
});
|
||||
const [templates, setTemplates] = useState<Template[]>([]);
|
||||
const [isSaved, setIsSaved] = useState(false);
|
||||
@@ -32,6 +30,20 @@ export default function SystemSettings() {
|
||||
setCurrentUser(user);
|
||||
|
||||
const savedSettings = storage.get<ISystemSettings & { frameMode?: 'uniform' | 'keep' }>('systemSettings', {} as ISystemSettings & { frameMode?: 'uniform' | 'keep' });
|
||||
// Migrate old flat fields to new structured format
|
||||
if (!savedSettings.aiProviders) {
|
||||
const providers = { ...DEFAULT_AI_PROVIDERS };
|
||||
if ((savedSettings as any).kimiApiKey || (savedSettings as any).kimiApiEndpoint) {
|
||||
providers.kimi = {
|
||||
endpoint: (savedSettings as any).kimiApiEndpoint || providers.kimi.endpoint,
|
||||
apiKey: (savedSettings as any).kimiApiKey || '',
|
||||
modelName: 'kimi-k2-5'
|
||||
};
|
||||
}
|
||||
savedSettings.aiProviders = providers;
|
||||
savedSettings.activeAiProvider = 'kimi';
|
||||
storage.set('systemSettings', savedSettings);
|
||||
}
|
||||
const savedTemplates = storage.get<Template[]>('templates', []);
|
||||
if (savedSettings.frameCount) {
|
||||
if (!savedSettings.defaultTemplate && savedTemplates.length > 0) {
|
||||
@@ -81,30 +93,24 @@ export default function SystemSettings() {
|
||||
};
|
||||
|
||||
const testApi = async () => {
|
||||
const endpoint = settings.kimiApiEndpoint || 'https://api.moonshot.cn/v1';
|
||||
const apiKey = settings.kimiApiKey;
|
||||
if (!apiKey) {
|
||||
alert('请先输入 Kimi API Key');
|
||||
const provider = settings.aiProviders[settings.activeAiProvider];
|
||||
if (!provider?.apiKey) {
|
||||
alert('请先输入 API 密钥');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${endpoint.replace(/\/$/, '')}/models`, {
|
||||
const res = await fetch(`${provider.endpoint}/models`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers: { 'Authorization': `Bearer ${provider.apiKey}`, 'Content-Type': 'application/json' }
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const modelCount = data?.data?.length || 0;
|
||||
alert(`Kimi API 连接成功!\nEndpoint: ${endpoint}\n可用模型数: ${modelCount}`);
|
||||
alert(`连接成功!可用模型数: ${data.data?.length || '未知'}`);
|
||||
} else {
|
||||
const text = await res.text();
|
||||
alert(`Kimi API 连接失败 (HTTP ${res.status})\n${text}`);
|
||||
alert(`连接失败: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
alert(`Kimi API 连接异常: ${err?.message || String(err)}`);
|
||||
} catch (e: any) {
|
||||
alert(`连接失败: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,12 +119,10 @@ export default function SystemSettings() {
|
||||
const defaultSettings: ISystemSettings & { frameMode?: 'uniform' | 'keep' } = {
|
||||
frameCount: 12,
|
||||
framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
|
||||
apiEndpoint: '',
|
||||
apiKey: '',
|
||||
kimiApiKey: '',
|
||||
kimiApiEndpoint: '',
|
||||
defaultTemplate: templates[0]?.id || '',
|
||||
frameMode: 'uniform',
|
||||
activeAiProvider: 'kimi',
|
||||
aiProviders: { ...DEFAULT_AI_PROVIDERS },
|
||||
autoInsertFrames: true,
|
||||
autoInsertDelay: 1,
|
||||
autoInsertFrameIndices: [0, 2, 4, 6, 8, 10]
|
||||
@@ -312,47 +316,63 @@ export default function SystemSettings() {
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">报告生成 API 接口 (Endpoint)</label>
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">AI 服务商</label>
|
||||
<select
|
||||
value={settings.activeAiProvider}
|
||||
onChange={(e) => setSettings({ ...settings, activeAiProvider: e.target.value })}
|
||||
className="input-minimal bg-white"
|
||||
>
|
||||
<option value="kimi">Kimi (Moonshot)</option>
|
||||
<option value="deepseek">DeepSeek</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">接口地址 (Base URL)</label>
|
||||
<input
|
||||
type="url"
|
||||
value={settings.apiEndpoint}
|
||||
onChange={(e) => setSettings({ ...settings, apiEndpoint: e.target.value })}
|
||||
placeholder="https://api.example.com/v1/generate"
|
||||
value={settings.aiProviders[settings.activeAiProvider]?.endpoint || ''}
|
||||
onChange={(e) => {
|
||||
const next = { ...settings.aiProviders };
|
||||
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], endpoint: e.target.value };
|
||||
setSettings({ ...settings, aiProviders: next });
|
||||
}}
|
||||
placeholder="https://api.example.com/v1"
|
||||
className="input-minimal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">API 密钥 (Secret Key)</label>
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">API 密钥</label>
|
||||
<input
|
||||
type="password"
|
||||
value={settings.apiKey}
|
||||
onChange={(e) => setSettings({ ...settings, apiKey: e.target.value })}
|
||||
value={settings.aiProviders[settings.activeAiProvider]?.apiKey || ''}
|
||||
onChange={(e) => {
|
||||
const next = { ...settings.aiProviders };
|
||||
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], apiKey: e.target.value };
|
||||
setSettings({ ...settings, aiProviders: next });
|
||||
}}
|
||||
placeholder="sk-xxxxxxxxxxxxxxxx"
|
||||
className="input-minimal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">Kimi API Endpoint</label>
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">模型名称 (Model Name)</label>
|
||||
<input
|
||||
type="url"
|
||||
value={settings.kimiApiEndpoint}
|
||||
onChange={(e) => setSettings({ ...settings, kimiApiEndpoint: e.target.value })}
|
||||
placeholder="https://api.moonshot.cn/v1"
|
||||
className="input-minimal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-bold text-text-main uppercase tracking-wider">Kimi API Key</label>
|
||||
<input
|
||||
type="password"
|
||||
value={settings.kimiApiKey}
|
||||
onChange={(e) => setSettings({ ...settings, kimiApiKey: e.target.value })}
|
||||
placeholder="sk-..."
|
||||
type="text"
|
||||
value={settings.aiProviders[settings.activeAiProvider]?.modelName || ''}
|
||||
onChange={(e) => {
|
||||
const next = { ...settings.aiProviders };
|
||||
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], modelName: e.target.value };
|
||||
setSettings({ ...settings, aiProviders: next });
|
||||
}}
|
||||
placeholder="kimi-k2-5"
|
||||
className="input-minimal"
|
||||
/>
|
||||
<p className="text-[11px] text-text-muted">当前服务商使用的具体模型版本,可随时修改以切换模型。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user