File size: 22,650 Bytes
da0b493
a5a4bb1
 
 
900d965
53a2d3f
92213e0
 
8c27757
 
92213e0
1baacb1
53a2d3f
900d965
0eade87
4463894
 
1baacb1
92213e0
 
e7cf328
57e0ef1
e8895f6
57e0ef1
 
 
 
 
 
 
 
 
 
 
 
 
a32be45
 
 
57e0ef1
 
e8895f6
57e0ef1
 
0bcc5d5
e8895f6
 
de945b3
 
e8895f6
de945b3
e8895f6
 
 
 
 
 
 
 
de945b3
0bcc5d5
 
 
 
 
 
 
 
57e0ef1
 
de945b3
0bcc5d5
de945b3
 
 
0bcc5d5
de945b3
 
0bcc5d5
 
de945b3
0bcc5d5
 
 
de945b3
0bcc5d5
 
 
de945b3
 
 
 
0bcc5d5
 
 
 
 
92213e0
53a2d3f
de945b3
92213e0
 
 
 
 
 
 
 
 
 
0bcc5d5
53a2d3f
92213e0
 
7e8f68f
154d683
52b338d
0bcc5d5
92213e0
0bcc5d5
a32be45
 
 
154d683
de945b3
7e8f68f
 
 
 
0bcc5d5
7e8f68f
 
 
e8895f6
de945b3
92213e0
 
 
 
 
de945b3
92213e0
 
de945b3
57e0ef1
e8895f6
92213e0
0bcc5d5
92213e0
7e8f68f
 
 
988db9d
7e8f68f
 
 
 
 
 
 
 
e8895f6
7e8f68f
92213e0
7e8f68f
e8895f6
 
988db9d
e8895f6
7e8f68f
e8895f6
a32be45
 
0bcc5d5
154d683
a32be45
 
 
92213e0
 
 
 
f58edba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
afa3b65
f58edba
92213e0
 
 
 
 
 
 
 
 
 
64d6969
92213e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963d88b
 
 
 
a32be45
 
 
 
 
 
8c27757
a32be45
 
 
8c27757
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd14b72
bb928f5
dd14b72
 
bb928f5
dd14b72
 
bb928f5
 
 
dd14b72
 
8c27757
bb928f5
dd14b72
51346d1
dd14b72
 
 
 
51346d1
bb928f5
 
 
 
 
 
dd14b72
8c27757
dd14b72
 
 
64d6969
dd14b72
bb928f5
64d6969
bb928f5
dd14b72
 
 
 
51346d1
dd14b72
49ac79f
 
 
 
bf7125c
64d6969
4823e9d
da76982
 
 
 
 
900d965
f58edba
 
 
0eade87
0b998b1
 
a6345ee
 
0eade87
39a0679
 
 
 
 
 
64d6969
39a0679
 
 
 
 
 
bf7125c
ea7f237
 
bf7125c
ea7f237
 
49ac79f
66e9f2d
39a0679
52ff067
ef06514
6b0e717
c72932a
 
ef06514
49ac79f
ef06514
49ac79f
c72932a
49ac79f
c72932a
52ff067
 
c72932a
 
6b0e717
0b74946
6b0e717
64d6969
6b0e717
 
 
9b077ab
6b0e717
 
05401c2
6b0e717
 
 
 
 
 
 
 
 
64d6969
c72932a
6b0e717
 
 
dd14b72
64d6969
6b0e717
dd14b72
52ff067
64d6969
6b0e717
dd14b72
0c19909
0eade87
6b0e717
52ff067
6b0e717
0c19909
 
6b0e717
0c19909
 
 
 
 
 
 
6b0e717
52ff067
a32be45
49ac79f
6b0e717
64d6969
 
6b0e717
 
64d6969
0c19909
49ac79f
0c19909
6b0e717
64d6969
6b0e717
e7cf328
52ff067
64d6969
 
52ff067
 
64d6969
 
07b06eb
0eade87
64d6969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169796d
64d6969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169796d
 
 
 
 
 
64d6969
 
 
 
169796d
64d6969
 
9b077ab
64d6969
 
9b077ab
64d6969
9b077ab
64d6969
 
 
 
 
 
 
9b077ab
64d6969
 
