Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
602d0ad
added group status (active, locked, upload disabled, and inactive)
paullizer Dec 21, 2025
64c2df0
added bulk member upload via csv for groups
paullizer Dec 21, 2025
ae7d1f0
add document metadata modified activity log tracking
paullizer Dec 21, 2025
59156be
activity logging for members deleted from groups
paullizer Dec 21, 2025
613144f
added group activity timeline
paullizer Dec 22, 2025
c801dea
added notification system
paullizer Dec 22, 2025
2da6ba7
added notifications for document upload to workspaces
paullizer Dec 22, 2025
71f1357
fixed badge sizing
paullizer Dec 22, 2025
6287da4
fixed url link
paullizer Dec 22, 2025
a8aa92a
fixed badge to not show with zero notifications
paullizer Dec 22, 2025
30287e0
Updated notification system
paullizer Dec 23, 2025
7ed89a1
Updated approval system
paullizer Dec 23, 2025
6cfa925
updated approval workflow
paullizer Dec 24, 2025
5f90672
updated notification workflow
paullizer Dec 24, 2025
6d64dba
Fixed set active bug on my public workspace page
paullizer Dec 27, 2025
30792e4
Added user retention policy, updated user profile page with dashboard…
paullizer Dec 28, 2025
d3339af
adding speed to text for chat UI
paullizer Dec 28, 2025
db4fadf
updated the speech wave form and input field
paullizer Jan 2, 2026
d0d482d
updated to transcribe entire recording
paullizer Jan 2, 2026
c2d6eaa
fixed bug creating new conversation with auto-send
paullizer Jan 2, 2026
059f1f1
add mic permissions
paullizer Jan 5, 2026
35cf17e
added stream token tracking
paullizer Jan 5, 2026
2ee7aae
Added public workspace reporting
paullizer Jan 5, 2026
ebb9e77
Updated AI search sizing analysis
paullizer Jan 5, 2026
e442c76
added management for public workspaces
paullizer Jan 6, 2026
a42800d
improved public workspace management includes stats and bulk actions
paullizer Jan 7, 2026
5a55f9a
updated groups dashboard for owners and admins with stats and bulk ac…
paullizer Jan 7, 2026
2feb9ec
added voice for ai to talk with users in chats
paullizer Jan 7, 2026
f68ce76
Auto Voice Response
paullizer Jan 7, 2026
6ccaea9
for speech service, added 429 randomized response pattern to prevent …
paullizer Jan 7, 2026
75c5472
updated admin settings for speech services and fixed dark mode for ra…
paullizer Jan 7, 2026
2d44e42
updated video extraction card
paullizer Jan 7, 2026
f1ea4c3
Added Control Center Admin and Dashboard Reader roles
paullizer Jan 8, 2026
d6ad5be
updated feedback and safety decorators so admins work unless required…
paullizer Jan 8, 2026
9f8250b
Updated and Validated logic for admin roles; control center, safety, …
paullizer Jan 8, 2026
a2ddf48
added support for control center admin and dashboard reader
paullizer Jan 8, 2026
f78c48c
Development (#566)
paullizer Jan 8, 2026
b9cb93e
updated tool tip to better inform user on status of ai response
paullizer Jan 8, 2026
17ea6ab
improve query parameters detection for swagger
paullizer Jan 8, 2026
d4f1a95
updated visual cue showing the ai is talking to the user
paullizer Jan 8, 2026
ef393b8
moved duplicates to shared js
paullizer Jan 13, 2026
0c2cc8a
replaced alert with toast.
paullizer Jan 13, 2026
5fba0bb
fixed and added log_event to exceptions
paullizer Jan 13, 2026
1a00ced
added @user_required and improved swagger generation
paullizer Jan 13, 2026
0c88dc6
Update route_frontend_profile.py
paullizer Jan 13, 2026
8c417ce
fixed swagger generation bug on affecting two apis
paullizer Jan 13, 2026
f9754ec
returned keyvault to admin settings ui
paullizer Jan 13, 2026
108dc40
Fixed bug when running local js
paullizer Jan 13, 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
180 changes: 154 additions & 26 deletions application/single_app/app.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,7 @@
from route_frontend_public_workspaces import *
from route_frontend_safety import *
from route_frontend_feedback import *
from route_frontend_notifications import *

from route_backend_chats import *
from route_backend_conversations import *
Expand All@@ -50,20 +51,30 @@
from route_backend_prompts import *
from route_backend_group_prompts import *
from route_backend_control_center import *
from route_backend_notifications import *
from route_backend_retention_policy import *
from route_backend_plugins import bpap as admin_plugins_bp, bpdp as dynamic_plugins_bp
from route_backend_agents import bpa as admin_agents_bp
from route_backend_public_workspaces import *
from route_backend_public_documents import *
from route_backend_public_prompts import *
from route_backend_speech import register_route_backend_speech
from route_backend_tts import register_route_backend_tts
from route_enhanced_citations import register_enhanced_citations_routes
from plugin_validation_endpoint import plugin_validation_bp
from route_openapi import register_openapi_routes
from route_migration import bp_migration
from route_plugin_logging import bpl as plugin_logging_bp
from functions_debug import debug_print

from opentelemetry.instrumentation.flask import FlaskInstrumentor

app = Flask(__name__, static_url_path='/static', static_folder='static')

disable_flask_instrumentation = os.environ.get("DISABLE_FLASK_INSTRUMENTATION", "0")
if not (disable_flask_instrumentation == "1" or disable_flask_instrumentation.lower() == "true"):
FlaskInstrumentor().instrument_app(app)

app.config['EXECUTOR_TYPE'] = EXECUTOR_TYPE
app.config['EXECUTOR_MAX_WORKERS'] = EXECUTOR_MAX_WORKERS
executor = Executor()
Expand DownExpand Up@@ -95,6 +106,12 @@
# Register Enhanced Citations routes
register_enhanced_citations_routes(app)

# Register Speech routes
register_route_backend_speech(app)

# Register TTS routes
register_route_backend_tts(app)

# Register Swagger documentation routes
from swagger_wrapper import register_swagger_routes
register_swagger_routes(app)
Expand All@@ -121,38 +138,54 @@ def configure_sessions(settings):
redis_auth_type = settings.get('redis_auth_type', 'key').strip().lower()

if redis_url:
app.config['SESSION_TYPE'] = 'redis'
if redis_auth_type == 'managed_identity':
print("Redis enabled using Managed Identity")
from config import get_redis_cache_infrastructure_endpoint
credential = DefaultAzureCredential()
redis_hostname = redis_url.split('.')[0]
cache_endpoint = get_redis_cache_infrastructure_endpoint(redis_hostname)
token = credential.get_token(cache_endpoint)
app.config['SESSION_REDIS'] = Redis(
host=redis_url,
port=6380,
db=0,
password=token.token,
ssl=True
)
else:
redis_key = settings.get('redis_key', '').strip()
print("Redis enabled using Access Key")
app.config['SESSION_REDIS'] = Redis(
host=redis_url,
port=6380,
db=0,
password=redis_key,
ssl=True
)
redis_client = None
try:
if redis_auth_type == 'managed_identity':
print("Redis enabled using Managed Identity")
from config import get_redis_cache_infrastructure_endpoint
credential = DefaultAzureCredential()
redis_hostname = redis_url.split('.')[0]
cache_endpoint = get_redis_cache_infrastructure_endpoint(redis_hostname)
token = credential.get_token(cache_endpoint)
redis_client = Redis(
host=redis_url,
port=6380,
db=0,
password=token.token,
ssl=True,
socket_connect_timeout=5,
socket_timeout=5
)
else:
redis_key = settings.get('redis_key', '').strip()
print("Redis enabled using Access Key")
redis_client = Redis(
host=redis_url,
port=6380,
db=0,
password=redis_key,
ssl=True,
socket_connect_timeout=5,
socket_timeout=5
)

# Test the connection
redis_client.ping()
print("✅ Redis connection successful")
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis_client

except Exception as redis_error:
print(f"⚠️ WARNING: Redis connection failed: {redis_error}")
print("Falling back to filesystem sessions for reliability")
app.config['SESSION_TYPE'] = 'filesystem'
else:
print("Redis enabled but URL missing; falling back to filesystem.")
app.config['SESSION_TYPE'] = 'filesystem'
else:
app.config['SESSION_TYPE'] = 'filesystem'
except Exception as e:
print(f"WARNING: Session configuration error; falling back to filesystem: {e}")
print(f"⚠️ WARNING: Session configuration error; falling back to filesystem: {e}")
app.config['SESSION_TYPE'] = 'filesystem'

# Initialize session interface
Expand DownExpand Up@@ -242,6 +275,86 @@ def check_logging_timers():
timer_thread.start()
print("Logging timer background task started.")

# Background task to check for expired approval requests
def check_expired_approvals():
"""Background task that checks for expired approval requests and auto-denies them"""
while True:
try:
from functions_approvals import auto_deny_expired_approvals
denied_count = auto_deny_expired_approvals()
if denied_count > 0:
print(f"Auto-denied {denied_count} expired approval request(s).")
except Exception as e:
print(f"Error in approval expiration check: {e}")

# Check every 6 hours (21600 seconds)
time.sleep(21600)
Comment thread
paullizer marked this conversation as resolved.

# Start the approval expiration check thread
approval_thread = threading.Thread(target=check_expired_approvals, daemon=True)
approval_thread.start()
print("Approval expiration background task started.")

# Background task to check retention policy execution time
def check_retention_policy():
"""Background task that executes retention policy at scheduled time"""
while True:
try:
settings = get_settings()

# Check if any retention policy is enabled
personal_enabled = settings.get('enable_retention_policy_personal', False)
group_enabled = settings.get('enable_retention_policy_group', False)
public_enabled = settings.get('enable_retention_policy_public', False)

if personal_enabled or group_enabled or public_enabled:
current_time = datetime.now(timezone.utc)
execution_hour = settings.get('retention_policy_execution_hour', 2)

# Check if we're in the execution hour
if current_time.hour == execution_hour:
# Check if we haven't run today yet
last_run = settings.get('retention_policy_last_run')
should_run = False

if last_run:
try:
last_run_dt = datetime.fromisoformat(last_run)
# Run if last run was more than 23 hours ago
if (current_time - last_run_dt).total_seconds() > (23 * 3600):
should_run = True
except:
should_run = True
else:
should_run = True

if should_run:
print(f"Executing scheduled retention policy at {current_time.isoformat()}")
from functions_retention_policy import execute_retention_policy
results = execute_retention_policy(manual_execution=False)

if results.get('success'):
print(f"Retention policy execution completed: "
f"{results['personal']['conversations']} personal conversations, "
f"{results['personal']['documents']} personal documents, "
f"{results['group']['conversations']} group conversations, "
f"{results['group']['documents']} group documents, "
f"{results['public']['conversations']} public conversations, "
f"{results['public']['documents']} public documents deleted.")
else:
print(f"Retention policy execution failed: {results.get('errors')}")

except Exception as e:
print(f"Error in retention policy check: {e}")

# Check every hour
time.sleep(3600)
Comment thread
paullizer marked this conversation as resolved.

# Start the retention policy check thread
retention_thread = threading.Thread(target=check_retention_policy, daemon=True)
retention_thread.start()
print("Retention policy background task started.")

# Initialize Semantic Kernel and plugins
enable_semantic_kernel = settings.get('enable_semantic_kernel', False)
per_user_semantic_kernel = settings.get('per_user_semantic_kernel', False)
Expand DownExpand Up@@ -330,6 +443,7 @@ def markdown_filter(text):

# =================== Default Routes =====================
@app.route('/')
@swagger_route(security=get_auth_security())
def index():
settings = get_settings()
public_settings = sanitize_settings_for_user(settings)
Expand All@@ -343,14 +457,17 @@ def index():
return render_template('index.html', app_settings=public_settings, landing_html=landing_html)

@app.route('/robots933456.txt')
@swagger_route(security=get_auth_security())
def robots():
return send_from_directory('static', 'robots.txt')

@app.route('/favicon.ico')
@swagger_route(security=get_auth_security())
def favicon():
return send_from_directory('static', 'favicon.ico')

@app.route('/static/js/<path:filename>')
@swagger_route(security=get_auth_security())
def serve_js_modules(filename):
"""Serve JavaScript modules with correct MIME type."""
from flask import send_from_directory, Response
Expand All@@ -363,10 +480,12 @@ def serve_js_modules(filename):
return send_from_directory('static/js', filename)

@app.route('/acceptable_use_policy.html')
@swagger_route(security=get_auth_security())
def acceptable_use_policy():
return render_template('acceptable_use_policy.html')

@app.route('/api/semantic-kernel/plugins')
@swagger_route(security=get_auth_security())
def list_semantic_kernel_plugins():
"""Test endpoint: List loaded Semantic Kernel plugins and their functions."""
global kernel
Expand DownExpand Up@@ -413,6 +532,9 @@ def list_semantic_kernel_plugins():
# ------------------- Feedback Routes -------------------
register_route_frontend_feedback(app)

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

# ------------------- API Chat Routes --------------------
register_route_backend_chats(app)

Expand DownExpand Up@@ -452,6 +574,12 @@ def list_semantic_kernel_plugins():
# ------------------- API Control Center Routes ---------
register_route_backend_control_center(app)

# ------------------- API Notifications Routes ----------
register_route_backend_notifications(app)

# ------------------- API Retention Policy Routes --------
register_route_backend_retention_policy(app)

# ------------------- API Public Workspaces Routes -------
register_route_backend_public_workspaces(app)

Expand Down
27 changes: 21 additions & 6 deletions application/single_app/config.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,7 +64,6 @@
from io import BytesIO
from typing import List

import azure.cognitiveservices.speech as speechsdk
from azure.cosmos import CosmosClient, PartitionKey, exceptions
from azure.cosmos.exceptions import CosmosResourceNotFoundError
from azure.core.credentials import AzureKeyCredential
Expand All@@ -89,7 +88,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.233.318"
VERSION = "0.234.225"


SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
Expand All@@ -102,10 +101,13 @@
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com https://stackpath.bootstrapcdn.com; "
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
#"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com https://stackpath.bootstrapcdn.com; "
"style-src 'self' 'unsafe-inline'; "
#"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com; "
"img-src 'self' data: https: blob:; "
"font-src 'self' https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com; "
"font-src 'self'; "
#"font-src 'self' https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com; "
"connect-src 'self' https: wss: ws:; "
"media-src 'self' blob:; "
"object-src 'none'; "
Expand DownExpand Up@@ -185,7 +187,6 @@
credential_scopes=[resource_manager + "/.default"]
cognitive_services_scope = "https://cognitiveservices.azure.com/.default"
video_indexer_endpoint = "https://api.videoindexer.ai"
search_resource_manager = "https://search.azure.com"
KEY_VAULT_DOMAIN = ".vault.azure.net"

def get_redis_cache_infrastructure_endpoint(redis_hostname: str) -> str:
Expand DownExpand Up@@ -394,6 +395,20 @@ def get_redis_cache_infrastructure_endpoint(redis_hostname: str) -> str:
partition_key=PartitionKey(path="/user_id")
)

cosmos_notifications_container_name = "notifications"
cosmos_notifications_container = cosmos_database.create_container_if_not_exists(
id=cosmos_notifications_container_name,
partition_key=PartitionKey(path="/user_id"),
default_ttl=-1 # TTL disabled by default, enabled per-document
)

cosmos_approvals_container_name = "approvals"
cosmos_approvals_container = cosmos_database.create_container_if_not_exists(
id=cosmos_approvals_container_name,
partition_key=PartitionKey(path="/group_id"),
default_ttl=-1 # TTL disabled by default, enabled per-document for auto-cleanup
)

def ensure_custom_logo_file_exists(app, settings):
"""
If custom_logo_base64 or custom_logo_dark_base64 is present in settings, ensure the appropriate
Expand Down
Loading