hyx/ui/rendering.py

148 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""用于把模型生成的 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)