feat(admin): 添加网络代理配置和钱包交易功能
- 在管理面板中集成网络代理配置显示和平台代理设置 - 添加钱包摘要和交易列表API接口及数据管理 - 实现SSE流式响应中的错误处理机制 - 添加全局HTTP代理环境变量配置支持 - 更新平台表单以支持代理模式选择和自定义代理地址 - 集成钱包交易查询过滤和分页功能 - 优化API错误详情解析和显示格式
This commit is contained in:
+91
-5
@@ -7,11 +7,14 @@ import type {
|
||||
GatewayAccessRuleUpsertRequest,
|
||||
GatewayApiKey,
|
||||
GatewayAuditLog,
|
||||
GatewayNetworkProxyConfig,
|
||||
GatewayTenantUpsertRequest,
|
||||
GatewayTask,
|
||||
GatewayUserUpsertRequest,
|
||||
GatewayTenant,
|
||||
GatewayUser,
|
||||
GatewayWalletAccount,
|
||||
GatewayWalletTransaction,
|
||||
IntegrationPlatform,
|
||||
ModelCatalogResponse,
|
||||
PlatformModel,
|
||||
@@ -39,9 +42,11 @@ import {
|
||||
deleteTenant,
|
||||
deleteUserGroup,
|
||||
getHealth,
|
||||
getNetworkProxyConfig,
|
||||
getTask,
|
||||
listAuditLogs,
|
||||
getWalletSummary,
|
||||
listAccessRules,
|
||||
listAuditLogs,
|
||||
listApiKeyAccessRules,
|
||||
listApiKeys,
|
||||
listBaseModels,
|
||||
@@ -55,6 +60,7 @@ import {
|
||||
listPricingRuleSets,
|
||||
listRuntimePolicySets,
|
||||
listTasks,
|
||||
listWalletTransactions,
|
||||
listPublicBaseModels,
|
||||
listPublicCatalogProviders,
|
||||
listRateLimitWindows,
|
||||
@@ -112,6 +118,7 @@ import type {
|
||||
RegisterForm,
|
||||
TaskForm,
|
||||
WorkspaceTaskQuery,
|
||||
WorkspaceTransactionQuery,
|
||||
WorkspaceSection,
|
||||
} from './types';
|
||||
|
||||
@@ -121,6 +128,7 @@ type DataKey =
|
||||
| 'playgroundApiKeys'
|
||||
| 'playgroundModels'
|
||||
| 'modelCatalog'
|
||||
| 'networkProxyConfig'
|
||||
| 'platforms'
|
||||
| 'models'
|
||||
| 'providers'
|
||||
@@ -133,6 +141,8 @@ type DataKey =
|
||||
| 'users'
|
||||
| 'userGroups'
|
||||
| 'tasks'
|
||||
| 'wallet'
|
||||
| 'walletTransactions'
|
||||
| 'accessRules'
|
||||
| 'auditLogs'
|
||||
| 'apiKeys';
|
||||
@@ -143,6 +153,7 @@ export function App() {
|
||||
const [adminSection, setAdminSection] = useState<AdminSection>(initialRoute.adminSection);
|
||||
const [workspaceSection, setWorkspaceSection] = useState<WorkspaceSection>(initialRoute.workspaceSection);
|
||||
const [workspaceTaskQuery, setWorkspaceTaskQuery] = useState<WorkspaceTaskQuery>(initialRoute.workspaceTaskQuery);
|
||||
const [workspaceTransactionQuery, setWorkspaceTransactionQuery] = useState<WorkspaceTransactionQuery>(() => defaultWorkspaceTransactionQuery());
|
||||
const [apiDocSection, setApiDocSection] = useState<ApiDocSection>(initialRoute.apiDocSection);
|
||||
const [playgroundMode, setPlaygroundMode] = useState<PlaygroundMode>(initialRoute.playgroundMode);
|
||||
const [token, setToken] = useState(readStoredAccessToken);
|
||||
@@ -159,6 +170,7 @@ export function App() {
|
||||
summary: { modelCount: 0, sourceCount: 0 },
|
||||
});
|
||||
const [playgroundModels, setPlaygroundModels] = useState<PlatformModel[]>([]);
|
||||
const [networkProxyConfig, setNetworkProxyConfig] = useState<GatewayNetworkProxyConfig | null>(null);
|
||||
const [providers, setProviders] = useState<CatalogProvider[]>([]);
|
||||
const [baseModels, setBaseModels] = useState<BaseModelCatalogItem[]>([]);
|
||||
const [pricingRules, setPricingRules] = useState<PricingRule[]>([]);
|
||||
@@ -179,6 +191,9 @@ export function App() {
|
||||
const [taskResult, setTaskResult] = useState<GatewayTask | null>(null);
|
||||
const [tasks, setTasks] = useState<GatewayTask[]>([]);
|
||||
const [taskTotal, setTaskTotal] = useState(0);
|
||||
const [walletAccounts, setWalletAccounts] = useState<GatewayWalletAccount[]>([]);
|
||||
const [walletTransactions, setWalletTransactions] = useState<GatewayWalletTransaction[]>([]);
|
||||
const [walletTransactionTotal, setWalletTransactionTotal] = useState(0);
|
||||
const [coreState, setCoreState] = useState<LoadState>('idle');
|
||||
const [coreMessage, setCoreMessage] = useState('');
|
||||
const [state, setState] = useState<LoadState>('idle');
|
||||
@@ -187,6 +202,8 @@ export function App() {
|
||||
const loadingDataKeysRef = useRef(new Set<DataKey>());
|
||||
const loadedTaskQueryKeyRef = useRef('');
|
||||
const currentTaskQueryKeyRef = useRef('');
|
||||
const loadedTransactionQueryKeyRef = useRef('');
|
||||
const currentTransactionQueryKeyRef = useRef('');
|
||||
const { removeBaseModel, removeProvider, resetAllBaseModelsToDefault, resetBaseModelToDefault, saveBaseModel, saveProvider } = useCatalogOperations({
|
||||
setBaseModels,
|
||||
setCoreMessage,
|
||||
@@ -207,14 +224,16 @@ export function App() {
|
||||
token,
|
||||
});
|
||||
const taskListRequestKey = workspaceTaskQueryKey(workspaceTaskQuery);
|
||||
const transactionListRequestKey = workspaceTransactionQueryKey(workspaceTransactionQuery);
|
||||
currentTaskQueryKeyRef.current = taskListRequestKey;
|
||||
currentTransactionQueryKeyRef.current = transactionListRequestKey;
|
||||
|
||||
useEffect(() => {
|
||||
void ensureData(['health']);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void ensureRouteData(token);
|
||||
}, [activePage, adminSection, taskListRequestKey, workspaceSection, token]);
|
||||
}, [activePage, adminSection, taskListRequestKey, transactionListRequestKey, workspaceSection, token]);
|
||||
useEffect(() => {
|
||||
function handlePopState() {
|
||||
applyRoute(parseAppRoute());
|
||||
@@ -249,6 +268,7 @@ export function App() {
|
||||
baseModels,
|
||||
modelCatalog,
|
||||
models,
|
||||
networkProxyConfig,
|
||||
platforms,
|
||||
pricingRules,
|
||||
pricingRuleSets,
|
||||
@@ -260,7 +280,9 @@ export function App() {
|
||||
tenants,
|
||||
userGroups,
|
||||
users,
|
||||
}), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, models, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runtimePolicySets, taskResult, tasks, tenants, userGroups, users]);
|
||||
walletAccounts,
|
||||
walletTransactions,
|
||||
}), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
||||
|
||||
async function refresh(nextToken = token) {
|
||||
await ensureRouteData(nextToken, true);
|
||||
@@ -275,6 +297,10 @@ export function App() {
|
||||
loadedDataKeysRef.current.delete('tasks');
|
||||
loadingDataKeysRef.current.delete('tasks');
|
||||
}
|
||||
if (activePage === 'workspace' && workspaceSection === 'transactions' && loadedTransactionQueryKeyRef.current !== transactionListRequestKey) {
|
||||
loadedDataKeysRef.current.delete('walletTransactions');
|
||||
loadingDataKeysRef.current.delete('walletTransactions');
|
||||
}
|
||||
await ensureData(dataKeysForRoute(activePage, adminSection, workspaceSection, Boolean(nextToken)), nextToken, force);
|
||||
}
|
||||
|
||||
@@ -326,6 +352,9 @@ export function App() {
|
||||
case 'modelCatalog':
|
||||
setModelCatalog(await listModelCatalog(nextToken));
|
||||
return;
|
||||
case 'networkProxyConfig':
|
||||
setNetworkProxyConfig(await getNetworkProxyConfig(nextToken));
|
||||
return;
|
||||
case 'playgroundModels':
|
||||
setPlaygroundModels((await listPlayableModels(nextToken)).items);
|
||||
return;
|
||||
@@ -373,6 +402,20 @@ export function App() {
|
||||
loadedTaskQueryKeyRef.current = requestKey;
|
||||
}
|
||||
return;
|
||||
case 'wallet': {
|
||||
const response = await getWalletSummary(nextToken);
|
||||
setWalletAccounts(response.accounts);
|
||||
return;
|
||||
}
|
||||
case 'walletTransactions': {
|
||||
const requestKey = transactionListRequestKey;
|
||||
const response = await listWalletTransactions(nextToken, { ...normalizeWorkspaceTransactionQuery(workspaceTransactionQuery), direction: 'debit' });
|
||||
if (requestKey !== currentTransactionQueryKeyRef.current) return;
|
||||
setWalletTransactions(response.items);
|
||||
setWalletTransactionTotal(response.total ?? response.items.length);
|
||||
loadedTransactionQueryKeyRef.current = requestKey;
|
||||
return;
|
||||
}
|
||||
case 'accessRules':
|
||||
setAccessRules((await (activePage === 'workspace' && workspaceSection === 'apiKeys'
|
||||
? listApiKeyAccessRules(nextToken)
|
||||
@@ -707,7 +750,7 @@ export function App() {
|
||||
const detail = await getTask(credential, response.task.id);
|
||||
setTaskResult(detail);
|
||||
setTasks((current) => [detail, ...current.filter((item) => item.id !== detail.id)]);
|
||||
invalidateDataKeys('tasks');
|
||||
invalidateDataKeys('tasks', 'wallet', 'walletTransactions');
|
||||
setCoreState('ready');
|
||||
setCoreMessage(`${taskForm.kind} 已通过 ${apiKeySecret ? '本地 API Key' : '当前 Access Token'} 完成 simulation。`);
|
||||
} catch (err) {
|
||||
@@ -726,6 +769,7 @@ export function App() {
|
||||
setModels([]);
|
||||
setModelCatalog({ items: [], filters: { capabilities: [], providers: [] }, summary: { modelCount: 0, sourceCount: 0 } });
|
||||
setPlaygroundModels([]);
|
||||
setNetworkProxyConfig(null);
|
||||
setProviders([]);
|
||||
setBaseModels([]);
|
||||
setPricingRules([]);
|
||||
@@ -744,6 +788,10 @@ export function App() {
|
||||
setTaskResult(null);
|
||||
setTasks([]);
|
||||
setTaskTotal(0);
|
||||
setWalletAccounts([]);
|
||||
setWalletTransactions([]);
|
||||
setWalletTransactionTotal(0);
|
||||
setWorkspaceTransactionQuery(defaultWorkspaceTransactionQuery());
|
||||
setCoreMessage('');
|
||||
navigatePath('/');
|
||||
}
|
||||
@@ -849,12 +897,15 @@ export function App() {
|
||||
state={coreState}
|
||||
taskQuery={workspaceTaskQuery}
|
||||
taskTotal={taskTotal}
|
||||
transactionQuery={workspaceTransactionQuery}
|
||||
transactionTotal={walletTransactionTotal}
|
||||
onBatchAccessRules={batchSaveAPIKeyAccessRules}
|
||||
onDeleteApiKey={removeAPIKey}
|
||||
onApiKeyFormChange={setApiKeyForm}
|
||||
onSectionChange={navigateWorkspaceSection}
|
||||
onSubmitApiKey={submitAPIKey}
|
||||
onTaskQueryChange={navigateWorkspaceTaskQuery}
|
||||
onTransactionQueryChange={setWorkspaceTransactionQuery}
|
||||
onUseApiKeyForPlayground={useApiKeyForPlayground}
|
||||
/>
|
||||
) : (
|
||||
@@ -1010,6 +1061,39 @@ function nonEmptyRecord(value: Record<string, unknown> | undefined) {
|
||||
return value && Object.keys(value).length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function defaultWorkspaceTransactionQuery(): WorkspaceTransactionQuery {
|
||||
return {
|
||||
query: '',
|
||||
createdFrom: '',
|
||||
createdTo: '',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeWorkspaceTransactionQuery(query: WorkspaceTransactionQuery): WorkspaceTransactionQuery {
|
||||
return {
|
||||
query: query.query.trim(),
|
||||
createdFrom: query.createdFrom.trim(),
|
||||
createdTo: query.createdTo.trim(),
|
||||
page: positiveInt(query.page, 1),
|
||||
pageSize: clampTransactionPageSize(query.pageSize),
|
||||
};
|
||||
}
|
||||
|
||||
function workspaceTransactionQueryKey(query: WorkspaceTransactionQuery) {
|
||||
return JSON.stringify(normalizeWorkspaceTransactionQuery(query));
|
||||
}
|
||||
|
||||
function positiveInt(value: number, fallback: number) {
|
||||
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
||||
}
|
||||
|
||||
function clampTransactionPageSize(value: number) {
|
||||
const normalized = positiveInt(value, 10);
|
||||
return Math.min(100, Math.max(1, normalized));
|
||||
}
|
||||
|
||||
function dataKeysForRoute(
|
||||
activePage: PageKey,
|
||||
adminSection: AdminSection,
|
||||
@@ -1027,8 +1111,10 @@ function dataKeysForRoute(
|
||||
|
||||
if (activePage === 'workspace') {
|
||||
if (workspaceSection === 'overview') return ['users', 'userGroups', 'apiKeys'];
|
||||
if (workspaceSection === 'billing') return ['wallet'];
|
||||
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'playgroundModels'];
|
||||
if (workspaceSection === 'tasks') return ['tasks'];
|
||||
if (workspaceSection === 'transactions') return ['wallet', 'walletTransactions'];
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1045,7 +1131,7 @@ function dataKeysForRoute(
|
||||
case 'baseModels':
|
||||
return ['baseModels', 'providers', 'pricingRuleSets', 'runtimePolicySets'];
|
||||
case 'platforms':
|
||||
return ['platforms', 'models', 'providers', 'baseModels', 'pricingRuleSets'];
|
||||
return ['platforms', 'models', 'providers', 'baseModels', 'pricingRuleSets', 'networkProxyConfig'];
|
||||
case 'tenants':
|
||||
return ['tenants', 'userGroups'];
|
||||
case 'users':
|
||||
|
||||
Reference in New Issue
Block a user