feat(dashboard): demo console with web chat, demo clock, scenario playback and split screen
- pages/2_Демо-консоль.py: persona selector, virtual clock bar (+1 h, to 08:00/20:00, next day, reset with confirm), fallback patient web chat (buttons from Reply.buttons, kind styling, outbox drained after every action and on a 2 s fragment poll), scenario panel - pages/3_Сплит-экран.py: patient chat or PATIENT_WEB_URL iframe on the left, compact doctor panel (patients, live alerts, red banner on a new acute alert) on the right - console/playback.py: data/synthetic/<persona>.jsonl runner (advance_to/press/text/ expect_color/start), date chaining between days, pause on failure with retry/skip/manual - console/api.py: own HTTP client over the frozen API contract, ApiError only Refs #17 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""Split screen for the projector (#17): patient on the left, doctor on the right, clock on top.
|
||||
|
||||
Left: the patient web app in an iframe when `PATIENT_WEB_URL` is set (React app, e.g. http://localhost:5173),
|
||||
otherwise the console's fallback web chat. Right: compact doctor panel polled every 2 s with a red banner
|
||||
on a new acute alert.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
|
||||
# `streamlit run dashboard/app.py` already puts dashboard/ on sys.path; this keeps the page runnable on its own.
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from console import widgets as w # noqa: E402
|
||||
|
||||
st.set_page_config(page_title="Сплит-экран", page_icon="🖥️", layout="wide", initial_sidebar_state="collapsed")
|
||||
st.markdown(
|
||||
"<style>.block-container{padding-top:1.2rem;max-width:100%} "
|
||||
"[data-testid='stMarkdownContainer'] p, [data-testid='stMarkdownContainer'] li{font-size:1.08rem}</style>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
persona = w.select_persona()
|
||||
w.show_flash()
|
||||
token = persona["token"]
|
||||
|
||||
top, play = st.columns([6, 1.3], vertical_alignment="center")
|
||||
with top:
|
||||
w.render_clock_bar(persona, key="split-clock", with_reset=False)
|
||||
with play:
|
||||
scenario, _ = w.scenario_for(persona)
|
||||
runner = w.make_runner(persona, delay=0.8)
|
||||
nxt = runner.next_day(scenario) if scenario.days else None
|
||||
clicked = st.button(
|
||||
f"▶ Сценарий: день {nxt.day}" if nxt else "Сценарий проигран",
|
||||
disabled=nxt is None,
|
||||
width="stretch",
|
||||
help="Проиграть следующий день из data/synthetic/<персона>.jsonl",
|
||||
)
|
||||
if clicked and nxt is not None:
|
||||
w.execute(runner.play_day(scenario, nxt.day), w.playback_state(token), title=f"День {nxt.day}")
|
||||
st.rerun()
|
||||
|
||||
left, right = st.columns([1.1, 1], gap="large")
|
||||
with left:
|
||||
url = w.patient_web_url()
|
||||
if url:
|
||||
st.markdown(f"#### 📱 Пациент — {persona.get('display_name') or token}")
|
||||
st.iframe(url, height=760)
|
||||
st.caption(f"Веб-приложение пациента: {url}")
|
||||
else:
|
||||
st.markdown(f"#### 💬 Пациент — {persona.get('display_name') or token}")
|
||||
w.render_chat(token, height=600, key="split-chat")
|
||||
with right:
|
||||
st.markdown("#### 🩺 Кабинет врача — д-р Смирнова Е. А.")
|
||||
w.polled_doctor_panel(token, key="split-doc")
|
||||
Reference in New Issue
Block a user