1yahoo commited on
Commit
91d07ed
·
verified ·
1 Parent(s): 0521bb0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -157
app.py CHANGED
@@ -2,45 +2,22 @@ import os
2
  import uuid
3
  import pypdf
4
  import chromadb
 
5
  from fastapi import FastAPI, Request, File, UploadFile, BackgroundTasks
6
  from fastapi.responses import StreamingResponse, HTMLResponse
7
  from fastapi.middleware.cors import CORSMiddleware
8
  from openai import OpenAI
9
  from chromadb.utils import embedding_functions
10
 
 
11
  app = FastAPI()
12
 
13
- # --- إعداد الذاكرة والذكاء ---
14
  STORAGE_PATH = "/data/neural_memory" if os.path.exists("/data") else "./neural_memory"
15
  chroma_client = chromadb.PersistentClient(path=STORAGE_PATH)
16
  default_ef = embedding_functions.DefaultEmbeddingFunction()
17
- collection = chroma_client.get_or_create_collection(name="vault_final", embedding_function=default_ef)
18
-
19
- client = OpenAI(
20
- base_url="https://router.huggingface.co/hf-inference/v1",
21
- api_key=os.getenv("HF_TOKEN")
22
- )
23
-
24
- # --- محرك المعالجة (RAG Engine) ---
25
- def process_document(file_content, filename):
26
- text = ""
27
- if filename.endswith('.pdf'):
28
- # معالجة PDF
29
- with open("temp.pdf", "wb") as f:
30
- f.write(file_content)
31
- reader = pypdf.PdfReader("temp.pdf")
32
- for page in reader.pages:
33
- text += page.extract_text() + " "
34
- else:
35
- # معالجة نص
36
- text = file_content.decode("utf-8", errors="ignore")
37
-
38
- # تقسيم النص لقطع (Chunking)
39
- chunks = [text[i:i+1000] for i in range(0, len(text), 800)]
40
- ids = [str(uuid.uuid4()) for _ in chunks]
41
- collection.add(documents=chunks, ids=ids)
42
 
43
- # --- واجهة المستخدم (Responsive UI) ---
44
  @app.get("/", response_class=HTMLResponse)
45
  async def get_ui():
