"""Streamlit UI scaffold for the investment assistant.""" from __future__ import annotations import sys from pathlib import Path import streamlit as st from streamlit_option_menu import option_menu ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from app.data.schema import initialize_database from app.ingest.checker import run_boot_check from app.ingest.rss import ingest_configured_rss from app.ui.portfolio_config import render_portfolio_config from app.ui.progress_state import render_factor_progress from app.ui.shared import LOGGER, LOG_EXTRA from app.ui.views import ( render_backtest_review, render_config_overview, render_data_settings, render_global_dashboard, render_llm_settings, render_log_viewer, render_market_visualization, render_pool_overview, render_stock_evaluation, render_tests, render_today_plan, render_factor_calculation, ) from app.utils.config import get_config def main() -> None: LOGGER.info("初始化 Streamlit UI", extra=LOG_EXTRA) st.set_page_config(page_title="多智能体个人投资助理", layout="wide") initialize_database() cfg = get_config() if cfg.auto_update_data: LOGGER.info("检测到自动更新数据选项已启用,开始执行数据拉取", extra=LOG_EXTRA) try: with st.spinner("正在自动更新数据..."): def progress_hook(message: str, progress: float) -> None: st.write(f"📊 {message} ({progress:.1%})") report = run_boot_check( days=30, auto_fetch=True, progress_hook=progress_hook, force_refresh=False, ) rss_count = ingest_configured_rss(hours_back=24, max_items_per_feed=50) LOGGER.info( "自动数据更新完成:日线数据覆盖%s-%s,RSS新闻%s条", report.start, report.end, rss_count, extra=LOG_EXTRA, ) st.success(f"✅ 自动数据更新完成:获取RSS新闻 {rss_count} 条") except Exception as exc: # noqa: BLE001 LOGGER.exception("自动数据更新失败", extra=LOG_EXTRA) st.error(f"❌ 自动数据更新失败:{exc}") render_global_dashboard() # --- 顶部导航(第三方组件 streamlit-option-menu) --- top_labels = ["今日计划", "投资池/仓位", "回测与复盘", "行情可视化", "日志钻取", "数据与设置", "自检测试"] selected_top = option_menu( menu_title=None, options=top_labels, icons=["calendar", "briefcase", "bar-chart", "activity", "file-text", "gear", "bug"], orientation="horizontal", ) LOGGER.debug("Top menu selected: %s", selected_top, extra=LOG_EXTRA) # --- 仅渲染当前选中页(懒加载) --- if selected_top == "今日计划": render_today_plan() elif selected_top == "投资池/仓位": render_pool_overview() elif selected_top == "回测与复盘": sub_labels = ["回测复盘", "股票评估", "因子计算"] selected_backtest = option_menu( menu_title=None, options=sub_labels, icons=["bar-chart", "graph-up", "calculator"], orientation="horizontal", ) LOGGER.debug("Backtest submenu selected: %s", selected_backtest, extra=LOG_EXTRA) if selected_backtest == "回测复盘": render_backtest_review() elif selected_backtest == "股票评估": render_stock_evaluation() else: render_factor_calculation() elif selected_top == "行情可视化": render_market_visualization() elif selected_top == "日志钻取": render_log_viewer() elif selected_top == "数据与设置": st.header("系统设置") settings_labels = ["配置概览", "LLM 设置", "投资组合", "数据源"] selected_settings = option_menu( menu_title=None, options=settings_labels, icons=["list-task", "cpu", "bank", "database"], orientation="horizontal", ) LOGGER.debug("Settings submenu selected: %s", selected_settings, extra=LOG_EXTRA) if selected_settings == "配置概览": render_config_overview() elif selected_settings == "LLM 设置": render_llm_settings() elif selected_settings == "投资组合": render_portfolio_config() else: render_data_settings() elif selected_top == "自检测试": render_tests() if __name__ == "__main__": main()