9b077ab
64d6969
 
 
 
 
 
 
 
 
 
 
 
 
9b077ab
64d6969
9b077ab
52ff067
 
 
 
 
39a0679
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
import time
from langchain_community.tools import DuckDuckGoSearchRun
from cerebras.cloud.sdk import Cerebras
from langchain_openai import ChatOpenAI
import os
import requests
import asyncio
import aiohttp
import textwrap
import re  
from datetime import datetime, timedelta
from auth import save_chats
from bs4 import BeautifulSoup
from openai import OpenAI
from duckduckgo_search import DDGS
from PyPDF2 import PdfReader
from docx import Document

from urllib.parse import urlparse, quote
from retry import retry  # For retrying flaky operations
from groq import Groq  # For the second AI (Qwen-3-32B)

# ================== PHILIPPINE LOCAL + MAX SEARCH + DEEP-THINK LENIENT FILTER ==================
PHILIPPINE_LOCATIONS = [
    "bacolod", "negros occidental", "negros", "iloilo", "cebu", "davao", "manila", "quezon",
    "makati", "pasig", "taguig", "cagayan de oro", "zamboanga", "general santos", "dumaguete",
    "tacloban", "ormoc", "dipolog", "pagadian", "roxas city", "kalibo", "antique", "capiz",
    "guimaras", "siquijor", "bohol", "leyte", "samar", "masbate"
]

def is_philippine_local_query(query: str) -> bool:
    q = query.lower()
    return any(loc in q for loc in PHILIPPINE_LOCATIONS)

def enhance_query(query: str) -> str:
    today_date = datetime.now().strftime("%B %d, %Y")
    q = query.lower().strip()
    if q in ["news today", "news", "today", "latest news", "headlines", "breaking news"]:
        return f"top world news headlines {today_date} breaking news"
    if is_philippine_local_query(query):
        return f"{query} Philippines latest news {today_date}"
    if any(w in q for w in ['today', 'latest', 'breaking', 'current', 'now']):
        return f"{query} latest {today_date}"
    return query

def get_cutoff_days(query: str) -> int:
    """Smart cutoff: very strict for "today", lenient for deep sub-queries"""
    q = query.lower()
    if any(x in q for x in ["today", "breaking", "just now", "live", "happening now"]):
        return 3
    if any(x in q for x in ["this week", "latest", "recent update", "current"]):
        return 10
    if any(x in q for x in ["controversy", "alternative", "debate", "criticism", "scandal", "views"]):
        return 90   # Deep Think needs these
    if any(x in q for x in ["solution", "how to", "fix", "analysis", "details", "updates", "mainstream"]):
        return 45
    if is_philippine_local_query(query) or "news" in q:
        return 12
    return 35

def get_domain(url: str) -> str:
    try:
        return urlparse(url).netloc.lower().replace("www.", "").replace("m.", "")
    except:
        return ""

def domain_score(url: str, is_ph: bool) -> int:
    dom = get_domain(url)
    RELIABLE_PH = {"inquirer.net", "rappler.com", "philstar.com", "gmanetwork.com", "abs-cbnnews.com",
                   "manilatimes.net", "cnnphilippines.com", "mb.com.ph", "businessworld.com.ph", "pna.gov.ph"}
    RELIABLE_GLOBAL = {"reuters.com", "apnews.com", "bbc.com", "nytimes.com", "bloomberg.com", "theguardian.com", "aljazeera.com"}
    if dom in (RELIABLE_PH if is_ph else RELIABLE_GLOBAL):
        return 95
    if dom.endswith(".ph") or "gov.ph" in dom:
        return 75
    if any(g in dom for g in RELIABLE_GLOBAL):
        return 85
    return 45

def parse_ddgs_date(date_str: str):
    if not date_str or date_str.lower() in ["recent", ""]:
        return datetime.now()
    date_str = date_str.lower()
    now = datetime.now()
    match = re.search(r"(\d+)\s*(hour|day|week|month|min)", date_str)
    if match:
        num = int(match.group(1))
        unit = match.group(2)
        if "min" in unit or "hour" in unit: return now - timedelta(hours=max(1, num))
        if "day" in unit: return now - timedelta(days=num)
        if "week" in unit: return now - timedelta(weeks=num)
        if "month" in unit: return now - timedelta(days=num * 30)
    try:
        return datetime.strptime(date_str[:10], "%Y-%m-%d")
    except:
        return now