46
  return """
@@ -54,141 +31,104 @@ async def get_ui():
54
  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
55
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
56
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
57
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;600&display=swap" rel="stylesheet">
58
  <style>
59
- body { font-family: 'IBM Plex Sans Arabic', sans-serif; background: #020617; color: #f8fafc; height: 100vh; overflow: hidden; }
60
- .glass-header { background: rgba(15, 23, 42, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(255,255,255,0.05); }
61
- .sidebar { background: rgba(7, 10, 25, 0.98); backdrop-filter: blur(20px); z-index: 100; transition: all 0.3s ease; }
62
- .chat-box { height: calc(100vh - 140px); overflow-y: auto; scroll-behavior: smooth; }
63
- .user-msg { background: #1e293b; border-radius: 18px 18px 0 18px; }
64
- .bot-msg { background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 18px 18px 18px 0; }
65
  </style>
66
  </head>
67
  <body>
68
- <div id="root"></div>
69
  <script type="text/babel">
70
  const { useState, useEffect, useRef } = React;
71
 
72
  function App() {
73
  const [messages, setMessages] = useState([]);
74
  const [input, setInput] = useState("");
 
75
  const [status, setStatus] = useState("idle");
76
- const [sidebar, setSidebar] = useState(false);
77
- const [settings, setSettings] = useState({ temp: 0.7, tokens: 2048 });
78
- const endRef = useRef(null);
79
-
80
- useEffect(() => endRef.current?.scrollIntoView({ behavior: "smooth" }), [messages]);
81
-
82
- const handleUpload = async (e) => {
83
- const file = e.target.files[0];
84
- if(!file) return;
85
- setStatus("indexing");
86
- const fd = new FormData();
87
- fd.append('file', file);
88
- await fetch('/upload', { method: 'POST', body: fd });
89
- setStatus("idle");
90
- alert("تم حقن المستند في الذاكرة بنجاح");
91
- };
92
 
93
  const send = async () => {
94
- if(!input.trim() || status !== "idle") return;
95
- const msg = { role: 'user', content: input };
96
- setMessages(p => [...p, msg]);
97
  setInput("");
98
  setStatus("thinking");
99
 
100
- const res = await fetch('/v1/chat/completions', {
101
- method: 'POST',
102
- headers: {'Content-Type': 'application/json'},
103
- body: JSON.stringify({ messages: [...messages, msg], temperature: settings.temp })
104
- });
105
-
106
- const reader = res.body.getReader();
107
- let botMsg = { role: 'assistant', content: "" };
108
- setMessages(p => [...p, botMsg]);
109
-
110
- while (true) {
111
- const {done, value} = await reader.read();
112
- if (done) break;
113
- const chunk = new TextDecoder().decode(value).replace(/data: /g, '');
114
- if(chunk.includes("[DONE]")) break;
115
- botMsg.content += chunk;
116
- setMessages(p => [...p.slice(0, -1), {...botMsg}]);
117
- }
 
118
  setStatus("idle");
119
  };
120
 
121
  return (
122
- <div className="flex h-screen relative">
123
- {/* Sidebar */}
124
- <aside className={`${sidebar ? 'translate-x-0' : 'translate-x-full'} md:translate-x-0 fixed md:relative right-0 w-72 h-full sidebar p-6 border-l border-white/5`}>
125
- <div className="flex justify-between items-center mb-10 md:hidden">
126
- <span className="font-bold">إغلاق</span>
127
- <button onClick={() => setSidebar(false)} className="text-2xl">✕</button>
128
- </div>
129
- <h2 className="text-indigo-400 font-bold mb-6 flex items-center gap-2"><span>⚙️</span> الإعدادات</h2>
130
-
131
- <div className="space-y-8">
132
- <div>
133
- <label className="text-xs text-slate-500 block mb-2 uppercase tracking-widest">مستوى الإبداع: {settings.temp}</label>
134
- <input type="range" min="0" max="1.5" step="0.1" value={settings.temp} onChange={e => setSettings({...settings, temp: parseFloat(e.target.value)})} className="w-full accent-indigo-500" />
135
- </div>
136
-
137
- <div className="pt-6 border-t border-white/5">
138
- <label className="text-xs text-slate-500 block mb-4 uppercase tracking-widest">حقن مستند (PDF/TXT)</label>
139
- <label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-700 rounded-xl cursor-pointer hover:border-indigo-500 transition-all">
140
- <div className="text-center">
141
- <span className="text-2xl block mb-2">📁</span>
142
- <span className="text-[10px] text-slate-400">اضغط للرفع</span>
143
- </div>
144
- <input type="file" className="hidden" onChange={handleUpload} />
145
- </label>
146
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  </div>
148
- </aside>
149
 
150
- {/* Main Chat */}
151
- <main className="flex-1 flex flex-col min-w-0 bg-[#020617]">
152
- <header className="glass-header p-4 flex justify-between items-center">
153
- <button onClick={() => setSidebar(true)} className="md:hidden text-indigo-400 text-xl"></button>
154
- <div className="flex items-center gap-3">
155
- <div className={`w-2 h-2 rounded-full ${status === 'thinking' ? 'bg-indigo-500 animate-pulse' : 'bg-emerald-500'}`}></div>
156
- <h1 className="text-sm font-bold tracking-tighter">DEEP NEURAL VAULT</h1>
157
- </div>
158
- <div className="text-[10px] font-mono text-slate-500">V.2.5_RAG</div>
159
- </header>
160
-
161
- <div className="chat-box p-4 space-y-6 custom-scrollbar">
162
- {messages.map((m, i) => (
163
- <div key={i} className={`flex ${m.role === 'user' ? 'justify-start' : 'justify-end'}`}>
164
- <div className={`max-w-[88%] md:max-w-[75%] p-4 text-sm md:text-base leading-relaxed ${m.role === 'user' ? 'user-msg text-slate-200' : 'bot-msg text-indigo-50'}`}>
165
- {m.content}
166
- </div>
167
- </div>
168
- ))}
169
- <div ref={endRef} />
170
  </div>
171
-
172
- <div className="p-4 bg-gradient-to-t from-[#020617] to-transparent">
173
- <div className="max-w-4xl mx-auto flex gap-2 bg-slate-900/80 border border-white/10 p-2 rounded-2xl">
174
- <input
175
- className="flex-1 bg-transparent px-4 py-2 outline-none text-sm"
176
- placeholder="تواصل مع الدهليز..."
177
- value={input}
178
- onChange={e => setInput(e.target.value)}
179
- onKeyDown={e => e.key === 'Enter' && send()}
180
- />
181
- <button onClick={send} className="bg-indigo-600 hover:bg-indigo-500 p-3 rounded-xl transition-all">
182
- <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>
183
- </button>
184
  </div>
 
185
  </div>
186
- </main>
187
- {sidebar && <div onClick={() => setSidebar(false)} className="fixed inset-0 bg-black/60 z-[90] md:hidden"></div>}
188
  </div>
189
  );
190
  }
191
-
192
  const root = ReactDOM.createRoot(document.getElementById('root'));
193
  root.render(<App />);
194
  </script>
@@ -196,40 +136,42 @@ async def get_ui():
196
  </html>
197
  """
