Spaces:
Runtime error
Runtime error
Download app.py from SanaAdeel/Creative_Writing_Assistant_Chatbot: direct link, hf CLI and curl.
- Browser
- Download file 12.7 kB
-
https://huggingface.co/spaces/SanaAdeel/Creative_Writing_Assistant_Chatbot/resolve/main/app.py
- Command line
-
hf download hf://spaces/SanaAdeel/Creative_Writing_Assistant_Chatbot/app.py
-
curl -L -o app.py https://huggingface.co/spaces/SanaAdeel/Creative_Writing_Assistant_Chatbot/resolve/main/app.py
12.7 kB
| import os | |
| import gradio as gr | |
| import requests | |
| import json | |
| from datetime import datetime | |
| # Load secrets | |
| PAYWALLS_API_KEY = os.environ.get("PAYWALLS_API_KEY") | |
| PAYWALLS_API_URL = os.environ.get("PAYWALLS_API_URL", "https://api.paywalls.ai/v1") | |
| # Free tier limits | |
| FREE_MAX_TOKENS = 150 | |
| PREMIUM_MAX_TOKENS = 2048 | |
| # Style presets (premium feature) | |
| STYLE_PRESETS = { | |
| "neutral": "You are a creative writing assistant.", | |
| "poetic": "You are a poetic writing assistant. Write in lyrical, expressive language with vivid imagery.", | |
| "dramatic": "You are a dramatic writing assistant. Create tension, conflict, and emotional depth.", | |
| "comedic": "You are a comedic writing assistant. Use humor, wit, and light-hearted tone.", | |
| "horror": "You are a horror writing assistant. Build suspense and create eerie, unsettling atmospheres." | |
| } | |
| def check_paywall(user_id: str): | |
| """Check user payment status and generate wallet topup link""" | |
| headers = { | |
| "Authorization": f"Bearer {PAYWALLS_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| # Check if user has active balance/subscription | |
| try: | |
| check_response = requests.get( | |
| f"{PAYWALLS_API_URL}/payments/check", | |
| headers=headers, | |
| params={"user_id": user_id}, | |
| timeout=10 | |
| ) | |
| is_paid = check_response.status_code == 200 and check_response.json().get("paid", False) | |
| except: | |
| is_paid = False | |
| # Direct users to AI Wallet topup page | |
| paywall_url = f"https://wallet.paywalls.ai/topup?user={user_id}" | |
| return is_paid, paywall_url | |
| def free_tier_generation(system_message, history, user_message, max_tokens, temperature): | |
| try: | |
| from huggingface_hub import InferenceClient | |
| client = InferenceClient() | |
| messages = [{"role": "system", "content": system_message}] + history + [{"role": "user", "content": user_message}] | |
| reply = client.chat_completion( | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| model="meta-llama/Llama-3.2-3B-Instruct" | |
| ).choices[0].message.content | |
| return reply | |
| except Exception as e: | |
| print(f"[Free Tier] Error: {e}") | |
| return f"β οΈ Free tier service error: {str(e)}" | |
| def premium_generation(user_id, system_message, history, user_message, max_tokens, temperature, top_p): | |
| """Premium tier using Paywalls proxy""" | |
| headers = { | |
| "Authorization": f"Bearer {PAYWALLS_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| messages = [{"role": "system", "content": system_message}] + history + [{"role": "user", "content": user_message}] | |
| payload = { | |
| "model": "gpt-3.5-turbo", | |
| "messages": messages, | |
| "max_tokens": max_tokens, | |
| "temperature": temperature, | |
| "top_p": top_p, | |
| "user": user_id | |
| } | |
| try: | |
| response = requests.post( | |
| f"{PAYWALLS_API_URL}/chat/completions", | |
| headers=headers, | |
| json=payload, | |
| timeout=60 | |
| ) | |
| response.raise_for_status() | |
| data = response.json() | |
| if data.get("choices"): | |
| return data["choices"][0]["message"]["content"] | |
| return "No response from premium model." | |
| except requests.exceptions.HTTPError as e: | |
| print(f"[Premium] HTTP Error: {e.response.status_code} - {e.response.text}") | |
| return f"β οΈ Premium service error: {e.response.status_code}" | |
| except Exception as e: | |
| print(f"[Premium] Error: {e}") | |
| return f"β οΈ Premium service error: {str(e)}" | |
| def respond(message, history, style, max_tokens, temperature, top_p): | |
| user_id = "demo_user" | |
| is_premium, paywall_url = check_paywall(user_id) | |
| # Check style restriction for free users | |
| if not is_premium: | |
| max_tokens = min(max_tokens, FREE_MAX_TOKENS) | |
| if style != "neutral": | |
| return history + [{"role": "assistant", "content": f"π **Premium Feature Locked**\n\nStyle customization is available for Premium users only.\n\n[β¨ Upgrade to Premium]({paywall_url})"}] | |
| system_message = STYLE_PRESETS.get(style, STYLE_PRESETS["neutral"]) | |
| # Generate response based on tier | |
| if is_premium: | |
| reply = premium_generation(user_id, system_message, history, message, max_tokens, temperature, top_p) | |
| tier_badge = "β¨ **PREMIUM**" | |
| else: | |
| reply = free_tier_generation(system_message, history, message, max_tokens, temperature) | |
| tier_badge = f"π **FREE TIER** β’ 150 tokens limit\n\nπ [Unlock Premium Features]({paywall_url}) β’ Longer stories β’ Custom styles β’ Export" | |
| full_reply = f"{reply}\n\n---\n{tier_badge}" | |
| return history + [{"role": "user", "content": message}, {"role": "assistant", "content": full_reply}] | |
| def export_conversation(history): | |
| user_id = "demo_user" | |
| is_premium, paywall_url = check_paywall(user_id) | |
| if not is_premium: | |
| return None, f"π Export feature requires Premium.\n\n[Upgrade Now]({paywall_url})" | |
| content = f"Creative Writing Session - {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n" | |
| for msg in history: | |
| role = msg["role"].upper() | |
| content += f"{role}: {msg['content']}\n\n" | |
| filepath = "/tmp/creative_writing.txt" | |
| with open(filepath, "w") as f: | |
| f.write(content) | |
| return filepath, "β Exported successfully!" | |
| # Custom CSS for elegant design | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); | |
| * { | |
| font-family: 'Inter', sans-serif !important; | |
| } | |
| .gradio-container { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| } | |
| .main-header { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 30px; | |
| margin-bottom: 25px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| text-align: center; | |
| } | |
| .main-header h1 { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| margin-bottom: 10px; | |
| } | |
| .main-header p { | |
| color: #64748b; | |
| font-size: 1.1em; | |
| } | |
| .chat-container { | |
| background: rgba(255, 255, 255, 0.98); | |
| border-radius: 20px; | |
| padding: 25px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| } | |
| .controls-container { | |
| background: rgba(255, 255, 255, 0.98); | |
| border-radius: 20px; | |
| padding: 25px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| } | |
| .premium-badge { | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| font-weight: 600; | |
| font-size: 0.85em; | |
| display: inline-block; | |
| margin-left: 10px; | |
| } | |
| .feature-card { | |
| background: rgba(102, 126, 234, 0.1); | |
| border-radius: 12px; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| border-left: 4px solid #667eea; | |
| } | |
| button { | |
| border-radius: 12px !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| button:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15) !important; | |
| } | |
| .primary-btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| } | |
| input, textarea, select { | |
| border-radius: 10px !important; | |
| border: 2px solid #e2e8f0 !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| input:focus, textarea:focus, select:focus { | |
| border-color: #667eea !important; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; | |
| } | |
| .slider { | |
| accent-color: #667eea !important; | |
| } | |
| """ | |
| # Build UI | |
| with gr.Blocks(css=custom_css, title="Creative Writing Assistant", theme=gr.themes.Soft()) as demo: | |
| # Header | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.HTML(""" | |
| <div class="main-header"> | |
| <h1>β¨ Creative Writing Assistant</h1> | |
| <p>Transform your ideas into captivating stories with AI-powered creativity</p> | |
| <div style="margin-top: 20px;"> | |
| <span style="background: rgba(102, 126, 234, 0.1); padding: 10px 20px; border-radius: 25px; color: #667eea; font-weight: 600; margin: 0 10px;"> | |
| π Free: 150 tokens | |
| </span> | |
| <span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 10px 20px; border-radius: 25px; color: white; font-weight: 600; margin: 0 10px;"> | |
| β¨ Premium: 2048 tokens + Styles + Export | |
| </span> | |
| </div> | |
| </div> | |
| """) | |
| # Main Content | |
| with gr.Row(equal_height=True): | |
| # Chat Section | |
| with gr.Column(scale=2, elem_classes="chat-container"): | |
| gr.Markdown("### π¬ Your Creative Space") | |
| chatbot = gr.Chatbot( | |
| type="messages", | |
| height=550, | |
| show_copy_button=True, | |
| avatar_images=(None, "https://em-content.zobj.net/source/twitter/376/sparkles_2728.png") | |
| ) | |
| with gr.Row(): | |
| msg = gr.Textbox( | |
| label="", | |
| placeholder="π¨ Write your creative prompt here... (e.g., 'Write a story about a magical garden')", | |
| lines=3, | |
| scale=9 | |
| ) | |
| with gr.Row(): | |
| submit = gr.Button("β¨ Generate Story", variant="primary", scale=2, size="lg") | |
| clear = gr.Button("ποΈ Clear", scale=1, size="lg") | |
| # Controls Section | |
| with gr.Column(scale=1, elem_classes="controls-container"): | |
| gr.Markdown("### ποΈ Creative Controls") | |
| gr.HTML('<div class="feature-card"><b>π¨ Writing Style</b><span class="premium-badge">PREMIUM</span></div>') | |
| style = gr.Dropdown( | |
| choices=list(STYLE_PRESETS.keys()), | |
| value="neutral", | |
| label="", | |
| info="Choose your narrative voice" | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("**π Generation Length**") | |
| max_tokens = gr.Slider( | |
| FREE_MAX_TOKENS, | |
| PREMIUM_MAX_TOKENS, | |
| value=FREE_MAX_TOKENS, | |
| step=50, | |
| label="Max Tokens", | |
| info="Free tier limited to 150 tokens", | |
| elem_classes="slider" | |
| ) | |
| gr.Markdown("**π‘οΈ Creativity Level**") | |
| temperature = gr.Slider( | |
| 0.1, | |
| 2.0, | |
| value=0.8, | |
| step=0.1, | |
| label="Temperature", | |
| info="Higher = more creative", | |
| elem_classes="slider" | |
| ) | |
| gr.Markdown("**π² Diversity**") | |
| top_p = gr.Slider( | |
| 0.1, | |
| 1.0, | |
| value=0.95, | |
| step=0.05, | |
| label="Top-p", | |
| info="Response variety", | |
| elem_classes="slider" | |
| ) | |
| gr.Markdown("---") | |
| gr.HTML('<div class="feature-card"><b>πΎ Export</b><span class="premium-badge">PREMIUM</span></div>') | |
| export_btn = gr.Button("π₯ Download Story", variant="secondary", size="lg") | |
| download_file = gr.File(label="Your File", visible=False) | |
| export_status = gr.Textbox(label="Status", interactive=False, visible=False) | |
| # Footer | |
| gr.HTML(""" | |
| <div style="text-align: center; margin-top: 30px; padding: 20px; background: rgba(255, 255, 255, 0.9); border-radius: 15px;"> | |
| <p style="color: #64748b; font-size: 0.95em;"> | |
| Powered by AI β’ Built with β€οΈ using Gradio & Paywalls.ai | |
| </p> | |
| </div> | |
| """) | |
| # Event handlers | |
| submit.click(respond, [msg, chatbot, style, max_tokens, temperature, top_p], [chatbot]).then(lambda: "", None, [msg]) | |
| msg.submit(respond, [msg, chatbot, style, max_tokens, temperature, top_p], [chatbot]).then(lambda: "", None, [msg]) | |
| clear.click(lambda: None, None, [chatbot]) | |
| export_btn.click(export_conversation, [chatbot], [download_file, export_status]).then( | |
| lambda: (gr.update(visible=True), gr.update(visible=True)), None, [download_file, export_status] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |