| import os |
| import re |
| import tempfile |
| import time |
|
|
| import gradio as gr |
| import numpy as np |
| import pandas as pd |
| from duckduckgo_search import DDGS |
| from google import genai |
| from google.genai import types |
|
|
| |
| glassy_css = """ |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
| |
| *, *::before, *::after { box-sizing: border-box; } |
| |
| body, html { |
| background: linear-gradient(135deg, #0a0f1a 0%, #111827 40%, #1a2332 100%) !important; |
| background-attachment: fixed; |
| color: #e0e0e0 !important; |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; |
| } |
| |
| .gradio-container { |
| background: transparent !important; |
| max-width: 1500px !important; |
| margin: 0 auto !important; |
| padding: 12px !important; |
| } |
| |
| /* ===== RESPONSIVE STACKING ===== */ |
| @media (max-width: 768px) { |
| .gradio-container { padding: 6px !important; } |
| .main-row { flex-direction: column !important; } |
| .main-row > .gr-column { min-width: 100% !important; max-width: 100% !important; } |
| .sidebar-col { display: none !important; } |
| h1 { font-size: 1.4rem !important; } |
| h3 { font-size: 1rem !important; } |
| } |
| @media (min-width: 769px) and (max-width: 1024px) { |
| .main-row { flex-wrap: wrap !important; } |
| .main-row > .gr-column { min-width: 48% !important; } |
| .sidebar-col { min-width: 100% !important; } |
| } |
| |
| /* ===== GLASS PANELS ===== */ |
| div[class*="panel"] { |
| background: rgba(255, 255, 255, 0.03) !important; |
| border: 1px solid rgba(255, 255, 255, 0.08) !important; |
| backdrop-filter: blur(20px) !important; |
| -webkit-backdrop-filter: blur(20px) !important; |
| border-radius: 16px !important; |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4) !important; |
| padding: 16px !important; |
| } |
| |
| /* ===== SIDEBAR ===== */ |
| .sidebar-col { border-right: 1px solid rgba(255,255,255,0.06) !important; } |
| .sidebar-col .gr-accordion { margin-bottom: 8px !important; } |
| |
| /* ===== INPUTS ===== */ |
| textarea, input[type="text"], input[type="password"] { |
| background: rgba(0, 0, 0, 0.3) !important; |
| border: 1px solid rgba(255, 255, 255, 0.12) !important; |
| color: #fff !important; |
| border-radius: 10px !important; |
| transition: border-color 0.2s ease !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| textarea:focus, input:focus { |
| border-color: rgba(0, 200, 150, 0.5) !important; |
| box-shadow: 0 0 12px rgba(0, 200, 150, 0.15) !important; |
| } |
| |
| /* ===== PRIMARY BUTTON ===== */ |
| button.primary { |
| background: linear-gradient(135deg, #00c896 0%, #00b4d8 100%) !important; |
| border: none !important; |
| color: #fff !important; |
| font-weight: 600 !important; |
| border-radius: 10px !important; |
| padding: 10px 20px !important; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; |
| box-shadow: 0 4px 15px rgba(0, 200, 150, 0.3) !important; |
| } |
| button.primary:hover { |
| transform: translateY(-2px) !important; |
| box-shadow: 0 6px 20px rgba(0, 200, 150, 0.5) !important; |
| } |
| |
| /* ===== SECONDARY BUTTON ===== */ |
| button.secondary { |
| background: rgba(255,255,255,0.06) !important; |
| border: 1px solid rgba(255,255,255,0.15) !important; |
| color: #c0c0c0 !important; |
| border-radius: 8px !important; |
| transition: all 0.2s ease !important; |
| } |
| button.secondary:hover { |
| background: rgba(255,255,255,0.12) !important; |
| color: #fff !important; |
| } |
| |
| /* ===== TYPOGRAPHY ===== */ |
| h1 { |
| color: #ffffff !important; |
| font-weight: 700 !important; |
| letter-spacing: -0.5px !important; |
| background: linear-gradient(135deg, #00c896, #00b4d8) !important; |
| -webkit-background-clip: text !important; |
| -webkit-text-fill-color: transparent !important; |
| background-clip: text !important; |
| } |
| h2, h3, h4 { color: #e8e8e8 !important; font-weight: 600 !important; } |
| p, span, label { color: #c0c0c0 !important; } |
| |
| /* ===== SURVEYED LINKS ===== */ |
| .surveyed-links a { |
| color: #60efff !important; |
| text-decoration: underline !important; |
| word-break: break-all !important; |
| } |
| .surveyed-links p { margin-bottom: 8px !important; line-height: 1.6 !important; } |
| |
| /* ===== GALLERY ===== */ |
| .viz-gallery { min-height: 200px; } |
| .viz-gallery .gallery-item img { |
| border-radius: 12px !important; |
| border: 1px solid rgba(255,255,255,0.08) !important; |
| cursor: pointer !important; |
| } |
| |
| /* ===== ACCORDION ===== */ |
| .gr-accordion { border-radius: 12px !important; overflow: hidden !important; } |
| |
| /* ===== SCROLLABLE MARKDOWN ===== */ |
| .report-body { |
| max-height: 70vh; |
| overflow-y: auto; |
| padding-right: 8px; |
| } |
| .report-body::-webkit-scrollbar { width: 6px; } |
| .report-body::-webkit-scrollbar-thumb { |
| background: rgba(255,255,255,0.15); |
| border-radius: 3px; |
| } |
| """ |
|
|
| |
| QUICK_MODE = "Quick Research (Direct)" |
| DEEP_MODE = "Deep Research & Debate" |
| DEBATE_SKIPPED = "*Debate skipped for Quick mode.*" |
| VIZ_DIR = tempfile.mkdtemp(prefix="research_viz_") |
|
|
| GEMINI_MODELS = [ |
| "gemini-2.5-flash", |
| "gemini-flash-latest", |
| "gemini-flash-lite-latest", |
| "gemini-2.5-flash-lite", |
| "gemini-2.0-flash", |
| ] |
|
|
| |
|
|
|
|
| def make_safe(text): |
| """ |
| STRICT SANITIZATION: Strips out ALL emojis and non-standard characters. |
| This guarantees that underlying network libraries on Windows will NEVER |
| crash with a 'UnicodeEncodeError'. |
| """ |
| if not text: |
| return "" |
| return str(text).encode("ascii", "ignore").decode("ascii") |
|
|
|
|
| def search_web( |
| api_key, query, time_limit, primary_model=GEMINI_MODELS[0], max_results=3 |
| ): |
| """Hybrid Grounding Engine: Tries Native Google Search first, falls back to DuckDuckGo.""" |
|
|
| |
| safe_query = make_safe(query) |
|
|
| |
| try: |
| client = genai.Client(api_key=api_key) |
| time_context = ( |
| f" Focus specifically on recent information from the {time_limit.lower()}." |
| if time_limit != "All time" |
| else "" |
| ) |
| prompt = f"Conduct detailed, objective research on the following query: '{safe_query}'.{time_context} Provide comprehensive facts and statistics." |
|
|
| |
| safe_prompt = make_safe(prompt) |
|
|
| config = types.GenerateContentConfig( |
| tools=[{"google_search": {}}], temperature=0.2 |
| ) |
|
|
| response = client.models.generate_content( |
| model=primary_model, contents=safe_prompt, config=config |
| ) |
|
|
| urls = [] |
| if response.candidates and response.candidates[0].grounding_metadata: |
| gm = response.candidates[0].grounding_metadata |
| chunks = getattr(gm, "grounding_chunks", []) |
| for chunk in chunks: |
| web = getattr(chunk, "web", None) |
| if web: |
| uri = getattr(web, "uri", None) |
| title = getattr(web, "title", "Source") |
| if uri: |
| urls.append(f"π **[{title}]({uri})**\n> {uri}") |
|
|
| unique_urls = list(dict.fromkeys(urls)) |
| if unique_urls: |
| |
| return make_safe(response.text), "\n\n".join(unique_urls) |
|
|
| except Exception as e: |
| print(f"Native Grounding Info (Falling back to DDG): {e}") |
|
|
| |
| try: |
| ddgs = DDGS() |
| timelimit_map = { |
| "Today": "d", |
| "Past week": "w", |
| "Past month": "m", |
| "Past year": "y", |
| "All time": None, |
| } |
| t = timelimit_map.get(time_limit) |
| results = list(ddgs.text(safe_query, timelimit=t, max_results=max_results)) |
|
|
| extracted = [] |
| urls = [] |
| for r in results: |
| title = make_safe(r.get("title", "Untitled")) |
| href = r.get("href", "") |
| body = make_safe(r.get("body", "")) |
|
|
| if href and href.startswith("http"): |
| urls.append(f"π **[{title}]({href})**\n> {href}") |
| extracted.append(f"Title: {title}\nLink: {href}\nSnippet: {body}") |
|
|
| url_text = "\n\n".join(urls) if urls else "" |
| data_text = "\n\n".join(extracted) if extracted else "" |
| return data_text, url_text |
| except Exception as e: |
| return "", f"β οΈ Search error: {e}" |
|
|
|
|
| def call_gemini(api_key, prompt, primary_model=GEMINI_MODELS[0], retries=2): |
| """Standard LLM execution with strict sanitization to prevent Windows encoding errors.""" |
| client = genai.Client(api_key=api_key) |
| models_to_try = [primary_model] + [m for m in GEMINI_MODELS if m != primary_model] |
|
|
| |
| safe_prompt = make_safe(prompt) |
|
|
| last_error = None |
| for model in models_to_try: |
| for attempt in range(retries): |
| try: |
| response = client.models.generate_content( |
| model=model, contents=safe_prompt |
| ) |
| return response.text |
| except Exception as e: |
| last_error = str(e) |
| if "429" in last_error or "quota" in last_error.lower(): |
| break |
| if attempt < retries - 1: |
| time.sleep(2 * (attempt + 1)) |
| continue |
| break |
| return f"β οΈ Error connecting to Gemini API. Details: {last_error}" |
|
|
|
|
| def execute_chart_code(code_str, output_filename="chart.png"): |
| match = re.search(r"```python(.*?)```", code_str, re.DOTALL) |
| if match: |
| code_str = match.group(1).strip() |
| code_str = re.sub( |
| r"plt\.savefig\(['\"].*?['\"]", f"plt.savefig('{output_filename}'", code_str |
| ) |
| safe_code = ( |
| "import matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\n" |
| + code_str |
| ) |
| namespace = {"pd": pd, "np": np} |
| try: |
| exec(safe_code, namespace) |
| if os.path.exists(output_filename): |
| return output_filename |
| except Exception: |
| pass |
| return None |
|
|
|
|
| def generate_visualizations( |
| api_key, topic, research_data, num_charts=1, primary_model=GEMINI_MODELS[0] |
| ): |
| chart_types = [ |
| ("statistical chart (bar, pie, line, or scatter)", "viz_chart"), |
| ("comparison table as an image using matplotlib", "viz_table"), |
| ("flowchart or process diagram using matplotlib", "viz_flow"), |
| ] |
| results = [] |
| for i in range(min(num_charts, 3)): |
| chart_desc, prefix = chart_types[i] |
| out_path = os.path.join(VIZ_DIR, f"{prefix}_{int(time.time())}_{i}.png") |
| chart_prompt = f"""Write a Python script using matplotlib to create a {chart_desc} based on: '{topic}'. |
| Research context: {research_data[:1500]} |
| 1. Import matplotlib.pyplot as plt |
| 2. Apply a dark theme using plt.style.use('dark_background') |
| 3. MUST save the figure as '{out_path}' using plt.savefig('{out_path}', bbox_inches='tight', dpi=150) |
| 4. Output ONLY valid python code inside ```python ``` blocks.""" |
| code_response = call_gemini(api_key, chart_prompt, primary_model=primary_model) |
| chart_path = execute_chart_code(code_response, output_filename=out_path) |
| if chart_path: |
| results.append(chart_path) |
| return results |
|
|
|
|
| def generate_custom_viz(api_key, viz_prompt, primary_model=GEMINI_MODELS[0]): |
| """Generate a standalone custom visualization from sidebar prompt.""" |
| if not api_key or not viz_prompt: |
| return [] |
|
|
| out_path = os.path.join(VIZ_DIR, f"custom_{int(time.time())}.png") |
| chart_prompt = f"""Write a Python script using matplotlib to create a visualization for: '{viz_prompt}'. |
| 1. Import matplotlib.pyplot as plt |
| 2. Apply a dark theme using plt.style.use('dark_background') |
| 3. Make it visually clear and professional. |
| 4. MUST save the figure as '{out_path}' using plt.savefig('{out_path}', bbox_inches='tight', dpi=150) |
| 5. Output ONLY valid python code inside ```python ``` blocks. No explanations.""" |
|
|
| code_response = call_gemini(api_key, chart_prompt, primary_model=primary_model) |
| chart_path = execute_chart_code(code_response, output_filename=out_path) |
| if chart_path: |
| return [chart_path] |
| return [] |
|
|
|
|
| def export_report(final_text, surveyed_urls, debate_text): |
| if not final_text or final_text.startswith("*The final"): |
| return None |
| report = f"# Research Report\n\n## Final Intelligence Report\n\n{final_text}\n\n\n\n## Surveyed Resources\n\n{surveyed_urls}\n\n\n\n## Debate Transcript\n\n{debate_text}\n" |
| out_path = os.path.join(VIZ_DIR, f"report_{int(time.time())}.md") |
| with open(out_path, "w", encoding="utf-8") as f: |
| f.write(report) |
| return out_path |
|
|
|
|
| def clear_outputs(): |
| return ( |
| "", |
| "*Web URLs will appear here...*", |
| "*Debate transcript will stream here...*", |
| "*The final synthesis will appear here...*", |
| [], |
| None, |
| ) |
|
|
|
|
| |
|
|
|
|
| def orchestrate_agents( |
| topic, mode, time_limit, num_viz, api_key, primary_model, history |
| ): |
| if not api_key: |
| yield ( |
| "β Error: Please provide a Gemini API Key in the sidebar.", |
| "No sites", |
| "No debate", |
| "Error", |
| [], |
| history, |
| gr.update(), |
| "Error", |
| ) |
| return |
| if not topic.strip(): |
| yield ( |
| "β Error: Please enter a research topic.", |
| "", |
| "", |
| "", |
| [], |
| history, |
| gr.update(), |
| "Error", |
| ) |
| return |
|
|
| log, live_debate = [], "" |
|
|
| def update_log(msg): |
| log.append(f"β
{msg}") |
| return "\n".join(log) |
|
|
| |
| actual_mode = mode |
| if mode == "Auto": |
| yield ( |
| update_log("Auto-Routing: Deciding research depth..."), |
| "", |
| "", |
| "Analyzing topic complexity...", |
| [], |
| history, |
| gr.update(), |
| "π Routing...", |
| ) |
| decision = ( |
| call_gemini( |
| api_key, |
| f"Analyze: '{topic}'. Quick factual question or complex deep research? Reply 'Quick' or 'Deep'.", |
| primary_model=primary_model, |
| ) |
| .strip() |
| .lower() |
| ) |
| actual_mode = QUICK_MODE if "quick" in decision else DEEP_MODE |
| yield ( |
| update_log(f"Auto-Routing decided: {actual_mode}"), |
| "", |
| "", |
| "Routing chosen...", |
| [], |
| history, |
| gr.update(), |
| f"Mode: {actual_mode}", |
| ) |
|
|
| |
| yield ( |
| update_log("Agents brainstorming search strategies..."), |
| "π‘ Generating queries...", |
| "", |
| "Optimizing intents...", |
| [], |
| history, |
| gr.update(), |
| "π§ Thinking...", |
| ) |
| queries_raw = ( |
| call_gemini( |
| api_key, |
| f"Topic: '{topic}'. Generate exactly 2 highly effective search queries. Return ONLY queries, one per line.", |
| primary_model=primary_model, |
| ) |
| .strip() |
| .split("\n") |
| ) |
| search_queries = [ |
| q.strip(' "-*') for q in queries_raw if q.strip() and "Error" not in q |
| ][:2] or [topic] |
|
|
| yield ( |
| update_log("Triggering Google AI Search Grounding..."), |
| "π Extracting context...", |
| "", |
| "Gathering grounded data...", |
| [], |
| history, |
| gr.update(), |
| "π Grounding...", |
| ) |
|
|
| all_broad_data, all_surveyed_urls = "", "" |
| for q in search_queries: |
| b_data, s_urls = search_web( |
| api_key, q, time_limit, primary_model, max_results=3 |
| ) |
| if b_data: |
| all_broad_data += f"\n\nSource [{q}]:\n" + b_data |
| if s_urls and "β οΈ" not in s_urls: |
| all_surveyed_urls += s_urls + "\n\n" |
|
|
| all_surveyed_urls = all_surveyed_urls.strip() or "β οΈ No valid links retrieved." |
| yield ( |
| update_log("Grounding complete."), |
| all_surveyed_urls, |
| "", |
| "Synthesizing...", |
| [], |
| history, |
| gr.update(), |
| "π Analyzing...", |
| ) |
|
|
| gallery_images, final_answer = [], "" |
|
|
| |
| if actual_mode == QUICK_MODE: |
| yield ( |
| update_log("Executing Quick Direct Answer..."), |
| all_surveyed_urls, |
| DEBATE_SKIPPED, |
| "Drafting final answer...", |
| [], |
| history, |
| gr.update(), |
| "βοΈ Writing...", |
| ) |
| prompt = f"You are a pragmatic expert. Based on this grounded data: {all_broad_data}. Answer: '{topic}'. Tone: Layman, simple. Provide verified resources." |
| final_answer = call_gemini(api_key, prompt, primary_model=primary_model) |
| else: |
| yield ( |
| update_log("Deep Research: Agent 1 analyzing..."), |
| all_surveyed_urls, |
| live_debate, |
| "Analyzing...", |
| [], |
| history, |
| gr.update(), |
| "π¬ Agent 1...", |
| ) |
| ra1_findings = call_gemini( |
| api_key, |
| f"Analyze raw data for '{topic}': {all_broad_data}. Extract core facts.", |
| primary_model=primary_model, |
| ) |
|
|
| yield ( |
| update_log("Deep Research: Agent 2 cross-referencing..."), |
| all_surveyed_urls, |
| live_debate, |
| "Cross-referencing...", |
| [], |
| history, |
| gr.update(), |
| "π Agent 2...", |
| ) |
| deep_data, deep_urls = search_web( |
| api_key, |
| f"{topic} critical analysis", |
| time_limit, |
| primary_model, |
| max_results=2, |
| ) |
| if deep_urls and "β οΈ" not in deep_urls: |
| all_surveyed_urls += "\n\n\n\n**Deep Search Results:**\n\n" + deep_urls |
| master_research = call_gemini( |
| api_key, |
| f"Review Agent 1: {ra1_findings}. Cross-reference with: {deep_data}. Output verified master summary.", |
| primary_model=primary_model, |
| ) |
|
|
| tone = "Tone: Use simple, layman terms. Be rational and constructive." |
| yield ( |
| update_log("Debate Round 1..."), |
| all_surveyed_urls, |
| live_debate, |
| "Debating...", |
| [], |
| history, |
| gr.update(), |
| "βοΈ Debate R1...", |
| ) |
| da1_r1 = call_gemini( |
| api_key, |
| f"Debate AI 1: Propose an answer to '{topic}' using: {master_research}. Under 100 words. {tone}", |
| primary_model=primary_model, |
| ) |
| live_debate += f"**π€ AI 1 (Proposal):**\n{da1_r1}\n\n" |
| da2_r1 = call_gemini( |
| api_key, |
| f"Debate AI 2: Review AI 1's draft: {da1_r1}. Point out missing context. Under 100 words. {tone}", |
| primary_model=primary_model, |
| ) |
| live_debate += f"**π§ AI 2 (Critique):**\n{da2_r1}\n\n" |
|
|
| yield ( |
| update_log("Debate Round 2..."), |
| all_surveyed_urls, |
| live_debate, |
| "Debating...", |
| [], |
| history, |
| gr.update(), |
| "βοΈ Debate R2...", |
| ) |
| da1_r2 = call_gemini( |
| api_key, |
| f"Debate AI 1: Refine based on AI 2's review: {da2_r1}. Under 100 words. {tone}", |
| primary_model=primary_model, |
| ) |
| live_debate += f"**π€ AI 1 (Refinement):**\n{da1_r2}\n\n" |
| da2_r2 = call_gemini( |
| api_key, |
| f"Debate AI 2: Final check on AI 1's revision: {da1_r2}. Under 100 words. {tone}", |
| primary_model=primary_model, |
| ) |
| live_debate += f"**π§ AI 2 (Final Check):**\n{da2_r2}\n\n" |
|
|
| yield ( |
| update_log("Master Orchestrator drafting output..."), |
| all_surveyed_urls, |
| live_debate, |
| "Drafting Final Report...", |
| [], |
| history, |
| gr.update(), |
| "π Synthesizing...", |
| ) |
| final_prompt = f"""You are the Final Orchestrator. Review this debate for topic '{topic}': |
| AI 1: {da1_r2} |
| AI 2: {da2_r2} |
| |
| Create the final intelligence report. |
| RULES: |
| 1. Tone: Simple, layman-friendly. Use examples and analogies. |
| 2. Formatting: Beautiful Markdown (headers, bullet points, tables if applicable). |
| 3. End with '### π Verified Resources' with clickable markdown links.""" |
| final_answer = call_gemini(api_key, final_prompt, primary_model=primary_model) |
|
|
| debate_display = live_debate if actual_mode != QUICK_MODE else DEBATE_SKIPPED |
| yield ( |
| update_log("Final text generated."), |
| all_surveyed_urls, |
| debate_display, |
| final_answer, |
| [], |
| history, |
| gr.update(), |
| "β
Report ready", |
| ) |
|
|
| |
| if num_viz > 0: |
| yield ( |
| update_log(f"Generating {num_viz} visualization(s)..."), |
| all_surveyed_urls, |
| debate_display, |
| final_answer, |
| [], |
| history, |
| gr.update(), |
| "π Generating charts...", |
| ) |
| gallery_images = generate_visualizations( |
| api_key, |
| topic, |
| all_broad_data, |
| num_charts=num_viz, |
| primary_model=primary_model, |
| ) |
| yield ( |
| update_log(f"{len(gallery_images)} visualization(s) generated!"), |
| all_surveyed_urls, |
| debate_display, |
| final_answer, |
| gallery_images, |
| history, |
| gr.update(), |
| "β
Charts ready", |
| ) |
|
|
| |
| yield ( |
| update_log("All Operations Completed Successfully!"), |
| all_surveyed_urls, |
| debate_display, |
| final_answer, |
| gallery_images, |
| history, |
| gr.update(), |
| "β
Done!", |
| ) |
|
|
| history.append( |
| { |
| "topic": topic, |
| "log": "\n".join(log), |
| "urls": all_surveyed_urls, |
| "debate": debate_display, |
| "final": final_answer, |
| "charts": gallery_images, |
| } |
| ) |
| yield ( |
| "\n".join(log), |
| all_surveyed_urls, |
| debate_display, |
| final_answer, |
| gallery_images, |
| history, |
| gr.update(choices=[h["topic"] for h in history]), |
| "β
Done!", |
| ) |
|
|
|
|
| def load_from_history(selected_topic, history): |
| for item in history: |
| if item["topic"] == selected_topic: |
| return ( |
| item["log"], |
| item["urls"], |
| item["debate"], |
| item["final"], |
| item.get("charts", []), |
| ) |
| return "", "", "", "No history found.", [] |
|
|
|
|
| |
| with gr.Blocks(title="AI Research Hub") as app: |
| history_state = gr.State([]) |
|
|
| gr.Markdown("# π Multi-Agent Research Hub") |
| gr.Markdown( |
| "*Native Google AI Grounding Β· Auto-Routing Β· Live Debates Β· Multi-Viz Analytics*" |
| ) |
|
|
| with gr.Row(elem_classes=["main-row"]): |
| with gr.Column(scale=1, min_width=220, elem_classes=["sidebar-col"]): |
| gr.Markdown("### π§ Sidebar") |
| with gr.Accordion("π API Key", open=True): |
| api_key = gr.Textbox( |
| label="Gemini API Key", |
| type="password", |
| placeholder="AIzaSy...", |
| show_label=False, |
| ) |
| with gr.Accordion("π Quick Actions", open=True): |
| export_btn = gr.Button( |
| "π₯ Export Report", variant="secondary", size="sm" |
| ) |
| export_file = gr.File(label="Download", visible=True, interactive=False) |
| clear_btn = gr.Button("ποΈ Clear Outputs", variant="secondary", size="sm") |
|
|
| with gr.Accordion("π¨ Custom Visualization", open=False): |
| custom_viz_prompt = gr.Textbox( |
| label="Describe your chart", |
| placeholder="e.g. Pie chart of global energy sources", |
| lines=2, |
| ) |
| custom_viz_btn = gr.Button("π Generate", variant="primary", size="sm") |
| custom_viz_gallery = gr.Gallery( |
| label="Custom Charts", |
| columns=1, |
| height=200, |
| object_fit="contain", |
| interactive=False, |
| ) |
|
|
| with gr.Accordion("π°οΈ History", open=False): |
| history_dropdown = gr.Dropdown(label="Past Queries", choices=[]) |
| load_history_btn = gr.Button("π Load", variant="secondary", size="sm") |
|
|
| with gr.Column(scale=5, min_width=400): |
| with gr.Row(): |
| topic = gr.Textbox( |
| label="π Research Topic", |
| placeholder="Enter any topic to research...", |
| lines=2, |
| scale=3, |
| ) |
| with gr.Column(scale=1, min_width=180): |
| model_select = gr.Dropdown( |
| choices=GEMINI_MODELS, |
| value=GEMINI_MODELS[0], |
| label="π€ Primary Model", |
| ) |
| mode = gr.Radio( |
| ["Auto", QUICK_MODE, DEEP_MODE], value="Auto", label="π§ Mode" |
| ) |
|
|
| with gr.Row(): |
| time_limit = gr.Dropdown( |
| ["All time", "Past year", "Past month", "Past week", "Today"], |
| value="All time", |
| label="π
Time Cutoff", |
| scale=1, |
| ) |
| num_viz = gr.Slider( |
| minimum=0, |
| maximum=3, |
| step=1, |
| value=1, |
| label="π Visualizations", |
| scale=1, |
| ) |
| submit_btn = gr.Button( |
| "π Start Research", variant="primary", size="lg", scale=1 |
| ) |
|
|
| status_bar = gr.Textbox( |
| show_label=False, |
| interactive=False, |
| lines=1, |
| placeholder="Ready to research...", |
| ) |
|
|
| with gr.Row(elem_classes=["main-row"]): |
| with gr.Column(scale=1, min_width=280): |
| with gr.Accordion("π€ Workflow Logs", open=True): |
| progress_box = gr.Textbox( |
| show_label=False, lines=8, interactive=False |
| ) |
| with gr.Column(scale=1, min_width=280): |
| with gr.Accordion("π Grounded Resources", open=True): |
| surveyed_sites = gr.Markdown( |
| "*Web URLs will appear here...*", |
| elem_classes=["surveyed-links"], |
| ) |
|
|
| with gr.Accordion("βοΈ Live AI Debate", open=False): |
| live_debate = gr.Markdown("*Debate transcript will stream here...*") |
|
|
| gr.Markdown("") |
| gr.Markdown("### π Final Intelligence Report") |
| final_output = gr.Markdown( |
| "*The final synthesis will appear here...*", |
| elem_classes=["report-body"], |
| ) |
|
|
| gr.Markdown("") |
| gr.Markdown("### π Data Visualizations") |
| viz_gallery = gr.Gallery( |
| label="Generated Visualizations", |
| columns=3, |
| height=350, |
| object_fit="contain", |
| interactive=False, |
| elem_classes=["viz-gallery"], |
| ) |
|
|
| submit_btn.click( |
| orchestrate_agents, |
| inputs=[topic, mode, time_limit, num_viz, api_key, model_select, history_state], |
| outputs=[ |
| progress_box, |
| surveyed_sites, |
| live_debate, |
| final_output, |
| viz_gallery, |
| history_state, |
| history_dropdown, |
| status_bar, |
| ], |
| ) |
| load_history_btn.click( |
| load_from_history, |
| inputs=[history_dropdown, history_state], |
| outputs=[progress_box, surveyed_sites, live_debate, final_output, viz_gallery], |
| ) |
| export_btn.click( |
| export_report, |
| inputs=[final_output, surveyed_sites, live_debate], |
| outputs=[export_file], |
| ) |
| clear_btn.click( |
| clear_outputs, |
| outputs=[ |
| progress_box, |
| surveyed_sites, |
| live_debate, |
| final_output, |
| viz_gallery, |
| export_file, |
| ], |
| ) |
|
|
| custom_viz_btn.click( |
| generate_custom_viz, |
| inputs=[api_key, custom_viz_prompt, model_select], |
| outputs=[custom_viz_gallery], |
| ) |
|
|
| if __name__ == "__main__": |
| app.launch(theme=gr.themes.Soft(), css=glassy_css) |
|
|