Test / app.py
1yahoo's picture
Update app.py
b0424b9 verified
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)