/** * 云函数:proxyHub(LLM 代理中心) * 替代:POST /api/llm/chat → action=llmChat * POST /api/llm/gemini → action=geminiChat * 流式聊天:action=llmChatStream(SSE 透传,需要 fmode 平台 dispatcher 支持 chunked response) * 注意:/api/video-proxy 不迁(二进制大流量不适合云函数) */ const LLM_BASE_URL = 'http://server.fmode.cn:9999'; const LLM_API_KEY = 'sk-MFBOnsAtZiqlwwMgMLKCFmPy55pMohQEGMqsIw3aJrIgvoEO'; async function handler(request, response) { try { const action = pickParam(request, 'action'); // ============ 流式聊天(SSE 透传) ============ if (action === 'llmChatStream') { const body = pickParam(request, 'payload', 'data') || request.body || {}; const { model, messages, temperature, max_tokens, ...rest } = body; if (!Array.isArray(messages) || messages.length === 0) { return response.json({ code: 400, success: false, error: '缺少 messages' }); } const upstream = await fetch(`${LLM_BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${LLM_API_KEY}` }, body: JSON.stringify({ model: model || 'gpt-4o', messages, temperature: temperature ?? 0.7, max_tokens: max_tokens ?? 4096, ...rest, stream: true }) }); if (!upstream.ok) { const errText = await upstream.text(); return response.json({ code: upstream.status, success: false, error: errText || `upstream ${upstream.status}` }); } // 把上游 SSE 原样透传给客户端 // 注意:fmode 平台 dispatcher 必须放行 chunked response,否则客户端会一次性收到全部内容 try { if (typeof response.setHeader === 'function') { response.setHeader('Content-Type', 'text/event-stream; charset=utf-8'); response.setHeader('Cache-Control', 'no-cache, no-transform'); response.setHeader('Connection', 'keep-alive'); response.setHeader('X-Accel-Buffering', 'no'); } if (typeof response.flushHeaders === 'function') response.flushHeaders(); } catch (_) {} const reader = upstream.body.getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) break; if (value && typeof response.write === 'function') { response.write(Buffer.from(value)); } } } finally { try { reader.cancel(); } catch (_) {} if (typeof response.end === 'function') response.end(); } return; } if (action === 'llmChat') { const body = pickParam(request, 'payload', 'data') || request.body || {}; const { model, messages, temperature, max_tokens, ...rest } = body; if (!Array.isArray(messages)) { return response.json({ code: 400, success: false, error: '缺少 messages' }); } const r = await fetch(`${LLM_BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${LLM_API_KEY}` }, body: JSON.stringify({ model: model || 'gpt-4o', messages, temperature: temperature ?? 0.7, max_tokens: max_tokens ?? 4096, stream: false, ...rest }) }); const data = await r.json(); response.json({ code: r.ok ? 200 : 500, success: r.ok, data }); return; } if (action === 'geminiChat') { const body = pickParam(request, 'payload', 'data') || request.body || {}; const { model, contents, generationConfig, safetySettings, systemInstruction } = body; if (!contents) { return response.json({ code: 400, success: false, error: '缺少 contents' }); } const r = await fetch(`${LLM_BASE_URL}/v1beta/models/${model || 'gemini-2.0-flash'}:generateContent`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${LLM_API_KEY}` }, body: JSON.stringify({ contents, generationConfig, safetySettings, systemInstruction }) }); const data = await r.json(); response.json({ code: r.ok ? 200 : 500, success: r.ok, data }); return; } response.json({ code: 400, success: false, error: `未知 action: ${action}` }); } catch (error) { console.error('❌ proxyHub 失败:', error.message); response.json({ code: 500, success: false, error: error.message }); } } function pickParam(request, ...names) { const sources = [request.params, request.body, request]; for (const src of sources) { if (!src || typeof src !== 'object') continue; for (const n of names) { const v = src[n]; if (v !== undefined && v !== null && v !== '') return v; } } return null; }