Add distributor token supported models view
This commit is contained in:
1 parent
52ee0df229
commit
bba7e560ec
4 files changed
+118
-1
No files matched your search
@@ -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}`);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user