"""用于把模型生成的 Markdown 摘要做轻量解析并用更产品化的方式展示。 说明:不引入复杂依赖,不要求模型输出 JSON。 通过解析固定标题: - # 一句话结论 - # 关键决策要点(Top 5) - # 主要风险(Top 3) - # 下一步行动清单 - # 需要进一步澄清的问题 另外提供: - render_opinion_preview:观点一句话预览 - summarize_round_opinions:把一轮的多角色观点压缩为可扫读摘要(用于时间线) """ from __future__ import annotations import re import streamlit as st SECTION_TITLES = [ "一句话结论", "关键决策要点(Top 5)", "主要风险(Top 3)", "下一步行动清单", "需要进一步澄清的问题", ] def _split_sections(md: str) -> dict[str, str]: if not md: return {} pattern = r"^#\s+(.*)$" lines = md.splitlines() sections: dict[str, list[str]] = {} current = None for line in lines: m = re.match(pattern, line.strip()) if m: title = m.group(1).strip() current = title sections.setdefault(current, []) continue if current is not None: sections[current].append(line) return {k: "\n".join(v).strip() for k, v in sections.items()} def _extract_decision_badge(one_liner: str) -> tuple[str, str]: t = (one_liner or "").strip() if any(k in t for k in ["不建议", "否决", "不推荐"]): return ("不建议", "error") if any(k in t for k in ["谨慎", "有条件", "需要修改"]): return ("谨慎推进", "warning") if any(k in t for k in ["推荐", "可行", "建议推进"]): return ("推荐", "success") return ("结论", "info") def render_decision_summary(md: str) -> None: """以更产品化的方式渲染结论摘要。""" sections = _split_sections(md) one_liner = sections.get("一句话结论", "").strip() badge_text, badge_kind = _extract_decision_badge(one_liner) col1, col2 = st.columns([1, 5]) with col1: if badge_kind == "success": st.success(badge_text) elif badge_kind == "warning": st.warning(badge_text) elif badge_kind == "error": st.error(badge_text) else: st.info(badge_text) with col2: if one_liner: st.markdown(f"**{one_liner}**") else: st.markdown("**(未识别到一句话结论,请检查模型输出格式)**") st.markdown("---") c1, c2 = st.columns(2) with c1: st.subheader("🎯 关键决策要点") content = sections.get("关键决策要点(Top 5)", "").strip() st.markdown(content if content else "(暂无)") st.subheader("✅ 下一步行动") actions = sections.get("下一步行动清单", "").strip() items = [ re.sub(r"^-\s*\[.\]\s*", "", l).strip() for l in actions.splitlines() if l.strip().startswith("-") ] if items: for i, it in enumerate(items): st.checkbox(it, value=False, key=f"action_{i}") else: st.markdown(actions if actions else "(暂无)") with c2: st.subheader("⚠️ 主要风险") risks = sections.get("主要风险(Top 3)", "").strip() st.markdown(risks if risks else "(暂无)") st.subheader("❓ 需要澄清") qs = sections.get("需要进一步澄清的问题", "").strip() st.markdown(qs if qs else "(暂无)") def render_opinion_preview(opinion: str, limit: int = 160) -> str: """返回用于预览的短文本。""" if not opinion: return "" t = opinion.strip().replace("\n", " ") if len(t) <= limit: return t return t[:limit].rstrip() + "..." def summarize_round_opinions(opinions: dict, role_descriptions: dict, limit_per_role: int = 60) -> str: """把一轮 opinions 生成适合扫读的摘要(不调用模型,避免额外成本与不稳定)。 输出格式示例: - 📊 产品经理:xxx... - 💻 技术专家:xxx... """ if not opinions: return "" lines: list[str] = [] for role, text in opinions.items(): info = role_descriptions.get(role, {"icon": "👤", "name": role}) preview = render_opinion_preview(str(text or ""), limit=limit_per_role) if not preview: preview = "(本轮未生成/生成失败)" lines.append(f"- {info['icon']} {info['name']}:{preview}") return "\n".join(lines)