# app_command_center.py import streamlit as st import sys import os import httpx import time import json from datetime import datetime # --- Path Setup --- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))) from agents.orchestrator_v3 import SentinelOrchestratorV3 # --- Configuration --- WATCHLIST_FILE = "watchlist.json" ALERTS_FILE = "alerts.json" # --- Page Configuration --- st.set_page_config( page_title="Aegis Digital Briefing", page_icon="🛡️", layout="wide" ) # --- Custom CSS for the Briefing Room Theme --- st.markdown(""" """, unsafe_allow_html=True) # --- Helper Functions & State --- @st.cache_data(ttl=60) def check_server_status(): urls = {"Gateway": "http://127.0.0.1:8000/", "Tavily": "http://127.0.0.1:8001/", "Alpha Vantage": "http://127.0.0.1:8002/", "Private DB": "http://127.0.0.1:8003/"} statuses = {} with httpx.Client(timeout=2.0) as client: for name, url in urls.items(): try: response = client.get(url) statuses[name] = "✅ Online" if response.status_code == 200 else "⚠️ Error" except: statuses[name] = "❌ Offline" return statuses def load_watchlist(): if not os.path.exists(WATCHLIST_FILE): return [] try: with open(WATCHLIST_FILE, 'r') as f: return json.load(f) except: return [] def save_watchlist(watchlist): with open(WATCHLIST_FILE, 'w') as f: json.dump(watchlist, f) def load_alerts(): if not os.path.exists(ALERTS_FILE): return [] try: with open(ALERTS_FILE, 'r') as f: return json.load(f) except: return [] if 'final_state' not in st.session_state: st.session_state.final_state = None # --- UI Rendering --- # Header st.markdown('

Aegis Digital Briefing Room

', unsafe_allow_html=True) st.markdown('

Automated Intelligence Reports for Modern Finance

