Vedika commited on
Commit
3bcf648
·
verified ·
1 Parent(s): 1df870b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +492 -77
app.py CHANGED
@@ -1,39 +1,398 @@
1
  import os
 
 
 
 
 
2
  import asyncio
3
  import tempfile
 
 
4
  import requests
 
 
 
 
 
 
 
 
 
 
 
 
5
  import edge_tts
6
- from flask import Flask, request, Response, stream_with_context, render_template_string, send_file
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  app = Flask(__name__)
9
 
10
- # 🔐 100% Secure Secrets
11
- API_KEY = os.environ.get("YOUR_VEDIKA_API_KEY")
12
- BASE_URL = os.environ.get("BASE_URL")
13
- MODEL_ID = os.environ.get("MODEL_ID")
 
 
 
 
 
 
 
 
 
14
 
15
- INVOKE_URL = ""
16
- if BASE_URL:
17
- if not BASE_URL.endswith("/chat/completions"):
18
- INVOKE_URL = f"{BASE_URL.rstrip('/')}/chat/completions"
19
- else:
20
- INVOKE_URL = BASE_URL
21
 
22
- @app.route('/')
23
- def home():
 
 
24
  try:
25
- with open('index.html', 'r', encoding='utf-8') as f:
 
 
26
  html_content = f.read()
27
  return render_template_string(html_content)
 
 
 
28
  except Exception as e:
29
- return f"index.html file missing! Error: {str(e)}"
 
30
 
31
- @app.route('/api/chat', methods=['POST'])
32
- def chat():
33
- if not API_KEY or not INVOKE_URL or not MODEL_ID:
34
- return Response("Server Error: Configuration secrets are missing.", status=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- data = request.get_json() or {}
37
  user_message = data.get("message", "")
38
  attachments = data.get("attachments", [])
39
  system_prompt = data.get("system_prompt", "")
@@ -41,40 +400,17 @@ def chat():
41
  max_tokens = data.get("max_tokens", 4096)
42
  temperature = data.get("temperature", 0.7)
43
 
44
- messages = []
45
- if system_prompt.strip():
46
- messages.append({"role": "system", "content": system_prompt})
47
 
48
- for msg in history:
49
- role = msg.get("role", "user")
50
- content = msg.get("content", "")
51
- if content:
52
- messages.append({"role": role, "content": content})
53
-
54
- content_payload = []
55
- if user_message.strip():
56
- content_payload.append({"type": "text", "text": user_message})
57
-
58
- for att in attachments:
59
- att_type = att.get("type")
60
- b64_data = att.get("data")
61
- if att_type == "image":
62
- content_payload.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_data}"}})
63
- elif att_type in ["audio", "file"]:
64
- content_payload.append({"type": "input_audio", "input_audio": {"data": b64_data, "format": "wav"}})
65
-
66
- if not content_payload:
67
- content_payload.append({"type": "text", "text": "Hello"})
68
-
69
- messages.append({"role": "user", "content": content_payload})
70
-
71
- headers = {
72
- "Authorization": f"Bearer {API_KEY}",
73
- "Accept": "text/event-stream"
74
- }
75
 
