107 lines
5.1 KiB
Python
107 lines
5.1 KiB
Python
"""Scenario playback panel of the demo console (#17): day list, «Проиграть день N», «Прокрутить до дня N»."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import streamlit as st
|
|
|
|
from . import widgets as w
|
|
from .playback import PlaybackState
|
|
from .scenario import Scenario
|
|
|
|
|
|
def _forget_upload(token: str) -> None:
|
|
st.session_state.pop(f"console_scenario_text:{token}", None)
|
|
st.session_state.pop(f"console_up:{token}", None) # resets the uploader widget too
|
|
|
|
|
|
def _scenario_source_controls(persona: dict[str, Any], source: str) -> None:
|
|
token = persona["token"]
|
|
with st.expander(f"Файл сценария: {source}"):
|
|
st.text_input(
|
|
"Путь в репозитории",
|
|
key=f"console_scenario_path:{token}",
|
|
placeholder=str(persona.get("scenario") or "data/synthetic/andrey.jsonl"),
|
|
)
|
|
upload = st.file_uploader("…или загрузить .jsonl", type=["jsonl", "json", "txt"], key=f"console_up:{token}")
|
|
if upload is not None:
|
|
try:
|
|
st.session_state[f"console_scenario_text:{token}"] = upload.getvalue().decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
st.error("Файл не в UTF-8")
|
|
if st.session_state.get(f"console_scenario_text:{token}"):
|
|
st.button("Забыть загруженный файл", on_click=_forget_upload, args=(token,))
|
|
|
|
|
|
def _day_table(scenario: Scenario, state: PlaybackState, next_day: int | None) -> None:
|
|
rows = []
|
|
for d in scenario.days:
|
|
mark = "✅" if d.day in state.done else ("▶️" if d.day == next_day else "")
|
|
rows.append({"": mark, "День": d.day, "Шагов": len(d.actions), "Подсказка ведущему": d.note})
|
|
st.dataframe(rows, hide_index=True, width="stretch", height=min(38 + 35 * len(rows), 330))
|
|
|
|
|
|
def _clear_failure(state: PlaybackState) -> None:
|
|
state.failed = None
|
|
state.position = None
|
|
|
|
|
|
def render_playback_panel(persona: dict[str, Any]) -> None:
|
|
token = persona["token"]
|
|
scenario, source = w.scenario_for(persona)
|
|
state = w.playback_state(token)
|
|
st.subheader("Сценарий демо")
|
|
_scenario_source_controls(persona, source)
|
|
if scenario.errors:
|
|
with st.expander(f"⚠️ Замечания к сценарию: {len(scenario.errors)}", expanded=not scenario.days):
|
|
for err in scenario.errors:
|
|
st.write(f"- {err}")
|
|
if not scenario.days:
|
|
st.info("Сценарий не загружен. Демо можно вести вручную: чат слева и кнопки часов сверху.")
|
|
return
|
|
|
|
delay = st.slider("Пауза между шагами, с", 0.0, 3.0, 1.0, 0.25, key=f"console_delay:{token}")
|
|
runner = w.make_runner(persona, delay=delay)
|
|
nxt = runner.next_day(scenario)
|
|
_day_table(scenario, state, nxt.day if nxt else None)
|
|
|
|
numbers = scenario.day_numbers
|
|
default = numbers.index(nxt.day) if nxt else len(numbers) - 1
|
|
day = st.selectbox("День", numbers, index=default, key=f"console_day:{token}", format_func=lambda n: f"день {n}")
|
|
c1, c2, c3 = st.columns(3)
|
|
action: tuple[str, Any] | None = None
|
|
if c1.button(f"▶ Проиграть день {day}", type="primary", width="stretch"):
|
|
action = ("Проигрываю день", runner.play_day(scenario, day))
|
|
if c2.button(f"⏭ Прокрутить до дня {day}", width="stretch", help="Быстро, без пауз: все непроигранные дни"):
|
|
runner.delay = 0.0
|
|
action = (f"Прокрутка до дня {day}", runner.scroll_to(scenario, day))
|
|
if c3.button(
|
|
f"▶ Следующий: день {nxt.day}" if nxt else "Все дни проиграны",
|
|
width="stretch",
|
|
disabled=nxt is None,
|
|
):
|
|
assert nxt is not None
|
|
action = ("Проигрываю день", runner.play_day(scenario, nxt.day))
|
|
|
|
point = state.failed or state.position
|
|
if point is not None and action is None:
|
|
what = "остановлено на ошибке" if state.failed else "прервано"
|
|
st.warning(f"Проигрывание {what}: день {point.day}, шаг {point.step + 1}.")
|
|
r1, r2, r3 = st.columns(3)
|
|
if r1.button("🔁 Повторить шаг", width="stretch"):
|
|
action = ("Продолжаю", runner.resume(scenario, skip=False))
|
|
if r2.button("⏩ Пропустить шаг", width="stretch"):
|
|
action = ("Продолжаю", runner.resume(scenario, skip=True))
|
|
r3.button("✋ Продолжить вручную", width="stretch", on_click=_clear_failure, args=(state,))
|
|
|
|
if action is not None:
|
|
title, logs = action
|
|
w.execute(logs, state, title=title)
|
|
st.session_state["console_log_open"] = True
|
|
st.rerun() # refresh the chat and the clock with what playback produced
|
|
|
|
if state.log:
|
|
with st.expander("Журнал последнего проигрывания", expanded=st.session_state.pop("console_log_open", False)):
|
|
st.text("\n".join(state.log[-80:]))
|