import os import streamlit as st from dotenv import load_dotenv from agent import DebateManager from datetime import datetime # 确保在任何机器/任何启动方式下都能加载 .env(符合课程要求) load_dotenv() # 启动即检查 API Key,避免老师电脑运行时“无提示失败” _api_key = os.getenv("OPENAI_API_KEY") or os.getenv("DEEPSEEK_API_KEY") if not _api_key: st.error( "未检测到 API Key。\n\n" "请按课程要求在 multi_agent_workshop/ 目录下创建 .env 文件(可参考 .env.example),并设置:\n" "- OPENAI_API_KEY=sk-xxxxxx(推荐)\n" "或\n" "- DEEPSEEK_API_KEY=sk-xxxxxx\n\n" "然后重新运行:uv run streamlit run app.py" ) st.stop() from ui.rendering import render_decision_summary # 从 ui.ui_components 模块导入所需的函数 from ui.ui_components import ( local_css, show_welcome_guide, show_role_descriptions, show_example_topic, validate_input, show_progress_indicator, show_empty_state, show_debate_summary, show_debate_timeline, show_debate_comparison, ) # 读取本地 CSS(原先大量内联 CSS 已经在 style.css,此处仅负责注入) local_css("style.css") st.set_page_config( page_title="多Agent决策工作坊", page_icon="🤖", layout="wide", initial_sidebar_state="expanded", ) def save_debate_history(topic, selected_agents, debate_rounds, debate_history, decision_points): """保存辩论历史到session state""" if "saved_debates" not in st.session_state: st.session_state.saved_debates = [] debate_record = { "id": len(st.session_state.saved_debates) + 1, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "topic": topic[:100] + "..." if len(topic) > 100 else topic, "agents": selected_agents, "rounds": debate_rounds, "debate_history": debate_history, "decision_points": decision_points, } st.session_state.saved_debates.append(debate_record) return debate_record def show_saved_debates(role_descriptions): """显示保存的辩论记录""" if "saved_debates" not in st.session_state or not st.session_state.saved_debates: return st.markdown("---") st.subheader("💾 历史记录") for record in reversed(st.session_state.saved_debates[-5:]): with st.expander(f"📅 {record['timestamp']} - {record['topic']}", expanded=False): col1, col2, col3 = st.columns(3) with col1: st.markdown(f"**参与角色**:{len(record['agents'])} 个") with col2: st.markdown(f"**评审轮次**:{record['rounds']} 轮") with col3: st.markdown( f"**观点数量**:{sum(len(r.get('opinions', {})) for r in record['debate_history'][1:])} 条" ) if st.button(f"查看详情", key=f"view_{record['id']}", use_container_width=True): st.session_state.debate_history = record["debate_history"] st.session_state.decision_points = record["decision_points"] st.rerun() with st.sidebar: st.markdown( """
🤖

多Agent决策工作坊

智能辅助决策系统

""", unsafe_allow_html=True, ) st.markdown("---") st.subheader("📋 方案描述") with st.expander("🧩 一键插入结构化模板(推荐)", expanded=False): st.markdown( """ 如果你不知道怎么写方案,建议用下面模板,AI 输出会更稳定: - 目标(要解决什么问题?) - 目标用户(谁用?使用场景?) - 核心功能(3-5 条) - 约束条件(时间/预算/技术/合规等) - 成功指标(如何衡量?) - 你最担心的风险(可选) """ ) if st.button("📌 插入模板到输入框", use_container_width=True): st.session_state.topic = ( "【目标】\n" "\n" "【目标用户/场景】\n" "\n" "【核心功能(3-5条)】\n" "1. \n2. \n3. \n" "\n" "【约束条件】\n" "- 时间:\n- 预算:\n- 技术:\n" "\n" "【成功指标】\n" "\n" "【已知风险/担忧(可选)】\n" ) st.rerun() topic = st.text_area( "请输入需要评审的方案内容", placeholder="请尽量结构化描述:目标 / 用户 / 核心功能 / 约束 / 指标...", height=180, help="建议提供至少50字的详细描述,以获得更准确的分析结果", ) if st.button("📝 使用示例方案", key="use_example"): st.session_state.topic = show_example_topic() st.rerun() if "topic" in st.session_state: topic = st.session_state.topic st.text_area("方案内容", value=topic, height=180, key="topic_display") st.markdown("---") st.subheader("👥 参与角色") role_descriptions = show_role_descriptions() # 快捷评审模式(更像产品,演示更丝滑) mode = st.radio( "评审模式", options=["快速(2角色)", "标准(3角色)", "全面(5角色)"], index=1, help="用于快速配置参与角色数量", ) if mode.startswith("快速"): default_roles = ["product_manager", "tech_expert"] elif mode.startswith("全面"): default_roles = list(role_descriptions.keys()) else: default_roles = ["product_manager", "tech_expert", "user_representative"] with st.expander("查看角色说明(可选)", expanded=False): for role_key, role_info in role_descriptions.items(): st.markdown(f"**{role_info['icon']} {role_info['name']}**:{role_info['focus']} ") st.markdown(f"{role_info['description']}", unsafe_allow_html=True) st.markdown("---") selected_agents = st.multiselect( "选择参与评审的角色", options=list(role_descriptions.keys()), format_func=lambda x: f"{role_descriptions[x]['icon']} {role_descriptions[x]['name']}", default=default_roles, help="建议至少选择3-4个角色以获得全面的评估", ) if selected_agents: st.info(f"已选择 {len(selected_agents)} 个角色") st.markdown("---") st.subheader("🔄 评审轮次") # 单轮版本:固定 1 轮,减少运行时间与 API 调用次数 debate_rounds = 1 est_low = max(1, len(selected_agents)) * 2 est_high = max(1, len(selected_agents)) * 4 st.markdown( f"""
💡 提示:
• 每个角色各输出一次观点
• 优点:更快、更省调用、更适合课堂演示

