danielrosehill commited on
Commit
06b949d
Β·
1 Parent(s): c2bc8dc
.streamlit/config.toml DELETED
@@ -1,9 +0,0 @@
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,6 +1,8 @@
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
 
@@ -11,12 +13,12 @@ A Streamlit-based Hugging Face Space that transforms draft emails into clear, ac
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
 
@@ -38,7 +40,7 @@ UI generates:
38
 
39
  ```bash
40
  pip install -r requirements.txt
41
- streamlit run app.py
42
  ```
43
 
44
  ## About BLUF
 
1
+ # BLUF Email Formatter
2
 
3
+ A Gradio-based Hugging Face Space that transforms draft emails into clear, actionable communication using the **Bottom Line Up Front (BLUF)** methodology.
4
+
5
+ [Learn more about BLUF](https://hbr.org/2016/11/how-to-write-email-with-military-precision).
6
 
7
  ## Features
8
 
 
13
 
14
  ## BLUF Tags
15
 
16
+ - **ACTION:** Requests action from recipient
17
+ - **SIGN:** Signature request from recipient
18
  - **INFO:** Informational only; no action required
19
+ - **DECISION:** Requests decision from recipient
20
+ - **REQUEST:** Request/approval
21
+ - **COORD:** Information shared for visibility and coordination among team
22
 
23
  ## How It Works
24
 
 
40
 
41
  ```bash
42
  pip install -r requirements.txt
43
+ python app.py
44
  ```
45
 
46
  ## About BLUF
ai-pieces/bluf-headers/my-defs.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "prefix": "ACTION:",
4
+ "example": "ACTION: Submit Timesheets by Friday",
5
+ "description": "Request for action by recipient(s)."
6
+ },
7
+ {
8
+ "prefix": "SIGN:",
9
+ "example": "SIGN: Approval Needed on Contract Addendum",
10
+ "description": "Requested signature from recipient (digital or physical)."
11
+ },
12
+ {
13
+ "prefix": "INFO:",
14
+ "example": "INFO: New Parking Policy Effective October 1",
15
+ "description": "For information sharing only; no action requested."
16
+ },
17
+ {
18
+ "prefix": "DECISION:",
19
+ "example": "DECISION: Choose Office Supply Vendor by Friday",
20
+ "description": "Requesting decision"
21
+ },
22
+ {
23
+ "prefix": "REQUEST:",
24
+ "example": "REQUEST: Vacation Days Approval for Oct 5–12",
25
+ "description": "Approval request from recipient."
26
+ },
27
+ {
28
+ "prefix": "COORD:",
29
+ "example": "COORD: Schedule Product Launch Strategy Meeting",
30
+ "description": "Coordination with or by the recipient is needed."
31
+ }
32
+ ]
33
+
app.py CHANGED
@@ -1,19 +1,9 @@
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
  {
@@ -115,8 +105,14 @@ def get_system_prompt():
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:
@@ -136,141 +132,124 @@ def format_email_with_openai(email_text: str, api_key: str) -> Dict[str, Any]:
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()
 
 
1
+ import gradio as gr
2
  import openai
3
  import json
4
+ from typing import Dict, Any, Tuple
 
 
 
 
 
 
 
 
 
5
 
6
  # Load BLUF tags from JSON
 
7
  def load_bluf_tags():
8
  bluf_tags = [
9
  {
 
105
  * Never include extra commentary, markdown, or explanations outside the JSON.
106
  * Always return an **array** with one object."""
107
 
108
+ def format_email_with_openai(email_text: str, api_key: str) -> Tuple[str, str, str, str]:
109
  """Format email using OpenAI API"""
110
+ if not api_key:
111
+ return "❌ Error: Please provide your OpenAI API key", "", "", ""
112
+
113
+ if not email_text.strip():
114
+ return "❌ Error: Please provide email text to format", "", "", ""
115
+
116
  client = openai.OpenAI(api_key=api_key)
117
 
118
  try:
 
132
  parsed_result = json.loads(result)
133
 
134
  if isinstance(parsed_result, list) and len(parsed_result) > 0:
135
+ data = parsed_result[0]
136
  else:
137
+ data = parsed_result
138
+
139
+ subject = data.get('subject', '')
140
+ email_body = data.get('email', '')
141
+ bluf_tag = data.get('bluf_tag', '')
142
+ bluf_summary = data.get('bluf_summary', '')
143
+
144
+ status = "βœ… Email formatted successfully!"
145
+
146
+ return status, subject, email_body, f"**Tag:** {bluf_tag}\n\n{bluf_summary}"
147
 
148
  except json.JSONDecodeError as e:
149
+ return f"❌ Error: Failed to parse AI response as JSON: {e}", "", "", ""
 
150
  except Exception as e:
151
+ return f"❌ Error calling OpenAI API: {e}", "", "", ""
 
152
 
153
+ def create_bluf_reference():
154
+ """Create BLUF tags reference text"""
155
+ bluf_tags = load_bluf_tags()
156
+ reference_text = "## πŸ“š BLUF Tags Reference\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ for tag in bluf_tags:
159
+ reference_text += f"**{tag['prefix']}** - {tag['description']}\n"
160
+ reference_text += f"*Example: {tag['example']}*\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ reference_text += "\n---\n\n**About BLUF:** 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."
 
163
 
164
+ return reference_text
165
+
166
+ # Create Gradio interface
167
+ def create_interface():
168
+ """Create the main Gradio interface"""
 
 
 
 
169
 
170
+ with gr.Blocks(title="πŸ“§ BLUF Email Formatter", theme=gr.themes.Soft()) as demo:
171
+ gr.Markdown(
172
+ """
173
+ # πŸ“§ BLUF Email Formatter
174
+ Transform your emails into clear, actionable communication using the **Bottom Line Up Front** methodology.
175
+
176
+ **Instructions:**
177
+ 1. Enter your OpenAI API key
178
+ 2. Paste your draft email text
179
+ 3. Click "Format Email"
180
+ 4. Copy the formatted subject line and email body
181
+ """
182
+ )
183
 
184
+ with gr.Row():
185
+ with gr.Column(scale=1):
186
+ gr.Markdown("## πŸ”‘ Configuration")
187
+ api_key = gr.Textbox(
188
+ label="OpenAI API Key",
189
+ type="password",
190
+ placeholder="Enter your OpenAI API key...",
191
+ info="Your API key is not stored and only used for this session"
192
+ )
193
 
194
+ gr.Markdown("## πŸ“ Input")
195
+ email_input = gr.Textbox(
196
+ label="Draft Email Text",
197
+ 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.",
198
+ lines=8,
199
+ max_lines=15
200
+ )
201
+
202
+ format_btn = gr.Button("πŸš€ Format Email", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ with gr.Column(scale=1):
205
+ gr.Markdown("## ✨ Formatted Output")
206
+
207
+ status_output = gr.Markdown(label="Status")
208
+
209
+ with gr.Group():
210
+ gr.Markdown("### πŸ“¬ Subject Line")
211
+ subject_output = gr.Textbox(
212
+ label="Formatted Subject",
213
+ interactive=False,
214
+ show_copy_button=True
215
  )
216
+
217
+ with gr.Group():
218
+ gr.Markdown("### πŸ“§ Formatted Email")
219
+ email_output = gr.Textbox(
220
+ label="Formatted Email Body",
221
+ lines=8,
222
+ max_lines=15,
223
+ interactive=False,
224
+ show_copy_button=True
225
+ )
226
+
227
+ with gr.Group():
228
+ gr.Markdown("### πŸ’‘ BLUF Summary")
229
+ summary_output = gr.Markdown()
230
+
231
+ # BLUF Reference in an accordion
232
+ with gr.Accordion("πŸ“š BLUF Tags Reference", open=False):
233
+ gr.Markdown(create_bluf_reference())
234
+
235
+ # Set up the format button click event
236
+ format_btn.click(
237
+ fn=format_email_with_openai,
238
+ inputs=[email_input, api_key],
239
+ outputs=[status_output, subject_output, email_output, summary_output]
240
+ )
241
+
242
+ gr.Markdown(
243
+ """
244
+ ---
245
+ <div style='text-align: center; color: #666;'>
246
+ <p>Built with ❀️ using Gradio β€’ Learn more about <a href='https://en.wikipedia.org/wiki/BLUF_(communication)' target='_blank'>BLUF Communication</a></p>
247
+ </div>
248
+ """
249
+ )
250
 
251
+ return demo
 
 
 
 
 
 
 
 
 
252
 
253
  if __name__ == "__main__":
254
+ demo = create_interface()
255
+ demo.launch()
requirements.txt CHANGED
@@ -1,3 +1,2 @@
1
- streamlit>=1.28.0
2
  openai>=1.0.0
3
- pyperclip>=1.8.2
 
1
+ gradio>=4.0.0
2
  openai>=1.0.0