Spaces:
Paused
Paused
| """ | |
| 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." |