| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- /**
- * 云函数: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;
- }
|