Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions application/single_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from route_frontend_public_workspaces import *
from route_frontend_safety import *
from route_frontend_feedback import *
from route_frontend_support import *
from route_frontend_notifications import *

from route_backend_chats import *
Expand Down Expand Up @@ -863,6 +864,9 @@ def list_semantic_kernel_plugins():
# ------------------- Feedback Routes -------------------
register_route_frontend_feedback(app)

# ------------------- Support Routes --------------------
register_route_frontend_support(app)

# ------------------- Notifications Routes --------------
register_route_frontend_notifications(app)

Expand Down
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.240.056"
VERSION = "0.240.066"

SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')

Expand Down
67 changes: 67 additions & 0 deletions application/single_app/functions_activity_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,73 @@ def log_admin_feedback_email_submission(
debug_print(f"[Admin Feedback] Failed to log feedback email submission for user {user_id}")


def log_user_support_feedback_email_submission(
user_id: str,
user_email: str,
feedback_type: str,
reporter_name: str,
reporter_email: str,
organization: str,
details: str,
recipient_email: str,
source: str = 'support_menu'
) -> None:
"""Log a user-initiated support feedback email draft event."""

feedback_metadata = {
'feedback_type': feedback_type,
'details_length': len(details or ''),
**_build_contact_metadata(reporter_name, reporter_email, organization),
}

try:
timestamp = datetime.utcnow().isoformat()
activity_record = {
'id': str(uuid.uuid4()),
'partitionKey': user_id,
'user_id': user_id,
'timestamp': timestamp,
'activity_type': 'user_support_feedback_email_submission',
'submission_channel': 'mailto',
'recipient_email': recipient_email,
'source': source,
'feedback_submission': feedback_metadata,
}

cosmos_activity_logs_container.create_item(body=activity_record)

log_event(
message='[Support Feedback] Mailto draft prepared',
extra={
'user_id': user_id,
'activity_type': 'user_support_feedback_email_submission',
'submission_channel': 'mailto',
'recipient_email': recipient_email,
'source': source,
**feedback_metadata,
},
level=logging.INFO
)
debug_print(f"[Support Feedback] Logged support feedback email submission for user {user_id}")

except Exception:
log_event(
message='[Support Feedback] Failed to record support feedback mailto draft',
extra={
'user_id': user_id,
'feedback_type': feedback_type,
'activity_type': 'user_support_feedback_email_submission',
'recipient_email': recipient_email,
'source': source,
'details_length': len(details or ''),
**_build_contact_metadata(reporter_name, reporter_email, organization),
},
level=logging.ERROR,
exceptionTraceback=True
)
debug_print(f"[Support Feedback] Failed to log support feedback email submission for user {user_id}")


def log_admin_release_notifications_registration(
user_id: str,
admin_email: str,
Expand Down
45 changes: 43 additions & 2 deletions application/single_app/functions_message_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"""Helpers for storing large assistant-side payloads outside primary chat items."""

import json
import math
import numbers
from copy import deepcopy
from typing import Any, Dict, List, Optional, Tuple

Expand Down Expand Up @@ -33,13 +35,50 @@ def filter_assistant_artifact_items(items: List[Dict[str, Any]]) -> List[Dict[st
return [item for item in items or [] if not is_assistant_artifact_role(item.get('role'))]


def _normalize_json_scalar(value: Any) -> Any:
"""Normalize scalar values into JSON-safe primitives."""
if hasattr(value, 'item') and not isinstance(value, (str, bytes)):
try:
value = value.item()
except (TypeError, ValueError):
pass

if value is None:
return None

if isinstance(value, bytes):
return value.decode('utf-8', errors='replace')

if isinstance(value, bool):
return value

if isinstance(value, numbers.Integral):
return int(value)

if isinstance(value, numbers.Real):
numeric_value = float(value)
if not math.isfinite(numeric_value):
return None
return numeric_value

if hasattr(value, 'isoformat') and not isinstance(value, str):
try:
return value.isoformat()
except TypeError:
pass

return value


def make_json_serializable(value: Any) -> Any:
"""Convert nested values into JSON-serializable structures."""
value = _normalize_json_scalar(value)

if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, dict):
return {str(key): make_json_serializable(item) for key, item in value.items()}
if isinstance(value, (list, tuple)):
if isinstance(value, (list, tuple, set)):
return [make_json_serializable(item) for item in value]
return str(value)

Expand Down Expand Up @@ -212,7 +251,7 @@ def _build_artifact_documents(
user_info: Optional[Dict[str, Any]] = None,
citation: Optional[Dict[str, Any]] = None,
) -> List[Dict[str, Any]]:
serialized_payload = json.dumps(payload, default=str)
serialized_payload = json.dumps(make_json_serializable(payload), default=str, allow_nan=False)
chunks = [
serialized_payload[index:index + ASSISTANT_ARTIFACT_CHUNK_SIZE]
for index in range(0, len(serialized_payload), ASSISTANT_ARTIFACT_CHUNK_SIZE)
Expand Down Expand Up @@ -374,6 +413,8 @@ def _compact_tabular_result_payload(function_name: str, payload: Any) -> Any:


def _compact_value(value: Any, depth: int = 0) -> Any:
value = _normalize_json_scalar(value)

if value is None or isinstance(value, (int, float, bool)):
return value

Expand Down
24 changes: 24 additions & 0 deletions application/single_app/functions_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import app_settings_cache
import inspect
import copy
from support_menu_config import (
get_default_support_latest_features_visibility,
has_visible_support_latest_features,
normalize_support_latest_features_visibility,
)


def is_tabular_processing_enabled(settings):
Expand Down Expand Up @@ -207,6 +212,14 @@ def get_settings(use_cosmos=False, include_source=False):
{"label": "Prompt Ideas", "url": "https://example.com/prompts"}
],

# Support Menu
'enable_support_menu': False,
'support_menu_name': 'Support',
'enable_support_send_feedback': True,
'support_feedback_recipient_email': '',
'enable_support_latest_features': True,
'support_latest_features_visibility': get_default_support_latest_features_visibility(),

# Enhanced Citations
'enable_enhanced_citations': False,
'enable_enhanced_citations_mount': False,
Expand Down Expand Up @@ -1295,6 +1308,8 @@ def sanitize_settings_for_user(full_settings: dict) -> dict:
sanitized = {}

for k, v in full_settings.items():
if k == 'support_feedback_recipient_email':
continue
if any(term in k.lower() for term in sensitive_terms):
continue
if k in ('model_endpoints', 'personal_model_endpoints') and isinstance(v, list):
Expand All @@ -1319,6 +1334,15 @@ def sanitize_settings_for_user(full_settings: dict) -> dict:
if 'custom_favicon_base64' in full_settings:
sanitized['custom_favicon_base64'] = bool(full_settings.get('custom_favicon_base64'))

if 'support_latest_features_visibility' in full_settings or 'enable_support_latest_features' in full_settings:
sanitized['support_latest_features_visibility'] = normalize_support_latest_features_visibility(
full_settings.get('support_latest_features_visibility', {})
)
sanitized['support_latest_features_has_visible_items'] = has_visible_support_latest_features(full_settings)
sanitized['support_feedback_recipient_configured'] = bool(
str(full_settings.get('support_feedback_recipient_email') or '').strip()
)

return sanitized

def sanitize_settings_for_logging(full_settings: dict) -> dict:
Expand Down
Loading
Loading