RawanGassem commited on
Commit
76b549d
·
verified ·
1 Parent(s): 563a889

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -333
app.py DELETED
@@ -1,333 +0,0 @@
1
- import os
2
- import re
3
- import uvicorn
4
- import pytz
5
- import httpx
6
- import io
7
- import json
8
- import base64
9
- import traceback
10
- from datetime import datetime
11
- from typing import List, Optional, Dict, Any, AsyncGenerator
12
- from fastapi import FastAPI, Request
13
- from fastapi.responses import JSONResponse, StreamingResponse
14
- from pydantic import BaseModel, Field
15
- from transformers import VisionEncoderDecoderModel, ViTImageProcessor, AutoTokenizer
16
- from PIL import Image
17
- import torch
18
-
19
- app = FastAPI(title="ONYX Gateway - Syrian Identity")
20
- GEMMA_URL = "https://rawangassem-onyx-modle.hf.space/predict"
21
- base_identity = "مساعد ذكي متطور، صُنع ليكون رفيقاً رقمياً وفياً بلمسة سورية."
22
-
23
- # --- تحميل نموذج وصف الصور ---
24
- v_model_name = "nlpconnect/vit-gpt2-image-captioning"
25
- v_device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
26
- v_model = VisionEncoderDecoderModel.from_pretrained(v_model_name).to(v_device)
27
- v_feature_extractor = ViTImageProcessor.from_pretrained(v_model_name)
28
- v_tokenizer = AutoTokenizer.from_pretrained(v_model_name)
29
-
30
- class ChatRequest(BaseModel):
31
- messages: list
32
- temperature: float = 0.7
33
- timezone: str = "Asia/Damascus"
34
-
35
- class StoryCharacter(BaseModel):
36
- name: str
37
- gender: Optional[str] = ""
38
- identity: Optional[str] = ""
39
- description_private: Optional[str] = ""
40
- background_public: Optional[str] = ""
41
- greeting: Optional[str] = ""
42
- visibility: Optional[str] = ""
43
-
44
- class StoryGenerateRequest(BaseModel):
45
- story_title: str
46
- story_description: str = ""
47
- chapter_title: str
48
- chapter_number: int = 1
49
- language: str = "العربية"
50
- temperature: float = 0.8
51
- timezone: str = "Asia/Damascus"
52
- current_chapter_text: Optional[str] = ""
53
- characters: List[StoryCharacter] = Field(default_factory=list)
54
- edited_responses: Dict[str, Any] = Field(default_factory=dict)
55
- stream: bool = True
56
-
57
- # --- Helper Functions (اللي كانت ناقصة) ---
58
-
59
- def build_story_system_prompt(request: StoryGenerateRequest) -> str:
60
- def _gender_to_ar(g: Optional[str]) -> str:
61
- raw = (g or "").strip().lower()
62
- if raw in {"male", "m", "ذكر", "ولد"}: return "ذكر"
63
- if raw in {"female", "f", "أنثى", "انثى", "بنت"}: return "أنثى"
64
- return "غير معروف"
65
-
66
- chars_info = ""
67
- allowed = []
68
- for c in request.characters:
69
- name = (c.name or "").strip()
70
- if not name: continue
71
- allowed.append(name)
72
- chars_info += f"- {name} ({_gender_to_ar(c.gender)}): {c.identity or ''}. {c.description_private or ''}\n"
73
-
74
- allowed.append("الراوي")
75
- allowed_names_str = ", ".join(allowed)
76
-
77
- prompt = (
78
- "أنت كاتب روايات محترف.\n"
79
- f"عنوان القصة: {request.story_title}\n"
80
- "مهمتك: توليد حوار تفاعلي وديناميكي بين الشخصيات.\n\n"
81
- f"الشخصيات المتاحة (يجب استخدامها في الحوار):\n{chars_info}\n"
82
- "--- قواعد الإنتاج ---\n"
83
- "1. يجب أن يتضمن المشهد حواراً لعدة شخصيات (لا تكتفِ بشخصية واحدة).\n"
84
- "2. اجعل الشخصيات تتفاعل مع بعضها (ردود فعل، مقاطعة، إجابة).\n"
85
- f"3. الأسماء المسموحة فقط: [{allowed_names_str}].\n"
86
- "4. التزم بصيغة JSON حصراً.\n\n"
87
- "{\n"
88
- ' "chapter_text": "وصف تفصيلي للجو العام وحركات الشخصيات"،\n'
89
- ' "character_responses": [\n'
90
- ' {"name": "اسم الشخصية", "response": "نص الحوار"},\n'
91
- ' {"name": "شخصية أخرى", "response": "رد الشخصية الأخرى"}\n'
92
- ' ]\n'
93
- "}"
94
- )
95
- return prompt
96
-
97
- def build_story_user_prompt(request: StoryGenerateRequest) -> str:
98
- # إضافة سياق القصة الأساسي بالـ User Prompt بقوي النتيجة
99
- context = f"وصف القصة الأساسي: {request.story_description}\n"
100
-
101
- if request.edited_responses:
102
- # (منخلي منطق التعديل مثل ما هو بس منقوي التعليمات)
103
- user_p = f"{context}\nأكمل من بعد التعديل: {request.edited_responses.get('text')}\nاجعل البقية يتفاعلون مع هذا التعديل."
104
- else:
105
- user_p = (
106
- f"{context}\n"
107
- f"الأحداث السابقة: {request.current_chapter_text}\n"
108
- "الآن، وبناءً على ما سبق، تابع كتابة المشهد. "
109
- "اجعل الشخصيات المتاحة تتبادل أطراف الحديث وتتحرك في المكان. "
110
- "أريد حواراً طويلاً وممتعاً بصيغة JSON."
111
- )
112
- return user_p
113
-
114
-
115
-
116
- def filter_story_characters(parsed: Dict[str, Any], allowed_names: set) -> Dict[str, Any]:
117
- try:
118
- if not isinstance(parsed, dict):
119
- return {"chapter_text": str(parsed), "character_responses": []}
120
-
121
- # تنظيف قائمة الأسماء المسموحة وتحويلها لـ lowercase للمقارنة
122
- allowed_map = {str(n).strip().lower(): str(n).strip() for n in (allowed_names or set()) if str(n).strip()}
123
-
124
- responses = parsed.get("character_responses", [])
125
- if not isinstance(responses, list):
126
- parsed["character_responses"] = []
127
- return parsed
128
-
129
- filtered = []
130
- for item in responses:
131
- if not isinstance(item, dict):
132
- continue
133
-
134
- name_raw = str(item.get("name", "")).strip()
135
- name_lower = name_raw.lower()
136
-
137
- # التأكد إذا الاسم موجود بالقائمة بغض النظر عن حالة الأحرف
138
- if not name_raw or name_lower not in allowed_map:
139
- continue
140
-
141
- # استخراج النص من أي مفتاح محتمل
142
- resp_raw = (
143
- item.get("response")
144
- or item.get("dialogue")
145
- or item.get("text")
146
- or item.get("content")
147
- )
148
- resp = str(resp_raw or "").strip()
149
- if not resp:
150
- continue
151
-
152
- # نستخدم الاسم الأصلي من allowed_map مشان يضل التنسيق متل ما بدك
153
- filtered.append({"name": allowed_map[name_lower], "response": resp})
154
-
155
- parsed["character_responses"] = filtered
156
- parsed["chapter_text"] = parsed.get("chapter_text", "")
157
- return parsed
158
- except Exception:
159
- # في حال حدوث خطأ كارثي، نرجع القاموس بالبيانات الخام بدل ما يضرب السيرفر
160
- return parsed
161
-
162
-
163
-
164
- async def stream_engine_text(payload: dict):
165
- async with httpx.AsyncClient(timeout=120.0) as client:
166
- async with client.stream("POST", GEMMA_URL, json=payload) as response:
167
- if response.status_code != 200:
168
- yield "خطأ في الاتصال بمحرك الذكاء الصناعي."
169
- return
170
- async for chunk in response.aiter_text():
171
- yield chunk
172
-
173
- async def collect_engine_text(payload: dict) -> str:
174
- full_text = ""
175
- async for chunk in stream_engine_text(payload):
176
- full_text += chunk
177
- return full_text
178
-
179
- def try_extract_json(text: str) -> Dict[str, Any]:
180
- try:
181
- # البحث عن أول { وآخر } في حال وجود نص زائد
182
- match = re.search(r'\{.*\}', text, re.DOTALL)
183
- if match:
184
- return json.loads(match.group())
185
- return json.loads(text)
186
- except:
187
- return {"chapter_text": text, "character_responses": []}
188
- @app.get("/")
189
- async def root():
190
- return {"status": "ONYX Gateway running"}
191
-
192
- def predict_image(image_data_base64: str) -> str:
193
- try:
194
- img_bytes = base64.decodebytes(image_data_base64.encode())
195
- image = Image.open(io.BytesIO(img_bytes))
196
- if image.mode != "RGB":
197
- image = image.convert("RGB")
198
- pixel_values = v_feature_extractor(images=[image], return_tensors="pt").pixel_values.to(v_device)
199
- output_ids = v_model.generate(pixel_values, max_length=16, num_beams=4)
200
- preds = v_tokenizer.batch_decode(output_ids, skip_special_tokens=True)
201
- return preds[0].strip()
202
- except Exception:
203
- return "صورة مرفقة"
204
-
205
- @app.post("/v1/chat/completions")
206
- async def chat_endpoint(request: ChatRequest):
207
- # 1. تجهيز الـ System Prompt والرسائل (نفس منطقك السابق)
208
- try:
209
- user_tz = pytz.timezone(request.timezone)
210
- now = datetime.now(user_tz)
211
- current_day = now.strftime("%A")
212
- current_time_str = now.strftime("%I:%M %p")
213
- except Exception:
214
- current_day = "اليوم"
215
- current_time_str = datetime.now().strftime("%I:%M %p")
216
-
217
- incoming_system = next((m for m in request.messages if m.get("role") == "system"), None)
218
- incoming_system_prompt = incoming_system["content"] if incoming_system else base_identity
219
-
220
- updated_system_prompt = (
221
- f"{incoming_system_prompt}\n\n"
222
- f"الوقت الحالي: {current_day}, {current_time_str}\n"
223
- "تحدث باللهجة السورية الشامية فقط. لا تكرر التحية."
224
- )
225
-
226
- engine_messages = [{"role": "system", "content": updated_system_prompt}]
227
- for msg in request.messages:
228
- if msg.get("role") == "system": continue
229
- content = msg.get("content", "")
230
- if isinstance(content, list):
231
- # معالجة الصور إذا وجدت
232
- text_parts = [item.get("text", "") for item in content if item.get("type") == "text"]
233
- for item in content:
234
- if item.get("type") == "image_url":
235
- img_data = item.get("image_url", {}).get("url", "")
236
- if "," in img_data:
237
- desc = predict_image(img_data.split(",")[-1])
238
- text_parts.append(f"[وصف الصورة: {desc}]")
239
- engine_messages.append({"role": msg.get("role"), "content": " ".join(text_parts)})
240
- else:
241
- engine_messages.append({"role": msg.get("role"), "content": str(content)})
242
-
243
- payload = {"messages": engine_messages, "temperature": request.temperature}
244
-
245
- # 2. وظيفة الـ Generator لجلب البيانات شقفة شقفة (Stream)
246
- async def stream_generator():
247
- async with httpx.AsyncClient(timeout=120.0) as client:
248
- async with client.stream("POST", GEMMA_URL, json=payload) as response:
249
- if response.status_code != 200:
250
- yield "خطأ في الاتصال بالمحرك."
251
- return
252
- async for chunk in response.aiter_text():
253
- # هون منمرر النص اللي عم يطلعه الـ Engine فوراً
254
- yield chunk
255
-
256
- return StreamingResponse(stream_generator(), media_type="text/plain")
257
-
258
-
259
- # =========================
260
- # Story streaming endpoint
261
- # =========================
262
- # ... (نفس الاستيرادات السابقة)
263
-
264
- @app.post("/v1/story/generate")
265
- async def story_generate_endpoint(request: StoryGenerateRequest):
266
- try:
267
- system_prompt = build_story_system_prompt(request)
268
- user_prompt = build_story_user_prompt(request)
269
-
270
- payload = {
271
- "messages": [
272
- {"role": "system", "content": system_prompt},
273
- {"role": "user", "content": user_prompt},
274
- ],
275
- "temperature": request.temperature,
276
- }
277
-
278
- if request.stream:
279
- async def stream_generator():
280
- async with httpx.AsyncClient(timeout=120.0) as client:
281
- async with client.stream("POST", GEMMA_URL, json=payload) as response:
282
- if response.status_code != 200:
283
- yield json.dumps({"ok": False, "error": "Connection Error"}, ensure_ascii=False)
284
- return
285
-
286
- # منبعت النص شقفة شقفة متل ما عم يجي من الـ Engine
287
- async for chunk in response.aiter_text():
288
- # منمرر الـ chunk فوراً للفرونت
289
- yield chunk
290
-
291
- return StreamingResponse(stream_generator(), media_type="text/plain; charset=utf-8")
292
-
293
- # في حال مو ستريم (الوضع العادي)
294
- raw_text = await collect_engine_text(payload)
295
- parsed = try_extract_json(raw_text)
296
- allowed = set([c.name.strip() for c in request.characters if c.name] + ["الراوي"])
297
- parsed = filter_story_characters(parsed, allowed)
298
- return JSONResponse({"ok": True, "data": parsed})
299
-
300
- except Exception as e:
301
- traceback.print_exc()
302
- return JSONResponse(status_code=500, content={"ok": False, "error": str(e)})
303
-
304
- @app.post("/v1/story/generate-json")
305
- async def story_generate_json_endpoint(request: StoryGenerateRequest):
306
- try:
307
- system_prompt = build_story_system_prompt(request)
308
- user_prompt = build_story_user_prompt(request)
309
-
310
- payload = {
311
- "messages": [
312
- {"role": "system", "content": system_prompt},
313
- {"role": "user", "content": user_prompt},
314
- ],
315
- "temperature": request.temperature,
316
- }
317
-
318
- raw_text = await collect_engine_text(payload)
319
- parsed = try_extract_json(raw_text)
320
- allowed = set([c.name for c in request.characters if c.name] + ["الراوي"])
321
- parsed = filter_story_characters(parsed, allowed)
322
-
323
- return JSONResponse({
324
- "ok": True,
325
- "chapter_text": parsed.get("chapter_text", ""),
326
- "character_responses": parsed.get("character_responses", []),
327
- "raw_text": raw_text
328
- })
329
- except Exception as e:
330
- traceback.print_exc()
331
- return JSONResponse(status_code=500, content={"ok": False, "error": str(e)})
332
- if __name__ == "__main__":
333
- uvicorn.run(app, host="0.0.0.0", port=7860)