| <!DOCTYPE html> |
| <html lang="ar" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <title>FECUOY AI | المحرك الاحترافي v2.2</title> |
| |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600&display=swap" rel="stylesheet"> |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css"> |
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> |
|
|
| <style> |
| :root { |
| --bg-dark: #1a1b1e; |
| --bg-sidebar: #111214; |
| --primary: #10a37f; |
| --text-main: #e1e1e6; |
| --input-bg: #2a2b32; |
| --border-color: rgba(255,255,255,0.08); |
| --assistant-msg: rgba(255,255,255,0.04); |
| } |
| |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { |
| font-family: 'IBM Plex Sans Arabic', sans-serif; |
| background-color: var(--bg-dark); |
| color: var(--text-main); |
| height: 100dvh; |
| overflow: hidden; |
| display: flex; |
| } |
| |
| .app-layout { display: flex; width: 100%; height: 100%; position: relative; } |
| |
| |
| .sidebar { |
| width: 280px; background: var(--bg-sidebar); |
| border-left: 1px solid var(--border-color); |
| display: flex; flex-direction: column; |
| transition: all 0.3s ease; |
| } |
| |
| @media (max-width: 768px) { |
| .sidebar { position: absolute; right: -280px; height: 100%; z-index: 1000; } |
| .sidebar.active { right: 0; } |
| } |
| |
| .main-content { flex: 1; display: flex; flex-direction: column; height: 100%; min-width: 0; } |
| |
| header { |
| height: 60px; padding: 0 20px; |
| display: flex; align-items: center; justify-content: space-between; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| #chatBox { flex: 1; overflow-y: auto; padding: 30px 15%; scroll-behavior: smooth; } |
| @media (max-width: 1024px) { #chatBox { padding: 20px 5%; } } |
| |
| .msg-row { display: flex; margin-bottom: 25px; animation: fadeIn 0.3s ease; } |
| .msg { max-width: 85%; padding: 15px 20px; border-radius: 12px; line-height: 1.7; font-size: 15.5px; word-wrap: break-word; } |
| .user { background: var(--primary); color: white; margin-right: auto; } |
| .assistant { background: var(--assistant-msg); border: 1px solid var(--border-color); margin-left: auto; } |
| |
| |
| .controls-bar { |
| padding: 10px 15%; |
| display: flex; |
| gap: 20px; |
| background: rgba(0,0,0,0.2); |
| border-top: 1px solid var(--border-color); |
| font-size: 13px; |
| align-items: center; |
| } |
| @media (max-width: 1024px) { .controls-bar { padding: 10px 5%; flex-wrap: wrap; } } |
| |
| .control-group { display: flex; align-items: center; gap: 8px; color: #aaa; } |
| select { background: var(--input-bg); color: white; border: 1px solid var(--border-color); padding: 4px 8px; border-radius: 5px; outline: none; } |
| input[type="checkbox"] { accent-color: var(--primary); cursor: pointer; } |
| |
| .input-area { padding: 10px 15% 30px; } |
| @media (max-width: 1024px) { .input-area { padding: 10px 5% 20px; } } |
| |
| .input-wrapper { |
| background: var(--input-bg); border: 1px solid var(--border-color); |
| border-radius: 16px; padding: 10px 15px; |
| display: flex; align-items: flex-end; gap: 10px; |
| } |
| |
| textarea { flex: 1; background: none; border: none; color: white; resize: none; font-size: 16px; outline: none; max-height: 200px; } |
| .action-btn { background: none; border: none; color: #8e8ea0; cursor: pointer; font-size: 20px; } |
| .send-btn { color: var(--primary); } |
| |
| pre { background: #000 !important; padding: 15px; border-radius: 8px; margin: 10px 0; overflow-x: auto; direction: ltr; } |
| code { font-family: monospace; } |
| #fileInput { display: none; } |
| .upload-success { color: var(--primary) !important; } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="app-layout"> |
| <aside class="sidebar" id="sidebar"> |
| <button style="margin:15px; padding:12px; background:var(--primary); border:none; color:white; border-radius:8px; cursor:pointer;" onclick="location.reload()"> |
| <i class="fas fa-plus"></i> محادثة جديدة |
| </button> |
| <div style="flex:1"></div> |
| <div style="padding: 20px; border-top: 1px solid var(--border-color); font-size: 12px; color: #444;"> |
| FECUOY AI v2.2 - Multi-Model Engine |
| </div> |
| </aside> |
|
|
| <main class="main-content"> |
| <header> |
| <button class="action-btn" onclick="document.getElementById('sidebar').classList.toggle('active')"><i class="fas fa-bars"></i></button> |
| <div style="font-weight:600;">FECUOY <span style="color:var(--primary)">AI</span></div> |
| <div style="width:10px; height:10px; background:var(--primary); border-radius:50%;"></div> |
| </header> |
|
|
| <div id="chatBox"> |
| <div class="msg-row"> |
| <div class="msg assistant">مرحباً! يمكنك الآن التبديل بين النماذج وتفعيل أدوات البحث المباشر من الأسفل.</div> |
| </div> |
| </div> |
|
|
| <div class="controls-bar"> |
| <div class="control-group"> |
| <span>النموذج:</span> |
| <select id="modelSelect"> |
| <option value="qwen_abliterated">Qwen 2.5 72B (محرر)</option> |
| <option value="kimi_k2">Kimi-K2 (رسمي)</option> |
| <option value="internvl_vision">InternVL2 (تحليل صور)</option> |
| </select> |
| </div> |
| <div class="control-group"> |
| <input type="checkbox" id="toolSearch"> |
| <label for="toolSearch">بحث ويب 🌐</label> |
| </div> |
| <div class="control-group"> |
| <input type="checkbox" id="toolURL"> |
| <label for="toolURL">تحليل روابط 🔗</label> |
| </div> |
| </div> |
|
|
| <div class="input-area"> |
| <div class="input-wrapper"> |
| <label for="fileInput" class="action-btn" id="attachLabel"><i class="fas fa-paperclip"></i></label> |
| <input type="file" id="fileInput" onchange="handleFileUpload(this)"> |
| <textarea id="userInput" placeholder="اسأل أي شيء..." rows="1" oninput="this.style.height='auto';this.style.height=this.scrollHeight+'px'"></textarea> |
| <button class="action-btn send-btn" id="sendBtn" onclick="send()"><i class="fas fa-paper-plane"></i></button> |
| </div> |
| </div> |
| </main> |
| </div> |
|
|
| <script> |
| let currentFileId = null; |
| |
| async function handleFileUpload(input) { |
| if (!input.files[0]) return; |
| const icon = document.querySelector('#attachLabel i'); |
| icon.className = "fas fa-spinner fa-spin"; |
| |
| const formData = new FormData(); |
| formData.append('file', input.files[0]); |
| |
| try { |
| const res = await fetch('/api/upload', { method: 'POST', body: formData }); |
| const data = await res.json(); |
| if (data.file_id) { |
| currentFileId = data.file_id; |
| icon.className = "fas fa-check-circle upload-success"; |
| } |
| } catch (e) { alert("خطأ في الرفع"); icon.className = "fas fa-paperclip"; } |
| } |
| |
| async function send() { |
| const input = document.getElementById('userInput'); |
| const text = input.value.trim(); |
| if (!text && !currentFileId) return; |
| |
| const selectedModel = document.getElementById('modelSelect').value; |
| const tools = []; |
| if(document.getElementById('toolSearch').checked) tools.push("web_search"); |
| if(document.getElementById('toolURL').checked) tools.push("url_reader"); |
| |
| appendMsg('user', text); |
| input.value = ''; |
| input.style.height = 'auto'; |
| |
| const assistantMsg = appendMsg('assistant', 'جاري المعالجة...'); |
| |
| try { |
| const response = await fetch('/api/chat/stream', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({ |
| message: text, |
| model: selectedModel, |
| tools: tools, |
| file_id: currentFileId |
| }) |
| }); |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| let fullText = ""; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| const chunk = decoder.decode(value); |
| const lines = chunk.split('\n'); |
| for (const line of lines) { |
| if (line.startsWith('data: ')) { |
| const data = JSON.parse(line.substring(6)); |
| if (data.token) { |
| fullText += data.token; |
| assistantMsg.innerHTML = marked.parse(fullText); |
| assistantMsg.querySelectorAll('pre code').forEach(el => hljs.highlightElement(el)); |
| } |
| } |
| } |
| } |
| } catch (e) { assistantMsg.innerText = "⚠️ خطأ في الاتصال."; } |
| } |
| |
| function appendMsg(role, content) { |
| const box = document.getElementById('chatBox'); |
| const row = document.createElement('div'); |
| row.className = 'msg-row'; |
| const msg = document.createElement('div'); |
| msg.className = `msg ${role}`; |
| msg.innerHTML = content; |
| row.appendChild(msg); |
| box.appendChild(row); |
| box.scrollTop = box.scrollHeight; |
| return msg; |
| } |
| |
| document.getElementById('userInput').addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } |
| }); |
| </script> |
| </body> |
| </html> |
|
|