chenemii commited on
Commit
f3b5cd6
·
1 Parent(s): a16808c

Implement 5-step flow with floating sidebar in Streamlit

Browse files

- Convert Gradio step-based design to Streamlit implementation
- Add custom CSS with Par-ity branding and zigzag effects
- Implement 5 steps: Upload → Analysis → Results → Improvements/Chatbot
- Add floating sidebar navigation after Step 3
- Include start over functionality and step navigation
- Switch back to Streamlit SDK in Hugging Face configuration
- Remove Gradio dependency and restore Streamlit in requirements

Files changed (3) hide show
  1. README.md +2 -2
  2. app/streamlit_app.py +319 -219
  3. requirements.txt +1 -1
README.md CHANGED
@@ -3,8 +3,8 @@ title: Par-ity Project
3
  emoji: ⛳
4
  colorFrom: blue
5
  colorTo: green
6
- sdk: gradio
7
- app_file: app.py
8
  pinned: false
9
  ---
10
 
 
3
  emoji: ⛳
4
  colorFrom: blue
5
  colorTo: green
6
+ sdk: streamlit
7
+ app_file: app/streamlit_app.py
8
  pinned: false
9
  ---
10
 
app/streamlit_app.py CHANGED
@@ -587,37 +587,106 @@ def display_video(video_path, width=300):
587
 
588
  # Main app
589
  def main():
590
- """Main Streamlit application"""
591
- st.title("Par-ity Project: Golf Swing Analysis 🏌️‍♀️")
592
- st.write("Founded to address the gender gap in golf participation and access to quality coaching resources, Par-ity Project is a technology-driven initiative empowering girls in golf through innovative AI based swing analysis. This technology uses computer vision and machine learning algorithms to analyze golf swings and provide personalized feedback to improve technique and performance.")
 
 
 
 
 
 
 
 
 
 
593
 
594
- # Check and display OpenAI client status immediately
595
- st.markdown("---")
596
- st.subheader("🔧 System Status")
 
 
 
 
597
 
598
- if RAG_AVAILABLE:
599
- try:
600
- # Create a temporary RAG instance to check OpenAI client status
601
- with st.spinner("Checking OpenAI API connection..."):
602
- temp_rag = GolfSwingRAG()
603
-
604
- if temp_rag.openai_client:
605
- st.success("✅ **OpenAI API Client**: Successfully initialized and tested")
606
- st.info("💡 **ChatBot & AI Analysis**: Available - You can use the Golf Swing Chatbot and AI-powered improvement recommendations")
607
- else:
608
- st.error("❌ **OpenAI API Client**: Not available")
609
- st.warning("⚠️ **Impact**: ChatBot will use fallback responses, AI analysis may be limited")
610
- st.info("🔑 **Fix**: Check that your OPENAI_API_KEY is set correctly in Hugging Face Space secrets")
611
- except Exception as e:
612
- st.error(f"❌ **OpenAI API Client**: Error during initialization - {str(e)}")
613
- st.warning("⚠️ **Impact**: ChatBot and AI features will use fallback mode")
614
- else:
615
- st.error("❌ **RAG System**: Not available due to missing dependencies")
616
- st.warning("⚠️ **Impact**: ChatBot feature will be disabled")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
- st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
619
 
620
- # Initialize session state for storing analysis results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  if 'video_analyzed' not in st.session_state:
622
  st.session_state.video_analyzed = False
623
  if 'analysis_data' not in st.session_state:
@@ -630,251 +699,282 @@ def main():
630
  'trajectory_data': None,
631
  'sample_rate': None
632
  }
633
- if 'show_chatbot' not in st.session_state:
634
- st.session_state.show_chatbot = False
635
 
636
- # Add session cleanup - clean up old files when starting a new session
637
  if 'session_initialized' not in st.session_state:
638
  cleanup_result = cleanup_downloads_directory(keep_annotated=True)
639
  if cleanup_result.get('files_removed', 0) > 0:
640
- st.info(f"🗑️ Cleaned up {cleanup_result['files_removed']} old files ({cleanup_result['space_freed_mb']} MB freed)")
641
  st.session_state.session_initialized = True
642
 
