carolinacc commited on
Commit
96ce57f
ยท
verified ยท
1 Parent(s): b777cd2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +301 -301
app.py CHANGED
@@ -1,302 +1,302 @@
1
- import gradio as gr
2
- from typing import List, Optional, Tuple, Dict, Any
3
- import uuid
4
- import os
5
- import sys
6
- import traceback
7
-
8
- from agent import run_agent
9
-
10
-
11
-
12
- # ============================================================================
13
- # CONSTANTS
14
- # ============================================================================
15
-
16
- # Example images hosted online (replace with your own URLs)
17
- EXAMPLE_IMAGE_URLS = [
18
- "https://64.media.tumblr.com/456e9e6d8f42e677581f7d7994554600/03546756eb18cebb-2e/s400x600/7cd50d0a76327cf08cc75d17e540a11212b56a3b.jpg",
19
- "https://64.media.tumblr.com/97e808fda7863d31729da77de9f03285/03546756eb18cebb-2b/s400x600/7fc1a84a8d3f5922ca1f24fd6cc453d45ba88f7f.jpg",
20
- "https://64.media.tumblr.com/380d1592fa32f1e2290531879cfdd329/03546756eb18cebb-61/s400x600/e9d78c4467fa3a8dc6223667e236b922bb943775.jpg",
21
- ]
22
-
23
- # ============================================================================
24
- # UI HELPER FUNCTIONS
25
- # ============================================================================
26
-
27
- def get_session_id():
28
- """Generate a unique session ID for the user"""
29
- return str(uuid.uuid4())
30
-
31
- def format_books_html(books: List[dict]) -> str:
32
- """Format final books as HTML for display in a 3-column layout with larger covers"""
33
- html = "<div style='width: 100%;'>"
34
- html += "<h2 style='color: #667eea; margin-bottom: 20px;'>๐Ÿ“š Your Personalized Recommendations</h2>"
35
- html += "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: start;'>"
36
-
37
- for i, book in enumerate(books, 1):
38
- desc = book.get("description", "")
39
- html += f"""
40
- <div style='padding: 16px; background: white;
41
- border-radius: 12px; border-left: 4px solid #667eea; box-shadow: 0 2px 8px rgba(0,0,0,0.08);'>
42
- <div style='display: flex; gap: 16px; align-items: flex-start;'>
43
- <img src='{book.get("cover_url", "")}'
44
- style='width: 120px; min-width: 120px; height: 180px; object-fit: cover; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);'
45
- onerror='this.style.display="none"' />
46
- <div style='flex: 1; min-width: 0;'>
47
- <h3 style='margin: 0 0 8px 0; color: #667eea; font-size: 1.1em; line-height: 1.3;'>{i}. {book["title"]}</h3>
48
- <p style='margin: 0; color: #666; font-style: italic; font-size: 0.9em;'>by {book["author"]}</p>
49
- </div>
50
- </div>
51
- <p style='margin: 16px 0 0 0; color: #555; line-height: 1.6; font-size: 0.9em;'>{desc}</p>
52
- </div>
53
- """
54
- html += "</div></div>"
55
- return html
56
-
57
- def load_example_images():
58
- """Load example images from URLs"""
59
- return EXAMPLE_IMAGE_URLS
60
-
61
- # REMOVED: messages_to_chatbot_format - using agent's List[Dict] directly
62
-
63
- # ============================================================================
64
- # EVENT HANDLERS
65
- # ============================================================================
66
-
67
- def process_upload(images: List, session_id: str, progress=gr.Progress()):
68
- """Handle image upload and start the agent workflow"""
69
- if not images:
70
- # Return empty list for the Chatbot component
71
- yield [], "Please upload images.", "", None, gr.update(visible=True), ""
72
- return
73
-
74
- # Process image paths
75
- image_paths = []
76
- for img in images:
77
- if hasattr(img, 'name'): image_paths.append(img.name)
78
- # Added safety checks for common Gradio formats
79
- elif isinstance(img, dict) and 'path' in img: image_paths.append(img['path'])
80
- elif isinstance(img, str) and img.startswith('http'): image_paths.append(img) # URLs
81
- elif isinstance(img, str) and os.path.isfile(img): image_paths.append(img)
82
- elif isinstance(img, tuple): image_paths.append(img[0])
83
-
84
-
85
- if not image_paths:
86
- yield [], "Error processing images.", "", None, gr.update(visible=True), ""
87
- return
88
-
89
- try:
90
- # Show loading status
91
- yield [], "", "", None, gr.update(visible=False), "๐ŸŽจ Analyzing your vibe images..."
92
-
93
- # Run agent with session_id acting as the thread_id
94
- result = run_agent(images=image_paths, thread_id=session_id)
95
-
96
- # CRUCIAL FIX: Use the agent's List[Dict] messages directly
97
- chat_history = result["messages"]
98
- reasoning = "\n".join(result.get("reasoning", []))
99
-
100
- # Outputs: [chatbot, reasoning, recommendations, soundtrack, start_btn, status]
101
- yield chat_history, reasoning, "", None, gr.update(visible=False), "โœจ Vibe analysis complete!"
102
- except Exception as e:
103
- yield [], f"Error: {e}\n{traceback.format_exc()}", "", None, gr.update(visible=True), "โŒ Error occurred"
104
-
105
- def add_user_message(user_message: str, history: List[Dict[str, str]]):
106
- """
107
- Step 1 of Chat: Add user message to history in the new Chatbot format.
108
- """
109
- if not user_message.strip():
110
- return history, ""
111
-
112
- # Append the new message in the List[Dict] format
113
- new_message = {"role": "user", "content": user_message}
114
- return history + [new_message], ""
115
-
116
- def generate_bot_response(history: List[Dict[str, str]], session_id: str):
117
- """
118
- Step 2 of Chat: Call agent and update history with response.
119
- Uses yield to show loading status.
120
- """
121
- print(f"[DEBUG] generate_bot_response called with session_id={session_id}")
122
- print(f"[DEBUG] history has {len(history) if history else 0} messages")
123
-
124
- # Get the last user message from the List[Dict] history
125
- if not history or history[-1]["role"] != "user":
126
- # Should not happen in normal flow, but safety check
127
- print("[DEBUG] No user message found in history")
128
- yield history, "No message to process", "", None, ""
129
- return
130
-
131
-
132
- # The user message is already in history, we only need the content to resume the agent
133
- user_content = history[-1]["content"]
134
- # Gradio 6 may return content as a list of dicts with 'text' key
135
- if isinstance(user_content, list):
136
- user_message = " ".join(item.get("text", str(item)) for item in user_content if isinstance(item, dict))
137
- else:
138
- user_message = str(user_content)
139
- print(f"[DEBUG] Resuming agent with user_message: {user_message[:50]}...")
140
-
141
- try:
142
- # Show loading status
143
- yield history, "", "", None, "๐Ÿ”„ Processing your response..."
144
-
145
- # Resume agent execution using the session_id
146
- result = run_agent(images=[], user_message=user_message, thread_id=session_id)
147
-
148
- print(f"[DEBUG] run_agent returned: {type(result)}")
149
- if result:
150
- print(f"[DEBUG] result keys: {result.keys() if isinstance(result, dict) else 'N/A'}")
151
-
152
- if result is None:
153
- print("[DEBUG] result is None - agent may not have resumed properly")
154
- history.append({"role": "assistant", "content": "Error: Agent did not return a response."})
155
- yield history, "Agent returned None", "", None, "โŒ Agent error"
156
- return
157
-
158
- # CRUCIAL FIX: The agent returns the full updated history in the List[Dict] format
159
- updated_history = result["messages"]
160
- reasoning = "\n".join(result.get("reasoning", []))
161
-
162
- print(f"[DEBUG] updated_history has {len(updated_history)} messages")
163
-
164
-
165
- # Check for final results
166
- books_html = ""
167
- if result.get("final_books"):
168
- books_html = format_books_html(result["final_books"])
169
-
170
- soundtrack = result.get("soundtrack_url", "") or None
171
-
172
- # Determine status based on what happened
173
- if result.get("final_books"):
174
- status = "โœ… Recommendations ready!"
175
- elif "retrieved_books" in result and result["retrieved_books"]:
176
- status = "๐Ÿ“š Books retrieved, refining..."
177
- else:
178
- status = "๐Ÿ’ญ Awaiting your input..."
179
-
180
- # Outputs: [chatbot, reasoning, recommendations, soundtrack, status]
181
- yield updated_history, reasoning, books_html, soundtrack, status
182
-
183
- except Exception as e:
184
- # Append error to chat by updating the last user message's response
185
- error_msg = f"Agent Error: {str(e)}"
186
- print(f"[DEBUG] Exception in generate_bot_response: {e}")
187
- traceback.print_exc()
188
- # Append assistant error message
189
- history.append({"role": "assistant", "content": error_msg})
190
- yield history, f"Error trace: {traceback.format_exc()}", "", None, "โŒ Error occurred"
191
-
192
- def reset_app():
193
- """Reset the session"""
194
- new_id = get_session_id()
195
- # Returns: [session_id, chatbot, reasoning, books, soundtrack, input, images, start_btn, status]
196
- return new_id, [], "", "", None, "", None, gr.update(visible=True), "Ready to analyze your vibe!"
197
-
198
- # ============================================================================
199
- # LAYOUT
200
- # ============================================================================
201
-
202
- with gr.Blocks() as demo:
203
- # State management for multi-user support
204
- session_id = gr.State(get_session_id())
205
-
206
- gr.Markdown("# ๐Ÿ“š The Vibe Reader", elem_id='main-title')
207
- gr.Markdown("""
208
- **How it works:**
209
- - ๐ŸŽจ **Vision AI** extracts mood, themes, and aesthetic keywords from your images
210
- - ๐Ÿ“š **Semantic search** queries a vector DB of 50k+ book recs from r/BooksThatFeelLikeThis
211
- - ๐Ÿ’ฌ **Conversational refinement** asks targeted questions to narrow down preferences
212
- - ๐Ÿ“– **Google Books MCP** enriches results with covers, descriptions, and metadata
213
- - ๐ŸŽต **ElevenLabs AI** generates a custom soundtrack that matches your reading vibe
214
- """, elem_id='subtitle')
215
-
216
- with gr.Row():
217
- # Left: Inputs
218
- with gr.Column(scale=1):
219
- gr.Markdown("### 1. Upload Your Vibe")
220
- image_input = gr.Gallery(label="Visual Inspiration", columns=3, height="300px")
221
- load_examples_btn = gr.Button("๐Ÿ“ท Load Example Images (Credits: @thegorgonist)", variant="secondary", size="md")
222
- start_btn = gr.Button("๐Ÿ”ฎ Analyze Vibe", variant="primary", size="lg")
223
- status_display = gr.Textbox(label="Status", value="Ready to analyze your vibe!", interactive=False, elem_id="status-display")
224
- reset_btn = gr.Button("๐Ÿ”„ Start Over", variant="secondary")
225
-
226
- # Right: Chat
227
- with gr.Column(scale=1):
228
- gr.Markdown("### 2. Refine & Discover")
229
- # Chatbot now uses the new List[Dict] format
230
- chatbot = gr.Chatbot(height=500, label="Agent Conversation")
231
-
232
- with gr.Row():
233
- msg_input = gr.Textbox(
234
- show_label=False,
235
- placeholder="Type your response here...",
236
- scale=4,
237
- container=False
238
- )
239
- submit_btn = gr.Button("Send", variant="primary", scale=1)
240
-
241
- # Outputs - Recommendations first, then reasoning
242
- recommendations_output = gr.HTML(label="Recommendations")
243
- soundtrack_player = gr.Audio(label="Vibe Soundtrack", type="filepath", interactive=False)
244
-
245
- with gr.Accordion("๐Ÿ” Internal Reasoning", open=True):
246
- reasoning_display = gr.Textbox(label="Agent Thoughts", lines=10, interactive=False)
247
-
248
- # ============================================================================
249
- # INTERACTION LOGIC
250
- # ============================================================================
251
-
252
- # 0. Load Example Images
253
- load_examples_btn.click(
254
- fn=load_example_images,
255
- inputs=[],
256
- outputs=[image_input]
257
- )
258
-
259
- # 1. Start Analysis
260
- start_btn.click(
261
- fn=process_upload,
262
- inputs=[image_input, session_id],
263
- outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, start_btn, status_display]
264
- )
265
-
266
- # 2. Chat Interaction (User enters text -> History updates -> Bot responds)
267
-
268
- # User adds message to history optimistically and clears input
269
- user_event = msg_input.submit(
270
- fn=add_user_message,
271
- inputs=[msg_input, chatbot],
272
- outputs=[chatbot, msg_input],
273
- queue=False
274
- )
275
-
276
- # Bot generates response and updates the full history
277
- user_event.then(
278
- fn=generate_bot_response,
279
- inputs=[chatbot, session_id],
280
- outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, status_display]
281
- )
282
-
283
- submit_btn.click(
284
- fn=add_user_message,
285
- inputs=[msg_input, chatbot],
286
- outputs=[chatbot, msg_input],
287
- queue=False
288
- ).then(
289
- fn=generate_bot_response,
290
- inputs=[chatbot, session_id],
291
- outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, status_display]
292
- )
293
-
294
- # 3. Reset
295
- reset_btn.click(
296
- fn=reset_app,
297
- inputs=[],
298
- outputs=[session_id, chatbot, reasoning_display, recommendations_output, soundtrack_player, msg_input, image_input, start_btn, status_display]
299
- )
300
-
301
- if __name__ == "__main__":
302
  demo.queue().launch(theme=gr.themes.Monochrome(), css_paths='app/custom.css')
 
