From e84ab5c29e94f3f435c5293d1a9602adc408d36d Mon Sep 17 00:00:00 2001 From: Ravi Date: Mon, 28 Jul 2025 17:45:53 +0530 Subject: [PATCH 1/7] date issue fix --- src/backend/app_config.py | 16 +++ src/backend/app_kernel.py | 36 +++++- src/backend/kernel_tools/hr_tools.py | 10 +- src/backend/models/messages_kernel.py | 3 + src/backend/requirements.txt | 3 + src/backend/test_utils_date_fixed.py | 52 +++++++++ src/backend/tests/test_utils_date_enhanced.py | 0 src/backend/utils_date.py | 107 +++++++++++++++++- src/frontend/src/api/apiClient.tsx | 6 - src/frontend/src/api/apiService.tsx | 15 ++- src/frontend/src/api/config.tsx | 4 - .../src/components/content/HomeInput.tsx | 3 +- src/frontend/src/index.tsx | 6 +- src/frontend/src/pages/PlanPage.tsx | 2 - 14 files changed, 235 insertions(+), 28 deletions(-) create mode 100644 src/backend/test_utils_date_fixed.py create mode 100644 src/backend/tests/test_utils_date_enhanced.py diff --git a/src/backend/app_config.py b/src/backend/app_config.py index d4b1a9e9a..ab5995903 100644 --- a/src/backend/app_config.py +++ b/src/backend/app_config.py @@ -182,6 +182,22 @@ def get_ai_project_client(self): except Exception as exc: logging.error("Failed to create AIProjectClient: %s", exc) raise + + def get_user_local_browser_language(self) -> str: + """Get the user's local browser language from environment variables. + + Returns: + The user's local browser language or 'en-US' if not set + """ + return self._get_optional("USER_LOCAL_BROWSER_LANGUAGE", "en-US") + + def set_user_local_browser_language(self, language: str): + """Set the user's local browser language in environment variables. + + Args: + language: The language code to set (e.g., 'en-US') + """ + os.environ["USER_LOCAL_BROWSER_LANGUAGE"] = language # Create a global instance of AppConfig diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 855c06f79..615d61156 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -29,6 +29,7 @@ InputTask, PlanWithSteps, Step, + UserLanguage ) # Updated import for KernelArguments @@ -70,7 +71,7 @@ # Add this near the top of your app.py, after initializing the app app.add_middleware( CORSMiddleware, - allow_origins=[frontend_url], + allow_origins=['*'], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -80,6 +81,39 @@ app.add_middleware(HealthCheckMiddleware, password="", checks={}) logging.info("Added health check middleware") +@app.post("/api/user_browser_language") +async def user_browser_language_endpoint( + user_language: UserLanguage, + request: Request +): + """ + Receive the user's browser language. + + --- + tags: + - User + parameters: + - name: language + in: query + type: string + required: true + description: The user's browser language + responses: + 200: + description: Language received successfully + schema: + type: object + properties: + status: + type: string + description: Confirmation message + """ + config.set_user_local_browser_language(user_language.language) + + # Log the received language for the user + logging.info(f"Received browser language '{user_language}' for user ") + + return {"status": "Language received successfully"} @app.post("/api/input_task") async def input_task_endpoint(input_task: InputTask, request: Request): diff --git a/src/backend/kernel_tools/hr_tools.py b/src/backend/kernel_tools/hr_tools.py index 9951c0a1a..253a337e0 100644 --- a/src/backend/kernel_tools/hr_tools.py +++ b/src/backend/kernel_tools/hr_tools.py @@ -6,25 +6,27 @@ import json from typing import get_type_hints from utils_date import format_date_for_user +from app_config import config class HrTools: # Define HR tools (functions) - formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did." + selecetd_language= config.get_user_local_browser_language() + formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones" agent_name = AgentType.HR.value @staticmethod @kernel_function(description="Schedule an orientation session for a new employee.") async def schedule_orientation_session(employee_name: str, date: str) -> str: - formatted_date = format_date_for_user(date) + #formatted_date = format_date_for_user(date) return ( f"##### Orientation Session Scheduled\n" f"**Employee Name:** {employee_name}\n" - f"**Date:** {formatted_date}\n\n" + f"**Date:** {date}\n\n" f"Your orientation session has been successfully scheduled. " f"Please mark your calendar and be prepared for an informative session.\n" - f"AGENT SUMMARY: I scheduled the orientation session for {employee_name} on {formatted_date}, as part of her onboarding process.\n" + #f"AGENT SUMMARY: I scheduled the orientation session for {employee_name} on {formatted_date}, as part of her onboarding process.\n" f"{HrTools.formatting_instructions}" ) diff --git a/src/backend/models/messages_kernel.py b/src/backend/models/messages_kernel.py index ac10f8e25..df9de5b8d 100644 --- a/src/backend/models/messages_kernel.py +++ b/src/backend/models/messages_kernel.py @@ -262,6 +262,9 @@ class InputTask(KernelBaseModel): session_id: str description: str # Initial goal + +class UserLanguage(KernelBaseModel): + language: str class ApprovalRequest(KernelBaseModel): diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 5cac25b2f..872e5b154 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -23,6 +23,9 @@ azure-ai-evaluation opentelemetry-exporter-otlp-proto-grpc +# Date and internationalization +babel>=2.9.0 + # Testing tools pytest>=8.2,<9 # Compatible version for pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/backend/test_utils_date_fixed.py b/src/backend/test_utils_date_fixed.py new file mode 100644 index 000000000..472081876 --- /dev/null +++ b/src/backend/test_utils_date_fixed.py @@ -0,0 +1,52 @@ +""" +Quick test for the fixed utils_date.py functionality +""" + +import os +from datetime import datetime +from utils_date import format_date_for_user + +def test_date_formatting(): + """Test the date formatting function with various inputs""" + + # Set up different language environments + test_cases = [ + ('en-US', '2025-07-29', 'US English'), + ('en-IN', '2025-07-29', 'Indian English'), + ('en-GB', '2025-07-29', 'British English'), + ('fr-FR', '2025-07-29', 'French'), + ('de-DE', '2025-07-29', 'German'), + ] + + print("Testing date formatting with different locales:") + print("=" * 50) + + for locale, date_str, description in test_cases: + os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = locale + try: + result = format_date_for_user(date_str) + print(f"{description} ({locale}): {result}") + except Exception as e: + print(f"{description} ({locale}): ERROR - {e}") + + print("\n" + "=" * 50) + print("Testing with datetime object:") + + # Test with datetime object + os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = 'en-US' + dt = datetime(2025, 7, 29, 14, 30, 0) + result = format_date_for_user(dt) + print(f"Datetime object: {result}") + + print("\nTesting error handling:") + print("=" * 30) + + # Test error handling + try: + result = format_date_for_user('invalid-date-string') + print(f"Invalid date: {result}") + except Exception as e: + print(f"Invalid date: ERROR - {e}") + +if __name__ == "__main__": + test_date_formatting() diff --git a/src/backend/tests/test_utils_date_enhanced.py b/src/backend/tests/test_utils_date_enhanced.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/utils_date.py b/src/backend/utils_date.py index d346e3cd0..591101baf 100644 --- a/src/backend/utils_date.py +++ b/src/backend/utils_date.py @@ -3,22 +3,117 @@ import logging from typing import Optional +from app_config import config + +# Try to import babel, with fallback if not available +try: + from babel.dates import format_datetime + BABEL_AVAILABLE = True +except ImportError: + BABEL_AVAILABLE = False + logging.warning("Babel library not available. Date formatting will use basic Python formatting.") + + +def _format_date_fallback(date_obj: datetime, language_code: str) -> str: + """ + Fallback date formatting when babel is not available. + + Args: + date_obj (datetime): The datetime object to format + language_code (str): Language code like 'en-US', 'en-IN', etc. + + Returns: + str: Formatted date string + """ + # Normalize the language code + normalized_code = language_code.replace('-', '_') + + # Define basic date formats for different locales + locale_date_formats = { + 'en_IN': '%d %B, %Y', # 29 July, 2025 + 'en_US': '%B %d, %Y', # July 29, 2025 + 'en_GB': '%d %B %Y', # 29 July 2025 + 'en_AU': '%d %B %Y', # 29 July 2025 + 'en_CA': '%B %d, %Y', # July 29, 2025 + 'es_ES': '%d de %B de %Y', # Would need Spanish month names + 'fr_FR': '%d %B %Y', # Would need French month names + 'de_DE': '%d. %B %Y', # Would need German month names + 'ja_JP': '%Y年%m月%d日', # 2025年07月29日 + 'ko_KR': '%Y년 %m월 %d일', # 2025년 07월 29일 + 'zh_CN': '%Y年%m月%d日', # 2025年07月29日 + } + + # Get the format for the locale, default to US format + date_format = locale_date_formats.get(normalized_code, '%B %d, %Y') + + try: + return date_obj.strftime(date_format) + except Exception as e: + logging.warning("Fallback date formatting failed: %s", str(e)) + return date_obj.strftime('%Y-%m-%d') # ISO format as last resort + def format_date_for_user(date_str: str, user_locale: Optional[str] = None) -> str: """ Format date based on user's desktop locale preference. Args: - date_str (str): Date in ISO format (YYYY-MM-DD). + date_str (str): Date in ISO format (YYYY-MM-DD) or datetime object. user_locale (str, optional): User's locale string, e.g., 'en_US', 'en_GB'. Returns: str: Formatted date respecting locale or raw date if formatting fails. """ try: - date_obj = datetime.strptime(date_str, "%Y-%m-%d") - locale.setlocale(locale.LC_TIME, user_locale or '') - return date_obj.strftime("%B %d, %Y") + # Get user's browser language from config + lang = config.get_user_local_browser_language() # e.g., 'en-US', 'fr-FR', 'de-DE' + + # Parse the date string if it's a string, otherwise use as-is if it's already a datetime + if isinstance(date_str, str): + # Try different date formats + date_formats = [ + "%Y-%m-%d", # 2025-07-29 + "%Y-%m-%d", # 2025-07-29 14:30:00 + "%Y-%m-%d", # 2025-07-29T14:30:00 + "%d/%m/%Y", # 29/07/2025 + "%m/%d/%Y", # 07/29/2025 + ] + + parsed_date = None + for date_format in date_formats: + try: + parsed_date = datetime.strptime(date_str, date_format) + break + except ValueError: + continue + + if parsed_date is None: + logging.warning("Could not parse date string: %s", date_str) + return date_str # Return original string if parsing fails + + date_to_format = parsed_date + else: + # Assume it's already a datetime object + date_to_format = date_str + + # Format the date using babel with the user's locale, or fallback to basic formatting + if BABEL_AVAILABLE: + try: + # Babel expects locale in format like 'en_US', not 'en-US' + babel_locale = lang.replace('-', '_') + formatted_date = format_datetime(date_to_format, locale=babel_locale) + except Exception as e: + logging.warning("Babel formatting failed: %s. Using fallback formatting.", str(e)) + formatted_date = _format_date_fallback(date_to_format, lang) + else: + formatted_date = _format_date_fallback(date_to_format, lang) + + print( + f"Formatted date for user ######################### : {formatted_date} using locale: {lang},browser lang {config.get_user_local_browser_language()}, locale: {babel_locale}" + ) + return formatted_date + except Exception as e: - logging.warning(f"Date formatting failed for '{date_str}': {e}") - return date_str + logging.error("Error formatting date '%s': %s", date_str, str(e)) + # Return the original input if anything goes wrong + return str(date_str) \ No newline at end of file diff --git a/src/frontend/src/api/apiClient.tsx b/src/frontend/src/api/apiClient.tsx index 8d574fb18..88bc4d606 100644 --- a/src/frontend/src/api/apiClient.tsx +++ b/src/frontend/src/api/apiClient.tsx @@ -45,11 +45,8 @@ const fetchWithAuth = async (url: string, method: string = "GET", body: BodyInit try { const apiUrl = getApiUrl(); const finalUrl = `${apiUrl}${url}`; - console.log('Final URL:', finalUrl); - console.log('Request Options:', options); // Log the request details const response = await fetch(finalUrl, options); - console.log('response', response); if (!response.ok) { const errorText = await response.text(); @@ -58,8 +55,6 @@ const fetchWithAuth = async (url: string, method: string = "GET", body: BodyInit const isJson = response.headers.get('content-type')?.includes('application/json'); const responseData = isJson ? await response.json() : null; - - console.log('Response JSON:', responseData); return responseData; } catch (error) { console.info('API Error:', (error as Error).message); @@ -87,7 +82,6 @@ const fetchWithoutAuth = async (url: string, method: string = "POST", body: Body const errorText = await response.text(); throw new Error(errorText || 'Login failed'); } - console.log('response', response); const isJson = response.headers.get('content-type')?.includes('application/json'); return isJson ? await response.json() : null; } catch (error) { diff --git a/src/frontend/src/api/apiService.tsx b/src/frontend/src/api/apiService.tsx index 1b11ab621..27f35b065 100644 --- a/src/frontend/src/api/apiService.tsx +++ b/src/frontend/src/api/apiService.tsx @@ -21,7 +21,8 @@ const API_ENDPOINTS = { APPROVE_STEPS: '/approve_step_or_steps', HUMAN_CLARIFICATION: '/human_clarification_on_plan', AGENT_MESSAGES: '/agent_messages', - MESSAGES: '/messages' + MESSAGES: '/messages', + USER_BROWSER_LANGUAGE: '/user_browser_language' }; // Simple cache implementation @@ -500,6 +501,18 @@ export class APIService { return Math.round((completedSteps / plan.steps.length) * 100); } + + /** + * Send the user's browser language to the backend + * @returns Promise with response object + */ + async sendUserBrowserLanguage(): Promise<{ status: string }> { + const language = navigator.language || navigator.languages[0] || 'en'; + const response = await apiClient.post(API_ENDPOINTS.USER_BROWSER_LANGUAGE, { + language + }); + return response; + } } // Export a singleton instance diff --git a/src/frontend/src/api/config.tsx b/src/frontend/src/api/config.tsx index bf99d97f7..5c8fa23e6 100644 --- a/src/frontend/src/api/config.tsx +++ b/src/frontend/src/api/config.tsx @@ -51,8 +51,6 @@ export function getConfigData() { export async function getUserInfo(): Promise { try { const response = await fetch("/.auth/me"); - console.log("Fetching user info from: ", "/.auth/me"); - console.log("Response ", response); if (!response.ok) { console.log( "No identity provider found. Access to chat will be blocked." @@ -60,7 +58,6 @@ export async function getUserInfo(): Promise { return {} as UserInfo; } const payload = await response.json(); - console.log("User info payload: ", payload[0]); const userInfo: UserInfo = { access_token: payload[0].access_token || "", expires_on: payload[0].expires_on || "", @@ -71,7 +68,6 @@ export async function getUserInfo(): Promise { user_first_last_name: payload[0].user_claims?.find((claim: claim) => claim.typ === 'name')?.val || "", user_id: payload[0].user_claims?.find((claim: claim) => claim.typ === 'http://schemas.microsoft.com/identity/claims/objectidentifier')?.val || '', }; - console.log("User info: ", userInfo); return userInfo; } catch (e) { return {} as UserInfo; diff --git a/src/frontend/src/components/content/HomeInput.tsx b/src/frontend/src/components/content/HomeInput.tsx index 4e2c140de..c2ff55283 100644 --- a/src/frontend/src/components/content/HomeInput.tsx +++ b/src/frontend/src/components/content/HomeInput.tsx @@ -69,13 +69,12 @@ const HomeInput: React.FC = ({ dismissToast(id); navigate(`/plan/${response.plan_id}`); } else { - console.log("Invalid plan:", response.status); showToast("Failed to create plan", "error"); dismissToast(id); } } catch (error) { - console.log("Failed to create plan:", error); dismissToast(id); + console.log("ERROR:", error); showToast("Something went wrong", "error"); } finally { setInput(""); diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index 0ece07e2e..dbea7f08c 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -6,6 +6,7 @@ import reportWebVitals from './reportWebVitals'; import { FluentProvider, teamsLightTheme, teamsDarkTheme } from "@fluentui/react-components"; import { setEnvData, setApiUrl, config as defaultConfig, toBoolean, getUserInfo, setUserInfoGlobal } from './api/config'; import { UserInfo } from './models'; +import { apiService } from './api'; const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); const AppWrapper = () => { @@ -22,7 +23,8 @@ const AppWrapper = () => { window.appConfig = config; setEnvData(config); setApiUrl(config.API_URL); - + const browserLanguage = await apiService.sendUserBrowserLanguage(); + console.log(" ******** Browser language sent:", browserLanguage); try { const response = await fetch('/config'); let config = defaultConfig; @@ -46,7 +48,7 @@ const AppWrapper = () => { setIsUserInfoLoaded(true); } }; - + initConfig(); // Call the async function inside useEffect }, []); // Effect to listen for changes in the user's preferred color scheme diff --git a/src/frontend/src/pages/PlanPage.tsx b/src/frontend/src/pages/PlanPage.tsx index 84067f239..e469ff4bb 100644 --- a/src/frontend/src/pages/PlanPage.tsx +++ b/src/frontend/src/pages/PlanPage.tsx @@ -124,7 +124,6 @@ const PlanPage: React.FC = () => { } catch (error) { dismissToast(id); showToast("Failed to submit clarification", "error"); - console.log("Failed to submit clarification:", error); } finally { setInput(""); setSubmitting(false); @@ -150,7 +149,6 @@ const PlanPage: React.FC = () => { } catch (error) { dismissToast(id); showToast(`Failed to ${approve ? "approve" : "reject"} step`, "error"); - console.log(`Failed to ${approve ? "approve" : "reject"} step:`, error); } finally { setProcessingSubtaskId(null); setSubmitting(false); From fb6bb2081f1ad3dd4d50072104a883712741192f Mon Sep 17 00:00:00 2001 From: Ravi Date: Tue, 29 Jul 2025 15:55:13 +0530 Subject: [PATCH 2/7] rate limit error fix --- src/frontend/src/components/content/HomeInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/content/HomeInput.tsx b/src/frontend/src/components/content/HomeInput.tsx index c2ff55283..79b01a697 100644 --- a/src/frontend/src/components/content/HomeInput.tsx +++ b/src/frontend/src/components/content/HomeInput.tsx @@ -75,7 +75,7 @@ const HomeInput: React.FC = ({ } catch (error) { dismissToast(id); console.log("ERROR:", error); - showToast("Something went wrong", "error"); + showToast(error instanceof Error ? error.message : String(error ?? ""), "error"); } finally { setInput(""); setSubmitting(false); From aa3c9dc30ce3544540a5fa34d6e78b0b475499ba Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 30 Jul 2025 15:21:48 +0530 Subject: [PATCH 3/7] date changes --- src/backend/app_kernel.py | 67 ++++++++++++++++++- src/backend/kernel_tools/product_tools.py | 8 +-- .../src/components/content/HomeInput.tsx | 5 +- src/frontend/src/services/TaskService.tsx | 2 +- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 615d61156..53ec24e46 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -10,6 +10,8 @@ from auth.auth_utils import get_authenticated_user_details # Azure monitoring +import re +from dateutil import parser from azure.monitor.opentelemetry import configure_azure_monitor from config_kernel import Config from event_utils import track_event_if_configured @@ -35,6 +37,7 @@ # Updated import for KernelArguments from utils_kernel import initialize_runtime_and_context, rai_success + # Check if the Application Insights Instrumentation Key is set in the environment variables connection_string = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING") if connection_string: @@ -71,7 +74,7 @@ # Add this near the top of your app.py, after initializing the app app.add_middleware( CORSMiddleware, - allow_origins=['*'], + allow_origins=[frontend_url], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -81,6 +84,53 @@ app.add_middleware(HealthCheckMiddleware, password="", checks={}) logging.info("Added health check middleware") +def format_dates_in_messages(messages, target_locale="en-US"): + """ + Format dates in agent messages according to the specified locale. + + Args: + messages: List of message objects or string content + target_locale: Target locale for date formatting (default: en-US) + + Returns: + Formatted messages with dates converted to target locale format + """ + # Define target format patterns per locale + locale_date_formats = { + "en-IN": "%d %b %Y", # 30 Jul 2025 + "en-US": "%b %d, %Y", # Jul 30, 2025 + } + + output_format = locale_date_formats.get(target_locale, "%d %b %Y") + # Match both "Jul 30, 2025, 12:00:00 AM" and "30 Jul 2025" + date_pattern = r'(\d{1,2} [A-Za-z]{3,9} \d{4}|[A-Za-z]{3,9} \d{1,2}, \d{4}(, \d{1,2}:\d{2}:\d{2} ?[APap][Mm])?)' + + def convert_date(match): + date_str = match.group(0) + try: + dt = parser.parse(date_str) + return dt.strftime(output_format) + except Exception: + return date_str # Leave it unchanged if parsing fails + + # Process messages + if isinstance(messages, list): + formatted_messages = [] + for message in messages: + if hasattr(message, 'content') and message.content: + # Create a copy of the message with formatted content + formatted_message = message.model_copy() if hasattr(message, 'model_copy') else message + if hasattr(formatted_message, 'content'): + formatted_message.content = re.sub(date_pattern, convert_date, formatted_message.content) + formatted_messages.append(formatted_message) + else: + formatted_messages.append(message) + return formatted_messages + elif isinstance(messages, str): + return re.sub(date_pattern, convert_date, messages) + else: + return messages + @app.post("/api/user_browser_language") async def user_browser_language_endpoint( user_language: UserLanguage, @@ -211,6 +261,13 @@ async def input_task_endpoint(input_task: InputTask, request: Request): } except Exception as e: + # Extract clean error message for rate limit errors + error_msg = str(e) + if "Rate limit is exceeded" in error_msg: + match = re.search(r"Rate limit is exceeded\. Try again in (\d+) seconds?\.", error_msg) + if match: + error_msg = f"Rate limit is exceeded. Try again in {match.group(1)} seconds." + track_event_if_configured( "InputTaskError", { @@ -219,7 +276,7 @@ async def input_task_endpoint(input_task: InputTask, request: Request): "error": str(e), }, ) - raise HTTPException(status_code=400, detail=f"Error creating plan: {e}") + raise HTTPException(status_code=400, detail=f"Error creating plan: {error_msg}") from e @app.post("/api/human_feedback") @@ -660,7 +717,11 @@ async def get_plans( plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps) plan_with_steps.update_step_counts() - return [plan_with_steps, messages] + + # Format dates in messages according to locale + formatted_messages = format_dates_in_messages(messages, config.get_user_local_browser_language()) + + return [plan_with_steps, formatted_messages] all_plans = await memory_store.get_all_plans() # Fetch steps for all plans concurrently diff --git a/src/backend/kernel_tools/product_tools.py b/src/backend/kernel_tools/product_tools.py index b5c119b76..d07957f9e 100644 --- a/src/backend/kernel_tools/product_tools.py +++ b/src/backend/kernel_tools/product_tools.py @@ -10,25 +10,25 @@ import json from typing import get_type_hints from utils_date import format_date_for_user +from app_config import config class ProductTools: """Define Product Agent functions (tools)""" agent_name = AgentType.PRODUCT.value - + selecetd_language = config.get_user_local_browser_language() @staticmethod @kernel_function( - description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service." + description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones" ) async def add_mobile_extras_pack(new_extras_pack_name: str, start_date: str) -> str: """Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. The arguments should include the new_extras_pack_name and the start_date as strings. You must provide the exact plan name, as found using the get_product_info() function.""" formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did." - formatted_date = format_date_for_user(start_date) analysis = ( f"# Request to Add Extras Pack to Mobile Plan\n" f"## New Plan:\n{new_extras_pack_name}\n" - f"## Start Date:\n{formatted_date}\n\n" + f"## Start Date:\n{start_date}\n\n" f"These changes have been completed and should be reflected in your app in 5-10 minutes." f"\n\n{formatting_instructions}" ) diff --git a/src/frontend/src/components/content/HomeInput.tsx b/src/frontend/src/components/content/HomeInput.tsx index 79b01a697..15ca5566c 100644 --- a/src/frontend/src/components/content/HomeInput.tsx +++ b/src/frontend/src/components/content/HomeInput.tsx @@ -72,10 +72,9 @@ const HomeInput: React.FC = ({ showToast("Failed to create plan", "error"); dismissToast(id); } - } catch (error) { + } catch (error:any) { dismissToast(id); - console.log("ERROR:", error); - showToast(error instanceof Error ? error.message : String(error ?? ""), "error"); + showToast(JSON.parse(error?.message)?.detail, "error"); } finally { setInput(""); setSubmitting(false); diff --git a/src/frontend/src/services/TaskService.tsx b/src/frontend/src/services/TaskService.tsx index d0c62ce0b..6178289c3 100644 --- a/src/frontend/src/services/TaskService.tsx +++ b/src/frontend/src/services/TaskService.tsx @@ -190,7 +190,7 @@ export class TaskService { if (error?.response?.data?.message) { message = error.response.data.message; } else if (error?.message) { - message = error.message; + message = error.message?.detail ? error.message.detail : error.message; } // Throw a new error with a user-friendly message throw new Error(message); From 645b94579ef67e0b1afb950fd96106a674e92794 Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 30 Jul 2025 15:37:47 +0530 Subject: [PATCH 4/7] code clean up --- src/backend/app_kernel.py | 2 +- src/backend/utils_date.py | 108 +++---------------------------------- src/frontend/src/index.tsx | 1 - 3 files changed, 7 insertions(+), 104 deletions(-) diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 53ec24e46..4c29c6278 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -74,7 +74,7 @@ # Add this near the top of your app.py, after initializing the app app.add_middleware( CORSMiddleware, - allow_origins=[frontend_url], + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/src/backend/utils_date.py b/src/backend/utils_date.py index 591101baf..bc4fbdbe6 100644 --- a/src/backend/utils_date.py +++ b/src/backend/utils_date.py @@ -3,117 +3,21 @@ import logging from typing import Optional -from app_config import config - -# Try to import babel, with fallback if not available -try: - from babel.dates import format_datetime - BABEL_AVAILABLE = True -except ImportError: - BABEL_AVAILABLE = False - logging.warning("Babel library not available. Date formatting will use basic Python formatting.") - - -def _format_date_fallback(date_obj: datetime, language_code: str) -> str: - """ - Fallback date formatting when babel is not available. - - Args: - date_obj (datetime): The datetime object to format - language_code (str): Language code like 'en-US', 'en-IN', etc. - - Returns: - str: Formatted date string - """ - # Normalize the language code - normalized_code = language_code.replace('-', '_') - - # Define basic date formats for different locales - locale_date_formats = { - 'en_IN': '%d %B, %Y', # 29 July, 2025 - 'en_US': '%B %d, %Y', # July 29, 2025 - 'en_GB': '%d %B %Y', # 29 July 2025 - 'en_AU': '%d %B %Y', # 29 July 2025 - 'en_CA': '%B %d, %Y', # July 29, 2025 - 'es_ES': '%d de %B de %Y', # Would need Spanish month names - 'fr_FR': '%d %B %Y', # Would need French month names - 'de_DE': '%d. %B %Y', # Would need German month names - 'ja_JP': '%Y年%m月%d日', # 2025年07月29日 - 'ko_KR': '%Y년 %m월 %d일', # 2025년 07월 29일 - 'zh_CN': '%Y年%m月%d日', # 2025年07月29日 - } - - # Get the format for the locale, default to US format - date_format = locale_date_formats.get(normalized_code, '%B %d, %Y') - - try: - return date_obj.strftime(date_format) - except Exception as e: - logging.warning("Fallback date formatting failed: %s", str(e)) - return date_obj.strftime('%Y-%m-%d') # ISO format as last resort - - def format_date_for_user(date_str: str, user_locale: Optional[str] = None) -> str: """ Format date based on user's desktop locale preference. Args: - date_str (str): Date in ISO format (YYYY-MM-DD) or datetime object. + date_str (str): Date in ISO format (YYYY-MM-DD). user_locale (str, optional): User's locale string, e.g., 'en_US', 'en_GB'. Returns: str: Formatted date respecting locale or raw date if formatting fails. """ try: - # Get user's browser language from config - lang = config.get_user_local_browser_language() # e.g., 'en-US', 'fr-FR', 'de-DE' - - # Parse the date string if it's a string, otherwise use as-is if it's already a datetime - if isinstance(date_str, str): - # Try different date formats - date_formats = [ - "%Y-%m-%d", # 2025-07-29 - "%Y-%m-%d", # 2025-07-29 14:30:00 - "%Y-%m-%d", # 2025-07-29T14:30:00 - "%d/%m/%Y", # 29/07/2025 - "%m/%d/%Y", # 07/29/2025 - ] - - parsed_date = None - for date_format in date_formats: - try: - parsed_date = datetime.strptime(date_str, date_format) - break - except ValueError: - continue - - if parsed_date is None: - logging.warning("Could not parse date string: %s", date_str) - return date_str # Return original string if parsing fails - - date_to_format = parsed_date - else: - # Assume it's already a datetime object - date_to_format = date_str - - # Format the date using babel with the user's locale, or fallback to basic formatting - if BABEL_AVAILABLE: - try: - # Babel expects locale in format like 'en_US', not 'en-US' - babel_locale = lang.replace('-', '_') - formatted_date = format_datetime(date_to_format, locale=babel_locale) - except Exception as e: - logging.warning("Babel formatting failed: %s. Using fallback formatting.", str(e)) - formatted_date = _format_date_fallback(date_to_format, lang) - else: - formatted_date = _format_date_fallback(date_to_format, lang) - - print( - f"Formatted date for user ######################### : {formatted_date} using locale: {lang},browser lang {config.get_user_local_browser_language()}, locale: {babel_locale}" - ) - return formatted_date - + date_obj = datetime.strptime(date_str, "%Y-%m-%d") + locale.setlocale(locale.LC_TIME, user_locale or '') + return date_obj.strftime("%B %d, %Y") except Exception as e: - logging.error("Error formatting date '%s': %s", date_str, str(e)) - # Return the original input if anything goes wrong - return str(date_str) \ No newline at end of file + logging.warning(f"Date formatting failed for '{date_str}': {e}") + return date_str diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index dbea7f08c..a47ac3767 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -24,7 +24,6 @@ const AppWrapper = () => { setEnvData(config); setApiUrl(config.API_URL); const browserLanguage = await apiService.sendUserBrowserLanguage(); - console.log(" ******** Browser language sent:", browserLanguage); try { const response = await fetch('/config'); let config = defaultConfig; From 3954e563439bbb861ae6a85c5065dbf04c9eebd6 Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 30 Jul 2025 15:39:00 +0530 Subject: [PATCH 5/7] config reverted --- src/backend/app_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 4c29c6278..53ec24e46 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -74,7 +74,7 @@ # Add this near the top of your app.py, after initializing the app app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=[frontend_url], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 8bec36ef9f810d9888924eab184707df97cdd67e Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 30 Jul 2025 16:17:55 +0530 Subject: [PATCH 6/7] lint issue fix --- src/backend/app_config.py | 2 +- src/backend/app_kernel.py | 19 +++++++++++-------- src/backend/kernel_tools/hr_tools.py | 5 +---- src/backend/kernel_tools/product_tools.py | 3 ++- src/backend/test_utils_date_fixed.py | 16 +++++++++------- src/backend/utils_date.py | 1 + 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/backend/app_config.py b/src/backend/app_config.py index c3b3d3774..fe2b9f90c 100644 --- a/src/backend/app_config.py +++ b/src/backend/app_config.py @@ -165,7 +165,7 @@ def get_ai_project_client(self): except Exception as exc: logging.error("Failed to create AIProjectClient: %s", exc) raise - + def get_user_local_browser_language(self) -> str: """Get the user's local browser language from environment variables. diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 4e9d4fd49..e0e81abd1 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -84,14 +84,15 @@ app.add_middleware(HealthCheckMiddleware, password="", checks={}) logging.info("Added health check middleware") + def format_dates_in_messages(messages, target_locale="en-US"): """ Format dates in agent messages according to the specified locale. - + Args: messages: List of message objects or string content target_locale: Target locale for date formatting (default: en-US) - + Returns: Formatted messages with dates converted to target locale format """ @@ -100,11 +101,11 @@ def format_dates_in_messages(messages, target_locale="en-US"): "en-IN": "%d %b %Y", # 30 Jul 2025 "en-US": "%b %d, %Y", # Jul 30, 2025 } - + output_format = locale_date_formats.get(target_locale, "%d %b %Y") # Match both "Jul 30, 2025, 12:00:00 AM" and "30 Jul 2025" date_pattern = r'(\d{1,2} [A-Za-z]{3,9} \d{4}|[A-Za-z]{3,9} \d{1,2}, \d{4}(, \d{1,2}:\d{2}:\d{2} ?[APap][Mm])?)' - + def convert_date(match): date_str = match.group(0) try: @@ -112,7 +113,7 @@ def convert_date(match): return dt.strftime(output_format) except Exception: return date_str # Leave it unchanged if parsing fails - + # Process messages if isinstance(messages, list): formatted_messages = [] @@ -131,6 +132,7 @@ def convert_date(match): else: return messages + @app.post("/api/user_browser_language") async def user_browser_language_endpoint( user_language: UserLanguage, @@ -165,6 +167,7 @@ async def user_browser_language_endpoint( return {"status": "Language received successfully"} + @app.post("/api/input_task") async def input_task_endpoint(input_task: InputTask, request: Request): """ @@ -267,7 +270,7 @@ async def input_task_endpoint(input_task: InputTask, request: Request): match = re.search(r"Rate limit is exceeded\. Try again in (\d+) seconds?\.", error_msg) if match: error_msg = f"Rate limit is exceeded. Try again in {match.group(1)} seconds." - + track_event_if_configured( "InputTaskError", { @@ -729,10 +732,10 @@ async def get_plans( plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps) plan_with_steps.update_step_counts() - + # Format dates in messages according to locale formatted_messages = format_dates_in_messages(messages, config.get_user_local_browser_language()) - + return [plan_with_steps, formatted_messages] all_plans = await memory_store.get_all_plans() diff --git a/src/backend/kernel_tools/hr_tools.py b/src/backend/kernel_tools/hr_tools.py index 253a337e0..fc106373e 100644 --- a/src/backend/kernel_tools/hr_tools.py +++ b/src/backend/kernel_tools/hr_tools.py @@ -5,20 +5,18 @@ from models.messages_kernel import AgentType import json from typing import get_type_hints -from utils_date import format_date_for_user from app_config import config class HrTools: # Define HR tools (functions) - selecetd_language= config.get_user_local_browser_language() + selecetd_language = config.get_user_local_browser_language() formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones" agent_name = AgentType.HR.value @staticmethod @kernel_function(description="Schedule an orientation session for a new employee.") async def schedule_orientation_session(employee_name: str, date: str) -> str: - #formatted_date = format_date_for_user(date) return ( f"##### Orientation Session Scheduled\n" @@ -26,7 +24,6 @@ async def schedule_orientation_session(employee_name: str, date: str) -> str: f"**Date:** {date}\n\n" f"Your orientation session has been successfully scheduled. " f"Please mark your calendar and be prepared for an informative session.\n" - #f"AGENT SUMMARY: I scheduled the orientation session for {employee_name} on {formatted_date}, as part of her onboarding process.\n" f"{HrTools.formatting_instructions}" ) diff --git a/src/backend/kernel_tools/product_tools.py b/src/backend/kernel_tools/product_tools.py index d07957f9e..89748e709 100644 --- a/src/backend/kernel_tools/product_tools.py +++ b/src/backend/kernel_tools/product_tools.py @@ -10,7 +10,7 @@ import json from typing import get_type_hints from utils_date import format_date_for_user -from app_config import config +from app_config import config class ProductTools: @@ -19,6 +19,7 @@ class ProductTools: agent_name = AgentType.PRODUCT.value selecetd_language = config.get_user_local_browser_language() @staticmethod + @kernel_function( description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones" ) diff --git a/src/backend/test_utils_date_fixed.py b/src/backend/test_utils_date_fixed.py index 472081876..62eb8fc67 100644 --- a/src/backend/test_utils_date_fixed.py +++ b/src/backend/test_utils_date_fixed.py @@ -6,9 +6,10 @@ from datetime import datetime from utils_date import format_date_for_user + def test_date_formatting(): """Test the date formatting function with various inputs""" - + # Set up different language environments test_cases = [ ('en-US', '2025-07-29', 'US English'), @@ -17,10 +18,10 @@ def test_date_formatting(): ('fr-FR', '2025-07-29', 'French'), ('de-DE', '2025-07-29', 'German'), ] - + print("Testing date formatting with different locales:") print("=" * 50) - + for locale, date_str, description in test_cases: os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = locale try: @@ -28,19 +29,19 @@ def test_date_formatting(): print(f"{description} ({locale}): {result}") except Exception as e: print(f"{description} ({locale}): ERROR - {e}") - + print("\n" + "=" * 50) print("Testing with datetime object:") - + # Test with datetime object os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = 'en-US' dt = datetime(2025, 7, 29, 14, 30, 0) result = format_date_for_user(dt) print(f"Datetime object: {result}") - + print("\nTesting error handling:") print("=" * 30) - + # Test error handling try: result = format_date_for_user('invalid-date-string') @@ -48,5 +49,6 @@ def test_date_formatting(): except Exception as e: print(f"Invalid date: ERROR - {e}") + if __name__ == "__main__": test_date_formatting() diff --git a/src/backend/utils_date.py b/src/backend/utils_date.py index bc4fbdbe6..d346e3cd0 100644 --- a/src/backend/utils_date.py +++ b/src/backend/utils_date.py @@ -3,6 +3,7 @@ import logging from typing import Optional + def format_date_for_user(date_str: str, user_locale: Optional[str] = None) -> str: """ Format date based on user's desktop locale preference. From 40b5ec59f5cdb22d3e6aa9d7e726b76ee3e47fbf Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 30 Jul 2025 16:28:07 +0530 Subject: [PATCH 7/7] lint issue fix --- src/backend/kernel_tools/product_tools.py | 2 +- src/backend/models/messages_kernel.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/kernel_tools/product_tools.py b/src/backend/kernel_tools/product_tools.py index 89748e709..e3d98e030 100644 --- a/src/backend/kernel_tools/product_tools.py +++ b/src/backend/kernel_tools/product_tools.py @@ -18,8 +18,8 @@ class ProductTools: agent_name = AgentType.PRODUCT.value selecetd_language = config.get_user_local_browser_language() + @staticmethod - @kernel_function( description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones" ) diff --git a/src/backend/models/messages_kernel.py b/src/backend/models/messages_kernel.py index df9de5b8d..533af6aa3 100644 --- a/src/backend/models/messages_kernel.py +++ b/src/backend/models/messages_kernel.py @@ -262,7 +262,8 @@ class InputTask(KernelBaseModel): session_id: str description: str # Initial goal - + + class UserLanguage(KernelBaseModel): language: str