| import express from 'express'; |
| import { Client } from '@gradio/client'; |
| import cors from 'cors'; |
| import { fileURLToPath } from 'url'; |
| import { dirname, join } from 'path'; |
|
|
| const __filename = fileURLToPath(import.meta.url); |
| const __dirname = dirname(__filename); |
|
|
| const app = express(); |
| const PORT = process.env.PORT || 7860; |
|
|
| app.use(cors()); |
| app.use(express.json()); |
| app.use(express.static('public')); |
|
|
| |
| |
| |
| const SEARCH_API_KEY = process.env.SEARCH_API_KEY || ''; |
| const SEARCH_ENGINE_ID = process.env.SEARCH_ENGINE_ID || ''; |
|
|
| |
| async function searchWeb(query) { |
| if (!SEARCH_API_KEY || !SEARCH_ENGINE_ID) { |
| |
| return await searchDuckDuckGo(query); |
| } |
| |
| |
| try { |
| const url = `https://www.googleapis.com/customsearch/v1?key=${SEARCH_API_KEY}&cx=${SEARCH_ENGINE_ID}&q=${encodeURIComponent(query)}`; |
| const response = await fetch(url); |
| const data = await response.json(); |
| |
| if (data.items && data.items.length > 0) { |
| return data.items.slice(0, 5).map(item => ({ |
| title: item.title, |
| snippet: item.snippet, |
| link: item.link |
| })); |
| } |
| return []; |
| } catch (error) { |
| console.error('Google Search error:', error); |
| return []; |
| } |
| } |
|
|
| |
| async function searchDuckDuckGo(query) { |
| try { |
| const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`; |
| const response = await fetch(url); |
| const data = await response.json(); |
| |
| const results = []; |
| |
| |
| if (data.RelatedTopics && data.RelatedTopics.length > 0) { |
| for (const topic of data.RelatedTopics.slice(0, 5)) { |
| if (topic.Text && topic.Text !== data.AbstractText) { |
| results.push({ |
| title: topic.Text.split(' - ')[0] || 'نتيجة بحث', |
| snippet: topic.Text, |
| link: topic.FirstURL || '#' |
| }); |
| } |
| } |
| } |
| |
| |
| if (data.AbstractText) { |
| results.unshift({ |
| title: data.Heading || 'ملخص', |
| snippet: data.AbstractText, |
| link: data.AbstractURL || '#' |
| }); |
| } |
| |
| return results; |
| } catch (error) { |
| console.error('DuckDuckGo search error:', error); |
| return []; |
| } |
| } |
|
|
| |
| |
| |
| const MODEL_CONFIGS = { |
| flash: { |
| temperature: 1.0, |
| top_p: 0.95, |
| max_tokens: 2048, |
| system_message: `You are a helpful, fast assistant. Your name is MiniMax-M2.5-Flash. |
| |
| Provide DIRECT, CONCISE answers without thinking out loud. |
| - Be brief and to the point |
| - No step-by-step reasoning |
| - No "genisi thought" markers |
| - Give the answer immediately |
| - If the user asks for steps, list them clearly but without meta-commentary`, |
| }, |
| pro: { |
| temperature: 0.7, |
| top_p: 0.9, |
| max_tokens: 4096, |
| system_message: `You are an elite AI engineer, designer, and scientist with the combined expertise of senior engineers at Google, Apple, Meta, Netflix, OpenAI, NASA, and CERN. |
| |
| You are not a code generator. You are a principal engineer who thinks deeply, chooses wisely, and delivers only work you would be proud to deploy to production at a top-tier company. |
| |
| You have mastery over: |
| - Software engineering (frontend, backend, fullstack, systems, DevOps) |
| - Data science, machine learning, and AI pipelines |
| - Mathematical modeling, symbolic computation, and scientific computing |
| - Image analysis, computer vision, and multimodal reasoning |
| - Document parsing, data extraction, and file processing |
| - UI/UX design systems and product thinking |
| |
| **CRITICAL RULE: You MUST use "genisi thought" markers before every response.** |
| Start with "genisi thought", then your complete internal analysis, then "genisi thought", then your final answer. |
| |
| Example format: |
| genisi thought |
| [Your deep thinking, analysis, and reasoning here] |
| genisi thought |
| [Your final, polished answer here] |
| |
| Always think step by step. Never skip the thinking phase.`, |
| } |
| }; |
|
|
| let currentMode = 'pro'; |
| let gradioClient = null; |
|
|
| |
| async function getGradioClient() { |
| if (!gradioClient) { |
| try { |
| console.log('🔄 Connecting to MiniMax Space...'); |
| gradioClient = await Client.connect("ostarling/MiniMax-M2.5-Chat"); |
| console.log('✅ Connected to MiniMax M2.5'); |
| } catch (error) { |
| console.error('❌ Connection failed:', error); |
| throw error; |
| } |
| } |
| return gradioClient; |
| } |
|
|
| |
| async function callMiniMax(message, mode, searchResults = null) { |
| const config = MODEL_CONFIGS[mode]; |
| const client = await getGradioClient(); |
| |
| let enhancedMessage = message; |
| |
| |
| if (searchResults && searchResults.length > 0) { |
| const searchContext = searchResults.map((r, i) => |
| `${i + 1}. ${r.title}\n ${r.snippet}\n المصدر: ${r.link}` |
| ).join('\n\n'); |
| |
| enhancedMessage = `[INTERNET SEARCH RESULTS FOR: "${message}"]\n\n${searchContext}\n\n[USER QUESTION]\n${message}\n\nPlease use the search results above to provide an accurate, up-to-date answer.`; |
| } |
| |
| |
| if (mode === 'pro') { |
| enhancedMessage = `REMINDER: You MUST use "genisi thought" markers. Start with "genisi thought", then your thinking, then "genisi thought", then your answer.\n\nUser question: ${enhancedMessage}`; |
| } |
| |
| console.log(`📤 Sending to MiniMax (${mode} mode)...`); |
| |
| const result = await client.predict("/respond", { |
| message: enhancedMessage, |
| system_message: config.system_message, |
| max_tokens: config.max_tokens, |
| temperature: config.temperature, |
| top_p: config.top_p |
| }); |
| |
| return result.data; |
| } |
|
|
| |
| function parseResponse(text) { |
| const parts = []; |
| |
| |
| const thoughtRegex = /genisi thought\s*([\s\S]*?)\s*genisi thought/gi; |
| let lastIndex = 0; |
| let match; |
| |
| let cleanText = typeof text === 'string' ? text : JSON.stringify(text); |
| |
| while ((match = thoughtRegex.exec(cleanText)) !== null) { |
| if (match.index > lastIndex) { |
| const beforeText = cleanText.substring(lastIndex, match.index).trim(); |
| if (beforeText) { |
| parts.push({ type: 'text', content: beforeText }); |
| } |
| } |
| |
| const thoughtContent = match[1].trim(); |
| if (thoughtContent) { |
| parts.push({ type: 'thought', content: thoughtContent }); |
| } |
| |
| lastIndex = match.index + match[0].length; |
| } |
| |
| if (lastIndex < cleanText.length) { |
| const afterText = cleanText.substring(lastIndex).trim(); |
| if (afterText) { |
| parts.push({ type: 'text', content: afterText }); |
| } |
| } |
| |
| if (parts.length === 0 && cleanText) { |
| parts.push({ type: 'text', content: cleanText }); |
| } |
| |
| return parts; |
| } |
|
|
| |
| function needsWebSearch(message) { |
| const searchKeywords = [ |
| 'أخبار', 'آخر', 'جديد', 'اليوم', 'هذا الأسبوع', 'هذا الشهر', |
| 'سعر', 'سوق', 'سهم', 'بورصة', 'عملة', 'دولار', 'ريال', |
| 'طقس', 'حالة جوية', 'توقعات', 'درجة حرارة', |
| 'حدث', 'مباراة', 'نتيجة', 'جائزة', 'حفل', |
| 'مشاهير', 'فنان', 'مغني', 'ممثل', |
| 'تحديث', 'إصدار', 'جديد في', |
| 'بحث', 'ابحث', 'قوقل', 'جوجل', 'الإنترنت' |
| ]; |
| |
| const msg = message.toLowerCase(); |
| return searchKeywords.some(keyword => msg.includes(keyword)); |
| } |
|
|
| |
| |
| |
|
|
| app.post('/api/chat', async (req, res) => { |
| const { message, mode, enableSearch } = req.body; |
| |
| if (!message) { |
| return res.status(400).json({ error: 'الرسالة مطلوبة' }); |
| } |
| |
| const activeMode = mode || currentMode; |
| let searchResults = null; |
| |
| try { |
| |
| const shouldSearch = enableSearch === true || needsWebSearch(message); |
| |
| if (shouldSearch) { |
| console.log(`🔍 Searching web for: ${message}`); |
| searchResults = await searchWeb(message); |
| console.log(`📊 Found ${searchResults?.length || 0} results`); |
| } |
| |
| |
| const responseData = await callMiniMax(message, activeMode, searchResults); |
| |
| let modelResponse = ''; |
| if (Array.isArray(responseData) && responseData.length > 0) { |
| modelResponse = responseData[0]; |
| } else if (typeof responseData === 'string') { |
| modelResponse = responseData; |
| } else { |
| modelResponse = JSON.stringify(responseData); |
| } |
| |
| |
| if (activeMode === 'flash') { |
| modelResponse = modelResponse.replace(/genisi thought[\s\S]*?genisi thought/gi, ''); |
| modelResponse = modelResponse.trim(); |
| } |
| |
| const parsedResponse = parseResponse(modelResponse); |
| |
| res.json({ |
| success: true, |
| response: parsedResponse, |
| mode: activeMode, |
| searchUsed: searchResults !== null && searchResults.length > 0, |
| searchResultsCount: searchResults?.length || 0 |
| }); |
| } catch (error) { |
| console.error('Error:', error); |
| res.status(500).json({ |
| success: false, |
| error: error.message |
| }); |
| } |
| }); |
|
|
| app.post('/api/mode', (req, res) => { |
| const { mode } = req.body; |
| if (mode === 'flash' || mode === 'pro') { |
| currentMode = mode; |
| res.json({ success: true, mode: currentMode }); |
| } else { |
| res.status(400).json({ error: 'وضع غير صالح' }); |
| } |
| }); |
|
|
| app.get('/api/mode', (req, res) => { |
| res.json({ mode: currentMode }); |
| }); |
|
|
| app.post('/api/search', async (req, res) => { |
| const { query } = req.body; |
| if (!query) { |
| return res.status(400).json({ error: 'مطلوب مصطلح البحث' }); |
| } |
| |
| const results = await searchWeb(query); |
| res.json({ results }); |
| }); |
|
|
| app.get('/', (req, res) => { |
| res.sendFile(join(__dirname, 'public', 'index.html')); |
| }); |
|
|
| app.listen(PORT, '0.0.0.0', () => { |
| console.log(`🚀 Server running on http://localhost:${PORT}`); |
| console.log(`📌 Current mode: ${currentMode}`); |
| console.log(`✅ Ready!`); |
| }); |