K00B404's picture
Create app.py
d5e8626 verified
"""
Prompt Engineering Training Chat – Gradio App πŸ’–
===================================================
Dark romantic neon theme with hearts.
Deploy as a HuggingFace Space (Docker Space).
Connects to local Flask API via ngrok/tunnel.
"""
import gradio as gr
import requests
import json
import os
from datetime import datetime
from dotenv import load_dotenv,find_dotenv
from pathlib import Path
from modules.shimsalabim import ShimSalaBim
load_dotenv(find_dotenv())
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
DEFAULT_API_URL = os.environ.get("FLASK_API_URL", "https://3d02-2a02-a459-18b8-0-23e8-aa8f-ba25-669a.ngrok-free.app")
# ---------------------------------------------------------------------------
# Prompt Engineering Tips & Lessons
# ---------------------------------------------------------------------------
PROMPT_ENG_TIPS = {
"System Prompt": """**The System Prompt** is the most powerful tool for controlling LLM behavior.
**What it does:** Sets the "personality" and rules the model follows for the entire conversation.
**Key techniques to try:**
1. **Role Assignment** – Tell the model WHO it is: *"You are a senior sales consultant with 15 years of B2B SaaS experience."*
2. **Output Format** – Specify HOW it should respond: *"Always respond in bullet points. Start with a summary, then details."*
3. **Constraints** – Set boundaries: *"Never mention competitors by name. Keep responses under 100 words."*
4. **Tone Control** – *"Speak in a friendly, conversational tone. Use 'you' and 'we' frequently."*
5. **Step-by-step** – *"Think through the problem step by step before giving your final answer."*
**Experiment:** Try the same user prompt with different system prompts and observe how the output changes dramatically!""",
"Temperature": """**Temperature** controls randomness/creativity in the model's output.
- **0.0 – 0.3:** Very focused, deterministic, repetitive. Great for factual Q&A, data extraction, code.
- **0.4 – 0.7:** Balanced. Good for general conversation, explanations, brainstorming.
- **0.8 – 1.5:** Creative, unpredictable. Good for storytelling, poetry, creative writing.
- **1.5+:** Chaotic, often incoherent. Useful to see what "too much creativity" looks like.
**Try this:** Set temperature to 0 and ask the same question 3 times β€” you'll get identical answers. Then set it to 1.2 and watch how the responses vary!""",
"Top-P (Nucleus Sampling)": """**Top-P** (also called nucleus sampling) filters which tokens the model can choose from.
- **0.1:** Only the most likely tokens (very focused, similar to low temperature).
- **0.5:** Moderate filtering.
- **0.9 – 1.0:** Almost all tokens are candidates (more diverse).
**The difference from temperature:** Temperature reshapes the probability distribution; Top-P cuts off the tail. They work together!
**Try this:** Set temperature=0.8, top_p=0.1 β†’ focused but not robotic. Then top_p=0.95 β†’ much more varied.""",
"Top-K": """**Top-K** limits the model to only choosing from the K most likely next tokens.
- **K=1:** Always picks the single most likely token (greedy decoding).
- **K=10-40:** Reasonable range for most tasks.
- **K=100+:** Very permissive, can produce unexpected results.
**When to use:** Top-K is a "hard cutoff" β€” it completely eliminates unlikely tokens. Top-P is "softer" β€” it adapts based on probability distribution.
**Try this:** Set top_k=1 and temperature=1.0. You'll see temperature has no effect because only 1 token is available!""",
"Max Tokens": """**Max Tokens** controls the maximum length of the model's response.
- **50-100:** Short answers, ideal for classification, yes/no, single facts.
- **200-500:** Medium responses, good for explanations, email drafts.
- **500-2000:** Long-form content, articles, detailed analysis.
**Tip for prompt engineering:** If you want concise answers, set max_tokens LOW (100-150) AND say in the system prompt "Keep responses brief." The combination is more reliable than either alone.
**Try this:** Ask "Explain quantum computing" with max_tokens=50, then max_tokens=500. See how the model adapts!""",
"Repeat Penalty": """**Repeat Penalty** discourages the model from repeating the same text.
- **1.0:** No penalty (model may repeat itself).
- **1.1-1.2:** Mild penalty (default, good for most uses).
- **1.3-1.5:** Strong penalty (may produce awkward phrasing to avoid repetition).
- **2.0+:** Extreme β€” the model will contort its output to never repeat.
**When to increase:** If the model gets stuck in loops ("The cat sat. The cat sat. The cat sat..."), increase this value.
**Try this:** Set repeat_penalty=1.0 and ask for a long response β€” watch for repetition. Then set it to 1.5 and compare.""",
"Frequency & Presence Penalty": """**Frequency Penalty** reduces the likelihood of tokens that have already appeared frequently. It penalizes based on HOW OFTEN a token appeared.
**Presence Penalty** reduces the likelihood of ANY token that has appeared at least once. It encourages the model to talk about NEW topics.
- **Frequency 0.0 – 0.5:** Subtle reduction of repetition.
- **Presence 0.0 – 0.5:** Encourages topic diversity.
**Practical use:** For a sales email, you might want presence_penalty=0.3 to keep the model from fixating on one selling point.
**Try this:** Ask "List 10 benefits of our product" with presence_penalty=0 vs presence_penalty=0.8. The higher value will push the model toward more diverse points.""",
"n_ctx (Context Window)": """**n_ctx** is the context window size β€” how many tokens the model can "see" at once (including both your prompt and its response).
- **512:** Very limited. Only short conversations.
- **2048:** Good for most single-turn Q&A and moderate conversations.
- **4096+:** Needed for long documents or extended chat history.
**Important:** n_ctx is set when the server starts (it determines how much RAM/VRAM to allocate). Changing it in the UI sends a request, but the server must be restarted for it to take effect.
**Tradeoff:** Larger n_ctx = more memory usage but can handle longer conversations. Your model has a maximum n_ctx it was trained on (e.g., 4096 for Llama 2, 8192+ for some newer models).""",
"Stop Sequences": """**Stop Sequences** tell the model to stop generating when it encounters specific text.
Common uses:
- `"\\nUser:"` – Stop before the model starts simulating user messages.
- `"\\n\\n"` – Stop at double newlines (keeps responses to one paragraph).
- `"<|end|>"` – Model-specific end tokens.
- `"---"` – Stop before generating separators.
**Try this:** Set a stop sequence of `"."` (a period) β€” the model will stop after the first sentence! Remove it and it generates a full paragraph.""",
"Effective Prompting Patterns": """**Proven patterns for better LLM outputs:**
1. **Few-Shot Examples:**
*"Here are examples of good sales emails:*
*Example 1: [your example]*
*Example 2: [your example]*
*Now write a similar email for [new situation]."*
2. **Chain of Thought:**
*"Think step by step: First analyze the customer's needs, then identify our best matching product, then craft the pitch."*
3. **Specify the Audience:**
*"Write this for a CTO who is technical but also cares about ROI. She has 15 minutes for this meeting."*
4. **Provide Structure:**
*"Format your response as: 1) Key Insight, 2) Supporting Evidence, 3) Recommended Action."*
5. **Negative Instructions:**
*"Do NOT use jargon. Do NOT mention pricing. Do NOT write more than 3 paragraphs."*
6. **Iterative Refinement:**
Start simple, read the output, then refine your prompt based on what you liked and didn't like.""",
}
# ---------------------------------------------------------------------------
# API Communication
# ---------------------------------------------------------------------------
def check_connection(api_url: str) -> str:
"""Test the connection to the Flask API."""
try:
resp = requests.get(f"{api_url.rstrip('/')}/health", timeout=10)
if resp.status_code == 200:
data = resp.json()
return f"πŸ’– Connected! Model: {data.get('model', 'unknown')}, n_ctx: {data.get('n_ctx', '?')}"
return f"⚠️ Server responded with status {resp.status_code}: {resp.text}"
except requests.exceptions.ConnectionError:
return "πŸ’” Cannot connect. Is the server running? Is the tunnel active?"
except requests.exceptions.Timeout:
return "πŸ’” Connection timed out. The server might be slow or unreachable."
except Exception as e:
return f"πŸ’” Error: {str(e)}"
def send_chat(
message: str,
chat_history: list,
api_url: str,
system_prompt: str,
max_tokens: int,
temperature: float,
top_p: float,
top_k: int,
repeat_penalty: float,
frequency_penalty: float,
presence_penalty: float,
n_ctx: int,
stop_sequences: str,
) -> tuple:
"""Send a chat message to the Flask API and get a response."""
if not message.strip():
return "", chat_history, ""
# Build messages array in OpenAI format
messages = []
if system_prompt.strip():
messages.append({"role": "system", "content": system_prompt.strip()})
# Add conversation history
for user_msg, assistant_msg in chat_history:
if user_msg:
messages.append({"role": "user", "content": user_msg})
if assistant_msg:
messages.append({"role": "assistant", "content": assistant_msg})
# Add current message
messages.append({"role": "user", "content": message.strip()})
# Parse stop sequences
stop = []
if stop_sequences.strip():
stop = [s.strip() for s in stop_sequences.split(",") if s.strip()]
payload = {
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"repeat_penalty": repeat_penalty,
"frequency_penalty": frequency_penalty,
"presence_penalty": presence_penalty,
"n_ctx": n_ctx,
"stop": stop if stop else None,
}
debug_info = f"**Request Payload:**\n```json\n{json.dumps(payload, indent=2)}\n```"
try:
resp = requests.post(
f"{api_url.rstrip('/')}/chat",
json=payload,
timeout=120,
)
if resp.status_code == 200:
data = resp.json()
assistant_content = data.get("message", {}).get("content", "")
usage = data.get("usage", {})
elapsed = data.get("elapsed_seconds", "?")
stats = (
f"πŸ’– **Tokens:** Prompt {usage.get('prompt_tokens', '?')} β†’ "
f"Completion {usage.get('completion_tokens', '?')} β†’ "
f"Total {usage.get('total_tokens', '?')}\n"
f"⏱️ **Time:** {elapsed}s\n"
)
full_debug = f"{stats}\n{debug_info}"
chat_history.append((message, assistant_content))
return "", chat_history, full_debug
else:
error_msg = f"πŸ’” API Error ({resp.status_code}): {resp.text}"
chat_history.append((message, error_msg))
return "", chat_history, debug_info
except requests.exceptions.ConnectionError:
error_msg = "πŸ’” Cannot connect to the API. Check if the server is running and the tunnel is active."
chat_history.append((message, error_msg))
return "", chat_history, debug_info
except requests.exceptions.Timeout:
error_msg = "πŸ’” Request timed out (120s). The model might be taking too long."
chat_history.append((message, error_msg))
return "", chat_history, debug_info
except Exception as e:
error_msg = f"πŸ’” Error: {str(e)}"
chat_history.append((message, error_msg))
return "", chat_history, debug_info
def send_completion(
prompt: str,
api_url: str,
system_prompt: str,
max_tokens: int,
temperature: float,
top_p: float,
top_k: int,
repeat_penalty: float,
frequency_penalty: float,
presence_penalty: float,
n_ctx: int,
stop_sequences: str,
) -> tuple:
"""Send a raw completion request (no chat history) and get the result."""
if not prompt.strip():
return "", ""
stop = []
if stop_sequences.strip():
stop = [s.strip() for s in stop_sequences.split(",") if s.strip()]
payload = {
"prompt": prompt.strip(),
"system_prompt": system_prompt.strip(),
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"repeat_penalty": repeat_penalty,
"frequency_penalty": frequency_penalty,
"presence_penalty": presence_penalty,
"n_ctx": n_ctx,
"stop": stop if stop else None,
}
debug_info = f"**Request Payload:**\n```json\n{json.dumps(payload, indent=2)}\n```"
try:
resp = requests.post(
f"{api_url.rstrip('/')}/generate",
json=payload,
timeout=120,
)
if resp.status_code == 200:
data = resp.json()
text = data.get("text", "")
usage = data.get("usage", {})
elapsed = data.get("elapsed_seconds", "?")
stats = (
f"πŸ’– **Tokens:** Prompt {usage.get('prompt_tokens', '?')} β†’ "
f"Completion {usage.get('completion_tokens', '?')} β†’ "
f"Total {usage.get('total_tokens', '?')}\n"
f"⏱️ **Time:** {elapsed}s\n"
)
return text, f"{stats}\n{debug_info}"
else:
return f"πŸ’” API Error ({resp.status_code}): {resp.text}", debug_info
except Exception as e:
return f"πŸ’” Error: {str(e)}", debug_info
# ---------------------------------------------------------------------------
# Presets for quick experimentation
# ---------------------------------------------------------------------------
PRESETS = {
"πŸ’– Default": {
"system_prompt": "You are a helpful, harmless, and honest assistant.",
"max_tokens": 512,
"temperature": 0.7,
"top_p": 0.9,
"top_k": 40,
"repeat_penalty": 1.1,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": "",
},
"πŸ“Š Factual / Analytical": {
"system_prompt": "You are a precise, factual assistant. Always provide accurate information. If you're unsure, say so. Use structured formats like numbered lists and tables when appropriate.",
"max_tokens": 300,
"temperature": 0.2,
"top_p": 0.8,
"top_k": 20,
"repeat_penalty": 1.15,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": "",
},
"✍️ Creative Writer": {
"system_prompt": "You are a creative and imaginative writer. Be vivid, expressive, and original. Use metaphors, sensory details, and varied sentence structures. Take creative risks.",
"max_tokens": 800,
"temperature": 1.0,
"top_p": 0.95,
"top_k": 80,
"repeat_penalty": 1.05,
"frequency_penalty": 0.2,
"presence_penalty": 0.2,
"stop": "",
},
"πŸ’• Sales Coach": {
"system_prompt": "You are a senior sales coach who helps sales representatives improve their pitch, objection handling, and closing techniques. Give specific, actionable advice with examples. Be encouraging but honest about areas for improvement.",
"max_tokens": 500,
"temperature": 0.6,
"top_p": 0.9,
"top_k": 40,
"repeat_penalty": 1.1,
"frequency_penalty": 0.1,
"presence_penalty": 0.1,
"stop": "",
},
"🧠 Step-by-Step Reasoner": {
"system_prompt": "You are a logical, step-by-step reasoning assistant. Always break down problems into clear steps. Show your reasoning process. Use 'Step 1:', 'Step 2:', etc. format. Double-check your logic before giving the final answer.",
"max_tokens": 600,
"temperature": 0.3,
"top_p": 0.85,
"top_k": 30,
"repeat_penalty": 1.1,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": "",
},
"πŸ“ Concise Responder": {
"system_prompt": "You are a concise assistant. Keep ALL responses under 50 words. Be direct and to the point. Never add filler or unnecessary elaboration.",
"max_tokens": 80,
"temperature": 0.5,
"top_p": 0.9,
"top_k": 40,
"repeat_penalty": 1.15,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": "",
},
}
# ---------------------------------------------------------------------------
# Custom Dark Romantic Neon CSS
# ---------------------------------------------------------------------------
DARK_ROMANTIC_CSS = """
/* ═══════════════════════════════════════════════════
DARK ROMANTIC NEON THEME
Black bg + Neon Pink/Magenta/Purple accents + Hearts
═══════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&display=swap');
/* ── Root overrides ── */
:root {
--neon-pink: #ff2d7b;
--neon-magenta: #ff00ff;
--neon-rose: #ff6b9d;
--neon-purple: #b44dff;
--neon-lavender: #d68fff;
--dark-bg: #0a0a0a;
--dark-surface: #111111;
--dark-card: #161616;
--dark-input: #1a1a1a;
--dark-border: #2a1a24;
--text-primary: #f0e6ef;
--text-secondary: #b8a0b5;
--text-dim: #7a6578;
--glow-pink: 0 0 10px rgba(255,45,123,0.3), 0 0 20px rgba(255,45,123,0.15);
--glow-purple: 0 0 10px rgba(180,77,255,0.3), 0 0 20px rgba(180,77,255,0.15);
}
/* ── Body & overall background ── */
body, .gradio-container {
background: var(--dark-bg) !important;
color: var(--text-primary) !important;
font-family: 'Quicksand', sans-serif !important;
}
.main {
background: var(--dark-bg) !important;
}
/* ── Animated heart background ── */
.gradio-container::before {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background:
radial-gradient(circle at 15% 25%, rgba(255,45,123,0.06) 0%, transparent 50%),
radial-gradient(circle at 85% 75%, rgba(180,77,255,0.06) 0%, transparent 50%),
radial-gradient(circle at 50% 50%, rgba(255,0,255,0.03) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
/* ── Hearts floating animation ── */
@keyframes floatHeart {
0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
10% { opacity: 0.15; }
90% { opacity: 0.15; }
100% { transform: translateY(-10vh) rotate(360deg); opacity: 0; }
}
.gradio-container::after {
content: 'β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯';
position: fixed;
top: 0; left: 0; right: 0;
font-size: 24px;
color: var(--neon-pink);
letter-spacing: 80px;
word-spacing: 120px;
animation: floatHeart 25s linear infinite;
pointer-events: none;
z-index: 0;
opacity: 0.08;
}
/* ── All panels, cards, containers ── */
.panel, .card, .block, .gr-panel, .gr-card,
.contain .card, .contain .block,
[data-testid="block"], .gr-box {
background: var(--dark-card) !important;
border-color: var(--dark-border) !important;
border-radius: 12px !important;
}
/* ── Section backgrounds ── */
section, .gr-group, .gr-form {
background: var(--dark-surface) !important;
border-color: var(--dark-border) !important;
}
/* ── Input & textarea styling ── */
input[type="text"], textarea, select,
.gr-input, .gr-text-input, .gr-textarea,
[data-testid="textbox"] input, [data-testid="textbox"] textarea {
background: var(--dark-input) !important;
color: var(--text-primary) !important;
border: 1px solid var(--dark-border) !important;
border-radius: 8px !important;
caret-color: var(--neon-pink) !important;
}
input[type="text"]:focus, textarea:focus, select:focus,
.gr-input:focus, .gr-text-input:focus, .gr-textarea:focus {
border-color: var(--neon-pink) !important;
box-shadow: var(--glow-pink) !important;
outline: none !important;
}
/* ── Slider styling ── */
input[type="range"], .gr-slider input[type="range"] {
accent-color: var(--neon-pink) !important;
}
.gr-slider .range-wrap .range-data .range-info {
color: var(--neon-rose) !important;
}
/* ── Dropdown / Select ── */
.gr-dropdown, select, .gr-select {
background: var(--dark-input) !important;
color: var(--text-primary) !important;
border: 1px solid var(--dark-border) !important;
}
/* ── Buttons ── */
button.primary, .gr-button-primary, .btn-primary {
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple)) !important;
color: #ffffff !important;
border: none !important;
border-radius: 10px !important;
font-weight: 600 !important;
letter-spacing: 0.5px !important;
box-shadow: var(--glow-pink) !important;
transition: all 0.3s ease !important;
}
button.primary:hover, .gr-button-primary:hover, .btn-primary:hover {
background: linear-gradient(135deg, var(--neon-rose), var(--neon-magenta)) !important;
box-shadow: 0 0 15px rgba(255,45,123,0.5), 0 0 30px rgba(255,0,255,0.3) !important;
transform: translateY(-1px) !important;
}
button.secondary, .gr-button-secondary, .btn-secondary {
background: var(--dark-card) !important;
color: var(--neon-rose) !important;
border: 1px solid var(--neon-pink) !important;
border-radius: 10px !important;
transition: all 0.3s ease !important;
}
button.secondary:hover, .gr-button-secondary:hover, .btn-secondary:hover {
background: rgba(255,45,123,0.1) !important;
box-shadow: var(--glow-pink) !important;
}
button.stop, .gr-button-stop {
background: var(--dark-card) !important;
color: #ff4466 !important;
border: 1px solid #ff4466 !important;
border-radius: 10px !important;
}
button.stop:hover, .gr-button-stop:hover {
background: rgba(255,68,102,0.1) !important;
box-shadow: 0 0 10px rgba(255,68,102,0.3) !important;
}
/* ── Chatbot area ── */
.gr-chatbot, [data-testid="chatbot"] {
background: var(--dark-input) !important;
border: 1px solid var(--dark-border) !important;
border-radius: 12px !important;
}
.gr-chatbot .message.user, [data-testid="chatbot"] .message.user {
background: linear-gradient(135deg, rgba(255,45,123,0.15), rgba(180,77,255,0.15)) !important;
color: var(--text-primary) !important;
border-left: 3px solid var(--neon-pink) !important;
border-radius: 0 10px 10px 0 !important;
}
.gr-chatbot .message.bot, [data-testid="chatbot"] .message.bot,
.gr-chatbot .message.assistant, [data-testid="chatbot"] .message.assistant {
background: rgba(180,77,255,0.08) !important;
color: var(--text-primary) !important;
border-left: 3px solid var(--neon-purple) !important;
border-radius: 0 10px 10px 0 !important;
}
/* ── Labels ── */
label, .gr-label, [data-testid="label"] {
color: var(--neon-rose) !important;
font-weight: 600 !important;
}
/* ── Info text under controls ── */
.gr-info, .gr-input-info, [data-testid="info"] {
color: var(--text-dim) !important;
font-size: 0.85em !important;
}
/* ── Markdown text ── */
.markdown-text, .gr-markdown, .prose {
color: var(--text-primary) !important;
}
.markdown-text h1, .gr-markdown h1, .prose h1,
.markdown-text h2, .gr-markdown h2, .prose h2,
.markdown-text h3, .gr-markdown h3, .prose h3 {
color: var(--neon-rose) !important;
border-color: var(--dark-border) !important;
}
.markdown-text strong, .gr-markdown strong, .prose strong {
color: var(--neon-lavender) !important;
}
.markdown-text code, .gr-markdown code, .prose code {
background: var(--dark-input) !important;
color: var(--neon-pink) !important;
border: 1px solid var(--dark-border) !important;
border-radius: 4px !important;
}
.markdown-text a, .gr-markdown a, .prose a {
color: var(--neon-pink) !important;
}
.markdown-text pre, .gr-markdown pre, .prose pre {
background: var(--dark-input) !important;
border: 1px solid var(--dark-border) !important;
border-radius: 8px !important;
}
.markdown-text pre code, .gr-markdown pre code, .prose pre code {
border: none !important;
}
/* ── Horizontal rules ── */
hr, .gr-hr {
border-color: var(--dark-border) !important;
background: linear-gradient(90deg, transparent, var(--neon-pink), transparent) !important;
height: 1px !important;
}
/* ── Scrollbar ── */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--dark-bg) !important;
}
::-webkit-scrollbar-thumb {
background: var(--neon-pink) !important;
border-radius: 4px;
opacity: 0.5;
}
::-webkit-scrollbar-thumb:hover {
background: var(--neon-rose) !important;
}
/* ── Tab styling ── */
.tab-nav, .gr-tab-nav {
border-color: var(--dark-border) !important;
}
.tab-nav button, .gr-tab-nav button {
color: var(--text-secondary) !important;
}
.tab-nav button.selected, .gr-tab-nav button.selected {
color: var(--neon-pink) !important;
border-color: var(--neon-pink) !important;
}
/* ── Title glow effect ── */
.title-glow {
text-shadow: 0 0 10px rgba(255,45,123,0.5), 0 0 20px rgba(255,0,255,0.3);
}
/* ── Heart dividers ── */
.heart-divider {
text-align: center;
color: var(--neon-pink);
font-size: 18px;
letter-spacing: 12px;
opacity: 0.4;
margin: 8px 0;
text-shadow: 0 0 8px rgba(255,45,123,0.4);
}
/* ── Neon border accent ── */
.neon-border {
border: 1px solid var(--neon-pink) !important;
box-shadow: var(--glow-pink) !important;
border-radius: 12px !important;
padding: 16px !important;
background: var(--dark-card) !important;
}
/* ── Connection status glow ── */
#connection-status input, #connection-status textarea {
font-family: 'Quicksand', sans-serif !important;
}
/* ── API URL monospace ── */
#api-url input {
font-family: 'Courier New', monospace !important;
color: var(--neon-lavender) !important;
}
/* ── Placeholder text ── */
::placeholder {
color: var(--text-dim) !important;
}
/* ── Row dividers with hearts ── */
.heart-separator {
display: flex;
align-items: center;
gap: 12px;
margin: 16px 0;
}
.heart-separator::before,
.heart-separator::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--neon-pink), transparent);
}
/* ── Tooltip / popup ── */
.gr-tooltip, .tooltip {
background: var(--dark-card) !important;
color: var(--text-primary) !important;
border: 1px solid var(--neon-pink) !important;
}
/* ── Footer ── */
footer {
display: none !important;
}
/* ── Gradio built-in theme overrides ── */
.gap { gap: 8px !important; }
/* ── Accordion ── */
.gr-accordion, details {
background: var(--dark-card) !important;
border-color: var(--dark-border) !important;
}
.gr-accordion summary, details summary {
color: var(--neon-rose) !important;
}
/* ── Bubble chat layout colors ── */
.message.bubble.user {
background: linear-gradient(135deg, rgba(255,45,123,0.2), rgba(180,77,255,0.15)) !important;
}
.message.bubble.bot, .message.bubble.assistant {
background: rgba(214,143,255,0.1) !important;
}
"""
# ---------------------------------------------------------------------------
# Build the Gradio Interface
# ---------------------------------------------------------------------------
def apply_preset(preset_name: str):
"""Apply a preset and return all parameter values."""
if preset_name in PRESETS:
p = PRESETS[preset_name]
return (
p["system_prompt"],
p["max_tokens"],
p["temperature"],
p["top_p"],
p["top_k"],
p["repeat_penalty"],
p["frequency_penalty"],
p["presence_penalty"],
p["stop"],
)
return [gr.update()] * 9
def build_ui():
"""Build the full Gradio interface with dark romantic neon theme."""
with gr.Blocks(
title="πŸ’– Prompt Engineering Lab",
theme=gr.themes.Base(
primary_hue="pink",
secondary_hue="purple",
neutral_hue="stone",
),
css=DARK_ROMANTIC_CSS,
) as demo:
# ---- Header ----
gr.HTML("""
<div style="text-align: center; padding: 20px 0 10px 0;">
<h1 style="
font-family: 'Quicksand', sans-serif;
font-size: 2.8em;
font-weight: 700;
margin: 0;
background: linear-gradient(135deg, #ff2d7b, #ff00ff, #b44dff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none;
filter: drop-shadow(0 0 20px rgba(255,45,123,0.4));
">πŸ’– Prompt Engineering Lab πŸ’–</h1>
<p style="
font-family: 'Quicksand', sans-serif;
font-size: 1.2em;
color: #b8a0b5;
margin: 8px 0 0 0;
">Learn prompt engineering by experimenting with every LLM parameter β™₯</p>
</div>
""")
# ---- Heart Divider ----
gr.HTML('<div class="heart-divider">β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯</div>')
# ---- API Connection ----
with gr.Row():
api_url = gr.Textbox(
value=DEFAULT_API_URL,
label="πŸ”— Flask API URL (your ngrok/tunnel URL)",
placeholder="https://your-ngrok-url.ngrok-free.app",
elem_id="api-url",
scale=4,
)
connect_btn = gr.Button("πŸ’– Test Connection", variant="primary", scale=1)
connection_status = gr.Textbox(
label="Status",
interactive=False,
scale=2,
elem_id="connection-status",
)
connect_btn.click(
fn=check_connection,
inputs=[api_url],
outputs=[connection_status],
)
# ---- Heart Divider ----
gr.HTML('<div class="heart-divider">β™₯ β™₯ β™₯ β™₯ β™₯ β™₯</div>')
# ---- Main Layout: Chat + Settings ----
with gr.Row():
# ────────────────────────────────────────────
# Left: Chat Interface
# ────────────────────────────────────────────
with gr.Column(scale=3):
gr.HTML("""
<div style="
background: linear-gradient(135deg, rgba(255,45,123,0.1), rgba(180,77,255,0.1));
border: 1px solid #2a1a24;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 8px;
">
<h2 style="margin:0; color:#ff6b9d; font-family:'Quicksand',sans-serif;">
πŸ’¬ Chat Mode
</h2>
<p style="margin:4px 0 0 0; color:#7a6578; font-size:0.9em;">
Multi-turn conversation with memory β™₯
</p>
</div>
""")
chatbot = gr.Chatbot(
label="Conversation",
height=450,
layout="bubble",
)
with gr.Row():
chat_input = gr.Textbox(
label="Your message",
placeholder="Type your message here... πŸ’•",
scale=5,
lines=2,
)
chat_send_btn = gr.Button("Send πŸ’–", variant="primary", scale=1)
chat_debug = gr.Markdown(
value="*Debug info will appear here after each message...*",
label="Debug Info",
)
clear_chat_btn = gr.Button("πŸ’” Clear Chat History", variant="stop")
# Heart Divider
gr.HTML('<div class="heart-divider">β™₯ β™₯ β™₯</div>')
gr.HTML("""
<div style="
background: linear-gradient(135deg, rgba(180,77,255,0.1), rgba(255,0,255,0.1));
border: 1px solid #2a1a24;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 8px;
">
<h2 style="margin:0; color:#d68fff; font-family:'Quicksand',sans-serif;">
πŸ“ Completion Mode
</h2>
<p style="margin:4px 0 0 0; color:#7a6578; font-size:0.9em;">
Single-turn, no history β€” test prompts in isolation β™₯
</p>
</div>
""")
with gr.Row():
completion_prompt = gr.Textbox(
label="Prompt",
placeholder="Enter your prompt here... πŸ’•",
lines=4,
scale=5,
)
completion_btn = gr.Button("Generate πŸ’–", variant="primary", scale=1)
completion_output = gr.Textbox(
label="Model Output",
lines=6,
)
completion_debug = gr.Markdown(value="*Debug info will appear here...*")
# ────────────────────────────────────────────
# Right: Settings Panel
# ────────────────────────────────────────────
with gr.Column(scale=2):
# ---- Presets ----
gr.HTML("""
<div class="neon-border" style="margin-bottom: 12px;">
<h3 style="margin:0 0 6px 0; color:#ff6b9d; font-family:'Quicksand',sans-serif;">
⚑ Quick Presets
</h3>
<p style="margin:0; color:#7a6578; font-size:0.85em;">
Load a preset to see how different settings create different behaviors πŸ’–
</p>
</div>
""")
preset_dropdown = gr.Dropdown(
choices=list(PRESETS.keys()),
value=list(PRESETS.keys())[0],
label="Choose a preset",
)
apply_preset_btn = gr.Button("Apply Preset πŸ’–", variant="secondary")
# Heart Divider
gr.HTML('<div class="heart-divider">β™₯ β™₯</div>')
# ---- System Prompt ----
gr.HTML("""
<div style="
background: linear-gradient(135deg, rgba(255,45,123,0.08), rgba(255,0,255,0.05));
border-left: 3px solid #ff2d7b;
border-radius: 0 8px 8px 0;
padding: 8px 12px;
margin-bottom: 8px;
">
<h3 style="margin:0; color:#ff2d7b; font-family:'Quicksand',sans-serif;">
🎭 System Prompt
</h3>
</div>
""")
system_prompt = gr.Textbox(
value=PRESETS["πŸ’– Default"]["system_prompt"],
label="System Prompt",
placeholder="Define the AI's role, personality, and rules... πŸ’•",
lines=5,
info="This sets the AI's behavior for the entire conversation.",
)
# ---- Generation Parameters ----
gr.HTML("""
<div style="
background: linear-gradient(135deg, rgba(180,77,255,0.08), rgba(214,143,255,0.05));
border-left: 3px solid #b44dff;
border-radius: 0 8px 8px 0;
padding: 8px 12px;
margin-bottom: 8px;
">
<h3 style="margin:0; color:#b44dff; font-family:'Quicksand',sans-serif;">
βš™οΈ Generation Parameters
</h3>
</div>
""")
max_tokens = gr.Slider(
minimum=16, maximum=4096, value=512, step=16,
label="Max Tokens",
info="Maximum response length. Higher = longer πŸ’–",
)
temperature = gr.Slider(
minimum=0.0, maximum=2.0, value=0.7, step=0.05,
label="🌑️ Temperature",
info="0 = deterministic, 1 = balanced, 2 = creative/chaotic πŸ’•",
)
top_p = gr.Slider(
minimum=0.0, maximum=1.0, value=0.9, step=0.05,
label="Top-P (Nucleus Sampling)",
info="0.1 = focused, 1.0 = all tokens πŸ’–",
)
top_k = gr.Slider(
minimum=1, maximum=200, value=40, step=1,
label="Top-K",
info="Choose from K most likely next tokens πŸ’•",
)
repeat_penalty = gr.Slider(
minimum=1.0, maximum=2.0, value=1.1, step=0.05,
label="πŸ” Repeat Penalty",
info="1.0 = no penalty, 1.5+ = strong anti-repetition πŸ’–",
)
frequency_penalty = gr.Slider(
minimum=0.0, maximum=2.0, value=0.0, step=0.05,
label="Frequency Penalty",
info="Penalizes based on how often tokens appeared πŸ’•",
)
presence_penalty = gr.Slider(
minimum=0.0, maximum=2.0, value=0.0, step=0.05,
label="Presence Penalty",
info="Encourages new topics πŸ’–",
)
# ---- Context & Stop ----
gr.HTML('<div class="heart-divider">β™₯ β™₯</div>')
gr.HTML("""
<div style="
background: linear-gradient(135deg, rgba(255,0,255,0.08), rgba(255,45,123,0.05));
border-left: 3px solid #ff00ff;
border-radius: 0 8px 8px 0;
padding: 8px 12px;
margin-bottom: 8px;
">
<h3 style="margin:0; color:#ff00ff; font-family:'Quicksand',sans-serif;">
πŸ“ Context & Stopping
</h3>
</div>
""")
n_ctx = gr.Slider(
minimum=256, maximum=8192, value=2048, step=256,
label="n_ctx (Context Window)",
info="⚠️ Requires server restart. Total token budget (prompt + response) πŸ’–",
)
stop_sequences = gr.Textbox(
value="",
label="Stop Sequences (comma-separated)",
placeholder="e.g. \\nUser:, <|end|>, ---",
info="Model stops generating when it hits any of these πŸ’•",
)
# Heart Divider
gr.HTML('<div class="heart-divider">β™₯ β™₯ β™₯</div>')
# ---- Prompt Engineering Tips ----
gr.HTML("""
<div class="neon-border" style="margin-bottom: 12px;">
<h3 style="margin:0 0 6px 0; color:#ff6b9d; font-family:'Quicksand',sans-serif;">
πŸ“š Prompt Engineering Tips
</h3>
<p style="margin:0; color:#7a6578; font-size:0.85em;">
Learn what each parameter does πŸ’–
</p>
</div>
""")
tip_dropdown = gr.Dropdown(
choices=list(PROMPT_ENG_TIPS.keys()),
value=list(PROMPT_ENG_TIPS.keys())[0],
label="Choose a topic",
)
tip_content = gr.Markdown(
value=PROMPT_ENG_TIPS[list(PROMPT_ENG_TIPS.keys())[0]],
)
tip_dropdown.change(
fn=lambda topic: PROMPT_ENG_TIPS.get(topic, ""),
inputs=[tip_dropdown],
outputs=[tip_content],
)
# ---- Bottom Heart Decoration ----
gr.HTML("""
<div style="text-align: center; padding: 16px 0 8px 0;">
<div class="heart-divider" style="font-size: 22px; letter-spacing: 18px; opacity: 0.3;">
β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯ β™₯
</div>
<p style="color: #7a6578; font-size: 0.8em; font-family: 'Quicksand', sans-serif;">
Prompt Engineering Lab πŸ’– Experiment Β· Learn Β· Master πŸ’•
</p>
</div>
""")
# ---- Wire up all the events ----
# Chat send
all_chat_inputs = [
chat_input, chatbot, api_url, system_prompt,
max_tokens, temperature, top_p, top_k,
repeat_penalty, frequency_penalty, presence_penalty,
n_ctx, stop_sequences,
]
chat_send_btn.click(
fn=send_chat,
inputs=all_chat_inputs,
outputs=[chat_input, chatbot, chat_debug],
)
chat_input.submit(
fn=send_chat,
inputs=all_chat_inputs,
outputs=[chat_input, chatbot, chat_debug],
)
# Clear chat
clear_chat_btn.click(
fn=lambda: ([], ""),
outputs=[chatbot, chat_debug],
)
# Completion
all_completion_inputs = [
completion_prompt, api_url, system_prompt,
max_tokens, temperature, top_p, top_k,
repeat_penalty, frequency_penalty, presence_penalty,
n_ctx, stop_sequences,
]
completion_btn.click(
fn=send_completion,
inputs=all_completion_inputs,
outputs=[completion_output, completion_debug],
)
# Presets
apply_preset_btn.click(
fn=apply_preset,
inputs=[preset_dropdown],
outputs=[
system_prompt, max_tokens, temperature, top_p, top_k,
repeat_penalty, frequency_penalty, presence_penalty, stop_sequences,
],
)
return demo
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
if __name__ == "__main__":
demo = build_ui()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
)
# gradio.exceptions.Error: "Data incompatible with messages format. Each message should be a dictionary with 'role' and 'content' keys or a ChatMessage object."