SciDER / streamlit-client /forms /case_study.py
leonardklin's picture
Upload 355 files
1499363 verified
"""Case study viewer — browse saved workflow conversations without API keys."""
import json
from pathlib import Path
import streamlit as st
# Search paths for saved conversations and case-study-memory examples
_APP_ROOT = Path(__file__).resolve().parent.parent # streamlit-client/
_PROJECT_ROOT = _APP_ROOT.parent # SciDER/
_CASE_STUDY_DIRS = [
_APP_ROOT / "saved_chats", # conversations saved by the app's own workflows
_PROJECT_ROOT / "case-study-memory", # pre-bundled example case studies
_APP_ROOT / "case-study-memory",
]
def list_case_studies() -> list[tuple[Path, dict]]:
"""List all chat_history.json files in case-study-memory directories."""
results = []
seen = set()
for base in _CASE_STUDY_DIRS:
if not base.exists():
continue
dirs = sorted(
[d for d in base.iterdir() if d.is_dir() and (d / "chat_history.json").exists()],
key=lambda x: x.stat().st_mtime,
reverse=True,
)
for memo_dir in dirs:
chat_file = memo_dir / "chat_history.json"
key = str(chat_file.resolve())
if key in seen:
continue
seen.add(key)
try:
with open(chat_file, encoding="utf-8") as f:
data = json.load(f)
ts = data.get("timestamp", "")[:19].replace("T", " ")
wf = data.get("workflow_type", "unknown")
query = data.get("metadata", {}).get("query", "")
label = (query[:80] + "...") if len(query) > 83 else (query or memo_dir.name)
results.append((chat_file, {"label": label, "timestamp": ts, "workflow_type": wf}))
except Exception:
results.append(
(
chat_file,
{"label": memo_dir.name, "timestamp": "", "workflow_type": "unknown"},
)
)
return results
_WORKFLOW_LABELS = {
"ideation": "Ideation",
"data": "Data Analysis",
"experiment": "Experiment",
"full": "Full Pipeline",
}
def render_case_study_viewer():
"""Render the case study browser UI."""
st.markdown(
"""<div class='scider-header'>
<span class='scider-header-icon'>📚</span>
<div>
<div class='scider-header-title'>Case Studies</div>
<div class='scider-header-sub'>Browse saved workflow conversations and workspace artifacts</div>
</div>
</div>""",
unsafe_allow_html=True,
)
available = list_case_studies()
if not available:
st.info("No case studies found. Run a workflow first to save conversations.")
return
options = [f"{m['label']} ({m['workflow_type']}, {m['timestamp']})" for _, m in available]
idx = st.selectbox(
"Select a case study",
range(len(options)),
format_func=lambda i: options[i],
key="case_study_select",
)
selected_path = available[idx][0]
selected_meta = available[idx][1]
with open(selected_path, encoding="utf-8") as f:
data = json.load(f)
# Metadata card
wf_type = selected_meta["workflow_type"]
wf_label = _WORKFLOW_LABELS.get(wf_type, wf_type.title())
query = data.get("metadata", {}).get("query", selected_meta["label"])
ts = selected_meta["timestamp"]
st.markdown(
f"""<div class='approval-card'>
<p class='approval-card-title'>{query}</p>
<p class='approval-card-subtitle'>{wf_label} &nbsp;·&nbsp; {ts}</p>
</div>""",
unsafe_allow_html=True,
)
# Workflow type badge + message count
msg_count = len(data.get("messages", []))
st.markdown(
f"<span class='chat-agent-badge'>{wf_label}</span>&nbsp;&nbsp;"
f"<span style='color:#64748b;font-size:0.85rem'>{msg_count} messages</span>",
unsafe_allow_html=True,
)
st.divider()
messages = data.get("messages", [])
for m in messages:
with st.chat_message(m["role"]):
st.markdown(m["content"])
# Show workspace files if available
from components.file_browser import render_file_browser, render_workspace_download
case_dir = selected_path.parent
ws_dir = case_dir / "workspace"
browse_dir = ws_dir if ws_dir.exists() else case_dir
render_file_browser(browse_dir, key_prefix="cs_fb")
render_workspace_download(browse_dir, key_prefix="cs_dl")