diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 18af585..4399877 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -315,7 +315,12 @@ "type_iflow": "iFlow", "type_vertex": "Vertex", "type_empty": "空文件", - "type_unknown": "其他" + "type_unknown": "其他", + "models_button": "模型", + "models_title": "支持的模型", + "models_loading": "正在加载模型列表...", + "models_empty": "该凭证暂无可用模型", + "models_empty_desc": "该认证凭证可能尚未被服务器加载或没有绑定任何模型" }, "vertex_import": { "title": "Vertex AI 凭证导入", @@ -663,7 +668,7 @@ "gemini_api_key": "Gemini API密钥", "codex_api_key": "Codex API密钥", "claude_api_key": "Claude API密钥", - "link_copied": "链接已复制到剪贴板" + "link_copied": "已复制" }, "language": { "switch": "语言", diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index d361b97..83681d7 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -390,3 +390,59 @@ text-align: center; padding: $spacing-lg; } + +// 模型列表弹窗 +.modelsList { + display: flex; + flex-direction: column; + gap: $spacing-sm; + max-height: 400px; + overflow-y: auto; +} + +.modelItem { + display: flex; + align-items: center; + gap: $spacing-sm; + padding: $spacing-sm $spacing-md; + background-color: var(--bg-secondary); + border-radius: $radius-md; + border: 1px solid var(--border-color); + flex-wrap: wrap; + cursor: pointer; + transition: all $transition-fast; + + &:hover { + background-color: var(--bg-hover); + border-color: var(--primary-color); + } + + &:active { + transform: scale(0.98); + } +} + +.modelId { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + word-break: break-all; +} + +.modelDisplayName { + font-size: 12px; + color: var(--text-secondary); + flex-shrink: 0; +} + +.modelType { + font-size: 11px; + color: var(--text-tertiary); + background-color: var(--bg-tertiary); + padding: 2px 8px; + border-radius: 10px; + flex-shrink: 0; + margin-left: auto; +} + diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 77ee440..b4e5b97 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -6,7 +6,7 @@ import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import { EmptyState } from '@/components/ui/EmptyState'; -import { IconDownload, IconInfo, IconTrash2 } from '@/components/ui/icons'; +import { IconBot, IconDownload, IconInfo, IconTrash2 } from '@/components/ui/icons'; import { useAuthStore, useNotificationStore, useThemeStore } from '@/stores'; import { authFilesApi, usageApi } from '@/services/api'; import { apiClient } from '@/services/api/client'; @@ -140,6 +140,12 @@ export function AuthFilesPage() { const [detailModalOpen, setDetailModalOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); + // 模型列表弹窗相关 + const [modelsModalOpen, setModelsModalOpen] = useState(false); + const [modelsLoading, setModelsLoading] = useState(false); + const [modelsList, setModelsList] = useState<{ id: string; display_name?: string; type?: string }[]>([]); + const [modelsFileName, setModelsFileName] = useState(''); + // OAuth 排除模型相关 const [excluded, setExcluded] = useState>({}); const [excludedModalOpen, setExcludedModalOpen] = useState(false); @@ -406,6 +412,23 @@ export function AuthFilesPage() { setDetailModalOpen(true); }; + // 显示模型列表 + const showModels = async (item: AuthFileItem) => { + setModelsFileName(item.name); + setModelsList([]); + setModelsModalOpen(true); + setModelsLoading(true); + try { + const models = await authFilesApi.getModelsForAuthFile(item.name); + setModelsList(models); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : ''; + showNotification(`${t('notification.load_failed')}: ${errorMessage}`, 'error'); + } finally { + setModelsLoading(false); + } + }; + // 获取类型标签显示文本 const getTypeLabel = (type: string): string => { const key = `auth_files.filter_${type}`; @@ -541,6 +564,16 @@ export function AuthFilesPage() { {t('auth_files.type_virtual') || '虚拟认证文件'} ) : ( <> + + } + > + {modelsLoading ? ( +
{t('auth_files.models_loading', { defaultValue: '正在加载模型列表...' })}
+ ) : modelsList.length === 0 ? ( + + ) : ( +
+ {modelsList.map((model) => ( +
{ + navigator.clipboard.writeText(model.id); + showNotification(t('notification.link_copied', { defaultValue: '已复制到剪贴板' }), 'success'); + }} + title={t('common.copy', { defaultValue: '点击复制' })} + > + {model.id} + {model.display_name && model.display_name !== model.id && ( + {model.display_name} + )} + {model.type && ( + {model.type} + )} +
+ ))} +
+ )} + + {/* OAuth 排除弹窗 */} - apiClient.delete(`/oauth-excluded-models?provider=${encodeURIComponent(provider)}`) + apiClient.delete(`/oauth-excluded-models?provider=${encodeURIComponent(provider)}`), + + // 获取认证凭证支持的模型 + async getModelsForAuthFile(name: string): Promise<{ id: string; display_name?: string; type?: string; owned_by?: string }[]> { + const data = await apiClient.get(`/auth-files/models?name=${encodeURIComponent(name)}`); + return (data && Array.isArray(data['models'])) ? data['models'] : []; + } };