leonardklin's picture
Upload 328 files
978fed5 verified
"""
Display Components for Streamlit Interface
Reusable components for displaying workflow outputs.
"""
import time
import streamlit as st
from scider.core.approval import ApprovalResponse, ApprovalResult
def render_live_intermediate(items: list[dict]) -> None:
"""Render intermediate state items as they arrive (real-time)."""
if not items:
return
for item in items:
node = item.get("node_name", "unknown")
output = item.get("output", "")
with st.status(f"Node: {node}", state="complete"):
st.markdown(output[:800] if output else "(no output)")
_APPROVAL_CONTENT_TRUNCATE = 800
def render_approval_ui(handler) -> None:
"""Render approval buttons + feedback input for a pending approval request."""
pending = handler.get_pending()
if not pending:
return
summary = pending["summary"]
node_name = pending["node_name"]
title = pending.get("title", "")
# Highlighted header with title
title_html = (
f"<br><span style='font-size: 16px; font-weight: 600; color: #856404;'>{title}</span>"
if title
else ""
)
st.markdown(
f"""<div style="background: #fff3cd; border-left: 4px solid #ffc107;
padding: 12px 16px; border-radius: 6px; margin-bottom: 8px;">
<span style="font-size: 18px; font-weight: 700;">⚠️ Approval required: {node_name}</span>
{title_html}
</div>""",
unsafe_allow_html=True,
)
# Approval content in a bordered container, collapsible if long
if len(summary) > _APPROVAL_CONTENT_TRUNCATE:
preview_lines = summary[:200].split("\n")
label = " | ".join(line.strip() for line in preview_lines if line.strip())[:200]
with st.expander(f"{label}...", expanded=True):
st.markdown(summary)
else:
with st.container(border=True):
st.markdown(summary)
# Selection UI (radio buttons for single select)
selected_index = None
if pending.get("has_selection") and pending.get("items"):
items = pending["items"]
options = [f"{it.get('title', f'Item {i + 1}')}" for i, it in enumerate(items)]
selected_label = st.radio("Select an idea:", options, key="idea_selection")
selected_index = options.index(selected_label)
# Show description of selected item
selected_item = items[selected_index]
if selected_item.get("description"):
st.caption(selected_item["description"])
# Use a counter-based key so each new approval request gets a fresh input
approval_count = st.session_state.get("_approval_count", 0)
feedback_text = st.text_input(
"Feedback (required for feedback option)",
key=f"approval_feedback_input_{approval_count}",
placeholder="Enter your feedback here...",
)
# Buttons β€” use_container_width for equal sizing
col1, col2, col3 = st.columns(3)
with col1:
label = "Approve selected" if selected_index is not None else "Approve"
if st.button(f"βœ… {label}", key="btn_approve", use_container_width=True):
st.session_state.messages.append(
{"role": "user", "content": f"βœ… Approved [{node_name}]"}
)
st.session_state["_approval_count"] = approval_count + 1
handler.submit_response(
ApprovalResponse(ApprovalResult.APPROVED, selected_index=selected_index)
)
st.rerun()
with col2:
if st.button("❌ Reject", key="btn_reject", use_container_width=True):
st.session_state.messages.append(
{"role": "user", "content": f"❌ Rejected [{node_name}]"}
)
st.session_state["_approval_count"] = approval_count + 1
handler.submit_response(ApprovalResponse(ApprovalResult.REJECTED))
st.rerun()
with col3:
if st.button(
"πŸ’¬ Feedback",
key="btn_feedback",
use_container_width=True,
disabled=not (feedback_text and feedback_text.strip()),
):
st.session_state.messages.append(
{
"role": "user",
"content": f"πŸ’¬ Feedback [{node_name}]: {feedback_text.strip()}",
}
)
st.session_state["_approval_count"] = approval_count + 1
handler.submit_response(
ApprovalResponse(ApprovalResult.FEEDBACK, feedback=feedback_text.strip())
)
st.rerun()