Spaces:
Sleeping
Sleeping
Commit
Β·
06b949d
1
Parent(s):
c2bc8dc
commit
Browse files- .streamlit/config.toml +0 -9
- README.md +10 -8
- ai-pieces/bluf-headers/my-defs.json +33 -0
- app.py +114 -135
- requirements.txt +1 -2
.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
|
2 |
|
3 |
-
A
|
|
|
|
|
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:**
|
15 |
-
- **SIGN:**
|
16 |
- **INFO:** Informational only; no action required
|
17 |
-
- **DECISION:**
|
18 |
-
- **REQUEST:**
|
19 |
-
- **COORD:**
|
20 |
|
21 |
## How It Works
|
22 |
|
@@ -38,7 +40,7 @@ UI generates:
|
|
38 |
|
39 |
```bash
|
40 |
pip install -r requirements.txt
|
41 |
-
|
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
|
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) ->
|
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 |
-
|
140 |
else:
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
|
143 |
except json.JSONDecodeError as e:
|
144 |
-
|
145 |
-
return None
|
146 |
except Exception as e:
|
147 |
-
|
148 |
-
return None
|
149 |
|
150 |
-
def
|
151 |
-
"""
|
152 |
-
|
153 |
-
|
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 |
-
|
180 |
-
|
181 |
-
|
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 |
-
|
197 |
-
col1, col2 = st.columns([1, 1])
|
198 |
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
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
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
|
212 |
-
|
213 |
-
with
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
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 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
with
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
)
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
|
264 |
-
|
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 |
-
|
|
|
|
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 |
-
|
2 |
openai>=1.0.0
|
3 |
-
pyperclip>=1.8.2
|
|
|
1 |
+
gradio>=4.0.0
|
2 |
openai>=1.0.0
|
|