198
 
199
- # --- مسارات الخلفية (Endpoints) ---
200
- @app.post("/upload")
201
- async def upload(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
202
- content = await file.read()
203
- background_tasks.add_task(process_document, content, file.filename)
204
- return {"status": "processing"}
 
 
205
 
206
  @app.post("/v1/chat/completions")
207
- async def chat(request: Request):
208
  body = await request.json()
209
- messages = body.get("messages", [])
 
210
 
211
- # استرجاع المعرفة
212
- query = messages[-1]["content"] if messages else ""
213
- results = collection.query(query_texts=[query], n_results=2)
214
- ctx = "\\n".join(results['documents'][0]) if results['documents'] else ""
215
-
216
- # بناء الرسائل مع السياق
217
- sys_prompt = {"role": "system", "content": f"You are the Neural Vault. Context: {ctx}"}
218
-
219
- def stream():
220
- response = client.chat.completions.create(
221
  model="huihui-ai/Qwen2.5-72B-Instruct-abliterated",
222
- messages=[sys_prompt] + messages,
223
- stream=True,
224
- temperature=body.get("temperature", 0.7)
225
  )
226
  for chunk in response:
227
  if chunk.choices[0].delta.content:
228
- yield f"data: {chunk.choices[0].delta.content}\\n\\n"
229
- yield "data: [DONE]\\n\\n"
 
 
230
 
231
- return StreamingResponse(stream(), media_type="text/event-stream")
 
 
 
 
232
 
 
233
  if __name__ == "__main__":
234
  import uvicorn
235
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
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 fastapi.middleware.cors import CORSMiddleware
9
  from openai import OpenAI
10
  from chromadb.utils import embedding_functions
11
 
12
+ # --- المحرك الخلفي (FastAPI) ---
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="user_vault_v1", embedding_function=default_ef)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
 
21
  @app.get("/", response_class=HTMLResponse)
22
  async def get_ui():
