"""UI 组件:将 app.py 中大量 UI 渲染函数集中到这里,减少主入口文件体积。 说明: - 为了不大改原有逻辑,本文件基本搬运并小幅整理原 app.py 的函数。 - HTML/CSS 仍有少量内联(Streamlit 常见),但我们会把大段模板集中在这里,避免 app.py 过长。 """ from __future__ import annotations import streamlit as st from ui.rendering import render_opinion_preview, summarize_round_opinions def local_css(file_name: str) -> None: """将本地 CSS 注入 Streamlit 页面""" try: with open(file_name, encoding="utf-8") as f: st.markdown(f"", unsafe_allow_html=True) except FileNotFoundError: st.warning("未找到样式文件 style.css ,页面将使用默认样式。") except UnicodeDecodeError: st.warning("样式文件编码错误,页面将使用默认样式。") def show_welcome_guide() -> None: """显示新手引导(精简版:更适合展示,不喧宾夺主)""" with st.expander("📖 使用速览(30秒上手)", expanded=False): st.markdown( """ - **在左侧输入方案**:尽量包含目标/用户/约束/关键指标(至少 50 字)。 - **选择评审角色**:建议 3-4 个角色,观点更全面。 - **评审轮次**:当前固定为 **1 轮**(每个角色各输出一次观点)。 - **点击开始评审**:首屏会给出**一页可执行结论**(结论/风险/行动清单/需澄清问题)。 > 提示:过程是“依据”,结论摘要是“答案”。 """ ) def show_role_descriptions() -> dict: """返回角色详细说明(UI 用)""" return { "product_manager": { "icon": "📊", "name": "产品经理", "focus": "用户需求、市场定位、产品价值", "description": "擅长从用户需求和市场角度分析方案,关注产品的价值和可行性。", }, "tech_expert": { "icon": "💻", "name": "技术专家", "focus": "架构设计、技术风险、性能优化", "description": "擅长从技术实现角度分析方案,关注架构设计、技术风险和性能优化。", }, "user_representative": { "icon": "👤", "name": "用户代表", "focus": "用户体验、易用性、实用性", "description": "擅长从实际使用角度分析方案,关注用户体验、易用性和实用性。", }, "business_analyst": { "icon": "💰", "name": "商业分析师", "focus": "成本效益、投资回报、市场竞争力", "description": "擅长从商业价值角度分析方案,关注成本效益、投资回报率和市场竞争力。", }, "designer": { "icon": "🎨", "name": "设计师", "focus": "视觉效果、交互设计、用户体验", "description": "擅长从设计角度分析方案,关注视觉效果、交互设计和用户体验。", }, } def show_example_topic() -> str: return """我们计划开发一个AI辅助学习平台,主要功能包括: 1. **智能答疑系统**:基于大语言模型,能够回答学生在学习过程中的各种问题 2. **个性化学习路径**:根据学生的学习进度和能力,自动推荐合适的学习内容和练习 3. **学习数据分析**:收集和分析学生的学习数据,生成学习报告和改进建议 4. **互动练习模块**:提供丰富的练习题和模拟考试,支持实时反馈和错题本功能 目标用户:高中生和大学生 技术栈:Python + React + MongoDB + OpenAI API 预期效果:提高学习效率 30%,用户满意度达到 4.5/5.0""" def validate_input(topic: str, selected_agents: list[str]): """验证用户输入""" errors: list[str] = [] warnings: list[str] = [] if not topic or len(topic.strip()) < 50: errors.append("方案描述太短,请提供更详细的信息(至少50字)") if not selected_agents or len(selected_agents) < 2: errors.append("请至少选择 2 个参与角色以获得全面的评估") if topic and len(topic) > 5000: warnings.append("方案描述较长,可能会影响响应速度") return errors, warnings def show_progress_indicator(current_step: int, total_steps: int, step_name: str) -> None: progress_percent = (current_step / total_steps) * 100 st.markdown( f"""
{step_name} {current_step}/{total_steps}
""", unsafe_allow_html=True, ) def show_empty_state() -> None: st.markdown( """
🤖

准备开始评审

在左侧配置您的方案并选择参与角色,然后点击"开始评审"按钮启动智能分析。

""", unsafe_allow_html=True, ) def show_debate_summary(debate_history: list[dict], decision_points) -> None: if not debate_history: return total_rounds = len(debate_history) - 1 total_opinions = sum(len(round_data.get("opinions", {})) for round_data in debate_history[1:]) col1, col2, col3 = st.columns(3) with col1: st.markdown( f"""
评审轮次
{total_rounds}
""", unsafe_allow_html=True, ) with col2: st.markdown( f"""
参与观点
{total_opinions}
""", unsafe_allow_html=True, ) with col3: st.markdown( f"""
决策要点
{len(decision_points) if decision_points else 0}
""", unsafe_allow_html=True, ) def show_debate_timeline(debate_history: list[dict], role_descriptions: dict) -> None: if not debate_history: return st.markdown("---") st.subheader("📈 评审时间线") for idx, round_data in enumerate(debate_history[1:], 1): round_type_map = {"initial_opinions": "初始观点", "interactive_debate": "互动讨论"} round_type = round_type_map.get(round_data["type"], round_data["type"]) # 新增:本轮摘要(可扫读),不删除原信息 round_summary = summarize_round_opinions(round_data.get("opinions", {}), role_descriptions) with st.container(): st.markdown( f"""
{idx}
第{round_data['round']}轮:{round_type}
{len(round_data.get('opinions', {}))} 个角色参与讨论
""", unsafe_allow_html=True, ) # 摘要展示 if round_summary: st.markdown("**本轮摘要:**") st.markdown(round_summary) # 保留原有参与角色标签(不删) st.markdown('
', unsafe_allow_html=True) for role in round_data.get("opinions", {}).keys(): role_info = role_descriptions.get(role, {"icon": "👤", "name": role}) st.markdown( f"""
{role_info['icon']} {role_info['name']}
""", unsafe_allow_html=True, ) st.markdown("
", unsafe_allow_html=True) def show_debate_comparison(debate_history: list[dict], role_descriptions: dict) -> None: if not debate_history or len(debate_history) < 2: return st.markdown("---") st.subheader("🔍 角色观点对比") st.markdown(f"**共 {len(debate_history) - 1} 轮评审**") for round_num, round_data in enumerate(debate_history[1:], 1): round_type_map = {"initial_opinions": "初始观点", "interactive_debate": "互动讨论"} round_type = round_type_map.get(round_data["type"], round_data["type"]) st.subheader(f"🔄 第{round_num}轮:{round_type}") st.markdown(f"**本轮参与角色:{len(round_data['opinions'])}个**") for role, opinion in round_data["opinions"].items(): role_info = role_descriptions.get(role, {"icon": "👤", "name": role}) preview = render_opinion_preview(opinion or "") title = f"{role_info['icon']} {role_info['name']}" if preview: title = f"{title} — {preview}" with st.expander(title, expanded=False): if opinion: st.markdown( f"""
{opinion}
""", unsafe_allow_html=True, ) else: st.warning(f"{role_info['name']} 在本轮没有生成观点") st.markdown("---") st.subheader("📊 最终观点对比") final_round = debate_history[-1] for role, opinion in final_round["opinions"].items(): role_info = role_descriptions.get(role, {"icon": "👤", "name": role}) with st.container(): st.markdown( f"""
{role_info['icon']} {role_info['name']}
{opinion}
""", unsafe_allow_html=True, )