1
0

feat: 初始化 AI Gateway 项目

实现支持 OpenAI 和 Anthropic 双协议的统一大模型 API 网关 MVP 版本,包含:
- OpenAI 和 Anthropic 协议代理
- 供应商和模型管理
- 用量统计
- 前端配置界面
This commit is contained in:
2026-04-15 16:53:28 +08:00
commit 915b004924
53 changed files with 5662 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
import { useState, useEffect } from 'react';
import * as api from '../api/client';
import { ProviderForm } from '../components/ProviderForm';
import { ModelForm } from '../components/ModelForm';
export function ProvidersPage() {
const [providers, setProviders] = useState<api.Provider[]>([]);
const [models, setModels] = useState<api.Model[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 表单状态
const [showProviderForm, setShowProviderForm] = useState(false);
const [editingProvider, setEditingProvider] = useState<api.Provider | null>(null);
const [showModelForm, setShowModelForm] = useState(false);
const [editingModel, setEditingModel] = useState<api.Model | null>(null);
useEffect(() => {
loadData();
}, []);
async function loadData() {
try {
setLoading(true);
const [providersData, modelsData] = await Promise.all([
api.listProviders(),
api.listModels(),
]);
setProviders(providersData);
setModels(modelsData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}
async function handleDeleteProvider(id: string) {
if (!confirm('确定要删除这个供应商吗?关联的模型也会被删除。')) return;
try {
await api.deleteProvider(id);
loadData();
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
}
async function handleDeleteModel(id: string) {
if (!confirm('确定要删除这个模型吗?')) return;
try {
await api.deleteModel(id);
loadData();
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
}
if (loading) return <div className="loading">...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="providers-page">
<h1></h1>
<div className="section">
<h2></h2>
<button onClick={() => {
setEditingProvider(null);
setShowProviderForm(true);
}}>
</button>
<table>
<thead>
<tr>
<th>ID</th>
<th></th>
<th>API Key</th>
<th>Base URL</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{providers.map(p => (
<tr key={p.id}>
<td>{p.id}</td>
<td>{p.name}</td>
<td>{p.api_key}</td>
<td>{p.base_url}</td>
<td>{p.enabled ? '启用' : '禁用'}</td>
<td>
<button onClick={() => {
setEditingProvider(p);
setShowProviderForm(true);
}}></button>
<button onClick={() => handleDeleteProvider(p.id)}></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="section">
<h2></h2>
<button onClick={() => {
setEditingModel(null);
setShowModelForm(true);
}}>
</button>
<table>
<thead>
<tr>
<th>ID</th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{models.map(m => {
const provider = providers.find(p => p.id === m.provider_id);
return (
<tr key={m.id}>
<td>{m.id}</td>
<td>{provider?.name || m.provider_id}</td>
<td>{m.model_name}</td>
<td>{m.enabled ? '启用' : '禁用'}</td>
<td>
<button onClick={() => {
setEditingModel(m);
setShowModelForm(true);
}}></button>
<button onClick={() => handleDeleteModel(m.id)}></button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* 供应商表单 */}
{showProviderForm && (
<ProviderForm
provider={editingProvider || undefined}
onSave={() => {
setShowProviderForm(false);
setEditingProvider(null);
loadData();
}}
onCancel={() => {
setShowProviderForm(false);
setEditingProvider(null);
}}
/>
)}
{/* 模型表单 */}
{showModelForm && (
<ModelForm
model={editingModel || undefined}
providers={providers}
onSave={() => {
setShowModelForm(false);
setEditingModel(null);
loadData();
}}
onCancel={() => {
setShowModelForm(false);
setEditingModel(null);
}}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,110 @@
import { useState, useEffect } from 'react';
import * as api from '../api/client';
export function StatsPage() {
const [stats, setStats] = useState<api.UsageStats[]>([]);
const [providers, setProviders] = useState<api.Provider[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 过滤条件
const [providerId, setProviderId] = useState('');
const [modelName, setModelName] = useState('');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
useEffect(() => {
loadData();
}, []);
async function loadData() {
try {
setLoading(true);
const [statsData, providersData] = await Promise.all([
api.getStats({
provider_id: providerId || undefined,
model_name: modelName || undefined,
start_date: startDate || undefined,
end_date: endDate || undefined,
}),
api.listProviders(),
]);
setStats(statsData);
setProviders(providersData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}
function handleFilter(e: React.FormEvent) {
e.preventDefault();
loadData();
}
if (loading) return <div className="loading">...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="stats-page">
<h1></h1>
<form onSubmit={handleFilter} className="filter-form">
<select value={providerId} onChange={e => setProviderId(e.target.value)}>
<option value=""></option>
{providers.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
))}
</select>
<input
type="text"
placeholder="模型名称"
value={modelName}
onChange={e => setModelName(e.target.value)}
/>
<input
type="date"
placeholder="开始日期"
value={startDate}
onChange={e => setStartDate(e.target.value)}
/>
<input
type="date"
placeholder="结束日期"
value={endDate}
onChange={e => setEndDate(e.target.value)}
/>
<button type="submit"></button>
</form>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{stats.map(s => {
const provider = providers.find(p => p.id === s.provider_id);
return (
<tr key={s.id}>
<td>{provider?.name || s.provider_id}</td>
<td>{s.model_name}</td>
<td>{s.date}</td>
<td>{s.request_count}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}