|
|
<!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> |
|
|
|