fugee / app /phases /documents.py
helmo's picture
[ux] Make all phase screens fill the shell at the same width
c7aa25a
Raw
History Blame Contribute Delete
6.81 kB
"""app/phases/documents.py — Phase 5 document package screen (T051/T052).
Matches mockup.html #phase-5: a document preview (with amber-tint highlights on
pre-filled fields), an evidence checklist (pre-checked from
``session.interview.documents_available``), the four generated files, a
"Download all" primary action, and a "Start over" ghost button.
The PDFs are generated by ``agent.tools.doc_generator`` from real session data.
"""
from __future__ import annotations
from dataclasses import dataclass
import gradio as gr
from agent.tools.doc_generator import all_files, generate, preview_statement_html, zip_package
from app.state.session import SessionState
# Evidence checklist: (label, sublabel, document_available key or None).
CHECKLIST = [
("Personal statement", "Drafted by Fugee · ready", "_always"),
("Identity document or sworn statement", "Passport, national ID, or a sworn statement", "passport"),
("Proof of journey / entry", "Tickets, stamps, or a written account", "travel"),
("Photos or messages showing the threat", "Optional, but strengthens your claim", "evidence"),
("Names of witnesses", "People who can confirm what happened", "witnesses"),
("Medical or police reports", "If you have any", "medical"),
]
DOCUMENTS_CSS = """
#docs-screen { background: var(--surface); border: 1px solid var(--line);
border-radius: var(--r-lg); box-shadow: var(--shadow-md); overflow: hidden;
max-width: 1180px; margin: 0 auto; }
.pkg { display:grid; grid-template-columns:1.1fr .9fr; gap:0; }
@media(max-width:820px){ .pkg { grid-template-columns:1fr; } }
.pkg__preview { padding:clamp(18px,4vw,28px); border-right:1px solid var(--line); background:var(--surface-2); }
@media(max-width:820px){ .pkg__preview { border-right:0; border-bottom:1px solid var(--line); } }
.pkg__side { padding:clamp(18px,4vw,28px); background:var(--surface); }
.pkg__lbl { font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted); font-weight:600; margin:0 0 12px; }
.doc { background:#fff; border:1px solid var(--line-strong); border-radius:var(--r-md); box-shadow:var(--shadow-sm);
padding:26px 28px; max-height:360px; overflow:hidden; position:relative; }
.doc::after { content:""; position:absolute; left:0; right:0; bottom:0; height:70px; background:linear-gradient(180deg,rgba(255,255,255,0),#fff); }
.doc h4 { font-family:var(--font-display); font-size:18px; font-weight:600; margin:0 0 2px; }
.doc .doc__sub { font-size:12px; color:var(--text-muted); margin:0 0 16px; }
.doc p { font-size:13px; line-height:1.7; color:#33373f; margin:0 0 11px; }
.doc .fill { background:var(--accent-tint); color:#7a431a; padding:0 4px; border-radius:3px; font-weight:600; }
.doc .blank { display:inline-block; min-width:90px; border-bottom:1px solid #aaa; }
.doc__divider { height:1px; background:var(--line); margin:14px 0; }
#docs-checklist .wrap label { font-size:14.5px !important; }
#docs-files .download-link, #docs-files label { font-size:13.5px; }
#docs-download, #docs-download button { background:var(--accent) !important; color:var(--on-accent) !important;
box-shadow:0 2px 0 var(--accent-deep) !important; border:0 !important; font-weight:600 !important;
border-radius:var(--r-md) !important; padding:14px 22px !important; }
#docs-download:hover, #docs-download button:hover { background:var(--accent-deep) !important; }
#docs-startover, #docs-startover button { background:transparent !important; color:var(--primary-deep) !important;
border:0 !important; box-shadow:none !important; font-weight:600 !important; }
#docs-startover:hover, #docs-startover button:hover { background:var(--primary-tint) !important; }
"""
@dataclass
class DocumentsUI:
column: gr.Column
populate: callable # (session) -> list of updates for render_outputs
render_outputs: list
start_over: gr.Button
session: gr.State
def _checklist_values(session: SessionState) -> list[str]:
"""Labels that should be pre-checked, from documents_available (SC-043)."""
available = {d.lower() for d in (session.interview.documents_available or [])}
checked = []
for label, _sub, key in CHECKLIST:
if key == "_always" or (key and key in available):
checked.append(label)
return checked
def build(visible: bool = False, session_st: gr.State | None = None) -> DocumentsUI:
session_st = session_st or gr.State(None)
with gr.Column(visible=visible, elem_classes=["screen-wrap"]) as column:
with gr.Column(elem_id="docs-screen"):
with gr.Row(elem_classes=["pkg"]):
with gr.Column(elem_classes=["pkg__preview"]):
gr.HTML('<p class="pkg__lbl">Preview · Personal statement</p>')
preview = gr.HTML('<article class="doc"></article>')
with gr.Column(elem_classes=["pkg__side"]):
gr.HTML('<p class="pkg__lbl">Evidence to gather</p>')
checklist = gr.CheckboxGroup(
choices=[label for label, _s, _k in CHECKLIST],
value=[], label="", show_label=False, elem_id="docs-checklist",
)
gr.HTML('<p class="pkg__lbl">Your files · Word (editable) + PDF</p>')
files = gr.File(label="", show_label=False, interactive=False, elem_id="docs-files")
download_all = gr.DownloadButton(
"⤓ Download all (Word + PDF)", elem_id="docs-download", visible=False
)
start_over = gr.Button(
"↺ Start over for a different country", elem_id="docs-startover"
)
render_outputs = [preview, checklist, files, download_all]
async def populate(session: SessionState) -> list:
# The agent drafts the personal statement (LLM), then we render PDF + Word.
from agent.drafting import draft_personal_statement
try:
statement = await draft_personal_statement(session)
except Exception:
statement = None
if not statement:
from agent.drafting import fallback_statement
statement = fallback_statement(session)
docs = generate(session, statement=statement)
zip_path = zip_package(docs)
return [
gr.update(value=preview_statement_html(session, statement)),
gr.update(value=_checklist_values(session)),
gr.update(value=all_files(docs)),
gr.update(value=str(zip_path), visible=True),
]
return DocumentsUI(
column=column, populate=populate, render_outputs=render_outputs,
start_over=start_over, session=session_st,
)
__all__ = ["build", "DocumentsUI", "DOCUMENTS_CSS", "CHECKLIST"]