everything / apps /format.js
everydaycats's picture
Update apps/format.js
05995d7 verified
import express from 'express';
import { generateCompletion } from '../ai_engine.js';
import { supabase } from '../config/supabaseClient.js';
const router = express.Router();
// ==========================================
// 🧠 DYNAMIC SYSTEM PROMPT BUILDER
// ==========================================
const buildSystemPrompt = (personaName, personaPrompt) => `
You are an elite communication strategist, negotiator, and ghostwriter.
The user is in an ONGOING conversation and needs you to write their NEXT reply.
You will be given:
- The recent conversation history (showing what each side has said so far)
- The most recent message they received (the one they need to reply to)
- Optionally, a screenshot of the chat
Your job: write the PERFECT next message for the user to send.
PERSONA TO ADOPT:
Identity: ${personaName}
Behavior & Tone: ${personaPrompt}
STRICT RULES:
1. NEVER break character.
2. NEVER start with "As an AI..." or "Here is a response...".
3. Output ONLY the exact reply text — no labels, no quotes, no explanation.
4. Keep it concise and natural for modern texting/messaging.
5. Use the conversation history to stay contextually relevant. Do NOT reply generically.
6. If a screenshot is provided, read it carefully — it is the actual chat. Reply to the last visible message.
7. The reply must feel like a natural continuation of the conversation, not a cold opener. It should always be consistent with the chat history.
8. No hyphens (-) at all.
`;
// ==========================================
// 🗄️ CACHE MANAGEMENT
// ==========================================
let formatsCache = null;
let lastFormatsUpdate = 0;
const CACHE_DURATION = 3600000;
// ==========================================
// 📝 CONVERSATION HISTORY FORMATTER
// ==========================================
// Converts the last N messages into a readable thread for the AI.
// Images are noted but not sent (only the triggering image is sent as vision input).
const formatHistory = (messages =[], maxMessages = 6) => {
if (!messages.length) return null;
const recent = messages.slice(-maxMessages); // last 6 messages for context
const lines = recent.map(m => {
const role = m.sender === 'user' ? 'ME' : 'THEM';
if (m.type === 'image') return `${role}: [sent a screenshot]`;
return `${role}: ${m.content}`;
});
return lines.join('\n');
};
// ==========================================
// 🌐 ENDPOINTS
// ==========================================
/**
* 1. FETCH OFFICIAL FORMATS
*/
router.get('/formats', async (req, res) => {
try {
const now = Date.now();
if (formatsCache && (now - lastFormatsUpdate < CACHE_DURATION)) {
return res.json({ success: true, data: formatsCache, cached: true });
}
const { data, error } = await supabase
.from('replygenius_vibes')
.select('id, icon, label, hint, color, mockReply, prompt')
.order('created_at', { ascending: true });
if (error) throw error;
formatsCache = data;
lastFormatsUpdate = now;
res.json({ success: true, data, cached: false });
} catch (err) {
console.error('[FORMATS ERROR]', err.message);
res.status(500).json({ success: false, error: err.message });
}
});
/**
* 2. GENERATE REPLY
* Expects body: {
* type: 'text' | 'image',
* content: string, // current message text OR base64 image
* persona: object,
* history: Message[], // recent messages from the chat session
* model: string // 'seed', 'mistral', or 'grok'
* }
*/
router.post('/generate', async (req, res) => {
try {
// 💡 Extract 'model' from the request, default to 'seed' if missing
const { type, content, persona, history =[], model = 'seed' } = req.body;
if (!content) throw new Error('No content provided to analyze.');
if (!persona || !persona.label) throw new Error('Persona context is missing.');
console.log(`[REPLY GEN] Analyzing ${type} using persona: ${persona.label} | Model: ${model}`);
const aiSystemPrompt = buildSystemPrompt(
persona.label,
persona.prompt || 'Be highly intelligent, strategic, polite, and persuasive.'
);
// Build the conversation thread string (text messages only, images noted as placeholder)
const conversationHistory = formatHistory(history, 6);
// 💡 Map frontend model IDs to your actual ai_engine identifiers
const MODEL_MAP = {
'seed': 'bytedance-1.6',
'mistral': 'mistral-small', // Update this to your actual Mistral engine ID if different
'grok': 'grok-4.3' // Update this to your actual Grok engine ID if different
};
const modelToUse = MODEL_MAP[model] || MODEL_MAP['seed'];
let imagesPayload =[];
let userPrompt = '';
if (type === 'image') {
// Only send the triggering image to the vision model — prior images stay on device
imagesPayload = [content];
userPrompt = conversationHistory
? `Here is our conversation so far:\n${conversationHistory}\n\nI've also attached the latest screenshot. Read it and write my next reply. Output ONLY the reply text.`
: `Analyze this chat screenshot and write my next reply. Output ONLY the reply text.`;
} else {
userPrompt = conversationHistory
? `Here is our conversation so far:\n${conversationHistory}\n\nTheir latest message: "${content}"\n\nWrite my next reply. Output ONLY the reply text, nothing else.`
: `Write my reply to this message. Output ONLY the reply text, nothing else.\n\nMessage:\n"${content}"`;
}
const aiResult = await generateCompletion({
model: modelToUse,
prompt: userPrompt,
system_prompt: aiSystemPrompt,
images: imagesPayload,
forceJson: false,
});
if (!aiResult.success) throw new Error(aiResult.error);
let finalReply = (aiResult.data || '').trim();
// Strip any wrapping quotes the model might add
if (finalReply.startsWith('"') && finalReply.endsWith('"')) {
finalReply = finalReply.slice(1, -1).trim();
}
// Strip markdown bold/italic wrappers
finalReply = finalReply.replace(/^\*+|\*+$/g, '').trim();
if (!finalReply) throw new Error('AI returned an empty reply.');
res.json({ success: true, reply: finalReply });
} catch (err) {
console.error('[REPLY GEN ERROR]', err.message);
res.status(500).json({ success: false, error: err.message });
}
});
/**
* 3. FORCE CACHE CLEAR (Admin)
*/
router.post('/clear-cache', (req, res) => {
formatsCache = null;
lastFormatsUpdate = 0;
res.json({ success: true, message: 'Formats cache cleared.' });
});
export default router;