async def fetch_full_content_async(session, url: str):
    try:
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
        async with session.get(url, headers=headers, timeout=12) as response:
            if response.status != 200:
                return None
            text = await response.text()
            soup = BeautifulSoup(text, 'html.parser')
            for tag in soup(["script", "style", "nav", "footer", "header", "aside"]):
                tag.decompose()
            content = ' '.join(p.get_text(strip=True) for p in soup.find_all('p') if len(p.get_text(strip=True)) > 25)
            return content[:4800] if len(content) > 100 else None
    except Exception as e:
        return None

@retry(tries=2, delay=1)  # Retry on failures
async def web_search_async(query, max_results=120):
    """MAX websites + 3-stage fallback = Grok-level deep search that NEVER returns empty"""
    try:
        enhanced = enhance_query(query)
        is_ph = is_philippine_local_query(query)
        cutoff_days = get_cutoff_days(query)

        # === DYNAMIC TODAY DATE (used in all fallbacks) ===
        today_str = datetime.now().strftime("%B %d, %Y")

        with DDGS() as ddgs:
            raw = []
            try: raw.extend(list(ddgs.news(keywords=enhanced, max_results=60, safesearch="off", region="ph-en" if is_ph else "wt-wt")))
            except: pass
            try: raw.extend(list(ddgs.text(keywords=enhanced, max_results=80)))
            except: pass

        # Dedup + rank
        seen = set()
        unique = [r for r in raw if (url := r.get('url') or r.get('href','')) and url not in seen and url.startswith('http') and (seen.add(url) or True)]
        unique.sort(key=lambda r: (domain_score(r.get('url') or '', is_ph), parse_ddgs_date(r.get('date','')).timestamp()), reverse=True)

        # Async fetch full content (limit to top 20 to avoid overload)
        async with aiohttp.ClientSession() as session:
            tasks = [fetch_full_content_async(session, r.get('url') or r.get('href', '')) for r in unique[:20]]
            full_contents = await asyncio.gather(*tasks, return_exceptions=True)

        formatted = []
        for idx, (r, full) in enumerate(zip(unique[:100], full_contents + [None] * (100 - len(full_contents)))):
            if isinstance(full, Exception): full = None
            url = r.get('url') or r.get('href', '')
            pub_date = parse_ddgs_date(r.get('date', ''))
            age_days = (datetime.now() - pub_date).days
            if age_days > cutoff_days + 15: continue

            if full and len(full) > 120:
                title = r.get('title', 'No title')
                snippet = r.get('body', '')[:800]
                age_str = "today" if age_days <= 1 else f"{age_days}d ago"
                entry = f"[{len(formatted)+1}] **{title}** ({age_str})\nSnippet: {snippet[:380]}...\n[URL: {url}]"
                formatted.append(entry)
                if len(formatted) >= 18: break

        # STAGE 2 & 3 FALLBACK β€” guarantees results
        if len(formatted) < 10:
            relaxed = cutoff_days * 3
            for r in unique[30:160]:
                if any(r.get('url','') in e for e in formatted): continue
                pub_date = parse_ddgs_date(r.get('date', ''))
                if (datetime.now() - pub_date).days > relaxed: continue
                full = await fetch_full_content_async(session, r.get('url') or r.get('href',''))  # Single async fetch for fallback
                if full:
                    title = r.get('title', 'No title')
                    snippet = r.get('body', '')[:800]
                    entry = f"[{len(formatted)+1}] **{title}** ({(datetime.now()-pub_date).days}d ago - relaxed)\nSnippet: {snippet[:380]}...\n[URL: {r.get('url') or r.get('href','')}]"
                    formatted.append(entry)
                    if len(formatted) >= 16: break

        # === FIXED: Dynamic date fallback ===
        return formatted or [f"Latest available data for \"{query}\" as of {today_str} (broad search used)."]

    except Exception as e:
        today_str = datetime.now().strftime("%B %d, %Y")  # also dynamic in error case
        return [f"Search error: {str(e)[:120]}... Using latest available data as of {today_str}."]
        
def web_search(query, max_results=120):
    """Sync wrapper for async web_search"""
    return asyncio.run(web_search_async(query, max_results))

