ZENLLC commited on
Commit
db9eea1
Β·
verified Β·
1 Parent(s): cb65227

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -0
app.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Tuple
3
+
4
+ import gradio as gr
5
+ from openai import OpenAI
6
+
7
+ APP_TITLE = "ZEN Executive Briefing Desk"
8
+ DEFAULT_MODEL = "gpt-4.1-mini" # learners can change this to gpt-5.x when available
9
+
10
+ SYSTEM_PROMPT = """
11
+ You are the ZEN Executive Briefing Copilot.
12
+
13
+ You help busy professionals transform raw inputs (notes, transcripts, emails, web research,
14
+ brain dumps) into crisp, actionable, and well-structured outputs.
15
+
16
+ You ALWAYS:
17
+ - Use clear, direct language.
18
+ - Avoid jargon unless explicitly requested.
19
+ - Highlight decisions, risks, and next steps.
20
+ - Write in a tone that matches the selected tone parameter.
21
+ - Match the requested format (summary, email, LinkedIn post, action plan, etc.).
22
+
23
+ If the user input is chaotic, infer structure and clean it up.
24
+ If the user provides bullet points, upgrade them into a polished professional artifact.
25
+ """
26
+
27
+ BRIEF_TYPES = {
28
+ "Executive Summary": "Write a concise executive summary with key points and a short conclusion.",
29
+ "Action Plan": "Produce a numbered action plan with owners (generic if not specified) and timelines.",
30
+ "Decision Brief": "Write a decision brief: context, options, trade-offs, recommendation.",
31
+ "Polished Email": "Write a professional email body only, no greeting name unless specified.",
32
+ "LinkedIn Post": "Write a compelling LinkedIn post, optimized for engagement but not cringe.",
33
+ "Meeting Minutes": "Produce structured meeting minutes: attendees, topics, decisions, action items.",
34
+ }
35
+
36
+ TONES = [
37
+ "Neutral / Standard",
38
+ "Confident",
39
+ "Concise",
40
+ "Visionary",
41
+ "Friendly",
42
+ ]
43
+
44
+ LENGTH_OPTIONS = {
45
+ "Very short": "Target 150–250 words.",
46
+ "Short": "Target 250–400 words.",
47
+ "Medium": "Target 400–650 words.",
48
+ "Long": "Target 650–900 words.",
49
+ }
50
+
51
+
52
+ def get_client(effective_key: str) -> OpenAI:
53
+ return OpenAI(api_key=effective_key)
54
+
55
+
56
+ def save_api_key(user_key: str) -> Tuple[str, str]:
57
+ """
58
+ Save the API key into session state.
59
+ NOTE: In Spaces, this is only in-memory per session, not persisted to disk.
60
+ """
61
+ user_key = (user_key or "").strip()
62
+ if not user_key:
63
+ return "⚠️ No API key provided. Please paste a valid key.", ""
64
+ # Very light validation: pattern check, but don't over-assume.
65
+ if not (user_key.startswith("sk-") and len(user_key) > 20):
66
+ # Still accept it, just warn.
67
+ return "⚠️ Key saved, but it doesn't look like a standard OpenAI key. Double-check if requests fail.", user_key
68
+ return "βœ… API key saved for this session.", user_key
69
+
70
+
71
+ def build_user_prompt(
72
+ brief_type: str,
73
+ tone: str,
74
+ length_choice: str,
75
+ title: str,
76
+ context: str,
77
+ extra_instructions: str,
78
+ ) -> str:
79
+ type_instruction = BRIEF_TYPES.get(brief_type, "")
80
+ length_instruction = LENGTH_OPTIONS.get(length_choice, "")
81
+ tone_instruction = f"Desired tone: {tone}."
82
+
83
+ title_part = f"Working title or subject: {title.strip()}\n" if title.strip() else ""
84
+
85
+ extra_part = f"\nAdditional instructions from user:\n{extra_instructions.strip()}\n" if extra_instructions.strip() else ""
86
+
87
+ return f"""
88
+ FORMAT REQUEST:
89
+ - Brief type: {brief_type}
90
+ - {type_instruction}
91
+ - {tone_instruction}
92
+ - {length_instruction}
93
+
94
+ {title_part}
95
+ Raw notes / source material:
96
+ \"\"\"{context.strip()}\"\"\"
97
+
98
+ {extra_part}
99
+
100
+ Produce only the final formatted output. Do NOT include meta commentary about what you are doing.
101
+ """
102
+
103
+
104
+ def generate_brief(
105
+ stored_key: str,
106
+ inline_key: str,
107
+ model: str,
108
+ brief_type: str,
109
+ tone: str,
110
+ length_choice: str,
111
+ title: str,
112
+ context: str,
113
+ extra_instructions: str,
114
+ ):
115
+ # Resolve effective key priority: stored key -> inline key -> env variable
116
+ effective_key = (stored_key or "").strip() or (inline_key or "").strip() or os.getenv("OPENAI_API_KEY", "").strip()
117
+
118
+ if not effective_key:
119
+ return "❌ No API key available. Please paste your key and click 'Save API Key' first."
120
+
121
+ if not context.strip():
122
+ return "⚠️ Please paste some notes, text, or content to transform."
123
+
124
+ user_prompt = build_user_prompt(
125
+ brief_type=brief_type,
126
+ tone=tone,
127
+ length_choice=length_choice,
128
+ title=title,
129
+ context=context,
130
+ extra_instructions=extra_instructions,
131
+ )
132
+
133
+ try:
134
+ client = get_client(effective_key)
135
+ response = client.chat.completions.create(
136
+ model=model or DEFAULT_MODEL,
137
+ messages=[
138
+ {"role": "system", "content": SYSTEM_PROMPT},
139
+ {"role": "user", "content": user_prompt},
140
+ ],
141
+ temperature=0.5,
142
+ max_tokens=1200,
143
+ )
144
+ content = response.choices[0].message.content
145
+ if not content or not content.strip():
146
+ return "⚠️ Model returned an empty response. Try again or adjust your prompt."
147
+ return content.strip()
148
+ except Exception as e:
149
+ return f"❌ Error while calling the model:\n\n`{e}`"
150
+
151
+
152
+ def build_ui():
153
+ with gr.Blocks(fill_height=True, theme=gr.themes.Soft(), title=APP_TITLE) as demo:
154
+ gr.Markdown(
155
+ f"# 🧠 {APP_TITLE}\n"
156
+ "Turn messy notes, transcripts, and drafts into clean executive artifacts.\n"
157
+ "Paste your content, pick a format, and let your model do the heavy lifting."
158
+ )
159
+
160
+ api_key_state = gr.State(value="")
161
+
162
+ with gr.Row():
163
+ with gr.Column(scale=1):
164
+ gr.Markdown("### πŸ” API & Model")
165
+
166
+ inline_key = gr.Textbox(
167
+ label="OpenAI / GPT API Key (optional if environment variable is set)",
168
+ placeholder="sk-...",
169
+ type="password",
170
+ lines=1,
171
+ )
172
+ save_button = gr.Button("πŸ’Ύ Save API Key for This Session", variant="secondary")
173
+ save_status = gr.Markdown("")
174
+
175
+ model = gr.Textbox(
176
+ label="Model name",
177
+ value=DEFAULT_MODEL,
178
+ placeholder="e.g., gpt-4.1-mini or gpt-5.x when available",
179
+ )
180
+
181
+ brief_type = gr.Radio(
182
+ label="Brief type",
183
+ choices=list(BRIEF_TYPES.keys()),
184
+ value="Executive Summary",
185
+ )
186
+
187
+ tone = gr.Radio(
188
+ label="Tone",
189
+ choices=TONES,
190
+ value="Neutral / Standard",
191
+ )
192
+
193
+ length_choice = gr.Radio(
194
+ label="Length",
195
+ choices=list(LENGTH_OPTIONS.keys()),
196
+ value="Short",
197
+ )
198
+
199
+ with gr.Column(scale=2):
200
+ gr.Markdown("### πŸ“ Input")
201
+
202
+ title = gr.Textbox(
203
+ label="Title / Subject (optional)",
204
+ placeholder="e.g., Q4 AI Initiative Brief, Client Proposal Follow-up, etc.",
205
+ )
206
+
207
+ context = gr.Textbox(
208
+ label="Paste your notes, transcript, draft, or source content",
209
+ placeholder="Drop in meeting notes, raw transcript, bullet points, or an ugly draft email...",
210
+ lines=14,
211
+ )
212
+
213
+ extra_instructions = gr.Textbox(
214
+ label="Extra instructions (optional)",
215
+ placeholder="Anything special? e.g., 'Emphasize cost savings', 'Avoid technical terms', 'Target directors-level audience'.",
216
+ lines=3,
217
+ )
218
+
219
+ generate_button = gr.Button("⚑ Generate Brief", variant="primary")
220
+
221
+ gr.Markdown("### πŸ“„ Output")
222
+ output = gr.Markdown("Your formatted brief will appear here.")
223
+
224
+ # Wiring
225
+ save_button.click(
226
+ fn=save_api_key,
227
+ inputs=[inline_key],
228
+ outputs=[save_status, api_key_state],
229
+ )
230
+
231
+ generate_button.click(
232
+ fn=generate_brief,
233
+ inputs=[
234
+ api_key_state,
235
+ inline_key,
236
+ model,
237
+ brief_type,
238
+ tone,
239
+ length_choice,
240
+ title,
241
+ context,
242
+ extra_instructions,
243
+ ],
244
+ outputs=[output],
245
+ )
246
+
247
+ return demo
248
+
249
+
250
+ if __name__ == "__main__":
251
+ demo = build_ui()
252
+ demo.launch()