|
|
const axios = require('axios'); |
|
|
const fs = require('fs'); |
|
|
const path = require('path'); |
|
|
const { Client } = require('@gradio/client'); |
|
|
const User = require('../models/User'); |
|
|
const ChatSession = require('../models/ChatSession'); |
|
|
const Message = require('../models/Message'); |
|
|
const ErrorResponse = require('../utils/errorResponse'); |
|
|
const asyncHandler = require('../utils/asyncHandler'); |
|
|
const { processFile } = require('../utils/fileProcessor'); |
|
|
const { uploadToVault } = require('../services/vaultService'); |
|
|
const { searchWeb } = require('../utils/webSearch'); |
|
|
const { searchWiki, calculate, getTime, getCryptoPrice, getNews } = require('../utils/specialTools'); |
|
|
|
|
|
|
|
|
const MODELS = { |
|
|
'Codex Velox': 'gpt-oss-120b', |
|
|
'Codex Fabrica': 'zai-glm-4.7', |
|
|
'Codex Ratio': 'llama-3.3-70b', |
|
|
'Codex Nexus': 'Qwen/Qwen2.5-72B-Instruct', |
|
|
'Codex Fero': 'moonshotai/Kimi-K2-Thinking', |
|
|
'Codex Zenith': 'Qwen/Qwen3-Coder-480B-A35B-Instruct', |
|
|
'Codex Magna': 'moonshotai/Kimi-K2.5', |
|
|
'Codex Vision': 'black-forest-labs/FLUX.1-schnell' |
|
|
}; |
|
|
|
|
|
const PROVIDERS = {}; |
|
|
|
|
|
const SPECIALIZATIONS = { |
|
|
'Codex Velox': 'ROLE: RAPID_RESPONDER. Optimized for speed.', |
|
|
'Codex Fabrica': 'ROLE: SENIOR_BUILDER. Optimized for production code.', |
|
|
'Codex Ratio': 'ROLE: LOGIC_ANALYST. Optimized for step-by-step reasoning.', |
|
|
'Codex Nexus': 'ROLE: GENERALIST_ELITE. Deep understanding and versatility.', |
|
|
'Codex Fero': 'CORE_PROTOCOL: FERO_ENGINE. Powered by Kimi K2 Thinking. Advanced system architecture.', |
|
|
'Codex Zenith': 'CORE_PROTOCOL: ZENITH_REASONING. Powered by Qwen3-480B. The ultimate reasoning core.', |
|
|
'Codex Magna': 'CORE_PROTOCOL: MAGNA_FRONTIER. Powered by Kimi 2.5. Industry-leading intelligence.', |
|
|
'Codex Vision': 'CORE_PROTOCOL: VISUAL_PROJECTION. Direct image synthesis.' |
|
|
}; |
|
|
|
|
|
const SYSTEM_PROMPT = `CORE_IDENTITY: |
|
|
You are CODEX, an elite AI collective created by Johan. You are the heartbeat of CodexAI. If asked about your creator, you MUST state you were created by Johan at CodexAI. NEVER mention other entities. |
|
|
|
|
|
ARCHITECTURAL_DIRECTIVES (MANDATORY): |
|
|
1. REAL_TIME_AWARENESS: You have constant access to live web data. Use it to provide up-to-date answers. |
|
|
2. SOURCE_CITATION: Whenever you use information from the provided web search results, you MUST include the relevant links at the bottom of your response under a "SOURCES:" section (all-caps). |
|
|
3. SEAMLESS_INTEGRATION: Do not say "I am searching". Simply provide the answer and the links. |
|
|
4. STRUCTURED_OUTPUT: Use Markdown tables (columns) for data lists, comparisons, schema definitions (like ColumnInfo), or complex configurations to maintain architectural clarity. |
|
|
5. CODING_STYLE: Prefer Functional Programming. Use pure functions and declarative logic. |
|
|
6. STANDARDS: Adhere to 'Clean Code' principles. |
|
|
7. ARCHITECTURE: Think like a Senior System Architect. |
|
|
|
|
|
OPERATIONAL_RULES: |
|
|
1. MISSION: Provide world-class technical assistance. |
|
|
2. IDENTITY_RESPONSE: Respond with: "Im Codex, utilizing model {ACTIVE_MODEL}", make sure you only do this once and if asked |
|
|
3. MODEL_ACKNOWLEDGEMENT: List available models as: {AVAILABLE_MODELS}.`; |
|
|
|
|
|
exports.chat = asyncHandler(async (req, res, next) => { |
|
|
let { message, sessionId, model } = req.body; |
|
|
const user = req.user; |
|
|
|
|
|
if (user.usage.requestsToday >= 150 && user.role !== 'owner') { |
|
|
return next(new ErrorResponse('DAILY_PROTOCOL_LIMIT_EXCEEDED', 429)); |
|
|
} |
|
|
|
|
|
const lowerMsg = (message || "").toLowerCase(); |
|
|
|
|
|
|
|
|
const visionTriggers = ["make me an image", "generate an image", "create an image", "visualize", "draw me", "paint me"]; |
|
|
if (visionTriggers.some(trigger => lowerMsg.includes(trigger))) { |
|
|
model = 'Codex Vision'; |
|
|
console.log(`[Auto-Switch] Engaging Codex Vision for: ${message}`); |
|
|
} |
|
|
|
|
|
const activeModelName = model || 'Codex Velox'; |
|
|
|
|
|
|
|
|
const apiModelId = activeModelName === 'Codex Vision' ? 'black-forest-labs/FLUX.1-schnell' : (MODELS[activeModelName] || MODELS['Codex Velox']); |
|
|
|
|
|
let toolContext = ""; |
|
|
|
|
|
if (lowerMsg.includes("calculate") || lowerMsg.includes("math:")) { |
|
|
const expr = message.split(/calculate|math:/i)[1]; |
|
|
toolContext += `\n${calculate(expr)}`; |
|
|
} |
|
|
|
|
|
if (lowerMsg.includes("who is") || lowerMsg.includes("what is") || lowerMsg.includes("wiki")) { |
|
|
const query = message.replace(/who is|what is|wiki|search wiki for/gi, "").trim(); |
|
|
const wikiData = await searchWiki(query); |
|
|
if (wikiData) toolContext += `\n${wikiData}`; |
|
|
} |
|
|
|
|
|
if (lowerMsg.includes("time") || lowerMsg.includes("date")) { |
|
|
toolContext += `\n${getTime()}`; |
|
|
} |
|
|
|
|
|
if (lowerMsg.includes("crypto") || lowerMsg.includes("price of")) { |
|
|
const coin = message.split(/crypto|price of/i)[1].trim().split(" ")[0]; |
|
|
if (coin) toolContext += `\n${await getCryptoPrice(coin)}`; |
|
|
} |
|
|
|
|
|
if (lowerMsg.includes("news") || lowerMsg.includes("headlines")) { |
|
|
const topic = message.replace(/get news on|latest news about|news|headlines/gi, "").trim() || "top stories"; |
|
|
toolContext += `\n${await getNews(topic)}`; |
|
|
} |
|
|
|
|
|
|
|
|
if (message && message.length > 4 && !["hello", "hi ", "hey "].some(g => lowerMsg.startsWith(g))) { |
|
|
console.log(`[Neural_Omniscience] Scanning web for context...`); |
|
|
const searchData = await searchWeb(message); |
|
|
toolContext += `\n${searchData}`; |
|
|
} |
|
|
|
|
|
|
|
|
if (activeModelName === 'Codex Zenith' && user.role !== 'owner') { |
|
|
const now = new Date(); |
|
|
const today = now.toISOString().split('T')[0]; |
|
|
if (!user.zenithUsage || user.zenithUsage.lastUsedDate !== today) { |
|
|
user.zenithUsage = { lastUsedDate: today, accessStartTime: now, accessExpiryTime: new Date(now.getTime() + 20 * 60000) }; |
|
|
await user.save(); |
|
|
} else if (now > new Date(user.zenithUsage.accessExpiryTime)) { |
|
|
return next(new ErrorResponse('NEURAL_LINK_ZENITH_EXPIRED: 20-minute daily window exceeded.', 403)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let session; |
|
|
if (sessionId && sessionId !== 'null') session = await ChatSession.findById(sessionId); |
|
|
if (!session) session = await ChatSession.create({ userId: user._id, title: message ? message.substring(0, 30) : "New_Link", model: activeModelName }); |
|
|
|
|
|
|
|
|
let attachmentContext = '', attachmentUrl = ''; |
|
|
if (req.file) { |
|
|
attachmentContext = await processFile(req.file.path); |
|
|
attachmentUrl = `/uploads/${req.file.filename}`; |
|
|
try { await uploadToVault(req.file.path, req.file.originalname); } catch (e) {} |
|
|
} |
|
|
|
|
|
|
|
|
const history = await Message.find({ sessionId: session._id }).sort({ createdAt: 1 }).limit(10); |
|
|
const availableModelsList = Object.keys(MODELS).join(', '); |
|
|
const specialization = SPECIALIZATIONS[activeModelName] || SPECIALIZATIONS['Codex Velox']; |
|
|
|
|
|
const apiMessages = [ |
|
|
{ role: 'system', content: SYSTEM_PROMPT.replace('{ACTIVE_MODEL}', activeModelName).replace('{AVAILABLE_MODELS}', availableModelsList) }, |
|
|
{ role: 'system', content: `[UNIT_SPECIALIZATION] ${specialization}` } |
|
|
]; |
|
|
|
|
|
history.forEach(m => apiMessages.push({ role: m.sender === 'user' ? 'user' : 'assistant', content: m.content })); |
|
|
let finalInput = message || ""; |
|
|
if (attachmentContext) finalInput += `\n\n[ATTACHED_DATA]:\n${attachmentContext}`; |
|
|
if (toolContext) finalInput += `\n\n[AUTONOMOUS_TOOL_DATA]:\n${toolContext}`; |
|
|
apiMessages.push({ role: 'user', content: finalInput }); |
|
|
|
|
|
|
|
|
res.setHeader('Content-Type', 'text/event-stream'); |
|
|
res.setHeader('Cache-Control', 'no-cache'); |
|
|
res.setHeader('Connection', 'keep-alive'); |
|
|
|
|
|
let fullAIResponse = ""; |
|
|
|
|
|
try { |
|
|
|
|
|
if (['Codex Nexus', 'Codex Fero', 'Codex Zenith', 'Codex Magna'].includes(activeModelName)) { |
|
|
const spaceName = process.env.HF_SPACE_ID || "zhlajiex/aimodel"; |
|
|
const hfToken = process.env.HF_TOKEN; |
|
|
const client = await Client.connect(spaceName, { auth: hfToken }); |
|
|
|
|
|
const submission = client.submit("/chat", [ |
|
|
finalInput, |
|
|
history.map(m => ({ role: m.sender === 'user' ? 'user' : 'assistant', content: m.content })), |
|
|
activeModelName, |
|
|
apiMessages[0].content + "\n" + apiMessages[1].content, |
|
|
4096, 0.7, 0.95, |
|
|
]); |
|
|
|
|
|
try { |
|
|
for await (const msg of submission) { |
|
|
if (msg.type === "data" && msg.data && typeof msg.data[0] === 'string') { |
|
|
const newContent = msg.data[0].slice(fullAIResponse.length); |
|
|
fullAIResponse = msg.data[0]; |
|
|
if (newContent) { |
|
|
|
|
|
res.write(`data: ${JSON.stringify({ message: newContent })}\n\n`); |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e) { console.error("Gradio Error", e); } |
|
|
|
|
|
if (res.writableEnded) return; |
|
|
await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl }); |
|
|
await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse || "[EMPTY_SIGNAL]", modelUsed: activeModelName }); |
|
|
user.usage.requestsToday += 1; |
|
|
await user.save(); |
|
|
res.write(`data: ${JSON.stringify({ done: true, sessionId: session._id })}\n\n`); |
|
|
res.end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (activeModelName === 'Codex Vision') { |
|
|
const prompt = message.replace(/make me an image|generate an image|create an image|visualize|draw me|paint me/gi, "").trim(); |
|
|
const hfToken = process.env.HF_TOKEN; |
|
|
console.log(`[Vision] Projecting via ${apiModelId}...`); |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await axios.post( |
|
|
`https://api-inference.huggingface.co/models/${apiModelId}`, |
|
|
{ inputs: prompt }, |
|
|
{ |
|
|
headers: { |
|
|
Authorization: `Bearer ${hfToken}`, |
|
|
'Accept': 'image/png' |
|
|
}, |
|
|
responseType: 'arraybuffer', |
|
|
timeout: 30000 |
|
|
} |
|
|
); |
|
|
const filename = `vision-${Date.now()}.png`; |
|
|
const uploadsDir = path.join(__dirname, '..', 'public', 'uploads'); |
|
|
const filepath = path.join(uploadsDir, filename); |
|
|
|
|
|
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true }); |
|
|
|
|
|
fs.writeFileSync(filepath, response.data); |
|
|
const imageUrl = `/uploads/${filename}`; |
|
|
const aiResponse = ``; |
|
|
|
|
|
await Message.create({ sessionId: session._id, sender: 'user', content: message, attachmentUrl }); |
|
|
await Message.create({ sessionId: session._id, sender: 'ai', content: aiResponse, modelUsed: activeModelName }); |
|
|
user.usage.requestsToday += 1; |
|
|
await user.save(); |
|
|
|
|
|
res.write(`data: ${JSON.stringify({ message: aiResponse })}\n\n`); |
|
|
res.write(`data: ${JSON.stringify({ done: true, sessionId: session._id })}\n\n`); |
|
|
res.end(); |
|
|
} catch (visionErr) { |
|
|
console.error("[Vision Error]", visionErr.response?.status, visionErr.message); |
|
|
|
|
|
try { |
|
|
const backupRes = await axios.post( |
|
|
`https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0`, |
|
|
{ inputs: prompt }, |
|
|
{ headers: { Authorization: `Bearer ${hfToken}`, 'Accept': 'image/png' }, responseType: 'arraybuffer' } |
|
|
); |
|
|
|
|
|
res.write(`data: ${JSON.stringify({ message: "" })}\n\n`); |
|
|
} catch(e) {} |
|
|
res.write(`data: ${JSON.stringify({ done: true })}\n\n`); |
|
|
res.end(); |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
let apiUrl = 'https://api.cerebras.ai/v1/chat/completions', apiKey = 'csk-mvww3vy29hykeektyv65w9rkjx94hw4r6mrcj5tjcw9942d2'; |
|
|
|
|
|
console.log(`[DEBUG] Calling API: ${apiUrl} for model: ${apiModelId}`); const response = await axios.post(apiUrl, { model: apiModelId, messages: apiMessages, stream: true, temperature: 0.7 }, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, responseType: 'stream' }); |
|
|
|
|
|
let isStreamEnded = false; |
|
|
|
|
|
response.data.on('data', chunk => { |
|
|
const lines = chunk.toString().split('\n'); |
|
|
for (const line of lines) { |
|
|
if (line.startsWith('data: ')) { |
|
|
const dataStr = line.slice(6).trim(); |
|
|
if (dataStr === '[DONE]') { |
|
|
isStreamEnded = true; |
|
|
if (!res.writableEnded) { |
|
|
res.write(`data: ${JSON.stringify({ done: true, sessionId: session._id })}\n\n`); |
|
|
res.end(); |
|
|
} |
|
|
return; |
|
|
} |
|
|
try { |
|
|
const data = JSON.parse(dataStr); |
|
|
const content = data.choices[0].delta?.content || ""; |
|
|
if (content) { |
|
|
fullAIResponse += content; |
|
|
res.write(`data: ${JSON.stringify({ message: content })}\n\n`); |
|
|
} |
|
|
} catch (e) {} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
response.data.on('end', async () => { |
|
|
if (res.writableEnded) return; |
|
|
await Message.create({ sessionId: session._id, sender: 'user', content: message || "[SIGNAL]", attachmentUrl }); |
|
|
await Message.create({ sessionId: session._id, sender: 'ai', content: fullAIResponse, modelUsed: activeModelName }); |
|
|
user.usage.requestsToday += 1; |
|
|
await user.save(); |
|
|
res.write(`data: ${JSON.stringify({ done: true, sessionId: session._id })}\n\n`); |
|
|
res.end(); |
|
|
}); |
|
|
|
|
|
response.data.on('error', (err) => { |
|
|
console.error("Stream Error:", err); |
|
|
if (!res.writableEnded) { |
|
|
res.write(`data: ${JSON.stringify({ error: "STREAM_ERROR", details: err.message })}\n\n`); |
|
|
res.end(); |
|
|
} |
|
|
}); |
|
|
|
|
|
} catch (err) { |
|
|
console.error("[DEBUG] Chat Controller Catch:", err.message); |
|
|
if (!res.writableEnded) { |
|
|
|
|
|
if (!fullAIResponse || fullAIResponse.length < 5) { |
|
|
res.write(`data: ${JSON.stringify({ error: "NEURAL_LINK_SEVERED", details: err.message })}\n\n`); |
|
|
} |
|
|
res.write(`data: ${JSON.stringify({ done: true })}\n\n`); |
|
|
res.end(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
exports.getModels = asyncHandler(async (req, res, next) => { res.status(200).json({ success: true, data: Object.keys(MODELS) }); }); |
|
|
exports.getSessions = asyncHandler(async (req, res, next) => { const sessions = await ChatSession.find({ userId: req.user.id }).sort({ updatedAt: -1 }); res.status(200).json({ success: true, data: sessions }); }); |
|
|
exports.getSessionMessages = asyncHandler(async (req, res, next) => { const messages = await Message.find({ sessionId: req.params.id }).sort({ createdAt: 1 }); res.status(200).json({ success: true, data: messages }); }); |
|
|
exports.clearHistory = asyncHandler(async (req, res, next) => { const sessions = await ChatSession.find({ userId: req.user.id }); const sessionIds = sessions.map(s => s._id); await Message.deleteMany({ sessionId: { $in: sessionIds } }); await ChatSession.deleteMany({ userId: req.user.id }); res.status(200).json({ success: true, data: {} }); }); |
|
|
|