| const COOKIES = `__gads=ID=fa119d4bd4f0deb4:T=1767802127:RT=1769915891:S=ALNI_MadOwOvOyuYGa63fhmTXAOSMuMPzA; __gpi=UID=000011dec0f55e42:T=1767802127:RT=1769915891:S=ALNI_MbmxAr7exkcHsXJpxvp5Jda40UEhQ; __eoi=ID=1b168e49673ee98a:T=1767802127:RT=1769915891:S=AA-Afjb1OtBK1G-c16acoNoP6uKS; NEXT_LOCALE=zh; u_device_id=d403ec97-7698-4c1a-a5ad-4121004056df; home_chat_id=f2b45c03-aeee-4c9e-862f-50aa2e7ba549; __Secure-authjs.callback-url=https%3A%2F%2Fapp.unlimitedai.chat; __Host-authjs.csrf-token=85724d6088bf62366e70be6dca13329f84a248a2e682b9469ea9c4edea86294b%7Cb6d8881d70b2a9fcb7b3bdc9c67cec59c17caba2225b24909cddbc1d6092d9c3; _clck=1ehq05y%5E2%5Eg5e%5E0%5E2302; _clsk=1j5vo0j%5E1776785008641%5E1%5E1%5Ez.clarity.ms%2Fcollect`; |
|
|
| |
| const TARGET_API = "https://app.unlimitedai.chat/api/chat"; |
|
|
| |
| const DEVICE_ID = "d403ec97-7698-4c1a-a5ad-4121004056df"; |
|
|
| |
|
|
| |
| const url = new URL(req.url); |
| |
| |
| if (req.method === 'OPTIONS') { |
| return new Response(null, { |
| status: 204, |
| headers: corsHeaders() |
| }); |
| } |
|
|
| |
| if (url.pathname === '/') { |
| return new Response(getUI(), { |
| headers: { 'Content-Type': 'text/html; charset=utf-8' } |
| }); |
| } |
|
|
| |
| if (url.pathname === '/v1/models' && req.method === 'GET') { |
| return new Response(JSON.stringify({ |
| object: "list", |
| data: [ |
| { id: "chat-model-reasoning", object: "model", created: Date.now(), owned_by: "unlimitedai" } |
| ] |
| }), { |
| headers: { 'Content-Type': 'application/json', ...corsHeaders() } |
| }); |
| } |
|
|
| |
| if (url.pathname === '/v1/chat/completions' && req.method === 'POST') { |
| return handleChatCompletion(req); |
| } |
|
|
| return new Response("Not Found", { status: 404 }); |
| } |
|
|
| async function handleChatCompletion(req: Request): Promise<Response> { |
| try { |
| const body = await req.json(); |
| const model = body.model || "chat-model-reasoning"; |
| const openAiMessages = body.messages || []; |
|
|
| |
| |
| const now = new Date().toISOString(); |
| const uaiMessages = openAiMessages.map((msg: any, index: number) => ({ |
| id: crypto.randomUUID(), |
| role: msg.role, |
| content: msg.content, |
| parts: [{ type: "text", text: msg.content }], |
| createdAt: now |
| })); |
|
|
| |
| |
| const chatId = crypto.randomUUID(); |
|
|
| const payload = { |
| chatId: chatId, |
| messages: uaiMessages, |
| selectedChatModel: model, |
| selectedCharacter: null, |
| selectedStory: null, |
| deviceId: DEVICE_ID, |
| locale: "zh" |
| }; |
|
|
| |
| const headers: Record<string, string> = { |
| "accept": "*/*", |
| "content-type": "application/json", |
| "cookie": COOKIES, |
| "origin": "https://app.unlimitedai.chat", |
| "referer": "https://app.unlimitedai.chat/zh", |
| "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0", |
| "x-next-intl-locale": "zh", |
| "sec-ch-ua": '"Microsoft Edge";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', |
| "sec-ch-ua-mobile": "?0", |
| "sec-ch-ua-platform": '"Windows"', |
| "sec-fetch-dest": "empty", |
| "sec-fetch-mode": "cors", |
| "sec-fetch-site": "same-origin" |
| }; |
|
|
| |
| const upstreamRes = await fetch(TARGET_API, { |
| method: "POST", |
| headers, |
| body: JSON.stringify(payload) |
| }); |
|
|
| if (!upstreamRes.ok) { |
| const errText = await upstreamRes.text(); |
| return new Response(JSON.stringify({ error: { message: `Upstream Error ${upstreamRes.status}: ${errText}` } }), { |
| status: upstreamRes.status, |
| headers: { 'Content-Type': 'application/json' } |
| }); |
| } |
|
|
| |
| const { readable, writable } = new TransformStream(); |
| const writer = writable.getWriter(); |
| const encoder = new TextEncoder(); |
| |
| |
| const openAiChatId = crypto.randomUUID(); |
| |
| (async () => { |
| try { |
| const reader = upstreamRes.body?.getReader(); |
| if (!reader) throw new Error("No body"); |
| const decoder = new TextDecoder(); |
| let buffer = ""; |
|
|
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
|
|
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split('\n'); |
| buffer = lines.pop() || ""; |
|
|
| for (const line of lines) { |
| if (!line.trim()) continue; |
| try { |
| const json = JSON.parse(line); |
| |
| |
| if (json.type === 'delta' && json.delta) { |
| const content = json.delta; |
| |
| |
| const sseChunk = { |
| id: openAiChatId, |
| object: "chat.completion.chunk", |
| created: Math.floor(Date.now() / 1000), |
| model: model, |
| choices: [{ |
| index: 0, |
| delta: { content: content }, |
| finish_reason: null |
| }] |
| }; |
| |
| writer.write(encoder.encode(`data: ${JSON.stringify(sseChunk)}\n\n`)); |
| } |
| } catch (e) { |
| console.error("Parse error", e); |
| } |
| } |
| } |
|
|
| |
| writer.write(encoder.encode(`data: ${JSON.stringify({ |
| id: openAiChatId, |
| object: "chat.completion.chunk", |
| created: Math.floor(Date.now() / 1000), |
| model: model, |
| choices: [{ |
| index: 0, |
| delta: {}, |
| finish_reason: "stop" |
| }] |
| })}\n\n`)); |
| |
| writer.write(encoder.encode('data: [DONE]\n\n')); |
|
|
| } catch (err: any) { |
| console.error("Stream processing error:", err); |
| writer.write(encoder.encode(`data: ${JSON.stringify({ error: { message: err.message } })}\n\n`)); |
| } finally { |
| writer.close(); |
| } |
| })(); |
|
|
| return new Response(readable, { |
| headers: { |
| "Content-Type": "text/event-stream; charset=utf-8", |
| "Cache-Control": "no-cache", |
| "Connection": "keep-alive", |
| ...corsHeaders() |
| } |
| }); |
|
|
| } catch (error: any) { |
| return new Response(JSON.stringify({ error: { message: error.message } }), { |
| status: 500, |
| headers: { 'Content-Type': 'application/json' } |
| }); |
| } |
| } |
|
|
| |
|
|
| function corsHeaders() { |
| return { |
| 'Access-Control-Allow-Origin': '*', |
| 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', |
| 'Access-Control-Allow-Headers': 'Content-Type, Authorization', |
| 'Access-Control-Max-Age': '86400', |
| }; |
| } |
|
|
| |
|
|
| function getUI() { |
| return `<!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>UnlimitedAI Proxy Test</title> |
| <style> |
| body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; } |
| .container { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } |
| textarea { width: 100%; height: 100px; margin-bottom: 10px; padding: 10px; border-radius: 5px; border: 1px solid #ccc; } |
| button { padding: 10px 20px; background: #007AFF; color: white; border: none; border-radius: 5px; cursor: pointer; } |
| button:disabled { background: #ccc; } |
| #output { margin-top: 20px; background: #333; color: #fff; padding: 15px; border-radius: 5px; min-height: 100px; white-space: pre-wrap; font-family: monospace; } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h2>UnlimitedAI Proxy (OpenAI 兼容)</h2> |
| <p>API Endpoint: <code>http://localhost:8000/v1/chat/completions</code></p> |
| <textarea id="prompt" placeholder="输入问题...">介绍一下人工智能</textarea> |
| <button id="sendBtn" onclick="send()">发送</button> |
| <div id="output">等待响应...</div> |
| </div> |
| |
| <script> |
| async function send() { |
| const btn = document.getElementById('sendBtn'); |
| const out = document.getElementById('output'); |
| const prompt = document.getElementById('prompt').value; |
| |
| btn.disabled = true; |
| out.textContent = ''; |
| |
| try { |
| const res = await fetch('/v1/chat/completions', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| model: 'chat-model-reasoning', |
| messages: [{ role: 'user', content: prompt }], |
| stream: true |
| }) |
| }); |
| |
| const reader = res.body.getReader(); |
| const decoder = new TextDecoder(); |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| const chunk = decoder.decode(value); |
| const lines = chunk.split('\\n'); |
| |
| for (const line of lines) { |
| if (line.startsWith('data: ')) { |
| const dataStr = line.substring(6); |
| if (dataStr === '[DONE]') continue; |
| try { |
| const data = JSON.parse(dataStr); |
| const content = data.choices?.[0]?.delta?.content || ''; |
| out.textContent += content; |
| } catch (e) {} |
| } |
| } |
| } |
| } catch (err) { |
| out.textContent = 'Error: ' + err.message; |
| } finally { |
| btn.disabled = false; |
| } |
| } |
| </script> |
| </body> |
| </html>`; |
| } |
|
|
| |
|
|
| const PORT = 8000; |
| console.log(`UnlimitedAI Proxy running on http://localhost:${PORT}`); |
| await Deno.serve({ port: PORT }, handler); |