diff --git a/application/single_app/config.py b/application/single_app/config.py index 87fb07e7..624ae367 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -94,7 +94,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.241.001" +VERSION = "0.241.002" SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') diff --git a/application/single_app/route_backend_chats.py b/application/single_app/route_backend_chats.py index cac4ee8d..c6e99a62 100644 --- a/application/single_app/route_backend_chats.py +++ b/application/single_app/route_backend_chats.py @@ -578,6 +578,33 @@ def _load_user_message_response_context( return response_context +def _initialize_assistant_response_tracking( + conversation_id, + user_message_id, + current_user_thread_id, + previous_thread_id, + retry_thread_attempt, + is_retry, + user_id, +): + """Create assistant response tracking state for both new and retry/edit flows.""" + assistant_message_id = f"{conversation_id}_assistant_{int(time.time())}_{random.randint(1000,9999)}" + thought_tracker = ThoughtTracker( + conversation_id=conversation_id, + message_id=assistant_message_id, + thread_id=current_user_thread_id, + user_id=user_id, + ) + assistant_thread_attempt = retry_thread_attempt if is_retry and retry_thread_attempt is not None else 1 + response_message_context = _load_user_message_response_context( + conversation_id=conversation_id, + user_message_id=user_message_id, + fallback_thread_id=current_user_thread_id, + fallback_previous_thread_id=previous_thread_id, + ) + return assistant_message_id, thought_tracker, assistant_thread_attempt, response_message_context + + def _build_safety_message_doc( conversation_id, message_id, @@ -5383,6 +5410,29 @@ def resolve_foundry_scope_for_auth(auth_settings, endpoint=None): return 'https://ai.azure.com/.default' +def get_foundry_api_version_candidates(primary_version, settings): + """Return distinct Foundry API versions to try for inference compatibility.""" + settings = settings or {} + candidates = [ + str(primary_version or '').strip(), + str(settings.get('azure_openai_gpt_api_version') or '').strip(), + '2024-10-01-preview', + '2024-07-01-preview', + '2024-05-01-preview', + '2024-02-01', + ] + + unique_candidates = [] + seen_candidates = set() + for candidate in candidates: + if not candidate or candidate in seen_candidates: + continue + seen_candidates.add(candidate) + unique_candidates.append(candidate) + + return unique_candidates + + def build_streaming_multi_endpoint_client(auth_settings, provider, endpoint, api_version): """Create an inference client for a resolved streaming model endpoint.""" auth_settings = auth_settings or {} @@ -5990,21 +6040,16 @@ def result_requires_message_reload(result: Any) -> bool: ) try: multi_endpoint_config = None - if should_use_default_model: - try: - multi_endpoint_config = resolve_default_model_gpt_config(settings) - if multi_endpoint_config: - debug_print("[GPTClient] Using default multi-endpoint model for agent request.") - except Exception as default_exc: - log_event( - f"[GPTClient] Default model selection unavailable: {default_exc}", - level=logging.WARNING, - exceptionTraceback=True - ) - if multi_endpoint_config is None and request_agent_info: - debug_print("[GPTClient] Skipping multi-endpoint resolution because agent_info is provided.") - elif multi_endpoint_config is None: - multi_endpoint_config = resolve_multi_endpoint_gpt_config(settings, data, enable_gpt_apim) + if settings.get('enable_multi_model_endpoints', False): + multi_endpoint_config = resolve_streaming_multi_endpoint_gpt_config( + settings, + data, + user_id, + active_group_ids=active_group_ids, + allow_default_selection=should_use_default_model, + ) + if multi_endpoint_config and should_use_default_model and not data.get('model_endpoint_id'): + debug_print("[GPTClient] Using default multi-endpoint model for agent request.") if multi_endpoint_config: gpt_client, gpt_model, gpt_provider, gpt_endpoint, gpt_auth, gpt_api_version = multi_endpoint_config elif enable_gpt_apim: @@ -6487,26 +6532,18 @@ def result_requires_message_reload(result: Any) -> bool: conversation_item['last_updated'] = datetime.utcnow().isoformat() cosmos_conversations_container.upsert_item(conversation_item) # Update timestamp and potentially title - # Generate assistant_message_id early for thought tracking - assistant_message_id = f"{conversation_id}_assistant_{int(time.time())}_{random.randint(1000,9999)}" - - # Initialize thought tracker - thought_tracker = ThoughtTracker( - conversation_id=conversation_id, - message_id=assistant_message_id, - thread_id=current_user_thread_id, - user_id=user_id - ) - assistant_thread_attempt = retry_thread_attempt if is_retry else 1 - response_message_context = _load_user_message_response_context( - conversation_id=conversation_id, - user_message_id=user_message_id, - fallback_thread_id=current_user_thread_id, - fallback_previous_thread_id=previous_thread_id, - ) - user_info_for_assistant = response_message_context.get('user_info') - user_thread_id = response_message_context.get('thread_id') - user_previous_thread_id = response_message_context.get('previous_thread_id') + assistant_message_id, thought_tracker, assistant_thread_attempt, response_message_context = _initialize_assistant_response_tracking( + conversation_id=conversation_id, + user_message_id=user_message_id, + current_user_thread_id=current_user_thread_id, + previous_thread_id=previous_thread_id, + retry_thread_attempt=retry_thread_attempt, + is_retry=is_retry, + user_id=user_id, + ) + user_info_for_assistant = response_message_context.get('user_info') + user_thread_id = response_message_context.get('thread_id') + user_previous_thread_id = response_message_context.get('previous_thread_id') # region 3 - Content Safety # --------------------------------------------------------------------- @@ -8417,7 +8454,12 @@ def invoke_gpt_fallback(): continue try: debug_print(f"[SKChat] Foundry retry api_version={candidate}") - retry_client = build_multi_endpoint_client(gpt_auth or {}, gpt_provider, gpt_endpoint, candidate) + retry_client = build_streaming_multi_endpoint_client( + gpt_auth or {}, + gpt_provider, + gpt_endpoint, + candidate, + ) response = retry_client.chat.completions.create(**api_params) break except Exception as retry_exc: @@ -9405,22 +9447,14 @@ def generate(publish_background_event=None): conversation_item['last_updated'] = datetime.utcnow().isoformat() cosmos_conversations_container.upsert_item(conversation_item) - # Generate assistant_message_id early for thought tracking - assistant_message_id = f"{conversation_id}_assistant_{int(time.time())}_{random.randint(1000,9999)}" - - # Initialize thought tracker for streaming path - thought_tracker = ThoughtTracker( - conversation_id=conversation_id, - message_id=assistant_message_id, - thread_id=current_user_thread_id, - user_id=user_id - ) - assistant_thread_attempt = retry_thread_attempt if is_retry else 1 - response_message_context = _load_user_message_response_context( + assistant_message_id, thought_tracker, assistant_thread_attempt, response_message_context = _initialize_assistant_response_tracking( conversation_id=conversation_id, user_message_id=user_message_id, - fallback_thread_id=current_user_thread_id, - fallback_previous_thread_id=previous_thread_id, + current_user_thread_id=current_user_thread_id, + previous_thread_id=previous_thread_id, + retry_thread_attempt=retry_thread_attempt, + is_retry=is_retry, + user_id=user_id, ) user_info_for_assistant = response_message_context.get('user_info') user_thread_id = response_message_context.get('thread_id') diff --git a/application/single_app/route_backend_settings.py b/application/single_app/route_backend_settings.py index 34c9f8d1..3ad09217 100644 --- a/application/single_app/route_backend_settings.py +++ b/application/single_app/route_backend_settings.py @@ -542,7 +542,8 @@ def send_support_feedback_email(): user_email = user.get('preferred_username', user.get('email', reporter_email)) feedback_label = 'Bug Report' if feedback_type == 'bug_report' else 'Feature Request' - subject_line = f'[SimpleChat User Support] {feedback_label} - {organization}' + application_title = str(settings.get('app_title') or '').strip() or 'Simple Chat' + subject_line = f'[{application_title} User Support] {feedback_label} - {organization}' log_user_support_feedback_email_submission( user_id=user_id, diff --git a/application/single_app/support_menu_config.py b/application/single_app/support_menu_config.py index d020daa8..fa5411da 100644 --- a/application/single_app/support_menu_config.py +++ b/application/single_app/support_menu_config.py @@ -7,6 +7,29 @@ _SUPPORT_LATEST_FEATURE_DOCS_SETTING_KEY = 'enable_support_latest_feature_documentation_links' +def _resolve_support_application_title(settings): + """Return the application title used for user-facing support copy.""" + app_title = str((settings or {}).get('app_title') or '').strip() + return app_title or 'Simple Chat' + + +def _apply_support_application_title(value, app_title): + """Replace hard-coded product naming in user-facing support metadata.""" + if isinstance(value, str): + return value.replace('{app_title}', app_title).replace('SimpleChat', app_title) + + if isinstance(value, list): + return [_apply_support_application_title(item, app_title) for item in value] + + if isinstance(value, dict): + return { + key: _apply_support_application_title(item, app_title) + for key, item in value.items() + } + + return value + + _SUPPORT_LATEST_FEATURE_CATALOG = [ { 'id': 'guided_tutorials', @@ -145,7 +168,7 @@ 'title': 'Tabular Analysis', 'icon': 'bi-table', 'summary': 'Spreadsheet and table workflows continue to improve for exploration, filtering, and grounded follow-up questions.', - 'details': 'Tabular Analysis improves how SimpleChat works with CSV and spreadsheet files for filtering, comparisons, and grounded follow-up questions.', + 'details': 'Tabular Analysis improves how {app_title} works with CSV and spreadsheet files for filtering, comparisons, and grounded follow-up questions.', 'why': 'You get the most value after the file is uploaded, because the assistant can reason over the stored rows and columns instead of only whatever is pasted into one message.', 'guidance': [ 'Upload your CSV or XLSX to Personal Workspace if it is enabled, or add the file directly to Chat when you want a quicker one-off analysis.', @@ -501,7 +524,7 @@ 'id': 'send_feedback', 'title': 'Send Feedback', 'icon': 'bi-envelope-paper', - 'summary': 'End users can prepare bug reports and feature requests for their SimpleChat admins directly from the Support menu.', + 'summary': 'End users can prepare bug reports and feature requests for their {app_title} admins directly from the Support menu.', 'details': 'Send Feedback opens a guided, text-only email draft workflow so you can report issues or request improvements without leaving the app.', 'why': 'That gives your admins a cleaner starting point for triage than a vague message without context or reproduction details.', 'guidance': [ @@ -577,7 +600,7 @@ 'icon': 'bi-download', 'summary': 'Export one or multiple conversations from Chat in JSON or Markdown without carrying internal-only metadata into the downloaded package.', 'details': 'Conversation Export adds a guided workflow for choosing format, packaging, and download options when you need to reuse or archive chat history outside the app.', - 'why': 'This matters because users often need to share, archive, or reuse a conversation without copying raw chat text by hand or exposing internal metadata that should stay inside SimpleChat.', + 'why': 'This matters because users often need to share, archive, or reuse a conversation without copying raw chat text by hand or exposing internal metadata that should stay inside {app_title}.', 'guidance': [ 'Open an existing conversation from Chat when you want to export content that already has enough context to share.', 'Choose JSON when you want a machine-readable export and Markdown when you want something easier for people to review directly.', @@ -975,6 +998,7 @@ def get_visible_support_latest_features(settings): normalized_visibility = normalize_support_latest_features_visibility( (settings or {}).get('support_latest_features_visibility', {}) ) + app_title = _resolve_support_application_title(settings) visible_items = [] for item in _SUPPORT_LATEST_FEATURE_CATALOG: @@ -984,6 +1008,7 @@ def get_visible_support_latest_features(settings): action for action in visible_item.get('actions', []) if _action_enabled(action, settings) ] + visible_item = _apply_support_application_title(visible_item, app_title) _normalize_feature_media(visible_item) visible_items.append(visible_item) @@ -995,6 +1020,7 @@ def get_visible_support_latest_feature_groups(settings): normalized_visibility = normalize_support_latest_features_visibility( (settings or {}).get('support_latest_features_visibility', {}) ) + app_title = _resolve_support_application_title(settings) visible_groups = [] for feature_group in _SUPPORT_LATEST_FEATURE_RELEASE_GROUPS: @@ -1008,12 +1034,14 @@ def get_visible_support_latest_feature_groups(settings): action for action in visible_feature.get('actions', []) if _action_enabled(action, settings) ] + visible_feature = _apply_support_application_title(visible_feature, app_title) _normalize_feature_media(visible_feature) visible_features.append(visible_feature) if visible_features: visible_group = deepcopy(feature_group) visible_group['features'] = visible_features + visible_group = _apply_support_application_title(visible_group, app_title) visible_groups.append(visible_group) return visible_groups @@ -1022,6 +1050,7 @@ def get_visible_support_latest_feature_groups(settings): def get_support_latest_feature_release_groups_for_settings(settings): """Return grouped latest-feature metadata with actions filtered for the current settings.""" filtered_groups = deepcopy(_SUPPORT_LATEST_FEATURE_RELEASE_GROUPS) + app_title = _resolve_support_application_title(settings) for feature_group in filtered_groups: for feature in feature_group.get('features', []): @@ -1029,8 +1058,11 @@ def get_support_latest_feature_release_groups_for_settings(settings): action for action in feature.get('actions', []) if _action_enabled(action, settings) ] + feature.update(_apply_support_application_title(feature, app_title)) _normalize_feature_media(feature) + feature_group.update(_apply_support_application_title(feature_group, app_title)) + return filtered_groups diff --git a/application/single_app/templates/profile.html b/application/single_app/templates/profile.html index 5e6dd0a9..87985b55 100644 --- a/application/single_app/templates/profile.html +++ b/application/single_app/templates/profile.html @@ -1103,7 +1103,6 @@