643
- # Automatic cleanup function
644
- def perform_cleanup():
645
- """Perform automatic cleanup of temporary files"""
646
- cleanup_result = cleanup_downloads_directory(keep_annotated=False)
647
- return cleanup_result
648
-
649
- # Set automatic defaults (no user configuration needed)
650
- # Check available LLM services and enable automatically if available
651
  llm_services = check_llm_services()
652
  any_service_available = llm_services['ollama']['available'] or llm_services['openai']['available']
653
-
654
- # Automatically enable LLM analysis if services are available
655
  enable_gpt = any_service_available
656
-
657
- # Set default frame processing rate (1 = all frames for best accuracy)
658
  sample_rate = 1
659
-
660
- # Disable pro comparison feature entirely
661
- enable_pro_comparison = False
662
- pro_url = None
663
-
664
- # Video input options
665
- st.header("Video Input")
666
- input_option = st.radio("Choose input method:",
667
- ["YouTube URL", "Upload Video"])
668
 
669
- video_path = None
670
- analyze_clicked = False
671
-
672
- if input_option == "YouTube URL":
673
- youtube_url = st.text_input("Enter YouTube URL of golf swing:")
674
-
675
- analyze_clicked = st.button("Analyze Swing", key="analyze_youtube")
676
- if youtube_url and analyze_clicked:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  if validate_youtube_url(youtube_url):
678
  with st.spinner("Downloading video..."):
679
  try:
680
  video_path = download_youtube_video(youtube_url)
681
  st.success("Video downloaded successfully!")
682
- display_video(video_path, width=400)
683
  except Exception as e:
684
  st.error(f"Error downloading video: {str(e)}")
685
- st.session_state.video_analyzed = False
686
  return
687
  else:
688
  st.error("Please enter a valid YouTube URL")
689
- st.session_state.video_analyzed = False
690
  return
 
 
 
 
 
 
 
 
691
 
692
- else: # Upload Video
693
- uploaded_file = st.file_uploader("Upload a golf swing video",
694
- type=["mp4", "mov", "avi"])
695
-
696
- analyze_clicked = st.button("Analyze Swing", key="analyze_upload")
697
- if uploaded_file and analyze_clicked:
698
- with st.spinner("Processing uploaded video..."):
699
- try:
700
- video_path = process_uploaded_video(uploaded_file)
701
- st.success("Video uploaded successfully!")
702
- display_video(video_path, width=400)
703
- except Exception as e:
704
- st.error(f"Error processing video: {str(e)}")
705
- st.session_state.video_analyzed = False
706
- return
707
 
708
- # Download pro reference if enabled
709
- if enable_pro_comparison and (video_path or st.session_state.video_analyzed):
710
- if not st.session_state.pro_reference_path:
711
- with st.spinner("Downloading professional golfer reference..."):
712
- try:
713
- pro_path = download_pro_reference(pro_url)
714
- st.session_state.pro_reference_path = pro_path
715
- st.success("Professional reference downloaded successfully!")
716
- except Exception as e:
717
- st.error(f"Error downloading pro reference: {str(e)}")
718
- st.session_state.pro_reference_path = None
719
-
720
- # Process video if available and analyze button was clicked
721
- if video_path and analyze_clicked:
722
  try:
723
- # Step 1: Process video and detect objects
724
  with st.spinner("Processing video and detecting objects..."):
725
- frames, detections = process_video(video_path,
726
- sample_rate=sample_rate)
727
- st.success("Video processing complete!")
728
 
729
- # Step 2: Analyze pose
730
  with st.spinner("Analyzing golfer's pose..."):
731
  pose_data = analyze_pose(frames)
732
- st.success("Pose analysis complete")
733
 
734
- # Step 3: Segment swing into phases
735
  with st.spinner("Segmenting swing phases..."):
736
- swing_phases = segment_swing(pose_data,
737
- detections,
738
- sample_rate=sample_rate)
739
 
740
- # Step 4: Analyze trajectory and speed
741
  with st.spinner("Analyzing trajectory and speed..."):
742
- trajectory_data = analyze_trajectory(frames,
743
- detections,
744
- swing_phases,
745
- sample_rate=sample_rate)
746
-
747
- # Prepare data for LLM regardless of whether GPT is enabled
748
- analysis_data = prepare_data_for_llm(pose_data, swing_phases,
749
- trajectory_data)
750
  prompt = create_llm_prompt(analysis_data)
