Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617const { stream } = require("@netlify/functions");
const dev = process.env.CONTEXT === "dev";
const allowedOrigins = [ "https://aesthetic.computer", "https://botce.ac", "https://chat-system.aesthetic.computer",];
// Supports both OpenAI and Anthropic Claude models// // Model detection: Any model name containing "claude" routes to Anthropic API//// CLAUDE 4.5 MODELS (Anthropic) - Current as of Jan 2026:// - claude-haiku-4-5-20251001 (Fastest, $1/MTok input, $5/MTok output) ⭐ RECOMMENDED for simple tasks// - claude-sonnet-4-5-20250929 (Best balance, $3/MTok input, $15/MTok output)// - claude-opus-4-5-20251101 (Most intelligent, $5/MTok input, $25/MTok output)//// LEGACY CLAUDE MODELS (may be deprecated):// - claude-sonnet-4-20250514, claude-opus-4-20250514// - claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022//// OPENAI MODELS:// - gpt-4o (Default for code hints)// - gpt-4o-mini (Default model)// - gpt-3.5-turbo//// USAGE EXAMPLES:// "code:claude-haiku-4-5-20251001" - Fast code generation with Haiku 4.5// "code:claude-sonnet-4-5-20250929" - High quality code with Sonnet 4.5// "character:gpt-4o-mini" - Character interactions with OpenAI//// ENVIRONMENT VARIABLES REQUIRED:// - ANTHROPIC_API_KEY (for Claude models)// - OPENAI_API_KEY (for OpenAI models)
async function handleClaudeRequest(messages, model, temperature, top_p, max_tokens, origin) { if (!process.env.ANTHROPIC_API_KEY) { console.error("ANTHROPIC_API_KEY not configured"); return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Claude API not configured", }; }
// Extract system messages for Anthropic API format const systemMessages = messages.filter(msg => msg.role === "system"); const nonSystemMessages = messages.filter(msg => msg.role !== "system"); // Combine system messages into a single system parameter const systemContent = systemMessages.map(msg => msg.content).join("\n\n"); const payload = { model, messages: nonSystemMessages, temperature, top_p, max_tokens, stream: true, };
// Add system parameter if we have system content if (systemContent) { payload.system = systemContent; }
// Add timeout and connection error handling const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout let res; try { res = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", }, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeoutId); } catch (error) { clearTimeout(timeoutId); console.error("Anthropic fetch error:", error); // Handle specific error types if (error.name === 'AbortError') { return { statusCode: 408, headers: { "Content-Type": "text/plain" }, body: "Request timeout", }; } if (error.code === 'ECONNRESET' || error.message.includes('ECONNRESET')) { return { statusCode: 503, headers: { "Content-Type": "text/plain" }, body: "Connection reset by server", }; } return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Network error", }; }
if (!res.ok) { const errorData = await res.json().catch(() => ({ error: "Failed to parse error response" })); console.error("Anthropic request failed:", JSON.stringify(errorData, null, 2)); return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Error", }; }
const stream = new ReadableStream({ async start(controller) { let buffer = ""; let tokenUsage = null;
try { const reader = res.body.getReader(); while (true) { let result; try { result = await reader.read(); } catch (readError) { console.error("Stream read error:", readError); if (readError.code === 'ECONNRESET' || readError.message.includes('ECONNRESET')) { controller.enqueue(new TextEncoder().encode("\n\n[Connection lost - partial response]")); } break; } if (result.done) break; const chunkText = new TextDecoder().decode(result.value); buffer += chunkText;
// Process complete SSE events (separated by double newlines) let boundary = buffer.indexOf("\n\n"); while (boundary !== -1) { const sseEvent = buffer.slice(0, boundary).trim(); buffer = buffer.slice(boundary + 2); boundary = buffer.indexOf("\n\n");
if (sseEvent) { // Parse SSE event (can have multiple lines: event: type, data: json) const lines = sseEvent.split('\n'); let eventType = ''; let eventData = ''; for (const line of lines) { if (line.startsWith('event: ')) { eventType = line.slice(7).trim(); } else if (line.startsWith('data: ')) { eventData = line.slice(6).trim(); } } if (eventData) { try { const json = JSON.parse(eventData); // Handle different Anthropic event types if (json.type === "content_block_delta" && json.delta?.type === "text_delta") { const text = json.delta.text || ""; if (text) { controller.enqueue(new TextEncoder().encode(text)); } } else if (json.type === "message_stop") { // Log token usage before closing if (tokenUsage) { const inputTokens = tokenUsage.input_tokens || 0; const outputTokens = tokenUsage.output_tokens || 0; const totalTokens = inputTokens + outputTokens; const maxTokens = max_tokens; // Create visual progress bar for output tokens vs limit const outputProgress = Math.min(outputTokens / maxTokens, 1.0); const barLength = 20; const filledBars = Math.floor(outputProgress * barLength); // Terminal escape codes for colors const colors = { green: '\x1b[32m', orange: '\x1b[33m', red: '\x1b[31m', reset: '\x1b[0m' }; // Color coding: green if under 80%, orange if 80-95%, red if over 95% let color = colors.green; if (outputProgress > 0.95) color = colors.red; else if (outputProgress > 0.8) color = colors.orange; const outputBar = color + '█'.repeat(filledBars) + colors.reset + colors.red + '█'.repeat(barLength - filledBars) + colors.reset; const percentage = Math.round(outputProgress * 100); console.log(`📊 Claude Tokens: In:${inputTokens} Out:${outputTokens}/${maxTokens} ${outputBar} ${percentage}%`); // Check if Claude hit the maximum token limit (100% usage) if (outputTokens === maxTokens) { console.log(`🚨 Claude hit maximum token limit: ${outputTokens}/${maxTokens} tokens`); // Send error message to client for compilation error handling const errorMessage = "COMPILATION ERROR: Maximum tokens reached - response truncated"; controller.enqueue(new TextEncoder().encode(errorMessage)); } } controller.close(); return; } else if (json.type === "message_start" && json.message?.usage) { // Capture initial token usage (input tokens) tokenUsage = json.message.usage; } else if (json.type === "message_delta" && json.usage) { // Update token usage with output tokens tokenUsage = { ...tokenUsage, ...json.usage }; } // Skip other event types (ping, content_block_start, etc.) } catch (e) { console.error("Error parsing Anthropic response:", e); // Don't error the whole stream for one bad event } } } } } controller.close(); } catch (e) { console.error("Stream error:", e); // Try to send a helpful error message to the client try { if (e.code === 'ECONNRESET' || e.message.includes('ECONNRESET')) { controller.enqueue(new TextEncoder().encode("\n\n[Connection reset - response may be incomplete]")); } else { controller.enqueue(new TextEncoder().encode("\n\n[Stream error occurred]")); } } catch (controllerError) { console.error("Error enqueueing error message:", controllerError); } // Close the stream gracefully instead of erroring try { controller.close(); } catch (closeError) { console.error("Error closing controller:", closeError); controller.error(e); } } }, });
return { headers: { "Content-Type": "text/event-stream", "Access-Control-Allow-Origin": origin, }, statusCode: 200, body: stream, };}
exports.handler = stream(async (event) => { const origin = event.headers.origin;
// CORS handling if (!dev && !allowedOrigins.includes(origin)) { return { statusCode: 403, headers: { "Content-Type": "text/plain" }, body: "Access denied.", }; }
// Handle OPTIONS preflight requests if (event.httpMethod === "OPTIONS") { return { statusCode: 200, headers: { "Content-Type": "text/plain", "Access-Control-Allow-Origin": origin, "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, body: "Success!", }; }
if (event.httpMethod !== "POST") { return { statusCode: 405, headers: { "Content-Type": "text/plain" }, body: "Wrong method.", }; }
const body = JSON.parse(event.body); let { messages, hint } = body; console.log("🧠 Hint:", hint);
try { messages = messages?.map((message) => { return { role: message.by, content: message.text }; });
// Defaults let temperature = 1; let top_p = 1; let max_tokens = 256;
if (hint.startsWith("character")) { temperature = 1; top_p = 0.5; max_tokens = 256; }
// Parse model from hint - handle both "model" and "prefix:model" formats let model; if (hint.includes(":")) { model = hint.split(":")[1] || "gpt-4o-mini"; } else if (hint.includes("claude") || hint.includes("gpt") || hint.includes("o1")) { // Direct model name without prefix model = hint; } else { model = "gpt-4o-mini"; }
if (hint.startsWith("code")) { // Only set default model if none was specified in the hint const hasModelInHint = hint.includes(":") && hint.split(":")[1] !== ""; console.log("🔧 Code hint detected, hasModelInHint:", hasModelInHint); if (!hasModelInHint) { model = "gpt-4o"; console.log("🔧 No model specified, using default:", model); } else { console.log("🔧 Using model from hint:", model); } max_tokens = 4096; // Increased for longer, more detailed code responses (was 512) temperature = 1; console.log("♦ Using tokens:", max_tokens); console.log("🌡 Temperature:", temperature); } console.log("🔧 Final model from hint:", model); // Set appropriate token limits for Claude models if (model.includes("claude")) { max_tokens = 4096; // 4096; // Claude models get 1024 tokens for code generation temperature = 1; console.log("♦️ Using tokens for Claude:", max_tokens); console.log("🌡️ Temperature:", temperature); }
// Check if it's a Claude model const isClaudeModel = model.includes("claude"); // Extract the last user message as the prompt for logging const lastUserMessage = messages.filter(msg => msg.role === "user").pop(); const prompt = lastUserMessage?.content || "No user message found"; // Log what's being asked and which model will be used console.log(`🤖 Model: ${model} ${isClaudeModel ? '(Anthropic)' : '(OpenAI)'}`); console.log(`❓ Prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`); if (isClaudeModel) { return handleClaudeRequest(messages, model, temperature, top_p, max_tokens, origin); } else { // OpenAI request if (!process.env.OPENAI_API_KEY) { console.error("OPENAI_API_KEY not configured"); return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "OpenAI API not configured", }; }
// Use max_completion_tokens for o1 models and other reasoning models, max_tokens for standard models const payload = { model, messages, temperature, top_p, frequency_penalty: 0, presence_penalty: 0, stream: true, stream_options: { include_usage: true }, // Request usage data in stream n: 1, };
// Add the appropriate token limit parameter based on model // o1 models and some newer models require max_completion_tokens if (model.startsWith('o1') || model.includes('o4') || model.startsWith('o4')) { payload.max_completion_tokens = max_tokens; // o1 models also don't support temperature and top_p if (model.startsWith('o1') || model.startsWith('o4')) { delete payload.temperature; delete payload.top_p; } } else { payload.max_tokens = max_tokens; }
// Add timeout and connection error handling const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
let res; try { res = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeoutId); } catch (error) { clearTimeout(timeoutId); console.error("OpenAI fetch error:", error); // Handle specific error types if (error.name === 'AbortError') { return { statusCode: 408, headers: { "Content-Type": "text/plain" }, body: "Request timeout", }; } if (error.code === 'ECONNRESET' || error.message.includes('ECONNRESET')) { return { statusCode: 503, headers: { "Content-Type": "text/plain" }, body: "Connection reset by server", }; } return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Network error", }; }
if (!res.ok) { const errorData = await res.json(); console.error("Request failed:", errorData); return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Error", }; }
const stream = new ReadableStream({ async start(controller) { let buffer = ""; let tokenUsage = null;
try { const reader = res.body.getReader(); while (true) { let result; try { result = await reader.read(); } catch (readError) { console.error("Stream read error:", readError); if (readError.code === 'ECONNRESET' || readError.message.includes('ECONNRESET')) { controller.enqueue(new TextEncoder().encode("\n\n[Connection lost - partial response]")); } break; } if (result.done) break; buffer += new TextDecoder().decode(result.value);
let boundary = buffer.indexOf("\n\n"); while (boundary !== -1) { const part = buffer.slice(0, boundary).trim(); buffer = buffer.slice(boundary + 2); boundary = buffer.indexOf("\n\n");
if (part.startsWith("data: ")) { const jsonString = part.slice(6).trim(); if (jsonString === "[DONE]") { // Log token usage before closing if (tokenUsage) { const inputTokens = tokenUsage.prompt_tokens || 0; const outputTokens = tokenUsage.completion_tokens || 0; const totalTokens = inputTokens + outputTokens; const maxTokens = max_tokens; // Create visual progress bar for output tokens vs limit const outputProgress = Math.min(outputTokens / maxTokens, 1.0); const barLength = 20; const filledBars = Math.floor(outputProgress * barLength); // Terminal escape codes for colors const colors = { green: '\x1b[32m', orange: '\x1b[33m', red: '\x1b[31m', reset: '\x1b[0m' }; // Color coding: green if under 80%, orange if 80-95%, red if over 95% let color = colors.green; if (outputProgress > 0.95) color = colors.red; else if (outputProgress > 0.8) color = colors.orange; const outputBar = color + '█'.repeat(filledBars) + colors.reset + colors.red + '█'.repeat(barLength - filledBars) + colors.reset; const percentage = Math.round(outputProgress * 100); console.log(`📊 OpenAI Tokens: In:${inputTokens} Out:${outputTokens}/${maxTokens} ${outputBar} ${percentage}%`); // Check if OpenAI hit the maximum token limit (100% usage) if (outputTokens === maxTokens) { console.log(`🚨 OpenAI hit maximum token limit: ${outputTokens}/${maxTokens} tokens`); // Send error message to client for compilation error handling const errorMessage = "COMPILATION ERROR: Maximum tokens reached - response truncated"; controller.enqueue(new TextEncoder().encode(errorMessage)); } } else { // No token usage data available from OpenAI - log that we completed without usage info console.log(`📊 OpenAI Tokens: Usage data not available (limit: ${max_tokens}) - Stream completed successfully`); } controller.close(); return; } try { const json = JSON.parse(jsonString); // Capture token usage if present (can appear in final chunks) if (json.usage) { tokenUsage = json.usage; // console.log("🔍 Found token usage:", tokenUsage); } // Also check for usage in choices (some OpenAI responses put it there) if (json.choices?.[0]?.usage) { tokenUsage = json.choices[0].usage; // console.log("🔍 Found token usage in choices:", tokenUsage); } const text = json.choices[0]?.delta?.content || ""; if (text) { controller.enqueue(new TextEncoder().encode(text)); } } catch (e) { console.error("Error parsing part:", e); // Don't error the whole stream for one bad event } } } } controller.close(); } catch (e) { console.error("Stream error:", e); // Try to send a helpful error message to the client try { if (e.code === 'ECONNRESET' || e.message.includes('ECONNRESET')) { controller.enqueue(new TextEncoder().encode("\n\n[Connection reset - response may be incomplete]")); } else { controller.enqueue(new TextEncoder().encode("\n\n[Stream error occurred]")); } } catch (controllerError) { console.error("Error enqueueing error message:", controllerError); } // Close the stream gracefully instead of erroring try { controller.close(); } catch (closeError) { console.error("Error closing controller:", closeError); controller.error(e); } } }, });
return { headers: { "Content-Type": "text/event-stream", "Access-Control-Allow-Origin": origin, }, statusCode: 200, body: stream, }; } } catch (error) { console.error("Failed to process the request:", error); return { statusCode: 500, headers: { "Content-Type": "text/plain" }, body: "Error", }; }});