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
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.241.001"
VERSION = "0.241.002"

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

Expand Down
134 changes: 84 additions & 50 deletions application/single_app/route_backend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
# ---------------------------------------------------------------------
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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')
Expand Down
3 changes: 2 additions & 1 deletion application/single_app/route_backend_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 35 additions & 3 deletions application/single_app/support_menu_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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': [
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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:
Expand All @@ -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)

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -1022,15 +1050,19 @@ 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', []):
feature['actions'] = [
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


Expand Down
Loading
Loading