feat(admin): 添加网络代理配置和钱包交易功能

- 在管理面板中集成网络代理配置显示和平台代理设置
- 添加钱包摘要和交易列表API接口及数据管理
- 实现SSE流式响应中的错误处理机制
- 添加全局HTTP代理环境变量配置支持
- 更新平台表单以支持代理模式选择和自定义代理地址
- 集成钱包交易查询过滤和分页功能
- 优化API错误详情解析和显示格式
This commit is contained in:
2026-05-11 23:02:10 +08:00
parent c992f1de60
commit f550c0acd5
30 changed files with 1455 additions and 76 deletions
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState, type FormEvent, type ReactNode } from 'react';
import { Boxes, CheckCircle2, KeyRound, Pencil, Plus, RotateCcw, Search, ServerCog, ShieldCheck, SlidersHorizontal, Trash2, X } from 'lucide-react';
import { Boxes, CheckCircle2, Globe2, KeyRound, Pencil, Plus, RotateCcw, Search, ServerCog, ShieldCheck, SlidersHorizontal, Trash2, X } from 'lucide-react';
import type { BaseModelCatalogItem, CatalogProvider, IntegrationPlatform, PlatformModel, PricingRuleSet } from '@easyai-ai-gateway/contracts';
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, EmptyState, FormDialog, Input, Label, ScreenMessage, Select, Table, TableCell, TableHead, TableRow } from '../../components/ui';
import type { LoadState, PlatformWithModelsInput } from '../../types';
@@ -23,6 +23,7 @@ import { ModelCatalogCard } from './ModelCatalogCard';
export function PlatformManagementPanel(props: {
baseModels: BaseModelCatalogItem[];
message: string;
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
platforms: IntegrationPlatform[];
platformModels: PlatformModel[];
pricingRuleSets: PricingRuleSet[];
@@ -37,6 +38,7 @@ export function PlatformManagementPanel(props: {
const [modelQuery, setModelQuery] = useState('');
const [selectedPlatformId, setSelectedPlatformId] = useState('');
const [validationMessage, setValidationMessage] = useState('');
const [globalProxyNoticeOpen, setGlobalProxyNoticeOpen] = useState(false);
const [editingPlatform, setEditingPlatform] = useState<IntegrationPlatform | null>(null);
const [pendingDeletePlatform, setPendingDeletePlatform] = useState<IntegrationPlatform | null>(null);
const providerMap = useMemo(() => new Map(props.providers.map((item) => [item.providerKey, item])), [props.providers]);
@@ -109,6 +111,17 @@ export function PlatformManagementPanel(props: {
});
}
function updateProxyMode(proxyMode: PlatformWizardForm['proxyMode']) {
if (proxyMode === 'global') {
setGlobalProxyNoticeOpen(true);
}
setForm({
...form,
proxyMode,
httpProxy: proxyMode === 'custom' ? form.httpProxy : '',
});
}
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const validationMessage = validatePlatformForm(form, selectedModels.length, { allowEmptyCredentials: Boolean(editingPlatform) });
@@ -254,6 +267,30 @@ export function PlatformManagementPanel(props: {
/>
</FormSection>
<FormSection icon={<Globe2 size={16} />} title="网络代理">
<Label>
<Select
value={form.proxyMode}
onChange={(event) => updateProxyMode(event.target.value as PlatformWizardForm['proxyMode'])}
>
<option value="none">使</option>
<option value="global">使</option>
<option value="custom"></option>
</Select>
</Label>
{form.proxyMode === 'custom' && (
<Label>
HTTP
<Input
value={form.httpProxy}
placeholder="http://127.0.0.1:7890"
onChange={(event) => setForm({ ...form, httpProxy: event.target.value })}
/>
</Label>
)}
</FormSection>
<FormSection icon={<SlidersHorizontal size={16} />} title="路由与计费">
<Label>
@@ -296,6 +333,19 @@ export function PlatformManagementPanel(props: {
/>
</FormSection>
</FormDialog>
<ConfirmDialog
cancelLabel="关闭"
confirmLabel="知道了"
confirmVariant="default"
description="使用全局代理前,请确认网关服务已设置 AI_GATEWAY_GLOBAL_HTTP_PROXY 或 GLOBAL_HTTP_PROXY 环境变量;也兼容 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY。修改环境变量后需要重启服务才会生效。"
loading={props.state === 'loading'}
open={globalProxyNoticeOpen}
title="使用全局代理"
onCancel={() => setGlobalProxyNoticeOpen(false)}
onConfirm={() => setGlobalProxyNoticeOpen(false)}
>
<p className="platformProxyStatus">{globalProxyStatusText(props.networkProxyConfig)}</p>
</ConfirmDialog>
<ConfirmDialog
confirmLabel="删除平台"
description="已绑定的模型会一并删除,删除后不可恢复。"
@@ -868,6 +918,7 @@ function platformToForm(
const config = platform.config ?? {};
const retryPolicy = platform.retryPolicy ?? {};
const rateLimitPolicy = platform.rateLimitPolicy ?? {};
const networkProxy = readNetworkProxyConfig(config);
const currentModels = platformModels.filter((model) => model.platformId === platform.id);
return {
...createEmptyPlatformForm(platform.provider, defaults),
@@ -892,6 +943,8 @@ function platformToForm(
tpmLimit: readLimit(rateLimitPolicy, 'tpm_total'),
concurrencyLimit: readLimit(rateLimitPolicy, 'concurrent'),
testMode: readBoolean(config, 'testMode', false),
proxyMode: networkProxy.proxyMode,
httpProxy: networkProxy.httpProxy,
supportBase64Input: readBoolean(config, 'supportBase64Input', true),
supportUrlInput: readBoolean(config, 'supportUrlInput', true),
selectedModelIds: platformModelBaseIds(platform, baseModels, currentModels),
@@ -969,6 +1022,16 @@ function readBoolean(source: Record<string, unknown>, key: string, fallback: boo
return typeof value === 'boolean' ? value : fallback;
}
function readNetworkProxyConfig(config: Record<string, unknown>): Pick<PlatformWizardForm, 'proxyMode' | 'httpProxy'> {
const networkProxy = readRecord(config.networkProxy);
const mode = readString(networkProxy.mode || config.proxyMode);
const httpProxy = readString(networkProxy.httpProxy || config.httpProxy);
if (mode === 'global' || mode === 'custom') {
return { proxyMode: mode, httpProxy };
}
return { proxyMode: 'none', httpProxy: '' };
}
function readNumber(source: Record<string, unknown>, key: string) {
const value = source[key];
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
@@ -1056,7 +1119,19 @@ function platformRuntimeSummary(platform: IntegrationPlatform) {
const retryPolicy = platform.retryPolicy ?? {};
const retryEnabled = readBoolean(retryPolicy, 'enabled', true);
const maxAttempts = readNumber(retryPolicy, 'maxAttempts') ?? 2;
return `优先级 ${platform.priority} · ${retryEnabled ? `最多重试 ${maxAttempts}` : '不重试'}`;
return `优先级 ${platform.priority} · ${retryEnabled ? `最多重试 ${maxAttempts}` : '不重试'} · ${proxyModeText(readNetworkProxyConfig(platform.config ?? {}).proxyMode)}`;
}
function proxyModeText(mode: PlatformWizardForm['proxyMode']) {
if (mode === 'global') return '全局代理';
if (mode === 'custom') return '自定义代理';
return '直连';
}
function globalProxyStatusText(config: { globalHttpProxy?: string; globalHttpProxySet: boolean } | null) {
if (!config) return '读取中';
const proxy = config.globalHttpProxy?.trim() ?? '';
return config.globalHttpProxySet && proxy ? proxy : '未设置';
}
function formatLimit(value: number) {