Spaces:
Sleeping
Sleeping
github-actions[bot]
commited on
Commit
Β·
d1ec0de
1
Parent(s):
a4d1767
π€ Auto-deploy from GitHub (push) - 3681966 - 2025-08-15 10:47:16 UTC
Browse files- apps/gradio-app/src/fitness_gradio/main.py +2 -0
- apps/gradio-app/src/fitness_gradio/ui/app.py +8 -7
- apps/gradio-app/src/fitness_gradio/ui/components.py +19 -9
- apps/gradio-app/src/fitness_gradio/ui/handlers.py +162 -26
- apps/gradio-app/src/fitness_gradio/ui/styles.py +26 -0
- apps/gradio-app/test_datetime.py +34 -0
- apps/gradio-app/test_datetime_simple.py +56 -0
- test_datetime.py +34 -0
apps/gradio-app/src/fitness_gradio/main.py
CHANGED
|
@@ -68,7 +68,9 @@ def main():
|
|
| 68 |
app.launch(**gradio_config)
|
| 69 |
|
| 70 |
except Exception as e:
|
|
|
|
| 71 |
logger.error(f"Failed to start Gradio app: {str(e)}")
|
|
|
|
| 72 |
sys.exit(1)
|
| 73 |
|
| 74 |
|
|
|
|
| 68 |
app.launch(**gradio_config)
|
| 69 |
|
| 70 |
except Exception as e:
|
| 71 |
+
import traceback
|
| 72 |
logger.error(f"Failed to start Gradio app: {str(e)}")
|
| 73 |
+
logger.error(f"Full traceback:\n{traceback.format_exc()}")
|
| 74 |
sys.exit(1)
|
| 75 |
|
| 76 |
|
apps/gradio-app/src/fitness_gradio/ui/app.py
CHANGED
|
@@ -64,7 +64,7 @@ class FitnessAppUI:
|
|
| 64 |
with gr.Row():
|
| 65 |
with gr.Column():
|
| 66 |
gr.Markdown("## π
Training Schedule Calendar")
|
| 67 |
-
calendar_html, calendar_view_selector, calendar_prev_btn, calendar_next_btn, calendar_today_btn, calendar_date_picker, refresh_calendar_btn, calendar_current_date = UIComponents.create_calendar_section()
|
| 68 |
|
| 69 |
# Voice conversation state
|
| 70 |
voice_state = gr.State(value=VoiceConversationState())
|
|
@@ -89,7 +89,7 @@ class FitnessAppUI:
|
|
| 89 |
voice_exit_btn, voice_row, voice_state,
|
| 90 |
plan_display, view_plan_btn, clear_plan_btn,
|
| 91 |
calendar_html, calendar_view_selector, calendar_prev_btn, calendar_next_btn,
|
| 92 |
-
calendar_today_btn, calendar_date_picker, refresh_calendar_btn, calendar_current_date
|
| 93 |
)
|
| 94 |
|
| 95 |
def _setup_event_handlers(
|
|
@@ -119,7 +119,8 @@ class FitnessAppUI:
|
|
| 119 |
calendar_today_btn: gr.Button,
|
| 120 |
calendar_date_picker: gr.DateTime,
|
| 121 |
refresh_calendar_btn: gr.Button,
|
| 122 |
-
calendar_current_date: gr.Textbox
|
|
|
|
| 123 |
) -> None:
|
| 124 |
"""Set up all event handlers."""
|
| 125 |
|
|
@@ -277,25 +278,25 @@ class FitnessAppUI:
|
|
| 277 |
calendar_prev_btn.click(
|
| 278 |
lambda current_date, view_type: UIHandlers.navigate_calendar(current_date, view_type, "prev"),
|
| 279 |
inputs=[calendar_current_date, calendar_view_selector],
|
| 280 |
-
outputs=[calendar_html, calendar_current_date]
|
| 281 |
)
|
| 282 |
|
| 283 |
calendar_next_btn.click(
|
| 284 |
lambda current_date, view_type: UIHandlers.navigate_calendar(current_date, view_type, "next"),
|
| 285 |
inputs=[calendar_current_date, calendar_view_selector],
|
| 286 |
-
outputs=[calendar_html, calendar_current_date]
|
| 287 |
)
|
| 288 |
|
| 289 |
calendar_today_btn.click(
|
| 290 |
UIHandlers.go_to_today,
|
| 291 |
inputs=[calendar_view_selector],
|
| 292 |
-
outputs=[calendar_html, calendar_current_date]
|
| 293 |
)
|
| 294 |
|
| 295 |
calendar_date_picker.change(
|
| 296 |
UIHandlers.jump_to_date,
|
| 297 |
inputs=[calendar_date_picker, calendar_view_selector],
|
| 298 |
-
outputs=[calendar_html, calendar_current_date]
|
| 299 |
)
|
| 300 |
|
| 301 |
# Update calendar after bot responses (when new fitness plans are created)
|
|
|
|
| 64 |
with gr.Row():
|
| 65 |
with gr.Column():
|
| 66 |
gr.Markdown("## π
Training Schedule Calendar")
|
| 67 |
+
calendar_html, calendar_view_selector, calendar_prev_btn, calendar_next_btn, calendar_today_btn, calendar_date_picker, refresh_calendar_btn, calendar_current_date, current_date_display = UIComponents.create_calendar_section()
|
| 68 |
|
| 69 |
# Voice conversation state
|
| 70 |
voice_state = gr.State(value=VoiceConversationState())
|
|
|
|
| 89 |
voice_exit_btn, voice_row, voice_state,
|
| 90 |
plan_display, view_plan_btn, clear_plan_btn,
|
| 91 |
calendar_html, calendar_view_selector, calendar_prev_btn, calendar_next_btn,
|
| 92 |
+
calendar_today_btn, calendar_date_picker, refresh_calendar_btn, calendar_current_date, current_date_display
|
| 93 |
)
|
| 94 |
|
| 95 |
def _setup_event_handlers(
|
|
|
|
| 119 |
calendar_today_btn: gr.Button,
|
| 120 |
calendar_date_picker: gr.DateTime,
|
| 121 |
refresh_calendar_btn: gr.Button,
|
| 122 |
+
calendar_current_date: gr.Textbox,
|
| 123 |
+
current_date_display: gr.Markdown
|
| 124 |
) -> None:
|
| 125 |
"""Set up all event handlers."""
|
| 126 |
|
|
|
|
| 278 |
calendar_prev_btn.click(
|
| 279 |
lambda current_date, view_type: UIHandlers.navigate_calendar(current_date, view_type, "prev"),
|
| 280 |
inputs=[calendar_current_date, calendar_view_selector],
|
| 281 |
+
outputs=[calendar_html, calendar_current_date, current_date_display, calendar_date_picker]
|
| 282 |
)
|
| 283 |
|
| 284 |
calendar_next_btn.click(
|
| 285 |
lambda current_date, view_type: UIHandlers.navigate_calendar(current_date, view_type, "next"),
|
| 286 |
inputs=[calendar_current_date, calendar_view_selector],
|
| 287 |
+
outputs=[calendar_html, calendar_current_date, current_date_display, calendar_date_picker]
|
| 288 |
)
|
| 289 |
|
| 290 |
calendar_today_btn.click(
|
| 291 |
UIHandlers.go_to_today,
|
| 292 |
inputs=[calendar_view_selector],
|
| 293 |
+
outputs=[calendar_html, calendar_current_date, current_date_display, calendar_date_picker]
|
| 294 |
)
|
| 295 |
|
| 296 |
calendar_date_picker.change(
|
| 297 |
UIHandlers.jump_to_date,
|
| 298 |
inputs=[calendar_date_picker, calendar_view_selector],
|
| 299 |
+
outputs=[calendar_html, calendar_current_date, current_date_display, calendar_date_picker]
|
| 300 |
)
|
| 301 |
|
| 302 |
# Update calendar after bot responses (when new fitness plans are created)
|
apps/gradio-app/src/fitness_gradio/ui/components.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
| 2 |
UI components for the fitness app.
|
| 3 |
"""
|
| 4 |
import gradio as gr
|
|
|
|
|
|
|
| 5 |
from typing import List, Any
|
| 6 |
-
from datetime import date
|
| 7 |
|
| 8 |
from fitness_core.agents import FitnessAgent
|
| 9 |
from fitness_core.agents.providers import ModelProvider
|
|
@@ -1014,7 +1016,7 @@ class UIComponents:
|
|
| 1014 |
Create the training calendar section with navigation controls and view options.
|
| 1015 |
|
| 1016 |
Returns:
|
| 1017 |
-
Tuple of (calendar_html, view_selector, prev_btn, next_btn, today_btn, date_picker, refresh_calendar_btn, current_date)
|
| 1018 |
"""
|
| 1019 |
from datetime import date
|
| 1020 |
|
|
@@ -1060,13 +1062,13 @@ class UIComponents:
|
|
| 1060 |
scale=2
|
| 1061 |
)
|
| 1062 |
|
| 1063 |
-
# Date picker for jumping to specific dates
|
| 1064 |
date_picker = gr.DateTime(
|
| 1065 |
-
value=
|
| 1066 |
-
label="Jump to Date",
|
| 1067 |
-
info="
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
)
|
| 1071 |
|
| 1072 |
refresh_calendar_btn = gr.Button(
|
|
@@ -1076,6 +1078,14 @@ class UIComponents:
|
|
| 1076 |
scale=1
|
| 1077 |
)
|
| 1078 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1079 |
# Hidden component to track current date being displayed
|
| 1080 |
current_date = gr.Textbox(
|
| 1081 |
value=date.today().isoformat(),
|
|
@@ -1083,7 +1093,7 @@ class UIComponents:
|
|
| 1083 |
label="Current Display Date"
|
| 1084 |
)
|
| 1085 |
|
| 1086 |
-
return calendar_html, view_selector, prev_btn, next_btn, today_btn, date_picker, refresh_calendar_btn, current_date
|
| 1087 |
|
| 1088 |
@staticmethod
|
| 1089 |
def _get_initial_calendar_html() -> str:
|
|
|
|
| 2 |
UI components for the fitness app.
|
| 3 |
"""
|
| 4 |
import gradio as gr
|
| 5 |
+
import calendar
|
| 6 |
+
import re
|
| 7 |
from typing import List, Any
|
| 8 |
+
from datetime import date, datetime
|
| 9 |
|
| 10 |
from fitness_core.agents import FitnessAgent
|
| 11 |
from fitness_core.agents.providers import ModelProvider
|
|
|
|
| 1016 |
Create the training calendar section with navigation controls and view options.
|
| 1017 |
|
| 1018 |
Returns:
|
| 1019 |
+
Tuple of (calendar_html, view_selector, prev_btn, next_btn, today_btn, date_picker, refresh_calendar_btn, current_date, current_date_display)
|
| 1020 |
"""
|
| 1021 |
from datetime import date
|
| 1022 |
|
|
|
|
| 1062 |
scale=2
|
| 1063 |
)
|
| 1064 |
|
| 1065 |
+
# Date picker for jumping to specific dates - improved for better UX
|
| 1066 |
date_picker = gr.DateTime(
|
| 1067 |
+
value=datetime.now(), # Use datetime.now() which works correctly
|
| 1068 |
+
label="π
Jump to Date",
|
| 1069 |
+
info="Click the calendar icon to select a date",
|
| 1070 |
+
interactive=True,
|
| 1071 |
+
include_time=False # Only show date picker, not time
|
| 1072 |
)
|
| 1073 |
|
| 1074 |
refresh_calendar_btn = gr.Button(
|
|
|
|
| 1078 |
scale=1
|
| 1079 |
)
|
| 1080 |
|
| 1081 |
+
# Small row to show current viewing date in readable format
|
| 1082 |
+
with gr.Row():
|
| 1083 |
+
current_date_display = gr.Markdown(
|
| 1084 |
+
value=f"**Current View:** {date.today().strftime('%A, %B %d, %Y')}",
|
| 1085 |
+
elem_classes=["current-date-display"],
|
| 1086 |
+
visible=True
|
| 1087 |
+
)
|
| 1088 |
+
|
| 1089 |
# Hidden component to track current date being displayed
|
| 1090 |
current_date = gr.Textbox(
|
| 1091 |
value=date.today().isoformat(),
|
|
|
|
| 1093 |
label="Current Display Date"
|
| 1094 |
)
|
| 1095 |
|
| 1096 |
+
return calendar_html, view_selector, prev_btn, next_btn, today_btn, date_picker, refresh_calendar_btn, current_date, current_date_display
|
| 1097 |
|
| 1098 |
@staticmethod
|
| 1099 |
def _get_initial_calendar_html() -> str:
|
apps/gradio-app/src/fitness_gradio/ui/handlers.py
CHANGED
|
@@ -857,10 +857,11 @@ Please check your API keys and try a different model."""
|
|
| 857 |
direction: Navigation direction ("prev" or "next")
|
| 858 |
|
| 859 |
Returns:
|
| 860 |
-
Tuple of (updated_calendar_html, new_current_date)
|
| 861 |
"""
|
| 862 |
try:
|
| 863 |
from .components import UIComponents
|
|
|
|
| 864 |
|
| 865 |
# Calculate new date
|
| 866 |
new_date_str = UIComponents.calculate_navigation_date(current_date_str, view_type, direction)
|
|
@@ -868,13 +869,34 @@ Please check your API keys and try a different model."""
|
|
| 868 |
# Refresh calendar with new date
|
| 869 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, new_date_str)
|
| 870 |
|
| 871 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
|
| 873 |
except Exception as e:
|
| 874 |
logger.error(f"Error navigating calendar: {str(e)}")
|
| 875 |
# Return current state on error
|
|
|
|
| 876 |
current_calendar = UIHandlers.refresh_calendar_with_date(view_type, current_date_str)
|
| 877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
|
| 879 |
@staticmethod
|
| 880 |
def go_to_today(view_type: str) -> tuple:
|
|
@@ -885,63 +907,177 @@ Please check your API keys and try a different model."""
|
|
| 885 |
view_type: Type of calendar view to display
|
| 886 |
|
| 887 |
Returns:
|
| 888 |
-
Tuple of (updated_calendar_html, today_date_str)
|
| 889 |
"""
|
| 890 |
try:
|
| 891 |
-
from datetime import date
|
| 892 |
|
| 893 |
-
|
|
|
|
| 894 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, today_str)
|
| 895 |
|
| 896 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
|
| 898 |
except Exception as e:
|
|
|
|
| 899 |
logger.error(f"Error going to today: {str(e)}")
|
| 900 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
|
| 902 |
@staticmethod
|
| 903 |
def jump_to_date(date_picker_value, view_type: str) -> tuple:
|
| 904 |
"""
|
| 905 |
Jump the calendar to a specific date from the date picker.
|
|
|
|
| 906 |
|
| 907 |
Args:
|
| 908 |
date_picker_value: Date value from the date picker component
|
| 909 |
view_type: Type of calendar view to display
|
| 910 |
|
| 911 |
Returns:
|
| 912 |
-
Tuple of (updated_calendar_html, target_date_str)
|
| 913 |
"""
|
| 914 |
try:
|
| 915 |
from datetime import datetime, date
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 916 |
|
| 917 |
-
# Handle
|
| 918 |
-
if
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 922 |
else:
|
| 923 |
-
# ISO date
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
|
|
|
|
|
|
| 928 |
elif isinstance(date_picker_value, date):
|
| 929 |
-
#
|
|
|
|
| 930 |
target_date = date_picker_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
else:
|
| 932 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 933 |
target_date = date.today()
|
| 934 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
target_date_str = target_date.isoformat()
|
|
|
|
|
|
|
| 936 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, target_date_str)
|
| 937 |
|
| 938 |
-
|
| 939 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 940 |
|
| 941 |
except Exception as e:
|
| 942 |
-
logger.error(f"Error jumping to date: {str(e)}")
|
| 943 |
-
|
| 944 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 945 |
|
| 946 |
@staticmethod
|
| 947 |
def handle_voice_input(
|
|
|
|
| 857 |
direction: Navigation direction ("prev" or "next")
|
| 858 |
|
| 859 |
Returns:
|
| 860 |
+
Tuple of (updated_calendar_html, new_current_date, current_date_display, date_picker_value)
|
| 861 |
"""
|
| 862 |
try:
|
| 863 |
from .components import UIComponents
|
| 864 |
+
from datetime import datetime
|
| 865 |
|
| 866 |
# Calculate new date
|
| 867 |
new_date_str = UIComponents.calculate_navigation_date(current_date_str, view_type, direction)
|
|
|
|
| 869 |
# Refresh calendar with new date
|
| 870 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, new_date_str)
|
| 871 |
|
| 872 |
+
# Format date for display
|
| 873 |
+
new_date = datetime.fromisoformat(new_date_str).date()
|
| 874 |
+
formatted_date = new_date.strftime('%A, %B %d, %Y')
|
| 875 |
+
current_date_display = f"**Current View:** {formatted_date}"
|
| 876 |
+
|
| 877 |
+
# Convert to timestamp for DateTime component
|
| 878 |
+
new_datetime = datetime.combine(new_date, datetime.min.time())
|
| 879 |
+
new_timestamp = new_datetime.timestamp()
|
| 880 |
+
|
| 881 |
+
return calendar_html, new_date_str, current_date_display, new_timestamp
|
| 882 |
|
| 883 |
except Exception as e:
|
| 884 |
logger.error(f"Error navigating calendar: {str(e)}")
|
| 885 |
# Return current state on error
|
| 886 |
+
from datetime import date
|
| 887 |
current_calendar = UIHandlers.refresh_calendar_with_date(view_type, current_date_str)
|
| 888 |
+
try:
|
| 889 |
+
current_date = datetime.fromisoformat(current_date_str).date()
|
| 890 |
+
formatted_date = current_date.strftime('%A, %B %d, %Y')
|
| 891 |
+
current_date_display = f"**Current View:** {formatted_date}"
|
| 892 |
+
current_datetime = datetime.combine(current_date, datetime.min.time())
|
| 893 |
+
current_timestamp = current_datetime.timestamp()
|
| 894 |
+
except:
|
| 895 |
+
current_date = datetime.fromisoformat(current_date_str).date() if current_date_str else date.today()
|
| 896 |
+
current_date_display = f"**Current View:** {current_date_str}"
|
| 897 |
+
current_datetime = datetime.combine(current_date, datetime.min.time())
|
| 898 |
+
current_timestamp = current_datetime.timestamp()
|
| 899 |
+
return current_calendar, current_date_str, current_date_display, current_timestamp
|
| 900 |
|
| 901 |
@staticmethod
|
| 902 |
def go_to_today(view_type: str) -> tuple:
|
|
|
|
| 907 |
view_type: Type of calendar view to display
|
| 908 |
|
| 909 |
Returns:
|
| 910 |
+
Tuple of (updated_calendar_html, today_date_str, current_date_display, date_picker_value)
|
| 911 |
"""
|
| 912 |
try:
|
| 913 |
+
from datetime import date, datetime
|
| 914 |
|
| 915 |
+
today = date.today()
|
| 916 |
+
today_str = today.isoformat()
|
| 917 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, today_str)
|
| 918 |
|
| 919 |
+
# Format today's date for display
|
| 920 |
+
today_formatted = today.strftime('%A, %B %d, %Y')
|
| 921 |
+
current_date_display = f"**Current View:** {today_formatted}"
|
| 922 |
+
|
| 923 |
+
# Convert to timestamp for DateTime component
|
| 924 |
+
today_datetime = datetime.combine(today, datetime.min.time())
|
| 925 |
+
today_timestamp = today_datetime.timestamp()
|
| 926 |
+
|
| 927 |
+
return calendar_html, today_str, current_date_display, today_timestamp
|
| 928 |
|
| 929 |
except Exception as e:
|
| 930 |
+
from datetime import date, datetime
|
| 931 |
logger.error(f"Error going to today: {str(e)}")
|
| 932 |
+
today = date.today()
|
| 933 |
+
today_str = today.isoformat()
|
| 934 |
+
today_formatted = today.strftime('%A, %B %d, %Y')
|
| 935 |
+
today_datetime = datetime.combine(today, datetime.min.time())
|
| 936 |
+
today_timestamp = today_datetime.timestamp()
|
| 937 |
+
return UIHandlers.refresh_calendar(view_type), today_str, f"**Current View:** {today_formatted}", today_timestamp
|
| 938 |
|
| 939 |
@staticmethod
|
| 940 |
def jump_to_date(date_picker_value, view_type: str) -> tuple:
|
| 941 |
"""
|
| 942 |
Jump the calendar to a specific date from the date picker.
|
| 943 |
+
Supports both manual text input and calendar selection.
|
| 944 |
|
| 945 |
Args:
|
| 946 |
date_picker_value: Date value from the date picker component
|
| 947 |
view_type: Type of calendar view to display
|
| 948 |
|
| 949 |
Returns:
|
| 950 |
+
Tuple of (updated_calendar_html, target_date_str, current_date_display, date_picker_value)
|
| 951 |
"""
|
| 952 |
try:
|
| 953 |
from datetime import datetime, date
|
| 954 |
+
import re
|
| 955 |
+
|
| 956 |
+
# Debug logging to understand the input format
|
| 957 |
+
logger.info(f"jump_to_date received: {date_picker_value} (type: {type(date_picker_value)})")
|
| 958 |
+
|
| 959 |
+
target_date = None
|
| 960 |
|
| 961 |
+
# Handle None or empty values
|
| 962 |
+
if date_picker_value is None:
|
| 963 |
+
logger.info("Received None value, using today")
|
| 964 |
+
target_date = date.today()
|
| 965 |
+
elif isinstance(date_picker_value, (int, float)):
|
| 966 |
+
# Unix timestamp (most common case for gr.DateTime)
|
| 967 |
+
logger.info(f"Processing Unix timestamp: {date_picker_value}")
|
| 968 |
+
try:
|
| 969 |
+
target_date = datetime.fromtimestamp(date_picker_value).date()
|
| 970 |
+
except (ValueError, OSError) as e:
|
| 971 |
+
logger.warning(f"Failed to parse timestamp {date_picker_value}: {e}")
|
| 972 |
+
target_date = date.today()
|
| 973 |
+
elif isinstance(date_picker_value, str):
|
| 974 |
+
# String input - handle various formats
|
| 975 |
+
date_str = date_picker_value.strip()
|
| 976 |
+
logger.info(f"Processing string date: '{date_str}'")
|
| 977 |
+
|
| 978 |
+
if not date_str:
|
| 979 |
+
logger.warning("Empty date string received")
|
| 980 |
+
target_date = date.today()
|
| 981 |
+
elif 'T' in date_str:
|
| 982 |
+
# ISO datetime format (2024-12-25T00:00:00)
|
| 983 |
+
logger.info("Parsing as ISO datetime format")
|
| 984 |
+
target_date = datetime.fromisoformat(date_str.split('T')[0]).date()
|
| 985 |
+
elif re.match(r'^\d{4}-\d{2}-\d{2}$', date_str):
|
| 986 |
+
# ISO date format (2024-12-25) - most common from gr.DateTime
|
| 987 |
+
logger.info("Parsing as ISO date format")
|
| 988 |
+
target_date = datetime.fromisoformat(date_str).date()
|
| 989 |
+
elif re.match(r'^\d{1,2}/\d{1,2}/\d{4}$', date_str):
|
| 990 |
+
# US date format (12/25/2024)
|
| 991 |
+
logger.info("Parsing as US date format")
|
| 992 |
+
month, day, year = map(int, date_str.split('/'))
|
| 993 |
+
target_date = date(year, month, day)
|
| 994 |
+
elif re.match(r'^\d{1,2}-\d{1,2}-\d{4}$', date_str):
|
| 995 |
+
# Alternative date format (12-25-2024)
|
| 996 |
+
logger.info("Parsing as alternative date format")
|
| 997 |
+
month, day, year = map(int, date_str.split('-'))
|
| 998 |
+
target_date = date(year, month, day)
|
| 999 |
else:
|
| 1000 |
+
# Try to parse as ISO date anyway
|
| 1001 |
+
logger.info("Attempting ISO date parsing as fallback")
|
| 1002 |
+
try:
|
| 1003 |
+
target_date = datetime.fromisoformat(date_str).date()
|
| 1004 |
+
except ValueError as ve:
|
| 1005 |
+
logger.warning(f"Failed to parse date string '{date_str}': {ve}")
|
| 1006 |
+
target_date = None
|
| 1007 |
elif isinstance(date_picker_value, date):
|
| 1008 |
+
# Direct date object
|
| 1009 |
+
logger.info("Processing as date object")
|
| 1010 |
target_date = date_picker_value
|
| 1011 |
+
elif isinstance(date_picker_value, datetime):
|
| 1012 |
+
# DateTime object - extract date part
|
| 1013 |
+
logger.info("Processing as datetime object")
|
| 1014 |
+
target_date = date_picker_value.date()
|
| 1015 |
+
elif hasattr(date_picker_value, 'year') and hasattr(date_picker_value, 'month') and hasattr(date_picker_value, 'day'):
|
| 1016 |
+
# Date-like object with year, month, day attributes
|
| 1017 |
+
logger.info("Processing as date-like object with year/month/day attributes")
|
| 1018 |
+
target_date = date(date_picker_value.year, date_picker_value.month, date_picker_value.day)
|
| 1019 |
+
elif hasattr(date_picker_value, 'date') and callable(getattr(date_picker_value, 'date')):
|
| 1020 |
+
# DateTime object with date() method
|
| 1021 |
+
logger.info("Processing as datetime object with date() method")
|
| 1022 |
+
target_date = date_picker_value.date()
|
| 1023 |
else:
|
| 1024 |
+
logger.warning(f"Unrecognized date picker value format: {date_picker_value} (type: {type(date_picker_value)})")
|
| 1025 |
+
# Check if it has attributes we can inspect
|
| 1026 |
+
if hasattr(date_picker_value, '__dict__'):
|
| 1027 |
+
logger.info(f"Object attributes: {date_picker_value.__dict__}")
|
| 1028 |
+
# Try to convert to string and parse
|
| 1029 |
+
try:
|
| 1030 |
+
str_value = str(date_picker_value)
|
| 1031 |
+
logger.info(f"Attempting to parse string representation: '{str_value}'")
|
| 1032 |
+
if re.match(r'^\d{4}-\d{2}-\d{2}', str_value):
|
| 1033 |
+
target_date = datetime.fromisoformat(str_value.split()[0]).date()
|
| 1034 |
+
else:
|
| 1035 |
+
target_date = None
|
| 1036 |
+
except Exception as conv_error:
|
| 1037 |
+
logger.warning(f"Failed to convert to string: {conv_error}")
|
| 1038 |
+
target_date = None
|
| 1039 |
+
|
| 1040 |
+
# If we couldn't parse the date, use today as fallback
|
| 1041 |
+
if target_date is None:
|
| 1042 |
+
logger.warning(f"Could not parse date picker value: {date_picker_value} (type: {type(date_picker_value)}), using today")
|
| 1043 |
target_date = date.today()
|
| 1044 |
|
| 1045 |
+
logger.info(f"Parsed target_date: {target_date}")
|
| 1046 |
+
|
| 1047 |
+
# Validate the date is reasonable (not too far in past/future)
|
| 1048 |
+
today = date.today()
|
| 1049 |
+
min_date = date(today.year - 10, 1, 1) # 10 years ago
|
| 1050 |
+
max_date = date(today.year + 10, 12, 31) # 10 years from now
|
| 1051 |
+
|
| 1052 |
+
if target_date < min_date or target_date > max_date:
|
| 1053 |
+
logger.warning(f"Date {target_date} is outside reasonable range, using today")
|
| 1054 |
+
target_date = today
|
| 1055 |
+
|
| 1056 |
target_date_str = target_date.isoformat()
|
| 1057 |
+
logger.info(f"Final target_date_str: {target_date_str}")
|
| 1058 |
+
|
| 1059 |
calendar_html = UIHandlers.refresh_calendar_with_date(view_type, target_date_str)
|
| 1060 |
|
| 1061 |
+
# Format date for display
|
| 1062 |
+
formatted_date = target_date.strftime('%A, %B %d, %Y')
|
| 1063 |
+
current_date_display = f"**Current View:** {formatted_date}"
|
| 1064 |
+
|
| 1065 |
+
logger.info(f"Successfully jumped to date: {target_date_str} (view: {view_type})")
|
| 1066 |
+
|
| 1067 |
+
# Convert target_date back to timestamp for the DateTime component
|
| 1068 |
+
target_datetime = datetime.combine(target_date, datetime.min.time())
|
| 1069 |
+
target_timestamp = target_datetime.timestamp()
|
| 1070 |
+
|
| 1071 |
+
return calendar_html, target_date_str, current_date_display, target_timestamp
|
| 1072 |
|
| 1073 |
except Exception as e:
|
| 1074 |
+
logger.error(f"Error jumping to date with value '{date_picker_value}': {str(e)}", exc_info=True)
|
| 1075 |
+
today = date.today()
|
| 1076 |
+
today_str = today.isoformat()
|
| 1077 |
+
today_formatted = today.strftime('%A, %B %d, %Y')
|
| 1078 |
+
today_datetime = datetime.combine(today, datetime.min.time())
|
| 1079 |
+
today_timestamp = today_datetime.timestamp()
|
| 1080 |
+
return UIHandlers.refresh_calendar(view_type), today_str, f"**Current View:** {today_formatted}", today_timestamp
|
| 1081 |
|
| 1082 |
@staticmethod
|
| 1083 |
def handle_voice_input(
|
apps/gradio-app/src/fitness_gradio/ui/styles.py
CHANGED
|
@@ -267,6 +267,32 @@ MAIN_CSS = """
|
|
| 267 |
max-width: 200px !important;
|
| 268 |
}
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
/* Month View Styles */
|
| 271 |
.calendar-grid.month-view {
|
| 272 |
display: flex;
|
|
|
|
| 267 |
max-width: 200px !important;
|
| 268 |
}
|
| 269 |
|
| 270 |
+
/* Current date display styles */
|
| 271 |
+
.current-date-display {
|
| 272 |
+
margin: 10px 0 !important;
|
| 273 |
+
padding: 8px 12px !important;
|
| 274 |
+
background-color: rgba(59, 130, 246, 0.1) !important;
|
| 275 |
+
border: 1px solid rgba(59, 130, 246, 0.3) !important;
|
| 276 |
+
border-radius: 6px !important;
|
| 277 |
+
text-align: center !important;
|
| 278 |
+
font-size: 14px !important;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.current-date-display p {
|
| 282 |
+
margin: 0 !important;
|
| 283 |
+
color: #1e40af !important;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/* Dark theme support for current date display */
|
| 287 |
+
.dark .current-date-display {
|
| 288 |
+
background-color: rgba(59, 130, 246, 0.2) !important;
|
| 289 |
+
border-color: rgba(59, 130, 246, 0.4) !important;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
.dark .current-date-display p {
|
| 293 |
+
color: #93c5fd !important;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
/* Month View Styles */
|
| 297 |
.calendar-grid.month-view {
|
| 298 |
display: flex;
|
apps/gradio-app/test_datetime.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to understand how Gradio DateTime component works
|
| 4 |
+
"""
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from datetime import date, datetime
|
| 7 |
+
|
| 8 |
+
def test_date_input(date_value):
|
| 9 |
+
"""Test function to see what format gr.DateTime passes to the function"""
|
| 10 |
+
print(f"Received: {date_value}")
|
| 11 |
+
print(f"Type: {type(date_value)}")
|
| 12 |
+
if hasattr(date_value, '__dict__'):
|
| 13 |
+
print(f"Attributes: {date_value.__dict__}")
|
| 14 |
+
|
| 15 |
+
# Try to convert to string representation
|
| 16 |
+
str_repr = str(date_value)
|
| 17 |
+
print(f"String representation: '{str_repr}'")
|
| 18 |
+
|
| 19 |
+
return f"Input: {date_value} (type: {type(date_value).__name__})\nString: {str_repr}"
|
| 20 |
+
|
| 21 |
+
# Create a simple interface to test the DateTime component
|
| 22 |
+
demo = gr.Interface(
|
| 23 |
+
fn=test_date_input,
|
| 24 |
+
inputs=gr.DateTime(
|
| 25 |
+
value=datetime.now(), # Use datetime.now() instead of date.today()
|
| 26 |
+
label="Test DateTime Picker",
|
| 27 |
+
include_time=False
|
| 28 |
+
),
|
| 29 |
+
outputs=gr.Textbox(label="Result"),
|
| 30 |
+
title="DateTime Component Test"
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
demo.launch(server_name="0.0.0.0", server_port=7861, debug=True)
|
apps/gradio-app/test_datetime_simple.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple test to understand Gradio DateTime component behavior
|
| 4 |
+
"""
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from datetime import date, datetime
|
| 7 |
+
|
| 8 |
+
def test_datetime_component():
|
| 9 |
+
"""Test DateTime component creation and behavior"""
|
| 10 |
+
print("Testing Gradio DateTime component...")
|
| 11 |
+
|
| 12 |
+
# Test 1: Create with datetime.now()
|
| 13 |
+
try:
|
| 14 |
+
dt1 = gr.DateTime(
|
| 15 |
+
value=datetime.now(),
|
| 16 |
+
include_time=False,
|
| 17 |
+
label="Test 1"
|
| 18 |
+
)
|
| 19 |
+
print(f"β Created DateTime with datetime.now(): {dt1.value}")
|
| 20 |
+
except Exception as e:
|
| 21 |
+
print(f"β Failed to create DateTime with datetime.now(): {e}")
|
| 22 |
+
|
| 23 |
+
# Test 2: Create with date.today()
|
| 24 |
+
try:
|
| 25 |
+
dt2 = gr.DateTime(
|
| 26 |
+
value=date.today(),
|
| 27 |
+
include_time=False,
|
| 28 |
+
label="Test 2"
|
| 29 |
+
)
|
| 30 |
+
print(f"β Created DateTime with date.today(): {dt2.value}")
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"β Failed to create DateTime with date.today(): {e}")
|
| 33 |
+
|
| 34 |
+
# Test 3: Create with ISO string
|
| 35 |
+
try:
|
| 36 |
+
dt3 = gr.DateTime(
|
| 37 |
+
value="2025-08-15",
|
| 38 |
+
include_time=False,
|
| 39 |
+
label="Test 3"
|
| 40 |
+
)
|
| 41 |
+
print(f"β Created DateTime with ISO string: {dt3.value}")
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"β Failed to create DateTime with ISO string: {e}")
|
| 44 |
+
|
| 45 |
+
# Test 4: Create with no value
|
| 46 |
+
try:
|
| 47 |
+
dt4 = gr.DateTime(
|
| 48 |
+
include_time=False,
|
| 49 |
+
label="Test 4"
|
| 50 |
+
)
|
| 51 |
+
print(f"β Created DateTime with no value: {dt4.value}")
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"β Failed to create DateTime with no value: {e}")
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
test_datetime_component()
|
test_datetime.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to understand how Gradio Date component works
|
| 4 |
+
"""
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from datetime import date, datetime
|
| 7 |
+
|
| 8 |
+
def test_date_input(date_value):
|
| 9 |
+
"""Test function to see what format gr.Date passes to the function"""
|
| 10 |
+
print(f"Received: {date_value}")
|
| 11 |
+
print(f"Type: {type(date_value)}")
|
| 12 |
+
if hasattr(date_value, '__dict__'):
|
| 13 |
+
print(f"Attributes: {date_value.__dict__}")
|
| 14 |
+
|
| 15 |
+
# Try to convert to string representation
|
| 16 |
+
str_repr = str(date_value)
|
| 17 |
+
print(f"String representation: '{str_repr}'")
|
| 18 |
+
|
| 19 |
+
return f"Input: {date_value} (type: {type(date_value).__name__})\nString: {str_repr}"
|
| 20 |
+
|
| 21 |
+
# Create a simple interface to test the Date component
|
| 22 |
+
demo = gr.Interface(
|
| 23 |
+
fn=test_date_input,
|
| 24 |
+
inputs=gr.DateTime(
|
| 25 |
+
value=datetime.now(), # Use datetime.now() instead of date.today()
|
| 26 |
+
label="Test DateTime Picker",
|
| 27 |
+
include_time=False
|
| 28 |
+
),
|
| 29 |
+
outputs=gr.Textbox(label="Result"),
|
| 30 |
+
title="DateTime Component Test"
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
demo.launch(server_name="0.0.0.0", server_port=7861, debug=True)
|