mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat: 添加 Provider Ops 扩展操作系统,支持余额监控
主要更改: - 新增 Provider Ops 服务框架,支持通过架构配置执行余额查询等扩展操作 - 后端:添加 provider_ops 服务层和 API 路由 - 前端:添加 ProviderAuthDialog 组件配置认证信息 - 前端:添加 providerOps API 和认证模板系统 UI/组件优化: - Input 组件:新增 masked 属性,使用 CSS 遮蔽敏感信息,避免触发密码管理器 - Pagination 组件:移除首页/末页/上下页按钮,改为页码跳转输入框 - KeyFormDialog:使用 masked 属性简化 API Key 输入逻辑 - ProviderManagement:重新设计表格布局,显示余额监控数据
This commit is contained in:
66
frontend/src/features/providers/auth-templates/index.ts
Normal file
66
frontend/src/features/providers/auth-templates/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 提供商认证模板注册表
|
||||
*
|
||||
* 集中管理所有认证模板。
|
||||
*
|
||||
* ## 添加新模板的步骤
|
||||
*
|
||||
* 1. 在 `auth-templates/` 目录下创建新的模板文件(如 `my-api.ts`)
|
||||
* 2. 实现 `AuthTemplate` 接口
|
||||
* 3. 在本文件中导入并注册到 `templates` 数组
|
||||
*
|
||||
* ## 模板需要实现的内容
|
||||
*
|
||||
* - `id`: 模板唯一标识(对应后端的 architecture_id)
|
||||
* - `name`: 显示名称
|
||||
* - `description`: 描述文本
|
||||
* - `getFields()`: 返回表单字段定义
|
||||
* - `buildRequest()`: 构建后端 API 请求
|
||||
* - `parseConfig()`: 从已有配置解析表单数据
|
||||
* - `validate()`: 验证表单数据
|
||||
* - `formatQuota()`: (可选)格式化 quota 显示
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateRegistry } from './types'
|
||||
import { newApiTemplate } from './new-api'
|
||||
|
||||
// ==================== 模板注册 ====================
|
||||
// 在这里添加新模板
|
||||
|
||||
const templates: AuthTemplate[] = [newApiTemplate]
|
||||
|
||||
// ==================== 注册表实现 ====================
|
||||
|
||||
const templateMap = new Map<string, AuthTemplate>()
|
||||
|
||||
// 初始化 Map
|
||||
templates.forEach((template) => {
|
||||
templateMap.set(template.id, template)
|
||||
})
|
||||
|
||||
/**
|
||||
* 认证模板注册表
|
||||
*/
|
||||
export const authTemplateRegistry: AuthTemplateRegistry = {
|
||||
getAll(): AuthTemplate[] {
|
||||
return templates
|
||||
},
|
||||
|
||||
get(id: string): AuthTemplate | undefined {
|
||||
return templateMap.get(id)
|
||||
},
|
||||
|
||||
getDefault(): AuthTemplate {
|
||||
return templates[0]
|
||||
},
|
||||
|
||||
register(template: AuthTemplate): void {
|
||||
templates.push(template)
|
||||
templateMap.set(template.id, template)
|
||||
},
|
||||
}
|
||||
|
||||
// ==================== 导出 ====================
|
||||
|
||||
export * from './types'
|
||||
export { newApiTemplate } from './new-api'
|
||||
97
frontend/src/features/providers/auth-templates/new-api.ts
Normal file
97
frontend/src/features/providers/auth-templates/new-api.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* New API 认证模板
|
||||
*
|
||||
* 适用于 New API 风格的中转站:
|
||||
* - 使用 Bearer Token 认证
|
||||
* - 需要 New-Api-User Header 传递用户 ID
|
||||
* - quota 单位通常是 1/500000 美元
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateFieldGroup } from './types'
|
||||
import type { SaveConfigRequest } from '@/api/providerOps'
|
||||
|
||||
export const newApiTemplate: AuthTemplate = {
|
||||
id: 'new_api',
|
||||
name: 'New API',
|
||||
description: '适用于 New API 风格的中转站,使用 Bearer Token + New-Api-User Header',
|
||||
|
||||
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
|
||||
return [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
key: 'base_url',
|
||||
label: 'API 地址',
|
||||
type: 'text',
|
||||
placeholder: providerWebsite || 'https://api.example.com',
|
||||
helpText: '提供商的 API 基础地址,留空则使用提供商官网',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: 'api_key',
|
||||
label: '访问令牌 (API Key)',
|
||||
type: 'password',
|
||||
placeholder: 'sk-xxx',
|
||||
required: true,
|
||||
sensitive: true,
|
||||
},
|
||||
{
|
||||
key: 'user_id',
|
||||
label: '用户 ID',
|
||||
type: 'text',
|
||||
placeholder: '用户 ID',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
|
||||
const baseUrl = formData.base_url || providerWebsite || ''
|
||||
|
||||
return {
|
||||
architecture_id: 'new_api',
|
||||
base_url: baseUrl,
|
||||
connector: {
|
||||
auth_type: 'api_key',
|
||||
config: {
|
||||
auth_method: 'bearer',
|
||||
},
|
||||
credentials: {
|
||||
api_key: formData.api_key,
|
||||
user_id: formData.user_id,
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
schedule: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseConfig(config: any): Record<string, any> {
|
||||
return {
|
||||
base_url: config?.base_url || '',
|
||||
api_key: config?.connector?.credentials?.api_key || '',
|
||||
user_id: config?.connector?.credentials?.user_id || '',
|
||||
}
|
||||
},
|
||||
|
||||
validate(formData: Record<string, any>): string | null {
|
||||
if (!formData.api_key?.trim()) {
|
||||
return '请填写访问令牌'
|
||||
}
|
||||
if (!formData.user_id?.trim()) {
|
||||
return '请填写用户 ID'
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
formatQuota(quota: number): string {
|
||||
// New API 的 quota 单位是 1/500000 美元
|
||||
const usd = quota / 500000
|
||||
if (usd >= 1) {
|
||||
return `$${usd.toFixed(2)}`
|
||||
}
|
||||
return `$${usd.toFixed(4)}`
|
||||
},
|
||||
}
|
||||
120
frontend/src/features/providers/auth-templates/types.ts
Normal file
120
frontend/src/features/providers/auth-templates/types.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 提供商认证模板类型定义
|
||||
*
|
||||
* 认证模板定义了:
|
||||
* - 需要收集的表单字段
|
||||
* - 如何构建后端请求
|
||||
* - 如何解析已有配置
|
||||
*/
|
||||
|
||||
import type { SaveConfigRequest } from '@/api/providerOps'
|
||||
|
||||
/**
|
||||
* 表单字段类型
|
||||
*/
|
||||
export type FieldType = 'text' | 'password' | 'select' | 'textarea'
|
||||
|
||||
/**
|
||||
* 表单字段定义
|
||||
*/
|
||||
export interface AuthTemplateField {
|
||||
/** 字段 key(用于表单数据) */
|
||||
key: string
|
||||
/** 显示标签 */
|
||||
label: string
|
||||
/** 字段类型 */
|
||||
type: FieldType
|
||||
/** 占位符 */
|
||||
placeholder?: string
|
||||
/** 帮助文本 */
|
||||
helpText?: string
|
||||
/** 是否必填 */
|
||||
required?: boolean
|
||||
/** 是否为敏感字段(使用 masked 输入) */
|
||||
sensitive?: boolean
|
||||
/** select 类型的选项 */
|
||||
options?: Array<{ value: string; label: string }>
|
||||
/** 默认值 */
|
||||
defaultValue?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单字段分组
|
||||
*/
|
||||
export interface AuthTemplateFieldGroup {
|
||||
/** 分组标题(可选,为空则不显示标题) */
|
||||
title?: string
|
||||
/** 分组内的字段 */
|
||||
fields: AuthTemplateField[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证结果数据
|
||||
*/
|
||||
export interface VerifyResultData {
|
||||
username?: string
|
||||
display_name?: string
|
||||
email?: string
|
||||
quota?: number
|
||||
used_quota?: number
|
||||
request_count?: number
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证模板接口
|
||||
*/
|
||||
export interface AuthTemplate {
|
||||
/** 模板 ID(对应后端 architecture_id) */
|
||||
id: string
|
||||
/** 显示名称 */
|
||||
name: string
|
||||
/** 描述 */
|
||||
description: string
|
||||
|
||||
/**
|
||||
* 获取表单字段定义
|
||||
* @param providerWebsite 提供商官网(用于设置默认 base_url)
|
||||
*/
|
||||
getFields(providerWebsite?: string): AuthTemplateFieldGroup[]
|
||||
|
||||
/**
|
||||
* 构建保存请求
|
||||
* @param formData 表单数据
|
||||
* @param providerWebsite 提供商官网
|
||||
*/
|
||||
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest
|
||||
|
||||
/**
|
||||
* 从已有配置解析表单数据
|
||||
* @param config 已有配置
|
||||
*/
|
||||
parseConfig(config: any): Record<string, any>
|
||||
|
||||
/**
|
||||
* 验证表单数据
|
||||
* @param formData 表单数据
|
||||
* @returns 错误消息,无错误返回 null
|
||||
*/
|
||||
validate(formData: Record<string, any>): string | null
|
||||
|
||||
/**
|
||||
* 格式化验证结果中的 quota 显示
|
||||
* @param quota quota 值
|
||||
*/
|
||||
formatQuota?(quota: number): string
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证模板注册表类型
|
||||
*/
|
||||
export interface AuthTemplateRegistry {
|
||||
/** 获取所有模板 */
|
||||
getAll(): AuthTemplate[]
|
||||
/** 根据 ID 获取模板 */
|
||||
get(id: string): AuthTemplate | undefined
|
||||
/** 获取默认模板 */
|
||||
getDefault(): AuthTemplate
|
||||
/** 注册模板 */
|
||||
register(template: AuthTemplate): void
|
||||
}
|
||||
Reference in New Issue
Block a user