Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
4e4f105
Add custom subdomain support for OpenAI and Speech Service in Terraform
Jan 2, 2026
be56540
Merge branch 'Development' of https://github.com/vivche/simplechat-de…
Jan 7, 2026
e13ba0c
Merge remote-tracking branch 'upstream/Development' into Development
Jan 23, 2026
087fb3d
feat: Add ServiceNow integration documentation and bug fixes
Jan 23, 2026
502355f
Removed the readme files for bug fix details
Jan 24, 2026
33bee68
Updated servicenow integration readme
Jan 24, 2026
cd8c520
chore: Revert custom logo changes to upstream version
Jan 24, 2026
fb8181b
chore: Revert terraform main.tf to upstream version
Jan 24, 2026
660d76c
Removed the two openai sample spec downloaed from servicennow site
Jan 24, 2026
de866eb
Update docs/how-to/agents/ServiceNow/servicenow_agent_instructions.txt
vivche Jan 24, 2026
20f994a
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow…
vivche Jan 24, 2026
351f143
Update docs/how-to/azure_speech_managed_identity_manul_setup.md
vivche Jan 24, 2026
5fe5b14
Update application/single_app/semantic_kernel_plugins/openapi_plugin_…
vivche Jan 24, 2026
9626219
Checked in the bug fix detail readme to docs/explanation/fixes/v0.236…
Jan 24, 2026
6364827
Merge branch 'servicenow-integration' of https://github.com/vivche/si…
Jan 24, 2026
dce54a1
Added version number to the feature readme files
Jan 24, 2026
8353d77
Added version number to document, and removed redudant import statement
Jan 24, 2026
5aa7007
refactor: use _ for intentionally unused variable in AI Search test
Jan 24, 2026
548d8d8
Removed azure_speech_managed_indeity_manual readme file since it is u…
Jan 24, 2026
62b0b5b
update version numbers to 0.236.012 in bug fix documentation
Jan 24, 2026
b0be501
Update application/single_app/semantic_kernel_loader.py
vivche Jan 24, 2026
e264a13
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowle…
vivche Jan 24, 2026
b04bd67
Update docs/explanation/fixes/v0.236.012/AZURE_AI_SEARCH_TEST_CONNECT…
vivche Jan 24, 2026
84c01e9
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowle…
vivche Jan 24, 2026
c8e383e
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow…
vivche Jan 24, 2026
39dc7a4
Update docs/explanation/fixes/v0.236.012/GROUP_AGENT_LOADING_FIX.md
vivche Jan 24, 2026
2e8c737
Remvoed debug statements that might include senstive info
Jan 24, 2026
d0581d5
Merge branch 'servicenow-integration' of https://github.com/vivche/si…
Jan 24, 2026
0c23a78
Rollback Azure AI Search test connection fix for separate PR
Jan 24, 2026
7f8248a
Update application/single_app/semantic_kernel_plugins/openapi_plugin_…
vivche Jan 24, 2026
a0fbffd
Update docs/explanation/fixes/v0.236.012/GROUP_AGENT_LOADING_FIX.md
vivche Jan 24, 2026
4bac07a
Update docs/explanation/fixes/v0.236.012/GROUP_ACTION_OAUTH_SCHEMA_ME…
vivche Jan 24, 2026
1c31db4
Update docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md
vivche Jan 24, 2026
7e0c688
Fix Azure AI Search test connection with managed identity
Jan 24, 2026
6b0164a
Fix Azure AI Search test connection with managed identity
Jan 24, 2026
c910ede
Corrected file folder name
Jan 24, 2026
f188224
Merge branch 'ai-search-test-connection-fix' of https://github.com/vi…
Jan 24, 2026
8ae8518
Corrected the version number to reference 0.236.012
Jan 24, 2026
a82ecb7
Removed unneeded folder and document
Jan 24, 2026
589291b
Revert terraform main.tf to upstream/Development version
Jan 24, 2026
d017028
updated the logging logic when running retention delete with archivin…
paullizer Jan 24, 2026
2e8e87a
Corrected version to 0.236.011 (#645)
paullizer Jan 26, 2026
6042461
v0.237.001 (#649)
paullizer Jan 26, 2026
9c698af
Merge branch 'Staging' into Development
paullizer Jan 26, 2026
84e00cb
Use Microsoft python base image
clarked-msft Jan 26, 2026
317c6ee
Add python ENV vars
clarked-msft Jan 26, 2026
25f41fb
Add python ENV vars
clarked-msft Jan 26, 2026
0753f52
Install deps to systme
clarked-msft Jan 26, 2026
f2958f0
Add temp dir to image and pip conf support
clarked-msft Jan 26, 2026
efd6fe7
Add custom-ca-certificates dir
clarked-msft Jan 26, 2026
231b792
Merge pull request #653 from clarked-msft/msft-python-image
Bionic711 Jan 26, 2026
7d0a792
Logo bug fix (#654)
paullizer Jan 26, 2026
1cdb27a
Merge branch 'Staging' into Development
paullizer Jan 26, 2026
823e6fa
Rentention policy (#657)
paullizer Jan 26, 2026
1c1c845
Merge branch 'Staging' into Development
paullizer Jan 26, 2026
31e8341
Added ServiceNow support for create and publish article. Including r…
Jan 27, 2026
c70db6b
Replace actual servicenow instance name with generic name in the read…
Jan 27, 2026
fa72a65
Merge remote-tracking branch 'upstream/Development' into servicenow-i…
Jan 27, 2026
0ed07b1
Changed version number in ServiceNow readme files to 0.237.005 since …
Jan 27, 2026
61d8a8b
Enhance ServiceNow agent for managing new KB article creation
Jan 28, 2026
715bb6b
Added readme and open ai specs and agent instructions to support Serv…
Jan 28, 2026
b5804ad
Remove any references to actual ServiceNow instances
Jan 28, 2026
6c4b14e
Merge upstream/Development into ai-search-test-connection-fix
Jan 28, 2026
7ace8b7
Merge pull request #641 from vivche/ai-search-test-connection-fix
Bionic711 Jan 29, 2026
05a14e4
fixed retention policy runtime bug and sidebar bug (#672)
paullizer Jan 30, 2026
5cd0f3b
Merge branch 'Staging' into Development
paullizer Jan 30, 2026
0e0c437
Fix: Windows Unicode encoding issue for video uploads (#662)
vivche Jan 30, 2026
ab456a4
Update docs/how-to/azure_speech_managed_identity_manul_setup.md (#675)
paullizer Jan 30, 2026
534eb72
Add custom subdomain support for OpenAI and Speech Service in Terrafo…
vivche Jan 30, 2026
251b949
0.237.006 (#676)
paullizer Jan 30, 2026
cd09c7a
docs: Update release notes for ServiceNow integration and bug fixes
Jan 30, 2026
634040d
Update release_notes.md
paullizer Jan 30, 2026
944b581
Merge branch 'Staging' into Development
paullizer Jan 30, 2026
39e812c
resolve conflict
Jan 30, 2026
0da710a
fixed sidebar race condition (#679)
paullizer Jan 30, 2026
1e4b524
Merge branch 'Staging' into Development
paullizer Jan 30, 2026
fd66132
Merge branch 'Development' into servicenow-integration, and moved fix…
Jan 31, 2026
0c161ae
fix the version number in config.py
Jan 31, 2026
bf90baf
Security: Restrict group agent loading to active group only
Jan 31, 2026
8a9ad98
Fixed an instruction error that caused semantic kernel to fall back t…
Jan 31, 2026
28a557e
Merge pull request #640 from vivche/servicenow-integration
Bionic711 Jan 31, 2026
82f8a89
Fixed! The issue was caused by duplicated code blocks (#683)
paullizer Feb 3, 2026
4c24cc8
Manage group frontend bug (#684)
paullizer Feb 3, 2026
ef14203
Bicepfix (#690)
eldong Feb 5, 2026
a4a4224
Search bug fix 20260229 (#697)
paullizer Feb 9, 2026
541dd60
Overhauled and updated file extension definition & MAG audio file tra…
Xeelee33 Feb 9, 2026
7c2eb0c
Update release_notes.md (#698)
paullizer Feb 9, 2026
e102efe
Merge branch 'Staging' into Development
paullizer Feb 9, 2026
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
49 changes: 43 additions & 6 deletions application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.237.007"

VERSION = "0.237.009"

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

Expand Down Expand Up @@ -123,11 +122,49 @@
CLIENTS = {}
CLIENTS_LOCK = threading.Lock()

ALLOWED_EXTENSIONS = {
'txt', 'pdf', 'doc', 'docm', 'docx', 'xlsx', 'xlsm', 'xls', 'csv', 'pptx', 'html', 'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'md', 'json',
'mp4', 'mov', 'avi', 'mkv', 'flv', 'mxf', 'gxf', 'ts', 'ps', '3gp', '3gpp', 'mpg', 'wmv', 'asf', 'm4a', 'm4v', 'isma', 'ismv',
'dvr-ms', 'wav', 'xml', 'yaml', 'yml', 'log'
# Base allowed extensions (always available)
BASE_ALLOWED_EXTENSIONS = {'txt', 'doc', 'docm', 'html', 'md', 'json', 'xml', 'yaml', 'yml', 'log'}
DOCUMENT_EXTENSIONS = {'pdf', 'docx', 'pptx', 'ppt'}
TABULAR_EXTENSIONS = {'csv', 'xlsx', 'xls', 'xlsm'}

# Updates to image, video, or audio extensions should also be made in static/js/chat/chat-enhanced-citations.js if the new file types can be natively rendered in the browser.
IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic'}

# Optional extensions by feature
VIDEO_EXTENSIONS = {
'mp4', 'mov', 'avi', 'mkv', 'flv', 'mxf', 'gxf', 'ts', 'ps', '3gp', '3gpp',
'mpg', 'wmv', 'asf', 'm4v', 'isma', 'ismv', 'dvr-ms', 'webm', 'mpeg'
}

AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'}

def get_allowed_extensions(enable_video=False, enable_audio=False):
"""
Get allowed file extensions based on feature flags.

Args:
enable_video: Whether video file support is enabled
enable_audio: Whether audio file support is enabled

Returns:
set: Allowed file extensions
"""
extensions = BASE_ALLOWED_EXTENSIONS.copy()
extensions.update(DOCUMENT_EXTENSIONS)
extensions.update(IMAGE_EXTENSIONS)
extensions.update(TABULAR_EXTENSIONS)

if enable_video:
extensions.update(VIDEO_EXTENSIONS)

if enable_audio:
extensions.update(AUDIO_EXTENSIONS)

return extensions

ALLOWED_EXTENSIONS = get_allowed_extensions(enable_video=True, enable_audio=True)

# Admin UI specific extensions (for logo/favicon uploads)
ALLOWED_EXTENSIONS_IMG = {'png', 'jpg', 'jpeg'}
MAX_CONTENT_LENGTH = 5000 * 1024 * 1024 # 5000 MB AKA 5 GB

Expand Down
159 changes: 138 additions & 21 deletions application/single_app/functions_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -4851,7 +4851,7 @@ def process_di_document(document_id, user_id, temp_file_path, original_filename,
is_pdf = file_ext == '.pdf'
is_word = file_ext in ('.docx', '.doc')
is_ppt = file_ext in ('.pptx', '.ppt')
is_image = file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif')
is_image = file_ext in tuple('.' + ext for ext in IMAGE_EXTENSIONS)

try:
if is_pdf:
Expand Down Expand Up @@ -5233,23 +5233,138 @@ def process_audio_document(
print(f"[Debug] Transcribing chunk {idx}: {chunk_path}")

# Get fresh config (tokens expire after ~1 hour)
speech_config = _get_speech_config(settings, endpoint, locale)
try:
speech_config = _get_speech_config(settings, endpoint, locale)
except Exception as e:
print(f"[Error] Failed to get speech config for chunk {idx}: {e}")
raise RuntimeError(f"Speech configuration failed for chunk {idx}: {e}")

audio_config = speechsdk.AudioConfig(filename=chunk_path)
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config,
audio_config=audio_config
)
try:
audio_config = speechsdk.AudioConfig(filename=chunk_path)
except Exception as e:
print(f"[Error] Failed to load audio file {chunk_path}: {e}")
raise RuntimeError(f"Audio file loading failed: {e}")

result = speech_recognizer.recognize_once()
if result.reason == speechsdk.ResultReason.RecognizedSpeech:
print(f"[Debug] Recognized: {result.text}")
all_phrases.append(result.text)
elif result.reason == speechsdk.ResultReason.NoMatch:
print(f"[Warning] No speech in {chunk_path}")
elif result.reason == speechsdk.ResultReason.Canceled:
print(f"[Error] {result.cancellation_details.reason}: {result.cancellation_details.error_details}")
raise RuntimeError(f"Transcription canceled for {chunk_path}: {result.cancellation_details.error_details}")
try:
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config,
audio_config=audio_config
)
except Exception as e:
print(f"[Error] Failed to create speech recognizer for chunk {idx}: {e}")
raise RuntimeError(f"Speech recognizer creation failed: {e}")

# Use continuous recognition instead of recognize_once
all_results = []
done = False
error_occurred = False
error_message = None

def stop_cb(evt):
nonlocal done
print(f"[Debug] Session stopped for chunk {idx}")
done = True

def recognized_cb(evt):
try:
if evt.result.reason == speechsdk.ResultReason.RecognizedSpeech:
all_results.append(evt.result.text)
print(f"[Debug] Recognized: {evt.result.text}")
elif evt.result.reason == speechsdk.ResultReason.NoMatch:
print(f"[Debug] No speech recognized in segment")
except Exception as e:
print(f"[Error] Error in recognized callback: {e}")
# Don't fail on individual recognition errors

def canceled_cb(evt):
nonlocal done, error_occurred, error_message
print(f"[Debug] Recognition canceled for chunk {idx}: {evt.cancellation_details.reason}")

if evt.cancellation_details.reason == speechsdk.CancellationReason.Error:
error_occurred = True
error_message = evt.cancellation_details.error_details
print(f"[Error] Recognition error: {error_message}")
elif evt.cancellation_details.reason == speechsdk.CancellationReason.EndOfStream:
print(f"[Debug] End of audio stream reached")

done = True

try:
# Connect callbacks
speech_recognizer.recognized.connect(recognized_cb)
speech_recognizer.session_stopped.connect(stop_cb)
speech_recognizer.canceled.connect(canceled_cb)

# Start continuous recognition
print(f"[Debug] Starting continuous recognition for chunk {idx}")
speech_recognizer.start_continuous_recognition()

# Wait for completion with timeout
import time
timeout_seconds = 600 # 10 minutes max per chunk
start_time = time.time()

while not done:
if time.time() - start_time > timeout_seconds:
print(f"[Error] Recognition timeout for chunk {idx}")
error_occurred = True
error_message = f"Recognition timed out after {timeout_seconds} seconds"
break
time.sleep(0.5)

# Stop recognition
try:
speech_recognizer.stop_continuous_recognition()
print(f"[Debug] Stopped continuous recognition for chunk {idx}")
except Exception as e:
print(f"[Warning] Error stopping recognition for chunk {idx}: {e}")
# Continue even if stop fails

# Check for errors after completion
if error_occurred:
raise RuntimeError(f"Recognition failed for chunk {idx}: {error_message}")

# Add all recognized phrases to the overall list
if all_results:
all_phrases.extend(all_results)
print(f"[Debug] Total phrases from chunk {idx}: {len(all_results)}")
else:
print(f"[Warning] No speech recognized in {chunk_path}")
# Continue to next chunk - empty result is not necessarily an error

except RuntimeError as e:
# Re-raise runtime errors (these are our custom errors)
raise
except Exception as e:
print(f"[Error] Unexpected error during recognition for chunk {idx}: {e}")
raise RuntimeError(f"Recognition failed unexpectedly for chunk {idx}: {e}")
finally:
# Cleanup: disconnect callbacks and dispose recognizer
try:
speech_recognizer.recognized.disconnect_all()
speech_recognizer.session_stopped.disconnect_all()
speech_recognizer.canceled.disconnect_all()
except Exception as e:
print(f"[Warning] Error disconnecting callbacks for chunk {idx}: {e}")

# # Get fresh config (tokens expire after ~1 hour)
# speech_config = _get_speech_config(settings, endpoint, locale)

# audio_config = speechsdk.AudioConfig(filename=chunk_path)
# speech_recognizer = speechsdk.SpeechRecognizer(
# speech_config=speech_config,
# audio_config=audio_config
# )

# result = speech_recognizer.recognize_once()
# if result.reason == speechsdk.ResultReason.RecognizedSpeech:
# print(f"[Debug] Recognized: {result.text}")
# all_phrases.append(result.text)
# elif result.reason == speechsdk.ResultReason.NoMatch:
# print(f"[Warning] No speech in {chunk_path}")
# elif result.reason == speechsdk.ResultReason.Canceled:
# print(f"[Error] {result.cancellation_details.reason}: {result.cancellation_details.error_details}")
# raise RuntimeError(f"Transcription canceled for {chunk_path}: {result.cancellation_details.error_details}")

else:
# Use the fast-transcription API if not in sovereign or custom cloud
Expand Down Expand Up @@ -5357,8 +5472,12 @@ def process_document_upload_background(document_id, user_id, temp_file_path, ori
enable_extract_meta_data = settings.get('enable_extract_meta_data', False) # Used by DI flow
max_file_size_bytes = settings.get('max_file_size_mb', 16) * 1024 * 1024

video_extensions = ('.mp4', '.mov', '.avi', '.mkv', '.flv')
audio_extensions = ('.mp3', '.wav', '.ogg', '.aac', '.flac', '.m4a')
# Get allowed extensions from config.py to determine which processing function to call
tabular_extensions = tuple('.' + ext for ext in TABULAR_EXTENSIONS)
image_extensions = tuple('.' + ext for ext in IMAGE_EXTENSIONS)
di_supported_extensions = tuple('.' + ext for ext in DOCUMENT_EXTENSIONS | IMAGE_EXTENSIONS)
video_extensions = tuple('.' + ext for ext in VIDEO_EXTENSIONS)
audio_extensions = tuple('.' + ext for ext in AUDIO_EXTENSIONS)

# --- Define update_document callback wrapper ---
# This makes it easier to pass the update function to helpers without repeating args
Expand Down Expand Up @@ -5402,8 +5521,6 @@ def update_doc_callback(**kwargs):

# --- 1. Dispatch to appropriate handler based on file type ---
# Note: .doc and .docm are handled separately by process_doc() using docx2txt
di_supported_extensions = ('.pdf', '.docx', '.pptx', '.ppt', '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif')
tabular_extensions = ('.csv', '.xlsx', '.xls', '.xlsm')

is_group = group_id is not None

Expand Down Expand Up @@ -5512,7 +5629,7 @@ def update_doc_callback(**kwargs):
final_status = "Processing complete"
if total_chunks_saved == 0:
# Provide more specific status if no chunks were saved
if file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif'):
if file_ext in image_extensions:
final_status = "Processing complete - no text found in image"
elif file_ext in tabular_extensions:
final_status = "Processing complete - no data rows found or file empty"
Expand Down
23 changes: 15 additions & 8 deletions application/single_app/route_backend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def result_requires_message_reload(result: Any) -> bool:
doc_results = list(cosmos_container.query_items(
query=doc_query, parameters=doc_params, enable_cross_partition_query=True
))
if doc_results:
if doc_results and 'workspace_search' in user_metadata:
doc_info = doc_results[0]
user_metadata['workspace_search']['document_name'] = doc_info.get('title') or doc_info.get('file_name')
user_metadata['workspace_search']['document_filename'] = doc_info.get('file_name')
Expand All @@ -465,18 +465,22 @@ def result_requires_message_reload(result: Any) -> bool:

if group_doc.get('name'):
group_name = group_doc.get('name')
user_metadata['workspace_search']['group_name'] = group_name
debug_print(f"Workspace search - set group_name to: {group_name}")
if 'workspace_search' in user_metadata:
user_metadata['workspace_search']['group_name'] = group_name
debug_print(f"Workspace search - set group_name to: {group_name}")
else:
debug_print(f"Workspace search - no name for group: {active_group_id}")
user_metadata['workspace_search']['group_name'] = None
if 'workspace_search' in user_metadata:
user_metadata['workspace_search']['group_name'] = None
else:
debug_print(f"Workspace search - no group found for id: {active_group_id}")
user_metadata['workspace_search']['group_name'] = None
if 'workspace_search' in user_metadata:
user_metadata['workspace_search']['group_name'] = None

except Exception as e:
debug_print(f"Error retrieving group details: {e}")
user_metadata['workspace_search']['group_name'] = None
if 'workspace_search' in user_metadata:
user_metadata['workspace_search']['group_name'] = None
import traceback
traceback.print_exc()

Expand All @@ -492,8 +496,11 @@ def result_requires_message_reload(result: Any) -> bool:
except Exception as e:
debug_print(f"Error checking public workspace status: {e}")

user_metadata['workspace_search']['active_public_workspace_id'] = active_public_workspace_id
else:
if 'workspace_search' in user_metadata:
user_metadata['workspace_search']['active_public_workspace_id'] = active_public_workspace_id

# Ensure workspace_search key always exists for consistency
if 'workspace_search' not in user_metadata:
user_metadata['workspace_search'] = {
'search_enabled': False
}
Expand Down
20 changes: 17 additions & 3 deletions application/single_app/route_backend_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,23 @@ def api_patch_user_document(document_id):
try:
# Log the metadata update transaction if any fields were updated
if updated_fields:
# Get document details for logging
doc = get_document(user_id, document_id)
if doc:
# Get document details for logging - handle tuple return
doc_response = get_document(user_id, document_id)
doc = None

# Handle tuple return (response, status_code)
if isinstance(doc_response, tuple):
resp, status_code = doc_response
if hasattr(resp, "get_json"):
doc = resp.get_json()
else:
doc = resp
elif hasattr(doc_response, "get_json"):
doc = doc_response.get_json()
else:
doc = doc_response

if doc and isinstance(doc, dict):
log_document_metadata_update_transaction(
user_id=user_id,
document_id=document_id,
Expand Down
Loading
Loading