751
 
752
- # Store analysis data in session state
753
- st.session_state.video_analyzed = True
754
- st.session_state.analysis_data = {
755
- 'video_path': video_path,
756
  'frames': frames,
757
  'detections': detections,
758
  'pose_data': pose_data,
759
  'swing_phases': swing_phases,
760
  'trajectory_data': trajectory_data,
761
- 'sample_rate': sample_rate,
762
  'analysis_data': analysis_data,
763
  'prompt': prompt
764
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
 
766
- # Trackio logging moved out of try block to avoid impacting UI
 
 
 
 
 
 
 
 
 
 
 
 
767
 
768
- # Keep the original video file for potential annotation
769
- # Video will be cleaned up when user uploads a new video or session ends
770
 
771
- # Present the options after analysis
772
- st.subheader("What would you like to do next?")
773
- options_col1, options_col2 = st.columns(2)
 
774
 
775
- with options_col1:
776
- st.info(
777
- "**Option 1: Generate Recommendations**\n\nGet AI-powered analysis of your swing with specific tips for improvement."
778
- )
779
-
780
- with options_col2:
781
- st.info(
782
- "**Option 2: Golf Swing Chatbot**\n\nAsk specific questions about golf swing technique and get expert advice from our knowledge base."
783
- )
784
 
785
- except Exception as e:
786
- st.error(f"Error during analysis: {str(e)}")
787
- st.session_state.video_analyzed = False
788
- # Clean up on error as well
789
- if video_path and os.path.exists(video_path):
790
- cleanup_video_file(video_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
 
792
- # Show action buttons and their results (only if analysis is complete)
 
 
 
793
  if st.session_state.video_analyzed:
794
- # Trackio/telemetry removed per request
795
-
796
- # Display the GPT prompt in an expander
797
- if 'prompt' in st.session_state.analysis_data:
798
- with st.expander("View LLM Prompt", expanded=False):
799
- st.code(st.session_state.analysis_data['prompt'],
800
- language="text")
801
-
802
- # Create columns for the action buttons
803
- button_col1, button_col2 = st.columns(2)
804
-
805
- with button_col1:
806
- improvements_clicked = st.button("Generate Improvements",
807
- key="gpt_recommendations",
808
- use_container_width=True)
809
 
810
- with button_col2:
811
- chatbot_clicked = st.button("Golf Swing Chatbot",
812
- key="rag_chatbot",
813
- use_container_width=True)
814
-
815
- # Handle improvement recommendations generation
816
- if improvements_clicked:
817
- # Reset chatbot state when other buttons are clicked
818
- st.session_state.show_chatbot = False
819
- with st.spinner(
820
- "Analyzing your swing and generating recommendations..."):
821
- # Get data from session state
822
- data = st.session_state.analysis_data
823
- pose_data = data['pose_data']
824
- swing_phases = data['swing_phases']
825
- trajectory_data = data['trajectory_data']
826
-
827
- # Generate detailed analysis with recommendations
828
- analysis = generate_swing_analysis(pose_data, swing_phases,
829
- trajectory_data)
830
-
831
- # Display the analysis
832
- st.subheader("Swing Analysis and Recommendations")
833
-
834
- # Check available services to show appropriate message
835
- llm_services = check_llm_services()
836
- any_service_available = llm_services['ollama'][
837
- 'available'] or llm_services['openai']['available']
838
-
839
- if not any_service_available or not enable_gpt:
840
- st.info(
841
- "ℹ️ **Using sample analysis mode**. The recommendations below are general examples and not personalized to your specific swing."
842
- )
843
- else:
844
- if llm_services['ollama']['available'] and llm_services[
845
- 'openai']['available']:
846
- st.info(
847
- "🔄 **Analysis generated using available LLM services** (tried Ollama first, OpenAI as fallback)"
848
- )
849
- elif llm_services['ollama']['available']:
850
- st.info("🦙 **Analysis generated using Ollama**")
851
- elif llm_services['openai']['available']:
852
- st.info("🤖 **Analysis generated using OpenAI**")
853
-
854
- # Parse and display the formatted analysis instead of raw markdown
855
- if "Error:" not in analysis:
856
  formatted_analysis = parse_and_format_analysis(analysis)
857
  display_formatted_analysis(formatted_analysis)
858
- else:
859
- # Show error message if analysis failed
860
- st.error(analysis)
861
-
862
- # Handle RAG chatbot
863
- if chatbot_clicked:
864
- st.session_state.show_chatbot = True
865
 
866
- # Always show chatbot interface if it's active
867
- if st.session_state.show_chatbot:
868
- # Create header with close button
869
- header_col1, header_col2 = st.columns([3, 1])
870
- with header_col1:
871
- st.subheader("Golf Swing Technique Chatbot")
872
- with header_col2:
873
- if st.button(" Close Chatbot", use_container_width=True):
874
- st.session_state.show_chatbot = False
875
- st.rerun()
876
-
877
- render_rag_interface()
 
 
878
 
879
 
880
  if __name__ == "__main__":
 
587
 
588
  # Main app
589
  def main():
590
+ """Main Streamlit application with 5-step flow"""
591
+
592
+ # Custom CSS for Par-ity branding
593
+ st.markdown("""
594
+ <style>
595
+ /* Par-ity Project Styling */
596
+ .main-header {
597
+ text-align: center;
598
+ color: #0B3B0B;
599
+ font-family: 'Georgia', serif;
600
+ font-weight: bold;
601
+ margin-bottom: 10px;
602
+ }
603
 
604
+ .step-header {
605
+ color: #0B3B0B;
606
+ font-family: 'Georgia', serif;
607
+ font-weight: bold;
608
+ margin: 20px 0;
609
+ position: relative;
610
+ }
611
 
612
+ .step-header::after {
613
+ content: "";
614
+ display: block;
615
+ height: 10px;
616
+ background: repeating-linear-gradient(
617
+ 135deg,
618
+ #1D6F42 0px,
619
+ #1D6F42 5px,
620
+ #A9DFBF 5px,
621
+ #A9DFBF 10px
622
+ );
623
+ margin: 8px auto 0;
624
+ width: 200px;
625
+ border-radius: 5px;
626
+ }
627
+
628
+ .sidebar-nav {
629
+ position: fixed;
630
+ top: 100px;
631
+ right: 20px;
632
+ background: rgba(76, 175, 80, 0.05);
633
+ border: 2px solid rgba(76, 175, 80, 0.2);
634
+ border-radius: 15px;
635
+ padding: 15px;
636
+ z-index: 1000;
637
+ min-width: 150px;
638
+ }
639
+
640
+ .nav-button {
641
+ display: block;
642
+ width: 100%;
643
+ margin: 5px 0;
644
+ padding: 8px 12px;
645
+ background: #0B3B0B;
646
+ color: white;
647
+ border: none;
648
+ border-radius: 20px;
649
+ text-align: center;
650
+ text-decoration: none;
651
+ font-size: 12px;
652
+ cursor: pointer;
653
+ }
654
 
655
+ .nav-button:hover {
656
+ background: #4CAF50;
657
+ }
658
+
659
+ .stButton > button {
660
+ background-color: #0B3B0B;
661
+ color: white;
662
+ border-radius: 25px;
663
+ border: none;
664
+ padding: 12px 28px;
665
+ font-weight: bold;
666
+ font-size: 16px;
667
+ transition: all 0.3s ease;
668
+ }
669
 
670
+ .stButton > button:hover {
671
+ background-color: #4CAF50;
672
+ transform: translateY(-1px);
673
+ }
674
+ </style>
675
+ """, unsafe_allow_html=True)
676
+
677
+ # Main title with logo placeholder
678
+ st.markdown("""
679
+ <div class="main-header">
680
+ <h1>⛳ Par-ity Project: Golf Swing Analysis</h1>
681
+ <p style="color: #4CAF50; font-size: 18px; margin-top: -10px;">
682
+ Founded to address the gender gap in golf participation and access to quality coaching resources
683
+ </p>
684
+ </div>
685
+ """, unsafe_allow_html=True)
686
+
687
+ # Initialize session state for step-based flow
688
+ if 'current_step' not in st.session_state:
689
+ st.session_state.current_step = 1
690
  if 'video_analyzed' not in st.session_state:
691
  st.session_state.video_analyzed = False
692
  if 'analysis_data' not in st.session_state:
 
699
  'trajectory_data': None,
700
  'sample_rate': None
701
  }
702
+ if 'show_sidebar' not in st.session_state:
703
+ st.session_state.show_sidebar = False
704
 
705
+ # Add session cleanup
706
  if 'session_initialized' not in st.session_state:
707
  cleanup_result = cleanup_downloads_directory(keep_annotated=True)
708
  if cleanup_result.get('files_removed', 0) > 0:
709
+ st.success(f"🗑️ Cleaned up {cleanup_result['files_removed']} old files ({cleanup_result['space_freed_mb']} MB freed)")
710
  st.session_state.session_initialized = True
711
 
712
+ # Set automatic defaults
 
 
 
 
 
 
 
713
  llm_services = check_llm_services()
714
  any_service_available = llm_services['ollama']['available'] or llm_services['openai']['available']
 
 
715
  enable_gpt = any_service_available
 
 
716
  sample_rate = 1
 
 
 
 
 
 
 
 
 
717
 
718
+ # Floating sidebar navigation (appears after Step 3)
719
+ if st.session_state.current_step >= 3:
720
+ st.session_state.show_sidebar = True
721
+
722
+ if st.session_state.show_sidebar:
723
+ st.markdown("""
724
+ <div class="sidebar-nav">
725
+ <h4 style="color: #0B3B0B; margin: 0 0 10px 0; font-size: 14px;">Navigation</h4>
726
+ </div>
727
+ """, unsafe_allow_html=True)
728
+
729
+ # Navigation buttons in sidebar
730
+ sidebar_col = st.sidebar
731
+ with sidebar_col:
732
+ st.markdown("### 🧭 Quick Navigation")
733
+
734
+ if st.button("📊 Results", key="nav_results", use_container_width=True):
735
+ st.session_state.current_step = 3
736
+ st.rerun()
737
+
738
+ if st.button("🎯 Improvements", key="nav_improvements", use_container_width=True):
739
+ st.session_state.current_step = 4
740
+ st.rerun()
741
+
742
+ if st.button("💬 Chatbot", key="nav_chatbot", use_container_width=True):
743
+ st.session_state.current_step = 5
744
+ st.rerun()
745
+
746
+ st.markdown("---")
747
+ if st.button("🔄 Start Over", key="nav_start_over", use_container_width=True):
748
+ # Reset all session state
749
+ for key in list(st.session_state.keys()):
750
+ if key != 'session_initialized':
751
+ del st.session_state[key]
752
+ st.session_state.current_step = 1
753
+ st.rerun()
754
+
755
+ # Step-based content rendering
756
+ if st.session_state.current_step == 1:
757
+ render_step_1()
758
+ elif st.session_state.current_step == 2:
759
+ render_step_2()
760
+ elif st.session_state.current_step == 3:
761
+ render_step_3()
762
+ elif st.session_state.current_step == 4:
763
+ render_step_4()
764
+ elif st.session_state.current_step == 5:
765
+ render_step_5()
766
+
767
+ def render_step_1():
768
+ """Step 1: Upload Video"""
769
+ st.markdown('<div class="step-header"><h2>Step 1: Upload Your Video</h2></div>', unsafe_allow_html=True)
770
+
771
+ st.markdown("**Choose your input method:**")
772
+
773
+ col1, col2 = st.columns(2)
774
+
775
+ with col1:
776
+ st.markdown("#### YouTube URL")
777
+ youtube_url = st.text_input("Enter YouTube URL of golf swing:", key="youtube_input")
778
+
779
+ with col2:
780
+ st.markdown("#### Upload Video File")
781
+ uploaded_file = st.file_uploader("Upload a golf swing video", type=["mp4", "mov", "avi"], key="video_upload")
782
+
783
+ # Analyze button
784
+ if st.button("🏌️ Start Analysis", key="start_analysis", use_container_width=True):
785
+ video_path = None
786
+
787
+ if uploaded_file is not None:
788
+ with st.spinner("Processing uploaded video..."):
789
+ try:
790
+ video_path = process_uploaded_video(uploaded_file)
791
+ st.success("Video uploaded successfully!")
792
+ except Exception as e:
793
+ st.error(f"Error processing video: {str(e)}")
794
+ return
795
+
796
+ elif youtube_url:
797
  if validate_youtube_url(youtube_url):
798
  with st.spinner("Downloading video..."):
799
  try:
800
  video_path = download_youtube_video(youtube_url)
801
  st.success("Video downloaded successfully!")
 
802
  except Exception as e:
803
  st.error(f"Error downloading video: {str(e)}")
 
804
  return
805
  else:
806
  st.error("Please enter a valid YouTube URL")
 
807
  return
808
+ else:
809
+ st.error("Please provide either a YouTube URL or upload a video file.")
810
+ return
811
+
812
+ if video_path:
813
+ st.session_state.analysis_data['video_path'] = video_path
814
+ st.session_state.current_step = 2
815
+ st.rerun()
816
 
817
+ def render_step_2():
818
+ """Step 2: Analyzing Video and Pose"""
819
+ st.markdown('<div class="step-header"><h2>Step 2: Analyzing Video and Pose</h2></div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
820
 
821
+ st.markdown("🔄 **Processing your swing video...**")
822
+ st.markdown("Please wait while we analyze your golf swing.")
823
+
824
+ video_path = st.session_state.analysis_data.get('video_path')
825
+
826
+ if video_path and not st.session_state.video_analyzed:
 
 
 
 
 
 
 
 
827
  try:
828
+ # Process the video
829
  with st.spinner("Processing video and detecting objects..."):
830
+ frames, detections = process_video(video_path, sample_rate=1)
831
+ st.success("✅ Video processing complete!")
 
832
 
 
833
  with st.spinner("Analyzing golfer's pose..."):
834
  pose_data = analyze_pose(frames)
835
+ st.success("Pose analysis complete!")
836
 
 
837
  with st.spinner("Segmenting swing phases..."):
838
+ swing_phases = segment_swing(pose_data, detections, sample_rate=1)
839
+ st.success("✅ Swing segmentation complete!")
 
840
 
 
841
  with st.spinner("Analyzing trajectory and speed..."):
842
+ trajectory_data = analyze_trajectory(frames, detections, swing_phases, sample_rate=1)
843
+ st.success("✅ Trajectory analysis complete!")
844
+
845
+ # Prepare data for LLM
846
+ analysis_data = prepare_data_for_llm(pose_data, swing_phases, trajectory_data)
 
 
 
847
  prompt = create_llm_prompt(analysis_data)
848
 
849
+ # Store analysis data
850
+ st.session_state.analysis_data.update({
 
 
851
  'frames': frames,
852
  'detections': detections,
853
  'pose_data': pose_data,
854
  'swing_phases': swing_phases,
855
  'trajectory_data': trajectory_data,
856
+ 'sample_rate': 1,
857
  'analysis_data': analysis_data,
858
  'prompt': prompt
859
+ })
860
+
861
+ st.session_state.video_analyzed = True
862
+ st.session_state.current_step = 3
863
+ st.rerun()
864
+
865
+ except Exception as e:
866
+ st.error(f"❌ **Analysis Failed**\n\n{str(e)}\n\nPlease try again.")
867
+
868
+ # Back button
869
+ if st.button("← Back to Upload", key="back_to_upload"):
870
+ st.session_state.current_step = 1
871
+ st.rerun()
872
+ else:
873
+ # Analysis already completed, move to results
874
+ st.session_state.current_step = 3
875
+ st.rerun()
876
 
877
+ def render_step_3():
878
+ """Step 3: See Results"""
879
+ st.markdown('<div class="step-header"><h2>Step 3: See Your Results</h2></div>', unsafe_allow_html=True)
880
+
881
+ if st.session_state.video_analyzed:
882
+ data = st.session_state.analysis_data
883
+ video_path = data.get('video_path', '')
884
+ swing_phases = data.get('swing_phases', [])
885
+ pose_data = data.get('pose_data', [])
886
+ trajectory_data = data.get('trajectory_data')
887
+
888
+ st.markdown(f"""
889
+ ## 🏌️ Swing Analysis Results
890
 
891
+ **Video:** {os.path.basename(video_path) if video_path else 'Unknown'}
 
892
 
893
+ ### 📊 **Analysis Summary**
894
+ - **Swing Phases Detected:** {len(swing_phases) if swing_phases else 0}
895
+ - **Pose Data Points:** {len(pose_data) if pose_data else 0}
896
+ - **Trajectory Analysis:** {'✅ Complete' if trajectory_data else '❌ Failed'}
897
 
898
+ ### 🎯 **Key Findings**
899
+ - Swing timing and rhythm patterns identified
900
+ - Pose alignment and form analyzed
901
+ - Ball trajectory and impact mechanics evaluated
 
 
 
 
 
902
 
903
+ **Ready to dive deeper?** Choose how you'd like to explore your swing analysis:
904
+ """)
905
+
906
+ # Display video if available
907
+ if video_path and os.path.exists(video_path):
908
+ st.markdown("### 📹 Your Swing Video")
909
+ display_video(video_path, width=400)
910
+
911
+ # Action buttons
912
+ col1, col2 = st.columns(2)
913
+
914
+ with col1:
915
+ if st.button("🎯 Get Improvements", key="get_improvements", use_container_width=True):
916
+ st.session_state.current_step = 4
917
+ st.rerun()
918
+
919
+ with col2:
920
+ if st.button("💬 Ask Questions", key="ask_questions", use_container_width=True):
921
+ st.session_state.current_step = 5
922
+ st.rerun()
923
+ else:
924
+ st.error("No analysis data available. Please start over.")
925
+ if st.button("🔄 Start Over", key="restart_analysis"):
926
+ st.session_state.current_step = 1
927
+ st.rerun()
928
 
929
+ def render_step_4():
930
+ """Step 4: AI-Powered Improvements"""
931
+ st.markdown('<div class="step-header"><h2>Step 4: AI-Powered Improvements</h2></div>', unsafe_allow_html=True)
932
+
933
  if st.session_state.video_analyzed:
934
+ data = st.session_state.analysis_data
935
+ pose_data = data['pose_data']
936
+ swing_phases = data['swing_phases']
937
+ trajectory_data = data['trajectory_data']
 
 
 
 
 
 
 
 
 
 
 
938
 
939
+ with st.spinner("🎯 **Generating personalized swing improvements...**"):
940
+ # Generate detailed analysis with recommendations
941
+ analysis = generate_swing_analysis(pose_data, swing_phases, trajectory_data)
942
+
943
+ st.markdown("## 🎯 Personalized Swing Improvements")
944
+
945
+ # Check available services
946
+ llm_services = check_llm_services()
947
+ any_service_available = llm_services['ollama']['available'] or llm_services['openai']['available']
948
+
949
+ if not any_service_available:
950
+ st.info("ℹ️ **Using sample analysis mode**. The recommendations below are general examples.")
951
+ else:
952
+ st.info("🤖 **Analysis generated using AI-powered swing analysis technology**")
953
+
954
+ # Display the analysis
955
+ if "Error:" not in analysis:
956
+ try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
957
  formatted_analysis = parse_and_format_analysis(analysis)
958
  display_formatted_analysis(formatted_analysis)
959
+ except:
960
+ st.markdown(analysis)
961
+ else:
962
+ st.error(analysis)
 
 
 
963
 
964
+ else:
965
+ st.error("No analysis data available. Please analyze a video first.")
966
+
967
+ def render_step_5():
968
+ """Step 5: Ask the Golf Expert"""
969
+ st.markdown('<div class="step-header"><h2>Step 5: Ask the Golf Expert</h2></div>', unsafe_allow_html=True)
970
+
971
+ st.markdown("💬 **Ready to answer your swing questions!**")
972
+
973
+ if RAG_AVAILABLE:
974
+ render_rag_interface()
975
+ else:
976
+ st.error("❌ **RAG System**: Not available due to missing dependencies")
977
+ st.info("The Golf Expert chatbot requires additional dependencies that are not currently available.")
978
 
979
 
980
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -9,7 +9,7 @@ torchvision==0.17.0
9
  openai>=1.0.0
10
  python-dotenv==1.0.0
11
  tqdm==4.66.1
12
- gradio>=4.0.0
13
  pandas==2.1.4
14
  sentence-transformers>=2.2.0
15
  faiss-cpu>=1.7.0
 
9
  openai>=1.0.0
10
  python-dotenv==1.0.0
11
  tqdm==4.66.1
12
+ streamlit>=1.32.0
13
  pandas==2.1.4
14
  sentence-transformers>=2.2.0
15
  faiss-cpu>=1.7.0