feat: Add 404 judgment; Virtual authentication files also support obtaining model lists; Chinese support for virtual authentication file i18n;

This commit is contained in:
Test
2025-12-14 17:19:51 +08:00
parent abf9b5f8c9
commit 2a87a4d82a
3 changed files with 99 additions and 22 deletions

View File

@@ -316,11 +316,16 @@
"type_vertex": "Vertex", "type_vertex": "Vertex",
"type_empty": "空文件", "type_empty": "空文件",
"type_unknown": "其他", "type_unknown": "其他",
"type_virtual": "虚拟认证文件",
"models_button": "模型", "models_button": "模型",
"models_title": "支持的模型", "models_title": "支持的模型",
"models_loading": "正在加载模型列表...", "models_loading": "正在加载模型列表...",
"models_empty": "该凭证暂无可用模型", "models_empty": "该凭证暂无可用模型",
"models_empty_desc": "该认证凭证可能尚未被服务器加载或没有绑定任何模型" "models_empty_desc": "该认证凭证可能尚未被服务器加载或没有绑定任何模型",
"models_unsupported": "当前版本不支持此功能",
"models_unsupported_desc": "请更新 CLI Proxy API 到最新版本后重试",
"models_excluded_badge": "已排除",
"models_excluded_hint": "此模型已被 OAuth 排除"
}, },
"vertex_import": { "vertex_import": {
"title": "Vertex AI 凭证导入", "title": "Vertex AI 凭证导入",

View File

@@ -260,6 +260,8 @@
padding: 4px 10px; padding: 4px 10px;
border-radius: $radius-sm; border-radius: $radius-sm;
font-style: italic; font-style: italic;
display: inline-flex;
align-items: center;
} }
// 分页 // 分页
@@ -446,3 +448,28 @@
margin-left: auto; margin-left: auto;
} }
.modelItemExcluded {
opacity: 0.6;
background-color: var(--bg-tertiary);
border-style: dashed;
.modelId {
text-decoration: line-through;
color: var(--text-tertiary);
}
&:hover {
border-color: var(--danger-color);
}
}
.modelExcludedBadge {
font-size: 10px;
color: var(--danger-color);
background-color: rgba(239, 68, 68, 0.1);
padding: 2px 6px;
border-radius: 8px;
border: 1px solid var(--danger-color);
flex-shrink: 0;
}

View File

