移除gitignore中的lib文件夹,上传被排除掉的前端lib文件

This commit is contained in:
墨梓柒
2026-01-21 15:41:43 +08:00
parent 761e4c8940
commit 433a4551f9
26 changed files with 4559 additions and 1 deletions

View File

@@ -0,0 +1,570 @@
/**
* 模型配置 Pack API
*
* 与 Cloudflare Workers Pack 服务交互
*/
import { fetchWithAuth } from './fetch-with-auth'
// ============ 类型定义 ============
/**
* 提供商配置(分享时不含 api_key
*/
export interface PackProvider {
name: string
base_url: string
client_type: 'openai' | 'gemini'
max_retry?: number
timeout?: number
retry_interval?: number
}
/**
* 模型配置
*/
export interface PackModel {
model_identifier: string
name: string
api_provider: string
price_in: number
price_out: number
temperature?: number
max_tokens?: number
force_stream_mode?: boolean
extra_params?: Record<string, unknown>
}
/**
* 单个任务配置
*/
export interface PackTaskConfig {
model_list: string[]
temperature?: number
max_tokens?: number
slow_threshold?: number
}
/**
* 所有任务配置
*/
export interface PackTaskConfigs {
utils?: PackTaskConfig
utils_small?: PackTaskConfig
tool_use?: PackTaskConfig
replyer?: PackTaskConfig
planner?: PackTaskConfig
vlm?: PackTaskConfig
voice?: PackTaskConfig
embedding?: PackTaskConfig
lpmm_entity_extract?: PackTaskConfig
lpmm_rdf_build?: PackTaskConfig
lpmm_qa?: PackTaskConfig
}
/**
* Pack 列表项
*/
export interface PackListItem {
id: string
name: string
description: string
author: string
version: string
created_at: string
updated_at: string
status: 'pending' | 'approved' | 'rejected'
reject_reason?: string
downloads: number
likes: number
tags?: string[]
provider_count: number
model_count: number
task_count: number
}
/**
* 完整的 Pack 数据
*/
export interface ModelPack extends Omit<PackListItem, 'provider_count' | 'model_count' | 'task_count'> {
providers: PackProvider[]
models: PackModel[]
task_config: PackTaskConfigs
}
/**
* Pack 列表响应
*/
export interface ListPacksResponse {
packs: PackListItem[]
total: number
page: number
page_size: number
total_pages: number
}
/**
* 应用 Pack 时的选项
*/
export interface ApplyPackOptions {
apply_providers: boolean
apply_models: boolean
apply_task_config: boolean
task_mode: 'replace' | 'append'
selected_providers?: string[]
selected_models?: string[]
selected_tasks?: string[]
}
/**
* 应用 Pack 时的冲突检测结果
*/
export interface ApplyPackConflicts {
existing_providers: Array<{
pack_provider: PackProvider
local_providers: Array<{ // 改为数组,支持多个匹配
name: string
base_url: string
}>
}>
new_providers: PackProvider[]
conflicting_models: Array<{
pack_model: string
local_model: string
}>
}
// ============ API 配置 ============
// Pack 服务基础 URLCloudflare Workers
const PACK_SERVICE_URL = 'https://maibot-plugin-stats.maibot-webui.workers.dev'
// ============ API 函数 ============
/**
* 获取 Pack 列表
*/
export async function listPacks(params?: {
status?: 'pending' | 'approved' | 'rejected' | 'all'
page?: number
page_size?: number
search?: string
sort_by?: 'created_at' | 'downloads' | 'likes'
sort_order?: 'asc' | 'desc'
}): Promise<ListPacksResponse> {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.set('status', params.status)
if (params?.page) searchParams.set('page', params.page.toString())
if (params?.page_size) searchParams.set('page_size', params.page_size.toString())
if (params?.search) searchParams.set('search', params.search)
if (params?.sort_by) searchParams.set('sort_by', params.sort_by)
if (params?.sort_order) searchParams.set('sort_order', params.sort_order)
const response = await fetch(`${PACK_SERVICE_URL}/pack?${searchParams.toString()}`)
if (!response.ok) {
throw new Error(`获取 Pack 列表失败: ${response.status}`)
}
return response.json()
}
/**
* 获取单个 Pack 详情
*/
export async function getPack(packId: string): Promise<ModelPack> {
const response = await fetch(`${PACK_SERVICE_URL}/pack/${packId}`)
if (!response.ok) {
throw new Error(`获取 Pack 失败: ${response.status}`)
}
const data = await response.json()
if (!data.success) {
throw new Error(data.error || '获取 Pack 失败')
}
return data.pack
}
/**
* 创建新 Pack
*/
export async function createPack(pack: {
name: string
description: string
author: string
tags?: string[]
providers: PackProvider[]
models: PackModel[]
task_config: PackTaskConfigs
}): Promise<{ pack_id: string; message: string }> {
const response = await fetch(`${PACK_SERVICE_URL}/pack`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(pack),
})
const data = await response.json()
if (!data.success) {
throw new Error(data.error || '创建 Pack 失败')
}
return data
}
/**
* 记录 Pack 下载
*/
export async function recordPackDownload(packId: string, userId?: string): Promise<void> {
await fetch(`${PACK_SERVICE_URL}/pack/download`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pack_id: packId, user_id: userId }),
})
}
/**
* 点赞/取消点赞 Pack
*/
export async function togglePackLike(packId: string, userId: string): Promise<{ likes: number; liked: boolean }> {
const response = await fetch(`${PACK_SERVICE_URL}/pack/like`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pack_id: packId, user_id: userId }),
})
const data = await response.json()
if (!data.success) {
throw new Error(data.error || '点赞失败')
}
return { likes: data.likes, liked: data.liked }
}
/**
* 检查是否已点赞
*/
export async function checkPackLike(packId: string, userId: string): Promise<boolean> {
const response = await fetch(
`${PACK_SERVICE_URL}/pack/like/check?pack_id=${packId}&user_id=${userId}`
)
const data = await response.json()
return data.liked || false
}
// ============ 本地应用 Pack 相关 ============
/**
* 检测应用 Pack 时的冲突
*/
export async function detectPackConflicts(
pack: ModelPack
): Promise<ApplyPackConflicts> {
// 获取当前配置
const response = await fetchWithAuth('/api/webui/config/model')
if (!response.ok) {
throw new Error('获取当前模型配置失败')
}
const responseData = await response.json()
const currentConfig = responseData.config || responseData
console.log('=== Pack Conflict Detection ===')
console.log('Pack providers:', pack.providers)
console.log('Local providers:', currentConfig.api_providers)
const conflicts: ApplyPackConflicts = {
existing_providers: [],
new_providers: [],
conflicting_models: [],
}
// 检测提供商冲突
const localProviders = currentConfig.api_providers || []
for (const packProvider of pack.providers) {
console.log(`\nChecking pack provider: ${packProvider.name}`)
console.log(` Pack URL: ${packProvider.base_url}`)
console.log(` Normalized: ${normalizeUrl(packProvider.base_url)}`)
// 按 URL 匹配 - 找出所有匹配的本地提供商
const matchedProviders = localProviders.filter(
(p: { base_url: string; name: string }) => {
const localNormalized = normalizeUrl(p.base_url)
const packNormalized = normalizeUrl(packProvider.base_url)
console.log(` Comparing with local "${p.name}": ${p.base_url}`)
console.log(` Local normalized: ${localNormalized}`)
console.log(` Match: ${localNormalized === packNormalized}`)
return localNormalized === packNormalized
}
)
if (matchedProviders.length > 0) {
console.log(` ✓ Matched with ${matchedProviders.length} local provider(s):`, matchedProviders.map((p: {name: string}) => p.name).join(', '))
conflicts.existing_providers.push({
pack_provider: packProvider,
local_providers: matchedProviders.map((p: { name: string; base_url: string }) => ({
name: p.name,
base_url: p.base_url,
})),
})
} else {
console.log(` ✗ No match found - will need API key`)
conflicts.new_providers.push(packProvider)
}
}
// 检测模型名称冲突
const localModels = currentConfig.models || []
console.log('\n=== Model Conflict Detection ===')
for (const packModel of pack.models) {
const conflictModel = localModels.find(
(m: { name: string }) => m.name === packModel.name
)
if (conflictModel) {
console.log(`Model conflict: ${packModel.name}`)
conflicts.conflicting_models.push({
pack_model: packModel.name,
local_model: conflictModel.name,
})
}
}
console.log('\n=== Detection Summary ===')
console.log(`Existing providers: ${conflicts.existing_providers.length}`)
console.log(`New providers: ${conflicts.new_providers.length}`)
console.log(`Conflicting models: ${conflicts.conflicting_models.length}`)
console.log('===========================\n')
return conflicts
}
/**
* 应用 Pack 到本地配置
*/
export async function applyPack(
pack: ModelPack,
options: ApplyPackOptions,
providerMapping: Record<string, string>, // pack_provider_name -> local_provider_name
newProviderApiKeys: Record<string, string>, // provider_name -> api_key
): Promise<void> {
// 获取当前配置
const response = await fetchWithAuth('/api/webui/config/model')
if (!response.ok) {
throw new Error('获取当前模型配置失败')
}
const responseData = await response.json()
const currentConfig = responseData.config || responseData
// 1. 处理提供商
if (options.apply_providers) {
const providersToApply = options.selected_providers
? pack.providers.filter(p => options.selected_providers!.includes(p.name))
: pack.providers
for (const packProvider of providersToApply) {
// 检查是否映射到已有提供商
if (providerMapping[packProvider.name]) {
// 使用已有提供商,不需要添加
continue
}
// 添加新提供商
const apiKey = newProviderApiKeys[packProvider.name]
if (!apiKey) {
throw new Error(`提供商 "${packProvider.name}" 缺少 API Key`)
}
const newProvider = {
...packProvider,
api_key: apiKey,
}
// 检查是否已存在同名提供商
const existingIndex = currentConfig.api_providers.findIndex(
(p: { name: string }) => p.name === packProvider.name
)
if (existingIndex >= 0) {
// 覆盖
currentConfig.api_providers[existingIndex] = newProvider
} else {
// 添加
currentConfig.api_providers.push(newProvider)
}
}
}
// 2. 处理模型
if (options.apply_models) {
const modelsToApply = options.selected_models
? pack.models.filter(m => options.selected_models!.includes(m.name))
: pack.models
for (const packModel of modelsToApply) {
// 映射提供商名称
const actualProvider = providerMapping[packModel.api_provider] || packModel.api_provider
const newModel = {
...packModel,
api_provider: actualProvider,
}
// 检查是否已存在同名模型
const existingIndex = currentConfig.models.findIndex(
(m: { name: string }) => m.name === packModel.name
)
if (existingIndex >= 0) {
// 覆盖
currentConfig.models[existingIndex] = newModel
} else {
// 添加
currentConfig.models.push(newModel)
}
}
}
// 3. 处理任务配置
if (options.apply_task_config) {
const taskKeys = options.selected_tasks || Object.keys(pack.task_config)
for (const taskKey of taskKeys) {
const packTaskConfig = pack.task_config[taskKey as keyof PackTaskConfigs]
if (!packTaskConfig) continue
// 映射模型名称(如果模型名称被跳过,则从任务列表中移除)
const appliedModelNames = new Set(
options.selected_models || pack.models.map(m => m.name)
)
const filteredModelList = packTaskConfig.model_list.filter(
name => appliedModelNames.has(name)
)
if (filteredModelList.length === 0) continue
const newTaskConfig = {
...packTaskConfig,
model_list: filteredModelList,
}
if (options.task_mode === 'replace') {
// 替换模式
currentConfig.model_task_config[taskKey] = newTaskConfig
} else {
// 追加模式
const existingConfig = currentConfig.model_task_config[taskKey]
if (existingConfig) {
// 合并模型列表(去重)
const mergedList = [...new Set([
...existingConfig.model_list,
...filteredModelList,
])]
currentConfig.model_task_config[taskKey] = {
...existingConfig,
model_list: mergedList,
}
} else {
currentConfig.model_task_config[taskKey] = newTaskConfig
}
}
}
}
// 保存配置
const saveResponse = await fetchWithAuth('/api/webui/config/model', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(currentConfig),
})
if (!saveResponse.ok) {
throw new Error('保存配置失败')
}
}
/**
* 从当前配置导出 Pack
*/
export async function exportCurrentConfigAsPack(params: {
name: string
description: string
author: string
tags?: string[]
selectedProviders?: string[]
selectedModels?: string[]
selectedTasks?: string[]
}): Promise<{
providers: PackProvider[]
models: PackModel[]
task_config: PackTaskConfigs
}> {
// 获取当前配置
const response = await fetchWithAuth('/api/webui/config/model')
if (!response.ok) {
throw new Error('获取当前模型配置失败')
}
const responseData = await response.json()
// API 返回的格式是 { success: true, config: {...} }
if (!responseData.success || !responseData.config) {
throw new Error('获取配置失败')
}
const currentConfig = responseData.config
// 过滤提供商(移除 api_key
let providers: PackProvider[] = (currentConfig.api_providers || []).map(
(p: { name: string; base_url: string; client_type: string; max_retry?: number; timeout?: number; retry_interval?: number }) => ({
name: p.name,
base_url: p.base_url,
client_type: p.client_type,
max_retry: p.max_retry,
timeout: p.timeout,
retry_interval: p.retry_interval,
})
)
if (params.selectedProviders) {
providers = providers.filter(p => params.selectedProviders!.includes(p.name))
}
// 过滤模型
let models: PackModel[] = currentConfig.models || []
if (params.selectedModels) {
models = models.filter(m => params.selectedModels!.includes(m.name))
}
// 过滤任务配置
const task_config: PackTaskConfigs = {}
const allTasks = currentConfig.model_task_config || {}
const taskKeys = params.selectedTasks || Object.keys(allTasks)
for (const key of taskKeys) {
if (allTasks[key]) {
task_config[key as keyof PackTaskConfigs] = allTasks[key]
}
}
return { providers, models, task_config }
}
// ============ 辅助函数 ============
/**
* 标准化 URL 用于比较
*/
function normalizeUrl(url: string): string {
try {
const parsed = new URL(url)
// 移除末尾斜杠,统一小写
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`.replace(/\/$/, '').toLowerCase()
} catch {
return url.toLowerCase().replace(/\/$/, '')
}
}
/**
* 生成用户 ID用于统计
*/
export function getPackUserId(): string {
const storageKey = 'maibot_pack_user_id'
let userId = localStorage.getItem(storageKey)
if (!userId) {
userId = 'pack_user_' + Math.random().toString(36).substring(2, 15)
localStorage.setItem(storageKey, userId)
}
return userId
}