76
- payload = {
77
- "model": MODEL_ID,
78
  "messages": messages,
79
  "max_tokens": int(max_tokens),
80
  "temperature": float(temperature),
@@ -82,39 +418,118 @@ def chat():
82
  "stream": True
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  try:
86
- response = requests.post(INVOKE_URL, headers=headers, json=payload, stream=True)
87
- def generate():
88
- for line in response.iter_lines():
89
- if line:
90
- decoded_line = line.decode("utf-8")
91
- if decoded_line.startswith("data: "):
92
- yield decoded_line + "\n\n"
93
- return Response(stream_with_context(generate()), mimetype='text/event-stream')
94
  except Exception as e:
95
- return Response("Internal Error: Unable to process request securely.", status=500)
96
-
97
- # 🎙️ PRABHAT NEURAL TTS ENDPOINT (भारी आवाज़ के लिए)
98
- @app.route('/api/tts', methods=['POST'])
99
- def tts():
100
- data = request.get_json() or {}
101
- text = data.get("text", "")
102
 
103
  if not text:
104
- return Response("No text provided", status=400)
 
105
 
106
- temp_path = tempfile.mktemp(suffix=".mp3")
 
 
 
107
 
108
- # ्रभात न्यू (India Male) - Pitch औRate डजस्िया गया है ताकि भाी और ्रोफशनलगे।
109
- async def generate_audio():
110
- communicate = edge_tts.Communicate(text, "en-IN-PrabhatNeural", pitch="-15%", rate="-5%")
111
- await communicate.save(temp_path)
 
 
112
 
 
 
 
 
 
113
  try:
114
- asyncio.run(generate_audio())
115
- return send_file(temp_path, mimetype="audio/mpeg", as_attachment=False)
 
 
 
 
 
116
  except Exception as e:
117
- return Response(f"TTS Error: {str(e)}", status=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  if __name__ == '__main__':
120
- app.run(host='0.0.0.0', port=7860)
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import sys
3
+ import json
4
+ import time
5
+ import uuid
6
+ import logging
7
  import asyncio
8
  import tempfile
9
+ import threading
10
+ import traceback
11
  import requests
12
+ from typing import Dict, Any, List, Optional, Generator
13
+
14
+ from flask import (
15
+ Flask,
16
+ request,
17
+ Response,
18
+ stream_with_context,
19
+ render_template_string,
20
+ send_file,
21
+ jsonify,
22
+ make_response
23
+ )
24
  import edge_tts
 
25
 
26
+ # =========================================================================================
27
+ # SECTION 1: ADVANCED LOGGING CONFIGURATION
28
+ # =========================================================================================
29
+ # सिस्टम की हर गतिविधि को ट्रैक करने के लिए कस्टम लॉगर।
30
+ class CustomFormatter(logging.Formatter):
31
+ """लॉगिंग आउटपुट को सुंदर और स्पष्ट बनाने के लिए कस्टम फॉर्मेटर।"""
32
+ grey = "\x1b[38;20m"
33
+ yellow = "\x1b[33;20m"
34
+ red = "\x1b[31;20m"
35
+ bold_red = "\x1b[31;1m"
36
+ green = "\x1b[32;20m"
37
+ reset = "\x1b[0m"
38
+ format_str = "%(asctime)s - ARJUN-CORE - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
39
+
40
+ FORMATS = {
41
+ logging.DEBUG: grey + format_str + reset,
42
+ logging.INFO: green + format_str + reset,
43
+ logging.WARNING: yellow + format_str + reset,
44
+ logging.ERROR: red + format_str + reset,
45
+ logging.CRITICAL: bold_red + format_str + reset
46
+ }
47
+
48
+ def format(self, record):
49
+ log_fmt = self.FORMATS.get(record.levelno)
50
+ formatter = logging.Formatter(log_fmt, datefmt='%Y-%m-%d %H:%M:%S')
51
+ return formatter.format(record)
52
+
53
+ logger = logging.getLogger("ArjunBackend")
54
+ logger.setLevel(logging.DEBUG)
55
+ ch = logging.StreamHandler()
56
+ ch.setFormatter(CustomFormatter())
57
+ logger.addHandler(ch)
58
+
59
+ # =========================================================================================
60
+ # SECTION 2: CUSTOM EXCEPTIONS
61
+ # =========================================================================================
62
+ # त्रुटियों को बारीकी से पकड़ने के लिए कस्टम एक्सेप्शन क्लासेस।
63
+ class ArjunBaseException(Exception):
64
+ """अर्जुन सिस्टम के लिए बेस एक्सेप्शन क्लास।"""
65
+ def __init__(self, message: str, status_code: int = 500):
66
+ super().__init__(message)
67
+ self.message = message
68
+ self.status_code = status_code
69
+
70
+ class ConfigurationError(ArjunBaseException):
71
+ """जब पर्यावरण चर (Environment Variables) गायब हों।"""
72
+ pass
73
+
74
+ class LLMAPIConnectionError(ArjunBaseException):
75
+ """जब भाषा मॉडल API से संपर्क टूट जाए।"""
76
+ pass
77
+
78
+ class TTSGenerationError(ArjunBaseException):
79
+ """जब प्रभात न्यूरल आवाज़ जनरेट करने में विफल हो।"""
80
+ pass
81
+
82
+ # =========================================================================================
83
+ # SECTION 3: SYSTEM CONFIGURATION MANAGER
84
+ # =========================================================================================
85
+ class ConfigManager:
86
+ """
87
+ यह क्लास सिस्टम की सभी सेटिंग्स और सीक्रेट्स (Secrets) को मैनेज करती है।
88
+ यह सुनिश्चित करती है कि कोई भी डेटा हार्डकोडेड (Hardcoded) न हो।
89
+ """
90
+ def __init__(self):
91
+ logger.info("Initializing Configuration Manager...")
92
+ self.api_key = os.environ.get("YOUR_VEDIKA_API_KEY")
93
+ self.base_url = os.environ.get("BASE_URL")
94
+ self.model_id = os.environ.get("MODEL_ID")
95
+
96
+ self.invoke_url = self._format_base_url(self.base_url)
97
+ self.validate_config()
98
+
99
+ def _format_base_url(self, url: Optional[str]) -> str:
100
+ """API URL को सही प्रारूप में बदलता है।"""
101
+ if not url:
102
+ return ""
103
+ url = url.strip()
104
+ if not url.endswith("/chat/completions"):
105
+ return f"{url.rstrip('/')}/chat/completions"
106
+ return url
107
+
108
+ def validate_config(self):
109
+ """जाँच करता है कि सभी ज़रूरी सीक्रेट्स मौजूद हैं या नहीं।"""
110
+ missing = []
111
+ if not self.api_key: missing.append("YOUR_VEDIKA_API_KEY")
112
+ if not self.base_url: missing.append("BASE_URL")
113
+ if not self.model_id: missing.append("MODEL_ID")
114
+
115
+ if missing:
116
+ error_msg = f"Critical Configuration Missing: {', '.join(missing)}"
117
+ logger.error(error_msg)
118
+ # हम सर्वर क्रैश नहीं करेंगे, बल्कि रिक्वेस्ट आने पर एरर देंगे।
119
+ else:
120
+ logger.info("All system configurations validated successfully.")
121
+
122
+ # ग्लोबल कॉन्फिग ऑब्जेक्ट
123
+ config = ConfigManager()
124
+
125
+ # =========================================================================================
126
+ # SECTION 4: TEMPORARY FILE MANAGER (GARBAGE COLLECTION)
127
+ # =========================================================================================
128
+ class TempFileManager:
129
+ """
130
+ यह क्लास सुनिश्चित करती है कि जनरेट की गई ऑडियो (MP3) फाइल्स
131
+ सिस्टम की मेमोरी न भरें। यह उन्हें सुरक्षित रूप से डिलीट करती है।
132
+ """
133
+ def __init__(self):
134
+ self.temp_dir = tempfile.gettempdir()
135
+ self.active_files = set()
136
+ logger.info(f"Temporary File Manager initialized at {self.temp_dir}")
137
+
138
+ def create_temp_audio_path(self) -> str:
139
+ """एक यूनीक सुरक्षित फाइल पाथ जनरेट करता है।"""
140
+ unique_id = uuid.uuid4().hex
141
+ file_path = os.path.join(self.temp_dir, f"arjun_tts_{unique_id}.mp3")
142
+ self.active_files.add(file_path)
143
+ return file_path
144
+
145
+ def schedule_cleanup(self, file_path: str, delay_seconds: int = 60):
146
+ """कुछ समय बाद फाइल को डिलीट करने के लिए बैकग्राउंड थ्रेड चलाता है।"""
147
+ def _cleanup():
148
+ time.sleep(delay_seconds)
149
+ try:
150
+ if os.path.exists(file_path):
151
+ os.remove(file_path)
152
+ logger.debug(f"Garbage Collection: Deleted {file_path}")
153
+ if file_path in self.active_files:
154
+ self.active_files.remove(file_path)
155
+ except Exception as e:
156
+ logger.error(f"Cleanup failed for {file_path}: {str(e)}")
157
+
158
+ cleanup_thread = threading.Thread(target=_cleanup, daemon=True)
159
+ cleanup_thread.start()
160
+
161
+ file_manager = TempFileManager()
162
+
163
+ # =========================================================================================
164
+ # SECTION 5: ADVANCED TTS ENGINE (PRABHAT NEURAL HANDLER)
165
+ # =========================================================================================
166
+ class TTSEngine:
167
+ """
168
+ टेक्स्ट-टू-स्पीच (TTS) को हैंडल करने के लिए विशेष क्लास।
169
+ यह Flask के सिंक्रोनस थ्रेड्स और Asyncio के बीच के टकराव को दूर करती है।
170
+ """
171
+ def __init__(self):
172
+ # प्रभात न्यूरल (India Male) की सेटिंग्स
173
+ # Pitch: -2% (प्राकृतिक भारीपन), Rate: +0% (सामान्य गति)
174
+ self.voice = "en-IN-PrabhatNeural"
175
+ self.pitch = "-2%"
176
+ self.rate = "+0%"
177
+ logger.info(f"TTS Engine initialized with Voice: {self.voice}")
178
+
179
+ def generate_audio_sync(self, text: str, output_path: str) -> bool:
180
+ """
181
+ यह फंक्शन asyncio लूप को एक सुरक्षित आइसोलेटेड (Isolated) तरीके से चलाता है
182
+ ताकि Flask सर्वर कभी हैंग न हो। यह यूज़र की सबसे बड़ी समस्या का समाधान है।
183
+ """
184
+ # टेक्स्ट की सफाई (Markdown हटाना)
185
+ clean_text = self._clean_text_for_speech(text)
186
+ if not clean_text:
187
+ logger.warning("TTS Engine received empty text after cleaning.")
188
+ return False
189
+
190
+ logger.info(f"Starting TTS Generation for text length: {len(clean_text)}")
191
+
192
+ # Async फंक्शन जो असल में edge-tts को कॉल करेगा
193
+ async def _async_generate():
194
+ try:
195
+ communicate = edge_tts.Communicate(
196
+ text=clean_text,
197
+ voice=self.voice,
198
+ pitch=self.pitch,
199
+ rate=self.rate
200
+ )
201
+ await communicate.save(output_path)
202
+ return True
203
+ except Exception as e:
204
+ logger.error(f"Edge-TTS Core Error: {str(e)}")
205
+ traceback.print_exc()
206
+ return False
207
+
208
+ # नया इवेंट लूप बनाकर चलाना (Thread-safe)
209
+ loop = asyncio.new_event_loop()
210
+ asyncio.set_event_loop(loop)
211
+ try:
212
+ success = loop.run_until_complete(_async_generate())
213
+ return success
214
+ except Exception as e:
215
+ logger.error(f"Event Loop Error during TTS: {str(e)}")
216
+ return False
217
+ finally:
218
+ loop.close()
219
+
220
+ def _clean_text_for_speech(self, text: str) -> str:
221
+ """AI के आउटपुट से अनचाहे चिह्न हटाता है ताकि आवाज़ न फटे।"""
222
+ import re
223
+ # Markdown इमेजेज हटाना
224
+ text = re.sub(r'!\[.*?\]\(.*?\)', '', text)
225
+ # Markdown लिंक्स हटाना
226
+ text = re.sub(r'\[.*?\]\(.*?\)', '', text)
227
+ # <think> टैग्स हटाना
228
+ text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
229
+ # बोल्ड/इटैलिक चिह्न हटाना
230
+ text = text.replace('*', '').replace('_', '').replace('`', '')
231
+ return text.strip()
232
+
233
+ tts_engine = TTSEngine()
234
+
235
+ # =========================================================================================
236
+ # SECTION 6: LLM API STREAMING HANDLER
237
+ # =========================================================================================
238
+ class LLMAPIHandler:
239
+ """
240
+ यह क्लास यूज़र के मैसेज और फाइल्स को प्रोसेस करके
241
+ भाषा मॉडल (LLM) को भेजती है और स्ट्रीमिंग रिस्पॉन्स को वापस लाती है।
242
+ """
243
+ def __init__(self, cfg: ConfigManager):
244
+ self.cfg = cfg
245
+
246
+ def construct_messages(self, user_msg: str, attachments: List[Dict], sys_prompt: str, history: List[Dict]) -> List[Dict]:
247
+ """पेलोड (Payload) का निर्माण करता है।"""
248
+ messages = []
249
+
250
+ # 1. System Prompt
251
+ if sys_prompt and sys_prompt.strip():
252
+ messages.append({"role": "system", "content": sys_prompt.strip()})
253
+
254
+ # 2. History (पुराने मैसेज)
255
+ for h_msg in history:
256
+ role = h_msg.get("role", "user")
257
+ content = h_msg.get("content", "")
258
+ if content:
259
+ messages.append({"role": role, "content": content})
260
+
261
+ # 3. Current User Message & Attachments
262
+ content_payload = []
263
+ if user_msg and user_msg.strip():
264
+ content_payload.append({"type": "text", "text": user_message.strip()})
265
+
266
+ for att in attachments:
267
+ att_type = att.get("type")
268
+ b64_data = att.get("data")
269
+
270
+ if not att_type or not b64_data:
271
+ continue
272
+
273
+ if att_type == "image":
274
+ content_payload.append({
275
+ "type": "image_url",
276
+ "image_url": {"url": f"data:image/jpeg;base64,{b64_data}"}
277
+ })
278
+ elif att_type in ["audio", "file", "video"]:
279
+ # वीडियो को भी ऑडियो/फाइल की तरह ट्रीट किया जाएगा अगर मॉडल सपोर्ट करता है
280
+ content_payload.append({
281
+ "type": "input_audio",
282
+ "input_audio": {"data": b64_data, "format": "wav"}
283
+ })
284
+
285
+ # Fallback if empty
286
+ if not content_payload:
287
+ content_payload.append({"type": "text", "text": "Hello"})
288
+
289
+ messages.append({"role": "user", "content": content_payload})
290
+ return messages
291
+
292
+ def stream_response(self, payload: Dict[str, Any]) -> Generator[str, None, None]:
293
+ """API से डेटा स्ट्रीम करता है और सुरक्षित रूप से यील्ड (Yield) करता है।"""
294
+ headers = {
295
+ "Authorization": f"Bearer {self.cfg.api_key}",
296
+ "Accept": "text/event-stream",
297
+ "Content-Type": "application/json"
298
+ }
299
+
300
+ logger.info(f"Initiating streaming request to {self.cfg.invoke_url}...")
301
+
302
+ try:
303
+ with requests.post(
304
+ self.cfg.invoke_url,
305
+ headers=headers,
306
+ json=payload,
307
+ stream=True,
308
+ timeout=120 # 2 minute timeout for safety
309
+ ) as response:
310
+
311
+ # Check for HTTP errors
312
+ if response.status_code != 200:
313
+ error_text = response.text
314
+ logger.error(f"API HTTP Error {response.status_code}: {error_text}")
315
+ yield f"data: {json.dumps({'choices': [{'delta': {'content': f'**API Error:** {response.status_code}'}}]})}\n\n"
316
+ yield "data: [DONE]\n\n"
317
+ return
318
+
319
+ for line in response.iter_lines():
320
+ if line:
321
+ decoded_line = line.decode("utf-8")
322
+ if decoded_line.startswith("data: "):
323
+ yield decoded_line + "\n\n"
324
+
325
+ except requests.exceptions.Timeout:
326
+ logger.error("API Request Timed Out.")
327
+ yield f"data: {json.dumps({'choices': [{'delta': {'content': '**Error:** Request timed out.'}}]})}\n\n"
328
+ except requests.exceptions.RequestException as e:
329
+ logger.error(f"API Network Error: {str(e)}")
330
+ yield f"data: {json.dumps({'choices': [{'delta': {'content': '**Error:** Network connection failed.'}}]})}\n\n"
331
+ except Exception as e:
332
+ logger.error(f"Unknown Streaming Error: {str(e)}")
333
+ traceback.print_exc()
334
+ yield f"data: {json.dumps({'choices': [{'delta': {'content': '**Error:** Internal server error during stream.'}}]})}\n\n"
335
+
336
+ llm_handler = LLMAPIHandler(config)
337
+
338
+ # =========================================================================================
339
+ # SECTION 7: FLASK APPLICATION SETUP
340
+ # =========================================================================================
341
  app = Flask(__name__)
342
 
343
+ # Request Logging Middleware
344
+ @app.before_request
345
+ def log_request_info():
346
+ """हर रिक्वेस्ट की जानकारी लॉग करता है।"""
347
+ logger.debug(f"Incoming Request: {request.method} {request.path}")
348
+
349
+ @app.after_request
350
+ def add_cors_headers(response):
351
+ """CORS और सिक्योरिटी हेडर्स जोड़ता है।"""
352
+ response.headers["Access-Control-Allow-Origin"] = "*"
353
+ response.headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
354
+ response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
355
+ return response
356
 
357
+ # =========================================================================================
358
+ # SECTION 8: API ROUTES & ENDPOINTS
359
+ # =========================================================================================
 
 
 
360
 
361
+ @app.route('/', methods=['GET'])
362
+ def serve_frontend():
363
+ """मुख्य HTML फाइल (index.html) सर्व करता है।"""
364
+ logger.info("Serving index.html to client.")
365
  try:
366
+ # सुनिश्चित करें कि index.html उसी डायरेक्टरी में है
367
+ file_path = os.path.join(os.path.dirname(__file__), 'index.html')
368
+ with open(file_path, 'r', encoding='utf-8') as f:
369
  html_content = f.read()
370
  return render_template_string(html_content)
371
+ except FileNotFoundError:
372
+ logger.error("index.html file not found on server.")
373
+ return make_response("<h1>Error: index.html missing!</h1><p>Please place index.html in the same directory as app.py</p>", 404)
374
  except Exception as e:
375
+ logger.error(f"Error reading index.html: {str(e)}")
376
+ return make_response(f"<h1>Internal Error</h1><p>{str(e)}</p>", 500)
377
 
378
+ @app.route('/api/chat', methods=['POST', 'OPTIONS'])
379
+ def chat_endpoint():
380
+ """LLM चैट प्रोसेसिंग के लिए मुख्य एंडपॉइंट।"""
381
+ if request.method == 'OPTIONS':
382
+ return Response(status=200)
383
+
384
+ # 1. Validate Config
385
+ if not config.api_key or not config.invoke_url or not config.model_id:
386
+ logger.critical("Chat request denied. Missing Configuration Secrets.")
387
+ return jsonify({"error": "Server is improperly configured. Check Secrets."}), 500
388
+
389
+ # 2. Parse Request
390
+ try:
391
+ data = request.get_json() or {}
392
+ except Exception as e:
393
+ logger.error(f"Invalid JSON payload: {str(e)}")
394
+ return jsonify({"error": "Invalid JSON format."}), 400
395
 
 
396
  user_message = data.get("message", "")
397
  attachments = data.get("attachments", [])
398
  system_prompt = data.get("system_prompt", "")
 
400
  max_tokens = data.get("max_tokens", 4096)
401
  temperature = data.get("temperature", 0.7)
402
 
403
+ logger.info(f"Received chat request. Msg length: {len(user_message)}, Attachments: {len(attachments)}")
 
 
404
 
405
+ # 3. Construct Payload
406
+ try:
407
+ messages = llm_handler.construct_messages(user_message, attachments, system_prompt, history)
408
+ except Exception as e:
409
+ logger.error(f"Payload construction failed: {str(e)}")
410
+ return jsonify({"error": "Failed to construct message payload."}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
+ api_payload = {
413
+ "model": config.model_id,
414
  "messages": messages,
415
  "max_tokens": int(max_tokens),
416
  "temperature": float(temperature),
 
418
  "stream": True
419
  }
420
 
421
+ # 4. Stream Response
422
+ return Response(
423
+ stream_with_context(llm_handler.stream_response(api_payload)),
424
+ mimetype='text/event-stream'
425
+ )
426
+
427
+
428
+ @app.route('/api/tts', methods=['POST', 'OPTIONS'])
429
+ def tts_endpoint():
430
+ """
431
+ टेक्स्ट-टू-स्पीच (Prabhat Neural) जनरेशन एंडपॉइंट।
432
+ यह ऑडियो फाइल बनाता है, उसे सर्व करता है, और फिर डिलीट करने का शेड्यूल बनाता है।
433
+ """
434
+ if request.method == 'OPTIONS':
435
+ return Response(status=200)
436
+
437
  try:
438
+ data = request.get_json() or {}
439
+ text = data.get("text", "").strip()
 
 
 
 
 
 
440
  except Exception as e:
441
+ logger.error(f"Invalid JSON in TTS request: {str(e)}")
442
+ return jsonify({"error": "Invalid JSON payload"}), 400
 
 
 
 
 
443
 
444
  if not text:
445
+ logger.warning("TTS Request received empty text.")
446
+ return jsonify({"error": "No text provided"}), 400
447
 
448
+ logger.info(f"Processing TTS request. Text length: {len(text)}")
449
+
450
+ # एक सुरक्षित टेम्पररी फाइल पाथ प्राप्त करें
451
+ output_audio_path = file_manager.create_temp_audio_path()
452
 
453
+ # सिंक्रस थ्रेड के अंदर एसिंकरोनस TTS सुर्षिाएं
454
+ success = tts_engine.generate_audio_sync(text, output_audio_path)
455
+
456
+ if not success or not os.path.exists(output_audio_path):
457
+ logger.error("TTS Audio file was not created successfully.")
458
+ return jsonify({"error": "TTS Engine failed to generate audio."}), 500
459
 
460
+ logger.info(f"TTS Audio generated successfully at: {output_audio_path}")
461
+
462
+ # फाइल को क्लाइंट को भेजने के बाद 60 सेकंड में डिलीट करने का शेड्यूल
463
+ file_manager.schedule_cleanup(output_audio_path, delay_seconds=60)
464
+
465
  try:
466
+ # फाइल को बाइट्स के रूप में भेजें (Streaming)
467
+ return send_file(
468
+ output_audio_path,
469
+ mimetype="audio/mpeg",
470
+ as_attachment=False,
471
+ download_name="arjun_voice.mp3"
472
+ )
473
  except Exception as e:
474
+ logger.error(f"Failed to send TTS file: {str(e)}")
475
+ return jsonify({"error": "Failed to transmit audio file."}), 500
476
+
477
+
478
+ @app.route('/health', methods=['GET'])
479
+ def health_check():
480
+ """सर्वर की स्थिति जाँचने के लिए एंडपॉइंट।"""
481
+ status = {
482
+ "status": "healthy",
483
+ "arjun_core": "online",
484
+ "tts_engine": "initialized",
485
+ "timestamp": time.time()
486
+ }
487
+ return jsonify(status), 200
488
+
489
+ # =========================================================================================
490
+ # SECTION 9: ERROR HANDLERS
491
+ # =========================================================================================
492
+ @app.errorhandler(404)
493
+ def not_found(error):
494
+ logger.warning(f"404 Not Found: {request.path}")
495
+ return jsonify({"error": "Resource not found"}), 404
496
+
497
+ @app.errorhandler(500)
498
+ def internal_error(error):
499
+ logger.error(f"500 Internal Server Error: {request.path}")
500
+ return jsonify({"error": "Internal Server Error"}), 500
501
+
502
+ # =========================================================================================
503
+ # MAIN EXECUTION BLOCK
504
+ # =========================================================================================
505
+ def print_startup_banner():
506
+ """सर्वर शुरू होने पर एक सुंदर बैनर प्रिंट करता है।"""
507
+ banner = """
508
+ ========================================================
509
+ █████╗ ██████╗ ██╗██╗ ██╗███╗ ██╗
510
+ ██╔══██╗██╔══██╗ ██║██║ ██║████╗ ██║
511
+ ███████║██████╔╝ ██║██║ ██║██╔██╗ ██║
512
+ ██╔══██║██╔══██╗██ ██║██║ ██║██║╚██╗██║
513
+ ██║ ██║██║ ██║╚█████╔╝╚██████╔╝██║ ╚████║
514
+ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═╝ ╚═══╝
515
+ ADVANCED EDITION (V2.0)
516
+ ========================================================
517
+ System : Arjun Intelligence Core
518
+ TTS Mode : Microsoft Edge (Prabhat Neural)
519
+ Status : Online and Listening...
520
+ ========================================================
521
+ """
522
+ print("\x1b[32;20m" + banner + "\x1b[0m")
523
 
524
  if __name__ == '__main__':
525
+ # बैनर दिखाएं
526
+ print_startup_banner()
527
+
528
+ # पोर्ट सेट करें
529
+ port = int(os.environ.get("PORT", 7860))
530
+
531
+ logger.info(f"Starting Arjun Backend Server on port {port}...")
532
+
533
+ # प्रोडक्शन डिप्लॉयमेंट के लिए, Waitress या Gunicorn का इस्तेमाल बेहतर है,
534
+ # लेकिन स्थानीय/डेवलपमेंट के लिए Flask का रन सर्वर (Threaded) पर्याप्त है।
535
+ app.run(host='0.0.0.0', port=port, threaded=True)