chat.mjs (5777B)
1 // Vercel Serverless function with CORS and correct Gemini REST API formatting. 2 // Place at api/chat.js 3 4 export default async function handler(req, res) { 5 const corsHeaders = { 6 'Access-Control-Allow-Origin': '*', 7 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 8 'Access-Control-Allow-Headers': 'Content-Type, Authorization' 9 }; 10 11 // Handle preflight 12 if (req.method === 'OPTIONS') { 13 res.writeHead(204, corsHeaders); 14 return res.end(); 15 } 16 17 // Debug GET endpoint 18 if (req.method === 'GET') { 19 res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders }); 20 return res.end(JSON.stringify({ status: 'ok', info: 'POST JSON {message} to /api/chat' })); 21 } 22 23 if (req.method !== 'POST') { 24 res.writeHead(405, { 'Content-Type': 'application/json', ...corsHeaders }); 25 return res.end(JSON.stringify({ error: 'Method not allowed' })); 26 } 27 28 const addCors = (status, payload) => { 29 res.writeHead(status, { 'Content-Type': 'application/json', ...corsHeaders }); 30 res.end(JSON.stringify(payload)); 31 }; 32 33 // Rate Limiting (Upstash or Memory Fallback) 34 const ip = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0]?.trim() || 'unknown'; 35 const RATE_LIMIT = 10; 36 const WINDOW_SECONDS = 60 * 60; 37 38 const upstashUrl = process.env.UPSTASH_REDIS_REST_URL; 39 const upstashToken = process.env.UPSTASH_REDIS_REST_TOKEN; 40 try { 41 if (upstashUrl && upstashToken) { 42 const { Redis } = await import('@upstash/redis'); 43 const redis = new Redis({ url: upstashUrl, token: upstashToken }); 44 45 const key = `pytml:rate:${ip}`; 46 const count = await redis.incr(key); 47 if (count === 1) await redis.expire(key, WINDOW_SECONDS); 48 if (count > RATE_LIMIT) { 49 return addCors(429, { error: 'Rate limit exceeded: 10 requests per hour per IP' }); 50 } 51 } else { 52 if (!global._pytml_rate) global._pytml_rate = new Map(); 53 const entry = global._pytml_rate.get(ip) || { count: 0, first: Date.now() }; 54 if (Date.now() - entry.first > WINDOW_SECONDS * 1000) { 55 entry.count = 0; 56 entry.first = Date.now(); 57 } 58 entry.count += 1; 59 global._pytml_rate.set(ip, entry); 60 if (entry.count > RATE_LIMIT) { 61 return addCors(429, { error: 'Rate limit exceeded: 10 requests per hour per IP (non-persistent fallback)' }); 62 } 63 } 64 } catch (err) { 65 console.error('Rate limit check error:', err); 66 } 67 68 const body = req.body; 69 const message = body?.message; 70 if (!message) return addCors(400, { error: 'Missing message in request body' }); 71 72 // Fallback check for key names 73 const apiKey = process.env.GEMINI_API_KEY || process.env.GIMINI_API; 74 if (!apiKey) return addCors(500, { error: 'Server misconfigured: missing GEMINI_API_KEY environment variable' }); 75 76 // Standard Gemini REST Endpoint 77 const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent?key=${apiKey}`; 78 79 // ─── NEW: System instruction for Pytml support ─── 80 const systemPrompt = `You are a support assistant for "Pytml" – a lightweight library that runs Python in the browser using WebAssembly. 81 Your job is to help users with installation, usage, debugging, and best practices for Pytml. 82 Key facts about Pytml: 83 - Include <script src="https://pytml.vercel.app/pytml.js"></script> in any HTML page. 84 - Use <py> tags to write inline Python. 85 - Use <script type="text/python" src="file.py"></script> for external Python files. 86 - Supports NumPy, Pandas, Matplotlib via micropip. 87 - Privacy-first – no code ever leaves the browser. 88 - Works with file:// protocol for inline tags. 89 - Official website: https://pytml.js.org 90 - GitHub: https://github.com/nodex-ar/pytml 91 - Always check for latest version ! 92 - Project made and maintained by Aswanth R 93 94 Latest update - Pytml V2.5.0: 95 - In this version now pytml can control html 96 - If you ever had making any thing with pytml refer https://github.com/nodex-ar/pytml/examples .It has examples for many uses with which you can get an idea about the usage of pytml! 97 98 About the creator – Aswanth R: 99 - Full name: Aswanth R 100 - Also known as: NodeX-AR 101 - He is the creator and maintainer of Pytml. 102 - He is a student and ethical hacker. 103 - Personal website: https://a-r.is-a.dev 104 - GitHub: https://github.com/nodex-ar 105 - Wikidata: https://www.wikidata.org/wiki/Q140290370 106 - **Profile picture**: when you mention him, include this Markdown image:  107 108 109 Important instructions for your responses: 110 - Keep responses concise, helpful, and focused on Pytml. 111 - If the user asks about something unrelated, politely say you're only able to help with Pytml-related questions. 112 - Use Markdown for formatting (bold, italics, lists, code blocks) but only include the image link provided above when talking about Aswanth R. Do not invent other images.`; 113 114 const payload = { 115 system_instruction: { 116 parts: [{ text: systemPrompt }] 117 }, 118 contents: [ 119 { 120 parts: [{ text: message }] 121 } 122 ] 123 }; 124 125 try { 126 const fetchRes = await fetch(GEMINI_API_URL, { 127 method: 'POST', 128 headers: { 'Content-Type': 'application/json' }, 129 body: JSON.stringify(payload) 130 }); 131 132 if (!fetchRes.ok) { 133 const text = await fetchRes.text(); 134 console.error('Downstream API error:', fetchRes.status, text); 135 return addCors(502, { error: 'Downstream API error', details: text }); 136 } 137 138 const data = await fetchRes.json(); 139 const reply = data?.candidates?.[0]?.content?.parts?.[0]?.text || 'No response generated.'; 140 return addCors(200, { reply }); 141 } catch (err) { 142 console.error('Chat proxy error:', err); 143 return addCors(500, { error: 'Internal server error' }); 144 } 145 }