# All files / config index.ts

0% Statements 0/95
0% Branches 0/1
0% Functions 0/1
0% Lines 0/95

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129

     import dotenv from 'dotenv';
    import path from 'path';
    import fs from 'fs';

    // 使用 process.cwd() 获取项目根目录（server目录）
    const projectRoot = process.cwd();
    dotenv.config({ path: path.resolve(projectRoot, '.env') });

    // 加载模型配置
    const modelsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'models.json'), 'utf-8'));

    // 获取所有模型（扁平化）
    function getAllModels() {
      const models: any[] = [];
      for (const [vendorKey, vendor] of Object.entries(modelsConfig.vendors) as [string, any][]) {
        for (const model of vendor.models) {
          models.push({
            ...model,
            vendor: vendorKey,
            vendorName: vendor.name,
            baseUrl: vendor.baseUrl,
            apiKey: vendor.apiKey,
            apiType: vendor.apiType,
          });
        }
      }
      return models;
    }

    // 获取启用的模型
    function getEnabledModels() {
      return getAllModels().filter((m: any) => m.enabled);
    }

    // 根据 ID 获取模型配置
    function getModel(id: string) {
      return getAllModels().find((m: any) => m.id === id);
    }

    // 根据类型获取模型列表 (text, tts, image, video)
    function getModelsByType(type: string) {
      return getAllModels().filter((m: any) => m.input?.includes(type) && m.enabled);
    }

    // 检查模型是否可切换（根据错误类型判断是否需要切换）
    function shouldSwitchModel(error: any): boolean {
      if (!error) return false;
      const message = (error?.message || error?.error?.message || '').toLowerCase();
      const status = error?.status || error?.response?.status || 0;

      // 不可切换的错误：认证/权限/参数问题，换供应商也没用
      const nonSwitchablePatterns = [
        'invalid api key', 'invalid api-key', 'authentication', 'unauthorized',
        'invalid token', 'token expired',
        'permission denied', 'access denied',
        'invalid request', 'bad request',
        'invalidparameter', 'invalid_parameter',  // TTS API 参数错误（如文本过短）
      ];
      if (nonSwitchablePatterns.some(p => message.includes(p))) return false;
      if (status === 401) return false; // 认证失败

      // 可切换的错误：限流、余额不足、服务不可用、模型不存在
      const switchablePatterns = [
        'rate limit', 'rate_limit', 'too many requests', '请求过于频繁',
        'quota', 'balance', 'insufficient', 'usage limit',
        'model not found', 'model not support', 'does not exist', 'invalid model',
        'service unavailable', 'bad gateway', 'gateway timeout',
        'internal server error',
      ];
      if (switchablePatterns.some(p => message.includes(p))) return true;

      // HTTP 状态码判断
      if ([429, 502, 503, 504, 500].includes(status)) return true;
      if (status === 403) return true;  // 403 多数是配额/权限，换Key可能有效
      if (status === 404) return true;  // 模型不存在，换供应商

      // 状态码文本匹配（兜底）
      if (['429', '502', '503', '504'].some(c => message.includes(c))) return true;

      return false;
    }

    // 获取下一个可用模型（用于自动切换）
    function getNextModel(currentModelId: string, type: string): string | null {
      const models = getModelsByType(type);
      const currentIndex = models.findIndex((m: any) => m.id === currentModelId);
      if (currentIndex === -1 || currentIndex >= models.length - 1) {
        return null; // 没有下一个模型
      }
      return models[currentIndex + 1].id;
    }

    export const config = {
      port: parseInt(process.env.PORT || process.env.SERVER_PORT || '3000', 10),
      nodeEnv: process.env.NODE_ENV || 'development',

      mongodb: {
        uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/audio-book',
      },

      jwt: {
        // 临时硬编码，确保登录和验证一致
        secret: 'my-jwt-secret-key-2024',
        expiresIn: process.env.JWT_EXPIRES_IN || '7d',
      },

      // 模型配置（统一管理，所有 TTS/LLM 配置均从 models.json 读取）
      models: {
        vendors: modelsConfig.vendors,
        list: getAllModels(),
        enabled: getEnabledModels(),
        getModel,
        getModelsByType,
        shouldSwitchModel,
        getNextModel,
        textGeneration: {
          defaultModel: modelsConfig.textGeneration.defaultModel,
        },
        tts: {
          defaultModel: modelsConfig.tts.defaultModel,
          defaultVoice: modelsConfig.tts.defaultVoice,
        },
      },

      upload: {
        dir: path.join(process.cwd(), 'uploads'),
        maxSize: 50 * 1024 * 1024, // 50MB
      },
    };