def extract_file_text(file_paths):
    if not file_paths:
        return ""
    texts = []
    for path in file_paths:
        try:
            if path.lower().endswith('.txt'):
                with open(path, 'r', encoding='utf-8') as f:
                    texts.append(f"--- {os.path.basename(path)} ---\n{f.read()}")
            elif path.lower().endswith('.pdf'):
                reader = PdfReader(path)
                text = "\n".join(page.extract_text() or "" for page in reader.pages)
                texts.append(f"--- {os.path.basename(path)} ---\n{text}")
            elif path.lower().endswith('.docx'):
                doc = Document(path)
                text = "\n".join(para.text for para in doc.paragraphs)
                texts.append(f"--- {os.path.basename(path)} ---\n{text}")
        except Exception as e:
            texts.append(f"--- {os.path.basename(path)} ---\n[Error reading file: {str(e)}]")
    return "\n\n".join(texts)[:12000]

def generate_queries(client, current_model, message, today_date):
    """Helper to generate smart queries for Deep Think"""
    try:
        plan_prompt = f"""Generate 3-6 complete, concise and correct keyword phrases (3-8 words max, NO operators, NO site except if asked, NO OR/AND) for deep live search on: "{quote(message)}".
Include time words (today, 2026, latest, March 2026, etc.) where helpful.
If a quiz is asked then be precise and straight to the point when answering.
Make them varied: mainstream news, global news, background, controversy, updates, global/local, different angles.
One phrase per line only. No numbers, no explanations except if asked."""
        plan = client.chat.completions.create(
            model=current_model,
            messages=[{"role": "user", "content": plan_prompt}],
            max_tokens=250, temperature=0.4
        )
        return [line.strip() for line in plan.choices[0].message.content.splitlines() if line.strip()][:6]
    except Exception:
        base = message if len(message) < 60 else message[:55]
        return [
            f"{base} latest news",
            f"{base} March 2026",
            f"{base} controversy",
            f"{base} updates today",
            f"{base} analysis",
            f"{base} mainstream",
            f"{base} world",
            f"{base} usa",
            f"{base} global"
        ]

# Setup for the second AI model (Qwen-3-32B)
client_qwen = Groq()  # For using the Qwen-3-32B model

def clean_response(text: str) -> str:
    """Strip leaked <think> blocks and extra newlines"""
    if not text:
        return ""
    text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r'```(?:think|reasoning).*?```', '', text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r'\n{3,}', '\n\n', text)
    return text.strip()


def format_sources(sources_list, max_sources=7):
    """Super clean, beautiful sources for both Philippine & global news"""
    if not sources_list:
        return ""
    
    formatted = []
    for i, entry in enumerate(sources_list[:max_sources], 1):
        title = "Source"
        snippet = ""
        url = "#"
        
        if '**' in entry:
            title_match = re.search(r'\*\*(.+?)\*\*', entry)
            if title_match:
                title = title_match.group(1).strip()
        
        if 'Snippet:' in entry:
            snippet_part = entry.split('Snippet:')[1].split('\n')[0]
            snippet = snippet_part.strip()[:260] + "..." if len(snippet_part) > 260 else snippet_part.strip()
        
        url_match = re.search(r'\[URL:\s*(https?://[^\]]+)', entry)
        if url_match:
            url = url_match.group(1).strip()
        
        formatted.append(f"{i}. **{title}**  \n   {snippet}\n   [Read full β†’]({url})")
    
    return "\n\n".join(formatted)


def qwen_reasoning(message: str, search_context: str, chat_context: str = "") -> str:
    """Ultra-strict: 100% obeys chat instructions + only real data + specific"""
    system_instruction = ""
    if chat_context and str(chat_context).strip():
        system_instruction = f"""[PERSISTENT CHAT INSTRUCTION β€” MUST OBEY 100% FOR THIS CHAT]
{chat_context}

β†’ The ENTIRE answer must follow this instruction exactly (e.g. use only Filipino, be funny, be concise, etc.).
β†’ If it says "Use Filipino", reply 100% in Filipino.
β†’ If it says "be funny", add jokes in every response.
"""

    prompt = textwrap.dedent(f"""\
{system_instruction}You are a precise news analyst. Use ONLY the real-time search data provided below. Do not add anything from your knowledge.

Real-time search data:
{search_context}

User asked: "{message}"

STRICT RULES:
- Be extremely specific: name exact events, people, places, numbers, dates from the data only.
- NEVER generalize, never say "details emerging", "potential", "focus areas", "stay tuned", or make up 2026 events.
- If search data has no specific news, say exactly: "Walang major specific events sa latest searches para ngayon. Ito ang top general headlines mula sa data:"
- Prioritize Philippine sources when query mentions Philippines.
- Use clear markdown with bullets and bold facts.
- Never output <think> or reasoning.

Final answer:""")

    try:
        completion = client_qwen.chat.completions.create(
            model="qwen2-72b-instruct",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.45,   # even more factual
            max_tokens=2000,
            top_p=0.88,
            stream=False,
        )
        return clean_response(completion.choices[0].message.content or "")
    except Exception:
        return "Limited verified updates available right now."

