162 lines
5.9 KiB
Python
162 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
import streamlit as st
|
|
|
|
from web.config_ui import format_params_summary, render_config_form
|
|
from web.pages.report_history import render_report_history
|
|
from web.report_viewer import render_report_focused
|
|
from web.run_pipeline import STEP_LABELS_ZH, run_analysis
|
|
|
|
SUBVIEW_RUN = "运行与进度"
|
|
SUBVIEW_HISTORY = "报告历史"
|
|
|
|
|
|
def _normalize_analysis_subview() -> None:
|
|
legacy = {
|
|
"Run / Progress": SUBVIEW_RUN,
|
|
"Report History": SUBVIEW_HISTORY,
|
|
}
|
|
v = st.session_state.get("analysis_subview")
|
|
if v in legacy:
|
|
st.session_state.analysis_subview = legacy[v]
|
|
|
|
|
|
def _render_steps(current_idx: int) -> None:
|
|
lines = []
|
|
for i, name in enumerate(STEP_LABELS_ZH):
|
|
if i < current_idx:
|
|
lines.append(f"✅ {i + 1:02d}. {name}")
|
|
elif i == current_idx:
|
|
lines.append(f"👉 {i + 1:02d}. {name}")
|
|
else:
|
|
lines.append(f"⬜ {i + 1:02d}. {name}")
|
|
st.code("\n".join(lines), language=None)
|
|
|
|
|
|
def _render_stats(stats: dict) -> None:
|
|
c1, c2, c3, c4 = st.columns(4)
|
|
c1.metric("LLM 调用次数", int(stats.get("llm_calls", 0)))
|
|
c2.metric("工具调用次数", int(stats.get("tool_calls", 0)))
|
|
c3.metric("输入 Token", int(stats.get("tokens_in", 0)))
|
|
c4.metric("输出 Token", int(stats.get("tokens_out", 0)))
|
|
|
|
|
|
def _render_run_panel() -> None:
|
|
st.subheader("运行与进度")
|
|
running = st.session_state.ui_mode == "running"
|
|
if st.button("新建分析", type="primary"):
|
|
st.session_state.show_config = True
|
|
st.session_state.ui_mode = "configuring"
|
|
st.rerun()
|
|
if st.session_state.last_params and not st.session_state.show_config:
|
|
if st.button("修改参数"):
|
|
st.session_state.show_config = True
|
|
st.session_state.ui_mode = "configuring"
|
|
st.rerun()
|
|
|
|
params_live = None
|
|
if st.session_state.show_config:
|
|
params_live = render_config_form(st.container(), disabled=running, key_prefix="task_cfg")
|
|
if st.session_state.get("prefill_ticker"):
|
|
st.caption(f"提示:当前关注的股票代码为 `{st.session_state['prefill_ticker']}`")
|
|
elif st.session_state.last_params:
|
|
st.markdown("<div class='ta-summary'>", unsafe_allow_html=True)
|
|
st.markdown("#### 已选参数")
|
|
st.markdown(
|
|
st.session_state.selected_params_summary or format_params_summary(st.session_state.last_params)
|
|
)
|
|
st.markdown("</div>", unsafe_allow_html=True)
|
|
|
|
run_clicked = st.button("开始分析", disabled=running) if st.session_state.show_config else False
|
|
params_for_run = None
|
|
if run_clicked:
|
|
if not params_live or not params_live.get("ticker"):
|
|
st.error("请填写股票代码(Ticker)。")
|
|
elif not params_live.get("selected_analysts"):
|
|
st.error("请至少选择一名分析师维度。")
|
|
else:
|
|
st.session_state.last_params = dict(params_live)
|
|
st.session_state.selected_params_summary = format_params_summary(params_live)
|
|
st.session_state.show_config = False
|
|
params_for_run = dict(params_live)
|
|
if params_for_run:
|
|
st.session_state.ui_mode = "running"
|
|
st.session_state.runtime_stats = {}
|
|
st.session_state.error = ""
|
|
step_box = st.empty()
|
|
stats_box = st.empty()
|
|
|
|
def _progress_cb(_msg: str) -> None:
|
|
return
|
|
|
|
def _step_cb(info: dict) -> None:
|
|
with step_box.container():
|
|
st.markdown("### 当前步骤")
|
|
_render_steps(int(info.get("index", 0)))
|
|
|
|
def _stats_cb(stats: dict) -> None:
|
|
st.session_state.runtime_stats = dict(stats)
|
|
with stats_box.container():
|
|
st.markdown("### Token / 调用统计")
|
|
_render_stats(stats)
|
|
|
|
_step_cb({"index": 0})
|
|
_stats_cb({})
|
|
try:
|
|
with st.spinner("分析运行中…"):
|
|
out_state, out_dir = run_analysis(
|
|
params=params_for_run,
|
|
progress_cb=_progress_cb,
|
|
stream_events=True,
|
|
step_cb=_step_cb,
|
|
stats_cb=_stats_cb,
|
|
)
|
|
_ = out_state
|
|
st.session_state.latest_run_dir = out_dir
|
|
st.session_state.active_output_dir = out_dir
|
|
st.session_state.ui_mode = "done"
|
|
st.success("分析完成。")
|
|
except Exception:
|
|
st.session_state.ui_mode = "failed"
|
|
st.session_state.error = traceback.format_exc()
|
|
|
|
if st.session_state.error:
|
|
st.error(st.session_state.error)
|
|
if st.session_state.active_output_dir and Path(st.session_state.active_output_dir).exists():
|
|
st.subheader("最新报告")
|
|
render_report_focused(st.session_state.active_output_dir)
|
|
|
|
|
|
def render_analysis_tasks() -> None:
|
|
st.header("个股分析")
|
|
_normalize_analysis_subview()
|
|
if "analysis_subview" not in st.session_state:
|
|
st.session_state.analysis_subview = SUBVIEW_RUN
|
|
c1, c2 = st.columns(2)
|
|
with c1:
|
|
if st.button(
|
|
SUBVIEW_RUN,
|
|
key="analysis_sub_run",
|
|
type="primary" if st.session_state.analysis_subview == SUBVIEW_RUN else "secondary",
|
|
use_container_width=True,
|
|
):
|
|
st.session_state.analysis_subview = SUBVIEW_RUN
|
|
st.rerun()
|
|
with c2:
|
|
if st.button(
|
|
SUBVIEW_HISTORY,
|
|
key="analysis_sub_history",
|
|
type="primary" if st.session_state.analysis_subview == SUBVIEW_HISTORY else "secondary",
|
|
use_container_width=True,
|
|
):
|
|
st.session_state.analysis_subview = SUBVIEW_HISTORY
|
|
st.rerun()
|
|
|
|
if st.session_state.analysis_subview == SUBVIEW_RUN:
|
|
_render_run_panel()
|
|
else:
|
|
render_report_history(default_ticker=st.session_state.get("selected_ticker"))
|