', unsafe_allow_html=True) # --- SIDEBAR: Watchlist & Notes --- sidebar = st.sidebar sidebar.title("🛡️ Command Center") # 1. Watchlist Manager sidebar.subheader("Active Watchlist") watchlist = load_watchlist() new_symbol = sidebar.text_input("Add Symbol:", placeholder="e.g. MSFT").upper() if sidebar.button("Add to Watchlist"): if new_symbol and new_symbol not in watchlist: watchlist.append(new_symbol) save_watchlist(watchlist) st.rerun() symbol_to_remove = sidebar.selectbox("Remove Symbol:", ["Select..."] + watchlist) if symbol_to_remove != "Select..." and sidebar.button("Remove"): watchlist.remove(symbol_to_remove) save_watchlist(watchlist) st.rerun() sidebar.markdown("---") # 2. Analyst Notes sidebar.title("👨‍💼 Analyst's Live Notes") notes_placeholder = sidebar.empty() notes_placeholder.info("Awaiting new directive...") # --- MAIN CONTENT --- main_col, alerts_col = st.columns([3, 1]) with main_col: # Main container for Research main_container = st.container(border=True) # Input Form with main_container: st.subheader("🚀 Launch On-Demand Analysis") with st.form("research_form"): task_input = st.text_input("", placeholder="Enter your directive, e.g., 'Analyze market reaction to the latest Apple ($AAPL) product launch'", label_visibility="collapsed") submitted = st.form_submit_button("Generate Briefing", use_container_width=True) # --- Main Logic --- if submitted and task_input: server_statuses = check_server_status() if not all(s == "✅ Online" for s in server_statuses.values()): main_container.error("Analysis cannot proceed. One or more backend services are offline. Please check the status.") else: # main_container.empty() # Don't clear, just show results below final_state_result = {} analyst_notes = [] try: with st.spinner("Your AI Analyst is compiling the briefing... This may take a moment."): for event in SentinelOrchestratorV3.stream({"task": task_input}): node_name = list(event.keys())[0] final_state_result.update(event[node_name]) # --- Generate and Display Live Analyst Notes --- note = "" if node_name == "extract_symbol": note = f"Identified target entity: **{event[node_name].get('symbol', 'N/A')}**" elif node_name == "web_researcher": note = "Sourced initial open-source intelligence from the web." elif node_name == "market_data_analyst": note = "Retrieved latest intraday market performance data." elif node_name == "data_analyzer": note = "Commenced deep-dive quantitative analysis of time-series data." elif node_name == "report_synthesizer": note = "Synthesizing all findings into the final executive briefing." if note: analyst_notes.append(f'
{node_name.replace("_", " ").title()}
{note}
') notes_placeholder.markdown("".join(analyst_notes), unsafe_allow_html=True) time.sleep(0.5) # --- Display the Final Briefing --- st.session_state.final_state = final_state_result final_state = st.session_state.final_state symbol = final_state.get("symbol", "N/A") # HEADLINE st.markdown(f"## Briefing: {symbol} - {datetime.now().strftime('%B %d, %Y')}") st.markdown("---") # KEY METRICS WIDGET st.subheader("Key Performance Indicators") df = final_state.get("analysis_results", {}).get("dataframe") if df is not None and not df.empty: m_col1, m_col2, m_col3, m_col4 = st.columns(4) with m_col1: st.markdown(f'
${df["close"].iloc[-1]:.2f}
Latest Close Price
', unsafe_allow_html=True) with m_col2: st.markdown(f'
{df["volume"].sum()/1e6:.2f}M
Total Volume
', unsafe_allow_html=True) with m_col3: st.markdown(f'
${df["high"].max():.2f}
Intraday High
', unsafe_allow_html=True) with m_col4: st.markdown(f'
${df["low"].min():.2f}
Intraday Low
', unsafe_allow_html=True) else: st.info("Quantitative market data was not applicable for this briefing.") st.markdown("
", unsafe_allow_html=True) # MAIN BRIEFING (REPORT + CHARTS) brief_col1, brief_col2 = st.columns([7, 5]) # 70/50 split with brief_col1: st.subheader("Executive Summary & Analysis") report_html = final_state.get("final_report", "No report generated.").replace("\n", "
") st.markdown(f'
{report_html}
', unsafe_allow_html=True) with brief_col2: st.subheader("Visual Data Debrief") charts = final_state.get("analysis_results", {}).get("charts", []) if charts: for chart in charts: st.plotly_chart(chart, use_container_width=True) else: st.markdown('

No visualizations were generated for this briefing.

', unsafe_allow_html=True) # EVIDENCE LOG with st.expander("Show Evidence Log & Methodology"): st.markdown("#### Open Source Intelligence (Web Research)") st.json(final_state.get('web_research_results', '{}')) st.markdown("#### Deep-Dive Analysis Insights") st.text(final_state.get("analysis_results", {}).get("insights", "No insights.")) if st.button("Start New Briefing"): st.session_state.final_state = None st.rerun() except Exception as e: st.error(f"An error occurred: {e}") # --- LIVE ALERTS FEED --- with alerts_col: st.subheader("🚨 Live Alerts") st.caption("Real-time monitoring feed") alerts_container = st.container(height=600) # Auto-refresh logic for alerts if 'last_refresh' not in st.session_state: st.session_state.last_refresh = time.time() # Refresh every 10 seconds if time.time() - st.session_state.last_refresh > 10: st.session_state.last_refresh = time.time() st.rerun() alerts = load_alerts() if not alerts: alerts_container.info("No active alerts.") else: for alert in alerts: alert_type = alert.get("type", "INFO") css_class = "alert-market" if alert_type == "MARKET" else "alert-news" if alert_type == "NEWS" else "" icon = "📉" if alert_type == "MARKET" else "📰" timestamp = datetime.fromisoformat(alert.get("timestamp", datetime.now().isoformat())).strftime("%H:%M") html = f"""
{icon} {alert.get("symbol")} {timestamp}
{alert.get("message")}
""" alerts_container.markdown(html, unsafe_allow_html=True)