23
  return """
 
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; display: flex; flex-direction: column; overflow: hidden; }
36
+ .mobile-safe-area { height: 100%; display: flex; flex-direction: column; position: relative; }
37
+ .chat-stream { flex: 1; overflow-y: auto; padding-bottom: 80px; -webkit-overflow-scrolling: touch; }
38
+ .input-area { position: absolute; bottom: 0; left: 0; right: 0; padding: 12px; background: linear-gradient(transparent, #020617 20%); }
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" class="mobile-safe-area"></div>
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 [isSideOpen, setSideOpen] = useState(false);
51
  const [status, setStatus] = useState("idle");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  const send = async () => {
54
+ if(!input.trim()) return;
55
+ const newMsgs = [...messages, {role: 'user', content: input}];
56
+ setMessages(newMsgs);
57
  setInput("");
58
  setStatus("thinking");
59
 
60
+ try {
61
+ const res = await fetch('/v1/chat/completions', {
62
+ method: 'POST',
63
+ headers: {'Content-Type': 'application/json'},
64
+ body: JSON.stringify({ messages: newMsgs })
65
+ });
66
+ const reader = res.body.getReader();
67
+ let currentBotMsg = { role: 'assistant', content: "" };
68
+ setMessages(prev => [...prev, currentBotMsg]);
69
+
70
+ while(true) {
71
+ const {done, value} = await reader.read();
72
+ if(done) break;
73
+ const text = new TextDecoder().decode(value).replace(/data: /g, '');
74
+ if(text.includes("[DONE]")) break;
75
+ currentBotMsg.content += text;
76
+ setMessages(prev => [...prev.slice(0, -1), {...currentBotMsg}]);
77
+ }
78
+ } catch(e) { console.error(e); }
79
  setStatus("idle");
80
  };
81
 
82
  return (
83
+ <div className="h-full flex flex-col">
84
+ <header className="p-4 border-b border-white/10 flex justify-between items-center bg-slate-900/50">
85
+ <button onClick={() => setSideOpen(true)} className="text-indigo-400">☰</button>
86
+ <span className="text-xs font-bold tracking-widest">NEURAL VAULT</span>
87
+ <div className={`w-2 h-2 rounded-full ${status === 'idle' ? 'bg-emerald-500' : 'bg-indigo-500 animate-pulse'}`}></div>
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-area">
101
+ <div className="max-w-xl mx-auto flex gap-2 bg-slate-900 border border-white/10 rounded-xl p-1 shadow-xl">
102
+ <input
103
+ className="flex-1 bg-transparent px-3 py-2 outline-none text-sm"
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 Drawer */}
116
+ <aside className={`sidebar-drawer p-6 ${isSideOpen ? 'translate-x-0' : 'translate-x-full'}`}>
117
+ <div className="flex justify-between mb-8">
118
+ <h2 className="font-bold">الحساب والذاكرة</h2>
119
+ <button onClick={() => setSideOpen(false)}>✕</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  </div>
121
+ <div className="space-y-6">
122
+ <div className="p-4 bg-indigo-600/10 rounded-xl border border-indigo-500/20 text-xs">
123
+ <p>نظام الاستهلاك: <b>Featherless API</b></p>
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>
130
  );
131
  }
 
132
  const root = ReactDOM.createRoot(document.getElementById('root'));
133
  root.render(<App />);
134
  </script>
 
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
+ # [span_2](start_span)هنا يتم ربط الطلب بـ featherless-ai كما في ملف app(2).py[span_2](end_span)
152
+ local_client = get_client()
153
 
154
+ def stream_gen():
155
+ response = local_client.chat.completions.create(
 
 
 
 
 
 
 
 
156
  model="huihui-ai/Qwen2.5-72B-Instruct-abliterated",
157
+ messages=body.get("messages", []),
158
+ stream=True
 
159
  )
160
  for chunk in response:
161
  if chunk.choices[0].delta.content:
162
+ yield f"data: {chunk.choices[0].delta.content}\n\n"
163
+ yield "data: [DONE]\n\n"
164
+
165
+ return StreamingResponse(stream_gen(), media_type="text/event-stream")
166
 
167
+ # إطلاق واجهة Gradio كطبقة دخول اختيارية لتأكيد الهوية
168
+ with gr.Blocks() as auth_layer:
169
+ gr.Markdown("# تسجيل الدخول")
170
+ login_btn = gr.LoginButton("تسجيل الدخول باستخدام Hugging Face")
171
+ gr.Markdown("بعد تسجيل الدخول، سيتم استخدام رصيدك الشخصي للعمليات.")
172
 
173
+ # تشغيل النظام
174
  if __name__ == "__main__":
175
  import uvicorn
176
  uvicorn.run(app, host="0.0.0.0", port=7860)
177
+