LLMFeed / app.py
codelion's picture
Update app.py
83b50de verified
raw
history blame
18.1 kB
import gradio as gr
from PIL import Image
from io import BytesIO
import base64
import random
import urllib.parse
import time
# Check Gradio version
required_version = "4.44.0"
current_version = gr.__version__
if current_version < required_version:
raise ValueError(f"Gradio version {current_version} is outdated. Please upgrade to {required_version} or later using 'pip install gradio=={required_version}'.")
def clean_response_text(response_text):
"""Clean API response by removing Markdown code block markers."""
cleaned_text = response_text.strip()
if cleaned_text.startswith("```json"):
cleaned_text = cleaned_text[len("```json"):].strip()
if cleaned_text.endswith("```"):
cleaned_text = cleaned_text[:-len("```")].strip()
return cleaned_text
def generate_ideas(user_input):
"""Generate 5 creative TikTok video ideas based on user input."""
yield (10, f"Brainstorming epic ideas for {user_input}... 🌟")
try:
# Placeholder for actual API call (e.g., Google Generative AI)
ideas = [
f"A vibrant {user_input} scene with dynamic lighting",
f"A close-up of {user_input} in a futuristic city",
f"A high-energy {user_input} moment with bold colors",
f"A serene {user_input} scene with soft focus",
f"An action-packed {user_input} challenge"
]
yield (20, f"Ideas locked in for {user_input}! πŸš€")
yield ideas # Yield the final list
except Exception as e:
print(f"Error generating ideas: {e}")
yield (20, f"Oops, tweaking the plan for {user_input}... πŸ”§")
default_ideas = [
f"A dramatic {user_input} scene with cinematic lighting",
f"A close-up of {user_input} in a futuristic setting",
f"A high-energy {user_input} moment with vibrant colors"
]
yield default_ideas # Yield default ideas on failure
def generate_item(user_input, ideas, generate_video=False):
"""Generate a feed item (image and optionally video) with progress updates."""
yield (20, f"Crafting your {user_input} masterpiece... 🎨")
try:
selected_idea = random.choice(ideas) # Requires ideas to be a list
# Simulate image generation
image = Image.new('RGB', (360, 640), color='gray') # Placeholder
buffered = BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
video_base64 = None
if generate_video:
yield (60, f"Filming a viral video for {user_input}... πŸŽ₯")
time.sleep(2) # Simulate video generation
# Placeholder for video generation
video_base64 = base64.b64encode(b"fake_video_data").decode()
yield (95, f"Polishing your {user_input} masterpiece... ✨")
yield {
'text': f"{selected_idea}! πŸ”₯ #{user_input.replace(' ', '')}",
'image_base64': img_str,
'video_base64': video_base64,
'ideas': ideas
}
except Exception as e:
print(f"Error generating item: {e}")
yield (95, f"Falling back to placeholder for {user_input}... πŸ–ΌοΈ")
image = Image.new('RGB', (360, 640), color='gray')
buffered = BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
yield {
'text': f"Amazing {user_input}! πŸ”₯ #{user_input.replace(' ', '')}",
'image_base64': img_str,
'video_base64': None,
'ideas': ideas
}
def generate_progress_html(progress, message, user_input):
"""Generate HTML for the progress bar and witty text."""
return f"""
<div id="progress-container" style="
display: flex; flex-direction: column; align-items: center; justify-content: center;
max-width: 360px; margin: 0 auto; background-color: #000; height: 200px;
border: 1px solid #333; border-radius: 10px; color: white; font-family: Arial, sans-serif;">
<div id="loading-message" style="font-size: 18px; font-weight: bold; text-align: center; margin-bottom: 20px;">
{message}
</div>
<div style="width: 80%; height: 10px; background-color: #333; border-radius: 5px; overflow: hidden;">
<div id="progress-bar" style="width: {progress}%; height: 100%; background: linear-gradient(to right, #ff2d55, #ff5e78);"></div>
</div>
<div style="margin-top: 10px; font-size: 14px; color: #ccc;">{int(progress)}% Complete</div>
<style>
@keyframes pulse {{ 0% {{ opacity: 1; }} 50% {{ opacity: 0.5; }} 100% {{ opacity: 1; }} }}
#loading-message {{ animation: pulse 2s infinite; }}
</style>
</div>
"""
def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False):
"""Generate HTML for the feed display."""
if is_loading:
return """
<div id="feed-container" style="
display: flex; flex-direction: column; align-items: center;
max-width: 360px; margin: 0 auto; background-color: #000; height: 640px;
border: 1px solid #333; border-radius: 10px; color: white;">
</div>
"""
if not feed_items or current_index >= len(feed_items):
return """
<div style="color: white; text-align: center; max-width: 360px; margin: 0 auto;">
Enter a concept or idea to start your feed!
</div>
"""
item = feed_items[current_index]
media_element = f"""
<img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 1;">
"""
if item['video_base64']:
media_element = f"""
<video id="feed-video" controls style="
width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 1;">
<source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
Your browser does not support the video tag.
</video>
"""
return f"""
<div id="feed-container" style="
display: flex; flex-direction: column; align-items: center;
max-width: 360px; margin: 0 auto; background-color: #000; height: 640px;
border: 1px solid #333; border-radius: 10px; position: relative;">
<div class="feed-item" style="
width: 100%; height: 100%; position: relative; display: flex;
flex-direction: column; justify-content: flex-end; overflow: hidden; cursor: pointer;"
onclick="handleClick(event)">
{media_element}
<div style="
position: relative; z-index: 2; background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
padding: 20px; color: white; font-family: Arial, sans-serif; font-size: 18px; font-weight: bold;">
{item['text']}
</div>
</div>
</div>
<script>
function handleClick(event) {{
const media = document.getElementById('feed-video') || document.getElementById('feed-image');
const rect = media.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const width = rect.width;
if (clickX > width * 0.75) {{
document.getElementById('previous-button').click();
}}
}}
</script>
"""
def generate_share_links(image_base64, video_base64, caption):
"""Generate share links for social media platforms."""
image_data_url = f"data:image/png;base64,{image_base64}"
encoded_caption = urllib.parse.quote(caption)
download_links = f"""
<p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
<a href="{image_data_url}" download="feed_item.png" style="
background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 5px;
text-decoration: none; font-size: 14px; font-weight: bold;">Download Image</a>
"""
if video_base64:
video_data_url = f"data:video/mp4;base64,{video_base64}"
download_links += f"""
<a href="{video_data_url}" download="feed_video.mp4" style="
background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 5px;
text-decoration: none; font-size: 14px; font-weight: bold;">Download Video</a>
"""
download_links += "</div>"
instruction = """
<p style="text-align: center; margin-bottom: 10px;">
Click a share button below to start a post with the caption, then manually upload the downloaded media.
</p>
"""
share_links = f"""
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
<a href="https://studio.youtube.com/channel/UC/videos/upload?description={encoded_caption}" target="_blank" style="
background-color: #ff0000; color: white; padding: 8px 16px; border-radius: 5px;
text-decoration: none; font-size: 14px; font-weight: bold;">Share to YouTube as a Short</a>
</div>
"""
return f"""
<div style="display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 10px; color: white;">
{download_links}
{instruction}
{share_links}
</div>
"""
def start_feed(user_input, generate_video, current_index, feed_items):
"""Start or update the feed based on user input."""
user_input = user_input.strip() or "trending"
current_user_input = user_input
is_loading = True
progress_html = generate_progress_html(0, f"Getting started with {user_input}... πŸš€", user_input)
yield (current_user_input, current_index, feed_items, gr.update(), gr.update(), is_loading, progress_html)
try:
ideas_gen = generate_ideas(user_input)
ideas = None
for update in ideas_gen:
if isinstance(update, tuple):
progress, message = update
progress_html = generate_progress_html(progress, message, user_input)
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
else:
ideas = update # Final list of ideas
if ideas is None or not isinstance(ideas, list):
raise ValueError("Failed to generate ideas")
item_gen = generate_item(user_input, ideas, generate_video)
item = None
for update in item_gen:
if isinstance(update, tuple):
progress, message = update
progress_html = generate_progress_html(progress, message, user_input)
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
else:
item = update # Final item dictionary
if item is None or not isinstance(item, dict):
raise ValueError("Failed to generate item")
feed_items = [item]
current_index = 0
feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
share_html = generate_share_links(item['image_base64'], item['video_base64'], item['text'])
yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
except Exception as e:
print(f"Error in start_feed: {e}")
feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
progress_html = generate_progress_html(100, "Oops, something went wrong! πŸ˜…", user_input)
yield (current_user_input, current_index, feed_items, feed_html, "", False, progress_html)
def load_next(user_input, generate_video, current_index, feed_items):
"""Load the next item in the feed."""
current_user_input = user_input.strip() or "trending"
user_input = current_user_input
is_loading = True
progress_html = generate_progress_html(0, f"Loading next {user_input} vibe... πŸš€", user_input)
yield (current_user_input, current_index, feed_items, gr.update(), gr.update(), is_loading, progress_html)
try:
if current_index + 1 < len(feed_items):
current_index += 1
progress_html = generate_progress_html(50, f"Switching to the next {user_input} moment... πŸ”„", user_input)
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
else:
ideas = feed_items[-1]['ideas'] if feed_items else None
if not ideas:
ideas_gen = generate_ideas(user_input)
for update in ideas_gen:
if isinstance(update, tuple):
progress, message = update
progress_html = generate_progress_html(progress, message, user_input)
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
else:
ideas = update
if ideas is None or not isinstance(ideas, list):
raise ValueError("Failed to generate ideas")
item_gen = generate_item(user_input, ideas, generate_video)
for update in item_gen:
if isinstance(update, tuple):
progress, message = update
progress_html = generate_progress_html(progress, message, user_input)
yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
else:
item = update
feed_items.append(item)
current_index = len(feed_items) - 1
if item is None or not isinstance(item, dict):
raise ValueError("Failed to generate item")
feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
except Exception as e:
print(f"Error in load_next: {e}")
feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
progress_html = generate_progress_html(100, "Oops, something went wrong! πŸ˜…", user_input)
yield (current_user_input, current_index, feed_items, feed_html, "", False, progress_html)
def load_previous(user_input, generate_video, current_index, feed_items):
"""Load the previous item in the feed."""
current_user_input = user_input.strip() or "trending"
if current_index > 0:
current_index -= 1
feed_html = generate_html(feed_items, False, current_index, current_user_input, is_loading=False)
share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
return current_user_input, current_index, feed_items, feed_html, share_html, False, ""
# Define the Gradio interface
with gr.Blocks(
css="""
body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
.gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
input, select, button, .gr-checkbox { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
button { background-color: #ff2d55; border: none; }
button:hover { background-color: #e0264b; }
.gr-button { width: 100%; margin-top: 10px; }
.gr-form { background-color: #111; padding: 15px; border-radius: 10px; }
""",
title="Create Your Feed"
) as demo:
# State variables
current_user_input = gr.State(value="")
current_index = gr.State(value=0)
feed_items = gr.State(value=[])
is_loading = gr.State(value=False)
share_links = gr.State(value="")
# Input section
with gr.Column(elem_classes="gr-form"):
gr.Markdown("### Create Your Feed")
user_input = gr.Textbox(
label="Enter Concept or Ideas",
value="",
placeholder="e.g., sushi adventure, neon tech",
submit_btn=False
)
generate_video_checkbox = gr.Checkbox(
label="Generate Video (may take longer)",
value=False
)
magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
# Output display
progress_html = gr.HTML(label="Progress", visible=True)
feed_html = gr.HTML()
share_html = gr.HTML(label="Share this item:")
# Event handlers
user_input.submit(
fn=start_feed,
inputs=[user_input, generate_video_checkbox, current_index, feed_items],
outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
)
magic_button.click(
fn=load_next,
inputs=[user_input, generate_video_checkbox, current_index, feed_items],
outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
)
previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
previous_button.click(
fn=load_previous,
inputs=[user_input, generate_video_checkbox, current_index, feed_items],
outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
)
# Launch the app with share=True for public link
demo.launch(share=True) # Note: share=True requires a Gradio account and may have usage limits