From 2f19f88735cfae3ab89ebbcd5fa9d030084e51ab Mon Sep 17 00:00:00 2001 From: Joshua Wilshere Date: Fri, 6 Feb 2026 23:49:36 +0000 Subject: [PATCH 1/3] Overhauled how file extensions are defined and referenced to reduce duplication and omission errors (mp3 was missing from list of allowed file type uploads). Added additional supported video file extensions and .heic image extension. Also rebuilt audio file transcription in MAG to use continuious recognition API instead of recognize_once() --- application/single_app/config.py | 46 ++++- application/single_app/functions_documents.py | 159 +++++++++++++++--- .../single_app/route_enhanced_citations.py | 11 +- .../single_app/route_frontend_chats.py | 10 +- .../route_frontend_group_workspaces.py | 29 +--- .../route_frontend_public_workspaces.py | 17 +- .../single_app/route_frontend_workspace.py | 17 +- .../static/js/chat/chat-enhanced-citations.js | 4 +- 8 files changed, 209 insertions(+), 84 deletions(-) diff --git a/application/single_app/config.py b/application/single_app/config.py index 2303a89e9..788dbd4bf 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -122,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 +IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic'} + +# Optional extensions by feature +VIDEO_EXTENSIONS = { + 'flv', 'mxf', 'ts', '3gp', 'mpg', 'mpeg', 'wmv', 'asf', + 'avi', 'mp4', 'm4v', 'dvr-ms', 'mkv', 'mov', 'webm' } + +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 diff --git a/application/single_app/functions_documents.py b/application/single_app/functions_documents.py index 017b819fa..9ae01a622 100644 --- a/application/single_app/functions_documents.py +++ b/application/single_app/functions_documents.py @@ -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: @@ -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 @@ -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 @@ -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 @@ -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" diff --git a/application/single_app/route_enhanced_citations.py b/application/single_app/route_enhanced_citations.py index 684559db7..c81ef2252 100644 --- a/application/single_app/route_enhanced_citations.py +++ b/application/single_app/route_enhanced_citations.py @@ -15,7 +15,7 @@ from functions_group import get_user_groups from functions_public_workspaces import get_user_visible_public_workspace_ids_from_settings from swagger_wrapper import swagger_route, get_auth_security -from config import CLIENTS, storage_account_user_documents_container_name, storage_account_group_documents_container_name, storage_account_public_documents_container_name +from config import CLIENTS, storage_account_user_documents_container_name, storage_account_group_documents_container_name, storage_account_public_documents_container_name, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, AUDIO_EXTENSIONS from functions_debug import debug_print def register_enhanced_citations_routes(app): @@ -49,9 +49,8 @@ def get_enhanced_citation_image(): # Check if it's an image file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - image_extensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif'] - if ext not in image_extensions: + if ext not in IMAGE_EXTENSIONS: return jsonify({"error": "File is not an image"}), 400 # Serve the image content directly @@ -88,9 +87,8 @@ def get_enhanced_citation_video(): # Check if it's a video file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - video_extensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv'] - if ext not in video_extensions: + if ext not in VIDEO_EXTENSIONS: return jsonify({"error": "File is not a video"}), 400 # Serve the video content directly @@ -127,9 +125,8 @@ def get_enhanced_citation_audio(): # Check if it's an audio file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - audio_extensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'] - if ext not in audio_extensions: + if ext not in AUDIO_EXTENSIONS: return jsonify({"error": "File is not an audio file"}), 400 # Serve the audio content directly diff --git a/application/single_app/route_frontend_chats.py b/application/single_app/route_frontend_chats.py index af3ce9b19..8e34c0f43 100644 --- a/application/single_app/route_frontend_chats.py +++ b/application/single_app/route_frontend_chats.py @@ -131,9 +131,9 @@ def upload_file(): try: # Check if this is an image file - is_image_file = file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif'] + is_image_file = file_ext in IMAGE_EXTENSIONS - if file_ext in ['.pdf', '.docx', '.pptx', '.html', '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif']: + if file_ext in ['.pdf', '.docx', '.pptx', '.ppt', '.html'] or is_image_file: extracted_content_raw = extract_content_with_azure_di(temp_file_path) # Convert pages_data list to string @@ -209,7 +209,7 @@ def upload_file(): elif file_ext in ['.xml', '.yaml', '.yml', '.log']: # Handle XML, YAML, and LOG files as text for inline chat extracted_content = extract_text_file(temp_file_path) - elif file_ext in ['.csv', '.xls', '.xlsx', '.xlsm']: + elif file_ext in TABULAR_EXTENSIONS: extracted_content = extract_table_file(temp_file_path, file_ext) is_table = True else: @@ -685,8 +685,8 @@ def view_document(): is_pdf = file_ext == '.pdf' is_word = file_ext in ('.docx', '.doc', '.docm') is_ppt = file_ext in ('.pptx', '.ppt') - is_image = file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.gif', '.webp') # Added more image types - is_text = file_ext in ('.txt', '.md', '.csv', '.json', '.log', '.xml', '.yaml', '.yml', '.html', '.htm') # Common text-based types + is_image = file_ext.lstrip('.') in (IMAGE_EXTENSIONS | {'gif', 'webp'}) # Added more image types + is_text = file_ext.lstrip('.') in (BASE_ALLOWED_EXTENSIONS - {'doc', 'docm'}) # Common text-based types try: # Download the file to the specified location diff --git a/application/single_app/route_frontend_group_workspaces.py b/application/single_app/route_frontend_group_workspaces.py index 75996d840..850cc8d04 100644 --- a/application/single_app/route_frontend_group_workspaces.py +++ b/application/single_app/route_frontend_group_workspaces.py @@ -45,30 +45,13 @@ def group_workspaces(): ) legacy_count = legacy_docs_from_cosmos[0] if legacy_docs_from_cosmos else 0 - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] - allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'group_workspaces.html', settings=public_settings, diff --git a/application/single_app/route_frontend_public_workspaces.py b/application/single_app/route_frontend_public_workspaces.py index 102354449..05d5b982a 100644 --- a/application/single_app/route_frontend_public_workspaces.py +++ b/application/single_app/route_frontend_public_workspaces.py @@ -69,18 +69,13 @@ def public_workspaces(): enable_video_file_support = settings.get('enable_video_file_support', False) enable_audio_file_support = settings.get('enable_audio_file_support', False) - # Build allowed extensions string as in workspace.html - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'public_workspaces.html', settings=public_settings, diff --git a/application/single_app/route_frontend_workspace.py b/application/single_app/route_frontend_workspace.py index 47f121e08..2ca1aad98 100644 --- a/application/single_app/route_frontend_workspace.py +++ b/application/single_app/route_frontend_workspace.py @@ -43,18 +43,13 @@ def workspace(): ) legacy_count = legacy_docs_from_cosmos[0] if legacy_docs_from_cosmos else 0 - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'workspace.html', settings=public_settings, diff --git a/application/single_app/static/js/chat/chat-enhanced-citations.js b/application/single_app/static/js/chat/chat-enhanced-citations.js index 18c75229c..f287e47a1 100644 --- a/application/single_app/static/js/chat/chat-enhanced-citations.js +++ b/application/single_app/static/js/chat/chat-enhanced-citations.js @@ -15,8 +15,8 @@ export function getFileType(fileName) { const ext = fileName.toLowerCase().split('.').pop(); - const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif']; - const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv']; + const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic']; + const videoExtensions = ['flv', 'mxf', 'ts', '3gp', 'mpg', 'mpeg', 'wmv', 'asf', 'avi', 'mp4', 'm4v', 'dvr-ms', 'mkv', 'mov', 'webm']; const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a']; if (imageExtensions.includes(ext)) return 'image'; From 833517441bd2bf30f428d779d003ac0f8c560bbe Mon Sep 17 00:00:00 2001 From: Joshua Wilshere Date: Sat, 7 Feb 2026 01:19:31 +0000 Subject: [PATCH 2/3] Adjustments to file extensions based on testing and review --- application/single_app/config.py | 6 +++--- .../single_app/static/js/chat/chat-enhanced-citations.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/single_app/config.py b/application/single_app/config.py index 788dbd4bf..9cbad1371 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -127,13 +127,13 @@ 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 +# 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 = { - 'flv', 'mxf', 'ts', '3gp', 'mpg', 'mpeg', 'wmv', 'asf', - 'avi', 'mp4', 'm4v', 'dvr-ms', 'mkv', 'mov', 'webm' + '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'} diff --git a/application/single_app/static/js/chat/chat-enhanced-citations.js b/application/single_app/static/js/chat/chat-enhanced-citations.js index f287e47a1..dcda708be 100644 --- a/application/single_app/static/js/chat/chat-enhanced-citations.js +++ b/application/single_app/static/js/chat/chat-enhanced-citations.js @@ -15,8 +15,8 @@ export function getFileType(fileName) { const ext = fileName.toLowerCase().split('.').pop(); - const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic']; - const videoExtensions = ['flv', 'mxf', 'ts', '3gp', 'mpg', 'mpeg', 'wmv', 'asf', 'avi', 'mp4', 'm4v', 'dvr-ms', 'mkv', 'mov', 'webm']; + const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif']; + const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv', 'm4v', '3gp']; const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a']; if (imageExtensions.includes(ext)) return 'image'; From 13644dce59817c8d0dc7b174b12c441d797c7dff Mon Sep 17 00:00:00 2001 From: Joshua Wilshere Date: Sat, 7 Feb 2026 01:55:25 +0000 Subject: [PATCH 3/3] Bugfix - fixed error thrown when saving file metadata edits --- .../single_app/route_backend_documents.py | 20 ++++++- .../route_backend_group_documents.py | 53 ++++++++++++------ .../route_backend_public_documents.py | 56 +++++++++++++------ 3 files changed, 91 insertions(+), 38 deletions(-) diff --git a/application/single_app/route_backend_documents.py b/application/single_app/route_backend_documents.py index 072577d6c..31619f693 100644 --- a/application/single_app/route_backend_documents.py +++ b/application/single_app/route_backend_documents.py @@ -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, diff --git a/application/single_app/route_backend_group_documents.py b/application/single_app/route_backend_group_documents.py index 194b5a6b9..68a1c0fa5 100644 --- a/application/single_app/route_backend_group_documents.py +++ b/application/single_app/route_backend_group_documents.py @@ -416,24 +416,43 @@ def api_patch_group_document(document_id): ) updated_fields['authors'] = authors_list - # Log the metadata update transaction if any fields were updated - if updated_fields: + # Save updates back to Cosmos + try: + # Log the metadata update transaction if any fields were updated + if updated_fields: + # Get document details for logging - handle tuple return # Get document details for logging - from functions_documents import get_document - doc = get_document(user_id, document_id, group_id=active_group_id) - if doc: - from functions_activity_logging import log_document_metadata_update_transaction - log_document_metadata_update_transaction( - user_id=user_id, - document_id=document_id, - workspace_type='group', - file_name=doc.get('file_name', 'Unknown'), - updated_fields=updated_fields, - file_type=doc.get('file_type'), - group_id=active_group_id - ) - - return jsonify({'message': 'Group document metadata updated successfully'}), 200 + from functions_documents import get_document + doc_response = get_document(user_id, document_id, group_id=active_group_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): + from functions_activity_logging import log_document_metadata_update_transaction + log_document_metadata_update_transaction( + user_id=user_id, + document_id=document_id, + workspace_type='group', + file_name=doc.get('file_name', 'Unknown'), + updated_fields=updated_fields, + file_type=doc.get('file_type'), + group_id=active_group_id + ) + + return jsonify({'message': 'Group document metadata updated successfully'}), 200 + except Exception as e: + return jsonify({'Error updating Group document metadata': str(e)}), 500 except Exception as e: return jsonify({'error': str(e)}), 500 diff --git a/application/single_app/route_backend_public_documents.py b/application/single_app/route_backend_public_documents.py index 9e228acd9..a209e9a20 100644 --- a/application/single_app/route_backend_public_documents.py +++ b/application/single_app/route_backend_public_documents.py @@ -299,25 +299,45 @@ def api_patch_public_document(doc_id): update_document(document_id=doc_id, public_workspace_id=active_ws, user_id=user_id, document_classification=data['document_classification']) updated_fields['document_classification'] = data['document_classification'] - # Log the metadata update transaction if any fields were updated - if updated_fields: - from functions_documents import get_document - from functions_activity_logging import log_document_metadata_update_transaction - doc = get_document(user_id, doc_id, public_workspace_id=active_ws) - if doc: - log_document_metadata_update_transaction( - user_id=user_id, - document_id=doc_id, - workspace_type='public', - file_name=doc.get('file_name', 'Unknown'), - updated_fields=updated_fields, - file_type=doc.get('file_type'), - public_workspace_id=active_ws - ) - - return jsonify({'message':'Metadata updated'}), 200 + # Save updates back to Cosmos + try: + # Log the metadata update transaction if any fields were updated + if updated_fields: + # Get document details for logging - handle tuple return + # Get document details for logging + from functions_documents import get_document + doc_response = get_document(user_id, doc_id, public_workspace_id=active_ws) + 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): + from functions_activity_logging import log_document_metadata_update_transaction + log_document_metadata_update_transaction( + user_id=user_id, + document_id=doc_id, + workspace_type='public', + file_name=doc.get('file_name', 'Unknown'), + updated_fields=updated_fields, + file_type=doc.get('file_type'), + public_workspace_id=active_ws + ) + + return jsonify({'message': 'Public document metadata updated successfully'}), 200 + except Exception as e: + return jsonify({'Error updating Public document metadata': str(e)}), 500 except Exception as e: - return jsonify({'error':str(e)}), 500 + return jsonify({'error': str(e)}), 500 @app.route('/api/public_documents/', methods=['DELETE']) @swagger_route(security=get_auth_security())