Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import requests
|
|
| 5 |
import random
|
| 6 |
import re
|
| 7 |
import tempfile
|
|
|
|
| 8 |
|
| 9 |
# --- CORE FUNCTIONS ---
|
| 10 |
|
|
@@ -18,81 +19,101 @@ def get_client():
|
|
| 18 |
return None, "β **HuggingFace Token Required**\n\n**Setup:**\n1. Go to Space Settings β Repository Secrets\n2. Add secret: Name=`HF_TOKEN`, Value=(your HF token)\n3. Get token: https://huggingface.co/settings/tokens\n4. Restart Space"
|
| 19 |
return InferenceClient(token=token), None
|
| 20 |
|
| 21 |
-
def
|
| 22 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 23 |
client, error = get_client()
|
| 24 |
-
if error: return None, None, error, None
|
| 25 |
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
failed_models = []
|
| 28 |
|
| 29 |
for model_id in MODELS_TO_TRY:
|
| 30 |
try:
|
| 31 |
-
prompt = f"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
Format your response
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
messages = [{"role": "user", "content": prompt}]
|
| 38 |
-
|
| 39 |
response_stream = client.chat_completion(
|
| 40 |
-
messages, model=model_id, max_tokens=
|
| 41 |
)
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
top_text, bottom_text = "",""
|
| 45 |
-
top_match = re.search(r"Top:\s*(.*)", response, re.IGNORECASE)
|
| 46 |
-
bottom_match = re.search(r"Bottom:\s*(.*)", response, re.IGNORECASE)
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
if not
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
if len(lines) >= 2: bottom_text = lines[1].replace("Bottom:", "").strip()
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
|
|
|
| 59 |
|
| 60 |
except Exception as e:
|
| 61 |
error_msg = str(e).lower()
|
| 62 |
-
if "404" in error_msg or "503" in error_msg or "is currently loading" in error_msg:
|
| 63 |
failed_models.append(model_id.split('/')[-1])
|
| 64 |
continue
|
| 65 |
else:
|
| 66 |
-
return None, None, f"β **AI Error:** {str(e)[:250]}", model_id
|
| 67 |
|
| 68 |
-
return None, None, f"β **All AI Models Are Offline**\n\n**Models Tried:** {', '.join(failed_models)}", None
|
| 69 |
|
| 70 |
|
| 71 |
-
def create_meme(idea: str
|
| 72 |
-
"""Main function to generate the complete meme."""
|
| 73 |
if not idea or len(idea.strip()) < 3:
|
| 74 |
return None, "β Please enter a meme idea (at least 3 characters)!"
|
| 75 |
|
| 76 |
-
#
|
| 77 |
imgflip_user = os.environ.get("IMGFLIP_USERNAME")
|
| 78 |
imgflip_pass = os.environ.get("IMGFLIP_PASSWORD")
|
| 79 |
-
|
| 80 |
if not imgflip_user or not imgflip_pass:
|
| 81 |
-
return None, "β **ImgFlip Credentials Required
|
| 82 |
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
template_id = MEME_TEMPLATES.get(
|
| 87 |
url = "https://api.imgflip.com/caption_image"
|
| 88 |
|
| 89 |
-
# --- FINAL FIX: Use your credentials from the secrets ---
|
| 90 |
payload = {
|
| 91 |
-
'template_id': template_id,
|
| 92 |
-
'
|
| 93 |
-
'password': imgflip_pass,
|
| 94 |
-
'text0': top,
|
| 95 |
-
'text1': bottom
|
| 96 |
}
|
| 97 |
|
| 98 |
try:
|
|
@@ -110,9 +131,10 @@ def create_meme(idea: str, template: str):
|
|
| 110 |
temp_path = tmpfile.name
|
| 111 |
|
| 112 |
status_message = (f"β
**Success!**\n\n"
|
|
|
|
| 113 |
f"π **Top Text:** {top}\n"
|
| 114 |
f"π **Bottom Text:** {bottom}\n\n"
|
| 115 |
-
f"π€ **
|
| 116 |
return temp_path, status_message
|
| 117 |
else:
|
| 118 |
return None, f"β **ImgFlip API Error:** {data.get('error_message', 'Unknown error')}"
|
|
@@ -122,53 +144,67 @@ def create_meme(idea: str, template: str):
|
|
| 122 |
except Exception as e:
|
| 123 |
return None, f"β **An unexpected error occurred:** {str(e)}"
|
| 124 |
|
| 125 |
-
# --- CONFIGURATION & UI
|
| 126 |
-
# [UI code remains the same]
|
| 127 |
|
|
|
|
| 128 |
MEME_TEMPLATES = {
|
| 129 |
"Drake": "181913649", "Distracted Boyfriend": "112126428", "Two Buttons": "87743020",
|
| 130 |
"Expanding Brain": "93895088", "Success Kid": "61544", "Batman Slapping Robin": "438680",
|
| 131 |
"Change My Mind": "129242436", "Woman Yelling at a Cat": "188390779", "Surprised Pikachu": "155067746",
|
| 132 |
}
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
examples = [
|
| 135 |
["When you fix a bug you don't understand"], ["My plans for the weekend vs. what I actually do"],
|
| 136 |
["Saying you'll just have one slice of pizza"], ["Me pretending to be productive in a Zoom meeting"],
|
| 137 |
-
["Checking my bank account after a long weekend"], ["Trying to assemble IKEA furniture with the instructions"],
|
| 138 |
]
|
| 139 |
|
| 140 |
-
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), title="AI Meme Generator") as demo:
|
| 141 |
|
| 142 |
gr.HTML("""
|
| 143 |
<div style='text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 144 |
padding: 30px; border-radius: 15px; color: white; margin-bottom: 20px;'>
|
| 145 |
-
<h1>
|
| 146 |
-
<h3>
|
| 147 |
</div>
|
| 148 |
""")
|
| 149 |
|
| 150 |
with gr.Row():
|
| 151 |
with gr.Column(scale=2):
|
| 152 |
-
idea_input = gr.Textbox(label="π¨ Your Meme Idea", placeholder="Example: When the CI/CD pipeline finally passes...", lines=
|
| 153 |
-
template_dropdown = gr.Dropdown(choices=list(MEME_TEMPLATES.keys()), value="Drake", label="πΌοΈ Meme Template")
|
| 154 |
generate_button = gr.Button("π Generate Meme", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
with gr.Column(scale=1):
|
| 157 |
output_image = gr.Image(label="πΌοΈ Your Generated Meme", type="filepath", show_download_button=True)
|
| 158 |
|
| 159 |
-
output_status = gr.Textbox(label="π Status & Details", lines=
|
| 160 |
|
| 161 |
-
with gr.Accordion("How does this work?", open=False):
|
| 162 |
-
gr.Markdown("""
|
| 163 |
-
This app is **resilient**. It has a list of reliable free models from Hugging Face and automatically tries them until it finds one that is online and working.
|
| 164 |
-
If your `HF_TOKEN` is set up correctly in your Space Secrets, this app should always work.
|
| 165 |
-
""")
|
| 166 |
-
|
| 167 |
gr.Examples(examples=examples, inputs=[idea_input], label="π‘ Meme Ideas to Try")
|
| 168 |
|
|
|
|
| 169 |
generate_button.click(
|
| 170 |
fn=create_meme,
|
| 171 |
-
inputs=[idea_input
|
| 172 |
outputs=[output_image, output_status]
|
| 173 |
)
|
| 174 |
|
|
|
|
| 5 |
import random
|
| 6 |
import re
|
| 7 |
import tempfile
|
| 8 |
+
import json
|
| 9 |
|
| 10 |
# --- CORE FUNCTIONS ---
|
| 11 |
|
|
|
|
| 19 |
return None, "β **HuggingFace Token Required**\n\n**Setup:**\n1. Go to Space Settings β Repository Secrets\n2. Add secret: Name=`HF_TOKEN`, Value=(your HF token)\n3. Get token: https://huggingface.co/settings/tokens\n4. Restart Space"
|
| 20 |
return InferenceClient(token=token), None
|
| 21 |
|
| 22 |
+
def generate_meme_content(idea: str):
|
| 23 |
+
"""
|
| 24 |
+
NEW: In a single AI call, choose the best template AND generate the text.
|
| 25 |
+
Returns: template_name, top_text, bottom_text, error, model_used
|
| 26 |
+
"""
|
| 27 |
client, error = get_client()
|
| 28 |
+
if error: return None, None, None, error, None
|
| 29 |
|
| 30 |
+
# We "teach" the AI about our templates
|
| 31 |
+
template_descriptions = "\n".join([f"- {name}: {desc}" for name, desc in TEMPLATE_GUIDANCE.items()])
|
| 32 |
+
|
| 33 |
+
MODELS_TO_TRY = ["mistralai/Mistral-7B-Instruct-v0.2", "HuggingFaceH4/zephyr-7b-beta"]
|
| 34 |
failed_models = []
|
| 35 |
|
| 36 |
for model_id in MODELS_TO_TRY:
|
| 37 |
try:
|
| 38 |
+
prompt = f"""You are an AI expert in meme culture. Your task is to analyze an idea, choose the best meme template, and generate a funny caption.
|
| 39 |
+
|
| 40 |
+
**1. Analyze the user's idea:** "{idea}"
|
| 41 |
+
|
| 42 |
+
**2. Choose the single best meme template from this list:**
|
| 43 |
+
{template_descriptions}
|
| 44 |
+
|
| 45 |
+
**3. Generate a funny, two-line caption for the chosen template.**
|
| 46 |
|
| 47 |
+
**4. Format your response as a valid JSON object with three keys: "template", "top_text", "bottom_text".**
|
| 48 |
+
|
| 49 |
+
Example Response:
|
| 50 |
+
{{
|
| 51 |
+
"template": "Drake",
|
| 52 |
+
"top_text": "Manually selecting a meme template",
|
| 53 |
+
"bottom_text": "Letting the AI choose the template automatically"
|
| 54 |
+
}}
|
| 55 |
+
|
| 56 |
+
Your JSON response:"""
|
| 57 |
|
| 58 |
messages = [{"role": "user", "content": prompt}]
|
|
|
|
| 59 |
response_stream = client.chat_completion(
|
| 60 |
+
messages, model=model_id, max_tokens=150, temperature=0.8, stream=False
|
| 61 |
)
|
| 62 |
+
response_text = response_stream.choices[0].message.content
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
# --- Robust JSON Parsing ---
|
| 65 |
+
# The model might sometimes add extra text or code blocks around the JSON
|
| 66 |
+
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 67 |
+
if not json_match:
|
| 68 |
+
failed_models.append(f"{model_id.split('/')[-1]} (bad format)")
|
| 69 |
+
continue
|
|
|
|
| 70 |
|
| 71 |
+
parsed_json = json.loads(json_match.group(0))
|
| 72 |
+
template = parsed_json.get("template")
|
| 73 |
+
top_text = parsed_json.get("top_text")
|
| 74 |
+
bottom_text = parsed_json.get("bottom_text")
|
| 75 |
+
|
| 76 |
+
# Validate the response
|
| 77 |
+
if not all([template, top_text, bottom_text]) or template not in MEME_TEMPLATES:
|
| 78 |
+
failed_models.append(f"{model_id.split('/')[-1]} (invalid content)")
|
| 79 |
+
continue
|
| 80 |
|
| 81 |
+
# SUCCESS!
|
| 82 |
+
return template, top_text, bottom_text, None, model_id
|
| 83 |
|
| 84 |
except Exception as e:
|
| 85 |
error_msg = str(e).lower()
|
| 86 |
+
if "404" in error_msg or "503" in error_msg or "is currently loading" in error_msg or "invalid json" in error_msg:
|
| 87 |
failed_models.append(model_id.split('/')[-1])
|
| 88 |
continue
|
| 89 |
else:
|
| 90 |
+
return None, None, None, f"β **AI Error:** {str(e)[:250]}", model_id
|
| 91 |
|
| 92 |
+
return None, None, None, f"β **All AI Models Are Offline or Failing**\n\n**Models Tried:** {', '.join(failed_models)}", None
|
| 93 |
|
| 94 |
|
| 95 |
+
def create_meme(idea: str):
|
| 96 |
+
"""Main function to generate the complete meme, now without a template input."""
|
| 97 |
if not idea or len(idea.strip()) < 3:
|
| 98 |
return None, "β Please enter a meme idea (at least 3 characters)!"
|
| 99 |
|
| 100 |
+
# Check for ImgFlip credentials
|
| 101 |
imgflip_user = os.environ.get("IMGFLIP_USERNAME")
|
| 102 |
imgflip_pass = os.environ.get("IMGFLIP_PASSWORD")
|
|
|
|
| 103 |
if not imgflip_user or not imgflip_pass:
|
| 104 |
+
return None, "β **ImgFlip Credentials Required in Secrets**"
|
| 105 |
|
| 106 |
+
# Call the new intelligent function
|
| 107 |
+
template_name, top, bottom, error, model_used = generate_meme_content(idea)
|
| 108 |
+
if error:
|
| 109 |
+
return None, error
|
| 110 |
|
| 111 |
+
template_id = MEME_TEMPLATES.get(template_name)
|
| 112 |
url = "https://api.imgflip.com/caption_image"
|
| 113 |
|
|
|
|
| 114 |
payload = {
|
| 115 |
+
'template_id': template_id, 'username': imgflip_user,
|
| 116 |
+
'password': imgflip_pass, 'text0': top, 'text1': bottom
|
|
|
|
|
|
|
|
|
|
| 117 |
}
|
| 118 |
|
| 119 |
try:
|
|
|
|
| 131 |
temp_path = tmpfile.name
|
| 132 |
|
| 133 |
status_message = (f"β
**Success!**\n\n"
|
| 134 |
+
f"π§ **AI Chose:** {template_name}\n"
|
| 135 |
f"π **Top Text:** {top}\n"
|
| 136 |
f"π **Bottom Text:** {bottom}\n\n"
|
| 137 |
+
f"π€ **Model Used:** {model_used.split('/')[-1] if model_used else 'N/A'}")
|
| 138 |
return temp_path, status_message
|
| 139 |
else:
|
| 140 |
return None, f"β **ImgFlip API Error:** {data.get('error_message', 'Unknown error')}"
|
|
|
|
| 144 |
except Exception as e:
|
| 145 |
return None, f"β **An unexpected error occurred:** {str(e)}"
|
| 146 |
|
| 147 |
+
# --- CONFIGURATION & UI ---
|
|
|
|
| 148 |
|
| 149 |
+
# Maps template names to their ImgFlip IDs
|
| 150 |
MEME_TEMPLATES = {
|
| 151 |
"Drake": "181913649", "Distracted Boyfriend": "112126428", "Two Buttons": "87743020",
|
| 152 |
"Expanding Brain": "93895088", "Success Kid": "61544", "Batman Slapping Robin": "438680",
|
| 153 |
"Change My Mind": "129242436", "Woman Yelling at a Cat": "188390779", "Surprised Pikachu": "155067746",
|
| 154 |
}
|
| 155 |
|
| 156 |
+
# NEW: Guidance for the AI on what each template means
|
| 157 |
+
TEMPLATE_GUIDANCE = {
|
| 158 |
+
"Drake": "Represents choosing one thing (good) over another (bad). Good for showing preference.",
|
| 159 |
+
"Distracted Boyfriend": "Represents being tempted by something new while neglecting something you already have.",
|
| 160 |
+
"Two Buttons": "Represents a difficult choice, a dilemma, or inner conflict.",
|
| 161 |
+
"Expanding Brain": "Shows increasing levels of enlightenment or absurdity on a topic.",
|
| 162 |
+
"Success Kid": "Represents a small victory, unexpected success, or relief.",
|
| 163 |
+
"Batman Slapping Robin": "Represents a sharp rebuke or correction of a silly idea.",
|
| 164 |
+
"Change My Mind": "For presenting a controversial opinion that you are confident about.",
|
| 165 |
+
"Woman Yelling at a Cat": "Represents a misunderstanding, with one side angry and the other confused.",
|
| 166 |
+
"Surprised Pikachu": "Represents feigned surprise at an obvious outcome.",
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
examples = [
|
| 170 |
["When you fix a bug you don't understand"], ["My plans for the weekend vs. what I actually do"],
|
| 171 |
["Saying you'll just have one slice of pizza"], ["Me pretending to be productive in a Zoom meeting"],
|
|
|
|
| 172 |
]
|
| 173 |
|
| 174 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), title="Intelligent AI Meme Generator") as demo:
|
| 175 |
|
| 176 |
gr.HTML("""
|
| 177 |
<div style='text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 178 |
padding: 30px; border-radius: 15px; color: white; margin-bottom: 20px;'>
|
| 179 |
+
<h1>π§ Intelligent AI Meme Generator</h1>
|
| 180 |
+
<h3>Enter an idea and let the AI choose the best meme template for you!</h3>
|
| 181 |
</div>
|
| 182 |
""")
|
| 183 |
|
| 184 |
with gr.Row():
|
| 185 |
with gr.Column(scale=2):
|
| 186 |
+
idea_input = gr.Textbox(label="π¨ Your Meme Idea", placeholder="Example: When the CI/CD pipeline finally passes...", lines=4)
|
|
|
|
| 187 |
generate_button = gr.Button("π Generate Meme", variant="primary", size="lg")
|
| 188 |
+
|
| 189 |
+
with gr.Accordion("How does this work?", open=False):
|
| 190 |
+
gr.Markdown("""
|
| 191 |
+
This app is **intelligent and resilient**. When you click generate:
|
| 192 |
+
1. The AI analyzes your idea and chooses the best meme template from its knowledge base.
|
| 193 |
+
2. It then generates a funny caption for that specific template.
|
| 194 |
+
3. It automatically finds a working AI model from a list of free options.
|
| 195 |
+
""")
|
| 196 |
|
| 197 |
with gr.Column(scale=1):
|
| 198 |
output_image = gr.Image(label="πΌοΈ Your Generated Meme", type="filepath", show_download_button=True)
|
| 199 |
|
| 200 |
+
output_status = gr.Textbox(label="π Status & Details", lines=5, show_copy_button=True)
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
gr.Examples(examples=examples, inputs=[idea_input], label="π‘ Meme Ideas to Try")
|
| 203 |
|
| 204 |
+
# UPDATED: The click function no longer needs the template dropdown
|
| 205 |
generate_button.click(
|
| 206 |
fn=create_meme,
|
| 207 |
+
inputs=[idea_input],
|
| 208 |
outputs=[output_image, output_status]
|
| 209 |
)
|
| 210 |
|