Update app.py
Browse files
app.py
CHANGED
|
@@ -5,19 +5,19 @@ import chromadb
|
|
| 5 |
import gradio as gr
|
| 6 |
from fastapi import FastAPI, Request, File, UploadFile, BackgroundTasks
|
| 7 |
from fastapi.responses import StreamingResponse, HTMLResponse
|
| 8 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from openai import OpenAI
|
| 10 |
from chromadb.utils import embedding_functions
|
| 11 |
|
| 12 |
-
# --- المحرك الخلفي
|
| 13 |
app = FastAPI()
|
| 14 |
|
| 15 |
-
# إعداد الذاكرة
|
| 16 |
STORAGE_PATH = "/data/neural_memory" if os.path.exists("/data") else "./neural_memory"
|
| 17 |
chroma_client = chromadb.PersistentClient(path=STORAGE_PATH)
|
| 18 |
default_ef = embedding_functions.DefaultEmbeddingFunction()
|
| 19 |
-
collection = chroma_client.get_or_create_collection(name="
|
| 20 |
|
|
|
|
| 21 |
@app.get("/", response_class=HTMLResponse)
|
| 22 |
async def get_ui():
|
| 23 |
return """
|
|
@@ -25,105 +25,88 @@ async def get_ui():
|
|
| 25 |
<html lang="ar" dir="rtl">
|
| 26 |
<head>
|
| 27 |
<meta charset="UTF-8">
|
| 28 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 29 |
<title>Deep Neural Vault</title>
|
| 30 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 31 |
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
| 32 |
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
| 33 |
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
| 34 |
<style>
|
| 35 |
-
body { background: #020617; color: #f8fafc; margin: 0; height: 100dvh;
|
| 36 |
-
.
|
| 37 |
-
.
|
| 38 |
-
|
| 39 |
-
.sidebar-drawer { position: fixed; top: 0; right: 0; bottom: 0; w: 80%; background: #0f172a; z-index: 1000; transition: transform 0.3s ease; shadow: -10px 0 30px rgba(0,0,0,0.5); }
|
| 40 |
</style>
|
| 41 |
</head>
|
| 42 |
<body>
|
| 43 |
-
<div id="root"
|
| 44 |
<script type="text/babel">
|
| 45 |
const { useState, useEffect, useRef } = React;
|
| 46 |
-
|
| 47 |
function App() {
|
| 48 |
const [messages, setMessages] = useState([]);
|
| 49 |
const [input, setInput] = useState("");
|
| 50 |
-
const [
|
| 51 |
-
const [status, setStatus] = useState("idle");
|
| 52 |
|
| 53 |
const send = async () => {
|
| 54 |
if(!input.trim()) return;
|
| 55 |
-
const
|
| 56 |
-
setMessages(
|
| 57 |
setInput("");
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
}
|
| 78 |
-
} catch(e) { console.error(e); }
|
| 79 |
-
setStatus("idle");
|
| 80 |
};
|
| 81 |
|
| 82 |
return (
|
| 83 |
-
<div className="
|
| 84 |
-
<header className="p-4 border-b border-white/10 flex justify-between items-center bg-slate-900/
|
| 85 |
-
<button onClick={() =>
|
| 86 |
-
<
|
| 87 |
-
<div className=
|
| 88 |
</header>
|
| 89 |
|
| 90 |
<div className="chat-stream p-4 space-y-4">
|
| 91 |
{messages.map((m, i) => (
|
| 92 |
<div key={i} className={`flex ${m.role === 'user' ? 'justify-start' : 'justify-end'}`}>
|
| 93 |
-
<div className={`max-w-[85%] p-3 rounded-2xl text-sm ${m.role === 'user' ? 'bg-slate-800' : 'bg-indigo-600/20 border border-indigo-500/30'}`}>
|
| 94 |
{m.content}
|
| 95 |
</div>
|
| 96 |
</div>
|
| 97 |
))}
|
| 98 |
</div>
|
| 99 |
|
| 100 |
-
<div className="input-
|
| 101 |
-
<div className="max-w-xl mx-auto flex gap-2
|
| 102 |
-
<input
|
| 103 |
-
|
| 104 |
-
placeholder="اسأل..."
|
| 105 |
-
value={input}
|
| 106 |
-
onChange={e => setInput(e.target.value)}
|
| 107 |
-
onKeyDown={e => e.key === 'Enter' && send()}
|
| 108 |
-
/>
|
| 109 |
-
<button onClick={send} className="bg-indigo-600 p-2 rounded-lg">
|
| 110 |
-
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
| 111 |
-
</button>
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
-
{/* Sidebar
|
| 116 |
-
<aside className={`sidebar
|
| 117 |
-
<div className="flex justify-between mb-
|
| 118 |
-
<
|
| 119 |
-
<button onClick={() =>
|
| 120 |
</div>
|
| 121 |
-
<div className="
|
| 122 |
-
<
|
| 123 |
-
|
| 124 |
-
<p className="mt-2 text-slate-400">سيتم الخصم من رصيدك في HF عند تسجيل الدخول.</p>
|
| 125 |
-
</div>
|
| 126 |
-
<input type="file" className="block w-full text-xs text-slate-500 file:bg-slate-800 file:text-white file:border-0 file:py-2 file:px-4 file:rounded-lg" />
|
| 127 |
</div>
|
| 128 |
</aside>
|
| 129 |
</div>
|
|
@@ -136,23 +119,18 @@ async def get_ui():
|
|
| 136 |
</html>
|
| 137 |
"""
|
| 138 |
|
| 139 |
-
# ---
|
| 140 |
-
def get_client(token: str = None):
|
| 141 |
-
# نستخدم التوكن الخاص بالمستخدم إذا سجل دخوله، وإلا نستخدم توكن النظام
|
| 142 |
-
api_key = token if token else os.getenv("HF_TOKEN")
|
| 143 |
-
return OpenAI(
|
| 144 |
-
base_url="https://router.huggingface.co/hf-inference/v1",
|
| 145 |
-
api_key=api_key
|
| 146 |
-
)
|
| 147 |
-
|
| 148 |
@app.post("/v1/chat/completions")
|
| 149 |
async def chat_endpoint(request: Request):
|
| 150 |
body = await request.json()
|
| 151 |
-
#
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
def stream_gen():
|
| 155 |
-
response =
|
| 156 |
model="huihui-ai/Qwen2.5-72B-Instruct-abliterated",
|
| 157 |
messages=body.get("messages", []),
|
| 158 |
stream=True
|
|
@@ -164,14 +142,14 @@ async def chat_endpoint(request: Request):
|
|
| 164 |
|
| 165 |
return StreamingResponse(stream_gen(), media_type="text/event-stream")
|
| 166 |
|
| 167 |
-
#
|
| 168 |
with gr.Blocks() as auth_layer:
|
| 169 |
-
gr.Markdown("#
|
| 170 |
-
|
| 171 |
-
gr.Markdown("بعد تسجيل الدخول، سيتم استخدام رصيدك الشخصي للعمليات.")
|
| 172 |
|
| 173 |
# تشغيل النظام
|
|
|
|
|
|
|
| 174 |
if __name__ == "__main__":
|
| 175 |
import uvicorn
|
| 176 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 177 |
-
|
|
|
|
| 5 |
import gradio as gr
|
| 6 |
from fastapi import FastAPI, Request, File, UploadFile, BackgroundTasks
|
| 7 |
from fastapi.responses import StreamingResponse, HTMLResponse
|
|
|
|
| 8 |
from openai import OpenAI
|
| 9 |
from chromadb.utils import embedding_functions
|
| 10 |
|
| 11 |
+
# --- المحرك الخلفي ---
|
| 12 |
app = FastAPI()
|
| 13 |
|
| 14 |
+
# إعداد الذاكرة
|
| 15 |
STORAGE_PATH = "/data/neural_memory" if os.path.exists("/data") else "./neural_memory"
|
| 16 |
chroma_client = chromadb.PersistentClient(path=STORAGE_PATH)
|
| 17 |
default_ef = embedding_functions.DefaultEmbeddingFunction()
|
| 18 |
+
collection = chroma_client.get_or_create_collection(name="user_vault_v2", embedding_function=default_ef)
|
| 19 |
|
| 20 |
+
# الواجهة المتجاوبة للهاتف
|
| 21 |
@app.get("/", response_class=HTMLResponse)
|
| 22 |
async def get_ui():
|
| 23 |
return """
|
|
|
|
| 25 |
<html lang="ar" dir="rtl">
|
| 26 |
<head>
|
| 27 |
<meta charset="UTF-8">
|
| 28 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
| 29 |
<title>Deep Neural Vault</title>
|
| 30 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 31 |
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
| 32 |
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
| 33 |
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
| 34 |
<style>
|
| 35 |
+
body { background: #020617; color: #f8fafc; margin: 0; padding: 0; height: 100dvh; overflow: hidden; width: 100vw; position: fixed; }
|
| 36 |
+
.chat-stream { height: calc(100dvh - 140px); overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 20px; }
|
| 37 |
+
.input-box { position: fixed; bottom: 0; left: 0; right: 0; padding: 15px; background: #020617; border-top: 1px solid rgba(255,255,255,0.05); }
|
| 38 |
+
@media (max-width: 768px) { .sidebar { width: 85% !important; transform: translateX(100%); position: fixed; top:0; right:0; height:100%; z-index: 1000; transition: transform 0.3s ease; background: #0f172a; } .sidebar.open { transform: translateX(0); } }
|
|
|
|
| 39 |
</style>
|
| 40 |
</head>
|
| 41 |
<body>
|
| 42 |
+
<div id="root"></div>
|
| 43 |
<script type="text/babel">
|
| 44 |
const { useState, useEffect, useRef } = React;
|
|
|
|
| 45 |
function App() {
|
| 46 |
const [messages, setMessages] = useState([]);
|
| 47 |
const [input, setInput] = useState("");
|
| 48 |
+
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
| 49 |
|
| 50 |
const send = async () => {
|
| 51 |
if(!input.trim()) return;
|
| 52 |
+
const userMsg = {role: 'user', content: input};
|
| 53 |
+
setMessages(p => [...p, userMsg]);
|
| 54 |
setInput("");
|
| 55 |
+
|
| 56 |
+
const res = await fetch('/v1/chat/completions', {
|
| 57 |
+
method: 'POST',
|
| 58 |
+
headers: {'Content-Type': 'application/json'},
|
| 59 |
+
body: JSON.stringify({ messages: [...messages, userMsg] })
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
const reader = res.body.getReader();
|
| 63 |
+
let botMsg = { role: 'assistant', content: "" };
|
| 64 |
+
setMessages(p => [...p, botMsg]);
|
| 65 |
+
|
| 66 |
+
while(true) {
|
| 67 |
+
const {done, value} = await reader.read();
|
| 68 |
+
if(done) break;
|
| 69 |
+
const chunk = new TextDecoder().decode(value).replace(/data: /g, '');
|
| 70 |
+
if(chunk.includes("[DONE]")) break;
|
| 71 |
+
botMsg.content += chunk;
|
| 72 |
+
setMessages(p => [...p.slice(0, -1), {...botMsg}]);
|
| 73 |
+
}
|
|
|
|
|
|
|
|
|
|
| 74 |
};
|
| 75 |
|
| 76 |
return (
|
| 77 |
+
<div className="flex flex-col h-full w-full">
|
| 78 |
+
<header className="p-4 border-b border-white/10 flex justify-between items-center bg-slate-900/80 backdrop-blur-md">
|
| 79 |
+
<button onClick={() => setIsOpen(true)} className="text-indigo-400 text-xl">☰</button>
|
| 80 |
+
<h1 className="text-sm font-black">NEURAL VAULT</h1>
|
| 81 |
+
<div className="w-2 h-2 rounded-full bg-emerald-500"></div>
|
| 82 |
</header>
|
| 83 |
|
| 84 |
<div className="chat-stream p-4 space-y-4">
|
| 85 |
{messages.map((m, i) => (
|
| 86 |
<div key={i} className={`flex ${m.role === 'user' ? 'justify-start' : 'justify-end'}`}>
|
| 87 |
+
<div className={`max-w-[85%] p-3 rounded-2xl text-sm ${m.role === 'user' ? 'bg-slate-800' : 'bg-indigo-600/20 border border-indigo-500/30 text-indigo-100'}`}>
|
| 88 |
{m.content}
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
))}
|
| 92 |
</div>
|
| 93 |
|
| 94 |
+
<div className="input-box">
|
| 95 |
+
<div className="max-w-xl mx-auto flex gap-2">
|
| 96 |
+
<input className="flex-1 bg-slate-900 border border-white/10 rounded-xl px-4 py-3 outline-none text-sm" placeholder="تكلم..." value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()} />
|
| 97 |
+
<button onClick={send} className="bg-indigo-600 px-4 rounded-xl">↑</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
</div>
|
| 99 |
</div>
|
| 100 |
|
| 101 |
+
{/* Sidebar */}
|
| 102 |
+
<aside className={`sidebar p-6 shadow-2xl ${isOpen ? 'open' : ''}`}>
|
| 103 |
+
<div className="flex justify-between items-center mb-10">
|
| 104 |
+
<span className="font-bold">الإعدادات</span>
|
| 105 |
+
<button onClick={() => setIsOpen(false)}>✕</button>
|
| 106 |
</div>
|
| 107 |
+
<div className="bg-indigo-600/10 p-4 rounded-xl text-[10px] text-slate-400 leading-loose">
|
| 108 |
+
<p>يتم الآن استهلاك الرصيد عبر: <b>Featherless Provider</b></p>
|
| 109 |
+
<p className="mt-2 uppercase">رابط المحرك: Active ✅</p>
|
|
|
|
|
|
|
|
|
|
| 110 |
</div>
|
| 111 |
</aside>
|
| 112 |
</div>
|
|
|
|
| 119 |
</html>
|
| 120 |
"""
|
| 121 |
|
| 122 |
+
# --- نظام تسجيل الدخول (OAuth) لربط الرصيد ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
@app.post("/v1/chat/completions")
|
| 124 |
async def chat_endpoint(request: Request):
|
| 125 |
body = await request.json()
|
| 126 |
+
# استخدام الرابط الحديث والصحيح لتجنب خطأ 410
|
| 127 |
+
client = OpenAI(
|
| 128 |
+
base_url="https://router.huggingface.co/hf-inference/v1",
|
| 129 |
+
api_key=os.getenv("HF_TOKEN")
|
| 130 |
+
)
|
| 131 |
|
| 132 |
def stream_gen():
|
| 133 |
+
response = client.chat.completions.create(
|
| 134 |
model="huihui-ai/Qwen2.5-72B-Instruct-abliterated",
|
| 135 |
messages=body.get("messages", []),
|
| 136 |
stream=True
|
|
|
|
| 142 |
|
| 143 |
return StreamingResponse(stream_gen(), media_type="text/event-stream")
|
| 144 |
|
| 145 |
+
# طبقة تسجيل الدخول من ملف app(2).py
|
| 146 |
with gr.Blocks() as auth_layer:
|
| 147 |
+
gr.Markdown("# بوابة الدخول")
|
| 148 |
+
gr.LoginButton()
|
|
|
|
| 149 |
|
| 150 |
# تشغيل النظام
|
| 151 |
+
app = gr.mount_gradio_app(app, auth_layer, path="/auth")
|
| 152 |
+
|
| 153 |
if __name__ == "__main__":
|
| 154 |
import uvicorn
|
| 155 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|