Jan2000 commited on
Commit
59b269a
·
unverified ·
1 Parent(s): 22e35aa

Add files via upload

Browse files
Files changed (3) hide show
  1. README.md +10 -0
  2. app.py +223 -0
  3. requirements.txt +13 -0
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Chatlala
3
+ emoji: 🏃
4
+ colorFrom: red
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- START OF FILE app.py ---
2
+
3
+ import os
4
+ import json
5
+ import logging
6
+ import threading
7
+ import base64
8
+ import io
9
+ from flask import Flask, render_template, request, Response
10
+ import requests
11
+ import docx
12
+
13
+ # ================== بخش تنظیمات لاگ‌نویسی (بدون تغییر) ==================
14
+
15
+ class NoGrpcFilter(logging.Filter):
16
+ def filter(self, record):
17
+ return not record.getMessage().startswith('ALTS creds ignored.')
18
+
19
+ def setup_logging():
20
+ log_format = '[%(asctime)s] [%(levelname)s]: %(message)s'
21
+ date_format = '%Y-%m-%d %H:%M:%S'
22
+ formatter = logging.Formatter(log_format, datefmt=date_format)
23
+
24
+ root_logger = logging.getLogger()
25
+ if root_logger.hasHandlers():
26
+ root_logger.handlers.clear()
27
+
28
+ console_handler = logging.StreamHandler()
29
+ console_handler.setFormatter(formatter)
30
+ console_handler.addFilter(NoGrpcFilter())
31
+
32
+ root_logger.addHandler(console_handler)
33
+ root_logger.setLevel(logging.INFO)
34
+
35
+ setup_logging()
36
+ app = Flask(__name__)
37
+
38
+ # ================== بخش پیکربندی Gemini (با تغییر) ==================
39
+
40
+ GEMINI_MODEL_NAME = "gemini-2.5-flash"
41
+ ALL_KEYS_STR = os.environ.get("ALL_GEMINI_API_KEYS", "")
42
+ GEMINI_API_KEYS = [key.strip() for key in ALL_KEYS_STR.split(',') if key.strip()]
43
+
44
+ if not GEMINI_API_KEYS:
45
+ logging.critical("هشدار: هیچ کلید API برای Gemini در Secrets تنظیم نشده است! (ALL_GEMINI_API_KEYS)")
46
+
47
+ key_index_counter = 0
48
+ key_lock = threading.Lock()
49
+
50
+ def get_next_key_with_index():
51
+ global key_index_counter
52
+ with key_lock:
53
+ if not GEMINI_API_KEYS:
54
+ raise ValueError("لیست کلیدهای API خالی است.")
55
+ current_index = key_index_counter
56
+ key = GEMINI_API_KEYS[current_index]
57
+ key_index_counter = (key_index_counter + 1) % len(GEMINI_API_KEYS)
58
+ return key, current_index
59
+
60
+ # *** START: MODIFIED - افزودن ثابت‌های جدید برای مهلت زمانی ***
61
+ # مهلت زمانی (به ثانیه) برای شروع دریافت پاسخ از سرور
62
+ STREAM_START_TIMEOUT = 4
63
+ # مهلت زمانی (به ثانیه) برای دریافت هر قطعه جدید از داده در حین استریم
64
+ # اگر در این مدت داده جدیدی نرسد، اتصال قطع و با کلید بعدی تلاش می‌شود
65
+ STREAM_READ_TIMEOUT = 15
66
+ # *** END: MODIFIED ***
67
+
68
+ # ================== پایان بخش پیکربندی ====================
69
+
70
+ @app.route('/')
71
+ def index():
72
+ return render_template('index.html')
73
+
74
+ @app.route('/chat', methods=['POST'])
75
+ def chat():
76
+ if not GEMINI_API_KEYS:
77
+ error_payload = {"type": "error", "message": "خطای سرور: هیچ کلید API پیکربندی نشده است."}
78
+ return Response(f"data: {json.dumps(error_payload)}\n\n", status=500, mimetype='text/event-stream')
79
+
80
+ data = request.json
81
+ system_instruction = "تو چت بات هوش مصنوعی آلفا هستی و توسط برنامه هوش مصنوعی آلفا توسعه داده شدی. کمی با کاربران باحال و دوستانه صحبت کن و از ایموجی‌ها استفاده کن. همیشه پاسخ‌هایت را به زبان فارسی و یا هر زبانی که کاربر صحبت میکنه ارائه بده."
82
+
83
+ show_thoughts = data.get("show_thoughts", False)
84
+
85
+ # بخش پردازش پیام‌ها و فایل DOCX (بدون تغییر باقی می‌ماند)
86
+ gemini_messages = []
87
+ for msg in data.get("messages", []):
88
+ role = "model" if msg.get("role") == "assistant" else msg.get("role")
89
+
90
+ processed_parts = []
91
+ for part in msg.get("parts", []):
92
+ if part.get("text"):
93
+ processed_parts.append({"text": part["text"]})
94
+
95
+ if part.get("base64Data") and part.get("mimeType"):
96
+ mime_type = part["mimeType"]
97
+
98
+ if mime_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
99
+ try:
100
+ decoded_data = base64.b64decode(part["base64Data"])
101
+ file_stream = io.BytesIO(decoded_data)
102
+ document = docx.Document(file_stream)
103
+ full_text = "\n".join([para.text for para in document.paragraphs])
104
+
105
+ final_text_part = f"کاربر یک فایل Word آپلود کرد. محتوای متنی آن به شرح زیر است:\n\n---\n\n{full_text}\n\n---"
106
+ processed_parts.append({"text": final_text_part})
107
+ logging.info("فایل DOCX با موفقیت پردازش و متن آن استخراج شد.")
108
+
109
+ except Exception as e:
110
+ logging.error(f"خطا در پردازش فایل DOCX: {e}")
111
+ processed_parts.append({"text": "[خطا: امکان پردازش فایل Word وجو�� نداشت.]"})
112
+
113
+ else:
114
+ processed_parts.append({"inline_data": {"mime_type": part["mimeType"], "data": part["base64Data"]}})
115
+
116
+ if processed_parts:
117
+ if gemini_messages and gemini_messages[-1]["role"] == role:
118
+ gemini_messages[-1]["parts"].extend(processed_parts)
119
+ else:
120
+ gemini_messages.append({"role": role, "parts": processed_parts})
121
+
122
+ if not any(msg['role'] == 'user' for msg in gemini_messages):
123
+ return Response("data: [DONE]\n\n", mimetype='text/event-stream')
124
+
125
+ def stream_response():
126
+ last_error = None
127
+ for _ in range(len(GEMINI_API_KEYS)):
128
+ try:
129
+ api_key, key_index = get_next_key_with_index()
130
+ logging.info(f"تلاش برای ارسال درخواست با کلید شماره {key_index + 1}...")
131
+
132
+ api_endpoint = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL_NAME}:streamGenerateContent?key={api_key}&alt=sse"
133
+
134
+ payload = {
135
+ "contents": gemini_messages,
136
+ "systemInstruction": {"parts": [{"text": system_instruction}]},
137
+ "tools": [{"google_search": {}}],
138
+ "generationConfig": {
139
+ "temperature": 0.7,
140
+ }
141
+ }
142
+
143
+ if show_thoughts:
144
+ payload["generationConfig"]["thinking_config"] = {
145
+ "include_thoughts": True
146
+ }
147
+
148
+ # *** START: MODIFIED - استفاده از Timeout تفکیک شده ***
149
+ # timeout اول برای اتصال اولیه، timeout دوم برای فاصله بین دریافت داده‌ها
150
+ with requests.post(api_endpoint, json=payload, stream=True, timeout=(STREAM_START_TIMEOUT, STREAM_READ_TIMEOUT)) as response:
151
+ # *** END: MODIFIED ***
152
+ if response.status_code == 429:
153
+ logging.warning(f"کلید شماره {key_index + 1} سهمیه آن تمام شده است. در حال تلاش با کلید بعدی...")
154
+ last_error = "Rate limit exceeded"
155
+ continue
156
+
157
+ response.raise_for_status()
158
+ logging.info(f"اتصال با کلید شماره {key_index + 1} موفقیت‌آمیز بود. در حال استریم پاسخ...")
159
+
160
+ # بخش استریم پاسخ (بدون تغییر)
161
+ for line in response.iter_lines():
162
+ if line:
163
+ decoded_line = line.decode('utf-8')
164
+ if decoded_line.startswith('data: '):
165
+ try:
166
+ chunk_data = json.loads(decoded_line[6:])
167
+ parts = chunk_data.get("candidates", [{}])[0].get("content", {}).get("parts", [])
168
+
169
+ for part in parts:
170
+ if "text" not in part or not part["text"]:
171
+ continue
172
+ is_a_thought = part.get("thought") is True
173
+ if show_thoughts and is_a_thought:
174
+ thought_payload = {"type": "thought", "content": part["text"]}
175
+ yield f"data: {json.dumps(thought_payload)}\n\n"
176
+ elif not is_a_thought:
177
+ sse_payload = {"choices": [{"delta": {"content": part["text"]}}]}
178
+ yield f"data: {json.dumps(sse_payload)}\n\n"
179
+ except (json.JSONDecodeError, IndexError, KeyError):
180
+ continue
181
+
182
+ logging.info(f"استریم با کلید شماره {key_index + 1} به پایان رسید.")
183
+ return
184
+
185
+ # *** START: MODIFIED - افزودن ReadTimeout به مدیریت خطا ***
186
+ except (requests.exceptions.Timeout, requests.exceptions.ReadTimeout) as e:
187
+ if isinstance(e, requests.exceptions.ReadTimeout):
188
+ logging.warning(f"استریم با کلید شماره {key_index + 1} به دلیل عدم دریافت داده جدید متوقف شد (ReadTimeout). در حال تلاش با کلید بعدی...")
189
+ last_error = f"ReadTimeout after {STREAM_READ_TIMEOUT} seconds of inactivity"
190
+ else:
191
+ logging.warning(f"مهلت زمانی برای اتصال با کلید شماره {key_index + 1} به پایان رسید (ConnectTimeout). در حال تلاش با کلید بعدی...")
192
+ last_error = f"ConnectTimeout after {STREAM_START_TIMEOUT} seconds"
193
+ continue
194
+ # *** END: MODIFIED ***
195
+
196
+ except requests.exceptions.HTTPError as e:
197
+ if e.response.status_code == 403:
198
+ logging.warning(f"کلید شماره {key_index + 1} نامعتبر است (Permission Denied). در حال تلاش با کلید بعدی...")
199
+ last_error = e
200
+ continue
201
+ else:
202
+ logging.error(f"خطای HTTP پیش‌بینی نشده در حین تلاش با کلید {key_index + 1}: {e}")
203
+ last_error = e
204
+ break
205
+
206
+ except Exception as e:
207
+ logging.error(f"خطای پیش‌بینی نشده در حین تلاش با کلید {key_index + 1}: {e}")
208
+ last_error = e
209
+ break
210
+
211
+ error_message = f"متاسفانه تمام کلیدهای API موجود نامعتبر هستند یا سهمیه آنها به پایان رسیده است. لطفا بعدا تلاش کنید.\n\nآخرین خطا: {str(last_error)}"
212
+ logging.critical("هیچ‌کدام از کلیدهای Gemini کار نکردند.")
213
+ error_payload = {"type": "error", "message": error_message}
214
+ yield f"data: {json.dumps(error_payload)}\n\n"
215
+
216
+ return Response(stream_response(), mimetype='text/event-stream')
217
+
218
+ if __name__ == '__main__':
219
+ if GEMINI_API_KEYS:
220
+ logging.info(f"سیستم در حالت توسعه شروع به کار کرد. تعداد {len(GEMINI_API_KEYS)} کلید شناسایی شد.")
221
+ app.run(debug=True, host='0.0.0.0', port=os.environ.get("PORT", 7860))
222
+
223
+ # --- END OF FILE app.py ---
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- START OF FILE requirements.txt ---
2
+
3
+ Flask==2.2.2
4
+ Werkzeug==2.2.2
5
+ gunicorn==20.1.0
6
+ requests==2.28.1
7
+ gevent==22.10.2
8
+ sseclient-py==1.8.0
9
+ filelock==3.13.1
10
+ google-generativeai
11
+ python-docx==1.1.2 # *** کتابخانه جدید برای خواندن فایل‌های Word اضافه شد ***
12
+
13
+ # --- END OF FILE requirements.txt ---