Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,8 +2,9 @@ import streamlit as st
|
|
| 2 |
import time
|
| 3 |
import random
|
| 4 |
import requests
|
| 5 |
-
from PIL import Image
|
| 6 |
import io
|
|
|
|
| 7 |
|
| 8 |
# Page config
|
| 9 |
st.set_page_config(
|
|
@@ -40,12 +41,10 @@ st.markdown("""
|
|
| 40 |
# Initialize session state
|
| 41 |
if 'generated_image' not in st.session_state:
|
| 42 |
st.session_state.generated_image = None
|
| 43 |
-
if 'image_prompt' not in st.session_state:
|
| 44 |
-
st.session_state.image_prompt = ""
|
| 45 |
|
| 46 |
# Header
|
| 47 |
st.markdown('<h1 class="main-header">π StoryVision AI</h1>', unsafe_allow_html=True)
|
| 48 |
-
st.markdown('<p style="text-align: center; font-size: 1.2rem; color: #666;">Create magical stories with
|
| 49 |
|
| 50 |
# Sidebar controls
|
| 51 |
st.sidebar.markdown("## π¨ Story Settings")
|
|
@@ -70,46 +69,124 @@ art_style = st.sidebar.selectbox(
|
|
| 70 |
["Fantasy Art", "Digital Painting", "Watercolor", "Anime Style", "Photorealistic", "Concept Art"]
|
| 71 |
)
|
| 72 |
|
| 73 |
-
#
|
| 74 |
@st.cache_data(show_spinner=False)
|
| 75 |
-
def
|
| 76 |
-
"""Generate image using
|
| 77 |
-
|
| 78 |
-
# Hugging Face API endpoint (free tier)
|
| 79 |
-
API_URL = "https://api-inference.huggingface.co/models/runwayml/stable-diffusion-v1-5"
|
| 80 |
-
|
| 81 |
-
# Enhance prompt based on style
|
| 82 |
-
style_prompts = {
|
| 83 |
-
"Fantasy Art": "fantasy art, magical, ethereal, detailed, artstation, concept art",
|
| 84 |
-
"Digital Painting": "digital painting, detailed, artstation, concept art, smooth",
|
| 85 |
-
"Watercolor": "watercolor painting, soft colors, artistic, traditional art",
|
| 86 |
-
"Anime Style": "anime style, manga, detailed, vibrant colors, studio quality",
|
| 87 |
-
"Photorealistic": "photorealistic, detailed, high quality, professional photography",
|
| 88 |
-
"Concept Art": "concept art, detailed, artstation, matte painting, digital art"
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
enhanced_prompt = f"{prompt}, {style_prompts.get(style, 'detailed artwork')}, high quality, masterpiece"
|
| 92 |
-
|
| 93 |
try:
|
| 94 |
-
#
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
response = requests.
|
| 99 |
|
| 100 |
if response.status_code == 200:
|
| 101 |
-
# Convert response to image
|
| 102 |
image = Image.open(io.BytesIO(response.content))
|
| 103 |
-
return image,
|
| 104 |
else:
|
| 105 |
-
|
| 106 |
-
return None, enhanced_prompt
|
| 107 |
|
| 108 |
except Exception as e:
|
| 109 |
-
st.error(f"Error
|
| 110 |
-
return None,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
# Story templates (same as before)
|
| 113 |
STORY_TEMPLATES = {
|
| 114 |
"Fantasy": [
|
| 115 |
"In the mystical realm of {setting}, {protagonist} discovered an ancient artifact that pulsed with magical energy. As whispers of forgotten spells filled the air, they realized their destiny was far greater than they ever imagined. Dragons soared overhead while mystical creatures watched from the shadows, all waiting to see if {protagonist} would embrace their newfound power or be consumed by it.",
|
|
@@ -163,17 +240,17 @@ def generate_story(genre, protagonist, setting):
|
|
| 163 |
def create_image_prompt(protagonist, setting, genre, art_style):
|
| 164 |
"""Create optimized prompt for image generation"""
|
| 165 |
prompts = {
|
| 166 |
-
"Fantasy": f"{protagonist} in {setting}, magical
|
| 167 |
-
"Sci-Fi": f"{protagonist} in {setting}, futuristic,
|
| 168 |
-
"Mystery": f"{protagonist} in {setting},
|
| 169 |
-
"Romance": f"{protagonist} in {setting},
|
| 170 |
-
"Adventure": f"{protagonist} in {setting}, action
|
| 171 |
-
"Horror": f"{setting},
|
| 172 |
-
"Comedy": f"{protagonist} in {setting},
|
| 173 |
}
|
| 174 |
|
| 175 |
base_prompt = prompts.get(genre, f"{protagonist} in {setting}")
|
| 176 |
-
return base_prompt
|
| 177 |
|
| 178 |
# Main app
|
| 179 |
col1, col2 = st.columns([1, 1])
|
|
@@ -210,17 +287,21 @@ if st.button("β¨ Generate Story & Art", type="primary"):
|
|
| 210 |
with col2:
|
| 211 |
st.markdown("### π¨ AI Generated Artwork")
|
| 212 |
|
| 213 |
-
# Generate image prompt
|
| 214 |
image_prompt = create_image_prompt(protagonist, setting, genre, art_style)
|
| 215 |
-
st.session_state.image_prompt = image_prompt
|
| 216 |
|
| 217 |
-
with st.spinner("
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
st.session_state.generated_image = generated_image
|
| 220 |
|
| 221 |
if generated_image:
|
| 222 |
st.markdown('<div class="image-container">', unsafe_allow_html=True)
|
| 223 |
-
st.image(generated_image, caption=f"
|
| 224 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 225 |
|
| 226 |
# Image download
|
|
@@ -238,11 +319,7 @@ if st.button("β¨ Generate Story & Art", type="primary"):
|
|
| 238 |
with st.expander("π Image Details"):
|
| 239 |
st.markdown(f"**Prompt:** {image_prompt}")
|
| 240 |
st.markdown(f"**Style:** {art_style}")
|
| 241 |
-
st.markdown(f"**
|
| 242 |
-
else:
|
| 243 |
-
# Fallback if image generation fails
|
| 244 |
-
st.error("π« Image generation temporarily unavailable. The Hugging Face API might be overloaded. Try again in a moment!")
|
| 245 |
-
st.info("π‘ **Tip:** The free Hugging Face API can be slow or temporarily unavailable due to high demand.")
|
| 246 |
|
| 247 |
# Show previous image if available
|
| 248 |
elif st.session_state.generated_image:
|
|
@@ -260,50 +337,47 @@ with col3:
|
|
| 260 |
st.rerun()
|
| 261 |
|
| 262 |
with col4:
|
| 263 |
-
if st.button("π¨
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
st.session_state.generated_image = generated_image
|
| 268 |
-
st.success("β
New artwork generated!")
|
| 269 |
st.rerun()
|
| 270 |
|
| 271 |
with col5:
|
| 272 |
if st.button("π² Surprise Me!"):
|
| 273 |
-
st.session_state.random_generate = True
|
| 274 |
st.rerun()
|
| 275 |
|
| 276 |
# Sidebar info
|
| 277 |
st.sidebar.markdown("---")
|
| 278 |
st.sidebar.markdown("### π About StoryVision AI")
|
| 279 |
st.sidebar.info("""
|
| 280 |
-
π― **
|
| 281 |
-
- β
|
| 282 |
-
- β
|
|
|
|
| 283 |
- β
Download stories & images
|
| 284 |
-
- β
Multiple art styles
|
| 285 |
|
| 286 |
π οΈ **Tech Stack:**
|
| 287 |
- Streamlit
|
| 288 |
-
-
|
| 289 |
-
- Stable Diffusion
|
| 290 |
- Python PIL
|
|
|
|
| 291 |
|
| 292 |
-
|
|
|
|
|
|
|
| 293 |
""")
|
| 294 |
|
| 295 |
-
# Usage stats
|
| 296 |
-
st.sidebar.markdown("### π Live Stats")
|
| 297 |
-
st.sidebar.metric("πΌοΈ Images Generated", f"{random.randint(800, 1200):,}")
|
| 298 |
-
st.sidebar.metric("π Stories Created", f"{random.randint(1500, 2000):,}")
|
| 299 |
-
st.sidebar.metric("π₯ Active Users", f"{random.randint(50, 150)}")
|
| 300 |
-
|
| 301 |
# Footer
|
| 302 |
st.markdown("---")
|
| 303 |
st.markdown("""
|
| 304 |
<div style="text-align: center; color: #666; padding: 2rem;">
|
| 305 |
-
<p>π <strong>StoryVision AI</strong> -
|
| 306 |
-
<p>Made with β€οΈ
|
| 307 |
-
<p><small>β‘ Free tier API - Images may take 10-30 seconds to generate</small></p>
|
| 308 |
</div>
|
| 309 |
""", unsafe_allow_html=True)
|
|
|
|
| 2 |
import time
|
| 3 |
import random
|
| 4 |
import requests
|
| 5 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 6 |
import io
|
| 7 |
+
import hashlib
|
| 8 |
|
| 9 |
# Page config
|
| 10 |
st.set_page_config(
|
|
|
|
| 41 |
# Initialize session state
|
| 42 |
if 'generated_image' not in st.session_state:
|
| 43 |
st.session_state.generated_image = None
|
|
|
|
|
|
|
| 44 |
|
| 45 |
# Header
|
| 46 |
st.markdown('<h1 class="main-header">π StoryVision AI</h1>', unsafe_allow_html=True)
|
| 47 |
+
st.markdown('<p style="text-align: center; font-size: 1.2rem; color: #666;">Create magical stories with AI-generated artwork!</p>', unsafe_allow_html=True)
|
| 48 |
|
| 49 |
# Sidebar controls
|
| 50 |
st.sidebar.markdown("## π¨ Story Settings")
|
|
|
|
| 69 |
["Fantasy Art", "Digital Painting", "Watercolor", "Anime Style", "Photorealistic", "Concept Art"]
|
| 70 |
)
|
| 71 |
|
| 72 |
+
# Working image generation using Pollinations AI (No API key needed!)
|
| 73 |
@st.cache_data(show_spinner=False)
|
| 74 |
+
def generate_image_pollinations(prompt, width=512, height=512):
|
| 75 |
+
"""Generate image using Pollinations.ai - completely free, no API key needed"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
try:
|
| 77 |
+
# Clean and encode the prompt
|
| 78 |
+
clean_prompt = prompt.replace(" ", "%20").replace(",", "%2C")
|
| 79 |
+
|
| 80 |
+
# Pollinations.ai API endpoint
|
| 81 |
+
url = f"https://image.pollinations.ai/prompt/{clean_prompt}?width={width}&height={height}&seed={random.randint(1, 10000)}"
|
| 82 |
|
| 83 |
+
response = requests.get(url, timeout=30)
|
| 84 |
|
| 85 |
if response.status_code == 200:
|
|
|
|
| 86 |
image = Image.open(io.BytesIO(response.content))
|
| 87 |
+
return image, True
|
| 88 |
else:
|
| 89 |
+
return None, False
|
|
|
|
| 90 |
|
| 91 |
except Exception as e:
|
| 92 |
+
st.error(f"Error: {str(e)}")
|
| 93 |
+
return None, False
|
| 94 |
+
|
| 95 |
+
# Alternative: Generate artistic image using code (always works)
|
| 96 |
+
def generate_procedural_art(prompt, protagonist, setting, genre, style):
|
| 97 |
+
"""Generate artistic image using Python - always works offline"""
|
| 98 |
+
|
| 99 |
+
# Create a 512x512 image
|
| 100 |
+
width, height = 512, 512
|
| 101 |
+
image = Image.new('RGB', (width, height))
|
| 102 |
+
draw = ImageDraw.Draw(image)
|
| 103 |
+
|
| 104 |
+
# Color schemes based on genre
|
| 105 |
+
color_schemes = {
|
| 106 |
+
"Fantasy": [(138, 43, 226), (75, 0, 130), (72, 61, 139), (147, 112, 219)],
|
| 107 |
+
"Sci-Fi": [(0, 191, 255), (30, 144, 255), (0, 100, 0), (50, 205, 50)],
|
| 108 |
+
"Mystery": [(25, 25, 112), (72, 61, 139), (105, 105, 105), (128, 128, 128)],
|
| 109 |
+
"Romance": [(255, 182, 193), (255, 105, 180), (219, 112, 147), (199, 21, 133)],
|
| 110 |
+
"Adventure": [(255, 140, 0), (255, 165, 0), (218, 165, 32), (184, 134, 11)],
|
| 111 |
+
"Horror": [(139, 0, 0), (128, 0, 0), (105, 105, 105), (47, 79, 79)],
|
| 112 |
+
"Comedy": [(255, 215, 0), (255, 165, 0), (255, 192, 203), (255, 20, 147)]
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
colors = color_schemes.get(genre, color_schemes["Fantasy"])
|
| 116 |
+
|
| 117 |
+
# Create gradient background
|
| 118 |
+
for y in range(height):
|
| 119 |
+
for x in range(width):
|
| 120 |
+
# Create complex gradient patterns
|
| 121 |
+
color_index = int((x + y + random.randint(-20, 20)) / 100) % len(colors)
|
| 122 |
+
base_color = colors[color_index]
|
| 123 |
+
|
| 124 |
+
# Add some variation
|
| 125 |
+
r = max(0, min(255, base_color[0] + random.randint(-30, 30)))
|
| 126 |
+
g = max(0, min(255, base_color[1] + random.randint(-30, 30)))
|
| 127 |
+
b = max(0, min(255, base_color[2] + random.randint(-30, 30)))
|
| 128 |
+
|
| 129 |
+
draw.point((x, y), (r, g, b))
|
| 130 |
+
|
| 131 |
+
# Add geometric patterns based on genre
|
| 132 |
+
if genre == "Fantasy":
|
| 133 |
+
# Draw magical circles
|
| 134 |
+
for _ in range(5):
|
| 135 |
+
x = random.randint(50, width-50)
|
| 136 |
+
y = random.randint(50, height-50)
|
| 137 |
+
radius = random.randint(20, 80)
|
| 138 |
+
draw.ellipse([x-radius, y-radius, x+radius, y+radius],
|
| 139 |
+
outline=(255, 215, 0), width=3)
|
| 140 |
+
|
| 141 |
+
elif genre == "Sci-Fi":
|
| 142 |
+
# Draw tech patterns
|
| 143 |
+
for _ in range(10):
|
| 144 |
+
x1 = random.randint(0, width)
|
| 145 |
+
y1 = random.randint(0, height)
|
| 146 |
+
x2 = random.randint(0, width)
|
| 147 |
+
y2 = random.randint(0, height)
|
| 148 |
+
draw.line([x1, y1, x2, y2], fill=(0, 255, 255), width=2)
|
| 149 |
+
|
| 150 |
+
# Add title text
|
| 151 |
+
try:
|
| 152 |
+
font_size = 24
|
| 153 |
+
# Try to use a better font, fall back to default
|
| 154 |
+
try:
|
| 155 |
+
font = ImageFont.truetype("arial.ttf", font_size)
|
| 156 |
+
except:
|
| 157 |
+
font = ImageFont.load_default()
|
| 158 |
+
|
| 159 |
+
# Add protagonist name
|
| 160 |
+
text_bbox = draw.textbbox((0, 0), protagonist, font=font)
|
| 161 |
+
text_width = text_bbox[2] - text_bbox[0]
|
| 162 |
+
text_x = (width - text_width) // 2
|
| 163 |
+
text_y = height - 60
|
| 164 |
+
|
| 165 |
+
# Add text with outline
|
| 166 |
+
for dx in [-1, 0, 1]:
|
| 167 |
+
for dy in [-1, 0, 1]:
|
| 168 |
+
if dx != 0 or dy != 0:
|
| 169 |
+
draw.text((text_x + dx, text_y + dy), protagonist, font=font, fill=(0, 0, 0))
|
| 170 |
+
draw.text((text_x, text_y), protagonist, font=font, fill=(255, 255, 255))
|
| 171 |
+
|
| 172 |
+
# Add setting
|
| 173 |
+
setting_bbox = draw.textbbox((0, 0), setting, font=font)
|
| 174 |
+
setting_width = setting_bbox[2] - setting_bbox[0]
|
| 175 |
+
setting_x = (width - setting_width) // 2
|
| 176 |
+
setting_y = 30
|
| 177 |
+
|
| 178 |
+
for dx in [-1, 0, 1]:
|
| 179 |
+
for dy in [-1, 0, 1]:
|
| 180 |
+
if dx != 0 or dy != 0:
|
| 181 |
+
draw.text((setting_x + dx, setting_y + dy), setting, font=font, fill=(0, 0, 0))
|
| 182 |
+
draw.text((setting_x, setting_y), setting, font=font, fill=(255, 255, 255))
|
| 183 |
+
|
| 184 |
+
except:
|
| 185 |
+
pass # Skip text if font issues
|
| 186 |
+
|
| 187 |
+
return image
|
| 188 |
|
| 189 |
+
# Story templates (keeping the same as before)
|
| 190 |
STORY_TEMPLATES = {
|
| 191 |
"Fantasy": [
|
| 192 |
"In the mystical realm of {setting}, {protagonist} discovered an ancient artifact that pulsed with magical energy. As whispers of forgotten spells filled the air, they realized their destiny was far greater than they ever imagined. Dragons soared overhead while mystical creatures watched from the shadows, all waiting to see if {protagonist} would embrace their newfound power or be consumed by it.",
|
|
|
|
| 240 |
def create_image_prompt(protagonist, setting, genre, art_style):
|
| 241 |
"""Create optimized prompt for image generation"""
|
| 242 |
prompts = {
|
| 243 |
+
"Fantasy": f"fantasy art of {protagonist} in {setting}, magical, ethereal, detailed artwork",
|
| 244 |
+
"Sci-Fi": f"sci-fi art of {protagonist} in {setting}, futuristic, technology, space art",
|
| 245 |
+
"Mystery": f"mysterious scene with {protagonist} in {setting}, noir style, detective",
|
| 246 |
+
"Romance": f"romantic art of {protagonist} in {setting}, beautiful, soft lighting",
|
| 247 |
+
"Adventure": f"adventure art of {protagonist} in {setting}, action, dynamic scene",
|
| 248 |
+
"Horror": f"dark art of {setting}, horror atmosphere, mysterious figure, gothic",
|
| 249 |
+
"Comedy": f"cheerful art of {protagonist} in {setting}, bright colors, happy scene"
|
| 250 |
}
|
| 251 |
|
| 252 |
base_prompt = prompts.get(genre, f"{protagonist} in {setting}")
|
| 253 |
+
return f"{base_prompt}, {art_style.lower()}, high quality, detailed"
|
| 254 |
|
| 255 |
# Main app
|
| 256 |
col1, col2 = st.columns([1, 1])
|
|
|
|
| 287 |
with col2:
|
| 288 |
st.markdown("### π¨ AI Generated Artwork")
|
| 289 |
|
|
|
|
| 290 |
image_prompt = create_image_prompt(protagonist, setting, genre, art_style)
|
|
|
|
| 291 |
|
| 292 |
+
with st.spinner("οΏ½οΏ½οΏ½οΏ½ Generating AI artwork..."):
|
| 293 |
+
# Try Pollinations AI first (free, no API key)
|
| 294 |
+
generated_image, success = generate_image_pollinations(image_prompt)
|
| 295 |
+
|
| 296 |
+
if not success or generated_image is None:
|
| 297 |
+
st.info("π¨ Using procedural art generation...")
|
| 298 |
+
generated_image = generate_procedural_art(image_prompt, protagonist, setting, genre, art_style)
|
| 299 |
+
|
| 300 |
st.session_state.generated_image = generated_image
|
| 301 |
|
| 302 |
if generated_image:
|
| 303 |
st.markdown('<div class="image-container">', unsafe_allow_html=True)
|
| 304 |
+
st.image(generated_image, caption=f"Generated: {protagonist} in {setting}", use_column_width=True)
|
| 305 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 306 |
|
| 307 |
# Image download
|
|
|
|
| 319 |
with st.expander("π Image Details"):
|
| 320 |
st.markdown(f"**Prompt:** {image_prompt}")
|
| 321 |
st.markdown(f"**Style:** {art_style}")
|
| 322 |
+
st.markdown(f"**Method:** {'Pollinations AI' if success else 'Procedural Art'}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
# Show previous image if available
|
| 325 |
elif st.session_state.generated_image:
|
|
|
|
| 337 |
st.rerun()
|
| 338 |
|
| 339 |
with col4:
|
| 340 |
+
if st.button("π¨ New Artwork"):
|
| 341 |
+
if 'generated_image' in st.session_state:
|
| 342 |
+
# Regenerate with different seed
|
| 343 |
+
image_prompt = create_image_prompt(protagonist, setting, genre, art_style)
|
| 344 |
+
with st.spinner("π¨ Creating new artwork..."):
|
| 345 |
+
generated_image, success = generate_image_pollinations(image_prompt)
|
| 346 |
+
if not success:
|
| 347 |
+
generated_image = generate_procedural_art(image_prompt, protagonist, setting, genre, art_style)
|
| 348 |
st.session_state.generated_image = generated_image
|
|
|
|
| 349 |
st.rerun()
|
| 350 |
|
| 351 |
with col5:
|
| 352 |
if st.button("π² Surprise Me!"):
|
|
|
|
| 353 |
st.rerun()
|
| 354 |
|
| 355 |
# Sidebar info
|
| 356 |
st.sidebar.markdown("---")
|
| 357 |
st.sidebar.markdown("### π About StoryVision AI")
|
| 358 |
st.sidebar.info("""
|
| 359 |
+
π― **Features:**
|
| 360 |
+
- β
Real AI image generation
|
| 361 |
+
- β
No API keys required
|
| 362 |
+
- β
Always works offline backup
|
| 363 |
- β
Download stories & images
|
|
|
|
| 364 |
|
| 365 |
π οΈ **Tech Stack:**
|
| 366 |
- Streamlit
|
| 367 |
+
- Pollinations.ai (free)
|
|
|
|
| 368 |
- Python PIL
|
| 369 |
+
- Procedural art generation
|
| 370 |
|
| 371 |
+
π¨ **Art Methods:**
|
| 372 |
+
1. Pollinations AI (primary)
|
| 373 |
+
2. Procedural art (backup)
|
| 374 |
""")
|
| 375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
# Footer
|
| 377 |
st.markdown("---")
|
| 378 |
st.markdown("""
|
| 379 |
<div style="text-align: center; color: #666; padding: 2rem;">
|
| 380 |
+
<p>π <strong>StoryVision AI</strong> - Reliable AI artwork generation!</p>
|
| 381 |
+
<p>Made with β€οΈ | Always works, no API keys needed</p>
|
|
|
|
| 382 |
</div>
|
| 383 |
""", unsafe_allow_html=True)
|