codelion commited on
Commit
4f86d21
·
verified ·
1 Parent(s): 0e83379

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -114
app.py CHANGED
@@ -15,64 +15,79 @@ except KeyError:
15
  raise ValueError("Please set the GEMINI_API_KEY environment variable.")
16
  client = genai.Client(api_key=api_key)
17
 
18
- def generate_item(tag, item_index):
19
  """
20
- Generate a single feed item with diverse text and image.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  Args:
23
  tag (str): The tag to base the content on.
24
- item_index (int): Index of the item to ensure diversity.
25
 
26
  Returns:
27
  dict: A dictionary with 'text' (str) and 'image_base64' (str).
28
  """
29
- # Define varied styles for diversity in image generation
30
- styles = [
31
- "futuristic neon lighting",
32
- "soft pastel tones with a dreamy vibe",
33
- "vibrant and colorful pop art style",
34
- "minimalist black and white aesthetic",
35
- "retro 80s synthwave look",
36
- "golden hour sunlight with warm tones"
37
- ]
38
- perspectives = [
39
- "a close-up view",
40
- "a wide-angle shot",
41
- "an aerial perspective",
42
- "a side profile",
43
- "a dynamic angled shot"
44
- ]
45
- style = random.choice(styles)
46
- perspective = random.choice(perspectives)
47
 
48
- # Generate text with high temperature for diversity, using the correct config
49
  prompt = f"""
50
- Generate a short, engaging TikTok-style caption about {tag}.
51
- Return the response as a JSON object with a single key 'caption' containing the caption text.
52
- Example: {{"caption": "Craving this yummy treat! 😍 #foodie"}}
53
- Do not include additional commentary or options.
54
- Use creative and varied language to ensure uniqueness.
 
55
  """
56
- text_response = client.models.generate_content(
57
  model='gemini-2.5-flash-preview-04-17',
58
  contents=[prompt],
59
- config=types.GenerateContentConfig(
60
- temperature=1.2 # High temperature for diversity, passed via config
61
- )
62
  )
63
- # Parse JSON response to extract the caption
64
  try:
65
- response_json = json.loads(text_response.text.strip())
66
  text = response_json['caption']
 
67
  except (json.JSONDecodeError, KeyError):
68
- text = f"Obsessed with {tag}! 🔥 #{tag}" # Fallback caption
 
 
69
 
70
- # Generate a diverse image based on the tag
71
- image_prompt = f"""
72
- A high-quality visual scene representing {tag}, designed for a TikTok video.
73
- The image should be {perspective} with a {style}.
74
- Ensure the image is colorful, engaging, and has no text or letters.
75
- """
76
  image_response = client.models.generate_images(
77
  model='imagen-3.0-generate-002',
78
  prompt=image_prompt,
@@ -89,59 +104,127 @@ def generate_item(tag, item_index):
89
  image = Image.open(BytesIO(generated_image.image.image_bytes))
90
  else:
91
  # Fallback to a placeholder image
92
- image = Image.new('RGB', (360, 640), color='gray') # 9:16 aspect ratio
93
 
94
  # Convert the image to base64
95
  buffered = BytesIO()
96
  image.save(buffered, format="PNG")
97
  img_str = base64.b64encode(buffered.getvalue()).decode()
98
 
99
- return {'text': text, 'image_base64': img_str}
100
 
101
- def start_feed(tag):
102
  """
103
- Start a new feed with the given tag by generating one initial item.
104
 
105
  Args:
106
  tag (str): The tag to generate content for.
 
 
 
107
 
108
  Returns:
109
- tuple: (current_tag, feed_items, html_content)
110
  """
111
  if not tag.strip():
112
  tag = "trending"
113
- item = generate_item(tag, 0)
114
- feed_items = [item]
115
- html_content = generate_html(feed_items)
116
- return tag, feed_items, html_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- def load_more(current_tag, feed_items):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  """
120
- Append a new item to the existing feed and scroll to the latest item.
121
 
122
  Args:
123
- current_tag (str): The tag currently being used for the feed.
 
124
  feed_items (list): The current list of feed items.
 
125
 
126
  Returns:
127
- tuple: (current_tag, updated_feed_items, updated_html_content)
128
  """
129
- new_item = generate_item(current_tag, len(feed_items))
130
- feed_items.append(new_item)
131
- html_content = generate_html(feed_items, scroll_to_latest=True)
132
- return current_tag, feed_items, html_content
133
 
134
- def generate_html(feed_items, scroll_to_latest=False):
135
  """
136
- Generate an HTML string to display the feed items in a TikTok-like carousel.
137
 
138
  Args:
139
  feed_items (list): List of dictionaries containing 'text' and 'image_base64'.
140
- scroll_to_latest (bool): Whether to auto-scroll to the latest item.
 
141
 
142
  Returns:
143
  str: HTML string representing the feed.
144
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  html_str = """
146
  <div id="feed-container" style="
147
  display: flex;
@@ -151,37 +234,21 @@ def generate_html(feed_items, scroll_to_latest=False):
151
  margin: 0 auto;
152
  background-color: #000;
153
  height: 640px;
154
- overflow-y: scroll;
155
- scroll-snap-type: y mandatory;
156
- scrollbar-width: none;
157
- -ms-overflow-style: none;
158
  border: 1px solid #333;
159
  border-radius: 10px;
 
160
  ">
161
- """
162
- # Hide scrollbar
163
- html_str += """
164
- <style>
165
- #feed-container::-webkit-scrollbar {
166
- display: none;
167
- }
168
- .feed-item {
169
- scroll-snap-align: start;
170
- }
171
- </style>
172
- """
173
- for idx, item in enumerate(feed_items):
174
- html_str += f"""
175
- <div class="feed-item" id="item-{idx}" style="
176
  width: 100%;
