import streamlit as st
import json
import os
import sys
import traceback
import logging
import time
from datetime import datetime
# =====================================================
# PATH SETUP
# =====================================================
sys.path.append(os.getcwd())
# =====================================================
# LOGGING CONFIG
# =====================================================
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, LOG_LEVEL, logging.INFO),
format="%(asctime)s | %(levelname)s | %(message)s",
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger("TLC_AGENT")
print("\n")
print("=" * 80)
print("🚀 TLC AGENT SCIENTIFIQUE - STARTUP")
print("=" * 80)
print(f"📍 Working directory : {os.getcwd()}")
print(f"📍 Python version : {sys.version}")
print(f"📍 Time : {datetime.now()}")
print("=" * 80)
print("\n")
# =====================================================
# SAFE IMPORTS
# =====================================================
try:
print("📦 Import extraction.py ...")
from extraction import extract_from_file, extract_from_text
print("✅ extraction.py loaded")
except Exception as e:
print("❌ FAILED loading extraction.py")
print(traceback.format_exc())
raise e
try:
print("📦 Import ir_builder.py ...")
from ir_builder import build_ir_variants
print("✅ ir_builder.py loaded")
except Exception as e:
print("❌ FAILED loading ir_builder.py")
print(traceback.format_exc())
raise e
try:
print("📦 Import pattern_detector.py ...")
from pattern_detector import detect_patterns
print("✅ pattern_detector.py loaded")
except Exception as e:
print("❌ FAILED loading pattern_detector.py")
print(traceback.format_exc())
raise e
try:
print("📦 Import optimizer.py ...")
from optimizer import optimize_ir
print("✅ optimizer.py loaded")
except Exception as e:
print("❌ FAILED loading optimizer.py")
print(traceback.format_exc())
raise e
# =====================================================
# PAGE CONFIG
# =====================================================
st.set_page_config(
page_title="TLC Agent Scientifique",
page_icon="🧠",
layout="wide"
)
# =====================================================
# CUSTOM CSS
# =====================================================
st.markdown("""
""", unsafe_allow_html=True)
# =====================================================
# TITLE
# =====================================================
st.title("🧠 TLC Agent – Cercle Scientifique")
st.caption("Extraction → IR → Patterns → Optimisation")
# =====================================================
# DEBUG HELPERS
# =====================================================
def debug_log(message, level="INFO"):
timestamp = datetime.now().strftime("%H:%M:%S")
formatted = f"[{timestamp}] [{level}] {message}"
print(formatted)
if "debug_logs" not in st.session_state:
st.session_state.debug_logs = []
st.session_state.debug_logs.append(formatted)
if level == "ERROR":
logger.error(message)
elif level == "WARNING":
logger.warning(message)
else:
logger.info(message)
def debug_exception(e):
err = traceback.format_exc()
print("\n")
print("=" * 80)
print("❌ EXCEPTION")
print("=" * 80)
print(err)
print("=" * 80)
print("\n")
logger.exception(str(e))
return err
def timed_call(label, fn, *args, **kwargs):
debug_log(f"START => {label}")
start = time.time()
result = fn(*args, **kwargs)
duration = round(time.time() - start, 2)
debug_log(f"END => {label} ({duration}s)")
return result
def normalize_provider(provider: str) -> str:
"""
UI provider -> provider attendu par les modules internes.
OpenAI UI = Azure OpenAI interne.
"""
provider = (provider or "").lower().strip()
if provider == "openai":
return "azure"
return provider
# =====================================================
# SESSION STATE
# =====================================================
DEFAULT_STATE = {
"step": "input",
"equations": [],
"ir_variants": [],
"chosen_ir": None,
"patterns": [],
"optimization_variants": [],
"chosen_optimization": None,
"provider": "openai",
"debug_logs": [],
"debug_mode": True,
}
for key, value in DEFAULT_STATE.items():
if key not in st.session_state:
st.session_state[key] = value
# =====================================================
# START LOG
# =====================================================
debug_log(f"APP RERUN | CURRENT STEP = {st.session_state.step}")
# =====================================================
# RESET
# =====================================================
def reset():
debug_log("RESET SESSION")
for key, value in DEFAULT_STATE.items():
st.session_state[key] = value
# =====================================================
# SIDEBAR
# =====================================================
with st.sidebar:
st.header("📋 Pipeline")
st.info(f"Étape actuelle : {st.session_state.step}")
st.divider()
debug_mode = st.toggle("🐞 Debug mode", value=st.session_state.debug_mode)
st.session_state.debug_mode = debug_mode
st.divider()
if st.button("🔄 Nouvelle session", use_container_width=True):
debug_log("NEW SESSION CLICKED")
reset()
st.rerun()
st.divider()
st.markdown("""
### Workflow
1. Input scientifique
2. Extraction
3. Construction IR
4. Détection patterns
5. Optimisation
6. Export JSON
""")
st.divider()
st.subheader("🔑 API Keys")
deepseek_ok = bool(os.getenv("DEEPSEEK_API_KEY"))
groq_ok = bool(os.getenv("GROQ_API_KEY"))
openai_ok = all([
os.getenv("AZUREOPENAI_API_KEY"),
os.getenv("AZUREOPENAI_API_ENDPOINT"),
os.getenv("AZUREOPENAI_API_VERSION"),
os.getenv("OPENAI_MODEL"),
])
st.write(f"OpenAI : {'✅' if openai_ok else '❌'}")
st.write(f"DeepSeek : {'✅' if deepseek_ok else '❌'}")
st.write(f"Groq : {'✅' if groq_ok else '❌'}")
debug_log(
f"API STATUS => OPENAI={openai_ok} | DEEPSEEK={deepseek_ok} | GROQ={groq_ok}"
)
# =====================================================
# DEBUG PANEL
# =====================================================
if st.session_state.get("debug_mode", False):
with st.expander("🐞 Debug Console", expanded=False):
logs = "\n".join(st.session_state.debug_logs[-200:])
st.markdown(f"""
{logs}
""", unsafe_allow_html=True)
# =====================================================
# STEP 1
# =====================================================
if st.session_state.step == "input":
debug_log("ENTER STEP INPUT")
st.header("1. Fournir le contenu scientifique")
providers = ["openai", "deepseek", "groq"]
provider = st.selectbox(
"LLM Provider",
providers,
index=providers.index(st.session_state.provider)
)
st.session_state.provider = provider
effective_provider = normalize_provider(provider)
debug_log(f"PROVIDER SELECTED => {provider} (effective: {effective_provider})")
mode = st.radio(
"Mode d'entrée",
["Fichier (PDF/DOCX/TXT)", "Texte long"],
horizontal=True
)
debug_log(f"INPUT MODE => {mode}")
uploaded_file = None
content = ""
if mode == "Fichier (PDF/DOCX/TXT)":
uploaded_file = st.file_uploader(
"Choisir un fichier",
type=["pdf", "docx", "txt"]
)
st.caption("Formats acceptés : PDF, DOCX, TXT")
if uploaded_file is not None:
debug_log(f"FILE UPLOADED => {uploaded_file.name}")
debug_log(f"FILE SIZE => {uploaded_file.size} bytes")
else:
content = st.text_area(
"Texte scientifique",
height=350,
placeholder="""
Exemple :
$$
E = mc^2
$$
ou texte scientifique brut.
"""
)
debug_log(f"TEXT LENGTH => {len(content)}")
st.divider()
if st.button("🚀 Lancer l'extraction", type="primary"):
debug_log("EXTRACTION BUTTON CLICKED")
try:
equations = []
if uploaded_file is not None:
debug_log("START FILE EXTRACTION")
with st.spinner("📄 Extraction du fichier..."):
equations = timed_call(
"extract_from_file",
extract_from_file,
uploaded_file
)
debug_log(f"FILE EXTRACTION DONE => {len(equations)} equations")
elif content.strip():
debug_log("START TEXT EXTRACTION")
with st.spinner("🧠 Analyse scientifique..."):
equations = timed_call(
"extract_from_text",
extract_from_text,
content
)
debug_log(f"TEXT EXTRACTION DONE => {len(equations)} equations")
else:
debug_log("NO INPUT PROVIDED", level="WARNING")
st.warning("Veuillez fournir du contenu.")
st.stop()
debug_log(f"EQUATIONS TYPE => {type(equations)}")
if equations:
debug_log(f"FIRST EQUATION => {str(equations[0])[:300]}")
if len(equations) > 0:
st.session_state.equations = equations
st.success(f"✅ {len(equations)} équation(s) détectée(s)")
debug_log("GO TO STEP EXTRACTION")
st.session_state.step = "extraction"
st.rerun()
else:
debug_log("NO EQUATIONS DETECTED", level="ERROR")
st.error("❌ Aucune équation détectée.")
except Exception as e:
err = debug_exception(e)
st.error(str(e))
st.code(err)
# =====================================================
# STEP 2
# =====================================================
elif st.session_state.step == "extraction":
debug_log("ENTER STEP EXTRACTION")
st.header("2. Équations extraites")
equations = st.session_state.equations
debug_log(f"DISPLAYING {len(equations)} EQUATIONS")
for i, eq in enumerate(equations):
debug_log(f"RENDER EQUATION {i+1}")
with st.expander(f"Équation {i+1}", expanded=True):
st.code(eq.get("latex", ""), language="latex")
context = eq.get("context", "")
if context:
st.caption(context[:300])
st.divider()
col1, col2 = st.columns(2)
with col1:
if st.button("✅ Valider", use_container_width=True):
debug_log("VALIDATE EXTRACTION")
st.session_state.step = "ir"
st.rerun()
with col2:
if st.button("❌ Recommencer", use_container_width=True):
debug_log("RESTART FROM EXTRACTION")
reset()
st.rerun()
# =====================================================
# STEP 3
# =====================================================
elif st.session_state.step == "ir":
debug_log("ENTER STEP IR")
st.header("3. Construction IR")
if len(st.session_state.ir_variants) == 0:
debug_log("NO IR IN CACHE => BUILDING")
try:
with st.spinner("⚙️ Génération des IR..."):
st.session_state.ir_variants = timed_call(
"build_ir_variants",
build_ir_variants,
st.session_state.equations,
num_variants=3,
provider=normalize_provider(st.session_state.provider)
)
debug_log(f"IR VARIANTS GENERATED => {len(st.session_state.ir_variants)}")
except Exception as e:
err = debug_exception(e)
st.error(str(e))
st.code(err)
st.stop()
ir_variants = st.session_state.ir_variants
for i, ir in enumerate(ir_variants):
debug_log(f"DISPLAY IR VARIANT {i+1}")
with st.expander(f"IR Variante {i+1}", expanded=(i == 0)):
st.json(ir)
choice = st.radio(
"Choisir une IR",
range(len(ir_variants)),
format_func=lambda x: f"Variante {x+1}"
)
debug_log(f"IR CHOICE => {choice}")
if st.button("✅ Valider cette IR", type="primary"):
debug_log(f"IR VALIDATED => VARIANT {choice+1}")
st.session_state.chosen_ir = ir_variants[choice]
st.session_state.step = "patterns"
st.rerun()
# =====================================================
# STEP 4
# =====================================================
elif st.session_state.step == "patterns":
debug_log("ENTER STEP PATTERNS")
st.header("4. Détection des patterns")
if len(st.session_state.patterns) == 0:
debug_log("START PATTERN DETECTION")
try:
with st.spinner("🔍 Analyse des patterns..."):
st.session_state.patterns = timed_call(
"detect_patterns",
detect_patterns,
st.session_state.chosen_ir,
provider=normalize_provider(st.session_state.provider)
)
debug_log(f"PATTERNS FOUND => {len(st.session_state.patterns)}")
except Exception as e:
err = debug_exception(e)
st.error(str(e))
st.code(err)
st.stop()
patterns = st.session_state.patterns
for pattern in patterns:
debug_log(f"PATTERN => {pattern.get('name', 'Unknown')}")
with st.container(border=True):
st.subheader(pattern.get("name", "Unknown"))
st.write(pattern.get("description", ""))
st.divider()
col1, col2 = st.columns(2)
with col1:
if st.button("✅ Continuer", use_container_width=True):
debug_log("GO TO OPTIMIZATION")
st.session_state.step = "optimization"
st.rerun()
with col2:
if st.button("⬅ Retour", use_container_width=True):
debug_log("BACK TO IR STEP")
st.session_state.step = "ir"
st.rerun()
# =====================================================
# STEP 5
# =====================================================
elif st.session_state.step == "optimization":
debug_log("ENTER STEP OPTIMIZATION")
st.header("5. Optimisation IR")
if len(st.session_state.optimization_variants) == 0:
debug_log("START OPTIMIZATION")
try:
with st.spinner("🚀 Optimisation..."):
st.session_state.optimization_variants = timed_call(
"optimize_ir",
optimize_ir,
st.session_state.chosen_ir,
provider=normalize_provider(st.session_state.provider)
)
debug_log(f"OPTIMIZATION VARIANTS => {len(st.session_state.optimization_variants)}")
except Exception as e:
err = debug_exception(e)
st.error(str(e))
st.code(err)
st.stop()
optimizations = st.session_state.optimization_variants
for i, opt in enumerate(optimizations):
debug_log(f"DISPLAY OPTIMIZATION {i+1}")
with st.expander(f"Stratégie {i+1}", expanded=(i == 0)):
st.markdown(f"**Explication :** {opt.get('explanation', '')}")
st.json(opt.get("optimized_ir", {}))
choice = st.radio(
"Choisir une optimisation",
range(len(optimizations)),
format_func=lambda x: f"Stratégie {x+1}"
)
debug_log(f"OPTIMIZATION CHOICE => {choice}")
if st.button("✅ Finaliser", type="primary"):
debug_log(f"FINAL OPTIMIZATION SELECTED => {choice+1}")
st.session_state.chosen_optimization = optimizations[choice]
st.session_state.step = "done"
st.rerun()
# =====================================================
# STEP 6
# =====================================================
elif st.session_state.step == "done":
debug_log("ENTER STEP DONE")
st.header("🎉 Pipeline terminé")
final_ir = (
st.session_state.chosen_optimization.get(
"optimized_ir",
st.session_state.chosen_ir
)
)
debug_log(f"FINAL IR NODES => {len(final_ir.get('nodes', []))}")
debug_log(f"FINAL IR EDGES => {len(final_ir.get('edges', []))}")
st.success("IR finale générée avec succès.")
st.json(final_ir)
json_data = json.dumps(
final_ir,
indent=2,
ensure_ascii=False
)
st.download_button(
label="📥 Télécharger JSON",
data=json_data,
file_name="final_ir.json",
mime="application/json"
)
st.divider()
if st.button("🔄 Nouveau traitement"):
debug_log("NEW PROCESS STARTED")
reset()
st.rerun()
# =====================================================
# FOOTER DEBUG
# =====================================================
debug_log(f"END RENDER | STEP={st.session_state.step}")