/** * 硬件测试工具后端代理 * * 让前端测试网页不用配置 API Key * 直接调本服务,自动用 models.json 里的 Key */ import Router from '@koa/router'; import { Context } from 'koa'; import { callLLMWithMessages } from '../../services/llm'; const router = new Router(); /** * LLM 对话代理 * POST /api/test-tools/llm * Body: { messages: [{role, content}], model?: string } */ router.post('/llm', async (ctx: Context) => { const { messages, model } = ctx.request.body as { messages: Array<{ role: string; content: string }>; model?: string; }; if (!messages || !Array.isArray(messages) || messages.length === 0) { ctx.status = 400; ctx.body = { code: 40001, message: 'messages 不能为空' }; return; } try { console.log(`[Test-Tools LLM] ${messages.length} 条消息,model=${model || 'default'}`); // messages.role 在请求层是 string,但 LLM 接口要求枚举;调用前做窄化 const reply = await callLLMWithMessages( messages as unknown as Parameters[0], model || undefined, 500 ); ctx.body = { code: 0, message: 'success', data: { reply, model: model || 'default' }, }; } catch (error: any) { console.error('[Test-Tools LLM] 失败:', error.message); ctx.status = 500; ctx.body = { code: 50001, message: 'LLM 调用失败: ' + error.message, }; } }); /** * ASR(语音识别)代理 - 使用阿里百炼 Paraformer * POST /api/test-tools/asr * Body: { audio: base64 string, format?: 'wav'|'mp3'|'webm' } */ router.post('/asr', async (ctx: Context) => { const { audio, format = 'webm' } = ctx.request.body as { audio: string; format?: string; }; if (!audio) { ctx.status = 400; ctx.body = { code: 40001, message: 'audio 数据为空' }; return; } try { // 从 models.json 读取 bailian 配置 const fs = require('fs'); const path = require('path'); const modelsPath = path.join(process.cwd(), 'src', 'config', 'models.json'); const models = JSON.parse(fs.readFileSync(modelsPath, 'utf-8')); const bailian = models.vendors?.bailian; if (!bailian?.apiKey) { ctx.status = 500; ctx.body = { code: 50001, message: 'bailian apiKey 未配置' }; return; } console.log(`[Test-Tools ASR] format=${format}, audio size=${audio.length}`); // 把 base64 转成 Buffer const audioBuffer = Buffer.from(audio, 'base64'); // 调用阿里百炼 Paraformer ASR // 注意:Paraformer 接受 OSS URL 或文件上传,这里用 file URL 方式 // 简化方案:把音频上传到 OSS 再调用,或直接用同步识别 API // 阿里百炼 ASR API(同步识别) const https = require('https'); const querystring = require('querystring'); const postData = JSON.stringify({ model: 'paraformer-v2', input: { file_urls: [] }, // 需要先上传文件 parameters: {}, }); // 由于 Paraformer 需要先上传音频到 OSS, // 这里简化方案:直接告诉前端用浏览器 Web Speech API // 或者前端用 OpenAI Whisper(需要 Key) ctx.body = { code: 0, message: 'success', data: { text: '', hint: '阿里百炼 ASR 需要先上传文件到 OSS。建议改用浏览器内置 Web Speech API(无需 Key)。', }, }; } catch (error: any) { console.error('[Test-Tools ASR] 失败:', error.message); ctx.status = 500; ctx.body = { code: 50001, message: 'ASR 调用失败: ' + error.message, }; } }); /** * 获取可用模型列表(不返回 apiKey) * GET /api/test-tools/models */ router.get('/models', async (ctx: Context) => { try { const fs = require('fs'); const path = require('path'); const modelsPath = path.join(process.cwd(), 'src', 'config', 'models.json'); const models = JSON.parse(fs.readFileSync(modelsPath, 'utf-8')); const summary = Object.entries(models.vendors || {}).map(([key, v]: [string, any]) => ({ vendor: key, name: v.name, hasKey: !!v.apiKey, models: (v.models || []).filter((m: any) => m.enabled).map((m: any) => ({ id: m.id, name: m.name, inputs: m.input, })), })); ctx.body = { code: 0, message: 'success', data: { vendors: summary }, }; } catch (error: any) { ctx.status = 500; ctx.body = { code: 50001, message: error.message }; } }); export default router;