chenemii commited on
Commit
cc3d7d0
·
1 Parent(s): be88a1a

Implement 5-step UI flow with floating sidebar navigation

Browse files

- Create clean step-based page flow (Upload → Analysis → Results → Improvements/Chatbot)
- Add floating sidebar with navigation buttons (Results, Improvements, Chatbot, Start Over)
- Remove progress bar for cleaner minimal design
- Implement Par-ity branding with logo and zigzag styling
- Add mobile-responsive design with adaptive layouts
- Enhance UX with smooth step transitions and state management

Files changed (3) hide show
  1. app/1.png +2 -0
  2. app/gradio_app.py +492 -0
  3. app/styles.css +122 -0
app/1.png ADDED
app/gradio_app.py ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio web UI for Golf Swing Analysis with Step-based Flow
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import gradio as gr
9
+ from dotenv import load_dotenv
10
+ import base64
11
+ from pathlib import Path
12
+ import shutil
13
+ import cv2
14
+ from PIL import Image
15
+ from datetime import datetime
16
+
17
+ # Load environment variables
18
+ load_dotenv()
19
+
20
+ # Add the app directory to the path
21
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
22
+
23
+ # Import analysis modules
24
+ from utils.video_downloader import download_youtube_video, download_pro_reference, cleanup_video_file, cleanup_downloads_directory
25
+ from utils.video_processor import process_video
26
+ from models.pose_estimator import analyze_pose
27
+ from models.swing_analyzer import segment_swing, analyze_trajectory
28
+ from models.llm_analyzer import generate_swing_analysis, create_llm_prompt, prepare_data_for_llm, check_llm_services, parse_and_format_analysis
29
+
30
+ # Import RAG functionality
31
+ try:
32
+ from golf_swing_rag import GolfSwingRAG
33
+ RAG_AVAILABLE = True
34
+ except ImportError:
35
+ RAG_AVAILABLE = False
36
+
37
+ def validate_youtube_url(url):
38
+ """Validate if the URL is a YouTube URL"""
39
+ return "youtube.com" in url or "youtu.be" in url
40
+
41
+ def start_analysis(input_text, video_file):
42
+ """Start video analysis process"""
43
+ try:
44
+ video_path = None
45
+
46
+ # Handle video input
47
+ if video_file is not None:
48
+ # Save uploaded file
49
+ os.makedirs("downloads", exist_ok=True)
50
+ video_path = os.path.join("downloads", f"uploaded_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
51
+ shutil.copy(video_file.name, video_path)
52
+ elif input_text and validate_youtube_url(input_text):
53
+ # Download from YouTube
54
+ video_path = download_youtube_video(input_text)
55
+ else:
56
+ return ("### Step 1: Upload Your Video",
57
+ "Please provide either a valid YouTube URL or upload a video file.",
58
+ 1, None)
59
+
60
+ if not video_path or not os.path.exists(video_path):
61
+ return ("### Step 1: Upload Your Video",
62
+ "Failed to process video. Please try again.",
63
+ 1, None)
64
+
65
+ # Move to analysis step
66
+ return ("### Step 2: Analyzing Video and Pose",
67
+ "🔄 **Processing your swing video...**\n\nPlease wait while we analyze your golf swing.",
68
+ 2, {'video_path': video_path})
69
+
70
+ except Exception as e:
71
+ error_message = f"Error starting analysis: {str(e)}"
72
+ return ("### Step 1: Upload Your Video",
73
+ error_message, 1, None)
74
+
75
+ def process_analysis(analysis_state):
76
+ """Process the actual video analysis"""
77
+ if not analysis_state or 'video_path' not in analysis_state:
78
+ return ("### Step 1: Upload Your Video",
79
+ "No video to analyze. Please upload a video first.",
80
+ 1, None)
81
+
82
+ try:
83
+ video_path = analysis_state['video_path']
84
+
85
+ # Process video analysis
86
+ frames, detections = process_video(video_path, sample_rate=1)
87
+ pose_data = analyze_pose(frames)
88
+ swing_phases = segment_swing(pose_data, detections, sample_rate=1)
89
+ trajectory_data = analyze_trajectory(frames, detections, swing_phases, sample_rate=1)
90
+ analysis_data = prepare_data_for_llm(pose_data, swing_phases, trajectory_data)
91
+
92
+ # Store complete results
93
+ results = {
94
+ 'video_path': video_path,
95
+ 'pose_data': pose_data,
96
+ 'swing_phases': swing_phases,
97
+ 'trajectory_data': trajectory_data,
98
+ 'analysis_data': analysis_data
99
+ }
100
+
101
+ # Generate results summary
102
+ results_content = f"""
103
+ ## 🏌️ Swing Analysis Results
104
+
105
+ **Video:** {os.path.basename(video_path)}
106
+
107
+ ### 📊 **Analysis Summary**
108
+ - **Swing Phases Detected:** {len(swing_phases) if swing_phases else 0}
109
+ - **Pose Data Points:** {len(pose_data) if pose_data else 0}
110
+ - **Trajectory Analysis:** {'✅ Complete' if trajectory_data else '❌ Failed'}
111
+
112
+ ### 🎯 **Key Findings**
113
+ - Swing timing and rhythm patterns identified
114
+ - Pose alignment and form analyzed
115
+ - Ball trajectory and impact mechanics evaluated
116
+
117
+ **Ready to dive deeper?** Choose how you'd like to explore your swing analysis:
118
+ """
119
+
120
+ return ("### Step 3: See Your Results",
121
+ results_content, 3, results)
122
+
123
+ except Exception as e:
124
+ error_message = f"Error during analysis: {str(e)}"
125
+ return ("### Step 2: Analyzing Video and Pose",
126
+ f"❌ **Analysis Failed**\n\n{error_message}\n\nPlease try again.",
127
+ 2, analysis_state)
128
+
129
+
130
+
131
+ def go_to_improvements(analysis_results):
132
+ """Navigate to improvements step"""
133
+ return ("### Step 4: AI-Powered Improvements",
134
+ "🎯 **Generating personalized swing improvements...**",
135
+ 4, analysis_results)
136
+
137
+ def go_to_chatbot(analysis_results):
138
+ """Navigate to chatbot step"""
139
+ return ("### Step 5: Ask the Golf Expert",
140
+ "💬 **Ready to answer your swing questions!**",
141
+ 5, analysis_results)
142
+
143
+ def go_back_to_results(analysis_results):
144
+ """Go back to results step"""
145
+ if not analysis_results:
146
+ return ("### Step 1: Upload Your Video",
147
+ "No analysis data available. Please start over.",
148
+ 1, None)
149
+
150
+ results_content = f"""
151
+ ## 🏌️ Swing Analysis Results
152
+
153
+ **Video:** {os.path.basename(analysis_results.get('video_path', 'Unknown'))}
154
+
155
+ ### 📊 **Analysis Summary**
156
+ - **Swing Phases Detected:** {len(analysis_results.get('swing_phases', [])) if analysis_results.get('swing_phases') else 0}
157
+ - **Pose Data Points:** {len(analysis_results.get('pose_data', [])) if analysis_results.get('pose_data') else 0}
158
+ - **Trajectory Analysis:** {'✅ Complete' if analysis_results.get('trajectory_data') else '❌ Failed'}
159
+
160
+ ### 🎯 **Key Findings**
161
+ - Swing timing and rhythm patterns identified
162
+ - Pose alignment and form analyzed
163
+ - Ball trajectory and impact mechanics evaluated
164
+
165
+ **Ready to dive deeper?** Choose how you'd like to explore your swing analysis:
166
+ """
167
+
168
+ return ("### Step 3: See Your Results",
169
+ results_content, 3, analysis_results)
170
+
171
+ def start_over():
172
+ """Reset to beginning"""
173
+ return ("### Step 1: Upload Your Video",
174
+ "**Choose your input method:**",
175
+ 1, None)
176
+
177
+ def handle_next_step(current_step, youtube_input, video_upload, analysis_results):
178
+ """Handle moving to next step"""
179
+ if current_step == 1:
180
+ # Start analysis
181
+ header, content, step, results = start_analysis(youtube_input, video_upload)
182
+ return (header, content, step, results,
183
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=True),
184
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),
185
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
186
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
187
+ elif current_step == 2:
188
+ # Process analysis
189
+ header, content, step, results = process_analysis(analysis_results)
190
+ if step == 3: # Analysis complete
191
+ return (header, content, step, results,
192
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=True),
193
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
194
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
195
+ gr.update(visible=True), gr.update(visible=True), gr.update(visible=True))
196
+ else:
197
+ return (header, content, step, results,
198
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=True),
199
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),
200
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
201
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
202
+ else:
203
+ # Should not happen with current flow
204
+ return (gr.update(), gr.update(), current_step, analysis_results,
205
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
206
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update())
207
+
208
+ def handle_back_step(current_step, analysis_results):
209
+ """Handle going back one step"""
210
+ header, content, step, results = go_back_step(current_step, analysis_results)
211
+
212
+ if step == 1:
213
+ return (header, content, step,
214
+ gr.update(value="🏌️ Start Analysis"), gr.update(visible=False), gr.update(visible=False),
215
+ gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
216
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
217
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
218
+ elif step == 3:
219
+ return (header, content, step,
220
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=True),
221
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
222
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
223
+ gr.update(visible=True), gr.update(visible=True), gr.update(visible=True))
224
+ else:
225
+ return (header, content, step,
226
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
227
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update())
228
+
229
+ def handle_go_to_step(target_step, analysis_results):
230
+ """Handle navigation to specific step"""
231
+ if target_step == 3:
232
+ header, content, step, results = go_back_to_results(analysis_results)
233
+ return (header, content, step,
234
+ gr.update(visible=False), gr.update(visible=True),
235
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
236
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
237
+ gr.update(visible=True), gr.update(visible=True), gr.update(visible=True))
238
+ elif target_step == 4:
239
+ header, content, step, results = go_to_improvements(analysis_results)
240
+ improvements_content = generate_improvements(analysis_results)
241
+ return (header, content, step,
242
+ gr.update(visible=False), gr.update(visible=True),
243
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
244
+ gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
245
+ gr.update(value=improvements_content), gr.update(visible=True), gr.update(visible=False), gr.update(visible=True))
246
+ elif target_step == 5:
247
+ header, content, step, results = go_to_chatbot(analysis_results)
248
+ return (header, content, step,
249
+ gr.update(visible=False), gr.update(visible=True),
250
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
251
+ gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),
252
+ gr.update(visible=True), gr.update(visible=False), gr.update(visible=True))
253
+ else:
254
+ return (gr.update(), gr.update(), target_step,
255
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
256
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update())
257
+
258
+ def handle_start_over():
259
+ """Handle starting over"""
260
+ header, content, step, results = start_over()
261
+ return (header, content, step, results,
262
+ gr.update(value="🏌️ Start Analysis"), gr.update(visible=False), gr.update(visible=False),
263
+ gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
264
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
265
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
266
+
267
+ def generate_improvements(analysis_results):
268
+ """Generate AI-powered swing improvements"""
269
+ if not analysis_results:
270
+ return "No analysis data available. Please analyze a video first."
271
+
272
+ try:
273
+ # Generate detailed analysis with recommendations
274
+ analysis = generate_swing_analysis(
275
+ analysis_results['pose_data'],
276
+ analysis_results['swing_phases'],
277
+ analysis_results['trajectory_data']
278
+ )
279
+
280
+ return f"""
281
+ ## 🎯 Personalized Swing Improvements
282
+
283
+ {analysis}
284
+
285
+ ---
286
+ *Analysis generated using AI-powered swing analysis technology*
287
+ """
288
+
289
+ except Exception as e:
290
+ return f"Error generating improvements: {str(e)}"
291
+
292
+ def get_chatbot_response(question, analysis_results, chat_history):
293
+ """Get response from golf swing chatbot"""
294
+ if not RAG_AVAILABLE:
295
+ return chat_history + [("System", "Chatbot is not available due to missing dependencies.")], ""
296
+
297
+ try:
298
+ # Initialize RAG system if needed
299
+ if not hasattr(get_chatbot_response, 'rag_system'):
300
+ get_chatbot_response.rag_system = GolfSwingRAG()
301
+ get_chatbot_response.rag_system.load_and_process_data()
302
+ get_chatbot_response.rag_system.create_embeddings()
303
+
304
+ # Search for relevant information
305
+ relevant_chunks = get_chatbot_response.rag_system.search_similar_chunks(question, top_k=3)
306
+
307
+ # Generate response (simplified version)
308
+ if relevant_chunks:
309
+ best_chunk = relevant_chunks[0]
310
+ response = f"Based on {best_chunk['metadata']['title']}: {best_chunk['chunk'][:300]}..."
311
+ else:
312
+ response = "I couldn't find specific information about that topic. Could you try rephrasing your question?"
313
+
314
+ # Add to chat history
315
+ chat_history.append((question, response))
316
+
317
+ return chat_history, ""
318
+
319
+ except Exception as e:
320
+ chat_history.append((question, f"Error: {str(e)}"))
321
+ return chat_history, ""
322
+
323
+ def go_back_step(current_step, analysis_results):
324
+ """Go back one step"""
325
+ if current_step <= 1:
326
+ return start_over()
327
+ elif current_step == 2:
328
+ return start_over()
329
+ elif current_step >= 3:
330
+ return go_back_to_results(analysis_results)
331
+ else:
332
+ return start_over()
333
+
334
+ # Create the Gradio interface
335
+ def create_interface():
336
+ with gr.Blocks(css="styles.css", title="Par-ity Project: Golf Swing Analysis") as demo:
337
+ # States
338
+ current_step = gr.State(1)
339
+ analysis_results = gr.State(None)
340
+
341
+ with gr.Row():
342
+ # Floating sidebar (initially hidden)
343
+ with gr.Column(scale=1, visible=False, elem_id="sidebar") as sidebar:
344
+ gr.Markdown("### Navigation", elem_classes="sidebar-header")
345
+ results_btn = gr.Button("📊 Results", variant="secondary", size="sm", visible=False)
346
+ improvements_btn = gr.Button("🎯 Improvements", variant="secondary", size="sm", visible=False)
347
+ chatbot_btn = gr.Button("💬 Chatbot", variant="secondary", size="sm", visible=False)
348
+ start_over_btn = gr.Button("🔄 Start Over", variant="secondary", size="sm")
349
+
350
+ # Main content area
351
+ with gr.Column(scale=4, elem_classes="centered"):
352
+ # Logo
353
+ logo = gr.Image("1.png", show_label=False, width=150, height=75, container=False)
354
+
355
+ # Step header with zigzag effect
356
+ step_header = gr.Markdown("### Step 1: Upload Your Video", elem_id="step-header")
357
+
358
+ # Main content area
359
+ content_box = gr.Markdown("**Choose your input method:**")
360
+
361
+ # Step 1: Upload section
362
+ with gr.Group(visible=True) as step1_content:
363
+ with gr.Tab("YouTube URL"):
364
+ youtube_input = gr.Textbox(
365
+ lines=2,
366
+ placeholder="Paste YouTube URL of golf swing here...",
367
+ label="YouTube URL",
368
+ show_label=False
369
+ )
370
+
371
+ with gr.Tab("Upload Video"):
372
+ video_upload = gr.File(
373
+ file_types=["video"],
374
+ label="Upload Video File",
375
+ show_label=False
376
+ )
377
+
378
+ # Step 2: Analysis in progress (hidden initially)
379
+ with gr.Group(visible=False) as step2_content:
380
+ gr.Markdown("### 🔄 Processing Your Video")
381
+ gr.Markdown("Please wait while we analyze your golf swing...")
382
+ analysis_progress = gr.HTML("⏳ Starting analysis...")
383
+
384
+ # Step 3: Results (hidden initially)
385
+ with gr.Group(visible=False) as step3_content:
386
+ results_display = gr.Markdown()
387
+
388
+ # Step 4: Improvements (hidden initially)
389
+ with gr.Group(visible=False) as step4_content:
390
+ improvements_output = gr.Markdown()
391
+
392
+ # Step 5: Chatbot (hidden initially)
393
+ with gr.Group(visible=False) as step5_content:
394
+ chatbot = gr.Chatbot(height=400)
395
+ chatbot_input = gr.Textbox(placeholder="Ask about golf swing technique...", show_label=False)
396
+ ask_btn = gr.Button("Ask Question")
397
+
398
+ # Navigation buttons
399
+ with gr.Row():
400
+ back_btn = gr.Button("← Back", variant="secondary", visible=False)
401
+ next_btn = gr.Button("🏌️ Start Analysis", variant="primary", size="lg")
402
+
403
+ # Step 3 action buttons (shown only on results step)
404
+ with gr.Row(visible=False) as step3_actions:
405
+ step3_improvements_btn = gr.Button("🎯 Get Improvements", variant="primary", size="lg")
406
+ step3_chatbot_btn = gr.Button("💬 Ask Questions", variant="secondary", size="lg")
407
+
408
+ # Event handlers for step navigation
409
+ next_btn.click(
410
+ fn=handle_next_step,
411
+ inputs=[current_step, youtube_input, video_upload, analysis_results],
412
+ outputs=[step_header, content_box, current_step, analysis_results,
413
+ next_btn, back_btn, sidebar, step1_content, step2_content, step3_content,
414
+ step4_content, step5_content, step3_actions, results_btn, improvements_btn, chatbot_btn]
415
+ )
416
+
417
+ back_btn.click(
418
+ fn=handle_back_step,
419
+ inputs=[current_step, analysis_results],
420
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
421
+ sidebar, step1_content, step2_content, step3_content, step4_content,
422
+ step5_content, step3_actions, results_btn, improvements_btn, chatbot_btn]
423
+ )
424
+
425
+ # Step 3 action buttons
426
+ step3_improvements_btn.click(
427
+ fn=lambda analysis_results: handle_go_to_step(4, analysis_results),
428
+ inputs=[analysis_results],
429
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
430
+ step1_content, step2_content, step3_content, step4_content, step5_content,
431
+ step3_actions, improvements_output, results_btn, improvements_btn, chatbot_btn]
432
+ )
433
+
434
+ step3_chatbot_btn.click(
435
+ fn=lambda analysis_results: handle_go_to_step(5, analysis_results),
436
+ inputs=[analysis_results],
437
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
438
+ step1_content, step2_content, step3_content, step4_content, step5_content,
439
+ step3_actions, results_btn, improvements_btn, chatbot_btn]
440
+ )
441
+
442
+ # Sidebar navigation
443
+ results_btn.click(
444
+ fn=lambda analysis_results: handle_go_to_step(3, analysis_results),
445
+ inputs=[analysis_results],
446
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
447
+ step1_content, step2_content, step3_content, step4_content, step5_content,
448
+ step3_actions, results_btn, improvements_btn, chatbot_btn]
449
+ )
450
+
451
+ improvements_btn.click(
452
+ fn=lambda analysis_results: handle_go_to_step(4, analysis_results),
453
+ inputs=[analysis_results],
454
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
455
+ step1_content, step2_content, step3_content, step4_content, step5_content,
456
+ step3_actions, improvements_output, results_btn, improvements_btn, chatbot_btn]
457
+ )
458
+
459
+ chatbot_btn.click(
460
+ fn=lambda analysis_results: handle_go_to_step(5, analysis_results),
461
+ inputs=[analysis_results],
462
+ outputs=[step_header, content_box, current_step, next_btn, back_btn,
463
+ step1_content, step2_content, step3_content, step4_content, step5_content,
464
+ step3_actions, results_btn, improvements_btn, chatbot_btn]
465
+ )
466
+
467
+ start_over_btn.click(
468
+ fn=handle_start_over,
469
+ outputs=[step_header, content_box, current_step, analysis_results,
470
+ next_btn, back_btn, sidebar, step1_content, step2_content, step3_content,
471
+ step4_content, step5_content, step3_actions, results_btn, improvements_btn, chatbot_btn]
472
+ )
473
+
474
+ # Chatbot functionality
475
+ ask_btn.click(
476
+ fn=get_chatbot_response,
477
+ inputs=[chatbot_input, analysis_results, chatbot],
478
+ outputs=[chatbot, chatbot_input]
479
+ )
480
+
481
+ chatbot_input.submit(
482
+ fn=get_chatbot_response,
483
+ inputs=[chatbot_input, analysis_results, chatbot],
484
+ outputs=[chatbot, chatbot_input]
485
+ )
486
+
487
+ return demo
488
+
489
+ # Launch the app
490
+ if __name__ == "__main__":
491
+ demo = create_interface()
492
+ demo.launch(share=True)
app/styles.css ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background-color: #FFFFFF;
3
+ font-family: 'Georgia', serif;
4
+ color: #0B3B0B;
5
+ }
6
+
7
+ .centered {
8
+ min-height: 100vh;
9
+ display: flex;
10
+ flex-direction: column;
11
+ justify-content: center;
12
+ align-items: center;
13
+ gap: 20px;
14
+ }
15
+
16
+ button, .gr-button {
17
+ background-color: #0B3B0B !important;
18
+ color: #FFFFFF !important;
19
+ border-radius: 25px !important;
20
+ padding: 12px 28px !important;
21
+ font-weight: bold !important;
22
+ font-size: 16px !important;
23
+ transition: all 0.3s ease !important;
24
+ border: none !important;
25
+ box-shadow: 0 2px 8px rgba(11, 59, 11, 0.2) !important;
26
+ }
27
+
28
+ button:hover, .gr-button:hover {
29
+ background-color: #4CAF50 !important;
30
+ transform: translateY(-1px) !important;
31
+ box-shadow: 0 4px 12px rgba(11, 59, 11, 0.3) !important;
32
+ }
33
+
34
+ textarea, input, .gr-textbox {
35
+ border: 2px solid #4CAF50 !important;
36
+ border-radius: 10px;
37
+ }
38
+
39
+ #step-header::after {
40
+ content: "";
41
+ display: block;
42
+ height: 10px;
43
+ background-image: repeating-linear-gradient(
44
+ 135deg,
45
+ #1D6F42,
46
+ #1D6F42 5px,
47
+ #A9DFBF 5px,
48
+ #A9DFBF 10px
49
+ );
50
+ margin-top: 8px;
51
+ }
52
+
53
+ /* Sidebar styling */
54
+ #sidebar {
55
+ background-color: rgba(76, 175, 80, 0.05) !important;
56
+ border-right: 2px solid rgba(76, 175, 80, 0.2) !important;
57
+ padding: 20px !important;
58
+ position: sticky !important;
59
+ top: 20px !important;
60
+ height: fit-content !important;
61
+ border-radius: 15px !important;
62
+ margin-right: 20px !important;
63
+ }
64
+
65
+ .sidebar-header {
66
+ color: #0B3B0B !important;
67
+ font-weight: bold !important;
68
+ text-align: center !important;
69
+ margin-bottom: 15px !important;
70
+ font-size: 16px !important;
71
+ }
72
+
73
+ #sidebar button {
74
+ width: 100% !important;
75
+ margin-bottom: 10px !important;
76
+ font-size: 12px !important;
77
+ padding: 8px 12px !important;
78
+ text-align: left !important;
79
+ }
80
+
81
+ /* Mobile responsive styles */
82
+ @media (max-width: 768px) {
83
+ .centered {
84
+ padding: 20px;
85
+ gap: 15px;
86
+ }
87
+
88
+ button, .gr-button {
89
+ padding: 10px 20px !important;
90
+ font-size: 14px !important;
91
+ width: 100% !important;
92
+ max-width: 280px !important;
93
+ }
94
+
95
+ #sidebar {
96
+ padding: 15px !important;
97
+ margin-right: 10px !important;
98
+ position: relative !important;
99
+ }
100
+
101
+ .sidebar-header {
102
+ font-size: 14px !important;
103
+ margin-bottom: 10px !important;
104
+ }
105
+
106
+ #sidebar button {
107
+ font-size: 11px !important;
108
+ padding: 6px 10px !important;
109
+ margin-bottom: 8px !important;
110
+ }
111
+ }
112
+
113
+ @media (max-width: 480px) {
114
+ #sidebar {
115
+ display: none !important;
116
+ }
117
+
118
+ .centered {
119
+ padding: 10px;
120
+ gap: 12px;
121
+ }
122
+ }