danielrosehill commited on
Commit
c2bc8dc
Β·
1 Parent(s): 6cd62e1
Files changed (4) hide show
  1. .streamlit/config.toml +9 -0
  2. README.md +45 -2
  3. app.py +276 -0
  4. requirements.txt +3 -0
.streamlit/config.toml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ primaryColor = "#ff6b6b"
3
+ backgroundColor = "#ffffff"
4
+ secondaryBackgroundColor = "#f0f2f6"
5
+ textColor = "#262730"
6
+
7
+ [server]
8
+ headless = true
9
+ port = 7860
README.md CHANGED
@@ -1,3 +1,46 @@
1
- # BLUF Email Formatter HF Space
2
 
3
- (WIP)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # BLUF Email Formatter (HF Space Demo)
2
 
3
+ A Streamlit-based Hugging Face Space that transforms draft emails into clear, actionable communication using the **Bottom Line Up Front (BLUF)** methodology.
4
+
5
+ ## Features
6
+
7
+ - **Bring Your Own Key**: Use your OpenAI API key for processing
8
+ - **Easy Copy**: One-click copying for subject lines and formatted emails
9
+ - **BLUF Tags Reference**: Built-in guide to all BLUF communication tags
10
+ - **Clean UI**: Intuitive interface with side-by-side input/output
11
+
12
+ ## BLUF Tags
13
+
14
+ - **ACTION:** Recipient must take an action
15
+ - **SIGN:** Recipient needs to sign a document
16
+ - **INFO:** Informational only; no action required
17
+ - **DECISION:** Recipient must make a decision
18
+ - **REQUEST:** Sender is asking for permission or approval
19
+ - **COORD:** Coordination with or by the recipient is needed
20
+
21
+ ## How It Works
22
+
23
+ 1. Enter your OpenAI API key in the sidebar
24
+ 2. Paste your draft email text
25
+ 3. Click "Format Email"
26
+ 4. Copy the formatted subject line and email body
27
+ 5. Use the BLUF summary for reference
28
+
29
+ ## Output Format
30
+
31
+ UI generates:
32
+
33
+ - **Subject Line**: BLUF tag + concise summary
34
+ - **Formatted Email**: Structured with BLUF methodology
35
+ - **BLUF Summary**: Two-sentence plain language summary
36
+
37
+ ## πŸ”§ Local Development
38
+
39
+ ```bash
40
+ pip install -r requirements.txt
41
+ streamlit run app.py
42
+ ```
43
+
44
+ ## About BLUF
45
+
46
+ Bottom Line Up Front (BLUF) is a communication technique that puts the most important information at the beginning of a message. It helps recipients quickly understand the purpose and required actions.
app.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import openai
3
+ import json
4
+ from typing import Dict, Any
5
+ import pyperclip
6
+
7
+ # Page configuration
8
+ st.set_page_config(
9
+ page_title="BLUF Email Formatter",
10
+ page_icon="πŸ“§",
11
+ layout="wide",
12
+ initial_sidebar_state="expanded"
13
+ )
14
+
15
+ # Load BLUF tags from JSON
16
+ @st.cache_data
17
+ def load_bluf_tags():
18
+ bluf_tags = [
19
+ {
20
+ "prefix": "ACTION:",
21
+ "example": "ACTION: Submit Timesheets by Friday",
22
+ "description": "Recipient must take an action."
23
+ },
24
+ {
25
+ "prefix": "SIGN:",
26
+ "example": "SIGN: Approval Needed on Contract Addendum",
27
+ "description": "Recipient needs to sign a document."
28
+ },
29
+ {
30
+ "prefix": "INFO:",
31
+ "example": "INFO: New Parking Policy Effective October 1",
32
+ "description": "Informational only; no action required."
33
+ },
34
+ {
35
+ "prefix": "DECISION:",
36
+ "example": "DECISION: Choose Office Supply Vendor by Friday",
37
+ "description": "Recipient must make a decision."
38
+ },
39
+ {
40
+ "prefix": "REQUEST:",
41
+ "example": "REQUEST: Vacation Days Approval for Oct 5–12",
42
+ "description": "Sender is asking for permission or approval."
43
+ },
44
+ {
45
+ "prefix": "COORD:",
46
+ "example": "COORD: Schedule Product Launch Strategy Meeting",
47
+ "description": "Coordination with or by the recipient is needed."
48
+ }
49
+ ]
50
+ return bluf_tags
51
+
52
+ def get_system_prompt():
53
+ return """1. **Input**:
54
+ The user will provide the **draft body text** of an email.
55
+
56
+ 2. **Output**:
57
+ You must respond **only in JSON**. The output should always be a JSON array with a single object containing four fields:
58
+
59
+ * `"subject"` β†’ A concise subject line for the email. It must:
60
+ * Begin with the most appropriate **BLUF tag** from the list below.
61
+ * Follow with a clear subject summary.
62
+
63
+ * `"bluf_tag"` β†’ The BLUF tag used (e.g., `"ACTION:"`, `"INFO:"`).
64
+
65
+ * `"bluf_summary"` β†’ A two-sentence summary of the email, written in plain language.
66
+
67
+ * `"email"` β†’ The rewritten email body. It must contain:
68
+ 1. `Bottom Line Up Front` on its own line.
69
+ 2. An empty line.
70
+ 3. `Purpose: {BLUF tag}` on its own line.
71
+ 4. One empty line.
72
+ 5. The **BLUF summary**.
73
+ 6. One empty line.
74
+ 7. The **original email text** (verbatim).
75
+
76
+ 3. **BLUF Tags** (choose the most fitting):
77
+
78
+ ```json
79
+ [
80
+ {
81
+ "prefix": "ACTION:",
82
+ "example": "ACTION: Submit Timesheets by Friday",
83
+ "description": "Recipient must take an action."
84
+ },
85
+ {
86
+ "prefix": "SIGN:",
87
+ "example": "SIGN: Approval Needed on Contract Addendum",
88
+ "description": "Recipient needs to sign a document."
89
+ },
90
+ {
91
+ "prefix": "INFO:",
92
+ "example": "INFO: New Parking Policy Effective October 1",
93
+ "description": "Informational only; no action required."
94
+ },
95
+ {
96
+ "prefix": "DECISION:",
97
+ "example": "DECISION: Choose Office Supply Vendor by Friday",
98
+ "description": "Recipient must make a decision."
99
+ },
100
+ {
101
+ "prefix": "REQUEST:",
102
+ "example": "REQUEST: Vacation Days Approval for Oct 5–12",
103
+ "description": "Sender is asking for permission or approval."
104
+ },
105
+ {
106
+ "prefix": "COORD:",
107
+ "example": "COORD: Schedule Product Launch Strategy Meeting",
108
+ "description": "Coordination with or by the recipient is needed."
109
+ }
110
+ ]
111
+ ```
112
+
113
+ 4. **Constraints**:
114
+ * Respond **only in JSON**.
115
+ * Never include extra commentary, markdown, or explanations outside the JSON.
116
+ * Always return an **array** with one object."""
117
+
118
+ def format_email_with_openai(email_text: str, api_key: str) -> Dict[str, Any]:
119
+ """Format email using OpenAI API"""
120
+ client = openai.OpenAI(api_key=api_key)
121
+
122
+ try:
123
+ response = client.chat.completions.create(
124
+ model="gpt-4",
125
+ messages=[
126
+ {"role": "system", "content": get_system_prompt()},
127
+ {"role": "user", "content": email_text}
128
+ ],
129
+ temperature=0.3,
130
+ max_tokens=1000
131
+ )
132
+
133
+ result = response.choices[0].message.content.strip()
134
+
135
+ # Parse JSON response
136
+ parsed_result = json.loads(result)
137
+
138
+ if isinstance(parsed_result, list) and len(parsed_result) > 0:
139
+ return parsed_result[0]
140
+ else:
141
+ return parsed_result
142
+
143
+ except json.JSONDecodeError as e:
144
+ st.error(f"Failed to parse AI response as JSON: {e}")
145
+ return None
146
+ except Exception as e:
147
+ st.error(f"Error calling OpenAI API: {e}")
148
+ return None
149
+
150
+ def copy_to_clipboard_js(text: str, button_id: str):
151
+ """Generate JavaScript for copying text to clipboard"""
152
+ return f"""
153
+ <script>
154
+ function copyToClipboard_{button_id}() {{
155
+ navigator.clipboard.writeText(`{text}`).then(function() {{
156
+ console.log('Copied to clipboard');
157
+ }}, function(err) {{
158
+ console.error('Could not copy text: ', err);
159
+ }});
160
+ }}
161
+ </script>
162
+ <button onclick="copyToClipboard_{button_id}()" style="
163
+ background-color: #ff6b6b;
164
+ color: white;
165
+ border: none;
166
+ padding: 8px 16px;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ font-size: 14px;
170
+ margin-left: 10px;
171
+ ">πŸ“‹ Copy</button>
172
+ """
173
+
174
+ # Main app
175
+ def main():
176
+ st.title("πŸ“§ BLUF Email Formatter")
177
+ st.markdown("Transform your emails into clear, actionable communication using the **Bottom Line Up Front** methodology.")
178
+
179
+ # Sidebar for API key and BLUF info
180
+ with st.sidebar:
181
+ st.header("πŸ”‘ Configuration")
182
+ api_key = st.text_input(
183
+ "OpenAI API Key",
184
+ type="password",
185
+ help="Enter your OpenAI API key to use the formatter"
186
+ )
187
+
188
+ st.header("πŸ“š BLUF Tags Reference")
189
+ bluf_tags = load_bluf_tags()
190
+
191
+ for tag in bluf_tags:
192
+ with st.expander(f"{tag['prefix']} - {tag['description']}"):
193
+ st.write(f"**Example:** {tag['example']}")
194
+ st.write(f"**Use when:** {tag['description']}")
195
+
196
+ # Main content area
197
+ col1, col2 = st.columns([1, 1])
198
+
199
+ with col1:
200
+ st.header("πŸ“ Input Email")
201
+ email_input = st.text_area(
202
+ "Paste your draft email text here:",
203
+ height=300,
204
+ placeholder="Hi team, just reminding everyone that timesheets for this week are due on Friday. Please make sure to submit them by then so payroll can be processed."
205
+ )
206
+
207
+ format_button = st.button("πŸš€ Format Email", type="primary", disabled=not api_key or not email_input)
208
+
209
+ with col2:
210
+ st.header("✨ Formatted Output")
211
+
212
+ if format_button and api_key and email_input:
213
+ with st.spinner("Formatting your email..."):
214
+ result = format_email_with_openai(email_input, api_key)
215
+
216
+ if result:
217
+ # Store result in session state
218
+ st.session_state.formatted_result = result
219
+
220
+ # Display results if available
221
+ if hasattr(st.session_state, 'formatted_result') and st.session_state.formatted_result:
222
+ result = st.session_state.formatted_result
223
+
224
+ # Subject line with copy button
225
+ st.subheader("πŸ“¬ Subject Line")
226
+ subject_container = st.container()
227
+ with subject_container:
228
+ col_subject, col_copy_subject = st.columns([4, 1])
229
+ with col_subject:
230
+ st.code(result.get('subject', ''), language=None)
231
+ with col_copy_subject:
232
+ if st.button("πŸ“‹", key="copy_subject", help="Copy subject line"):
233
+ try:
234
+ pyperclip.copy(result.get('subject', ''))
235
+ st.success("Copied!")
236
+ except:
237
+ st.info("Copy manually from above")
238
+
239
+ # Formatted email with copy button
240
+ st.subheader("πŸ“§ Formatted Email")
241
+ email_container = st.container()
242
+ with email_container:
243
+ col_email, col_copy_email = st.columns([4, 1])
244
+ with col_email:
245
+ st.text_area(
246
+ "Formatted email body:",
247
+ value=result.get('email', ''),
248
+ height=200,
249
+ key="formatted_email_display"
250
+ )
251
+ with col_copy_email:
252
+ if st.button("πŸ“‹", key="copy_email", help="Copy formatted email"):
253
+ try:
254
+ pyperclip.copy(result.get('email', ''))
255
+ st.success("Copied!")
256
+ except:
257
+ st.info("Copy manually from above")
258
+
259
+ # BLUF Summary (display only)
260
+ st.subheader("πŸ’‘ BLUF Summary")
261
+ st.info(f"**Tag:** {result.get('bluf_tag', '')}")
262
+ st.write(result.get('bluf_summary', ''))
263
+
264
+ # Footer
265
+ st.markdown("---")
266
+ st.markdown(
267
+ """
268
+ <div style='text-align: center; color: #666;'>
269
+ <p>Built with ❀️ using Streamlit β€’ Learn more about <a href='https://en.wikipedia.org/wiki/BLUF_(communication)' target='_blank'>BLUF Communication</a></p>
270
+ </div>
271
+ """,
272
+ unsafe_allow_html=True
273
+ )
274
+
275
+ if __name__ == "__main__":
276
+ main()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ streamlit>=1.28.0
2
+ openai>=1.0.0
3
+ pyperclip>=1.8.2