Spaces:
Running
Running
File size: 6,658 Bytes
23bbcba fdb5d73 23bbcba fdb5d73 23bbcba fdb5d73 23bbcba fdb5d73 05995d7 23bbcba c696f2f 23bbcba c696f2f 23bbcba fdb5d73 620707d fdb5d73 23bbcba c696f2f 23bbcba c696f2f 23bbcba c696f2f fdb5d73 620707d fdb5d73 23bbcba c696f2f 620707d c696f2f 620707d c696f2f fdb5d73 620707d bc8b2d0 620707d c696f2f fdb5d73 c696f2f fdb5d73 c696f2f fdb5d73 c696f2f fdb5d73 c696f2f 23bbcba fdb5d73 c696f2f 23bbcba c696f2f 23bbcba c696f2f 23bbcba | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | 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; |