@@ -145,6 +145,8 @@ export function AuthFilesPage() {
const [modelsLoading, setModelsLoading] = useState(false); const [modelsLoading, setModelsLoading] = useState(false);
const [modelsList, setModelsList] = useState<{ id: string; display_name?: string; type?: string }[]>([]); const [modelsList, setModelsList] = useState<{ id: string; display_name?: string; type?: string }[]>([]);
const [modelsFileName, setModelsFileName] = useState(''); const [modelsFileName, setModelsFileName] = useState('');
const [modelsFileType, setModelsFileType] = useState('');
const [modelsError, setModelsError] = useState<'unsupported' | null>(null);
// OAuth 排除模型相关 // OAuth 排除模型相关
const [excluded, setExcluded] = useState<Record<string, string[]>>({}); const [excluded, setExcluded] = useState<Record<string, string[]>>({});
@@ -415,20 +417,40 @@ export function AuthFilesPage() {
// 显示模型列表 // 显示模型列表
const showModels = async (item: AuthFileItem) => { const showModels = async (item: AuthFileItem) => {
setModelsFileName(item.name); setModelsFileName(item.name);
setModelsFileType(item.type || '');
setModelsList([]); setModelsList([]);
setModelsError(null);
setModelsModalOpen(true); setModelsModalOpen(true);
setModelsLoading(true); setModelsLoading(true);
try { try {
const models = await authFilesApi.getModelsForAuthFile(item.name); const models = await authFilesApi.getModelsForAuthFile(item.name);
setModelsList(models); setModelsList(models);
} catch (err) { } catch (err) {
// 检测是否是 API 不支持的错误 (404 或特定错误消息)
const errorMessage = err instanceof Error ? err.message : ''; const errorMessage = err instanceof Error ? err.message : '';
if (errorMessage.includes('404') || errorMessage.includes('not found') || errorMessage.includes('Not Found')) {
setModelsError('unsupported');
} else {
showNotification(`${t('notification.load_failed')}: ${errorMessage}`, 'error'); showNotification(`${t('notification.load_failed')}: ${errorMessage}`, 'error');
}
} finally { } finally {
setModelsLoading(false); setModelsLoading(false);
} }
}; };
// 检查模型是否被 OAuth 排除
const isModelExcluded = (modelId: string, providerType: string): boolean => {
const excludedModels = excluded[providerType] || [];
return excludedModels.some(pattern => {
if (pattern.includes('*')) {
// 支持通配符匹配
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
return regex.test(modelId);
}
return pattern.toLowerCase() === modelId.toLowerCase();
});
};
// 获取类型标签显示文本 // 获取类型标签显示文本
const getTypeLabel = (type: string): string => { const getTypeLabel = (type: string): string => {
const key = `auth_files.filter_${type}`; const key = `auth_files.filter_${type}`;
@@ -561,7 +583,19 @@ export function AuthFilesPage() {
<div className={styles.cardActions}> <div className={styles.cardActions}>
{isRuntimeOnly ? ( {isRuntimeOnly ? (
<span className={styles.virtualBadge}>{t('auth_files.type_virtual') || '虚拟认证文件'}</span> <>
<div className={styles.virtualBadge}>{t('auth_files.type_virtual') || '虚拟认证文件'}</div>
<Button
variant="secondary"
size="sm"
onClick={() => showModels(item)}
className={styles.iconButton}
title={t('auth_files.models_button', { defaultValue: '模型' })}
disabled={disableControls}
>
<IconBot className={styles.actionIcon} size={16} />
</Button>
</>
) : ( ) : (
<> <>
<Button <Button
@@ -814,6 +848,11 @@ export function AuthFilesPage() {
> >
{modelsLoading ? ( {modelsLoading ? (
<div className={styles.hint}>{t('auth_files.models_loading', { defaultValue: '正在加载模型列表...' })}</div> <div className={styles.hint}>{t('auth_files.models_loading', { defaultValue: '正在加载模型列表...' })}</div>
) : modelsError === 'unsupported' ? (
<EmptyState
title={t('auth_files.models_unsupported', { defaultValue: '当前版本不支持此功能' })}
description={t('auth_files.models_unsupported_desc', { defaultValue: '请更新 CLI Proxy API 到最新版本后重试' })}
/>
) : modelsList.length === 0 ? ( ) : modelsList.length === 0 ? (
<EmptyState <EmptyState
title={t('auth_files.models_empty', { defaultValue: '该凭证暂无可用模型' })} title={t('auth_files.models_empty', { defaultValue: '该凭证暂无可用模型' })}
@@ -821,15 +860,17 @@ export function AuthFilesPage() {
/> />
) : ( ) : (
<div className={styles.modelsList}> <div className={styles.modelsList}>
{modelsList.map((model) => ( {modelsList.map((model) => {
const isExcluded = isModelExcluded(model.id, modelsFileType);
return (
<div <div
key={model.id} key={model.id}
className={styles.modelItem} className={`${styles.modelItem} ${isExcluded ? styles.modelItemExcluded : ''}`}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(model.id); navigator.clipboard.writeText(model.id);
showNotification(t('notification.link_copied', { defaultValue: '已复制到剪贴板' }), 'success'); showNotification(t('notification.link_copied', { defaultValue: '已复制到剪贴板' }), 'success');
}} }}
title={t('common.copy', { defaultValue: '点击复制' })} title={isExcluded ? t('auth_files.models_excluded_hint', { defaultValue: '此模型已被 OAuth 排除' }) : t('common.copy', { defaultValue: '点击复制' })}
> >
<span className={styles.modelId}>{model.id}</span> <span className={styles.modelId}>{model.id}</span>
{model.display_name && model.display_name !== model.id && ( {model.display_name && model.display_name !== model.id && (
@@ -838,8 +879,12 @@ export function AuthFilesPage() {
{model.type && ( {model.type && (
<span className={styles.modelType}>{model.type}</span> <span className={styles.modelType}>{model.type}</span>
)} )}
{isExcluded && (
<span className={styles.modelExcludedBadge}>{t('auth_files.models_excluded_badge', { defaultValue: '已排除' })}</span>
)}
</div> </div>
))} );
})}
</div> </div>
)} )}
</Modal> </Modal>