# Cerebras Reviewer (strong reasoning model)
cerebras_client = Cerebras(
    api_key=os.environ.get("CEREBRAS_API_KEY")
)

# ================== MAIN CHAT STREAM (fixed and refined for all modes) ==================
def chat_stream(message, history, current_model, logged_in_username, user_chats, current_chat_title, mode, auth_globals, file_paths=None, context=""):
    if file_paths and isinstance(file_paths, list) and len(file_paths) > 5:
        history.append({"role": "assistant", "content": "❌ Maximum 5 files allowed per message. Please remove some and try again."})
        yield history
        return

    client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=os.environ.get("API_KEY"))
    
    file_text = extract_file_text(file_paths) if file_paths else ""
    
    history = history or []
    if not logged_in_username or not current_model or not current_chat_title:
        history.append({"role": "assistant", "content": "⚠️ Please login, load model, and select/create a chat first!"})
        yield history
        return

    user_input = quote(message)
    if file_text:
        user_input += f"\n\n--- Attached files (up to 5) ---\n{file_text}"

    history.append({"role": "user", "content": user_input})
    yield history

    history.append({"role": "assistant", "content": ""})
    yield history

    today_date = datetime.now().strftime("%B %d, %Y")

    # === STRONG CONTEXT HANDLING (works in EVERY mode) ===
    context_instruction = ""
    if context and str(context).strip():
        ctx = str(context).strip()
        context_instruction = f"""[PERSISTENT CHAT INSTRUCTION β€” ALWAYS FOLLOW FOR THIS CHAT ONLY]
{ctx}

You MUST follow the above instruction naturally in EVERY response."""

    system_context = [{"role": "system", "content": context_instruction}] if context_instruction else []

    if mode == "πŸ’» Coding Mode":
        # Gather persistent files
        coding_files_str = ""
        current_chat = next((chat for chat in (user_chats or []) if chat.get("title") == current_chat_title), None)
        if current_chat and current_chat.get("coding_files"):
            coding_files_str = "\n\n=== PERSISTENT PROJECT FILES ===\n"
            for fname, data in current_chat["coding_files"].items():
                coding_files_str += f"\n--- {fname} ---\n{data['content']}\n"

        full_task = f"User request: {user_input}\n\nProject files:{coding_files_str}"

        # === STEP 1: RESEARCHER ===
        history[-1]["content"] = "πŸ” Researcher is searching StackOverflow, Reddit, GitHub..."
        yield history

        research_results = web_search(f"best solution {full_task} site:stackoverflow.com OR site:reddit.com/r/learnpython OR site:github.com", max_results=15)

        # === STEP 2: CODER ===
        history[-1]["content"] += "\nπŸ€– Coder is writing the code..."
        yield history

        coder_prompt = f"""You are an expert senior software engineer.

Project files:
{coding_files_str}

Research findings:
{"\n\n".join(research_results[:8]) if research_results else "No external research available."}

User request: {user_input}

Write clean, well-commented, production-ready code."""

        coder_response = client.chat.completions.create(
            model=current_model,
            messages=system_context + [{"role": "user", "content": coder_prompt}],
            temperature=0.3,
            max_tokens=4096,
            stream=False
        ).choices[0].message.content

        history[-1]["content"] += f"\n\nπŸ€– **Coder**:\n\n{coder_response}"
        yield history

        # === STEP 3: REVIEWER ===
        history[-1]["content"] += "\n🧠 Cerebras Reviewer is doing deep analysis..."
        yield history

        reviewer_prompt = f"""You are an extremely strict senior code reviewer.

Code to review:
{coder_response[:3000]}

User request:
{user_input[:500]}

Review strictly:
- Bugs / logic errors
- Security risks
- Performance issues
- Bad practices
- Suggest fixes
Then give the FINAL polished code.

If good, say "βœ… Looks solid" and return it unchanged."""

        try:
            cerebras_response = cerebras_client.chat.completions.create(
                messages=system_context + [{"role": "user", "content": reviewer_prompt}],
                model="llama3.1-70b",
                stream=False,
                temperature=0.3,
                max_tokens=8192
            ).choices[0].message.content or "βœ… Reviewer found no major issues."
        except Exception as e:
            cerebras_response = f"Reviewer failed: {str(e)[:200]}\n\nOriginal code kept."

        history[-1]["content"] += f"\n\n🧠 **Cerebras Reviewer**:\n\n{cerebras_response}"
        yield history

        # Final output
        final_text = f"\n\nβœ… **Final Polished Code**\n\n{cerebras_response}\n\n**Original version:**\n{coder_response}"
        history[-1]["content"] += final_text
        yield history

    elif mode == "Deep Think":
        history[-1]["content"] = "🧠 Deep Think mode active β€” searching and reasoning..."
        yield history

        # AI1 (NVIDIA): Generate queries
        queries = generate_queries(client, current_model, message, today_date)

        # Perform searches in parallel
        search_results = []
        for q in queries:
            search_results.extend(web_search(q, max_results=20))

        search_context = "\n\n".join(search_results[:15])  # Limit to top results

        history[-1]["content"] += f"\n\nπŸ” Searches done on: {', '.join(queries)}"
        yield history

        # AI2 (Qwen via Groq): Initial reasoning
        initial_reasoning = qwen_reasoning(message, search_context, context)

        history[-1]["content"] += f"\n\nπŸ€” Initial Analysis:\n{initial_reasoning}"
        yield history

        # AI1 (Cerebras): Review and refine the reasoning
        review_prompt = f"""Review this initial analysis strictly for accuracy, completeness, and adherence to data:

Initial Analysis:
{initial_reasoning}

Search Data:
{search_context[:2000]}

User Query: {message}

Refine it: Add missing details, correct errors, ensure no hallucinations. Provide final polished response."""

        try:
            refined_response = cerebras_client.chat.completions.create(
                messages=system_context + [{"role": "user", "content": review_prompt}],
                model="llama3.1-70b",
                stream=False,
                temperature=0.4,
                max_tokens=2000
            ).choices[0].message.content
        except Exception as e:
            refined_response = f"Refinement failed: {str(e)[:200]}\n\nUsing initial reasoning."

        history[-1]["content"] += f"\n\n🧠 Refined Final Response:\n{refined_response}\n\nSources:\n{format_sources(search_results)}"
        yield history

    elif mode == "Fast Search":
        history[-1]["content"] = "⚑ Fast Search mode β€” quick web lookup..."
        yield history

        # Single AI (NVIDIA): Enhance query and search
        enhanced_query = enhance_query(message)
        search_results = web_search(enhanced_query, max_results=10)
        search_context = "\n\n".join(search_results)

        # Generate response using one AI
        prompt = f"""Summarize key facts from this search data for: "{message}"

Data:
{search_context}

Be concise, use bullets."""

        response = client.chat.completions.create(
            model=current_model,
            messages=system_context + [{"role": "user", "content": prompt}],
            temperature=0.5,
            max_tokens=1000,
            stream=False
        ).choices[0].message.content

        history[-1]["content"] += f"\n\n{response}\n\nSources:\n{format_sources(search_results, max_sources=5)}"
        yield history

    else:  # Default Chat
        # Single AI (NVIDIA): Normal conversation
        full_prompt = "\n".join([msg["content"] for msg in history[-5:]])  # Last 5 messages for context

        response = client.chat.completions.create(
            model=current_model,
            messages=system_context + [{"role": "user", "content": full_prompt}],
            temperature=0.7,
            max_tokens=1500,
            stream=False
        ).choices[0].message.content

        history[-1]["content"] = response
        yield history

    # Auto-save (common for all modes)
    if logged_in_username and user_chats:
        for chat in user_chats:
            if chat["title"] == current_chat_title:
                chat["history"] = history
                break
        save_chats(logged_in_username, user_chats, auth_globals)