177
- height: 640px;
178
  position: relative;
179
  display: flex;
180
  flex-direction: column;
181
  justify-content: flex-end;
182
  overflow: hidden;
183
- ">
184
- <img src="data:image/png;base64,{item['image_base64']}" style="
 
185
  width: 100%;
186
  height: 100%;
187
  object-fit: cover;
@@ -201,19 +268,28 @@ def generate_html(feed_items, scroll_to_latest=False):
201
  font-weight: bold;
202
  text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
203
  ">
204
- {item['text']}
205
  </div>
206
  </div>
207
- """
208
- html_str += "</div>"
209
-
210
- # Auto-scroll to the latest item if requested
211
- if scroll_to_latest and feed_items:
212
- html_str += f"""
213
- <script>
214
- document.getElementById('item-{len(feed_items) - 1}').scrollIntoView({{ behavior: 'smooth' }});
215
- </script>
216
- """
 
 
 
 
 
 
 
 
 
217
 
218
  return html_str
219
 
@@ -222,14 +298,18 @@ with gr.Blocks(
222
  css="""
223
  body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
224
  .gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
225
- input, select, button { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
226
- button { background-color: #ff2d55; border: none; }
227
- button:hover { background-color: #e0264b; }
228
- .gr-button { width: 100%; margin-top: 10px; }
229
  .gr-form { background-color: #111; padding: 15px; border-radius: 10px; }
 
230
  """,
231
  title="TikTok-Style Infinite Feed"
232
  ) as demo:
 
 
 
 
 
 
233
  # Input section
234
  with gr.Column(elem_classes="gr-form"):
235
  gr.Markdown("### Create Your TikTok Feed")
@@ -242,34 +322,77 @@ with gr.Blocks(
242
  tag_input = gr.Textbox(
243
  label="Or Enter a Custom Tag",
244
  value="food",
245
- placeholder="e.g., sushi, adventure"
 
246
  )
247
- with gr.Row():
248
- start_button = gr.Button("Start Feed")
249
- load_more_button = gr.Button("Load More")
 
 
 
 
 
 
250
 
251
  # Output display
252
  feed_html = gr.HTML()
253
 
254
- # State variables
255
- current_tag = gr.State(value="")
256
- feed_items = gr.State(value=[])
257
-
258
  # Event handlers
259
  def set_tag(selected_tag):
260
- """Update the tag input when a suggested tag is selected."""
261
  return selected_tag
262
 
263
- suggested_tags.change(fn=set_tag, inputs=suggested_tags, outputs=tag_input)
264
- start_button.click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  fn=start_feed,
266
- inputs=tag_input,
267
- outputs=[current_tag, feed_items, feed_html]
 
 
 
 
268
  )
269
- load_more_button.click(
270
- fn=load_more,
271
- inputs=[current_tag, feed_items],
272
- outputs=[current_tag, feed_items, feed_html]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  )
274
 
275
  # Launch the app with a public link
 
15
  raise ValueError("Please set the GEMINI_API_KEY environment variable.")
16
  client = genai.Client(api_key=api_key)
17
 
18
+ def generate_ideas(tag):
19
  """
20
+ Generate a diverse set of ideas related to the tag using the LLM.
21
+
22
+ Args:
23
+ tag (str): The tag to base the ideas on.
24
+
25
+ Returns:
26
+ list: A list of ideas as strings.
27
+ """
28
+ prompt = f"""
29
+ Generate a list of 5 diverse and creative ideas related to {tag} that can be used for a TikTok video.
30
+ Each idea should be a short sentence describing a specific scene or concept.
31
+ Return the response as a JSON object with a single key 'ideas' containing a list of 5 ideas.
32
+ Example: {{"ideas": ["A neon-lit gaming setup with RGB lights flashing", "A futuristic robot assembling a gadget"]}}
33
+ """
34
+ response = client.models.generate_content(
35
+ model='gemini-2.5-flash-preview-04-17',
36
+ contents=[prompt],
37
+ config=types.GenerateContentConfig(temperature=1.2)
38
+ )
39
+ try:
40
+ response_json = json.loads(response.text.strip())
41
+ ideas = response_json['ideas']
42
+ return ideas
43
+ except (json.JSONDecodeError, KeyError):
44
+ # Fallback ideas if parsing fails
45
+ return [
46
+ f"A vibrant {tag} scene at sunset",
47
+ f"A close-up of {tag} with neon lights",
48
+ f"A futuristic take on {tag} with holograms",
49
+ f"A cozy {tag} moment with warm lighting",
50
+ f"An action-packed {tag} scene with dynamic colors"
51
+ ]
52
+
53
+ def generate_item(tag, ideas):
54
+ """
55
+ Generate a single feed item using one of the ideas.
56
 
57
  Args:
58
  tag (str): The tag to base the content on.
59
+ ideas (list): List of ideas to choose from.
60
 
61
  Returns:
62
  dict: A dictionary with 'text' (str) and 'image_base64' (str).
63
  """
64
+ # Select a random idea for diversity
65
+ selected_idea = random.choice(ideas)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ # Second LLM call to generate the precise image prompt and caption
68
  prompt = f"""
69
+ Based on the idea "{selected_idea}", create content for a TikTok video about {tag}.
70
+ Return a JSON object with two keys:
71
+ - 'caption': A short, viral TikTok-style caption with hashtags.
72
+ - 'image_prompt': A detailed image prompt for generating a high-quality visual scene.
73
+ The image prompt should describe the scene vividly, specify a perspective and style, and ensure no text or letters are included.
74
+ Example: {{"caption": "Neon vibes only! 🌌 #tech", "image_prompt": "A close-up view of a neon-lit gaming setup with RGB lights flashing, in a futuristic style, no text or letters"}}
75
  """
76
+ response = client.models.generate_content(
77
  model='gemini-2.5-flash-preview-04-17',
78
  contents=[prompt],
79
+ config=types.GenerateContentConfig(temperature=1.2)
 
 
80
  )
 
81
  try:
82
+ response_json = json.loads(response.text.strip())
83
  text = response_json['caption']
84
+ image_prompt = response_json['image_prompt']
85
  except (json.JSONDecodeError, KeyError):
86
+ # Fallback if parsing fails
87
+ text = f"Obsessed with {tag}! 🔥 #{tag}"
88
+ image_prompt = f"A vivid scene of {selected_idea}, in a vibrant pop art style, no text or letters"
89
 
90
+ # Generate the image using the precise prompt
 
 
 
 
 
91
  image_response = client.models.generate_images(
92
  model='imagen-3.0-generate-002',
93
  prompt=image_prompt,
 
104
  image = Image.open(BytesIO(generated_image.image.image_bytes))
105
  else:
106
  # Fallback to a placeholder image
107
+ image = Image.new('RGB', (360, 640), color='gray')
108
 
109
  # Convert the image to base64
110
  buffered = BytesIO()
111
  image.save(buffered, format="PNG")
112
  img_str = base64.b64encode(buffered.getvalue()).decode()
113
 
114
+ return {'text': text, 'image_base64': img_str, 'ideas': ideas}
115
 
116
+ def start_feed(tag, current_index, feed_items, is_loading):
117
  """
118
+ Start or update the feed based on the tag.
119
 
120
  Args:
121
  tag (str): The tag to generate content for.
122
+ current_index (int): The current item index.
123
+ feed_items (list): The current list of feed items.
124
+ is_loading (bool): Whether the feed is currently loading.
125
 
126
  Returns:
127
+ tuple: (current_tag, current_index, feed_items, html_content, is_loading)
128
  """
129
  if not tag.strip():
130
  tag = "trending"
131
+
132
+ # Set loading state to True
133
+ is_loading = True
134
+ yield tag, current_index, feed_items, generate_html([], False, 0), is_loading
135
+
136
+ # Generate new ideas for the tag
137
+ ideas = generate_ideas(tag)
138
+ # Generate the first item
139
+ item = generate_item(tag, ideas)
140
+ feed_items = [item] # Reset feed with the new item
141
+ current_index = 0
142
+
143
+ # Set loading state to False
144
+ is_loading = False
145
+ return tag, current_index, feed_items, generate_html(feed_items, False, current_index), is_loading
146
+
147
+ def load_next(tag, current_index, feed_items, is_loading):
148
+ """
149
+ Load the next item in the feed.
150
+
151
+ Args:
152
+ tag (str): The tag to generate content for.
153
+ current_index (int): The current item index.
154
+ feed_items (list): The current list of feed items.
155
+ is_loading (bool): Whether the feed is currently loading.
156
+
157
+ Returns:
158
+ tuple: (current_tag, current_index, feed_items, html_content, is_loading)
159
+ """
160
+ # Set loading state to True
161
+ is_loading = True
162
+ yield tag, current_index, feed_items, generate_html(feed_items, False, current_index), is_loading
163
 
164
+ # If there’s a next item, show it; otherwise, generate a new one
165
+ if current_index + 1 < len(feed_items):
166
+ current_index += 1
167
+ else:
168
+ # Use the ideas from the last item to generate a new one
169
+ ideas = feed_items[-1]['ideas'] if feed_items else generate_ideas(tag)
170
+ new_item = generate_item(tag, ideas)
171
+ feed_items.append(new_item)
172
+ current_index = len(feed_items) - 1
173
+
174
+ # Set loading state to False
175
+ is_loading = False
176
+ return tag, current_index, feed_items, generate_html(feed_items, False, current_index), is_loading
177
+
178
+ def load_previous(tag, current_index, feed_items, is_loading):
179
  """
180
+ Load the previous item in the feed.
181
 
182
  Args:
183
+ tag (str): The tag to generate content for.
184
+ current_index (int): The current item index.
185
  feed_items (list): The current list of feed items.
186
+ is_loading (bool): Whether the feed is currently loading.
187
 
188
  Returns:
189
+ tuple: (current_tag, current_index, feed_items, html_content, is_loading)
190
  """
191
+ if current_index > 0:
192
+ current_index -= 1
193
+ return tag, current_index, feed_items, generate_html(feed_items, False, current_index), is_loading
 
194
 
195
+ def generate_html(feed_items, scroll_to_latest=False, current_index=0):
196
  """
197
+ Generate an HTML string to display the current feed item with click navigation.
198
 
199
  Args:
200
  feed_items (list): List of dictionaries containing 'text' and 'image_base64'.
201
+ scroll_to_latest (bool): Whether to auto-scroll to the latest item (not used here).
202
+ current_index (int): The index of the item to display.
203
 
204
  Returns:
205
  str: HTML string representing the feed.
206
  """
207
+ if not feed_items or current_index >= len(feed_items):
208
+ return """
209
+ <div style="
210
+ display: flex;
211
+ flex-direction: column;
212
+ align-items: center;
213
+ justify-content: center;
214
+ max-width: 360px;
215
+ margin: 0 auto;
216
+ background-color: #000;
217
+ height: 640px;
218
+ border: 1px solid #333;
219
+ border-radius: 10px;
220
+ color: white;
221
+ font-family: Arial, sans-serif;
222
+ ">
223
+ <p>Select a tag to start your feed!</p>
224
+ </div>
225
+ """
226
+
227
+ item = feed_items[current_index]
228
  html_str = """
229
  <div id="feed-container" style="
230
  display: flex;
 
234
  margin: 0 auto;
235
  background-color: #000;
236
  height: 640px;
 
 
 
 
237
  border: 1px solid #333;
238
  border-radius: 10px;
239
+ position: relative;
240
  ">
241
+ <div class="feed-item" style="
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  width: 100%;
243
+ height: 100%;
244
  position: relative;
245
  display: flex;
246
  flex-direction: column;
247
  justify-content: flex-end;
248
  overflow: hidden;
249
+ cursor: pointer;
250
+ " onclick="handleClick(event)">
251
+ <img id="feed-image" src="data:image/png;base64,{image_base64}" style="
252
  width: 100%;
253
  height: 100%;
254
  object-fit: cover;
 
268
  font-weight: bold;
269
  text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
270
  ">
271
+ {text}
272
  </div>
273
  </div>
274
+ </div>
275
+ <script>
276
+ function handleClick(event) {{
277
+ const image = document.getElementById('feed-image');
278
+ const rect = image.getBoundingClientRect();
279
+ const clickX = event.clientX - rect.left;
280
+ const width = rect.width;
281
+ if (clickX > width * 0.75) {{
282
+ // Click on the right 25% to go to previous
283
+ document.getElementById('previous-button').click();
284
+ }} else {{
285
+ // Click anywhere else to go to next
286
+ document.getElementById('next-button').click();
287
+ }}
288
+ }}
289
+ </script>
290
+ <button id="next-button" style="display: none;" onclick="document.getElementById('next-button').click()"></button>
291
+ <button id="previous-button" style="display: none;" onclick="document.getElementById('previous-button').click()"></button>
292
+ """.format(image_base64=item['image_base64'], text=item['text'])
293
 
294
  return html_str
295
 
 
298
  css="""
299
  body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
300
  .gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
301
+ input, select { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
 
 
 
302
  .gr-form { background-color: #111; padding: 15px; border-radius: 10px; }
303
+ .gr-progress { background-color: #ff2d55; }
304
  """,
305
  title="TikTok-Style Infinite Feed"
306
  ) as demo:
307
+ # State variables
308
+ current_tag = gr.State(value="")
309
+ current_index = gr.State(value=0)
310
+ feed_items = gr.State(value=[])
311
+ is_loading = gr.State(value=False)
312
+
313
  # Input section
314
  with gr.Column(elem_classes="gr-form"):
315
  gr.Markdown("### Create Your TikTok Feed")
 
322
  tag_input = gr.Textbox(
323
  label="Or Enter a Custom Tag",
324
  value="food",
325
+ placeholder="e.g., sushi, adventure",
326
+ submit_btn=False # Disable default submit button
327
  )
328
+
329
+ # Progress bar
330
+ progress_bar = gr.Slider(
331
+ minimum=0,
332
+ maximum=1,
333
+ value=0,
334
+ label="Loading Feed...",
335
+ visible=False
336
+ )
337
 
338
  # Output display
339
  feed_html = gr.HTML()
340
 
 
 
 
 
341
  # Event handlers
342
  def set_tag(selected_tag):
343
+ """Update the tag input when a suggested tag is selected and start the feed."""
344
  return selected_tag
345
 
346
+ def update_progress(is_loading):
347
+ """Show or hide the progress bar based on loading state."""
348
+ return gr.update(visible=is_loading, value=0 if is_loading else 1)
349
+
350
+ # Handle dropdown selection
351
+ suggested_tags.change(
352
+ fn=set_tag,
353
+ inputs=suggested_tags,
354
+ outputs=tag_input
355
+ ).then(
356
+ fn=start_feed,
357
+ inputs=[tag_input, current_index, feed_items, is_loading],
358
+ outputs=[current_tag, current_index, feed_items, feed_html, is_loading]
359
+ ).then(
360
+ fn=update_progress,
361
+ inputs=is_loading,
362
+ outputs=progress_bar
363
+ )
364
+
365
+ # Handle Enter keypress in the custom tag input
366
+ tag_input.submit(
367
  fn=start_feed,
368
+ inputs=[tag_input, current_index, feed_items, is_loading],
369
+ outputs=[current_tag, current_index, feed_items, feed_html, is_loading]
370
+ ).then(
371
+ fn=update_progress,
372
+ inputs=is_loading,
373
+ outputs=progress_bar
374
  )
375
+
376
+ # Hidden buttons for navigation
377
+ next_button = gr.Button("Next", elem_id="next-button", visible=False)
378
+ previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
379
+
380
+ # Handle click to go to next item
381
+ next_button.click(
382
+ fn=load_next,
383
+ inputs=[current_tag, current_index, feed_items, is_loading],
384
+ outputs=[current_tag, current_index, feed_items, feed_html, is_loading]
385
+ ).then(
386
+ fn=update_progress,
387
+ inputs=is_loading,
388
+ outputs=progress_bar
389
+ )
390
+
391
+ # Handle click to go to previous item
392
+ previous_button.click(
393
+ fn=load_previous,
394
+ inputs=[current_tag, current_index, feed_items, is_loading],
395
+ outputs=[current_tag, current_index, feed_items, feed_html, is_loading]
396
  )
397
 
398
  # Launch the app with a public link