Spaces:
Sleeping
Sleeping
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,158 +0,0 @@
|
|
| 1 |
-
"""app.py — Compact Gradio + Mistral + BERTopic. Version 2.1.0 | 4 April 2026. ZERO for/while/if.
|
| 2 |
-
|
| 3 |
-
FUNCTIONS IN THIS FILE:
|
| 4 |
-
_latest_output() — Scans /tmp for newest rq4_* file → feeds download button
|
| 5 |
-
respond() — Core chat handler: takes message + history + file → yields agent response
|
| 6 |
-
gr.Blocks() — One-page UI: header + upload + chatbot + input + download
|
| 7 |
-
"""
|
| 8 |
-
import os
|
| 9 |
-
import glob
|
| 10 |
-
import gradio as gr
|
| 11 |
-
from langchain_mistralai import ChatMistralAI
|
| 12 |
-
from langgraph.prebuilt import create_react_agent
|
| 13 |
-
from langgraph.checkpoint.memory import MemorySaver
|
| 14 |
-
from agent import SYSTEM_PROMPT, get_local_tools
|
| 15 |
-
|
| 16 |
-
print(">>> app.py: imports complete")
|
| 17 |
-
|
| 18 |
-
# ═══════════════════════════════════════════════
|
| 19 |
-
# AGENT SETUP — Mistral brain + 5 BERTopic tools
|
| 20 |
-
# ═══════════════════════════════════════════════
|
| 21 |
-
llm = ChatMistralAI(model="mistral-small-latest", temperature=0, timeout=300)
|
| 22 |
-
tools = get_local_tools()
|
| 23 |
-
agent = create_react_agent(model=llm, tools=tools, prompt=SYSTEM_PROMPT, checkpointer=MemorySaver())
|
| 24 |
-
print(f">>> app.py: agent ready ({len(tools)} tools, Mistral Large)")
|
| 25 |
-
|
| 26 |
-
_msg_count = 0
|
| 27 |
-
_uploaded = {"path": ""}
|
| 28 |
-
|
| 29 |
-
# ═══════════════════════════════════════════════
|
| 30 |
-
# COMPACT HEADER — fits in ~60px
|
| 31 |
-
# ═══════════════════════════════════════════════
|
| 32 |
-
HEADER_HTML = """
|
| 33 |
-
<style>
|
| 34 |
-
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
|
| 35 |
-
.gradio-container {max-width: 900px !important; margin: 0 auto !important; font-family: 'DM Sans', sans-serif !important; padding: 8px !important;}
|
| 36 |
-
footer {display: none !important;}
|
| 37 |
-
</style>
|
| 38 |
-
<div style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%); border-radius: 10px; padding: 12px 20px; margin-bottom: 8px; color: white;">
|
| 39 |
-
<div style="display: flex; align-items: center; gap: 12px;">
|
| 40 |
-
<span style="font-size: 1.5em;">🔬</span>
|
| 41 |
-
<div>
|
| 42 |
-
<div style="font-size: 1.1em; font-weight: 700; color: #e0e0ff;">BERTopic Research Discovery Agent</div>
|
| 43 |
-
<div style="font-size: 0.7em; color: #94a3b8;">Mistral Large 🇫🇷 · BERTopic HDBSCAN · 384d Embeddings · 5 Nearest Centroid · 4 Plotly Charts</div>
|
| 44 |
-
</div>
|
| 45 |
-
</div>
|
| 46 |
-
</div>
|
| 47 |
-
"""
|
| 48 |
-
|
| 49 |
-
# ═══════════════════════════════════════════════
|
| 50 |
-
# FUNCTION 1: Find latest output for download
|
| 51 |
-
# ═══════════════════════════════════════════════
|
| 52 |
-
def _latest_output():
|
| 53 |
-
"""Scan /tmp for most recent rq4_* file (CSV, HTML, or TXT).
|
| 54 |
-
Returns filepath string or None. Fed to gr.File download component."""
|
| 55 |
-
files = sorted(glob.glob("/tmp/rq4_*.csv") + glob.glob("/tmp/rq4_*.html") + glob.glob("/tmp/rq4_*.txt"), key=os.path.getmtime)
|
| 56 |
-
return (files and files[-1]) or None
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
# ═══════════════════════════════════════════════
|
| 60 |
-
# FUNCTION 2: Chat handler — core of the app
|
| 61 |
-
# ═══════════════════════════════════════════════
|
| 62 |
-
def respond(message, chat_history, uploaded_file):
|
| 63 |
-
"""Handle one chat turn:
|
| 64 |
-
1. Store uploaded file path (if new upload)
|
| 65 |
-
2. Append file context to message so agent knows where CSV is
|
| 66 |
-
3. Show progress bubble immediately (user sees instant feedback)
|
| 67 |
-
4. Invoke agent (Mistral brain decides which tools to call)
|
| 68 |
-
5. Replace progress bubble with agent's actual response
|
| 69 |
-
6. Update download link to latest output file
|
| 70 |
-
|
| 71 |
-
Uses per-message thread_id to prevent context overflow. State persists via disk checkpoints.
|
| 72 |
-
Agent asks clarification FIRST (via SYSTEM_PROMPT) before running heavy tools."""
|
| 73 |
-
global _msg_count
|
| 74 |
-
_msg_count += 1
|
| 75 |
-
|
| 76 |
-
# Store file path — no if/else, uses `or` short-circuit
|
| 77 |
-
_uploaded["path"] = uploaded_file or _uploaded.get("path", "")
|
| 78 |
-
file_note = f"\n[CSV file at: {_uploaded['path']}]" * bool(_uploaded["path"])
|
| 79 |
-
text = ((message or "").strip() or "Analyze my Scopus CSV") + file_note
|
| 80 |
-
print(f"\n{'='*60}\n>>> MSG #{_msg_count}: '{text[:100]}'\n{'='*60}")
|
| 81 |
-
|
| 82 |
-
# Yield progress bubble immediately — user sees instant response
|
| 83 |
-
chat_history = chat_history + [
|
| 84 |
-
{"role": "user", "content": (message or "").strip()},
|
| 85 |
-
{"role": "assistant", "content": "🔬 **Working...** _Agent is thinking..._"},
|
| 86 |
-
]
|
| 87 |
-
yield chat_history, "", _latest_output()
|
| 88 |
-
|
| 89 |
-
# Per-message thread_id to prevent context window overflow.
|
| 90 |
-
# WHY: MemorySaver accumulates ALL previous messages + tool outputs.
|
| 91 |
-
# After 3 turns with 100 topic labels each → 300K tokens → exceeds 262K limit.
|
| 92 |
-
# Tools save state to DISK (labels.json, themes.json, taxonomy_map.json).
|
| 93 |
-
# Agent reads from disk checkpoints, NOT conversation memory.
|
| 94 |
-
# Multi-turn works because disk state persists across messages.
|
| 95 |
-
result = agent.invoke(
|
| 96 |
-
{"messages": [("human", text)]},
|
| 97 |
-
config={"configurable": {"thread_id": f"t{_msg_count}"}},
|
| 98 |
-
)
|
| 99 |
-
response = result["messages"][-1].content
|
| 100 |
-
print(f">>> Response ({len(response)} chars)")
|
| 101 |
-
|
| 102 |
-
# Replace progress bubble with actual response
|
| 103 |
-
chat_history[-1] = {"role": "assistant", "content": response}
|
| 104 |
-
yield chat_history, "", _latest_output()
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
# ═══════════════════════════════════════════════
|
| 108 |
-
# UI LAYOUT — Everything fits ONE screen (~500px)
|
| 109 |
-
#
|
| 110 |
-
# ┌────────────────────────────────────┐
|
| 111 |
-
# │ 🔬 BERTopic Agent (compact) │ ~55px
|
| 112 |
-
# ├────────────────────────────────────┤
|
| 113 |
-
# │ [📂 Upload CSV] │ ~35px
|
| 114 |
-
# ├────────────────────────────────────┤
|
| 115 |
-
# │ 💬 Chat bubbles (cloud style) │
|
| 116 |
-
# │ User: "Analyze my data" │ ~320px
|
| 117 |
-
# │ Agent: "I found 1390 papers. │
|
| 118 |
-
# │ Which config? All 3?" │
|
| 119 |
-
# ├────────────────────────────────────┤
|
| 120 |
-
# │ [Type message... ] [⏎] │ ~35px
|
| 121 |
-
# ├────────────────────────────────────┤
|
| 122 |
-
# │ 📥 Download │ ~35px
|
| 123 |
-
# └────────────────────────────────────┘
|
| 124 |
-
# TOTAL: ~480px — fits any screen
|
| 125 |
-
# ═══════════════════════════════════════════════
|
| 126 |
-
print(">>> Building UI...")
|
| 127 |
-
with gr.Blocks(title="BERTopic Research Discovery Agent") as demo:
|
| 128 |
-
gr.HTML(HEADER_HTML)
|
| 129 |
-
|
| 130 |
-
upload = gr.File(label="📂 Upload Scopus CSV", file_types=[".csv"], height=35)
|
| 131 |
-
|
| 132 |
-
chatbot = gr.Chatbot(height=300, show_label=False, placeholder="Upload CSV ↑ then click a quick action or type below")
|
| 133 |
-
|
| 134 |
-
with gr.Row():
|
| 135 |
-
msg = gr.Textbox(
|
| 136 |
-
placeholder="Or type: group 0 1 5 · re-run 15 · topic 2 looks wrong · approve",
|
| 137 |
-
show_label=False, scale=9, lines=1, max_lines=1, container=False)
|
| 138 |
-
send = gr.Button("⏎", variant="primary", scale=1, min_width=45)
|
| 139 |
-
|
| 140 |
-
gr.Examples(
|
| 141 |
-
examples=[
|
| 142 |
-
["Run abstract only"],
|
| 143 |
-
["Run all columns (title + keywords + abstract)"],
|
| 144 |
-
["Run both configs"],
|
| 145 |
-
["approve"],
|
| 146 |
-
["Generate narrative"],
|
| 147 |
-
],
|
| 148 |
-
inputs=msg,
|
| 149 |
-
label="⚡ Quick actions (click to populate textbox, then press ⏎)",
|
| 150 |
-
)
|
| 151 |
-
|
| 152 |
-
download = gr.File(label="📥 Download", visible=True, height=35)
|
| 153 |
-
|
| 154 |
-
msg.submit(respond, [msg, chatbot, upload], [chatbot, msg, download])
|
| 155 |
-
send.click(respond, [msg, chatbot, upload], [chatbot, msg, download])
|
| 156 |
-
|
| 157 |
-
print(">>> Launching...")
|
| 158 |
-
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|