1
+ import gradio as gr
2
+ from typing import List, Optional, Tuple, Dict, Any
3
+ import uuid
4
+ import os
5
+ import sys
6
+ import traceback
7
+
8
+ from app.agent import run_agent
9
+
10
+
11
+
12
+ # ============================================================================
13
+ # CONSTANTS
14
+ # ============================================================================
15
+
16
+ # Example images hosted online (replace with your own URLs)
17
+ EXAMPLE_IMAGE_URLS = [
18
+ "https://64.media.tumblr.com/456e9e6d8f42e677581f7d7994554600/03546756eb18cebb-2e/s400x600/7cd50d0a76327cf08cc75d17e540a11212b56a3b.jpg",
19
+ "https://64.media.tumblr.com/97e808fda7863d31729da77de9f03285/03546756eb18cebb-2b/s400x600/7fc1a84a8d3f5922ca1f24fd6cc453d45ba88f7f.jpg",
20
+ "https://64.media.tumblr.com/380d1592fa32f1e2290531879cfdd329/03546756eb18cebb-61/s400x600/e9d78c4467fa3a8dc6223667e236b922bb943775.jpg",
21
+ ]
22
+
23
+ # ============================================================================
24
+ # UI HELPER FUNCTIONS
25
+ # ============================================================================
26
+
27
+ def get_session_id():
28
+ """Generate a unique session ID for the user"""
29
+ return str(uuid.uuid4())
30
+
31
+ def format_books_html(books: List[dict]) -> str:
32
+ """Format final books as HTML for display in a 3-column layout with larger covers"""
33
+ html = "<div style='width: 100%;'>"
34
+ html += "<h2 style='color: #667eea; margin-bottom: 20px;'>๐Ÿ“š Your Personalized Recommendations</h2>"
35
+ html += "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: start;'>"
36
+
37
+ for i, book in enumerate(books, 1):
38
+ desc = book.get("description", "")
39
+ html += f"""
40
+ <div style='padding: 16px; background: white;
41
+ border-radius: 12px; border-left: 4px solid #667eea; box-shadow: 0 2px 8px rgba(0,0,0,0.08);'>
42
+ <div style='display: flex; gap: 16px; align-items: flex-start;'>
43
+ <img src='{book.get("cover_url", "")}'
44
+ style='width: 120px; min-width: 120px; height: 180px; object-fit: cover; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);'
45
+ onerror='this.style.display="none"' />
46
+ <div style='flex: 1; min-width: 0;'>
47
+ <h3 style='margin: 0 0 8px 0; color: #667eea; font-size: 1.1em; line-height: 1.3;'>{i}. {book["title"]}</h3>
48
+ <p style='margin: 0; color: #666; font-style: italic; font-size: 0.9em;'>by {book["author"]}</p>
49
+ </div>
50
+ </div>
51
+ <p style='margin: 16px 0 0 0; color: #555; line-height: 1.6; font-size: 0.9em;'>{desc}</p>
52
+ </div>
53
+ """
54
+ html += "</div></div>"
55
+ return html
56
+
57
+ def load_example_images():
58
+ """Load example images from URLs"""
59
+ return EXAMPLE_IMAGE_URLS
60
+
61
+ # REMOVED: messages_to_chatbot_format - using agent's List[Dict] directly
62
+
63
+ # ============================================================================
64
+ # EVENT HANDLERS
65
+ # ============================================================================
66
+
67
+ def process_upload(images: List, session_id: str, progress=gr.Progress()):
68
+ """Handle image upload and start the agent workflow"""
69
+ if not images:
70
+ # Return empty list for the Chatbot component
71
+ yield [], "Please upload images.", "", None, gr.update(visible=True), ""
72
+ return
73
+
74
+ # Process image paths
75
+ image_paths = []
76
+ for img in images:
77
+ if hasattr(img, 'name'): image_paths.append(img.name)
78
+ # Added safety checks for common Gradio formats
79
+ elif isinstance(img, dict) and 'path' in img: image_paths.append(img['path'])
80
+ elif isinstance(img, str) and img.startswith('http'): image_paths.append(img) # URLs
81
+ elif isinstance(img, str) and os.path.isfile(img): image_paths.append(img)
82
+ elif isinstance(img, tuple): image_paths.append(img[0])
83
+
84
+
85
+ if not image_paths:
86
+ yield [], "Error processing images.", "", None, gr.update(visible=True), ""
87
+ return
88
+
89
+ try:
90
+ # Show loading status
91
+ yield [], "", "", None, gr.update(visible=False), "๐ŸŽจ Analyzing your vibe images..."
92
+
93
+ # Run agent with session_id acting as the thread_id
94
+ result = run_agent(images=image_paths, thread_id=session_id)
95
+
96
+ # CRUCIAL FIX: Use the agent's List[Dict] messages directly
97
+ chat_history = result["messages"]
98
+ reasoning = "\n".join(result.get("reasoning", []))
99
+
100
+ # Outputs: [chatbot, reasoning, recommendations, soundtrack, start_btn, status]
101
+ yield chat_history, reasoning, "", None, gr.update(visible=False), "โœจ Vibe analysis complete!"
102
+ except Exception as e:
103
+ yield [], f"Error: {e}\n{traceback.format_exc()}", "", None, gr.update(visible=True), "โŒ Error occurred"
104
+
105
+ def add_user_message(user_message: str, history: List[Dict[str, str]]):
106
+ """
107
+ Step 1 of Chat: Add user message to history in the new Chatbot format.
108
+ """
109
+ if not user_message.strip():
110
+ return history, ""
111
+
112
+ # Append the new message in the List[Dict] format
113
+ new_message = {"role": "user", "content": user_message}
114
+ return history + [new_message], ""
115
+
116
+ def generate_bot_response(history: List[Dict[str, str]], session_id: str):
117
+ """
118
+ Step 2 of Chat: Call agent and update history with response.
119
+ Uses yield to show loading status.
120
+ """
121
+ print(f"[DEBUG] generate_bot_response called with session_id={session_id}")
122
+ print(f"[DEBUG] history has {len(history) if history else 0} messages")
123
+
124
+ # Get the last user message from the List[Dict] history
125
+ if not history or history[-1]["role"] != "user":
126
+ # Should not happen in normal flow, but safety check
127
+ print("[DEBUG] No user message found in history")
128
+ yield history, "No message to process", "", None, ""
129
+ return
130
+
131
+
132
+ # The user message is already in history, we only need the content to resume the agent
133
+ user_content = history[-1]["content"]
134
+ # Gradio 6 may return content as a list of dicts with 'text' key
135
+ if isinstance(user_content, list):
136
+ user_message = " ".join(item.get("text", str(item)) for item in user_content if isinstance(item, dict))
137
+ else:
138
+ user_message = str(user_content)
139
+ print(f"[DEBUG] Resuming agent with user_message: {user_message[:50]}...")
140
+
141
+ try:
142
+ # Show loading status
143
+ yield history, "", "", None, "๐Ÿ”„ Processing your response..."
144
+
145
+ # Resume agent execution using the session_id
146
+ result = run_agent(images=[], user_message=user_message, thread_id=session_id)
147
+
148
+ print(f"[DEBUG] run_agent returned: {type(result)}")
149
+ if result:
150
+ print(f"[DEBUG] result keys: {result.keys() if isinstance(result, dict) else 'N/A'}")
151
+
152
+ if result is None:
153
+ print("[DEBUG] result is None - agent may not have resumed properly")
154
+ history.append({"role": "assistant", "content": "Error: Agent did not return a response."})
155
+ yield history, "Agent returned None", "", None, "โŒ Agent error"
156
+ return
157
+
158
+ # CRUCIAL FIX: The agent returns the full updated history in the List[Dict] format
159
+ updated_history = result["messages"]
160
+ reasoning = "\n".join(result.get("reasoning", []))
161
+
162
+ print(f"[DEBUG] updated_history has {len(updated_history)} messages")
163
+
164
+
165
+ # Check for final results
166
+ books_html = ""
167
+ if result.get("final_books"):
168
+ books_html = format_books_html(result["final_books"])
169
+
170
+ soundtrack = result.get("soundtrack_url", "") or None
171
+
172
+ # Determine status based on what happened
173
+ if result.get("final_books"):
174
+ status = "โœ… Recommendations ready!"
175
+ elif "retrieved_books" in result and result["retrieved_books"]:
176
+ status = "๐Ÿ“š Books retrieved, refining..."
177
+ else:
178
+ status = "๐Ÿ’ญ Awaiting your input..."
179
+
180
+ # Outputs: [chatbot, reasoning, recommendations, soundtrack, status]
181
+ yield updated_history, reasoning, books_html, soundtrack, status
182
+
183
+ except Exception as e:
184
+ # Append error to chat by updating the last user message's response
185
+ error_msg = f"Agent Error: {str(e)}"
186
+ print(f"[DEBUG] Exception in generate_bot_response: {e}")
187
+ traceback.print_exc()
188
+ # Append assistant error message
189
+ history.append({"role": "assistant", "content": error_msg})
190
+ yield history, f"Error trace: {traceback.format_exc()}", "", None, "โŒ Error occurred"
191
+
192
+ def reset_app():
193
+ """Reset the session"""
194
+ new_id = get_session_id()
195
+ # Returns: [session_id, chatbot, reasoning, books, soundtrack, input, images, start_btn, status]
196
+ return new_id, [], "", "", None, "", None, gr.update(visible=True), "Ready to analyze your vibe!"
197
+
198
+ # ============================================================================
199
+ # LAYOUT
200
+ # ============================================================================
201
+
202
+ with gr.Blocks() as demo:
203
+ # State management for multi-user support
204
+ session_id = gr.State(get_session_id())
205
+
206
+ gr.Markdown("# ๐Ÿ“š The Vibe Reader", elem_id='main-title')
207
+ gr.Markdown("""
208
+ **How it works:**
209
+ - ๐ŸŽจ **Vision AI** extracts mood, themes, and aesthetic keywords from your images
210
+ - ๐Ÿ“š **Semantic search** queries a vector DB of 50k+ book recs from r/BooksThatFeelLikeThis
211
+ - ๐Ÿ’ฌ **Conversational refinement** asks targeted questions to narrow down preferences
212
+ - ๐Ÿ“– **Google Books MCP** enriches results with covers, descriptions, and metadata
213
+ - ๐ŸŽต **ElevenLabs AI** generates a custom soundtrack that matches your reading vibe
214
+ """, elem_id='subtitle')
215
+
216
+ with gr.Row():
217
+ # Left: Inputs
218
+ with gr.Column(scale=1):
219
+ gr.Markdown("### 1. Upload Your Vibe")
220
+ image_input = gr.Gallery(label="Visual Inspiration", columns=3, height="300px")
221
+ load_examples_btn = gr.Button("๐Ÿ“ท Load Example Images (Credits: @thegorgonist)", variant="secondary", size="md")
222
+ start_btn = gr.Button("๐Ÿ”ฎ Analyze Vibe", variant="primary", size="lg")
223
+ status_display = gr.Textbox(label="Status", value="Ready to analyze your vibe!", interactive=False, elem_id="status-display")
224
+ reset_btn = gr.Button("๐Ÿ”„ Start Over", variant="secondary")
225
+
226
+ # Right: Chat
227
+ with gr.Column(scale=1):
228
+ gr.Markdown("### 2. Refine & Discover")
229
+ # Chatbot now uses the new List[Dict] format
230
+ chatbot = gr.Chatbot(height=500, label="Agent Conversation")
231
+
232
+ with gr.Row():
233
+ msg_input = gr.Textbox(
234
+ show_label=False,
235
+ placeholder="Type your response here...",
236
+ scale=4,
237
+ container=False
238
+ )
239
+ submit_btn = gr.Button("Send", variant="primary", scale=1)
240
+
241
+ # Outputs - Recommendations first, then reasoning
242
+ recommendations_output = gr.HTML(label="Recommendations")
243
+ soundtrack_player = gr.Audio(label="Vibe Soundtrack", type="filepath", interactive=False)
244
+
245
+ with gr.Accordion("๐Ÿ” Internal Reasoning", open=True):
246
+ reasoning_display = gr.Textbox(label="Agent Thoughts", lines=10, interactive=False)
247
+
248
+ # ============================================================================
249
+ # INTERACTION LOGIC
250
+ # ============================================================================
251
+
252
+ # 0. Load Example Images
253
+ load_examples_btn.click(
254
+ fn=load_example_images,
255
+ inputs=[],
256
+ outputs=[image_input]
257
+ )
258
+
259
+ # 1. Start Analysis
260
+ start_btn.click(
261
+ fn=process_upload,
262
+ inputs=[image_input, session_id],
263
+ outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, start_btn, status_display]
264
+ )
265
+
266
+ # 2. Chat Interaction (User enters text -> History updates -> Bot responds)
267
+
268
+ # User adds message to history optimistically and clears input
269
+ user_event = msg_input.submit(
270
+ fn=add_user_message,
271
+ inputs=[msg_input, chatbot],
272
+ outputs=[chatbot, msg_input],
273
+ queue=False
274
+ )
275
+
276
+ # Bot generates response and updates the full history
277
+ user_event.then(
278
+ fn=generate_bot_response,
279
+ inputs=[chatbot, session_id],
280
+ outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, status_display]
281
+ )
282
+
283
+ submit_btn.click(
284
+ fn=add_user_message,
285
+ inputs=[msg_input, chatbot],
286
+ outputs=[chatbot, msg_input],
287
+ queue=False
288
+ ).then(
289
+ fn=generate_bot_response,
290
+ inputs=[chatbot, session_id],
291
+ outputs=[chatbot, reasoning_display, recommendations_output, soundtrack_player, status_display]
292
+ )
293
+
294
+ # 3. Reset
295
+ reset_btn.click(
296
+ fn=reset_app,
297
+ inputs=[],
298
+ outputs=[session_id, chatbot, reasoning_display, recommendations_output, soundtrack_player, msg_input, image_input, start_btn, status_display]
299
+ )
300
+
301
+ if __name__ == "__main__":
302
  demo.queue().launch(theme=gr.themes.Monochrome(), css_paths='app/custom.css')