|
|
import os |
|
|
import gradio as gr |
|
|
import openai |
|
|
|
|
|
|
|
|
|
|
|
PRESETS = { |
|
|
"Executive Summary": { |
|
|
"system_role": ( |
|
|
"You are an executive briefing assistant for senior leaders. " |
|
|
"You distill complex material into clear, non-fluffy summaries that support decisions." |
|
|
), |
|
|
"user_hint": "Paste a long report, transcript, or document here and I'll summarize it.", |
|
|
}, |
|
|
"Polished Email Reply": { |
|
|
"system_role": ( |
|
|
"You are a professional communications assistant. " |
|
|
"You write clear, concise, and respectful business emails suitable for executives." |
|
|
), |
|
|
"user_hint": "Paste the email you received and notes on how you want to respond.", |
|
|
}, |
|
|
"Meeting Notes β Action Plan": { |
|
|
"system_role": ( |
|
|
"You transform messy meeting notes into a crisp, action-oriented summary." |
|
|
), |
|
|
"user_hint": "Paste rough notes or bullet points from a meeting, even if they are messy.", |
|
|
}, |
|
|
"Idea Generator / Brainstorm": { |
|
|
"system_role": ( |
|
|
"You are a creative strategist. You generate structured ideas with next steps." |
|
|
), |
|
|
"user_hint": "Describe what you want to build, improve, or launch. I'll propose ideas.", |
|
|
}, |
|
|
} |
|
|
|
|
|
|
|
|
PRESET_FORMAT_SPECS = { |
|
|
"Executive Summary": """ |
|
|
Output format spec (use GitHub-flavored Markdown): |
|
|
|
|
|
- Start with: `## Executive Summary` |
|
|
- Then add `### Context` with 2β4 sentences. |
|
|
- Add `### Key Points` as a bullet list (β’) with the most important insights. |
|
|
- Add `### Recommendations` as a numbered list (1., 2., 3.) with concrete actions. |
|
|
- Use **bold** for critical terms and *italics* for nuance where helpful. |
|
|
""", |
|
|
"Polished Email Reply": """ |
|
|
Output format spec (use GitHub-flavored Markdown): |
|
|
|
|
|
- Start with: `## Email Draft` |
|
|
- Then include: |
|
|
- `**Subject:** ...` |
|
|
- A realistic greeting line (e.g., `Hi Alex,`) |
|
|
- Body in short paragraphs (2β4 sentences each). |
|
|
- Optional bullet points if listing items. |
|
|
- A professional closing (e.g., `Best,` or `Kind regards,`) and a placeholder name. |
|
|
- Do NOT include meta-comments about the email. Only output the email content. |
|
|
""", |
|
|
"Meeting Notes β Action Plan": """ |
|
|
Output format spec (use GitHub-flavored Markdown): |
|
|
|
|
|
- Start with: `## Meeting Summary` |
|
|
- Then sections: |
|
|
- `### Context` β 2β3 sentences on what the meeting was about. |
|
|
- `### Key Decisions` β bullet list. |
|
|
- `### Action Items` β numbered list, each item formatted like: |
|
|
`1. **Owner:** Name (or Generic Owner) β *Due:* date or timeframe β Task description` |
|
|
- `### Risks & Open Questions` β bullets with risks, blockers, or unknowns. |
|
|
""", |
|
|
"Idea Generator / Brainstorm": """ |
|
|
Output format spec (use GitHub-flavored Markdown): |
|
|
|
|
|
- Start with: `## Idea Set` |
|
|
- For each idea: |
|
|
- `### Idea N β Short Title` |
|
|
- Bullet list: |
|
|
- `**Overview:** ...` |
|
|
- `**Why it matters:** ...` |
|
|
- `**Next steps (1β3):** ...` |
|
|
- Aim for 3β5 strong ideas rather than many weak ones. |
|
|
""", |
|
|
} |
|
|
|
|
|
BASE_SYSTEM = """ |
|
|
You are the ZEN Promptboard Pro assistant. |
|
|
|
|
|
Rules: |
|
|
- Always respond in GitHub-flavored Markdown. |
|
|
- Follow any provided "Output format spec" exactly, especially headings and bullet structure. |
|
|
- Make the output immediately usable and ready to send or paste into a document. |
|
|
- Never explain what you are doing; do NOT include meta-instructions in the final output. |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_completion(api_key, model, preset_name, system_prompt, user_prompt, history): |
|
|
""" |
|
|
Core call to the OpenAI Chat Completions API. |
|
|
|
|
|
- Does NOT throw on missing API key; returns a friendly message instead. |
|
|
- Uses model-agnostic parameters (no temperature / max_tokens) to avoid 400s. |
|
|
- Injects preset-specific formatting rules so output is richly structured. |
|
|
""" |
|
|
|
|
|
history = history or [] |
|
|
|
|
|
if not api_key: |
|
|
msg = "β No API key found. Paste your key and click **Save Key** first." |
|
|
history.append(("System", msg)) |
|
|
return history, msg |
|
|
|
|
|
if not user_prompt.strip(): |
|
|
msg = "β οΈ Please enter a prompt or paste some content before running." |
|
|
history.append(("System", msg)) |
|
|
return history, msg |
|
|
|
|
|
openai.api_key = api_key.strip() |
|
|
|
|
|
|
|
|
system_parts = [BASE_SYSTEM] |
|
|
|
|
|
if preset_name in PRESETS: |
|
|
system_parts.append(f"Preset role description:\n{PRESETS[preset_name]['system_role']}") |
|
|
fmt = PRESET_FORMAT_SPECS.get(preset_name, "") |
|
|
if fmt: |
|
|
system_parts.append(fmt) |
|
|
|
|
|
if system_prompt.strip(): |
|
|
system_parts.append( |
|
|
"Additional system instructions from the user (apply after all above rules):\n" |
|
|
+ system_prompt.strip() |
|
|
) |
|
|
|
|
|
final_system = "\n\n".join(system_parts) |
|
|
|
|
|
messages = [ |
|
|
{"role": "system", "content": final_system}, |
|
|
{"role": "user", "content": user_prompt.strip()}, |
|
|
] |
|
|
|
|
|
try: |
|
|
|
|
|
response = openai.chat.completions.create( |
|
|
model=model.strip(), |
|
|
messages=messages, |
|
|
) |
|
|
answer = (response.choices[0].message.content or "").strip() |
|
|
if not answer: |
|
|
answer = "β οΈ The model returned an empty response. Try again with more context." |
|
|
except Exception as e: |
|
|
answer = f"β Error while calling the model:\n\n`{e}`" |
|
|
|
|
|
history.append(("You", user_prompt)) |
|
|
history.append(("Assistant", answer)) |
|
|
|
|
|
return history, answer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_api_key(raw_key: str): |
|
|
cleaned = (raw_key or "").strip() |
|
|
if not cleaned: |
|
|
raise gr.Error("π Please paste a valid API key before saving.") |
|
|
|
|
|
if not cleaned.startswith("sk-"): |
|
|
gr.Info("Key saved, but it doesnβt start with 'sk-'. Double-check your provider format.") |
|
|
|
|
|
return cleaned, gr.update(value="", placeholder="API key saved β") |
|
|
|
|
|
|
|
|
def load_preset(preset_name: str): |
|
|
if not preset_name or preset_name not in PRESETS: |
|
|
return gr.update(), gr.update() |
|
|
preset = PRESETS[preset_name] |
|
|
return preset["system_role"], preset["user_hint"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CUSTOM_CSS = """ |
|
|
#zen-root { |
|
|
background: radial-gradient(circle at top left, #020617 0, #020617 40%, #000000 100%); |
|
|
color: #f9fafb; |
|
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif; |
|
|
min-height: 100vh; |
|
|
padding-bottom: 2rem; |
|
|
} |
|
|
.zen-card { |
|
|
border-radius: 20px; |
|
|
background: linear-gradient(135deg, rgba(15,23,42,0.95), rgba(15,23,42,0.7)); |
|
|
border: 1px solid rgba(148,163,184,0.45); |
|
|
backdrop-filter: blur(22px); |
|
|
box-shadow: |
|
|
0 24px 60px rgba(15,23,42,0.95), |
|
|
0 0 0 1px rgba(15,23,42,0.85); |
|
|
} |
|
|
.zen-header { |
|
|
padding: 1.8rem 2rem; |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
.zen-pill { |
|
|
border-radius: 9999px; |
|
|
padding: 0.25rem 0.9rem; |
|
|
border: 1px solid rgba(129,140,248,0.7); |
|
|
color: #a5b4fc; |
|
|
font-size: 0.75rem; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.12em; |
|
|
} |
|
|
.zen-title { |
|
|
font-size: 1.9rem; |
|
|
font-weight: 700; |
|
|
letter-spacing: -0.03em; |
|
|
} |
|
|
.zen-subtitle { |
|
|
font-size: 0.9rem; |
|
|
color: #9ca3af; |
|
|
} |
|
|
#zen-output-card { |
|
|
padding: 1.3rem 1.6rem; |
|
|
} |
|
|
#zen-output-card .markdown-body h2 { |
|
|
margin-top: 0.2rem; |
|
|
} |
|
|
#zen-output-card .markdown-body h3 { |
|
|
margin-top: 0.9rem; |
|
|
} |
|
|
#zen-meta-card { |
|
|
padding: 0.75rem 1rem; |
|
|
font-size: 0.85rem; |
|
|
color: #e5e7eb; |
|
|
border-bottom: 1px solid rgba(148,163,184,0.35); |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks( |
|
|
css=CUSTOM_CSS, |
|
|
elem_id="zen-root", |
|
|
fill_height=True, |
|
|
title="ZEN Promptboard Pro β GPT-5", |
|
|
) as demo: |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
<div class="zen-card zen-header"> |
|
|
<div class="zen-pill">ZEN VANGUARD β’ GPT-5 WORKSTATION</div> |
|
|
<div class="zen-title" style="margin-top: 0.8rem;">Professional AI Promptboard</div> |
|
|
<div class="zen-subtitle" style="margin-top: 0.5rem;"> |
|
|
Transform unstructured content into structured, ready-to-use executive artifacts β |
|
|
summaries, action plans, emails, and idea sets. |
|
|
</div> |
|
|
</div> |
|
|
""", |
|
|
) |
|
|
|
|
|
api_key_state = gr.State("") |
|
|
|
|
|
with gr.Row(equal_height=True): |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
with gr.Group(elem_classes=["zen-card"], elem_id="zen-left-card"): |
|
|
gr.Markdown("#### 1. Connect to your model") |
|
|
|
|
|
api_key_input = gr.Textbox( |
|
|
label="API Key", |
|
|
placeholder="Paste your GPT-style key here (not stored on server)", |
|
|
type="password", |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
save_btn = gr.Button("Save Key", variant="primary", scale=1) |
|
|
key_status = gr.Markdown("Key not saved yet.") |
|
|
|
|
|
model_name = gr.Textbox( |
|
|
label="Model ID", |
|
|
value="gpt-5", |
|
|
info="Default: gpt-5. You can switch to gpt-4o, gpt-4.1-mini, etc.", |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("#### 2. Choose a workflow") |
|
|
|
|
|
preset_radio = gr.Radio( |
|
|
label="Quick presets", |
|
|
choices=list(PRESETS.keys()), |
|
|
interactive=True, |
|
|
) |
|
|
|
|
|
with gr.Group(elem_classes=["zen-card"]): |
|
|
gr.Markdown("#### 3. System behavior (optional)") |
|
|
system_box = gr.Textbox( |
|
|
label="Additional system / role instructions", |
|
|
placeholder="Optional: refine how the AI behaves (e.g., 'Write at director level, avoid buzzwords.')", |
|
|
lines=4, |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=2): |
|
|
with gr.Group(elem_classes=["zen-card"]): |
|
|
gr.Markdown("#### 4. Your content") |
|
|
|
|
|
user_box = gr.Textbox( |
|
|
label="Prompt / Notes / Source Content", |
|
|
placeholder="Paste meeting notes, drafts, transcripts, or describe what you want.", |
|
|
lines=9, |
|
|
) |
|
|
|
|
|
run_btn = gr.Button("Run Prompt", variant="primary") |
|
|
|
|
|
with gr.Group(elem_classes=["zen-card"], elem_id="zen-output-wrapper"): |
|
|
meta_md = gr.Markdown( |
|
|
"Ready. Choose a preset and run your first prompt.", |
|
|
elem_id="zen-meta-card", |
|
|
) |
|
|
with gr.Tab("Formatted Output"): |
|
|
output_md = gr.Markdown( |
|
|
"Output will appear here.", |
|
|
elem_id="zen-output-card", |
|
|
) |
|
|
with gr.Tab("History"): |
|
|
history_chat = gr.Chatbot( |
|
|
label="Conversation History", |
|
|
height=320, |
|
|
type="tuples", |
|
|
show_copy_button=True, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def _save_and_ack(key: str): |
|
|
stored_key, placeholder_update = save_api_key(key) |
|
|
status_msg = "β
API key stored for this session." |
|
|
return stored_key, status_msg, placeholder_update |
|
|
|
|
|
save_btn.click( |
|
|
_save_and_ack, |
|
|
inputs=[api_key_input], |
|
|
outputs=[api_key_state, key_status, api_key_input], |
|
|
) |
|
|
|
|
|
preset_radio.change( |
|
|
fn=load_preset, |
|
|
inputs=[preset_radio], |
|
|
outputs=[system_box, user_box], |
|
|
) |
|
|
|
|
|
def _run(api_state, model, preset_name, system, user, history): |
|
|
history, answer = run_completion( |
|
|
api_key=api_state, |
|
|
model=model, |
|
|
preset_name=preset_name, |
|
|
system_prompt=system, |
|
|
user_prompt=user, |
|
|
history=history, |
|
|
) |
|
|
|
|
|
preset_label = preset_name or "Custom" |
|
|
meta_text = f"**Preset:** {preset_label} Β· **Model:** `{model.strip() or 'gpt-5'}`" |
|
|
return meta_text, history, answer |
|
|
|
|
|
run_btn.click( |
|
|
_run, |
|
|
inputs=[api_key_state, model_name, preset_radio, system_box, user_box, history_chat], |
|
|
outputs=[meta_md, history_chat, output_md], |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|