Add distributor token supported models view

This commit is contained in:
abingyyds committed 2026-04-13 19:46:06 +08:00
1 parent 52ee0df229
commit bba7e560ec
4 files changed
+118 -1

No files matched your search

+1
View File
@@ -62,6 +62,7 @@ export const getUserLogs = (params) => api.get('/api/dist/user/logs', { params }
// ===== Tokens =====
export const getTokens = () => api.get('/api/dist/token/list');
export const getTokenSupportedModels = (id) => api.get(`/api/dist/token/${id}/models`);
export const createToken = (data) => api.post('/api/dist/token/create', data);
export const updateToken = (id, data) => api.put(`/api/dist/token/${id}`, data);
export const deleteToken = (id) => api.delete(`/api/dist/token/${id}`);
+9
View File
@@ -168,6 +168,15 @@
"tokens.unavailable": "Unavailable",
"tokens.rmbPerUsd": "RMB / $1",
"tokens.myKeys": "My API Keys",
"tokens.viewSupportedModels": "Supported Models",
"tokens.hideSupportedModels": "Hide Models",
"tokens.loadingSupportedModels": "Loading supported models...",
"tokens.loadSupportedModelsFailed": "Failed to load supported models",
"tokens.supportedModels": "Supported Models",
"tokens.supportedProviders": "Providers",
"tokens.noSupportedModels": "No available models for this key",
"tokens.restrictedByModels": "Model limited",
"tokens.restrictedByProviders": "Provider limited",
"tokens.selectedGroup": "Selected Group",
"tokens.quickStart": "Quick Start",
"tokens.quickStartComment": "# Use your API key with OpenAI-compatible endpoint",
+9
View File
@@ -168,6 +168,15 @@
"tokens.unavailable": "暂不可用",
"tokens.rmbPerUsd": "块 / 1 美元",
"tokens.myKeys": "我的 API Keys",
"tokens.viewSupportedModels": "支持模型",
"tokens.hideSupportedModels": "收起模型",
"tokens.loadingSupportedModels": "正在加载支持模型...",
"tokens.loadSupportedModelsFailed": "加载支持模型失败",
"tokens.supportedModels": "支持模型",
"tokens.supportedProviders": "支持商家",
"tokens.noSupportedModels": "当前没有可用模型",
"tokens.restrictedByModels": "模型限制",
"tokens.restrictedByProviders": "商家限制",
"tokens.selectedGroup": "已选分组",
"tokens.quickStart": "快速开始",
"tokens.quickStartComment": "# 使用 OpenAI 兼容端点调用 API",
+99 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getTokens, createToken, updateToken, deleteToken, getSiteKeyGroups } from '../api';
import { getTokens, createToken, updateToken, deleteToken, getSiteKeyGroups, getTokenSupportedModels } from '../api';
import toast from 'react-hot-toast';
export default function Tokens() {
@@ -10,6 +10,8 @@ export default function Tokens() {
const [copiedId, setCopiedId] = useState(null);
const [newKey, setNewKey] = useState(null);
const [deleteConfirm, setDeleteConfirm] = useState(null);
const [expandedTokens, setExpandedTokens] = useState({});
const [tokenModels, setTokenModels] = useState({});
// Key groups
const [keyGroups, setKeyGroups] = useState([]);
@@ -128,6 +130,45 @@ export default function Tokens() {
try { return JSON.parse(tagsStr || '[]'); } catch { return []; }
};
const handleToggleSupportedModels = async (tokenId) => {
const isExpanded = !!expandedTokens[tokenId];
setExpandedTokens((prev) => ({ ...prev, [tokenId]: !isExpanded }));
if (isExpanded || tokenModels[tokenId]) return;
setTokenModels((prev) => ({
...prev,
[tokenId]: { loading: true, models: [], count: 0, provider_names: [], restricted_by_providers: false, restricted_by_models: false },
}));
try {
const res = await getTokenSupportedModels(tokenId);
if (res.data.success) {
const data = res.data.data || {};
setTokenModels((prev) => ({
...prev,
[tokenId]: {
loading: false,
models: data.models || [],
count: data.count || 0,
provider_names: data.provider_names || [],
restricted_by_providers: Boolean(data.restricted_by_providers),
restricted_by_models: Boolean(data.restricted_by_models),
},
}));
} else {
setTokenModels((prev) => ({
...prev,
[tokenId]: { loading: false, error: true, models: [], count: 0, provider_names: [], restricted_by_providers: false, restricted_by_models: false },
}));
}
} catch (e) {
setTokenModels((prev) => ({
...prev,
[tokenId]: { loading: false, error: true, models: [], count: 0, provider_names: [], restricted_by_providers: false, restricted_by_models: false },
}));
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[50vh]">
@@ -314,6 +355,12 @@ export default function Tokens() {
{token.created_time ? new Date(token.created_time * 1000).toLocaleDateString() : ''}
</span>
<div className="flex items-center gap-2">
<button
onClick={() => handleToggleSupportedModels(token.id)}
className="px-3 py-1 text-xs rounded-lg border border-page-divider text-page-secondary hover:bg-page-surface-hover transition-colors"
>
{expandedTokens[token.id] ? t('tokens.hideSupportedModels') : t('tokens.viewSupportedModels')}
</button>
<button
onClick={() => handleToggle(token)}
className={`px-3 py-1 text-xs rounded-lg border transition-colors ${
@@ -345,6 +392,57 @@ export default function Tokens() {
</button>
</div>
)}
{expandedTokens[token.id] && (
<div className="mt-3 rounded-xl border border-page-divider bg-page-surface/50 px-4 py-3">
{tokenModels[token.id]?.loading ? (
<div className="flex items-center gap-2 text-sm text-page-secondary">
<div className="w-4 h-4 border-2 border-brand-500/30 border-t-brand-500 rounded-full animate-spin" />
<span>{t('tokens.loadingSupportedModels')}</span>
</div>
) : tokenModels[token.id]?.error ? (
<p className="text-sm text-page-danger">{t('tokens.loadSupportedModelsFailed')}</p>
) : (
<>
<div className="flex flex-wrap items-center gap-2">
<p className="text-sm font-medium text-page">
{t('tokens.supportedModels')} ({tokenModels[token.id]?.count || 0})
</p>
{tokenModels[token.id]?.restricted_by_models && (
<span className="px-2 py-0.5 rounded-full text-[11px] bg-brand-500/10 text-brand-500">
{t('tokens.restrictedByModels')}
</span>
)}
{tokenModels[token.id]?.restricted_by_providers && (
<span className="px-2 py-0.5 rounded-full text-[11px] bg-brand-500/10 text-brand-500">
{t('tokens.restrictedByProviders')}
</span>
)}
</div>
{tokenModels[token.id]?.provider_names?.length > 0 && (
<div className="mt-2 flex flex-wrap items-center gap-2">
<span className="text-xs text-page-muted">{t('tokens.supportedProviders')}</span>
{tokenModels[token.id].provider_names.map((name) => (
<span key={name} className="px-2 py-0.5 rounded-full text-[11px] bg-page-inset text-page-secondary">
{name}
</span>
))}
</div>
)}
{tokenModels[token.id]?.models?.length > 0 ? (
<div className="mt-3 flex flex-wrap gap-2">
{tokenModels[token.id].models.map((modelName) => (
<code key={modelName} className="px-2.5 py-1 rounded-lg text-[11px] font-mono bg-page-inset text-page-secondary">
{modelName}
</code>
))}
</div>
) : (
<p className="mt-3 text-sm text-page-muted">{t('tokens.noSupportedModels')}</p>
)}
</>
)}
</div>
)}
</div>
))}
</div>