modified_ai_server / aiEngine.js
everydaytok's picture
Update aiEngine.js
01a73c9 verified
raw
history blame
7.97 kB
import dotenv from 'dotenv';
import fs from 'fs';
import path from 'path';
dotenv.config();
const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:7860";
// --- PROMPT LOADING ---
let prompts = {};
try {
const promptsPath = path.resolve('./prompts.json');
if (fs.existsSync(promptsPath)) {
prompts = JSON.parse(fs.readFileSync(promptsPath, 'utf8'));
}
} catch (e) {
console.error("Prompt Load Error:", e);
}
// --- HISTORY FLATTENER (WITH ECONOMIC LIMITS) ---
const flattenHistory = (history, currentInput, systemPrompt, limit = 10, gdd = null) => {
// ECONOMIC CAP: Slice history to the last 'limit' messages to save tokens
const recentHistory = history.slice(-limit);
let context = recentHistory.map(m => {
const roleName = m.role === 'model' ? 'Assistant' : 'User';
return `${roleName}: ${m.parts[0].text}`;
}).join('\n');
// Inject GDD only if provided (usually for PM)
const projectAnchor = gdd ? `[PROJECT GDD REFERENCE]:\n${gdd}\n\n` : "";
return `System: ${systemPrompt}\n\n${projectAnchor}${context}\nUser: ${currentInput}\nAssistant:`;
};
// --- STREAM HANDLER & USAGE PARSER ---
const handleStreamResponse = async (response, onThink, onOutput) => {
if (!response.ok) throw new Error(`Stream Error: ${response.statusText}`);
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let fullStreamData = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
fullStreamData += chunk;
// Streaming Logic: Don't show Usage to frontend, parse thoughts
if (!chunk.includes("__USAGE__")) {
if (chunk.includes("__THINK__")) {
const parts = chunk.split("__THINK__");
if (parts[0] && onOutput) onOutput(parts[0]);
if (parts[1] && onThink) onThink(parts[1]);
} else {
if (onOutput) onOutput(chunk);
}
}
}
// --- USAGE EXTRACTION FOR BILLING ---
let usage = { totalTokenCount: 0, inputTokens: 0, outputTokens: 0 };
let finalCleanText = fullStreamData;
if (fullStreamData.includes("__USAGE__")) {
const parts = fullStreamData.split("__USAGE__");
finalCleanText = parts[0]; // The actual text content
const usageRaw = parts[1];
try {
const parsedUsage = JSON.parse(usageRaw);
usage.totalTokenCount = parsedUsage.totalTokenCount || 0;
usage.inputTokens = parsedUsage.inputTokens || 0;
usage.outputTokens = parsedUsage.outputTokens || 0;
} catch (e) {
console.warn("Usage Parse Failed in Engine:", e);
}
}
// Clean any remaining tags
finalCleanText = finalCleanText.split("__THINK__")[0].trim();
return { text: finalCleanText, usage };
};
export const AIEngine = {
// --- STREAMING METHODS (Main Loop) ---
callPMStream: async (history, input, onThink, onOutput, gdd = null) => {
const systemPrompt = prompts.pm_system_prompt || "You are a Project Manager.";
// ECONOMIC CAP: 15 messages max for PM to maintain context but control costs
const prompt = flattenHistory(history, input, systemPrompt, 15, gdd);
const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "claude",
prompt: prompt,
system_prompt: systemPrompt
})
});
return await handleStreamResponse(response, onThink, onOutput);
},
callWorkerStream: async (history, input, onThink, onOutput, images = []) => {
const systemPrompt = prompts.worker_system_prompt || "You are a Senior Engineer.";
// ECONOMIC CAP: 8 messages max for Worker (they only need recent context)
const prompt = flattenHistory(history, input, systemPrompt, 8, null);
const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "gpt",
prompt: prompt,
system_prompt: systemPrompt,
images: images
})
});
return await handleStreamResponse(response, onThink, onOutput);
},
// --- BLOCKING CALLS (Background Initialization) ---
callPM: async (history, input, gdd = null) => {
const systemPrompt = prompts.pm_system_prompt || "You are a Project Manager.";
const prompt = flattenHistory(history, input, systemPrompt, 15, gdd); // Limit 15
const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "claude",
prompt: prompt,
system_prompt: systemPrompt
})
});
const result = await response.json();
return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
},
callWorker: async (history, input) => {
const systemPrompt = prompts.worker_system_prompt || "You are a Senior Engineer.";
const prompt = flattenHistory(history, input, systemPrompt, 8, null); // Limit 8
const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "gpt",
prompt: prompt,
system_prompt: systemPrompt
})
});
const result = await response.json();
return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
},
// --- UTILITIES (One-off calls) ---
generateEntryQuestions: async (desc) => {
const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "gpt",
prompt: `Analyze this project idea: ${desc}`,
system_prompt: prompts.analyst_system_prompt || "Output JSON only."
})
});
const result = await response.json();
// Return parsed data AND usage for billing
return { ...JSON.parse(result.data), usage: result.usage };
},
gradeProject: async (desc, ans) => {
const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "gpt",
prompt: `Grade this project. Description: ${desc} Answers: ${JSON.stringify(ans)}`,
system_prompt: prompts.analyst_system_prompt || "Output JSON only."
})
});
const result = await response.json();
const parsed = JSON.parse(result.data);
parsed.usage = result.usage; // Attach usage for billing
return parsed;
},
generateImage: async (prompt) => {
try {
const response = await fetch(`${REMOTE_SERVER_URL}/api/image`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const result = await response.json();
return result; // Expected { image: "base64..." }
} catch (e) {
console.error("Image Gen Error:", e);
return null;
}
}
};