File size: 15,568 Bytes
1dc8372
 
9ddb9ef
a705c01
1dc8372
 
 
 
 
 
 
f29c287
bd18039
1dc8372
13c6122
1dc8372
13c6122
 
 
4237118
0c4f642
 
 
ceb4b3d
1dc8372
 
4237118
12262cc
2bdae4d
4ec9eaf
 
 
 
0c4f642
 
 
d636564
2bdae4d
 
1dc8372
d636564
1dc8372
a6ea324
df2d5a9
87c245f
993a42a
0aaf25d
 
 
 
a6ea324
1dc8372
a6ea324
 
 
1dc8372
 
acc3f7d
5db933c
1dc8372
12262cc
 
1dc8372
 
df2d5a9
 
acc3f7d
490ee65
 
acc3f7d
490ee65
acc3f7d
 
13c6122
12262cc
490ee65
 
 
f29c287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd18039
 
 
 
 
 
 
 
 
 
df2d5a9
 
 
 
f29c287
 
 
8c5dfb7
12262cc
 
 
 
8c5dfb7
12262cc
8c5dfb7
 
12262cc
 
 
1dc8372
 
8c5dfb7
 
1dc8372
f0b458c
8c5dfb7
1dc8372
 
072fffe
8c5dfb7
1dc8372
 
 
 
0b27045
0c4f642
0b27045
1dc8372
8c5dfb7
f0b458c
1dc8372
 
 
 
0c4f642
f29c287
1dc8372
 
f0b458c
1dc8372
 
 
 
c909305
 
1dc8372
4237118
 
a705c01
 
f0b458c
a705c01
f0b458c
 
 
4ec9eaf
fecc7c3
f0b458c
 
 
fecc7c3
 
 
 
 
a058845
 
 
 
fecc7c3
a705c01
0c4f642
f0b458c
afc2629
f0b458c
fecc7c3
f0b458c
 
 
 
 
a705c01
 
0c4f642
 
490ee65
0c4f642
d67a7b5
 
 
dd7c202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d67a7b5
 
 
 
 
 
 
 
dd7c202
 
 
 
 
 
 
 
 
 
d67a7b5
 
 
0c4f642
 
 
acc3f7d
8c5dfb7
2b8a809
4237118
1dc8372
afc2629
 
1dc8372
f0b458c
1dc8372
f0b458c
afc2629
fecc7c3
afc2629
a058845
 
 
 
fecc7c3
 
1dc8372
 
 
fecc7c3
 
 
 
f0b458c
1dc8372
 
 
 
 
afc2629
f0b458c
 
 
 
fecc7c3
1dc8372
 
 
afc2629
 
 
 
 
 
 
 
1dc8372
d67a7b5
afc2629
c909305
 
 
 
afc2629
 
 
1dc8372
 
 
8c5dfb7
 
 
d67a7b5
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
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');

// Internal Mapping: Theme Name -> API Model ID
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();

  // --- AUTOMATIC VISION ROUTING ---
  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';
  
  // Force correct Vision ID if triggered
  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)}`;
  }

  // GLOBAL SEARCH: Run for every message that isn't a simple greeting
  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}`;
  }

  // ZENITH LIMITATION LOGIC
  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));
    }
  }

  // 1. Handle Session
  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 });

  // 2. Handle File
  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) {}
  }

  // 3. Build History
  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 });

  // SSE Headers
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  let fullAIResponse = "";

  try {
    // --- GRADIO CORES (Nexus, Fero, Zenith, Magna) ---
    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) {
                // INSTANT RESET TRIGGER: We can't do it here easily, but we'll ensure done is sent
                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;
    }

    // --- VISION CORE ---
    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 {
            // BYPASS ROUTER: Use direct model endpoint to avoid 402/404 errors
            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 = `![Generated Image](${imageUrl})`;

            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);
            // Backup to SDXL via direct URL
            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' }
                );
                // ... same save logic ...
                res.write(`data: ${JSON.stringify({ message: "![Generated Image](/uploads/backup.png)" })}\n\n`);
            } catch(e) {}
            res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
            res.end();
        }
        return;
    }

    // Standard API Call
    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) {
      // Only send error if we haven't sent any AI content yet
      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: {} }); });