feat: 同步上游仓库更新并增强功能

主要更新:
- 新增使用统计功能,支持按模型显示成功/失败计数
- 大幅增强认证文件页面功能
  - 新增每个文件的启用/禁用切换
  - 新增前缀/代理 URL 模态编辑器
  - 新增 OAuth 映射的模型建议功能
  - 优化模型映射 UI,使用自动完成输入
  - 新增禁用状态样式
- UI/UX 改进
  - 实现自定义 AutocompleteInput 组件
  - 优化页面过渡动画,使用交叉淡入淡出效果
  - 改进 GSAP 页面过渡流畅度
- 配额管理优化
  - 统一 Gemini CLI 配额组(Flash/Pro 系列)
- 系统监控增强
  - 扩展健康监控窗口到 200 分钟
- 修复多个 bug 和改进代码质量

涉及文件:21 个文件修改,新增 1484 行,删除 461 行
This commit is contained in:
kongkongyo
2026-01-25 15:49:20 +08:00
parent 8c3ac0d50a
commit 82cb521b2e
21 changed files with 1482 additions and 459 deletions

View File

@@ -7,6 +7,7 @@ import type { AuthFilesResponse } from '@/types/authFile';
import type { OAuthModelMappingEntry } from '@/types';
type StatusError = { status?: number };
type AuthFileStatusResponse = { status: string; disabled: boolean };
const getStatusCode = (err: unknown): number | undefined => {
if (!err || typeof err !== 'object') return undefined;
@@ -17,7 +18,8 @@ const getStatusCode = (err: unknown): number | undefined => {
const normalizeOauthExcludedModels = (payload: unknown): Record<string, string[]> => {
if (!payload || typeof payload !== 'object') return {};
const source = (payload as any)['oauth-excluded-models'] ?? (payload as any).items ?? payload;
const record = payload as Record<string, unknown>;
const source = record['oauth-excluded-models'] ?? record.items ?? payload;
if (!source || typeof source !== 'object') return {};
const result: Record<string, string[]> = {};
@@ -54,10 +56,11 @@ const normalizeOauthExcludedModels = (payload: unknown): Record<string, string[]
const normalizeOauthModelMappings = (payload: unknown): Record<string, OAuthModelMappingEntry[]> => {
if (!payload || typeof payload !== 'object') return {};
const record = payload as Record<string, unknown>;
const source =
(payload as any)['oauth-model-mappings'] ??
(payload as any)['oauth-model-alias'] ??
(payload as any).items ??
record['oauth-model-mappings'] ??
record['oauth-model-alias'] ??
record.items ??
payload;
if (!source || typeof source !== 'object') return {};
@@ -70,16 +73,17 @@ const normalizeOauthModelMappings = (payload: unknown): Record<string, OAuthMode
if (!key) return;
if (!Array.isArray(mappings)) return;
const seen = new Set<string>();
const normalized = mappings
.map((item) => {
if (!item || typeof item !== 'object') return null;
const name = String((item as any).name ?? (item as any).id ?? (item as any).model ?? '').trim();
const alias = String((item as any).alias ?? '').trim();
if (!name || !alias) return null;
const fork = (item as any).fork === true;
return fork ? { name, alias, fork } : { name, alias };
})
const seen = new Set<string>();
const normalized = mappings
.map((item) => {
if (!item || typeof item !== 'object') return null;
const entry = item as Record<string, unknown>;
const name = String(entry.name ?? entry.id ?? entry.model ?? '').trim();
const alias = String(entry.alias ?? '').trim();
if (!name || !alias) return null;
const fork = entry.fork === true;
return fork ? { name, alias, fork } : { name, alias };
})
.filter(Boolean)
.filter((entry) => {
const mapping = entry as OAuthModelMappingEntry;
@@ -103,6 +107,9 @@ const OAUTH_MODEL_MAPPINGS_LEGACY_ENDPOINT = '/oauth-model-alias';
export const authFilesApi = {
list: () => apiClient.get<AuthFilesResponse>('/auth-files'),
setStatus: (name: string, disabled: boolean) =>
apiClient.patch<AuthFileStatusResponse>('/auth-files/status', { name, disabled }),
upload: (file: File) => {
const formData = new FormData();
formData.append('file', file, file.name);

View File

@@ -20,12 +20,21 @@ const normalizeBaseUrl = (baseUrl: string): string => {
const buildModelsEndpoint = (baseUrl: string): string => {
const normalized = normalizeBaseUrl(baseUrl);
if (!normalized) return '';
return normalized.endsWith('/v1') ? `${normalized}/models` : `${normalized}/v1/models`;
return `${normalized}/models`;
};
const buildV1ModelsEndpoint = (baseUrl: string): string => {
const normalized = normalizeBaseUrl(baseUrl);
if (!normalized) return '';
return `${normalized}/v1/models`;
};
export const modelsApi = {
/**
* Fetch available models from /v1/models endpoint (for system info page)
*/
async fetchModels(baseUrl: string, apiKey?: string, headers: Record<string, string> = {}) {
const endpoint = buildModelsEndpoint(baseUrl);
const endpoint = buildV1ModelsEndpoint(baseUrl);
if (!endpoint) {
throw new Error('Invalid base url');
}
@@ -42,6 +51,9 @@ export const modelsApi = {
return normalizeModelList(payload, { dedupe: true });
},
/**
* Fetch models from /models endpoint via api-call (for OpenAI provider discovery)
*/
async fetchModelsViaApiCall(
baseUrl: string,
apiKey?: string,