everything / ai_engine.js
everydaycats's picture
Update ai_engine.js
5125142 verified
Raw History Blame Contribute Delete
6.94 kB
import OpenAI from "openai";
const openRouterAI = new OpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
defaultHeaders: {
"HTTP-Referer": process.env.YOUR_SITE_URL || "https://everydaycats.web.app",
"X-Title": process.env.YOUR_SITE_NAME || "Aura Scanner",
}
});
// --- MODEL ROUTER ---
const getModelId = (modelName) => {
switch (modelName?.toLowerCase()) {
case "llama": case "maverick": return "meta-llama/llama-4-maverick:floor";
case "llama-3.2-11b": return "meta-llama/llama-3.2-11b-vision-instruct:free";
case "qwen-2-vl-7b": return "qwen/qwen-2-vl-7b-instruct:free";
case "nemotron-12b": return "nvidia/nemotron-nano-12b-v2-vl:free";
case "gpt": case "gpt-5.4-nano": return "openai/gpt-5.4-nano";
case "qwen-non": return "qwen/qwen3-vl-30b-a3b-instruct";
case "bytedance-1.6": return "bytedance-seed/seed-1.6-flash:floor";
case "mistral-small": return "mistralai/mistral-small-2603:floor";
case "grok-4.3": return "x-ai/grok-4.3:floor";
case "qwen": default: return "qwen/qwen3-vl-30b-a3b-thinking:floor";
}
};
// --- IMAGE MODEL ROUTER ---
const getImageModelId = (tier) => {
switch (tier) {
case "premium": return "bytedance-seed/seedream-4.5";
case "quick": return "sourceful/riverflow-v2-fast";
default: return "sourceful/riverflow-v2-fast";
}
};
// --- ERROR LOGGER ---
const logOpenRouterError = (error, context) => {
const status = error.status || error.response?.status;
if (status === 402) console.error(`💸 [402] ${context}: Insufficient credits.`);
else if (status === 429) console.error(`🚨 [429] ${context}: Rate limit exceeded.`);
else if (status === 401) console.error(`🛑 [401] ${context}: Invalid API Key.`);
else if (status === 400) console.error(`⚠️ [400] ${context}: Bad request / invalid payload.`);
else if (status >= 500) console.error(`🔥 [${status}] ${context}: Provider backend down.`);
else if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT')
console.error(`⏳ [TIMEOUT] ${context}: Connection dropped.`);
else console.error(`❌ [ERROR] ${context}:`, error.message || error);
};
// --- TEXT COMPLETION ---
// forceJson: true → response_format json_object (structured calls, e.g. aura scan)
// forceJson: false → plain text (e.g. ReplyGenius chat replies)
export const generateCompletion = async ({ model, prompt, system_prompt, images, forceJson = true }) => {
try {
const targetModel = getModelId(model);
let messagesPayload = [
{ role: "system", content: system_prompt || "You are an elite AI assistant." }
];
if (images?.length > 0) {
let userContent = [{ type: "text", text: prompt }];
images.forEach((imgStr) => {
if (imgStr.length > 7000000) throw new Error("Image too large. Max ~4MB.");
const formattedImg = imgStr.startsWith('data:image')
? imgStr : `data:image/jpeg;base64,${imgStr}`;
userContent.push({ type: "image_url", image_url: { url: formattedImg } });
});
messagesPayload.push({ role: "user", content: userContent });
} else {
messagesPayload.push({ role: "user", content: prompt });
}
const requestParams = {
model: targetModel,
messages: messagesPayload,
max_tokens: 1200,
};
// ✅ Only enforce JSON schema when explicitly requested.
// Sending response_format with a plain-text prompt causes 400 on most providers.
if (forceJson) {
requestParams.response_format = { type: "json_object" };
}
const response = await openRouterAI.chat.completions.create(requestParams);
return {
success: true,
data: response.choices[0].message.content,
usage: response.usage,
model_used: targetModel
};
} catch (error) {
logOpenRouterError(error, "generateCompletion");
return { success: false, error: error.message, status: error.status || 500 };
}
};
// --- STREAMING TEXT COMPLETION ---
export const streamCompletion = async ({ model, prompt, system_prompt, images, res }) => {
try {
const targetModel = getModelId(model);
let messagesPayload = [
{ role: "system", content: system_prompt || "You are an elite AI assistant." }
];
if (images?.length > 0) {
let userContent = [{ type: "text", text: prompt }];
images.forEach((imgStr) => {
if (imgStr.length > 7000000) throw new Error("Image too large. Max ~4MB.");
const formattedImg = imgStr.startsWith('data:image')
? imgStr : `data:image/jpeg;base64,${imgStr}`;
userContent.push({ type: "image_url", image_url: { url: formattedImg } });
});
messagesPayload.push({ role: "user", content: userContent });
} else {
messagesPayload.push({ role: "user", content: prompt });
}
const stream = await openRouterAI.chat.completions.create({
model: targetModel,
messages: messagesPayload,
stream: true,
stream_options: { include_usage: true },
max_tokens: 2000,
});
let totalTokenCount = 0;
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.reasoning_content) res.write(`__THINK__${delta.reasoning_content}`);
else if (delta?.content) res.write(delta.content);
if (chunk.usage) totalTokenCount = chunk.usage.total_tokens;
}
res.write(`__USAGE__${JSON.stringify({ totalTokenCount })}`);
res.end();
} catch (error) {
logOpenRouterError(error, "streamCompletion");
res.write(`\n\n[ERROR]: ${error.message}`);
res.end();
}
};
// --- IMAGE GENERATION ---
export const generateImage = async ({ prompt, tier = "quick" }) => {
try {
const model = getImageModelId(tier);
const response = await fetch("https://openrouter.ai/api/v1/images/generations", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": process.env.YOUR_SITE_URL || "https://everydaycats.web.app",
"X-Title": process.env.YOUR_SITE_NAME || "lightnovel.ai",
},
body: JSON.stringify({ model, prompt, n: 1, size: "1024x1024" })
});
if (!response.ok) {
const err = await response.json();
throw Object.assign(new Error(err?.error?.message || "Image gen failed"), { status: response.status });
}
const data = await response.json();
const imageUrl = data?.data?.[0]?.url;
if (!imageUrl) throw new Error("No image URL returned.");
return { success: true, url: imageUrl, model_used: model };
} catch (error) {
logOpenRouterError(error, "generateImage");
return { success: false, error: error.message, status: error.status || 500 };
}
};