Nonabzbssbbsbs commited on
Commit
e35ec28
·
verified ·
1 Parent(s): e64bc47

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +634 -0
app.py ADDED
@@ -0,0 +1,634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import gradio as gr
4
+ import pandas as pd
5
+ import requests
6
+ import datetime
7
+ import time
8
+ import base64
9
+ import json
10
+ import logging
11
+ import tempfile
12
+ from typing import List, Dict, Any
13
+ import sys
14
+
15
+ # Set logging level
16
+ logging.basicConfig(level=logging.INFO)
17
+
18
+ # -----------------------------------------------------------------
19
+ # 0. OPEN SOURCE LLM SETUP (Llama 3 8B Reserve)
20
+ # -----------------------------------------------------------------
21
+ LLM_AVAILABLE = False
22
+ LLM_STATUS = "❌ Llama 3 (Сөндірулі)"
23
+ LLM_MODEL = None
24
+ LLM_TOKENIZER = None
25
+
26
+ try:
27
+ # Requires: torch, transformers, accelerate, bitsandbytes
28
+ from transformers import AutoModelForCausalLM, AutoTokenizer
29
+ import torch
30
+
31
+ # Initialization check (This step can be slow and memory intensive)
32
+ LLM_MODEL_NAME = "meta-llama/Meta-Llama-3-8B-Instruct"
33
+
34
+ LLM_TOKENIZER = AutoTokenizer.from_pretrained(LLM_MODEL_NAME)
35
+
36
+ # Load model using 8-bit quantization to save RAM (Crucial for 16GB RAM)
37
+ LLM_MODEL = AutoModelForCausalLM.from_pretrained(
38
+ LLM_MODEL_NAME,
39
+ device_map="auto",
40
+ torch_dtype=torch.bfloat16,
41
+ load_in_8bit=True # Important for memory efficiency on CPU/low-VRAM GPU
42
+ )
43
+ LLM_AVAILABLE = True
44
+ LLM_STATUS = "✅ Llama 3 8B (Резерв)"
45
+ except ImportError:
46
+ LLM_STATUS = "❌ Llama 3 (Пакет жоқ)"
47
+ except Exception as e:
48
+ LLM_STATUS = f"❌ Llama 3 (Қате: {e.__class__.__name__})"
49
+
50
+ # -----------------------------------------------------------------
51
+ # 1. API CLIENT INITIALIZATION (Standardized)
52
+ # -----------------------------------------------------------------
53
+ GEMINI_CLIENT = None
54
+ API_STATUSES = {'OpenLLM': LLM_STATUS}
55
+
56
+ def initialize_api(name, env_key, client_class=None):
57
+ key = os.environ.get(env_key)
58
+ status = "❌"
59
+ client = None
60
+
61
+ # 1. Key Presence Check
62
+ if not key or "YOUR_" in key.upper() or len(key) < 10:
63
+ status = "⚠️ (Кілт Жоқ/Жарамсыз)"
64
+ return None, status
65
+
66
+ try:
67
+ # 2. Client Initialization Check
68
+ if client_class:
69
+ from google import genai
70
+ client = genai.Client(api_key=key)
71
+
72
+ status = "✅"
73
+ except Exception as e:
74
+ status = f"❌ (Қате: {e.__class__.__name__})"
75
+
76
+ return client, status
77
+
78
+ # Initialize Gemini Client (Primary AI)
79
+ try:
80
+ from google import genai
81
+ GEMINI_CLIENT, status = initialize_api('Gemini', 'GEMINI_API_KEY', genai.Client)
82
+ API_STATUSES['Gemini'] = status
83
+ except Exception:
84
+ API_STATUSES['Gemini'] = "❌ (genai пакеті жоқ)"
85
+
86
+ # Check and store status for other APIs
87
+ _, API_STATUSES['OpenAI Mod'] = initialize_api('OpenAI Mod', 'OPENAI_API_KEY')
88
+ _, API_STATUSES['News'] = initialize_api('News', 'NEWS_API_KEY')
89
+ _, API_STATUSES['Weather'] = initialize_api('Weather', 'OPENWEATHER_API_KEY')
90
+ _, API_STATUSES['Stability (SDXL)'] = initialize_api('Stability (SDXL)', 'STABILITY_API_KEY')
91
+
92
+ NEWS_API_KEY = os.environ.get('NEWS_API_KEY')
93
+ OPENWEATHER_API_KEY = os.environ.get('OPENWEATHER_API_KEY')
94
+ OPENAI_KEY = os.environ.get('OPENAI_API_KEY')
95
+ STABILITY_API_KEY = os.environ.get('STABILITY_API_KEY')
96
+
97
+ API_STATUS_NOTE = " | ".join([f"{k} {v}" for k, v in API_STATUSES.items()])
98
+ print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] API Status: {API_STATUS_NOTE}")
99
+
100
+ LAST_AI_RESPONSE = ""
101
+ # -----------------------------------------------------------------
102
+ # 2. CORE FUNCTIONS
103
+ # -----------------------------------------------------------------
104
+
105
+ # 190-жолдағы қатенің шығуын болдырмау үшін 'global' бірінші тұр.
106
+ def text_to_speech_html(text):
107
+ global LAST_AI_RESPONSE # <--- МІНДЕТТІ ТҮРДЕ БІРІНШІ ОПЕРАТОР
108
+ LAST_AI_RESPONSE = text
109
+ return f"""<div style="color: #ffd700; font-weight: bold;">🗣️ Ауызша оқуға дайын. Түймені басыңыз.</div>"""
110
+
111
+ def play_last_response():
112
+ global LAST_AI_RESPONSE # <--- МІНДЕТТІ ТҮРДЕ БІРІНШІ ОПЕРАТОР
113
+ if not LAST_AI_RESPONSE:
114
+ return f"""<div style="color: red; font-weight: bold;">❌ Оқылатын мәтін жоқ.</div>"""
115
+
116
+ clean_text = LAST_AI_RESPONSE[:500].replace('"', "'").replace('\n', ' ')
117
+
118
+ js_script = f"""
119
+ <script>
120
+ function speakText(text, lang) {{
121
+ if ('speechSynthesis' in window) {{
122
+ window.speechSynthesis.cancel();
123
+ var utterance = new SpeechSynthesisUtterance(text);
124
+ utterance.lang = 'kk';
125
+ utterance.pitch = 1;
126
+ utterance.rate = 1.05;
127
+ setTimeout(() => {{
128
+ window.speechSynthesis.speak(utterance);
129
+ }}, 100);
130
+ }}
131
+ }}
132
+ speakText("{clean_text}", "kk");
133
+ </script>
134
+ """
135
+ return f"""<div style="color: #00ff00; font-weight: bold;">🎧 Оқылуда...</div>{js_script}"""
136
+
137
+
138
+ # 2.1. PRIMARY CHAT FUNCTION (Gemini OR Llama 3 Fallback)
139
+ def text_chat_gemini_or_llama(message: str, chat_history: List[List[str]]):
140
+ if not message: return ""
141
+
142
+ # 1. --- Try Gemini (Primary) ---
143
+ if GEMINI_CLIENT and API_STATUSES.get('Gemini') == "✅":
144
+ try:
145
+ contents = []
146
+ for user_msg, model_msg in chat_history:
147
+ if user_msg:
148
+ contents.append({"role": "user", "parts": [{"text": user_msg}]})
149
+ if model_msg:
150
+ contents.append({"role": "model", "parts": [{"text": model_msg}]})
151
+ contents.append({"role": "user", "parts": [{"text": message}]})
152
+
153
+ system_instruction = "Сіз 'SQG Quantum Leap AI' атты интеллект. Жауапты тек қазақ тілінде, достық тонмен беріңіз. Сіз Баян-Өлгий аймағына бағытталған ақпарат орталығысыз."
154
+
155
+ response = GEMINI_CLIENT.models.generate_content(
156
+ model='gemini-2.5-flash', contents=contents, system_instruction=system_instruction
157
+ )
158
+ ai_text = response.text.strip()
159
+ global LAST_AI_RESPONSE
160
+ LAST_AI_RESPONSE = ai_text
161
+ return ai_text
162
+ except Exception as e:
163
+ logging.error(f"Gemini API Error: {e}")
164
+ # Fall through to Llama 3 if Gemini fails
165
+ pass
166
+
167
+ # 2. --- Fallback to Llama 3 (Backup) ---
168
+ if LLM_AVAILABLE:
169
+ try:
170
+ system_prompt = "Сіз 'SQG Quantum Leap AI' атты интеллект. Жауапты тек қазақ тілінде, достық тонмен беріңіз."
171
+ messages = [{"role": "system", "content": system_prompt}]
172
+
173
+ for user_msg, model_msg in chat_history:
174
+ if user_msg:
175
+ messages.append({"role": "user", "content": user_msg})
176
+ if model_msg:
177
+ messages.append({"role": "assistant", "content": model_msg})
178
+ messages.append({"role": "user", "content": message})
179
+
180
+ prompt = LLM_TOKENIZER.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
181
+
182
+ inputs = LLM_TOKENIZER(prompt, return_tensors="pt").to(LLM_MODEL.device)
183
+
184
+ outputs = LLM_MODEL.generate(
185
+ **inputs, max_new_tokens=256, temperature=0.7, do_sample=True
186
+ )
187
+
188
+ ai_text = LLM_TOKENIZER.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True).strip()
189
+
190
+ global LAST_AI_RESPONSE
191
+ LAST_AI_RESPONSE = ai_text
192
+ return ai_text + "\n\n(🌐 Llama 3 8B арқылы жауап берілді - Резерв)"
193
+
194
+ except Exception as e:
195
+ logging.error(f"Llama 3 LLM Error: {e}")
196
+ return f"❌ Gemini API Қатесі және Llama 3 Резерві де істен шықты: {e.__class__.__name__}. Ресурстарды тексеріңіз."
197
+
198
+ # 3. --- Total Failure ---
199
+ return "❌ API Қатесі. Gemini API кілтін тексеріңіз немесе Llama 3 резерві іске қосылмаған."
200
+
201
+
202
+ # 2.2. Weather Prediction (Only uses Gemini for prediction, will show basic weather if Gemini fails)
203
+ def get_weather_and_prediction():
204
+ lat, lon = 48.97, 89.97
205
+ output = "OpenWeatherMap Қатесі ❌."
206
+ prediction_output = "Gemini болжамы жоқ. (LLM резерві бұл функцияға қолдау көрсетпейді)"
207
+
208
+ if not OPENWEATHER_API_KEY or API_STATUSES.get('Weather') != "✅":
209
+ return "OpenWeatherMap Key Қате ❌", prediction_output
210
+
211
+ forecast_url = f"https://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={OPENWEATHER_API_KEY}&units=metric&lang=kz"
212
+
213
+ try:
214
+ response = requests.get(forecast_url, timeout=5)
215
+ response.raise_for_status()
216
+ forecast_data = response.json()
217
+
218
+ current_data = forecast_data['list'][0]
219
+ temp = current_data['main']['temp']
220
+ wind_speed = current_data['wind']['speed']
221
+ desc = current_data['weather'][0]['description'].capitalize()
222
+
223
+ output = f"⛈️ Өлгий (Ағымдағы)\n\n* Температура: **{temp} °C**\n* Жағдайы: **{desc}**\n* Жел: **{wind_speed} м/с**"
224
+
225
+ prediction_points = [forecast_data['list'][i] for i in range(0, min(len(forecast_data['list']), 40), 8)]
226
+
227
+ if GEMINI_CLIENT and API_STATUSES.get('Gemini') == "✅":
228
+ weather_text = json.dumps(prediction_points)
229
+ prompt = f"Сіз ауа райын талдаушысыз. Келесі JSON деректері Өлгий қаласының алдағы 5 күндегі ауа райы болжамдарын көрсетеді. Толығымен қазақ тілінде, қарапайым 5 күндік шолу жасап беріңіз. JSON деректері: {weather_text}"
230
+
231
+ gemini_response = GEMINI_CLIENT.models.generate_content(
232
+ model='gemini-2.5-flash', contents=prompt
233
+ )
234
+ prediction_output = gemini_response.text.strip()
235
+
236
+ return output, prediction_output
237
+
238
+ except Exception as e:
239
+ return f"Белгісіз Қате: {e.__class__.__name__}", prediction_output
240
+
241
+ # 2.3. Image Generation (API Dependent - No LLM reserve for this)
242
+ def generate_image_sdxl(prompt):
243
+ if not STABILITY_API_KEY or API_STATUSES.get('Stability (SDXL)') != "✅":
244
+ return None, "Stability Key Қате ❌. Немесе API-ға қолжетімділік жоқ."
245
+
246
+ if not prompt:
247
+ return None, "Мәтінді енгізіңіз."
248
+
249
+ url = "https://api.stability.ai/v2beta/stable-image/generate/sd3"
250
+
251
+ headers = { "authorization": f"Bearer {STABILITY_API_KEY}", "accept": "image/*" }
252
+ data = {
253
+ "prompt": f"Detailed cinematic photo, Kazakh culture theme, high resolution, soft lighting, 8k, {prompt}",
254
+ "output_format": "jpeg", "aspect_ratio": "1:1", "model": "sd3-medium"
255
+ }
256
+
257
+ try:
258
+ response = requests.post(url, headers=headers, files={'none': ''}, data=data, timeout=30)
259
+ response.raise_for_status()
260
+
261
+ if response.status_code == 200:
262
+ base64_img = base64.b64encode(response.content).decode("utf-8")
263
+ return f"data:image/jpeg;base64,{base64_img}", "Сурет сәтті жасалды ✅"
264
+ else:
265
+ return None, f"SDXL Қатесі: {response.status_code}. API лимит."
266
+
267
+ except Exception as e:
268
+ return None, f"Сурет Қатесі: {e.__class__.__name__}. API байланысын тексеріңіз."
269
+
270
+ # 2.4. AI Voice Chat (API Dependent - Uses Gemini for both ASR and Chat)
271
+ def process_voice_chat(audio_path):
272
+ if not audio_path: return "Дауыс жазбасы жоқ", "Gemini API Қатесі ❌"
273
+
274
+ if GEMINI_CLIENT is None or API_STATUSES.get('Gemini') != "✅":
275
+ return "Дауыс жазбасы жоқ", "GemINI API Қатесі ❌"
276
+
277
+ audio_file = None
278
+ try:
279
+ transcribe_prompt = "Transcribe the audio and provide ONLY the text in the original language. Do not add any extra commentary."
280
+
281
+ audio_file = GEMINI_CLIENT.upload_file(file=audio_path)
282
+
283
+ transcribe_response = GEMINI_CLIENT.models.generate_content(
284
+ model='gemini-2.5-flash', contents=[transcribe_prompt, audio_file]
285
+ )
286
+ original_text = transcribe_response.text.strip()
287
+
288
+ chat_prompt = f"Сіз 'SQG Quantum Leap AI'-сіз. Жауабыңызды толығымен **қазақ тілінде** беріңіз:\n\nСұрақ: {original_text}"
289
+ chat_response = GEMINI_CLIENT.models.generate_content(
290
+ model='gemini-2.5-flash', contents=chat_prompt
291
+ )
292
+
293
+ GEMINI_CLIENT.delete_file(audio_file.name)
294
+
295
+ return original_text, chat_response.text.strip()
296
+
297
+ except Exception as e:
298
+ if audio_file:
299
+ try: GEMINI_CLIENT.delete_file(audio_file.name)
300
+ except: pass
301
+ return "Түпнұсқа мәтін қатесі", f"ЖИ Сөйлесу Қатесі: {e.__class__.__name__}."
302
+
303
+
304
+ # 2.5. Vision (Image Analysis - API Dependent)
305
+ def analyze_vision_gemini(img):
306
+ if img is None:
307
+ df = pd.DataFrame({"Нәтиже": ["Сурет жоқ"], "Сенімділік": ["-"]})
308
+ return df, "Сурет жүктеңіз."
309
+ if GEMINI_CLIENT is None or API_STATUSES.get('Gemini') != "✅":
310
+ df = pd.DataFrame({"Нәтиже": ["Қате"], "Сенімділік": ["-"]})
311
+ return df, "Gemini API Қатесі ❌"
312
+
313
+ vision_file = None
314
+ temp_img_path = None
315
+ try:
316
+ with tempfile.NamedTemporaryFile(suffix=".jpeg", delete=False) as tmp:
317
+ img.save(tmp.name, format="JPEG")
318
+ temp_img_path = tmp.name
319
+
320
+ vision_file = GEMINI_CLIENT.upload_file(file=temp_img_path)
321
+
322
+ prompt = "Analyze this image in detail and provide ONLY the analysis in fluent Kazakh. Start with a short, encouraging greeting."
323
+ response = GEMINI_CLIENT.models.generate_content(
324
+ model='gemini-2.5-flash', contents=[prompt, vision_file]
325
+ )
326
+
327
+ GEMINI_CLIENT.delete_file(vision_file.name)
328
+ os.unlink(temp_img_path)
329
+
330
+ full_text = response.text.strip()
331
+ df = pd.DataFrame([["ЖИ Талдауы", "Жоғары"]], columns=["Нәтиже", "Сенімділік"])
332
+ return df, f"Талдау: {full_text}"
333
+
334
+ except Exception as e:
335
+ if vision_file:
336
+ try: GEMINI_CLIENT.delete_file(vision_file.name)
337
+ except: pass
338
+ if temp_img_path and os.path.exists(temp_img_path):
339
+ os.unlink(temp_img_path)
340
+
341
+ df = pd.DataFrame({"Нәтиже": ["Қате"], "Сенімділік": ["-"]})
342
+ return df, f"Gemini Vision Қатесі: {e.__class__.__name__}"
343
+
344
+
345
+ # 2.6. News and Moderation (Strong key check)
346
+ def get_latest_news(query):
347
+ if not NEWS_API_KEY or API_STATUSES.get('News') != "✅":
348
+ return "NewsAPI Key Қате ❌. Жаңалықтар қолжетімсіз."
349
+
350
+ if not query: query = "Kazakhstan OR Mongolia OR Kazakh Diaspora"
351
+ url = f"https://newsapi.org/v2/everything?q={query}&language=en&sortBy=publishedAt&apiKey={NEWS_API_KEY}&pageSize=5"
352
+ try:
353
+ response = requests.get(url, timeout=10)
354
+ response.raise_for_status()
355
+ data = response.json()
356
+ articles = data.get('articles', [])
357
+
358
+ if not articles: return f"Жаңалықтар жоқ: '{query}'"
359
+
360
+ article_summaries = "\n---\n".join([f"Атауы: {a.get('title')}. Қысқаша: {a.get('description') or 'N/A'}" for a in articles])
361
+
362
+ prompt = f"Төмендегі 5 жаңалықты 1-ден 5-ке дейін нөмірлеп, әрқайсысын 1 сөйлеммен толығымен қазақ тілінде түсіндіріп беріңіз. Жауапты '🚨 Жаңалықтар Шолуы:' деп бастаңыз. Мәтін: {article_summaries}"
363
+
364
+ if GEMINI_CLIENT and API_STATUSES.get('Gemini') == "✅":
365
+ summary_response = GEMINI_CLIENT.models.generate_content(
366
+ model='gemini-2.5-flash', contents=prompt
367
+ )
368
+ return summary_response.text.strip()
369
+ else:
370
+ return "🚨 Жаңалықтар (Gemini жоқ): Қорытындылау мүмкін емес."
371
+
372
+ except Exception as e:
373
+ return f"Жаңалықтар Қатесі: {e.__class__.__name__}. Кілтті немесе лимиттерді тексеріңіз."
374
+
375
+ def add_post_with_moderation(current_state, new_message):
376
+ if not new_message: return current_state, "\n\n---\n\n".join(current_state), time.time(), "Модерация ✅"
377
+
378
+ is_toxic, ai_note = False, "Модерация ✅ (OpenAI)"
379
+ if OPENAI_KEY and API_STATUSES.get('OpenAI Mod') == "✅":
380
+ try:
381
+ mod_response = requests.post(
382
+ "https://api.openai.com/v1/moderations",
383
+ headers={"Authorization": f"Bearer {OPENAI_KEY}", "Content-Type": "application/json"},
384
+ json={"input": new_message}
385
+ )
386
+ mod_response.raise_for_status()
387
+ data = mod_response.json()
388
+ if data["results"][0]["flagged"]:
389
+ is_toxic = True
390
+ ai_note = "🛡️ Қабылданбады (Токсикалық Контент)"
391
+ except Exception:
392
+ ai_note = "Модерация Қатесі ⚠️"
393
+
394
+ if is_toxic:
395
+ return current_state, "\n\n---\n\n".join(current_state), time.time(), ai_note
396
+
397
+ now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).strftime('%H:%M')
398
+ formatted_msg = "[{}] Хабарлама: {}".format(now, new_message)
399
+ updated_state = [formatted_msg] + current_state
400
+
401
+ return updated_state[:10], "\n\n---\n\n".join(updated_state[:10]), time.time(), ai_note
402
+
403
+ def get_local_time_and_holiday(last_active):
404
+ now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))
405
+ time_str = now.strftime('%Y-%m-%d, %H:%M')
406
+
407
+ holiday_note = ""
408
+ if now.month == 7 and now.day in [11, 12, 13]:
409
+ holiday_note = "Бүгін - Наадам Мерекесі 🇲🇳"
410
+
411
+ return f"⏱️ Өлгий Уақыты: **{time_str}**\n\n{holiday_note}"
412
+
413
+ def translate_text_gemini(text, source_lang, target_lang="Қазақ"):
414
+ if not text: return "Мәтін енгізіңіз."
415
+ if GEMINI_CLIENT is None or API_STATUSES.get('Gemini') != "✅":
416
+ return "❌ Gemini API Қатесі."
417
+
418
+ prompt = f"Сіз жоғары дәлдіктегі аудармашысыз. Түпнұсқа тіл '{source_lang}' тілінен '{target_lang}' тіліне аударыңыз. Тек аударма мәтінін ғана беріңіз. Мәтін: '{text}'"
419
+
420
+ try:
421
+ response = GEMINI_CLIENT.models.generate_content(
422
+ model='gemini-2.5-flash', contents=prompt
423
+ )
424
+ return response.text.strip()
425
+ except Exception as e:
426
+ return f"❌ Аударма Қатесі: {e.__class__.__name__}"
427
+
428
+ def get_map_ai_analysis():
429
+ if GEMINI_CLIENT is None or API_STATUSES.get('Gemini') != "✅":
430
+ return "❌ Gemini API Қатесі."
431
+
432
+ prompt = "Баян-Өлгий аймағының географиялық ерекшеліктері, климаты және осы аймақтың қазақ халқы үшін маңыздылығы туралы 5 қысқа фактіні толығымен қазақ тілінде, таза тілмен сипаттаңыз. Жауапты '🌐 Гео-Талдау:' деп бастаңыз."
433
+
434
+ try:
435
+ response = GEMINI_CLIENT.models.generate_content(
436
+ model='gemini-2.5-flash', contents=prompt
437
+ )
438
+ return response.text.strip()
439
+ except Exception as e:
440
+ return f"❌ Карта Талдауы Қатесі: {e.__class__.__name__}"
441
+
442
+
443
+ # -----------------------------------------------------------------
444
+ # 3. GRADIO INTERFACE (Finalized Design)
445
+ # -----------------------------------------------------------------
446
+
447
+ # Finalized CSS for stability and readability
448
+ css_style = """
449
+ body {
450
+ background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_Starry_Night_-_Restored.jpg/2560px-Van_Gogh_Starry_Night_-_Restored.jpg') !important;
451
+ background-size: cover !important;
452
+ background-attachment: fixed !important;
453
+ background-position: center !important;
454
+ color: #f0f0f0 !important;
455
+ }
456
+ .gradio-container {
457
+ background: rgba(0, 0, 0, 0.95) !important;
458
+ border-radius: 15px !important;
459
+ padding: 20px !important;
460
+ box-shadow: 0 0 30px rgba(70, 130, 180, 0.5);
461
+ }
462
+ h1, h3 { color: #ffd700 !important; font-family: sans-serif; }
463
+ .gr-box, .gr-input, .gr-textarea {
464
+ background: rgba(30, 30, 30, 0.9) !important;
465
+ color: #f0f0f0 !important;
466
+ border: 1px solid #4682b4 !important;
467
+ border-radius: 6px !important;
468
+ }
469
+ .gr-button {
470
+ background: #4682b4 !important;
471
+ color: #ffffff !important;
472
+ font-weight: bold !important;
473
+ border-radius: 8px !important;
474
+ }
475
+ .gr-tab-button.selected {
476
+ background: #1a4d95 !important;
477
+ color: #ffd700 !important;
478
+ }
479
+ .status-note {
480
+ background-color: rgba(255, 243, 205, 0.9);
481
+ color: #856404;
482
+ text-align: center;
483
+ padding: 5px;
484
+ border-radius: 5px;
485
+ }
486
+ .gradio-chatbot {
487
+ background-color: rgba(255, 255, 255, 0.05) !important;
488
+ }
489
+ """
490
+
491
+ with gr.Blocks(theme=gr.themes.Soft(), css=css_style, title="SQG Quantum Leap AI V69.0") as interface:
492
+ # State variables
493
+ message_state = gr.State([])
494
+ last_active = gr.State(0)
495
+
496
+ # Header
497
+ gr.Markdown(f"<h1><center>🌌 SQG Quantum Leap AI: Баян-Өлгий</center></h1>")
498
+ gr.Markdown(f"<div class='status-note'>ЖИ Қызметтер Күйі: {API_STATUS_NOTE}</div>")
499
+
500
+ with gr.Tabs():
501
+
502
+ # 1. DEDICATED HYBRID CHAT (Uses Gemini, falls back to Llama 3)
503
+ with gr.TabItem("💬 Чат & 🔊"):
504
+ gr.Markdown("## Гибридті ЖИ Чат (Gemini + Llama 3 Резерві)")
505
+
506
+ tts_status_output = gr.HTML(value="Оқылатын мәтін жоқ.", visible=True)
507
+ tts_btn = gr.Button("🔊 Соңғы Жауапты Оқыту (TTS)")
508
+
509
+ chat_interface = gr.ChatInterface(
510
+ fn=text_chat_gemini_or_llama, # <-- USING THE HYBRID FUNCTION
511
+ textbox=gr.Textbox(placeholder="Сұрағыңызды енгізіңіз...", container=False),
512
+ title="",
513
+ submit_btn="Жіберу"
514
+ )
515
+
516
+ tts_btn.click(
517
+ fn=play_last_response,
518
+ outputs=tts_status_output,
519
+ queue=False
520
+ )
521
+
522
+
523
+ # 2. VOICE CHAT
524
+ with gr.TabItem("🎙️ Дауыс"):
525
+ gr.Markdown("## Дауыстық Сөйлесу")
526
+ audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Дауыс жазба")
527
+ voice_btn = gr.Button("🗣️ Сөйлесуді Бастау")
528
+ with gr.Row():
529
+ user_text_output = gr.Textbox(label="Сіздің Сөзіңіз", lines=2, interactive=False)
530
+ ai_text_output = gr.Textbox(label="ЖИ Жауабы", lines=4, interactive=False)
531
+ voice_btn.click(fn=process_voice_chat, inputs=[audio_input], outputs=[user_text_output, ai_text_output])
532
+
533
+
534
+ # 3. IMAGE GENERATION
535
+ with gr.TabItem("✨ Сурет"):
536
+ gr.Markdown("## Кескін Генерациясы")
537
+ prompt_input = gr.Textbox(label="Сурет Идеясын Енгізіңіз", lines=2, placeholder="Сипаттама")
538
+ gen_btn = gr.Button("🖼️ Сурет Жасау")
539
+ with gr.Row():
540
+ image_output = gr.Image(label="Нәтиже", type="filepath", height=450)
541
+ status_output = gr.Textbox(label="Күйі", lines=2, interactive=False)
542
+ gen_btn.click(fn=generate_image_sdxl, inputs=[prompt_input], outputs=[image_output, status_output])
543
+
544
+ # 4. VISION (Image Analysis)
545
+ with gr.TabItem("🖼️ Талдау"):
546
+ gr.Markdown("## Кескінді Талдау")
547
+ with gr.Row():
548
+ img_input = gr.Image(type="pil", label="Сурет Жүктеу", height=350)
549
+ with gr.Column():
550
+ output_table = gr.Dataframe(headers=["Нәтиже", "Сенімділік"], label="ЖИ Нәтижесі", row_count=1, col_count=(2, 'fixed'))
551
+ qazaq_note_output = gr.Textbox(label="Толық Талдау", lines=6, interactive=False)
552
+ submit_btn = gr.Button("▶️ Талдауды Бастау")
553
+ submit_btn.click(fn=analyze_vision_gemini, inputs=[img_input], outputs=[output_table, qazaq_note_output])
554
+
555
+
556
+ # 5. WEATHER & MAP AI
557
+ with gr.TabItem("🗺️ Ауа Райы & Карта"):
558
+ gr.Markdown("## Өлгий Ауа Райы & Гео-Талдау")
559
+
560
+ initial_weather, initial_prediction = get_weather_and_prediction()
561
+ initial_map_ai = get_map_ai_analysis()
562
+
563
+ with gr.Row():
564
+ with gr.Column(scale=1):
565
+ weather_output = gr.Markdown(value=initial_weather)
566
+ time_output = gr.Markdown(get_local_time_and_holiday(0))
567
+
568
+ refresh_btn = gr.Button("🔄 Жаңарту")
569
+ time_refresh_btn = gr.Button("⏱️ Уақытты Жаңарту")
570
+
571
+ with gr.Column(scale=2):
572
+ gr.Markdown("### 5 Күндік Болжам")
573
+ prediction_output = gr.Textbox(value=initial_prediction, label="Gemini Шолуы", lines=10, interactive=False)
574
+
575
+ refresh_btn.click(fn=get_weather_and_prediction, outputs=[weather_output, prediction_output])
576
+ time_refresh_btn.click(fn=get_local_time_and_holiday, inputs=last_active, outputs=time_output)
577
+
578
+
579
+ gr.Markdown("---")
580
+ gr.Markdown("### 🌐 Карта & ЖИ Гео-Талдау")
581
+
582
+ gr.HTML(value='<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1525.8675121175628!2d89.96783857500351!3d48.97116742514331!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x56a624597b6a67f1%3A0x6a053229b1d7d56e!2z0J7QvdC-0YDQsNC90Ysg0J7QvdC10YDQsA!5e0!3m2!1sru!2smn!4v1700000000000!5m2!1sru!2smn" width="100%" height="300" style="border:0; border-radius: 10px;" allowfullscreen="" loading="lazy"></iframe>', label="Карта")
583
+
584
+ map_ai_output = gr.Textbox(value=initial_map_ai, label="Gemini Талдауы", lines=6, interactive=False)
585
+ map_ai_btn = gr.Button("🧠 Гео-Талдауды Жаңарту")
586
+ map_ai_btn.click(fn=get_map_ai_analysis, outputs=map_ai_output)
587
+
588
+
589
+ # 6. NEWS
590
+ with gr.TabItem("📰 Жаңалықтар"):
591
+ gr.Markdown("## Жаңалықтар Шолуы")
592
+ news_query = gr.Textbox(label="Тақырып бойынша іздеу", placeholder="Мысалы: 'Қазақ музыкасы'", lines=1)
593
+ news_btn = gr.Button("🔎 Жаңалықтарды Көру")
594
+ news_output = gr.Textbox(label="Gemini Қорытындысы", lines=10, interactive=False)
595
+ news_btn.click(fn=get_latest_news, inputs=[news_query], outputs=news_output)
596
+
597
+
598
+ # 7. HUB (Moderation Chat)
599
+ with gr.TabItem("📢 Хабарламалар"):
600
+ gr.Markdown("## Коммуникация Хабы")
601
+ moderation_status = gr.Markdown("🛡️ Модерация Күйі", elem_id="mod_status")
602
+ message_display = gr.Markdown(value="Хабарлама жоқ...", label="Соңғы 10 Хабарлама")
603
+ with gr.Row():
604
+ message_input = gr.Textbox(label="Жаңа Хабарлама", placeholder="Хабарлама енгізіңіз...", lines=2)
605
+ send_button = gr.Button("Жіберу (AI Тексереді)")
606
+ send_button.click(
607
+ fn=add_post_with_moderation, inputs=[message_state, message_input], outputs=[message_state, message_display, last_active, moderation_status]
608
+ )
609
+
610
+ # 8. TRANSLATION TAB
611
+ with gr.TabItem("🌍 Аударма"):
612
+ gr.Markdown("## ЖИ Аудармашы")
613
+ with gr.Row():
614
+ source_text = gr.Textbox(label="Түпнұсқа Мәтін", lines=5)
615
+ source_lang_dropdown = gr.Dropdown(
616
+ ["Орыс", "Түрік", "Моңғол", "Ағылшын"],
617
+ label="Түпнұсқа Тіл",
618
+ value="Орыс"
619
+ )
620
+ translate_btn = gr.Button("➡️ Аударма")
621
+ translated_text = gr.Textbox(label="Аударма", lines=5, interactive=False)
622
+
623
+ translate_btn.click(
624
+ fn=translate_text_gemini,
625
+ inputs=[source_text, source_lang_dropdown],
626
+ outputs=translated_text
627
+ )
628
+
629
+
630
+ # -----------------------------------------------------------------
631
+ # 4. LAUNCH INTERFACE
632
+ # -----------------------------------------------------------------
633
+ if __name__ == "__main__":
634
+ interface.launch()