Test / app.py
1yahoo's picture
Update app.py
b0424b9 verified
Raw
History Blame Contribute Delete
7.51 kB
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)