Spaces:
Sleeping
Sleeping
| # app.py — Multi-Agent Debate System | ATLAS vs NEXUS vs ORACLE | |
| import streamlit as st | |
| import os | |
| st.set_page_config( | |
| page_title="Debate Arena · Multi-Agent", | |
| page_icon="⚔️", | |
| layout="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| # ── CSS: ARENA THEME ────────────────────────────────────────── | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=IBM+Plex+Mono:wght@400;600&family=Crimson+Pro:ital,wght@0,400;0,600;1,400&display=swap'); | |
| html, body, [class*="css"] { | |
| background: #08080e; | |
| color: #d8d8e8; | |
| font-family: 'Crimson Pro', Georgia, serif; | |
| } | |
| #MainMenu, footer, header { visibility: hidden; } | |
| .block-container { padding-top: 1rem; padding-bottom: 2rem; max-width: 1200px; } | |
| /* ── ARENA HEADER ── */ | |
| .arena-header { | |
| text-align: center; | |
| padding: 1.5rem 0 0.5rem; | |
| position: relative; | |
| } | |
| .arena-title { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 4rem; | |
| letter-spacing: 0.12em; | |
| background: linear-gradient(180deg, #ffffff 0%, #888899 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| line-height: 1; | |
| margin: 0; | |
| } | |
| .arena-sub { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.7rem; | |
| color: #44445a; | |
| letter-spacing: 0.25em; | |
| text-transform: uppercase; | |
| margin-top: 0.2rem; | |
| } | |
| /* ── AGENT NAMEPLATES ── */ | |
| .nameplate-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin: 1.5rem 0 1rem; | |
| gap: 1rem; | |
| } | |
| .nameplate-atlas { | |
| flex: 1; | |
| background: linear-gradient(135deg, #0a0a1a 0%, #0d1a2e 100%); | |
| border: 1px solid #1a4080; | |
| border-left: 4px solid #2d7fff; | |
| border-radius: 8px; | |
| padding: 0.8rem 1.2rem; | |
| text-align: left; | |
| } | |
| .nameplate-nexus { | |
| flex: 1; | |
| background: linear-gradient(135deg, #1a0a0a 0%, #2e0d0d 100%); | |
| border: 1px solid #801a1a; | |
| border-right: 4px solid #ff2d2d; | |
| border-radius: 8px; | |
| padding: 0.8rem 1.2rem; | |
| text-align: right; | |
| } | |
| .nameplate-oracle { | |
| background: linear-gradient(135deg, #1a1400 0%, #2e2400 100%); | |
| border: 1px solid #806600; | |
| border-bottom: 4px solid #ffd700; | |
| border-radius: 8px; | |
| padding: 0.6rem 1.5rem; | |
| text-align: center; | |
| min-width: 140px; | |
| } | |
| .agent-name { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 1.6rem; | |
| letter-spacing: 0.1em; | |
| } | |
| .atlas-name { color: #2d7fff; } | |
| .nexus-name { color: #ff2d2d; } | |
| .oracle-name { color: #ffd700; font-size: 1.3rem; } | |
| .agent-role { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.65rem; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| margin-top: 0.1rem; | |
| } | |
| .atlas-role { color: #1a5aaa; } | |
| .nexus-role { color: #aa1a1a; } | |
| .oracle-role { color: #aa9000; } | |
| /* ── SPEECH BUBBLES ── */ | |
| .bubble-atlas { | |
| background: #080d18; | |
| border: 1px solid #1a3060; | |
| border-left: 3px solid #2d7fff; | |
| border-radius: 2px 12px 12px 12px; | |
| padding: 1rem 1.3rem; | |
| margin: 0.6rem 0 0.6rem 0; | |
| font-size: 1rem; | |
| line-height: 1.7; | |
| position: relative; | |
| animation: slideInLeft 0.3s ease; | |
| } | |
| .bubble-nexus { | |
| background: #180808; | |
| border: 1px solid #601a1a; | |
| border-right: 3px solid #ff2d2d; | |
| border-radius: 12px 2px 12px 12px; | |
| padding: 1rem 1.3rem; | |
| margin: 0.6rem 0 0.6rem 0; | |
| font-size: 1rem; | |
| line-height: 1.7; | |
| position: relative; | |
| animation: slideInRight 0.3s ease; | |
| } | |
| .bubble-status { | |
| background: #0f0f0f; | |
| border: 1px solid #222; | |
| border-radius: 20px; | |
| padding: 0.3rem 1rem; | |
| margin: 0.4rem auto; | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.72rem; | |
| color: #44445a; | |
| text-align: center; | |
| width: fit-content; | |
| } | |
| .speaker-tag { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 0.85rem; | |
| letter-spacing: 0.1em; | |
| margin-bottom: 0.4rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.4rem; | |
| } | |
| .tag-atlas { color: #2d7fff; } | |
| .tag-nexus { color: #ff2d2d; } | |
| .turn-badge { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.6rem; | |
| color: #333355; | |
| border: 1px solid #222233; | |
| padding: 0.1rem 0.4rem; | |
| border-radius: 10px; | |
| } | |
| @keyframes slideInLeft { | |
| from { opacity: 0; transform: translateX(-12px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| @keyframes slideInRight { | |
| from { opacity: 0; transform: translateX(12px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| /* ── VERDICT CARD ── */ | |
| .verdict-container { | |
| background: linear-gradient(135deg, #0f0e00 0%, #1a1800 100%); | |
| border: 1px solid #806600; | |
| border-top: 4px solid #ffd700; | |
| border-radius: 12px; | |
| padding: 1.5rem 2rem; | |
| margin-top: 1.5rem; | |
| } | |
| .verdict-title { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 1.2rem; | |
| color: #ffd700; | |
| letter-spacing: 0.2em; | |
| margin-bottom: 1rem; | |
| } | |
| .winner-banner { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 3rem; | |
| letter-spacing: 0.15em; | |
| text-align: center; | |
| margin: 0.5rem 0; | |
| } | |
| .winner-atlas { color: #2d7fff; text-shadow: 0 0 30px #2d7fff44; } | |
| .winner-nexus { color: #ff2d2d; text-shadow: 0 0 30px #ff2d2d44; } | |
| .winner-empate { color: #ffd700; } | |
| .score-bar-container { margin: 0.8rem 0; } | |
| .score-label { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.7rem; | |
| color: #666688; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| margin-bottom: 0.2rem; | |
| } | |
| .score-bar-track { | |
| background: #111118; | |
| border-radius: 4px; | |
| height: 8px; | |
| width: 100%; | |
| overflow: hidden; | |
| } | |
| .score-bar-fill-atlas { | |
| height: 100%; | |
| background: linear-gradient(90deg, #1a4080, #2d7fff); | |
| border-radius: 4px; | |
| transition: width 0.8s ease; | |
| } | |
| .score-bar-fill-nexus { | |
| height: 100%; | |
| background: linear-gradient(90deg, #801a1a, #ff2d2d); | |
| border-radius: 4px; | |
| transition: width 0.8s ease; | |
| } | |
| .criteria-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 0.6rem; | |
| margin-top: 0.8rem; | |
| } | |
| .criteria-item { | |
| background: #0a0a10; | |
| border: 1px solid #1a1a2a; | |
| border-radius: 6px; | |
| padding: 0.5rem 0.8rem; | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.72rem; | |
| } | |
| .crit-name { color: #555577; text-transform: uppercase; letter-spacing: 0.08em; } | |
| .crit-scores { display: flex; gap: 0.8rem; margin-top: 0.2rem; } | |
| .crit-atlas { color: #2d7fff; } | |
| .crit-nexus { color: #ff2d2d; } | |
| .insight-box { | |
| background: #0d0d00; | |
| border: 1px solid #333300; | |
| border-left: 3px solid #ffd700; | |
| border-radius: 4px; | |
| padding: 0.7rem 1rem; | |
| margin-top: 1rem; | |
| font-style: italic; | |
| color: #cccc88; | |
| font-size: 0.95rem; | |
| } | |
| /* ── SIDEBAR ── */ | |
| section[data-testid="stSidebar"] { | |
| background: #05050a; | |
| border-right: 1px solid #111120; | |
| } | |
| section[data-testid="stSidebar"] * { color: #9090aa !important; } | |
| /* ── INPUT ── */ | |
| .stChatInput textarea { | |
| background: #0a0a14 !important; | |
| border: 1px solid #222233 !important; | |
| border-radius: 8px !important; | |
| color: #d8d8e8 !important; | |
| font-family: 'IBM Plex Mono', monospace !important; | |
| } | |
| .stButton button { | |
| background: #0a0a14 !important; | |
| border: 1px solid #222233 !important; | |
| color: #7070aa !important; | |
| border-radius: 6px !important; | |
| font-family: 'IBM Plex Mono', monospace !important; | |
| font-size: 0.75rem !important; | |
| transition: all 0.2s !important; | |
| } | |
| .stButton button:hover { | |
| border-color: #ffd700 !important; | |
| color: #ffd700 !important; | |
| } | |
| hr { border-color: #111120 !important; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ── SESSION STATE ───────────────────────────────────────────── | |
| for k, v in { | |
| 'debates': [], | |
| 'openai_key': '', | |
| 'running': False, | |
| }.items(): | |
| if k not in st.session_state: | |
| st.session_state[k] = v | |
| # ── HELPERS ─────────────────────────────────────────────────── | |
| def get_key(): | |
| try: | |
| if 'OPENAI_API_KEY' in st.secrets: | |
| return st.secrets['OPENAI_API_KEY'] | |
| except Exception: | |
| pass | |
| return os.getenv('OPENAI_API_KEY', st.session_state.openai_key) | |
| # ── SIDEBAR ─────────────────────────────────────────────────── | |
| with st.sidebar: | |
| st.markdown(""" | |
| <div style='font-family:Bebas Neue;font-size:1.8rem;letter-spacing:0.12em; | |
| background:linear-gradient(180deg,#fff,#666);-webkit-background-clip:text; | |
| -webkit-text-fill-color:transparent;background-clip:text'>DEBATE ARENA</div> | |
| <div style='font-family:IBM Plex Mono;font-size:0.6rem;color:#333355; | |
| letter-spacing:0.2em;text-transform:uppercase'>Multi-Agent System v1.0</div> | |
| """, unsafe_allow_html=True) | |
| st.divider() | |
| st.markdown("#### 🔑 OpenAI API Key") | |
| key_in = st.text_input("", type="password", value=st.session_state.openai_key, | |
| placeholder="sk-...", label_visibility="collapsed") | |
| if key_in: | |
| st.session_state.openai_key = key_in | |
| if get_key(): | |
| st.success("✅ Key configurada") | |
| else: | |
| st.warning("Configure a API Key") | |
| st.divider() | |
| st.markdown("#### ⚙️ Configuração") | |
| n_rounds = st.slider("Rounds de debate", 1, 4, 2, | |
| help="Cada round = 1 turno NEXUS + 1 turno ATLAS") | |
| st.divider() | |
| st.markdown(""" | |
| <div style='font-family:IBM Plex Mono;font-size:0.65rem;color:#222244'> | |
| <div style='color:#2d7fff;margin-bottom:0.3rem'>▮ ATLAS — Arguer PRO</div> | |
| Defende a posição com dados e lógica.<br><br> | |
| <div style='color:#ff2d2d;margin-bottom:0.3rem'>▮ NEXUS — Arguer CON</div> | |
| Ataca com contra-argumentos e edge cases.<br><br> | |
| <div style='color:#ffd700;margin-bottom:0.3rem'>▮ ORACLE — Judge</div> | |
| Avalia 4 critérios e emite veredicto JSON. | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.divider() | |
| st.markdown(""" | |
| <div style='font-family:IBM Plex Mono;font-size:0.62rem;color:#222233;text-align:center'> | |
| gpt-4o-mini · 3 system prompts<br> | |
| orquestração manual · CPU-only | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.divider() | |
| if st.button("🗑️ Limpar arena", use_container_width=True): | |
| st.session_state.debates = [] | |
| st.rerun() | |
| # ── HEADER ──────────────────────────────────────────────────── | |
| st.markdown(""" | |
| <div class="arena-header"> | |
| <div class="arena-title">⚔ DEBATE ARENA ⚔</div> | |
| <div class="arena-sub">ATLAS vs NEXUS · Julgado por ORACLE · Sistema Multi-Agente</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ── NAMEPLATES ──────────────────────────────────────────────── | |
| st.markdown(""" | |
| <div class="nameplate-row"> | |
| <div class="nameplate-atlas"> | |
| <div class="agent-name atlas-name">ATLAS</div> | |
| <div class="agent-role atlas-role">◈ Arguer PRO · Defende</div> | |
| </div> | |
| <div class="nameplate-oracle"> | |
| <div class="agent-name oracle-name">ORACLE</div> | |
| <div class="agent-role oracle-role">◆ Judge</div> | |
| </div> | |
| <div class="nameplate-nexus"> | |
| <div class="agent-name nexus-name">NEXUS</div> | |
| <div class="agent-role nexus-role">Ataca · Arguer CON ◈</div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ── TÓPICOS SUGERIDOS ───────────────────────────────────────── | |
| TOPICS = [ | |
| "RAG é superior a Fine-tuning para domínios especializados", | |
| "LLMs vão substituir engenheiros de software até 2030", | |
| "Graph Neural Networks superam Transformers em dados relacionais", | |
| "Modelos open-source já rivalizam com GPT-4 em tarefas práticas", | |
| "Prompting é mais importante que arquitetura do modelo", | |
| "Agentes autônomos de IA são prontos para produção em 2025", | |
| ] | |
| if not st.session_state.debates: | |
| st.markdown(""" | |
| <div style='font-family:IBM Plex Mono;font-size:0.7rem;color:#333355; | |
| text-transform:uppercase;letter-spacing:0.15em;margin:1rem 0 0.5rem'> | |
| ◈ Escolha um tema ou digite o seu | |
| </div> | |
| """, unsafe_allow_html=True) | |
| cols = st.columns(3) | |
| for i, topic in enumerate(TOPICS): | |
| with cols[i % 3]: | |
| if st.button(topic, key=f"t{i}", use_container_width=True): | |
| st.session_state["pending_topic"] = topic | |
| st.rerun() | |
| # ── HISTÓRICO DE DEBATES ────────────────────────────────────── | |
| for debate in st.session_state.debates: | |
| topic = debate["topic"] | |
| st.markdown(f""" | |
| <div style='font-family:IBM Plex Mono;font-size:0.65rem;color:#333355; | |
| text-align:center;text-transform:uppercase;letter-spacing:0.12em; | |
| margin:1.5rem 0 0.5rem'>◆ TEMA: {topic} ◆</div> | |
| """, unsafe_allow_html=True) | |
| for ev in debate.get("events", []): | |
| if ev.get("type") == "status": | |
| st.markdown(f'<div class="bubble-status">⟳ {ev["msg"]}</div>', unsafe_allow_html=True) | |
| elif ev.get("agent") == "ATLAS": | |
| st.markdown(f""" | |
| <div class="bubble-atlas"> | |
| <div class="speaker-tag tag-atlas"> | |
| ATLAS <span class="turn-badge">T{ev['turn']}</span> | |
| </div> | |
| {ev['content']} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| elif ev.get("agent") == "NEXUS": | |
| st.markdown(f""" | |
| <div class="bubble-nexus"> | |
| <div class="speaker-tag tag-nexus" style="justify-content:flex-end"> | |
| <span class="turn-badge">T{ev['turn']}</span> NEXUS | |
| </div> | |
| {ev['content']} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Veredicto | |
| if debate.get("verdict"): | |
| v = debate["verdict"] | |
| winner = v.get("vencedor", "EMPATE") | |
| pa = v.get("placar_atlas", 0) | |
| pn = v.get("placar_nexus", 0) | |
| winner_class = { | |
| "ATLAS": "winner-atlas", | |
| "NEXUS": "winner-nexus", | |
| "EMPATE": "winner-empate" | |
| }.get(winner, "winner-empate") | |
| crit = v.get("criterios", {}) | |
| crit_items = "" | |
| crit_labels = { | |
| "qualidade_argumentos": "Qualidade dos Argumentos", | |
| "uso_de_evidencias": "Uso de Evidências", | |
| "coerencia_logica": "Coerência Lógica", | |
| "poder_de_refutacao": "Poder de Refutação", | |
| } | |
| for key, label in crit_labels.items(): | |
| ca = crit.get(key, {}).get("atlas", 0) | |
| cn = crit.get(key, {}).get("nexus", 0) | |
| crit_items += f""" | |
| <div class="criteria-item"> | |
| <div class="crit-name">{label}</div> | |
| <div class="crit-scores"> | |
| <span class="crit-atlas">ATLAS {ca}/10</span> | |
| <span class="crit-nexus">NEXUS {cn}/10</span> | |
| </div> | |
| </div>""" | |
| st.markdown(f""" | |
| <div class="verdict-container"> | |
| <div class="verdict-title">◆ VEREDICTO DE ORACLE ◆</div> | |
| <div class="winner-banner {winner_class}">{winner} VENCE</div> | |
| <div style="display:flex;gap:2rem;margin:1rem 0"> | |
| <div style="flex:1"> | |
| <div class="score-label">ATLAS — {pa}/10</div> | |
| <div class="score-bar-track"> | |
| <div class="score-bar-fill-atlas" style="width:{pa*10}%"></div> | |
| </div> | |
| </div> | |
| <div style="flex:1"> | |
| <div class="score-label">NEXUS — {pn}/10</div> | |
| <div class="score-bar-track"> | |
| <div class="score-bar-fill-nexus" style="width:{pn*10}%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="criteria-grid">{crit_items}</div> | |
| <div style="margin-top:1rem;display:grid;grid-template-columns:1fr 1fr;gap:0.8rem"> | |
| <div style="font-family:IBM Plex Mono;font-size:0.75rem;color:#666688"> | |
| <div style="color:#2d7fff;margin-bottom:0.3rem">ANÁLISE ATLAS</div> | |
| {v.get('analise_atlas','')} | |
| </div> | |
| <div style="font-family:IBM Plex Mono;font-size:0.75rem;color:#666688"> | |
| <div style="color:#ff2d2d;margin-bottom:0.3rem">ANÁLISE NEXUS</div> | |
| {v.get('analise_nexus','')} | |
| </div> | |
| </div> | |
| <div style="font-family:IBM Plex Mono;font-size:0.72rem;color:#666644; | |
| margin-top:0.8rem;border-top:1px solid #222200;padding-top:0.6rem"> | |
| ◈ PONTO DECISIVO: {v.get('ponto_decisivo','')} | |
| </div> | |
| <div class="insight-box"> | |
| 💡 {v.get('licao','')} | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.divider() | |
| # ── INPUT ───────────────────────────────────────────────────── | |
| pending = st.session_state.pop("pending_topic", None) | |
| topic = st.chat_input("Digite o tema do debate...") or pending | |
| if topic: | |
| if not get_key(): | |
| st.warning("Configure a OpenAI API Key na sidebar.") | |
| st.stop() | |
| from agents import DebateEngine | |
| debate_record = {"topic": topic, "events": [], "verdict": None} | |
| st.session_state.debates.append(debate_record) | |
| # Header do debate | |
| topic_ph = st.empty() | |
| topic_ph.markdown(f""" | |
| <div style='font-family:IBM Plex Mono;font-size:0.65rem;color:#333355; | |
| text-align:center;text-transform:uppercase;letter-spacing:0.12em; | |
| margin:1.5rem 0 0.5rem'>◆ TEMA: {topic} ◆</div> | |
| """, unsafe_allow_html=True) | |
| engine = DebateEngine(get_key()) | |
| placeholders = [] | |
| for event in engine.run_debate(topic, n_rounds=n_rounds): | |
| if event.get("type") == "status": | |
| ph = st.empty() | |
| ph.markdown(f'<div class="bubble-status">⟳ {event["msg"]}</div>', unsafe_allow_html=True) | |
| debate_record["events"].append(event) | |
| elif event.get("type") == "verdict": | |
| verdict = event["data"] | |
| debate_record["verdict"] = verdict | |
| v = verdict | |
| winner = v.get("vencedor", "EMPATE") | |
| pa = v.get("placar_atlas", 0) | |
| pn = v.get("placar_nexus", 0) | |
| winner_class = {"ATLAS": "winner-atlas", "NEXUS": "winner-nexus", "EMPATE": "winner-empate"}.get(winner, "winner-empate") | |
| crit = v.get("criterios", {}) | |
| crit_labels = { | |
| "qualidade_argumentos": "Qualidade dos Argumentos", | |
| "uso_de_evidencias": "Uso de Evidências", | |
| "coerencia_logica": "Coerência Lógica", | |
| "poder_de_refutacao": "Poder de Refutação", | |
| } | |
| crit_items = "" | |
| for key, label in crit_labels.items(): | |
| ca = crit.get(key, {}).get("atlas", 0) | |
| cn = crit.get(key, {}).get("nexus", 0) | |
| crit_items += f""" | |
| <div class="criteria-item"> | |
| <div class="crit-name">{label}</div> | |
| <div class="crit-scores"> | |
| <span class="crit-atlas">ATLAS {ca}/10</span> | |
| <span class="crit-nexus">NEXUS {cn}/10</span> | |
| </div> | |
| </div>""" | |
| st.markdown(f""" | |
| <div class="verdict-container"> | |
| <div class="verdict-title">◆ VEREDICTO DE ORACLE ◆</div> | |
| <div class="winner-banner {winner_class}">{winner} VENCE</div> | |
| <div style="display:flex;gap:2rem;margin:1rem 0"> | |
| <div style="flex:1"> | |
| <div class="score-label">ATLAS — {pa}/10</div> | |
| <div class="score-bar-track"><div class="score-bar-fill-atlas" style="width:{pa*10}%"></div></div> | |
| </div> | |
| <div style="flex:1"> | |
| <div class="score-label">NEXUS — {pn}/10</div> | |
| <div class="score-bar-track"><div class="score-bar-fill-nexus" style="width:{pn*10}%"></div></div> | |
| </div> | |
| </div> | |
| <div class="criteria-grid">{crit_items}</div> | |
| <div style="margin-top:1rem;display:grid;grid-template-columns:1fr 1fr;gap:0.8rem"> | |
| <div style="font-family:IBM Plex Mono;font-size:0.75rem;color:#666688"> | |
| <div style="color:#2d7fff;margin-bottom:0.3rem">ANÁLISE ATLAS</div>{v.get('analise_atlas','')} | |
| </div> | |
| <div style="font-family:IBM Plex Mono;font-size:0.75rem;color:#666688"> | |
| <div style="color:#ff2d2d;margin-bottom:0.3rem">ANÁLISE NEXUS</div>{v.get('analise_nexus','')} | |
| </div> | |
| </div> | |
| <div style="font-family:IBM Plex Mono;font-size:0.72rem;color:#666644; | |
| margin-top:0.8rem;border-top:1px solid #222200;padding-top:0.6rem"> | |
| ◈ PONTO DECISIVO: {v.get('ponto_decisivo','')} | |
| </div> | |
| <div class="insight-box">💡 {v.get('licao','')}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| # Speech bubble ao vivo | |
| agent = event.get("agent", "") | |
| content = event.get("content", "") | |
| turn = event.get("turn", 0) | |
| debate_record["events"].append(event) | |
| if agent == "ATLAS": | |
| st.markdown(f""" | |
| <div class="bubble-atlas"> | |
| <div class="speaker-tag tag-atlas"> | |
| ATLAS <span class="turn-badge">T{turn}</span> | |
| </div> | |
| {content} | |
| </div>""", unsafe_allow_html=True) | |
| elif agent == "NEXUS": | |
| st.markdown(f""" | |
| <div class="bubble-nexus"> | |
| <div class="speaker-tag tag-nexus" style="justify-content:flex-end"> | |
| <span class="turn-badge">T{turn}</span> NEXUS | |
| </div> | |
| {content} | |
| </div>""", unsafe_allow_html=True) |