-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
155 lines (141 loc) · 5.71 KB
/
Copy pathapp.py
File metadata and controls
155 lines (141 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import json
import logging
import streamlit as st
import streamlit.components.v1 as components
from email_assistant.client import create_client, get_model_name
from email_assistant.models import EmailRequest
from email_assistant.reflection import run_reflection
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger(__name__)
def render_copy_button(subject: str, body: str) -> None:
"""Render a local browser clipboard button for the final email."""
email_text = f"Subject: {subject}\n\n{body}"
clipboard_value = json.dumps(email_text)
components.html(
f"""
<style>
button {{
border: 1px solid #c7cbd1;
border-radius: 6px;
background: white;
color: #31333f;
padding: 0.35rem 0.8rem;
cursor: pointer;
font-size: 0.9rem;
}}
button:hover {{ background: #f0f2f6; }}
#message {{ margin-left: 0.5rem; color: #16803c; }}
</style>
<button onclick="copyEmail()">📋 Copy email</button>
<span id="message"></span>
<script>
function copyEmail() {{
navigator.clipboard.writeText({clipboard_value}).then(function() {{
document.getElementById("message").textContent = "Copied";
}}).catch(function() {{
document.getElementById("message").textContent = "Copy failed";
}});
}}
</script>
""",
height=45,
)
st.set_page_config(page_title="Email Reflection Assistant", page_icon="✉️")
st.markdown(
"""
<style>
body, .stApp, .stApp * {
unicode-bidi: plaintext;
direction: initial !important;
}
[data-testid="stMainBlockContainer"] {
max-width: 900px;
padding-top: 3.5rem !important;
padding-bottom: 1rem;
}
h1 { font-size: 2rem !important; margin-bottom: 0.15rem !important; }
[data-testid="stCaptionContainer"] { margin-bottom: 0.6rem; }
[data-testid="stForm"] { padding: 0.7rem 0.9rem; }
[data-testid="stVerticalBlock"] { gap: 0.45rem; }
</style>
""",
unsafe_allow_html=True,
)
st.title("✉️ Email Reflection Assistant")
st.caption("Draft, critique, and improve an email with a reflection loop.")
with st.form("email_request"):
title_col, style_col = st.columns([1.5, 1])
with title_col:
title = st.text_input("Email purpose or title", placeholder="Reschedule tomorrow's meeting")
with style_col:
style = st.text_input("Tone and style", value="Professional and friendly")
sender_col, receiver_col = st.columns(2)
with sender_col:
sender = st.text_area(
"Your context",
placeholder="Who are you and what should the email say?",
height=105,
)
with receiver_col:
receiver = st.text_area(
"Recipient context",
placeholder="Who will receive this email?",
height=105,
)
initial_draft = st.text_area("Existing draft (optional)", height=85)
settings_col, button_col = st.columns([1.5, 1])
with settings_col:
max_iterations = st.slider("Maximum reflection iterations", 1, 3, 1)
with button_col:
st.write("")
submitted = st.form_submit_button("✉️ Write email", type="primary", use_container_width=True)
if submitted:
logger.info("UI: email generation requested")
if not sender.strip() or not receiver.strip():
logger.warning("UI: request rejected because required context is missing")
st.error("Please provide both your context and the recipient context.")
else:
progress = None
try:
progress = st.status("Starting email reflection…", expanded=True)
status = progress
def show_progress(message: str) -> None:
status.write(f"• {message}")
with st.spinner("Writing and reviewing your email…"):
result = run_reflection(
EmailRequest(
email_title=title or None,
sender_context=sender,
receiver_context=receiver,
style=style or None,
initial_draft=initial_draft or None,
),
create_client(),
get_model_name(),
max_iterations=max_iterations,
progress_callback=show_progress,
)
progress.update(
label=f"Reflection complete after {result.iterations} iteration(s)",
state="complete",
)
st.success(f"Completed after {result.iterations} iteration(s).")
st.subheader(result.email.subject)
render_copy_button(result.email.subject, result.email.body)
st.text_area("Final email", value=result.email.body, height=180)
with st.expander("Review details"):
st.write(result.final_critique.summary)
if result.final_critique.issues:
for issue in result.final_critique.issues:
st.markdown(f"**{issue.category}:** {issue.problem}")
st.caption(f"Suggested fix: {issue.suggested_fix}")
st.json(result.model_dump())
except Exception as exc:
logger.exception("UI: email generation failed")
if progress is not None:
progress.update(label="Reflection failed", state="error")
progress.write(f"• Error: {exc}")
st.error(f"Unable to generate the email: {exc}")