File size: 7,512 Bytes
0d7c367 91d07ed cdf481e a24c18a 010efa7 492ea5a 1dc8c03 b0424b9 cdf481e b0424b9 cdf481e b0424b9 cdf481e ec00cab cdf481e 91d07ed cdf481e a24c18a cdf481e 91d07ed cdf481e a24c18a cdf481e b0424b9 cdf481e a24c18a cdf481e b0424b9 cdf481e aa5b70d cdf481e 010efa7 cdf481e b0424b9 ec00cab b0424b9 ec00cab cdf481e 91d07ed cdf481e 91d07ed 1aa319d cdf481e a24c18a 91d07ed 1dc8c03 b0424b9 cdf481e b0424b9 cdf481e a24c18a cdf481e ec00cab a24c18a 492ea5a | 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 | import os
import gradio as gr
from fastapi import FastAPI, Request, File, UploadFile
from fastapi.responses import StreamingResponse, HTMLResponse
from openai import OpenAI
app = FastAPI()
# واجهة المستخدم (HTML/React) - متجاوبة تماماً للهواتف
HTML_UI = """
<!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>Neural Vault</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
body { background: #020617; color: #f8fafc; height: 100dvh; overflow: hidden; margin: 0; position: fixed; width: 100vw; }
.chat-area { height: calc(100dvh - 160px); overflow-y: auto; padding: 20px; -webkit-overflow-scrolling: touch; }
.sidebar { position: fixed; top: 0; right: -100%; width: 80%; max-width: 300px; height: 100%; background: #0f172a; z-index: 2000; transition: 0.3s ease; border-left: 1px solid #1e293b; }
.sidebar.open { right: 0; }
.input-bar { position: fixed; bottom: 0; width: 100%; padding: 15px; background: #020617; border-top: 1px solid #1e293b; }
.bubble { max-width: 85%; padding: 12px; border-radius: 18px; font-size: 14px; margin-bottom: 10px; line-height: 1.5; }
.user { background: #1e293b; margin-right: auto; border-bottom-right-radius: 4px; }
.bot { background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.2); margin-left: auto; border-bottom-left-radius: 4px; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
function App() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const [isSideOpen, setSideOpen] = useState(false);
const [status, setStatus] = useState("idle");
const chatRef = useRef(null);
useEffect(() => { chatRef.current?.scrollTo(0, chatRef.current.scrollHeight); }, [messages]);
const send = async () => {
if(!input.trim()) return;
const userMsg = {role: 'user', content: input};
setMessages(prev => [...prev, userMsg]);
setInput("");
setStatus("thinking");
try {
const res = await fetch('/v1/chat/completions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ messages: [...messages, userMsg] })
});
const reader = res.body.getReader();
let botMsg = { role: 'assistant', content: "" };
setMessages(p => [...p, botMsg]);
while(true) {
const {done, value} = await reader.read();
if(done) break;
const chunk = new TextDecoder().decode(value).replace(/data: /g, '');
if(chunk.includes("[DONE]")) break;
botMsg.content += chunk;
setMessages(p => [...p.slice(0, -1), {...botMsg}]);
}
} catch (e) { console.error(e); }
setStatus("idle");
};
return (
<div className="flex flex-col h-full">
<header className="p-4 border-b border-white/5 flex justify-between items-center bg-slate-900/50">
<button onClick={() => setSideOpen(true)} className="text-xl">☰</button>
<span className="font-bold text-sm tracking-widest">NEURAL VAULT</span>
<div className={`w-2 h-2 rounded-full ${status === 'idle' ? 'bg-emerald-500' : 'bg-indigo-500 animate-pulse'}`}></div>
</header>
<div ref={chatRef} className="chat-area space-y-4">
{messages.map((m, i) => (
<div key={i} className={`bubble ${m.role === 'user' ? 'user' : 'bot'}`}>
{m.content}
</div>
))}
</div>
<div className="input-bar">
<div className="flex gap-2 max-w-xl mx-auto">
<input
className="flex-1 bg-slate-900 border border-white/10 rounded-xl px-4 py-2 outline-none text-sm"
placeholder="اكتب سؤالك..."
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && send()}
/>
<button onClick={send} className="bg-indigo-600 px-5 rounded-xl">↑</button>
</div>
</div>
<div className={`sidebar p-6 ${isSideOpen ? 'open' : ''}`}>
<div className="flex justify-between items-center mb-10">
<h2 className="font-bold">الإعدادات</h2>
<button onClick={() => setSideOpen(false)}>✕</button>
</div>
<div className="p-4 bg-slate-800/50 rounded-xl text-xs">
<p>الموديل: Mistral-Small-24B</p>
<p className="mt-2 text-emerald-400">الحالة: متصل</p>
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>
"""
@app.get("/")
async def ui():
return HTMLResponse(HTML_UI)
@app.post("/v1/chat/completions")
async def chat_api(request: Request):
body = await request.json()
# استخدام توكن النظام كاحتياطي أو توكن المستخدم إذا توفر
api_key = os.getenv("HF_TOKEN")
client = OpenAI(
base_url="https://router.huggingface.co/hf-inference/v1",
api_key=api_key
)
def stream_gen():
resp = client.chat.completions.create(
model="huihui-ai/Mistral-Small-24B-Instruct-2501-abliterated",
messages=body.get("messages", []),
stream=True
)
for chunk in resp:
if chunk.choices[0].delta.content:
yield f"data: {chunk.choices[0].delta.content}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(stream_gen(), media_type="text/event-stream")
# إعداد واجهة Gradio كمدخل
with gr.Blocks() as auth_interface:
gr.Markdown("# مرحبا بك في الدهليز")
# زر الدخول المذكور في ملفك
gr.LoginButton("الدخول عبر Hugging Face")
app = gr.mount_gradio_app(app, auth_interface, path="/auth")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)
|