| 1 | import { aitoearnAuthConfigSchema } from '@yikart/aitoearn-auth' |
| 2 | import { assetsConfigSchema } from '@yikart/assets' |
| 3 | import { baseConfig, createZodDto, i18nObjectSchema, selectConfig } from '@yikart/common' |
| 4 | import { AiLogChannel, mongodbConfigSchema } from '@yikart/mongodb' |
| 5 | import { redisConfigSchema } from '@yikart/redis' |
| 6 | import { redlockConfigSchema } from '@yikart/redlock' |
| 7 | import z from 'zod' |
| 8 | import { dashscopeConfigSchema } from './core/ai/libs/dashscope' |
| 9 | import { geminiConfigSchema } from './core/ai/libs/gemini' |
| 10 | import { grokConfigSchema } from './core/ai/libs/grok' |
| 11 | import { openaiConfigSchema } from './core/ai/libs/openai' |
| 12 | import { relayConfigSchema } from './core/ai/libs/relay/relay.config' |
| 13 | import { volcengineConfigSchema } from './core/ai/libs/volcengine' |
| 14 | |
| 15 | const videoModelInputConstraintSchema = z.object({ |
| 16 | maxCount: z.number().int().min(0).optional(), |
| 17 | formats: z.array(z.string()).optional(), |
| 18 | minDuration: z.number().positive().optional(), |
| 19 | maxDuration: z.number().positive().optional(), |
| 20 | maxTotalDuration: z.number().positive().optional(), |
| 21 | maxSizeMb: z.number().positive().optional(), |
| 22 | minAspectRatio: z.number().positive().optional(), |
| 23 | maxAspectRatio: z.number().positive().optional(), |
| 24 | minWidth: z.number().int().positive().optional(), |
| 25 | maxWidth: z.number().int().positive().optional(), |
| 26 | minPixels: z.number().int().positive().optional(), |
| 27 | maxPixels: z.number().int().positive().optional(), |
| 28 | minFps: z.number().positive().optional(), |
| 29 | maxFps: z.number().positive().optional(), |
| 30 | }) |
| 31 | |
| 32 | const videoModelInputConstraintsSchema = z.object({ |
| 33 | images: videoModelInputConstraintSchema.optional(), |
| 34 | videos: videoModelInputConstraintSchema.optional(), |
| 35 | audios: videoModelInputConstraintSchema.optional(), |
| 36 | }).optional() |
| 37 | |
| 38 | const videoModelRuntimeFallbackSchema = z.object({ |
| 39 | channel: z.enum(AiLogChannel), |
| 40 | model: z.string(), |
| 41 | }).strict() |
| 42 | |
| 43 | const videoModelRuntimeModelSchema = z.object({ |
| 44 | model: z.string(), |
| 45 | mode: z.string().optional(), |
| 46 | resolution: z.string().optional(), |
| 47 | fallback: videoModelRuntimeFallbackSchema.optional(), |
| 48 | }).strict() |
| 49 | |
| 50 | export const aiModelsConfigSchema = z.object({ |
| 51 | chat: z.array(z.object({ |
| 52 | name: z.string(), |
| 53 | description: z.string(), |
| 54 | summary: z.string().optional(), |
| 55 | logo: z.string().optional(), |
| 56 | tags: z.array(i18nObjectSchema).default([]), |
| 57 | mainTag: z.string().optional(), |
| 58 | channel: z.enum(AiLogChannel), |
| 59 | scenes: z.string().array().optional(), |
| 60 | inputModalities: z.array(z.enum(['text', 'image', 'video', 'audio'])), |
| 61 | outputModalities: z.array(z.enum(['text', 'image', 'video', 'audio'])), |
| 62 | })), |
| 63 | image: z.object({ |
| 64 | generation: z.array(z.object({ |
| 65 | name: z.string(), |
| 66 | description: z.string(), |
| 67 | summary: z.string().optional(), |
| 68 | logo: z.string().optional(), |
| 69 | tags: z.array(i18nObjectSchema).default([]), |
| 70 | mainTag: z.string().optional(), |
| 71 | runtimeModel: z.string().optional(), |
| 72 | retry: z.number().int().min(0).optional(), |
| 73 | sizes: z.array(z.string()), |
| 74 | qualities: z.array(z.string()), |
| 75 | styles: z.array(z.string()), |
| 76 | })), |
| 77 | edit: z.array(z.object({ |
| 78 | name: z.string(), |
| 79 | description: z.string(), |
| 80 | summary: z.string().optional(), |
| 81 | logo: z.string().optional(), |
| 82 | tags: z.array(i18nObjectSchema).default([]), |
| 83 | mainTag: z.string().optional(), |
| 84 | runtimeModel: z.string().optional(), |
| 85 | retry: z.number().int().min(0).optional(), |
| 86 | sizes: z.array(z.string()), |
| 87 | maxInputImages: z.number(), |
| 88 | })), |
| 89 | }), |
| 90 | video: z.object({ |
| 91 | generation: z.array(z.object({ |
| 92 | name: z.string(), |
| 93 | description: z.string(), |
| 94 | summary: z.string().optional(), |
| 95 | logo: z.string().optional(), |
| 96 | tags: z.array(i18nObjectSchema).default([]), |
| 97 | mainTag: z.string().optional(), |
| 98 | channel: z.enum(AiLogChannel), |
| 99 | modes: z.array(z.enum(['text2video', 'image2video', 'flf2video', 'lf2video', 'multi-image2video', 'multi-ref', 'video2video'])), |
| 100 | resolutions: z.array(z.string()), |
| 101 | durations: z.array(z.number()), |
| 102 | maxInputImages: z.number().int().min(0), |
| 103 | inputConstraints: videoModelInputConstraintsSchema, |
| 104 | aspectRatios: z.array(z.string()), |
| 105 | runtimeModels: z.array(videoModelRuntimeModelSchema).optional(), |
| 106 | defaults: z.object({ |
| 107 | resolution: z.string().optional(), |
| 108 | aspectRatio: z.string().optional(), |
| 109 | duration: z.number().optional(), |
| 110 | }), |
| 111 | queuePriority: z.number().int().min(1).max(2097152).optional(), |
| 112 | retry: z.number().int().min(0).optional(), |
| 113 | })), |
| 114 | }), |
| 115 | }) |
| 116 | |
| 117 | export const aiConfigSchema = z.object({ |
| 118 | models: aiModelsConfigSchema, |
| 119 | openai: openaiConfigSchema, |
| 120 | volcengine: z.object({ |
| 121 | ...volcengineConfigSchema.shape, |
| 122 | callbackUrl: z.string().optional(), |
| 123 | }), |
| 124 | grok: grokConfigSchema, |
| 125 | dashscope: dashscopeConfigSchema, |
| 126 | gemini: geminiConfigSchema, |
| 127 | relay: relayConfigSchema.optional(), |
| 128 | anthropic: z.object({ |
| 129 | baseUrl: z.string(), |
| 130 | apiKey: z.string(), |
| 131 | }), |
| 132 | draftGeneration: z.object({ |
| 133 | planner: z.object({ |
| 134 | defaultModel: z.string(), |
| 135 | }), |
| 136 | queue: z.object({ |
| 137 | lowPriorityMinPriority: z.number().int().min(1).max(2097152), |
| 138 | lowPriorityConcurrency: z.number().int().min(1), |
| 139 | }).default({ |
| 140 | lowPriorityMinPriority: 1000, |
| 141 | lowPriorityConcurrency: 2, |
| 142 | }), |
| 143 | imageModels: z.array(z.object({ |
| 144 | model: z.string().describe('对外模型名'), |
| 145 | displayName: z.string().describe('展示名称'), |
| 146 | runtimeModel: z.string().optional().describe('实际调用的上游模型名'), |
| 147 | queuePriority: z.number().int().min(1).max(2097152).optional().describe('BullMQ priority,数值越小优先级越高'), |
| 148 | tags: z.array(i18nObjectSchema).default([]), |
| 149 | supportedAspectRatios: z.array(z.string()).describe('支持的图片宽高比列表'), |
| 150 | maxInputImages: z.number().int().min(1).describe('最多可输入的参考图片数量'), |
| 151 | pricing: z.array(z.object({ |
| 152 | resolution: z.string().describe('分辨率,如 1K, 2K, 4K'), |
| 153 | })), |
| 154 | })), |
| 155 | }), |
| 156 | }) |
| 157 | |
| 158 | const defaultAgentModels = [ |
| 159 | 'claude-opus-4-6', |
| 160 | 'claude-haiku-4-5-20251001-thinking', |
| 161 | 'claude-opus-4-5-20251101-thinking', |
| 162 | 'claude-opus-4-5-20251101', |
| 163 | 'claude-sonnet-4-5-20250929-thinking', |
| 164 | 'claude-haiku-4-5-20251001', |
| 165 | 'claude-opus-4-1-20250805', |
| 166 | 'claude-opus-4-1-20250805-thinking', |
| 167 | 'claude-sonnet-4-5-20250929', |
| 168 | 'claude-opus-4-6', |
| 169 | 'claude-opus-4-6-thinking', |
| 170 | ] |
| 171 | |
| 172 | const agentModelNameSchema = z.string().min(1).regex(/^[^,]+$/) |
| 173 | |
| 174 | // Agent 配置 |
| 175 | export const agentConfigSchema = z.object({ |
| 176 | baseUrl: z.string(), |
| 177 | apiKey: z.string(), |
| 178 | models: z.array(agentModelNameSchema).min(1).default(defaultAgentModels).describe('Agent 可用模型列表'), |
| 179 | defaultModel: agentModelNameSchema.default('claude-opus-4-6').describe('Agent 默认模型'), |
| 180 | backgroundModel: agentModelNameSchema.default('claude-haiku-4-5-20251001').describe('Agent 后台子任务模型'), |
| 181 | thinkModel: agentModelNameSchema.default('claude-opus-4-6').describe('Agent 思考任务模型'), |
| 182 | taskTimeoutMs: z.number().default(60 * 60 * 1000).describe('Agent 任务超时时间(毫秒),默认 60 分钟'), |
| 183 | }).superRefine((agent, ctx) => { |
| 184 | const configuredModels = new Set(agent.models) |
| 185 | |
| 186 | for (const [field, model] of Object.entries({ |
| 187 | defaultModel: agent.defaultModel, |
| 188 | backgroundModel: agent.backgroundModel, |
| 189 | thinkModel: agent.thinkModel, |
| 190 | })) { |
| 191 | if (!configuredModels.has(model)) { |
| 192 | ctx.addIssue({ |
| 193 | code: 'custom', |
| 194 | path: [field], |
| 195 | message: `${field} must be included in agent.models`, |
| 196 | }) |
| 197 | } |
| 198 | } |
| 199 | }) |
| 200 | |
| 201 | const serverClientConfigSchema = z.object({ |
| 202 | baseUrl: z.string(), |
| 203 | }) |
| 204 | |
| 205 | export const appConfigSchema = z.object({ |
| 206 | ...baseConfig.shape, |
| 207 | auth: aitoearnAuthConfigSchema, |
| 208 | redis: redisConfigSchema, |
| 209 | mongodb: mongodbConfigSchema, |
| 210 | redlock: redlockConfigSchema, |
| 211 | serverClient: serverClientConfigSchema, |
| 212 | assets: assetsConfigSchema, |
| 213 | ai: aiConfigSchema, |
| 214 | agent: agentConfigSchema, |
| 215 | }) |
| 216 | |
| 217 | export class AppConfig extends createZodDto(appConfigSchema) { } |
| 218 | |
| 219 | export const config = selectConfig(AppConfig) |
| 220 |