1yahoo commited on
Commit
cdf481e
·
verified ·
1 Parent(s): ec00cab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -103
app.py CHANGED
@@ -1,64 +1,66 @@
1
  import os
2
  import uuid
3
- import pypdf
4
- 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 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 """
24
- <!DOCTYPE html>
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]);
@@ -71,84 +73,121 @@ async def get_ui():
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>
113
- );
114
- }
115
- const root = ReactDOM.createRoot(document.getElementById('root'));
116
- root.render(<App />);
117
- </script>
118
- </body>
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
137
  )
138
- for chunk in response:
139
  if chunk.choices[0].delta.content:
140
  yield f"data: {chunk.choices[0].delta.content}\n\n"
141
  yield "data: [DONE]\n\n"
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
 
1
  import os
2
  import uuid
 
 
3
  import gradio as gr
4
+ from fastapi import FastAPI, Request, File, UploadFile
5
  from fastapi.responses import StreamingResponse, HTMLResponse
6
  from openai import OpenAI
7
+ from gradio.oauth import get_space_authorized_header
8
 
9
+ # إعداد FastAPI
10
  app = FastAPI()
11
 
12
+ # واجهة المستخدم (HTML/React) - مصممة للهواتف
13
+ HTML_UI = """
14
+ <!DOCTYPE html>
15
+ <html lang="ar" dir="rtl">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
19
+ <title>Neural Vault</title>
20
+ <script src="https://cdn.tailwindcss.com"></script>
21
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
22
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
23
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
24
+ <style>
25
+ body { background: #020617; color: #f8fafc; height: 100dvh; overflow: hidden; margin: 0; position: fixed; width: 100vw; font-family: system-ui; }
26
+ .chat-area { height: calc(100dvh - 160px); overflow-y: auto; padding: 20px; -webkit-overflow-scrolling: touch; }
27
+ .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; }
28
+ .sidebar.open { right: 0; }
29
+ .input-bar { position: fixed; bottom: 0; width: 100%; padding: 15px; background: #020617; border-top: 1px solid #1e293b; }
30
+ .bubble { max-width: 85%; padding: 12px; border-radius: 18px; font-size: 14px; margin-bottom: 10px; line-height: 1.5; }
31
+ .user { background: #1e293b; margin-right: auto; border-bottom-right-radius: 4px; }
32
+ .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; }
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div id="root"></div>
37
+ <script type="text/babel">
38
+ const { useState, useEffect, useRef } = React;
39
+ function App() {
40
+ const [messages, setMessages] = useState([]);
41
+ const [input, setInput] = useState("");
42
+ const [isSideOpen, setSideOpen] = useState(false);
43
+ const [status, setStatus] = useState("idle");
44
+ const chatRef = useRef(null);
45
+
46
+ useEffect(() => { chatRef.current?.scrollTo(0, chatRef.current.scrollHeight); }, [messages]);
47
+
48
+ const send = async () => {
49
+ if(!input.trim()) return;
50
+ const userMsg = {role: 'user', content: input};
51
+ setMessages(prev => [...prev, userMsg]);
52
+ setInput("");
53
+ setStatus("thinking");
54
+
55
+ try {
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
+ if (res.status === 401) { window.location.href = "/auth"; return; }
63
+
64
  const reader = res.body.getReader();
65
  let botMsg = { role: 'assistant', content: "" };
66
  setMessages(p => [...p, botMsg]);
 
73
  botMsg.content += chunk;
74
  setMessages(p => [...p.slice(0, -1), {...botMsg}]);
75
  }
76
+ } catch (e) { console.error(e); }
77
+ setStatus("idle");
78
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ const uploadFile = async (e) => {
81
+ const file = e.target.files[0];
82
+ if(!file) return;
83
+ const fd = new FormData();
84
+ fd.append('file', file);
85
+ setStatus("uploading");
86
+ await fetch('/upload', { method: 'POST', body: fd });
87
+ setStatus("idle");
88
+ alert("تمت إضافة الملف للذاكرة");
89
+ };
90
+
91
+ return (
92
+ <div className="flex flex-col h-full">
93
+ <header className="p-4 border-b border-white/5 flex justify-between items-center bg-slate-900/50">
94
+ <button onClick={() => setSideOpen(true)} className="text-xl">☰</button>
95
+ <span className="font-bold text-sm tracking-widest">NEURAL VAULT</span>
96
+ <div className={`w-2 h-2 rounded-full ${status === 'idle' ? 'bg-emerald-500' : 'bg-indigo-500 animate-pulse'}`}></div>
97
+ </header>
98
+
99
+ <div ref={chatRef} className="chat-area space-y-4">
100
+ {messages.map((m, i) => (
101
+ <div key={i} className={`bubble ${m.role === 'user' ? 'user' : 'bot'}`}>
102
+ {m.content}
103
  </div>
104
+ ))}
105
+ </div>
106
+
107
+ <div className="input-bar">
108
+ <div className="flex gap-2 max-w-xl mx-auto">
109
+ <label className="bg-slate-800 p-3 rounded-xl cursor-pointer">
110
+ 📎 <input type="file" className="hidden" onChange={uploadFile} />
111
+ </label>
112
+ <input
113
+ className="flex-1 bg-slate-900 border border-white/10 rounded-xl px-4 py-2 outline-none text-sm"
114
+ placeholder="اكتب سؤالك..."
115
+ value={input}
116
+ onChange={e => setInput(e.target.value)}
117
+ onKeyDown={e => e.key === 'Enter' && send()}
118
+ />
119
+ <button onClick={send} className="bg-indigo-600 px-5 rounded-xl">↑</button>
120
  </div>
121
+ </div>
122
 
123
+ <div className={`sidebar p-6 ${isSideOpen ? 'open' : ''}`}>
124
+ <div className="flex justify-between items-center mb-10">
125
+ <h2 className="font-bold">الإعدادات</h2>
126
+ <button onClick={() => setSideOpen(false)}>✕</button>
127
+ </div>
128
+ <div className="space-y-4 text-xs text-slate-400">
129
+ <div className="p-4 bg-slate-800/50 rounded-xl">
130
+ <p>النموذج: Mistral-Small-24B</p>
131
+ <p className="mt-2 text-emerald-400">الحالة: متصل عبر الرصيد الشخصي</p>
132
  </div>
133
+ <button className="w-full py-3 bg-red-500/10 text-red-500 rounded-lg" onClick={() => window.location.href='/logout'}>تسجيل الخروج</button>
134
+ </div>
135
  </div>
136
+ </div>
137
+ );
138
+ }
139
+ const root = ReactDOM.createRoot(document.getElementById('root'));
140
+ root.render(<App />);
141
+ </script>
142
+ </body>
143
+ </html>
144
+ """
145
+
146
+ @app.get("/")
147
+ async def ui(request: Request):
148
+ # إذا لم يسجل دخوله، نوجهه لصفحة الدخول في Gradio
149
+ if not get_space_authorized_header(request):
150
+ return HTMLResponse("<html><script>window.location.href='/auth'</script></html>")
151
+ return HTMLResponse(HTML_UI)
152
+
153
+ @app.post("/upload")
154
+ async def handle_upload(file: UploadFile = File(...)):
155
+ # هنا يتم استقبال الملفات وإضافتها للذاكرة
156
+ return {"status": "success"}
157
+
158
  @app.post("/v1/chat/completions")
159
+ async def chat_api(request: Request):
160
+ auth_header = get_space_authorized_header(request)
161
+ if not auth_header: return StreamingResponse(iter(["data: [401 Unauthorized]\n\n"]), status_code=401)
162
+
163
+ # استخراج التوكن الخاص بالمستخدم للخصم من رصيده
164
+ user_token = auth_header.get("Authorization").replace("Bearer ", "")
165
  body = await request.json()
166
+
167
  client = OpenAI(
168
  base_url="https://router.huggingface.co/hf-inference/v1",
169
+ api_key=user_token
170
  )
171
+
172
  def stream_gen():
173
+ resp = client.chat.completions.create(
174
+ model="huihui-ai/Mistral-Small-24B-Instruct-2501-abliterated",
175
  messages=body.get("messages", []),
176
  stream=True
177
  )
178
+ for chunk in resp:
179
  if chunk.choices[0].delta.content:
180
  yield f"data: {chunk.choices[0].delta.content}\n\n"
181
  yield "data: [DONE]\n\n"
182
 
183
  return StreamingResponse(stream_gen(), media_type="text/event-stream")
184
 
185
+ # إعداد واجهة تسجيل الدخول الخاصة بـ Hugging Face
186
+ with gr.Blocks() as auth_interface:
187
+ gr.Markdown("# مرحبا بك في الدهليز\nسجل دخولك لاستخدام رصيدك الشخصي")
188
+ gr.LoginButton("الدخول عبر Hugging Face")
189
 
190
+ app = gr.mount_gradio_app(app, auth_interface, path="/auth")
 
191
 
192
  if __name__ == "__main__":
193
  import uvicorn