⏱ 预计耗时:约 {est_low} ~ {est_high} 秒(与网络和模型负载有关)
""", unsafe_allow_html=True, ) st.markdown("---") errors, warnings = validate_input(topic, selected_agents) if errors: for error in errors: st.error(error) if warnings: for warning in warnings: st.warning(warning) start_button = st.button( "🚀 开始评审", type="primary", disabled=not topic or not selected_agents or len(errors) > 0, use_container_width=True, ) st.title("🤖 多Agent决策工作坊") show_welcome_guide() if "debate_manager" not in st.session_state: st.session_state.debate_manager = DebateManager() if "debate_history" not in st.session_state: st.session_state.debate_history = [] if "decision_points" not in st.session_state: st.session_state.decision_points = "" if start_button: st.session_state.debate_manager.reset() show_progress_indicator(1, 4, "初始化评审环境") with st.spinner("正在初始化评审环境..."): for agent_name in selected_agents: st.session_state.debate_manager.add_agent(agent_name) show_progress_indicator(2, 4, "启动评审") with st.spinner("正在进行评审..."): debate_history = st.session_state.debate_manager.start_debate(topic, debate_rounds) st.session_state.debate_history = debate_history show_progress_indicator(3, 4, "生成决策要点") with st.spinner("正在生成决策要点..."): decision_points = st.session_state.debate_manager.generate_decision_points() st.session_state.decision_points = decision_points show_progress_indicator(4, 4, "完成") save_debate_history(topic, selected_agents, debate_rounds, debate_history, decision_points) st.success("✅ 评审完成!") st.balloons() if st.session_state.debate_history: # 1) 先给“结论摘要”(首屏答案) if st.session_state.decision_points: st.subheader("✅ 一页结论摘要") # 产品化渲染:优先用结构化展示;若解析失败,仍保留原 Markdown 兜底(不删除原功能) try: render_decision_summary(st.session_state.decision_points) except Exception: st.markdown( f"""
{st.session_state.decision_points}
""", unsafe_allow_html=True, ) col1, col2 = st.columns(2) with col1: st.download_button( label="📄 下载结论(Markdown)", data=st.session_state.decision_points, file_name="decision_summary.md", mime="text/markdown", use_container_width=True, ) with col2: if st.button("🔄 重新开始", use_container_width=True): st.session_state.debate_history = [] st.session_state.decision_points = "" st.rerun() st.markdown("---") # 2) 再给简要统计 show_debate_summary(st.session_state.debate_history, st.session_state.decision_points) # 3) 过程作为依据:默认折叠展示 with st.expander("查看评审过程(依据)", expanded=False): show_debate_timeline(st.session_state.debate_history, role_descriptions) st.markdown("---") st.subheader("📊 详细辩论结果") show_debate_comparison(st.session_state.debate_history, role_descriptions) show_saved_debates(role_descriptions) else: show_empty_state() st.markdown("---") st.markdown( """
💡 使用提示
• 您可以随时调整方案内容与参与角色,然后重新开始评审
• 建议使用示例方案进行第一次测试,以了解系统功能
• 评审结果仅供参考,最终决策请结合实际情况和专业判断
""", unsafe_allow_html=True, )