diff --git a/application/single_app/admin_settings_nav.py b/application/single_app/admin_settings_nav.py new file mode 100644 index 000000000..84229254c --- /dev/null +++ b/application/single_app/admin_settings_nav.py @@ -0,0 +1,586 @@ +# admin_settings_nav.py +"""Single source of truth for the Admin Settings navigation. + +The top tab strip and the sidebar previously maintained the same +structure twice, by hand, and had already drifted: tab order differed +between them and three tabs carried different labels in each. Both now +render from this one definition, so they cannot disagree. + +Structure is GROUP -> TAB -> SECTION. A section points at a card id in +the rendered page; card ids are stable and are what cross-references +resolve against, so cards can move between tabs without breaking links. +""" + + +ADMIN_NAV = [ + { + "id": "appearance", + "label": "Appearance", + "icon": "bi-palette", + "tabs": [ + { + "id": "branding", + "label": "Branding", + "icon": "bi-palette", + "sections": [ + {"id": "branding-section", "label": "Branding", "icon": "bi-palette"}, + {"id": "home-page-text-section", "label": "Home Page Text", "icon": "bi-house"}, + {"id": "appearance-section", "label": "Appearance", "icon": "bi-brush"}, + ], + }, + { + # Everything the app states to the user before or during use: + # banners, notices and the agreements they must accept. + "id": "notices", + "label": "Notices & Agreements", + "icon": "bi-megaphone", + "sections": [ + {"id": "classification-banner-section", "label": "Classification Banner", "icon": "bi-shield-exclamation"}, + {"id": "ai-notice-section", "label": "Chat AI Notice", "icon": "bi-robot"}, + {"id": "terms-of-use-section", "label": "Terms of Use", "icon": "bi-door-open"}, + {"id": "user-agreement-section", "label": "User Agreement", "icon": "bi-file-earmark-check"}, + ], + }, + { + "id": "custom-pages", + "label": "Pages & Links", + "icon": "bi-window-plus", + "sections": [ + {"id": "custom-pages-section", "label": "Static Pages", "icon": "bi-file-earmark-richtext"}, + {"id": "external-links-section", "label": "External Links", "icon": "bi-box-arrow-up-right"}, + ], + }, + ], + }, + { + "id": "chat", + "label": "Chat", + "icon": "bi-chat-square-text", + "tabs": [ + { + "id": "chat-experience", + "label": "Chat Experience", + "icon": "bi-chat-square-dots", + "sections": [ + {"id": "processing-thoughts-section", "label": "Processing Thoughts", "icon": "bi-stars"}, + {"id": "chat-file-uploads-section", "label": "Chat File Uploads", "icon": "bi-paperclip"}, + {"id": "conversation-contents-drawer-section", "label": "Conversation Contents Drawer", "icon": "bi-list-nested"}, + {"id": "workspace-scope-lock-section", "label": "Workspace Scope Lock", "icon": "bi-lock"}, + {"id": "conversation-history-section", "label": "Conversation History", "icon": "bi-clock-history"}, + {"id": "default-system-prompt-section", "label": "Default System Prompt", "icon": "bi-chat-square-quote"}, + ], + }, + { + "id": "feedback-alerts", + "label": "Feedback & Alerts", + "icon": "bi-chat-square-heart", + "sections": [ + {"id": "user-feedback-section", "label": "User Feedback", "icon": "bi-chat-square-heart"}, + {"id": "desktop-notifications-section", "label": "Desktop Conversation Notifications", "icon": "bi-bell"}, + ], + }, + { + "id": "citation", + "label": "Citations", + "icon": "bi-quote", + "sections": [ + {"id": "standard-citations-section", "label": "Standard", "icon": "bi-quote"}, + {"id": "enhanced-citations-section", "label": "Enhanced", "icon": "bi-star"}, + ], + }, + ], + }, + { + "id": "ai-models", + "label": "AI Models", + "icon": "bi-cpu", + "tabs": [ + { + # The Chat Model card is reached through the legacy model + # settings dialog opened from here, so it is listed with the + # endpoints rather than as a tab of its own. + "id": "model-endpoints", + "label": "Model Endpoints", + "icon": "bi-hdd-network", + "sections": [ + {"id": "multi-endpoint-configuration", "label": "Model Endpoints", "icon": "bi-hdd-network"}, + {"id": "gpt-config", "label": "Chat Model", "icon": "bi-chat-square-text"}, + ], + }, + { + "id": "embeddings", + "label": "Embeddings", + "icon": "bi-vector-pen", + "sections": [ + {"id": "embeddings-config", "label": "Embeddings", "icon": "bi-vector-pen"}, + ], + }, + { + "id": "image-generation", + "label": "Image Generation", + "icon": "bi-image", + "sections": [ + {"id": "image-config", "label": "Image Generation", "icon": "bi-image"}, + ], + }, + ], + }, + { + "id": "agents-actions", + "label": "Agents & Actions", + "icon": "bi-robot", + "tabs": [ + { + "id": "agents", + "label": "Agents", + "icon": "bi-robot", + "sections": [ + {"id": "agents-config", "label": "Agents Configuration", "icon": "bi-robot"}, + {"id": "agent-template-approvals-section", "label": "Agent Template Approvals", "icon": "bi-layers", "condition": "enable_agent_template_gallery"}, + ], + }, + { + "id": "actions", + "label": "Actions", + "icon": "bi-plugin", + "sections": [ + {"id": "document-action-capabilities-card", "label": "Document Action Capabilities", "icon": "bi-files"}, + {"id": "actions-config", "label": "Actions Configuration", "icon": "bi-plugin"}, + ], + }, + { + # The whole tab is behind mcp_ui_enabled, so the tab carries the + # condition rather than the single section inside it. + "id": "inbound-mcp", + "label": "Inbound MCP", + "icon": "bi-diagram-3", + "condition": "mcp_ui_enabled", + "sections": [ + {"id": "inbound-mcp-configuration", "label": "Inbound MCP", "icon": "bi-diagram-3", "condition": "mcp_ui_enabled"}, + ], + }, + ], + }, + { + "id": "workspaces", + "label": "Workspaces", + "icon": "bi-folder", + "tabs": [ + { + "id": "workspace-types", + "label": "Workspace Types", + "icon": "bi-folder", + "sections": [ + {"id": "personal-workspaces-section", "label": "Personal Workspaces", "icon": "bi-person"}, + {"id": "group-workspaces-section", "label": "Group Workspaces", "icon": "bi-people"}, + {"id": "public-workspaces-section", "label": "Public Workspaces", "icon": "bi-globe"}, + ], + }, + { + # Who may take a file out of a workspace, and who must approve + # it first. + "id": "files-sharing", + "label": "Files & Sharing", + "icon": "bi-share", + "sections": [ + {"id": "file-download-settings-section", "label": "File Downloads", "icon": "bi-download"}, + {"id": "file-sharing-section", "label": "File Sharing", "icon": "bi-share"}, + {"id": "shared-conversation-file-approvals-section", "label": "Shared Conversation File Approvals", "icon": "bi-check2-square"}, + {"id": "file-size-limit-section", "label": "Maximum File Size", "icon": "bi-file-earmark-arrow-up"}, + ], + }, + { + "id": "workspace-identities", + "label": "Global Identities", + "icon": "bi-person-badge", + "sections": [], + }, + ], + }, + { + # Workflow drives approvals and assignment across workspaces and is + # large enough to stand on its own rather than sit inside Workspaces. + "id": "workflow", + "label": "Workflow", + "icon": "bi-diagram-3", + "tabs": [ + { + "id": "workflow", + "label": "Workflow", + "icon": "bi-diagram-3", + "sections": [ + {"id": "workflow-settings-section", "label": "Workflow", "icon": "bi-diagram-3"}, + ], + }, + ], + }, + { + "id": "knowledge", + "label": "Knowledge", + "icon": "bi-search", + "tabs": [ + { + # Reaching outside the tenant for material. + "id": "web-research", + "label": "Web & Research", + "icon": "bi-globe", + "sections": [ + {"id": "web-search-section", "label": "Web Search", "icon": "bi-globe"}, + {"id": "url-access-section", "label": "URL Access", "icon": "bi-link-45deg"}, + {"id": "source-review-section", "label": "Deep Research", "icon": "bi-binoculars"}, + ], + }, + { + "id": "search-index", + "label": "Search Index", + "icon": "bi-search", + "sections": [ + {"id": "azure-ai-search-section", "label": "Azure AI Search", "icon": "bi-search"}, + ], + }, + { + # Turning a document into something searchable. + "id": "extraction", + "label": "Document Extraction", + "icon": "bi-file-earmark-text", + "sections": [ + {"id": "document-intelligence-section", "label": "Document Intelligence", "icon": "bi-file-earmark-text"}, + {"id": "chunk-size-section", "label": "Chunk Sizes", "icon": "bi-collection"}, + {"id": "metadata-extraction-section", "label": "Metadata Extraction", "icon": "bi-file-earmark-code"}, + {"id": "multimodal-vision-section", "label": "Multi-Modal Vision Analysis", "icon": "bi-eye"}, + ], + }, + { + # Voice and video are extraction pipelines, not chat features. + "id": "audio-video", + "label": "Audio & Video", + "icon": "bi-play-circle", + "sections": [ + {"id": "video-intelligence-section", "label": "AI Video Intelligence", "icon": "bi-play-circle"}, + {"id": "ai-voice-chat-section", "label": "AI Voice Conversations", "icon": "bi-mic"}, + ], + }, + { + "id": "file-sync", + "label": "File Sync", + "icon": "bi-arrow-repeat", + "sections": [ + {"id": "file-sync-section", "label": "File Sync", "icon": "bi-arrow-repeat"}, + {"id": "file-sync-source-types-section", "label": "Visible Source Types", "icon": "bi-sliders"}, + {"id": "file-sync-personal-section", "label": "Personal Workspace Sync", "icon": "bi-person"}, + {"id": "file-sync-group-section", "label": "Group Workspace Sync", "icon": "bi-people"}, + {"id": "file-sync-public-section", "label": "Public Workspace Sync", "icon": "bi-globe"}, + ], + }, + ], + }, + { + "id": "security", + "label": "Security", + "icon": "bi-shield-lock", + "tabs": [ + { + # Who can get in and what role they need. Distinct from Content + # Safety, which is about what may be said once you are in. + "id": "access-roles", + "label": "Access & Roles", + "icon": "bi-person-check", + "sections": [ + {"id": "permissions-section", "label": "Permissions", "icon": "bi-person-check"}, + {"id": "app-role-requirements-section", "label": "App Role Requirements", "icon": "bi-person-badge"}, + {"id": "access-denied-message-section", "label": "Access Denied Message", "icon": "bi-shield-x"}, + ], + }, + { + "id": "secrets", + "label": "Secrets", + "icon": "bi-safe", + "sections": [ + {"id": "keyvault-section", "label": "Key Vault", "icon": "bi-safe"}, + ], + }, + { + "id": "content-safety", + "label": "Content Safety", + "icon": "bi-shield-exclamation", + "sections": [ + {"id": "content-safety-section", "label": "Content Safety", "icon": "bi-shield-exclamation"}, + ], + }, + { + # Idle timeout is the only thing left here. The rest of the old + # System Settings card was split out to the tabs that own each + # setting, without renaming a single field. + "id": "session", + "label": "Session", + "icon": "bi-hourglass-split", + "sections": [ + {"id": "idle-timeout-section", "label": "Idle Session Timeout", "icon": "bi-hourglass-split"}, + ], + }, + { + # Front Door configures authentication and redirect flows, so it + # belongs with Security rather than with throughput settings. + "id": "network", + "label": "Network", + "icon": "bi-door-open", + "sections": [ + {"id": "front-door-section", "label": "Azure Front Door", "icon": "bi-door-open"}, + ], + }, + ], + }, + { + "id": "governance", + "label": "Governance", + "icon": "bi-clipboard-check", + "tabs": [ + { + "id": "feature-governance", + "label": "Feature Governance", + "icon": "bi-braces-asterisk", + "sections": [ + {"id": "governance-feature-toggles-section", "label": "Governance Feature Toggles", "icon": "bi-braces-asterisk"}, + ], + }, + { + "id": "governance-policies", + "label": "Policies", + "icon": "bi-sliders2", + "sections": [ + {"id": "governance-feature-policies-section", "label": "Feature Policies", "icon": "bi-sliders2"}, + {"id": "governance-item-policies-section", "label": "Delegated Item Policies", "icon": "bi-list-check"}, + ], + }, + { + "id": "mcp-governance", + "label": "MCP Governance", + "icon": "bi-diagram-3", + "sections": [ + {"id": "governance-mcp-destination-section", "label": "MCP Action Destination Governance", "icon": "bi-diagram-3"}, + {"id": "governance-inbound-mcp-section", "label": "Inbound MCP Source Governance", "icon": "bi-box-arrow-in-down-right"}, + ], + }, + ], + }, + { + "id": "data-lifecycle", + "label": "Data Lifecycle", + "icon": "bi-hourglass-split", + "tabs": [ + { + "id": "retention", + "label": "Retention", + "icon": "bi-hourglass-split", + "sections": [ + {"id": "retention-policy-section", "label": "Retention Policy", "icon": "bi-hourglass-split"}, + ], + }, + { + "id": "classification", + "label": "Classification", + "icon": "bi-tags", + "sections": [ + {"id": "document-classification-section", "label": "Document Classification", "icon": "bi-tags"}, + ], + }, + { + "id": "archiving", + "label": "Archiving", + "icon": "bi-archive", + "sections": [ + {"id": "conversation-archiving-section", "label": "Conversation Archiving", "icon": "bi-archive"}, + ], + }, + ], + }, + { + "id": "backup-recovery", + "label": "Backup & Recovery", + "icon": "bi-database", + "tabs": [ + { + # Schedule, storage and encryption are cards nested inside the + # backup card, so they stay with it. + "id": "backup", + "label": "Backup", + "icon": "bi-archive", + "sections": [ + {"id": "data-management-readiness-section", "label": "Start Here", "icon": "bi-compass"}, + {"id": "data-management-backup-section", "label": "Backup", "icon": "bi-archive"}, + {"id": "data-management-schedule-section", "label": "Schedule", "icon": "bi-calendar-event"}, + {"id": "data-management-storage-section", "label": "Storage", "icon": "bi-hdd"}, + {"id": "data-management-encryption-section", "label": "Encryption", "icon": "bi-key"}, + ], + }, + { + "id": "migrate", + "label": "Migrate", + "icon": "bi-arrow-left-right", + "sections": [ + {"id": "data-management-migration-section", "label": "Migration", "icon": "bi-arrow-left-right"}, + ], + }, + { + "id": "restore", + "label": "Restore", + "icon": "bi-box-seam", + "sections": [ + {"id": "data-management-backup-inventory-section", "label": "Backup Inventory & Restore", "icon": "bi-box-seam"}, + ], + }, + { + # A direct database editor. It is a repair tool that belongs + # with the backup and restore tooling it shares a module with. + "id": "cosmos-editor", + "label": "Cosmos Editor", + "icon": "bi-database-exclamation", + "sections": [ + {"id": "data-management-cosmos-editor-section", "label": "Cosmos Editor", "icon": "bi-database-exclamation"}, + ], + }, + { + "id": "jobs", + "label": "Jobs", + "icon": "bi-clock-history", + "sections": [ + {"id": "data-management-jobs-section", "label": "Jobs", "icon": "bi-clock-history"}, + ], + }, + ], + }, + { + "id": "scale", + "label": "Scale", + "icon": "bi-speedometer2", + "tabs": [ + { + "id": "redis-caching", + "label": "Redis & Caching", + "icon": "bi-database", + "sections": [ + {"id": "redis-cache-section", "label": "Redis Cache", "icon": "bi-database"}, + {"id": "redis-monitoring-section", "label": "Redis Metrics", "icon": "bi-activity"}, + {"id": "conversation-cache-section", "label": "Conversation Cache", "icon": "bi-chat-square-text"}, + ], + }, + { + "id": "cosmos", + "label": "Cosmos", + "icon": "bi-diagram-3", + "sections": [ + {"id": "document-access-index-section", "label": "DAI Metrics", "icon": "bi-diagram-3"}, + {"id": "cosmos-maintenance-section", "label": "Cosmos Maintenance", "icon": "bi-tools"}, + {"id": "cosmos-throughput-section", "label": "Cosmos DB Throughput", "icon": "bi-speedometer2"}, + {"id": "cosmos-throughput-metrics-table-section", "label": "Cosmos Metrics", "icon": "bi-table"}, + ], + }, + ], + }, + { + "id": "operations", + "label": "Operations", + "icon": "bi-activity", + "tabs": [ + { + "id": "control-center-config", + "label": "Control Center", + "icon": "bi-speedometer2", + "sections": [ + {"id": "control-center-auto-refresh-section", "label": "Automatic Data Refresh", "icon": "bi-calendar-check"}, + {"id": "control-center-overview-section", "label": "Control Center Access", "icon": "bi-gear-wide-connected"}, + ], + }, + { + "id": "logging", + "label": "Logging & Health", + "icon": "bi-journal-text", + "sections": [ + {"id": "application-insights-section", "label": "Application Insights", "icon": "bi-graph-up"}, + {"id": "debug-logging-section", "label": "Debug Logging", "icon": "bi-bug"}, + {"id": "file-processing-logs-section", "label": "File Process Logging", "icon": "bi-file-earmark-text"}, + {"id": "health-check-section", "label": "Health Check", "icon": "bi-heart-pulse"}, + {"id": "swagger-section", "label": "API Documentation", "icon": "bi-file-earmark-code"}, + ], + }, + ], + }, + { + "id": "help", + "label": "Help", + "icon": "bi-life-preserver", + "tabs": [ + { + # Where an admin looks first when a user needs help, ahead of + # raising it with the project. + "id": "support-menu", + "label": "Support Menu", + "icon": "bi-life-preserver", + "sections": [ + {"id": "support-menu-section", "label": "Support", "icon": "bi-life-preserver"}, + ], + }, + { + "id": "send-feedback", + "label": "Send Feedback", + "icon": "bi-envelope-paper", + "sections": [ + {"id": "send-feedback-overview-card", "label": "Overview", "icon": "bi-info-circle"}, + {"id": "send-feedback-bug-card", "label": "Report a Bug", "icon": "bi-bug"}, + {"id": "send-feedback-feature-card", "label": "Request a Feature", "icon": "bi-lightbulb"}, + ], + }, + { + "id": "latest-features", + "label": "Latest Features", + "icon": "bi-lightning-charge", + "sections": [], + # Rendered specially: it can be hidden per user, carries a New + # badge and a hide/unhide menu, and its sections are generated + # from the release catalogue rather than declared here. + "render": "latest_features", + }, + ], + }, +] + + +def iter_tabs(): + """Yield (group, tab) pairs in navigation order.""" + for group in ADMIN_NAV: + for tab in group["tabs"]: + yield group, tab + + +def get_tab_ids(): + """Return every tab id in navigation order.""" + return [tab["id"] for _, tab in iter_tabs()] + + +def get_group_for_tab(tab_id): + """Return the group owning a tab, or None when the tab is unknown.""" + for group, tab in iter_tabs(): + if tab["id"] == tab_id: + return group + return None + + +def get_landing_tab_id(): + """Return the tab an admin lands on when no tab is requested. + + This is the first tab of the first group rather than a fixed id, so the + landing pane follows the navigation map as cards are regrouped. Latest + Features is pinned last precisely so it can never win this. + """ + for _, tab in iter_tabs(): + return tab["id"] + return None + + +def get_section_ids(): + """Return every section target declared across the navigation.""" + return [ + section["id"] + for _, tab in iter_tabs() + for section in tab["sections"] + ] \ No newline at end of file diff --git a/application/single_app/app.py b/application/single_app/app.py index fb6eade46..d8f837e33 100644 --- a/application/single_app/app.py +++ b/application/single_app/app.py @@ -31,6 +31,7 @@ from functions_content import * from functions_documents import * from functions_latest_features_nav import should_hide_latest_features_nav +from admin_settings_nav import ADMIN_NAV, get_landing_tab_id from functions_search import * from functions_settings import * from functions_mcp_server_config import is_mcp_ui_enabled @@ -598,6 +599,8 @@ def inject_settings(): app_settings=public_settings, user_settings=user_settings, custom_pages_nav=custom_pages_nav, + admin_nav=ADMIN_NAV, + admin_landing_tab=get_landing_tab_id(), latest_features_current_version=VERSION, latest_features_nav_hidden=latest_features_nav_hidden, latest_features_nav_hidden_by_development=IS_DEVELOPMENT, diff --git a/application/single_app/config.py b/application/single_app/config.py index 7dab85ab9..0c548824e 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.260.011" +VERSION = "0.260.018" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/static/js/admin/admin_access_roles_roster.js b/application/single_app/static/js/admin/admin_access_roles_roster.js new file mode 100644 index 000000000..3b7993b16 --- /dev/null +++ b/application/single_app/static/js/admin/admin_access_roles_roster.js @@ -0,0 +1,148 @@ +// admin_access_roles_roster.js +// +// Access & Roles shows every "require an Entra app role" switch in one place. +// +// The switches themselves stay on the tabs that own them, because that is where +// they make sense in context. Duplicating the real inputs here would submit +// each setting twice, so this builds a roster of mirrors instead: each row +// carries no name attribute and simply drives the canonical input. +// +// The roster is built from the page rather than from a hand-written list, so a +// new role requirement anywhere in Admin Settings appears here on its own and +// this list cannot fall out of step with reality. +// +// Roster links carry data-admin-link, which admin_card_links.js already handles +// through a delegated listener, so no wiring is needed here. + +const ROLE_INPUT_SELECTOR = 'input[type="checkbox"][name^="require_member_of_"]'; +const LIST_ID = 'app-role-requirements-list'; +const EMPTY_ID = 'app-role-requirements-empty'; +const ROSTER_CARD_ID = 'app-role-requirements-section'; + +/** + * Read the visible label for a control, falling back to its field name. + * @param {HTMLInputElement} input Canonical role checkbox. + * @returns {string} Human readable label. + */ +function labelFor(input) { + const explicit = input.id ? document.querySelector(`label[for="${input.id}"]`) : null; + if (explicit && explicit.textContent.trim()) { + return explicit.textContent.trim(); + } + + const wrapping = input.closest('label'); + if (wrapping && wrapping.textContent.trim()) { + return wrapping.textContent.trim(); + } + + return input.name; +} + +/** + * Find the card a control belongs to, so the roster can link back to it. + * @param {HTMLInputElement} input Canonical role checkbox. + * @returns {HTMLElement|null} The owning card, when it has an id. + */ +function owningCard(input) { + let card = input.closest('.card[id]'); + while (card && card.id === ROSTER_CARD_ID) { + card = card.parentElement ? card.parentElement.closest('.card[id]') : null; + } + return card; +} + +/** + * Read the heading of a card, used as the "where does this live" hint. + * @param {HTMLElement} card Owning card. + * @returns {string} Card title, or an empty string when it has none. + */ +function cardTitle(card) { + const heading = card ? card.querySelector('h5, h4, h6, .card-title') : null; + return heading ? heading.textContent.trim() : ''; +} + +/** + * Build one roster row: a mirror switch, its label, and a link to the setting. + * @param {HTMLInputElement} input Canonical role checkbox. + * @returns {HTMLElement} The row element. + */ +function buildRow(input) { + const row = document.createElement('div'); + row.className = 'd-flex flex-wrap align-items-center gap-2'; + row.setAttribute('data-role-requirement-row', input.name); + + const wrapper = document.createElement('div'); + wrapper.className = 'form-check form-switch mb-0 flex-grow-1'; + + // No name attribute: only the canonical input is submitted with the form. + const mirror = document.createElement('input'); + mirror.type = 'checkbox'; + mirror.className = 'form-check-input'; + mirror.id = `${input.name}-roster-mirror`; + mirror.checked = input.checked; + mirror.disabled = input.disabled; + mirror.setAttribute('data-role-mirror-for', input.id || input.name); + mirror.setAttribute('data-ignore-settings-change', 'true'); + + const label = document.createElement('label'); + label.className = 'form-check-label ms-2'; + label.setAttribute('for', mirror.id); + label.textContent = labelFor(input); + + wrapper.append(mirror, label); + row.appendChild(wrapper); + + const card = owningCard(input); + if (card) { + const title = cardTitle(card); + const link = document.createElement('a'); + link.href = `#${card.id}`; + link.className = 'small text-nowrap'; + link.setAttribute('data-admin-link', card.id); + link.textContent = title ? `In ${title}` : 'Go to setting'; + row.appendChild(link); + } + + // Two-way: the mirror drives the real input, and the real input keeps the + // mirror honest when it is changed on its own tab. + mirror.addEventListener('change', () => { + if (input.checked === mirror.checked) { + return; + } + input.checked = mirror.checked; + input.dispatchEvent(new Event('change', { bubbles: true })); + }); + + input.addEventListener('change', () => { + mirror.checked = input.checked; + mirror.disabled = input.disabled; + }); + + return row; +} + +/** + * Populate the Access & Roles roster from the role switches on the page. + */ +export function initAdminAccessRolesRoster() { + const list = document.getElementById(LIST_ID); + if (!list) { + return; + } + + const empty = document.getElementById(EMPTY_ID); + const inputs = Array.from(document.querySelectorAll(ROLE_INPUT_SELECTOR)) + .filter(input => !list.contains(input)); + + list.replaceChildren(); + inputs + .map(input => ({ input, label: labelFor(input) })) + .sort((a, b) => a.label.localeCompare(b.label)) + .forEach(({ input }) => list.appendChild(buildRow(input))); + + if (empty) { + empty.classList.toggle('d-none', inputs.length > 0); + } +} + +document.addEventListener('DOMContentLoaded', initAdminAccessRolesRoster); diff --git a/application/single_app/static/js/admin/admin_card_links.js b/application/single_app/static/js/admin/admin_card_links.js new file mode 100644 index 000000000..9da7cf139 --- /dev/null +++ b/application/single_app/static/js/admin/admin_card_links.js @@ -0,0 +1,103 @@ +// admin_card_links.js +// Resolves links that point at an Admin Settings card rather than at a tab. +// +// Cross-tab links used to name a tab button directly, for example +// switchTab(event, 'workspaces-tab'). That couples the link to a tab id, so a +// tab rename or an information-architecture change silently breaks the link: +// the button no longer exists, no pane is activated, and the URL hash is left +// pointing at nothing. +// +// A link declares the card it wants instead: +// +// +// +// The owning tab is resolved from the DOM at click time, so card ids are the +// only contract. Cards can move between tabs, and tabs can be renamed or +// regrouped, without touching a single link. + +const HIGHLIGHT_CLASS = 'admin-card-link-target'; +const HIGHLIGHT_MS = 1600; + +/** + * Find the tab pane that contains a card. + * @param {string} cardId Element id of the target card. + * @returns {{card: HTMLElement, paneId: string|null}|null} + */ +export function resolveAdminCard(cardId) { + const card = document.getElementById(cardId); + if (!card) { + return null; + } + + const pane = card.closest('.tab-pane'); + return { card, paneId: pane ? pane.id : null }; +} + +/** + * Activate the tab owning a card, scroll to it, and highlight it briefly. + * @param {string} cardId Element id of the target card. + * @returns {boolean} Whether the card was found. + */ +export function openAdminCard(cardId) { + const resolved = resolveAdminCard(cardId); + if (!resolved) { + console.warn(`openAdminCard: no card with id "${cardId}"`); + return false; + } + + const { card, paneId } = resolved; + + if (paneId && typeof window.showAdminTab === 'function') { + window.showAdminTab(paneId); + syncSidebarActiveState(paneId); + } + + // Let the pane become visible before measuring scroll position. + window.setTimeout(() => { + card.scrollIntoView({ behavior: 'smooth', block: 'start' }); + card.classList.add(HIGHLIGHT_CLASS); + window.setTimeout(() => card.classList.remove(HIGHLIGHT_CLASS), HIGHLIGHT_MS); + }, 120); + + return true; +} + +/** + * Mirror the activated tab in the sidebar, when the sidebar layout is in use. + * @param {string} paneId Tab pane id that was activated. + */ +function syncSidebarActiveState(paneId) { + const navLink = document.querySelector(`.admin-nav-tab[data-tab="${paneId}"]`); + if (!navLink) { + return; + } + + document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach((link) => { + link.classList.remove('active'); + }); + navLink.classList.add('active'); +} + +/** + * Delegate clicks so links added after load keep working. + */ +export function initAdminCardLinks() { + document.addEventListener('click', (event) => { + const link = event.target.closest('[data-admin-link]'); + if (!link) { + return; + } + + const cardId = link.getAttribute('data-admin-link'); + if (!cardId) { + return; + } + + event.preventDefault(); + openAdminCard(cardId); + }); +} + +window.openAdminCard = openAdminCard; + +document.addEventListener('DOMContentLoaded', initAdminCardLinks); diff --git a/application/single_app/static/js/admin/admin_data_management.js b/application/single_app/static/js/admin/admin_data_management.js index 481b2069f..e8c2761a9 100644 --- a/application/single_app/static/js/admin/admin_data_management.js +++ b/application/single_app/static/js/admin/admin_data_management.js @@ -388,7 +388,8 @@ function bindEvents() { pendingDataManagementBackupDelete = null; }); elements.dataManagementConfirmBackupDeleteBtn?.addEventListener("click", deleteDataManagementBackup); - elements.dataManagementKeyVaultLink?.addEventListener("click", openKeyVaultSettings); + // The Key Vault link carries data-admin-link, so admin_card_links.js + // resolves the owning tab from the DOM and scrolls to the card. elements.dataManagementCosmosEditorOpenDangerBtn?.addEventListener("click", showCosmosEditorDangerModal); elements.datamanagementcosmoseditordangeraccept?.addEventListener("change", updateCosmosEditorDangerAcceptState); elements.dataManagementCosmosEditorAcceptDangerBtn?.addEventListener("click", acceptCosmosEditorDanger); @@ -909,20 +910,6 @@ function setKeyStorageAlert(variant, iconClass, title, message, linkText) { setText(elements.dataManagementKeyVaultLink, linkText); } -function openKeyVaultSettings(event) { - event?.preventDefault(); - const securityTabButton = document.getElementById("security-tab"); - if (securityTabButton && window.bootstrap?.Tab) { - window.bootstrap.Tab.getOrCreateInstance(securityTabButton).show(); - } else if (securityTabButton) { - securityTabButton.click(); - } - window.location.hash = "security"; - window.setTimeout(() => { - document.getElementById("keyvault-section")?.scrollIntoView({ block: "start", behavior: "smooth" }); - }, 100); -} - function collectSettings() { const backupStorageAuthenticationType = getValue(elements.datamanagementstorageauth) || backupStorageAuthManagedIdentity; return { diff --git a/application/single_app/static/js/admin/admin_settings.js b/application/single_app/static/js/admin/admin_settings.js index 26726f5f3..722a3d49f 100644 --- a/application/single_app/static/js/admin/admin_settings.js +++ b/application/single_app/static/js/admin/admin_settings.js @@ -1508,7 +1508,12 @@ function validateCosmosThroughputSettings(options = {}) { setCosmosThroughputValidationMessage(errors); if (options.report && invalidFields.length > 0) { - document.getElementById('scale-tab')?.click(); + // Navigate to wherever the invalid field actually lives rather than to + // a named tab, so this keeps working when settings are regrouped. + const owningCard = invalidFields[0].closest('.card[id]'); + if (owningCard && typeof window.openAdminCard === 'function') { + window.openAdminCard(owningCard.id); + } invalidFields[0].focus({ preventScroll: false }); invalidFields[0].reportValidity(); } @@ -9227,31 +9232,6 @@ function clearStatusAlert(statusAlert) { } -function switchTab(event, tabButtonId) { - event.preventDefault(); - const triggerEl = document.getElementById(tabButtonId); - if (triggerEl) { - const tabObj = new bootstrap.Tab(triggerEl); - tabObj.show(); - return; - } - - const inferredTabId = tabButtonId.replace(/-tab$/, ''); - if (typeof window.showAdminTab === 'function') { - window.showAdminTab(inferredTabId); - - const navLink = document.querySelector(`.admin-nav-tab[data-tab="${inferredTabId}"]`); - if (navLink) { - document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach(link => { - link.classList.remove('active'); - }); - navLink.classList.add('active'); - } - } -} - -window.switchTab = switchTab; - function togglePassword(btnId, inputId) { const btn = document.getElementById(btnId); const inp = document.getElementById(inputId); @@ -10073,111 +10053,55 @@ function findPreviousApplicableStep(currentStep) { } /** - * Navigate to the appropriate tab based on the walkthrough step + * Navigate to the setting a walkthrough step is about. + * + * Steps used to name a tab id directly, which meant the same knowledge was + * recorded twice and went stale the moment a setting moved to another tab. + * Steps now name the card they are about, and openAdminCard finds the owning + * tab from the page itself, so this cannot drift again. + * * @param {number} stepNumber - The current step number */ function handleTabNavigation(stepNumber) { - // Map steps to tabs that need to be activated - const stepToTab = { - 1: 'general-tab', // App title and logo (General tab) - 2: 'ai-models-tab', // GPT settings (now in AI Models tab) - 3: 'ai-models-tab', // GPT model selection (now in AI Models tab) - 4: 'workspaces-tab', // Workspace and groups settings - 5: 'ai-models-tab', // Embedding settings (now in AI Models tab) - 6: 'search-extract-tab', // AI Search settings - 7: 'search-extract-tab', // Document Intelligence settings - 8: 'search-extract-tab', // Video support - 9: 'search-extract-tab', // Audio support - 10: 'safety-tab', // Content safety - 11: 'safety-tab', // User feedback and archiving (changed from system-tab) - 12: 'citation-tab' // Enhanced Citations and Image Generation + // card: the setting this step is about. + // focus: an optional finer scroll target inside that card. + const stepToCard = { + 1: { card: 'branding-section' }, + 2: { card: 'multi-endpoint-configuration' }, + 3: { card: 'multi-endpoint-configuration' }, + 4: { card: 'personal-workspaces-section' }, + 5: { card: 'embeddings-configuration' }, + 6: { card: 'azure-ai-search-section' }, + 7: { card: 'document-intelligence-section' }, + 8: { card: 'video-intelligence-section', focus: 'enable_video_file_support' }, + 9: { card: 'ai-voice-chat-section', focus: 'enable_audio_file_support' }, + 10: { card: 'content-safety-section' }, + 11: { card: 'user-feedback-section' }, + 12: { card: 'enhanced-citations-section' }, }; - - // Activate the appropriate tab - const tabId = stepToTab[stepNumber]; - if (tabId) { - // Check if we're using sidebar navigation or tab navigation - const sidebarToggle = document.getElementById('admin-settings-toggle'); - - if (sidebarToggle) { - // Using sidebar navigation - call showAdminTab function - const tabName = tabId.replace('-tab', ''); // Remove '-tab' suffix - if (typeof showAdminTab === 'function') { - showAdminTab(tabName); - } else if (typeof window.showAdminTab === 'function') { - window.showAdminTab(tabName); - } - } else { - // Using Bootstrap tabs - const tab = document.getElementById(tabId); - if (tab) { - // Use bootstrap Tab to show the tab - const bootstrapTab = new bootstrap.Tab(tab); - bootstrapTab.show(); - } - } - - // Scroll to the relevant section after a small delay to allow tab to switch - setTimeout(() => { - scrollToRelevantSection(stepNumber, tabId); - }, 300); + + const target = stepToCard[stepNumber]; + if (!target) { + return; } -} -/** - * Scroll to relevant section within a tab based on the step - * @param {number} stepNumber - The current step number - * @param {string} tabId - The ID of the tab that was activated - */ -function scrollToRelevantSection(stepNumber, tabId) { - // Define which sections to scroll to for each step - let targetElement = null; - - switch (stepNumber) { - case 1: // App title and logo - targetElement = document.getElementById('branding-section'); - break; - case 2: // GPT settings - targetElement = document.getElementById('gpt-configuration'); - break; - case 3: // GPT model selection - targetElement = document.getElementById('gpt_models_list')?.closest('.mb-3'); - break; - case 4: // Workspaces toggle section - targetElement = document.getElementById('personal-workspaces-section'); - break; - case 5: // Embedding settings - targetElement = document.getElementById('embeddings-configuration'); - break; - case 6: // AI Search settings - targetElement = document.getElementById('azure-ai-search-section'); - break; - case 7: // Document Intelligence settings - targetElement = document.getElementById('document-intelligence-section'); - break; - case 8: // Video file support - targetElement = document.getElementById('enable_video_file_support')?.closest('.form-group'); - break; - case 9: // Audio file support - targetElement = document.getElementById('enable_audio_file_support')?.closest('.form-group'); - break; - case 10: // Content safety - targetElement = document.getElementById('content-safety-section'); - break; - case 11: // User feedback and archiving - targetElement = document.getElementById('user-feedback-section'); - break; - case 12: // Enhanced citations and image generation - targetElement = document.getElementById('enhanced-citations-section'); - break; - default: - // For other steps, no specific scrolling - break; + const opened = typeof window.openAdminCard === 'function' + ? window.openAdminCard(target.card) + : false; + + if (!opened) { + console.warn(`handleTabNavigation: could not open card "${target.card}" for step ${stepNumber}`); + return; } - - // If we found a target element, scroll to it - if (targetElement) { - targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + + if (target.focus) { + // openAdminCard scrolls to the card first, so refine afterwards. + window.setTimeout(() => { + const element = document.getElementById(target.focus); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, 400); } } diff --git a/application/single_app/static/js/admin/admin_settings_dependencies.js b/application/single_app/static/js/admin/admin_settings_dependencies.js new file mode 100644 index 000000000..266490a18 --- /dev/null +++ b/application/single_app/static/js/admin/admin_settings_dependencies.js @@ -0,0 +1,220 @@ +// admin_settings_dependencies.js +// Announces and enforces Admin Settings options that require another option. +// +// Some settings only work when a different setting is enabled, and those two +// settings often live in different tabs. Previously that was communicated in +// prose, or in a tooltip, or only by a flash message after saving, so an admin +// could turn something on and have nothing happen with no visible reason. +// +// A dependent card declares what it needs: +// +//
+// +// When the prerequisite is off, a notice is inserted at the top of the card +// containing a mirror of the prerequisite control and a link to its card, so +// the admin can satisfy it inline or jump to the full configuration. +// +// Modes: +// block (default) disables the dependent inputs until the prerequisite is on +// warn leaves inputs usable, for prerequisites the backend already +// accepts as intent and reconciles later +// +// This is a usability layer only. The backend remains authoritative and still +// refuses or flashes on unmet prerequisites. + +const NOTICE_CLASS = 'admin-dependency-notice'; +const DISABLED_FLAG = 'data-dependency-disabled'; + +/** + * Read every dependency declared in the document. + * @returns {Array} Parsed dependency descriptors. + */ +function collectDependencies() { + return Array.from(document.querySelectorAll('[data-requires]')).map((card) => ({ + card, + prerequisiteId: card.getAttribute('data-requires'), + label: card.getAttribute('data-requires-label') || 'another setting', + targetCardId: card.getAttribute('data-requires-target') || '', + mode: card.getAttribute('data-requires-mode') === 'warn' ? 'warn' : 'block', + description: card.getAttribute('data-requires-description') || '', + // Optional selector limiting which controls the dependency guards, for + // cards that hold a mix of dependent and independent settings. + scope: card.getAttribute('data-requires-scope') || '', + })); +} + +/** + * Whether a prerequisite control is currently satisfied. + * @param {HTMLElement|null} control Prerequisite input. + * @returns {boolean} + */ +function isSatisfied(control) { + if (!control) { + return true; + } + if (control.type === 'checkbox' || control.type === 'radio') { + return control.checked; + } + return Boolean(control.value); +} + +/** + * Build the notice shown when a prerequisite is unmet. + * @param {object} dependency Dependency descriptor. + * @param {HTMLElement} prerequisite Prerequisite control. + * @returns {HTMLElement} + */ +function buildNotice(dependency, prerequisite) { + const notice = document.createElement('div'); + notice.className = `alert ${dependency.mode === 'warn' ? 'alert-warning' : 'alert-info'} ${NOTICE_CLASS}`; + notice.setAttribute('role', 'note'); + + const heading = document.createElement('div'); + heading.className = 'fw-semibold mb-1'; + const icon = document.createElement('i'); + icon.className = 'bi bi-info-circle me-2'; + icon.setAttribute('aria-hidden', 'true'); + heading.append(icon, document.createTextNode(`This needs ${dependency.label}`)); + notice.appendChild(heading); + + const body = document.createElement('p'); + body.className = 'mb-2'; + body.textContent = dependency.description + || (dependency.mode === 'warn' + ? `These settings are saved, but stay inactive until ${dependency.label} is enabled and configured.` + : `These settings are unavailable until ${dependency.label} is enabled.`); + notice.appendChild(body); + + const actions = document.createElement('div'); + actions.className = 'd-flex flex-wrap align-items-center gap-3'; + + // Inline mirror: satisfy the prerequisite without leaving this tab. It + // carries no name attribute, so only the canonical input is submitted. + if (prerequisite && prerequisite.type === 'checkbox') { + const wrapper = document.createElement('div'); + wrapper.className = 'form-check form-switch mb-0'; + + const proxy = document.createElement('input'); + proxy.type = 'checkbox'; + proxy.className = 'form-check-input'; + proxy.id = `${dependency.card.id}-requires-proxy`; + proxy.checked = prerequisite.checked; + proxy.setAttribute('data-dependency-proxy-for', prerequisite.id); + proxy.setAttribute('data-ignore-settings-change', 'true'); + + proxy.addEventListener('change', () => { + prerequisite.checked = proxy.checked; + prerequisite.dispatchEvent(new Event('change', { bubbles: true })); + }); + + const proxyLabel = document.createElement('label'); + proxyLabel.className = 'form-check-label ms-2'; + proxyLabel.setAttribute('for', proxy.id); + proxyLabel.textContent = `Enable ${dependency.label}`; + + wrapper.append(proxy, proxyLabel); + actions.appendChild(wrapper); + } + + if (dependency.targetCardId) { + const link = document.createElement('a'); + link.href = `#${dependency.targetCardId}`; + link.setAttribute('data-admin-link', dependency.targetCardId); + link.className = 'small'; + link.textContent = `Go to ${dependency.label}`; + actions.appendChild(link); + } + + notice.appendChild(actions); + return notice; +} + +/** + * Enable or disable the controls a dependency guards. + * @param {object} dependency Dependency descriptor. + * @param {boolean} satisfied Whether the prerequisite is met. + */ +function setControlsDisabled(dependency, satisfied) { + if (dependency.mode === 'warn') { + return; + } + + const selector = dependency.scope || 'input, select, textarea, button'; + dependency.card.querySelectorAll(selector).forEach((control) => { + if (control.closest(`.${NOTICE_CLASS}`)) { + return; + } + + if (!satisfied) { + // Remember controls that were already disabled for other + // reasons so re-enabling does not override them. + if (!control.disabled) { + control.setAttribute(DISABLED_FLAG, 'true'); + control.disabled = true; + } + } else if (control.getAttribute(DISABLED_FLAG) === 'true') { + control.removeAttribute(DISABLED_FLAG); + control.disabled = false; + } + }); +} + +/** + * Apply the current state of one dependency. + * @param {object} dependency Dependency descriptor. + */ +function applyDependency(dependency) { + const prerequisite = document.getElementById(dependency.prerequisiteId); + const satisfied = isSatisfied(prerequisite); + + const existing = dependency.card.querySelector(`:scope > .${NOTICE_CLASS}`); + if (existing) { + existing.remove(); + } + + setControlsDisabled(dependency, satisfied); + + if (satisfied) { + return; + } + + const notice = buildNotice(dependency, prerequisite); + const heading = dependency.card.querySelector('h4, h5, h6'); + if (heading && heading.parentElement === dependency.card) { + heading.insertAdjacentElement('afterend', notice); + } else { + dependency.card.prepend(notice); + } +} + +/** + * Wire up every declared dependency and keep it live. + */ +export function initAdminSettingsDependencies() { + const dependencies = collectDependencies(); + if (!dependencies.length) { + return; + } + + dependencies.forEach((dependency) => { + applyDependency(dependency); + + const prerequisite = document.getElementById(dependency.prerequisiteId); + if (!prerequisite) { + console.warn( + `admin dependencies: "${dependency.card.id}" requires missing control ` + + `"${dependency.prerequisiteId}"`, + ); + return; + } + + prerequisite.addEventListener('change', () => applyDependency(dependency)); + prerequisite.addEventListener('input', () => applyDependency(dependency)); + }); +} + +document.addEventListener('DOMContentLoaded', initAdminSettingsDependencies); diff --git a/application/single_app/static/js/admin/admin_sidebar_nav.js b/application/single_app/static/js/admin/admin_sidebar_nav.js index ff628ec79..b452447c9 100644 --- a/application/single_app/static/js/admin/admin_sidebar_nav.js +++ b/application/single_app/static/js/admin/admin_sidebar_nav.js @@ -1,6 +1,10 @@ // admin_sidebar_nav.js // Admin Sidebar Navigation document.addEventListener('DOMContentLoaded', function() { + // The top-nav group pills exist only in the tab layout, so they are wired + // independently of the sidebar. + setupAdminGroupPills(); + // Only initialize if we're on admin settings page with sidebar nav if (!document.getElementById('admin-settings-toggle')) return; @@ -77,6 +81,10 @@ function initAdminSidebarNav() { }); } + // Set up group expand/collapse. Groups are the level above tabs, so a + // collapsed group hides its tabs without affecting which tab is active. + setupAdminGroupToggles(); + // Set up tab navigation document.querySelectorAll('.admin-nav-tab').forEach(tabLink => { tabLink.addEventListener('click', function(e) { @@ -135,20 +143,218 @@ function initAdminSidebarNav() { }); }); - // Set the initial active tab (General) - but only if no tab is already active + // Set the initial active tab - but only if no tab is already active. + // Latest Features is deliberately excluded so it never opens by default. + // The landing tab is whichever tab the nav map renders first rather than a + // hardcoded id, so it stays correct as the information architecture moves. const activeTab = document.querySelector('.admin-nav-tab.active, .admin-nav-section.active'); if (!activeTab) { - const firstTab = document.querySelector('.admin-nav-tab[data-tab="latest-features"]'); - if (firstTab) { + const firstTab = document.querySelector('.admin-nav-tab[data-tab]'); + const firstTabId = firstTab ? firstTab.getAttribute('data-tab') : null; + if (firstTabId) { firstTab.classList.add('active'); - showAdminTab('latest-features'); + showAdminTab(firstTabId); } } else { console.log('initAdminSidebarNav - Found existing active tab, preserving current state:', activeTab.getAttribute('data-tab')); + syncAdminGroupSharedRegions(activeTab.getAttribute('data-tab')); + } + + // Clicking a tab button directly does not go through showAdminTab, so the + // shared regions are synced from Bootstrap's own event as well. + document.querySelectorAll('button.nav-link[data-bs-target^="#"]').forEach(button => { + button.addEventListener('shown.bs.tab', event => { + const target = event.target.getAttribute('data-bs-target'); + if (target) { + syncAdminGroupSharedRegions(target.slice(1)); + } + }); + }); +} + +function setupAdminGroupToggles() { + document.querySelectorAll('[data-admin-group-toggle]').forEach(toggle => { + toggle.addEventListener('click', function (e) { + e.preventDefault(); + const groupId = this.getAttribute('data-admin-group-toggle'); + setAdminGroupExpanded(groupId, !isAdminGroupExpanded(groupId), true); + }); + }); +} + +/** + * Wire the top-nav group pills, which filter the tab strip to one group. + * Only relevant in the tab layout; the sidebar layout uses group headers. + */ +function setupAdminGroupPills() { + const pills = document.querySelectorAll('[data-admin-group-pill]'); + if (!pills.length) { + return; + } + + pills.forEach(pill => { + pill.addEventListener('click', function () { + showAdminGroupTabs(this.getAttribute('data-admin-group-pill'), true); + }); + }); +} + +/** + * Show one group's tabs in the top strip. + * @param {string} groupId Group to reveal. + * @param {boolean} activateFirstTab Whether to open that group's first tab. + */ +function showAdminGroupTabs(groupId, activateFirstTab) { + document.querySelectorAll('[data-admin-group-pill]').forEach(pill => { + const selected = pill.getAttribute('data-admin-group-pill') === groupId; + pill.classList.toggle('active', selected); + pill.setAttribute('aria-selected', selected ? 'true' : 'false'); + }); + + let firstTabButton = null; + document.querySelectorAll('.admin-tab-item').forEach(item => { + const inGroup = item.getAttribute('data-admin-group') === groupId; + item.hidden = !inGroup; + if (inGroup && !firstTabButton) { + firstTabButton = item.querySelector('button[data-bs-target]'); + } + }); + + if (activateFirstTab && firstTabButton) { + const target = firstTabButton.getAttribute('data-bs-target') || ''; + showAdminTab(target.replace('#', '')); + } +} + +/** + * Reveal the group owning a tab in the top strip, so a deep link or a + * cross-reference never activates a pane whose tab is filtered out of view. + * @param {string} tabId Tab pane id. + */ +function revealAdminGroupPillForTab(tabId) { + const item = document.querySelector(`.admin-tab-item [data-bs-target="#${tabId}"]`); + const groupItem = item && item.closest('.admin-tab-item'); + if (!groupItem) { + return; + } + + const groupId = groupItem.getAttribute('data-admin-group'); + if (groupId && groupItem.hidden) { + showAdminGroupTabs(groupId, false); + } +} + +function getAdminGroupElements(groupId) { + return { + toggle: document.querySelector(`[data-admin-group-toggle="${groupId}"]`), + list: document.getElementById(`admin-group-${groupId}`), + }; +} + +function isAdminGroupExpanded(groupId) { + const { list } = getAdminGroupElements(groupId); + return Boolean(list) && !list.classList.contains('d-none'); +} + +/** + * Expand or collapse a nav group. + * @param {string} groupId Group identifier. + * @param {boolean} expanded Desired state. + * @param {boolean} persist Whether to remember the state for this user. + */ +function setAdminGroupExpanded(groupId, expanded, persist) { + const { toggle, list } = getAdminGroupElements(groupId); + if (!toggle || !list) { + return; + } + + list.classList.toggle('d-none', !expanded); + toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false'); + + const caret = toggle.querySelector('.admin-nav-group-caret'); + if (caret) { + caret.classList.toggle('collapsed', !expanded); + } + + if (persist && typeof window.setPersistentSidebarMenuExpanded === 'function') { + window.setPersistentSidebarMenuExpanded(`adminGroup:${groupId}`, expanded); } } -function showAdminTab(tabId) { +/** + * Open the group that owns a tab, so activating a tab never leaves it hidden. + * @param {string} tabId Tab pane id. + */ +function revealAdminGroupForTab(tabId) { + const tabLink = document.querySelector(`.admin-nav-tab[data-tab="${tabId}"]`); + const groupItem = tabLink && tabLink.closest('[data-admin-group]'); + if (!groupItem) { + return; + } + + const groupId = groupItem.getAttribute('data-admin-group'); + if (!isAdminGroupExpanded(groupId)) { + setAdminGroupExpanded(groupId, true, true); + } +} + +// Tabs that existed before the information architecture rework, mapped to +// where their content now lives. Old bookmarks and links keep working. +const LEGACY_TAB_REDIRECTS = { + 'governance': 'feature-governance', + 'scale': 'redis-caching', + 'general': 'branding', + 'safety': 'access-roles', + 'security': 'secrets', + 'workspaces': 'workspace-types', + 'search-extract': 'web-research', + 'ai-models': 'model-endpoints', + 'data-management': 'backup', +}; + +function resolveAdminTabId(tabId) { + return LEGACY_TAB_REDIRECTS[tabId] || tabId; +} + +/** + * Some groups share one set of controls across all of their tabs, such as the + * single save button that serves every Backup & Recovery tab. Those controls + * cannot be duplicated into each pane without repeating element ids, and they + * cannot sit in one pane because the other tabs would lose them, so they live + * outside the panes and are revealed only while their group is active. + */ +function syncAdminGroupSharedRegions(tabId) { + const regions = document.querySelectorAll('[data-admin-group-shared]'); + if (!regions.length) { + return; + } + + // Only one of the two navigations is rendered at a time, so resolve the + // owning group from whichever is present. Looking only at the top tab strip + // would leave the region hidden for good in the sidebar layout. + const tabButton = document.querySelector(`.admin-tab-item[data-admin-group] button[data-bs-target="#${tabId}"]`); + let owner = tabButton ? tabButton.closest('[data-admin-group]') : null; + if (!owner) { + const sidebarLink = document.querySelector(`.admin-nav-tab[data-tab="${tabId}"]`); + owner = sidebarLink ? sidebarLink.closest('[data-admin-group]') : null; + } + const activeGroup = owner ? owner.getAttribute('data-admin-group') : null; + + regions.forEach(region => { + const ownerGroup = region.getAttribute('data-admin-group-shared'); + region.hidden = ownerGroup !== activeGroup; + }); +} + +function showAdminTab(requestedTabId) { + const tabId = resolveAdminTabId(requestedTabId); + + // Open the owning group first, in whichever layout is active, so + // activating a tab never leaves it hidden behind a collapsed group header + // or filtered out of the top strip. + revealAdminGroupForTab(tabId); + revealAdminGroupPillForTab(tabId); + const bootstrapTabButton = document.querySelector(`button.nav-link[data-bs-target="#${tabId}"]`); if (bootstrapTabButton && typeof bootstrap !== 'undefined' && typeof bootstrap.Tab === 'function') { const tab = bootstrap.Tab.getOrCreateInstance(bootstrapTabButton); @@ -170,6 +376,7 @@ function showAdminTab(tabId) { // Update the hash in URL for deep linking window.location.hash = tabId; + syncAdminGroupSharedRegions(tabId); if (typeof window.updateAdminSettingsSaveButtonState === 'function') { window.updateAdminSettingsSaveButtonState(); } @@ -179,91 +386,17 @@ function showAdminTab(tabId) { window.showAdminTab = showAdminTab; function scrollToSection(sectionId) { - // Map section IDs to actual element IDs/classes in the admin settings + // Resolve a sidebar data-section value to the element it should scroll to. const sectionMap = { + // Only genuine aliases belong here. Any sidebar data-section value + // that already matches its element id resolves through the + // `sectionMap[sectionId] || sectionId` fallback below. 'gpt-config': 'gpt-configuration', - 'embeddings-config': 'embeddings-configuration', + 'embeddings-config': 'embeddings-configuration', 'image-config': 'image-generation-configuration', - 'multi-endpoint-configuration': 'multi-endpoint-configuration', - 'document-action-capabilities-card': 'document-action-capabilities-card', 'agents-config': 'agents-configuration', - 'agent-template-approvals-section': 'agent-template-approvals-section', 'actions-config': 'actions-configuration', - // Governance tab sections - 'governance-feature-toggles-section': 'governance-feature-toggles-section', - 'governance-mcp-destination-section': 'governance-mcp-destination-section', - 'governance-inbound-mcp-section': 'governance-inbound-mcp-section', - 'governance-feature-policies-section': 'governance-feature-policies-section', - 'governance-item-policies-section': 'governance-item-policies-section', - // General tab sections - 'branding-section': 'branding-section', - 'home-page-text-section': 'home-page-text-section', - 'appearance-section': 'appearance-section', - 'classification-banner-section': 'classification-banner-section', - 'ai-notice-section': 'ai-notice-section', - 'terms-of-use-section': 'terms-of-use-section', - 'custom-pages-section': 'custom-pages-section', - 'external-links-section': 'external-links-section', - 'health-check-section': 'health-check-section', - 'system-settings-section': 'system-settings-section', - 'control-center-admin-section': 'control-center-admin-section', - // Control Center tab sections - 'control-center-auto-refresh-section': 'control-center-auto-refresh-section', - 'control-center-overview-section': 'control-center-overview-section', - // Logging tab sections - 'application-insights-section': 'application-insights-section', - 'debug-logging-section': 'debug-logging-section', - 'file-processing-logs-section': 'file-processing-logs-section', - // Scale tab sections - 'redis-cache-section': 'redis-cache-section', - 'redis-monitoring-section': 'redis-monitoring-section', - 'conversation-cache-section': 'conversation-cache-section', - 'document-access-index-section': 'document-access-index-section', - 'cosmos-maintenance-section': 'cosmos-maintenance-section', - 'cosmos-throughput-section': 'cosmos-throughput-section', - 'cosmos-throughput-metrics-table-section': 'cosmos-throughput-metrics-table-section', - 'front-door-section': 'front-door-section', - // Workspaces tab sections - 'personal-workspaces-section': 'personal-workspaces-section', - 'group-workspaces-section': 'group-workspaces-section', - 'public-workspaces-section': 'public-workspaces-section', - 'file-sharing-section': 'file-sharing-section', - 'file-download-settings-section': 'file-download-settings-section', - 'chat-file-uploads-section': 'chat-file-uploads-section', - 'metadata-extraction-section': 'metadata-extraction-section', - 'multimodal-vision-section': 'multimodal-vision-section', - 'document-classification-section': 'document-classification-section', - 'workspace-scope-lock-section': 'workspace-scope-lock-section', - // Citations tab sections - 'standard-citations-section': 'standard-citations-section', - 'enhanced-citations-section': 'enhanced-citations-section', - // Safety tab sections - 'content-safety-section': 'content-safety-section', - 'user-feedback-section': 'user-feedback-section', - 'desktop-notifications-section': 'desktop-notifications-section', - 'permissions-section': 'permissions-section', - 'conversation-archiving-section': 'conversation-archiving-section', - // Security tab sections - 'keyvault-section': 'keyvault-section', - // Data Management tab sections - 'data-management-readiness-section': 'data-management-readiness-section', - 'data-management-backup-section': 'data-management-backup-section', - 'data-management-schedule-section': 'data-management-schedule-section', - 'data-management-storage-section': 'data-management-storage-section', - 'data-management-encryption-section': 'data-management-encryption-section', - 'data-management-migration-section': 'data-management-migration-section', - 'data-management-target-cosmos-section': 'data-management-target-cosmos-section', - 'data-management-backup-inventory-section': 'data-management-backup-inventory-section', - 'data-management-jobs-section': 'data-management-jobs-section', - // Search & Extract tab sections 'web-search-section': 'web-search-foundry-section', - 'url-access-section': 'url-access-section', - 'source-review-section': 'source-review-section', - 'azure-ai-search-section': 'azure-ai-search-section', - 'document-intelligence-section': 'document-intelligence-section', - 'chunk-size-section': 'chunk-size-section', - 'video-intelligence-section': 'video-intelligence-section', - 'ai-voice-chat-section': 'ai-voice-chat-section' }; const targetElementId = sectionMap[sectionId] || sectionId; @@ -320,112 +453,171 @@ style.textContent = ` .admin-search-hidden { display: none !important; } + .admin-nav-group { + font-weight: 500; + letter-spacing: 0.01em; + } + .admin-nav-group:hover { + background-color: rgba(0, 0, 0, 0.05); + } + .admin-nav-group-caret { + font-size: 0.75em; + transition: transform 0.2s ease; + } + .admin-nav-group-caret.collapsed { + transform: rotate(-90deg); + } `; document.head.appendChild(style); // Admin search functionality function filterAdminSections(searchTerm) { const normalizedSearch = searchTerm.toLowerCase().trim(); - + if (!normalizedSearch) { - // Show all sections if search is empty showAllAdminSections(); return; } - + let hasVisibleSections = false; - - // Get all admin nav items + + const groupItems = document.querySelectorAll('.admin-nav-group-item'); const allTabs = document.querySelectorAll('.admin-nav-tab'); const allSections = document.querySelectorAll('.admin-nav-section'); - - // Hide all sections and tabs initially + + // Start from everything hidden, then reveal what matches. + groupItems.forEach(group => group.classList.add('admin-search-hidden')); + document.querySelectorAll('.admin-nav-group').forEach(group => { + group.classList.remove('admin-search-highlight'); + }); + allTabs.forEach(tab => { tab.closest('li').classList.add('admin-search-hidden'); - // Hide submenu + tab.classList.remove('admin-search-highlight'); const submenu = document.getElementById(tab.getAttribute('data-tab') + '-submenu'); if (submenu) { submenu.style.display = 'none'; } }); - + allSections.forEach(section => { section.closest('li').classList.add('admin-search-hidden'); section.classList.remove('admin-search-highlight'); }); - - // Search through tabs and sections + + /** + * Reveal the group containing an element and expand its tab list, so a + * match is never left hidden behind a collapsed group. + * @param {HTMLElement} element Element inside a group. + */ + const revealGroupOf = (element) => { + const groupItem = element.closest('.admin-nav-group-item'); + if (!groupItem) { + return; + } + groupItem.classList.remove('admin-search-hidden'); + const list = groupItem.querySelector('.admin-nav-group-tabs'); + if (list) { + list.classList.remove('d-none'); + } + }; + + // A group matching by name reveals everything it holds. + document.querySelectorAll('.admin-nav-group').forEach(group => { + const label = group.querySelector('.nav-text'); + if (!label || !label.textContent.toLowerCase().includes(normalizedSearch)) { + return; + } + + group.classList.add('admin-search-highlight'); + revealGroupOf(group); + hasVisibleSections = true; + + const groupItem = group.closest('.admin-nav-group-item'); + groupItem.querySelectorAll('.admin-nav-tab').forEach(tab => { + tab.closest('li').classList.remove('admin-search-hidden'); + }); + }); + allTabs.forEach(tab => { - const tabText = tab.querySelector('.nav-text').textContent.toLowerCase(); + const label = tab.querySelector('.nav-text'); + const tabText = label ? label.textContent.toLowerCase() : ''; const tabId = tab.getAttribute('data-tab'); let tabHasMatch = false; - - // Check if tab name matches + if (tabText.includes(normalizedSearch)) { tab.closest('li').classList.remove('admin-search-hidden'); tab.classList.add('admin-search-highlight'); + revealGroupOf(tab); tabHasMatch = true; hasVisibleSections = true; - - // Show submenu for matched tab + const submenu = document.getElementById(tabId + '-submenu'); if (submenu) { submenu.style.display = 'block'; - // Show all sections under this tab submenu.querySelectorAll('.admin-nav-section').forEach(section => { section.closest('li').classList.remove('admin-search-hidden'); }); } } - - // Check sections under this tab + const sections = document.querySelectorAll(`.admin-nav-section[data-tab="${tabId}"]`); let sectionHasMatch = false; - + sections.forEach(section => { - const sectionText = section.querySelector('.nav-text').textContent.toLowerCase(); - + const sectionLabel = section.querySelector('.nav-text'); + const sectionText = sectionLabel ? sectionLabel.textContent.toLowerCase() : ''; + if (sectionText.includes(normalizedSearch)) { - // Show the section section.closest('li').classList.remove('admin-search-hidden'); section.classList.add('admin-search-highlight'); sectionHasMatch = true; hasVisibleSections = true; - - // Show the parent tab + tab.closest('li').classList.remove('admin-search-hidden'); - - // Show the submenu + revealGroupOf(tab); + const submenu = document.getElementById(tabId + '-submenu'); if (submenu) { submenu.style.display = 'block'; } } }); - - // If tab has matching sections but doesn't match itself, remove tab highlight + + // A tab surfaced only because a section matched is not itself a hit. if (sectionHasMatch && !tabHasMatch) { tab.classList.remove('admin-search-highlight'); } }); - - // Show "No results" message if nothing found + showSearchResults(hasVisibleSections, normalizedSearch); } function showAllAdminSections() { - // Remove all search-related classes and show all items - document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach(item => { - item.closest('li').classList.remove('admin-search-hidden'); + // Restore the normal browsing state: nothing hidden, nothing highlighted, + // submenus closed, and groups back to their persisted expansion. + document.querySelectorAll('.admin-nav-group-item').forEach(group => { + group.classList.remove('admin-search-hidden'); + }); + + document.querySelectorAll('.admin-nav-tab, .admin-nav-section, .admin-nav-group').forEach(item => { + const listItem = item.closest('li'); + if (listItem) { + listItem.classList.remove('admin-search-hidden'); + } item.classList.remove('admin-search-highlight'); }); - - // Hide all submenus (normal collapsed state) + document.querySelectorAll('[id$="-submenu"]').forEach(submenu => { submenu.style.display = 'none'; }); - - // Remove search results message + + document.querySelectorAll('[data-admin-group-toggle]').forEach(toggle => { + const groupId = toggle.getAttribute('data-admin-group-toggle'); + const expanded = toggle.getAttribute('aria-expanded') === 'true'; + setAdminGroupExpanded(groupId, expanded, false); + }); + hideSearchResults(); } @@ -462,4 +654,4 @@ function hideSearchResults() { if (noResults) { noResults.remove(); } -} \ No newline at end of file +} diff --git a/application/single_app/templates/_sidebar_nav.html b/application/single_app/templates/_sidebar_nav.html index 198eab56b..8c2880678 100644 --- a/application/single_app/templates/_sidebar_nav.html +++ b/application/single_app/templates/_sidebar_nav.html @@ -441,7 +441,28 @@
-
diff --git a/application/single_app/templates/admin/_panes/access-roles.html b/application/single_app/templates/admin/_panes/access-roles.html new file mode 100644 index 000000000..e87ef059d --- /dev/null +++ b/application/single_app/templates/admin/_panes/access-roles.html @@ -0,0 +1,79 @@ +
+
+
+ Permissions +
+

Control which users can access specific administrative views related to safety and feedback.

+ + +
+ + + +
+

+ Required app role value: SafetyViolationAdmin. Assign this role to users or groups in the Enterprise App before enabling the requirement. If disabled, any user with the general Admin app role can access the Safety Violations admin page. +

+ + +
+ + + +
+

+ Required app role value: FeedbackAdmin. Assign this role to users or groups in the Enterprise App before enabling the requirement. If disabled, any user with the general Admin app role can access the User Feedback admin page. Requires Enable User Feedback to be active. +

+
+ +
+
+ App Role Requirements +
+

+ Every setting that can require an Entra app role, gathered here so the + full access policy can be read in one place. Each switch is a mirror of + the setting on its own tab, so changing it here changes it there. +

+ {# Rows are built from the page itself by + admin_access_roles_roster.js, so a new role requirement appears here + automatically and this list can never fall out of step. #} +
+

+ No app role requirements are available on this page. +

+
+ +
+
+ Access Denied Message +
+

+ Shown to a user whose account does not carry a required role. +

+
+ + Shown to signed-in users who lack the required roles. Use Enter for line breaks. + +
+
+ +
diff --git a/application/single_app/templates/admin/_panes/actions.html b/application/single_app/templates/admin/_panes/actions.html new file mode 100644 index 000000000..221cfac5b --- /dev/null +++ b/application/single_app/templates/admin/_panes/actions.html @@ -0,0 +1,205 @@ +
+
+
+
+
Document Action Capabilities
+

Configure the document actions shown in the Action dropdown in Chat and Workflow. These controls live in the Agents and Actions tab, but remain separate from the global agent and custom action cards below.

+
+ Chat: 2-300 | Workflow: 2-1000 +
+ +
+
+
+
+ + + +
+
+
+ + {{ analyze_capability.chat_max_documents }} +
+
+
+ +
+
+ +
+
+
+ 2 + 300 +
+
+
+
+ + {{ analyze_capability.workflow_max_documents }} +
+
+
+ +
+
+ +
+
+
+ 2 + 1000 +
+
+
+
+
+
+
+ + + +
+
+
+ + {{ comparison_capability.chat_max_documents }} +
+
+
+ +
+
+ +
+
+
+ 2 + 300 +
+
+
+
+ + {{ comparison_capability.workflow_max_documents }} +
+
+
+ +
+
+ +
+
+
+ 2 + 1000 +
+
+
+
+
+
+ + +
+
+ Global Actions Configuration +
+

+ Configure custom actions and tools to extend functionality with integrations and specialized capabilities. +

+ + {% if not settings.enable_semantic_kernel %} +
+ Actions are not available while agents are disabled. Enable agents above to configure actions. +
+ {% else %} +
+
+
Global Actions
+ +
+

Disable a global action to keep the configuration without exposing it to runtime action loading until it is re-enabled.

+ {% if settings.per_user_semantic_kernel %} + +
+
Workspace Action Feature Toggles
+
+ + + + +
+
+ + + + +
+
+ {% endif %} + + +
+
Core Action Toggles
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ Tabular Processing Action + +
+ Automatically enabled when Enhanced Citations is enabled +
+
+ + + + + + + + + +
Display NameDescriptionActions
+
+ {% endif %} +
+
diff --git a/application/single_app/templates/admin/_panes/agents.html b/application/single_app/templates/admin/_panes/agents.html new file mode 100644 index 000000000..889caa9cc --- /dev/null +++ b/application/single_app/templates/admin/_panes/agents.html @@ -0,0 +1,283 @@ +
+ +

+ Configure AI agents and actions for enhanced functionality. Agents provide AI-driven task automation while Actions extend functionality with custom tools and integrations. +

+ + + + + + {% set analyze_capability = settings.document_action_capabilities.analyze %} + {% set comparison_capability = settings.document_action_capabilities.comparison %} +
+
+ Agents Configuration +
+

+ Configure AI agents powered by Semantic Kernel for task automation and orchestration. +

+
+
+ Agents Page Customization +
+

Customize the public Agents page hero, color treatment, and optional guidance message.

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Used when two tone gradient is selected.
+
+
+ + +
Shown below the Agents page hero. Use this for contact details, request guidance, or governance reminders.
+
+
+
+ + +
+
When disabled, the Agents tab details popup hides instructions and the catalog API response omits them.
+
+
+
+
Promoted Popular Agents
+

Highlight selected agents in the Popular tab even before they have usage counts. Users only see promoted agents that are already visible to them.

+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+
When enabled, promoted agents show the configured tag in the Popular tab.
+
+
+
+
+
+ + +
+
+ +
+
+
+ + + + + + + + + +
AgentShow InActions
+
+ + +
+
+
+
+
+ + + + + +
+ +
+ {% if not settings.enable_semantic_kernel %} +
+ Agents UI is not available while agents are disabled. +
+ {% else %} +
+
+ + + +
+ {% if settings.per_user_semantic_kernel %} + +
+
Workspace Feature Toggles
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ + + {% endif %} + +
+

Agent Orchestration Settings

+
+
+

Configure how the chat system orchestrates agents (single or multi-agent group chat).

+
+ + +
+ + +
+
+

Global Agents

+
+ + +
+
+

Disable a global agent to keep it saved for admins while hiding it from runtime selection until it is re-enabled.

+
+ {% if settings.orchestration_type == "default_agent" %} + + {% else %} + + {% endif %} + + +
+ + + + + + + {% if settings.orchestration_type == "default_agent" %} + + {% else %} + + {% endif %} + + + + +
NameDisplay NameDescriptionSelected AgentOrchestrator AgentActions
+
+ {% endif %} +
+ + {% if settings.enable_agent_template_gallery %} +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+
+
+ Agent Template Approvals +
+

Template submissions are now reviewed from the shared approvals page.

+
+ + Open Approvals Queue + +
+
+ {% endif %} +
diff --git a/application/single_app/templates/admin/_panes/archiving.html b/application/single_app/templates/admin/_panes/archiving.html new file mode 100644 index 000000000..d2943f47d --- /dev/null +++ b/application/single_app/templates/admin/_panes/archiving.html @@ -0,0 +1,21 @@ +
+
+
+ Conversation Archiving +
+

When enabled, conversation deletions will be archived instead of permanently deleted.

+
+ + + +
+
+
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/audio-video.html b/application/single_app/templates/admin/_panes/audio-video.html new file mode 100644 index 000000000..fba46cabe --- /dev/null +++ b/application/single_app/templates/admin/_panes/audio-video.html @@ -0,0 +1,472 @@ +
+
+
+
+
+ +
+
+

+ AI Video Intelligence +

+

Extract insights and make video content searchable and interactive

+
+
+ +
+ +
+ + Make Video Content Fully Searchable: Upload videos to your workspace and automatically extract transcripts, identify speakers, detect topics, and generate searchable timestamps. Your AI can then answer questions about video content, cite specific moments, and provide deep insights. +
+ + +
+
+ + +
+

+ Upload video files (MP4, AVI, MOV, etc.) to workspaces where Azure Video Indexer automatically processes them. + Extract spoken content, identify speakers, detect faces, recognize brands, and generate searchable metadata—perfect for meetings, presentations, training videos, and recorded content. +

+
+ + Full-Text Search + + + Speaker ID + + + Timestamped Citations + + + Multi-Language + +
+
+ + {% set public_video_indexer_endpoint = "https://api.videoindexer.ai" %} + {% set government_video_indexer_endpoint = "https://api.videoindexer.ai.azure.us" %} + {% set configured_video_indexer_endpoint = settings.video_indexer_endpoint or default_video_indexer_endpoint %} + {% if configured_video_indexer_endpoint == government_video_indexer_endpoint %} + {% set video_indexer_cloud = "usgovernment" %} + {% elif configured_video_indexer_endpoint == public_video_indexer_endpoint %} + {% set video_indexer_cloud = "public" %} + {% else %} + {% set video_indexer_cloud = "custom" %} + {% endif %} + {% if azure_environment == "usgovernment" %} + {% set azure_environment_label = "Azure Government" %} + {% elif azure_environment == "custom" %} + {% set azure_environment_label = "Custom Cloud" %} + {% else %} + {% set azure_environment_label = "Azure Public / Commercial" %} + {% endif %} + + +
+
+ Azure Video Indexer Configuration +
+

Connect to your Azure Video Indexer resource for advanced video processing and content extraction.

+ +
+ Identity overview: +
    +
  • Video Indexer can use its own managed identity to reach the linked Storage account during account creation.
  • +
  • SimpleChat authenticates to Video Indexer with the App Service system-assigned managed identity.
  • +
  • The App Service managed identity must have Contributor on the Video Indexer resource.
  • +
  • See Azure Video Indexer documentation for setup details
  • +
+
+ +
+ + +
Choose the endpoint family that matches your deployed cloud. Use Custom only when you need a non-standard Video Indexer endpoint.
+
+ +
+ Current App Service environment: {{ azure_environment_label }}. This selector saves the Video Indexer API endpoint. If you are switching between Azure Public, Azure Government, or a custom cloud, update AZURE_ENVIRONMENT in your App Service configuration as well. +
+ +
+ The selected Video Indexer cloud does not match this app's current AZURE_ENVIRONMENT. Update the App Service configuration if you are moving to a different cloud. +
+ + + +
+ + +
Only required when using a custom cloud or non-standard Video Indexer endpoint.
+
+ +
+ + +
Saved endpoint value used for Video Indexer API calls.
+
+ +
+ + +
The Azure resource group containing your Video Indexer account
+
+ +
+ + +
Your Azure subscription ID
+
+ +
+ + +
The name of your Video Indexer account resource
+
+ +
+ + +
Azure region where your Video Indexer account is deployed (e.g., eastus, westus2, northeurope)
+
+ +
+ + +
Found in the Video Indexer account Overview page in Azure Portal
+
+ +
+ + +
Default for {{ azure_environment_label }}: {{ default_video_indexer_arm_api_version }}
+
+ +
+ + +
+
+
+ + +
+
+
+
+ +
+
+

+ AI Voice Conversations +

+

Transform your AI experience with natural voice interactions

+
+
+ +
+ + +
+
+ + +
+

+ Let users opt in to a bundled sound when an AI response finishes outside their active view. + These local cues do not require Azure Speech Service. +

+
+ +
+ + One Speech resource, three features: Audio file uploads, Voice Input, and Voice Responses all use the same Azure Speech Service section below. Configure the Speech resource once, then turn on whichever speech features you need. +
+ + +
+ {% set audio_runtime = audio_runtime_capabilities or {} %} + {% set audio_supported_extensions = audio_runtime.supported_extensions or [] %} + {% set audio_direct_extensions = audio_runtime.direct_transcription_extensions or [] %} +
+ + +
+

+ Upload audio files (MP3, WAV, M4A, etc.) to workspaces where they're automatically transcribed and indexed. + The AI can then search, analyze, and answer questions about audio content—perfect for meetings, interviews, lectures, and podcasts. +

+
+
+ {{ audio_runtime.message or 'Audio runtime support has not been checked yet.' }} +
+
+ Supported audio upload extensions: {{ audio_supported_extensions|join(', ') }} +
+ {% if audio_direct_extensions %} +
+ Without FFmpeg, source-file fallback is best for: {{ audio_direct_extensions|join(', ') }}. Container builds can include FFmpeg for broader codec support. +
+ {% endif %} +
+
+ + +
+
+ + +
+

+ Talk to your AI instead of typing. Record voice messages up to 90 seconds directly in the chat interface. + Azure Speech Service instantly transcribes your speech with high accuracy, supporting multiple languages and accents. +

+
+ + Hands-Free + + + Accessible + + + Fast Input + +
+
+ + +
+
+ + +
+

+ Hear AI responses read aloud in natural, human-like voices powered by Azure's advanced neural Text-to-Speech. + Each message includes a speaker button—click to listen while multitasking, commuting, or whenever reading isn't convenient. +

+
+ + Natural Voices + + + Multitask-Friendly + + + Multi-Language + +
+
+ + +
+
+ Azure Speech Service Configuration +
+

Configure the shared Azure Speech resource used by audio transcription, voice input, and text-to-speech.

+ +
+ Shared configuration: These settings apply to all enabled speech features. If you use managed identity and also enable Voice Responses, fill in the Speech Resource ID field so the Speech SDK can authenticate synthesis requests. +
+ +
+ + +
Use the resource-specific custom-domain endpoint when selecting Managed Identity.
+
+ +
+ + +
Required for speech recognition locale defaults and for text-to-speech when using Managed Identity.
+
+ +
+
+ Resource ID builder: If the full ARM resource ID is hard to find in the portal, enter the subscription ID, resource group, and Speech resource name below. Simple Chat can build the resource ID for you. +
+ +
+
+ + +
+
+ + +
+
+ + +
If you use a custom-domain Speech endpoint, this is usually the first part of that hostname.
+
+
+ +
+
+ +
Provide Subscription ID, Resource Group, and Speech Resource Name to auto-build the ARM resource ID.
+ + + +
Required for Voice Responses when using Managed Identity. You can paste the full value manually, or let the fields above build it for you.
+
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
Required only when Authentication Type is set to Key.
+
+ +
+ Managed identity roles: Start with Cognitive Services Speech User. If fast transcription or other write-style speech operations still return authorization errors, add Cognitive Services Speech Contributor. Managed identity also requires a custom-domain endpoint. +
+ +
+

+ + + Enhanced Citations + + will dramatically improve the citation experience for video and audio files. + +

+
+
diff --git a/application/single_app/templates/admin/_panes/backup.html b/application/single_app/templates/admin/_panes/backup.html new file mode 100644 index 000000000..a00542658 --- /dev/null +++ b/application/single_app/templates/admin/_panes/backup.html @@ -0,0 +1,323 @@ +
+
+
+
+

Start Here

+

Use these checkpoints before running backup, migration, restore, or advanced repair actions.

+
+ +
+
+
+
+
Back up
+

Configure dedicated storage, encryption, schedule, and backup scope before queueing jobs.

+ +
+
+
+
+
Migrate
+

Connect a destination, choose who moves, run preflight, then execute a recoverable transfer.

+ +
+
+
+
+
Restore
+

Review backup readiness and stage restore decisions from Backup Inventory.

+ +
+
+
+
+
RU Boost
+

Temporarily raise eligible Cosmos capacity during approved backup or migration windows.

+ +
+
+
+
+ +
+
+
+ +
+
+

Backup

+

Configure when backups run, where artifacts are stored, and how backup files are encrypted.

+
+
+ +
+
+
+
Schedule
+

Full backups run on the selected cadence; partial backups run daily only.

+
+
+
+ + +
+
+
+ + +
+
+ + +
Default is 03:00 UTC.
+
+
+ +
+ + + +
+ +
Automatic cleanup keeps the newest successful full backup as a safety baseline.
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+ + +
Core application records required for meaningful restore and migration.
+
+
+
+
+ + +
Search index schemas and retrievable indexed documents.
+
+
+
+
+ + +
Original source files used by Enhanced Citations.
+
+
+
+
+
+
+
+ +
+
+
+
Storage
+

Store backup artifacts in Azure Blob Storage.

+
+ +
+
+ + Use a dedicated backup storage account. Data Management will reject storage that matches the Enhanced Citations connection string or Blob endpoint. +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
No connection string saved yet.
+
+
+ + +
+
+
+ +
+
+
+
Encryption
+

Generate a 256-bit backup encryption key.

+
+ +
+
+ + +
+
+
Key storage
+
Not configured
+
Key reference
+
Not configured
+
+
+ +
+
Key Vault is strongly recommended
+
Generated backup encryption keys are stored in the Data Management settings document when Key Vault is not enabled.
+ Open Key Vault settings +
+
+
+ +
+
+
+
Cosmos Backup Performance
+

Backups stream deterministic checkpoint batches and commit only verified work. Higher concurrency can increase source Cosmos cost and pressure.

+
+ +
+ +
+
+
Source Blob Backup Performance
+

Source files stream through bounded chunks and durable per-file checkpoints. Peak transfer buffering is bounded by concurrent transfers multiplied by chunk size.

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
Defaults bound application transfer buffering to approximately 32 MiB, excluding Azure SDK overhead. Throttling temporarily reduces active transfers.
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
The backup records the current source capacity, raises only eligible targets up to 10,000 RU/s, and restores the original setting after completion, cancellation, failure, or recovery. This can increase Cosmos charges and requires source ARM throughput permission.
+
+
+
+ +
+
+
+
Backup Operations
+

Queue immediate full or partial backup jobs using the settings above.

+
+
+ + +
+
+

Jobs use Cosmos-backed leases so scaled-out App Service workers do not run the same backup twice.

+
+
+ + +
diff --git a/application/single_app/templates/admin/_panes/branding.html b/application/single_app/templates/admin/_panes/branding.html new file mode 100644 index 000000000..c648c07fa --- /dev/null +++ b/application/single_app/templates/admin/_panes/branding.html @@ -0,0 +1,165 @@ +
+ +

+ Configure general application settings, including the application's title, logo, and landing page text. +

+
+
+ Branding +
+

+ Configure your application's title, logo, and branding elements. +

+
+ + +
+
Logo Settings
+
+ + + +
+
+ + + +
+
+
+ + {{ settings.landing_page_logo_scale_percent | default(100) }}% +
+ +
+ 50% + 500% +
+ + Adjust the logo size on the home page only. This does not change the logo size in the top or sidebar navigation. + +
+
+ + + This logo will be displayed in light mode and stored at up to 500px tall so the main page can render it sharply without keeping oversized assets in settings. +
+
+ + + This logo will be displayed in dark mode. If not provided, the light mode logo will be used in both themes. Dark logos are also stored at up to 500px tall. +
+
+ + + Recommended: 16x16 or 32x32 pixel png or jpg or ico. +
+
+ +
+
+ Home Page Text +
+

Configure the text content displayed on your application's home page using Markdown formatting.

+ +
+ + + Choose how the landing page markdown is aligned on the home page. +
+ +
+ + + +
+ + + + +
+ +
+
+ Appearance +
+

Configure the app appearance and theme settings.

+ +
+ +
+ + + +
+

Users can still toggle dark mode individually from the navigation bar.

+
+ +
+ +
+ + + +
+

Users can still toggle the left navigation sidebar individually from the header.

+
+
+ + + + + + +
diff --git a/application/single_app/templates/admin/_panes/chat-experience.html b/application/single_app/templates/admin/_panes/chat-experience.html new file mode 100644 index 000000000..9073b87ba --- /dev/null +++ b/application/single_app/templates/admin/_panes/chat-experience.html @@ -0,0 +1,130 @@ +
+
+
+ Processing Thoughts +
+

When enabled, real-time processing steps are shown to users during chat responses and persisted for later review.

+
+ + + +
+
+ + +
+
+ Chat File Uploads +
+

+ Control whether users can upload files directly into chat conversations. +

+
+ + + +
+ +
+
+ Conversation Contents Drawer +
+

+ Let users navigate long conversations from a list of their prompts. +

+
+ + + +
+

+ When enabled, users can turn the drawer off for their own account from Profile settings. +

+
+
+ + + +
+

+ Required app role value: ChatFileUploadUser. Assign this role to users or groups in the Enterprise App before enabling the requirement. Existing chat attachments remain visible; this only controls new uploads. +

+
+
+
+ Workspace Scope Lock +
+

+ Control whether users can unlock workspace scope in chat conversations. When scope is locked, conversations are restricted to the workspaces that produced search results, preventing accidental cross-contamination with other data sources. +

+
+ + + +
+
+
+
+ Conversation History +
+

+ How many previous messages are carried into each new request. +

+
+ + +
+
+ +
+
+ Default System Prompt +
+

+ The system prompt applied to conversations that do not set their own. +

+
+ + +
+
+ +
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/citation.html b/application/single_app/templates/admin/_panes/citation.html new file mode 100644 index 000000000..4d42286e9 --- /dev/null +++ b/application/single_app/templates/admin/_panes/citation.html @@ -0,0 +1,244 @@ +
+ +

+ Configure standard and enhanced citations features for your and group workspaces. +

+ + +
+
+ Standard Citations +
+

+ Standard citations is always enabled for both Your Workspace and Group Workspace. +

+

+ Users can see text content of the source/citation for documents. +

+
+ + +
+
+ Enhanced Citations +
+

+ Enable Enhanced Citation to store files in a Storage Account, + and show direct references (Preview feature, files are saved to storage, presentation + layer will be available in a future release). +

+ + {% set ec_storage_status = settings.enhanced_citations_storage_status or {} %} + {% set ec_storage_state = ec_storage_status.state or 'not_initialized' %} + {% if settings.enable_enhanced_citations %} +
+
Enhanced Citations storage startup status
+
{{ ec_storage_status.message or 'Enhanced Citations storage status is not available for this process.' }}
+
+ Startup skips live storage container checks so storage outages do not block application boot. + Use the connection test to validate current storage reachability and container access. +
+ +
+
+ {% endif %} + + +
+ + + +
+ + + +
+
diff --git a/application/single_app/templates/admin/_panes/classification.html b/application/single_app/templates/admin/_panes/classification.html new file mode 100644 index 000000000..8a4dc6e11 --- /dev/null +++ b/application/single_app/templates/admin/_panes/classification.html @@ -0,0 +1,45 @@ +
+
+
+ Document Classification +
+

+ Enable this feature to allow users to classify documents uploaded to their workspaces using predefined categories. +

+
+ + + +
+ + +
+
Classification Categories
+

Define the labels and corresponding colors for document classification.

+ + + + + + + + + + + +
LabelColorActions
+ + + +
+
+
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/content-safety.html b/application/single_app/templates/admin/_panes/content-safety.html new file mode 100644 index 000000000..88eae95bb --- /dev/null +++ b/application/single_app/templates/admin/_panes/content-safety.html @@ -0,0 +1,181 @@ +
+ +

+ Configure content safety, archiving, and user feedback settings. If Content Safety is enabled, user + messages will be sent to the safety endpoint for analysis. If User Feedback is enabled, users will see + thumbs up/down to provide feedback on AI responses. +

+
+
+ Content Safety +
+

Enable content safety to filter out inappropriate content.

+
+ + + +
+ + +
+ + +
+ + + +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+ + +
Displayed when Content Safety blocks a chat message.
+
+ +
+ + + +
+

Disable this option to show only the configured message.

+ + +
+
+
+ + + + + + + + + +
diff --git a/application/single_app/templates/admin/_panes/control-center-config.html b/application/single_app/templates/admin/_panes/control-center-config.html new file mode 100644 index 000000000..6567e5537 --- /dev/null +++ b/application/single_app/templates/admin/_panes/control-center-config.html @@ -0,0 +1,180 @@ +
+

+ Configure Control Center access and permissions for administrators. +

+ +
+
+
+
+ +
+
+

+ Automatic Data Refresh +

+

Run the Control Center metrics refresh overnight on a daily timezone-aware schedule

+
+
+
+ +
+ + + +
+ +
+
+ + + +
+ Your browser timezone is local time. +
+
+
+ +
+ {% if settings.control_center_auto_refresh_next_run %} + Loading local time... + {% else %} + Will be calculated when saved + {% endif %} +
+
+
+
+ + +
+
+
+
+ +
+
+

+ Control Center Access +

+

Manage who can access Control Center features and administrative tools

+
+
+ +
+ +
+ + About Control Center: The Control Center is a powerful administrative dashboard that provides user management, group oversight, public workspace control, and detailed activity monitoring. Use role-based access controls below to delegate administrative responsibilities. +
+ + +
+
+ + +
+

+ Required app role value: ControlCenterAdmin. Assign this role to users or groups in the Enterprise App before enabling the requirement. When enabled, only users assigned this role can access the Control Center and all management features. Regular Admins will lose access. +

+
+ + User Management + + + Group Management + + + Public Workspaces + + + Activity Logs + +
+
+ + +
+
+ + +
+

+ Dashboard-only app role value: ControlCenterDashboardReader. Assign this role to users or groups in the Enterprise App before enabling dashboard-only access. Users assigned ControlCenterAdmin can also access the dashboard when Control Center app-role enforcement is enabled. +

+
+ + Dashboard Statistics + + + Activity Trends + + + Usage Metrics + +
+
+ +
+ + Important: Configure these roles in your identity provider (Entra ID). When role requirements are enabled, standard Admins without the specific roles will be denied access to Control Center features. +
+ + +
+
+ Best Practices +
+
    +
  • ControlCenterAdmin: Grant to IT administrators who need full control over users, groups, and workspaces
  • +
  • ControlCenterDashboardReader: Grant to managers, compliance officers, or stakeholders who need visibility into platform usage without administrative powers
  • +
  • Separation of Duties: Enable role requirements if you need to restrict Control Center access from general application admins
  • +
  • Audit Trail: All Control Center actions are logged in Activity Logs for compliance and security auditing
  • +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/cosmos-editor.html b/application/single_app/templates/admin/_panes/cosmos-editor.html new file mode 100644 index 000000000..3639ba706 --- /dev/null +++ b/application/single_app/templates/admin/_panes/cosmos-editor.html @@ -0,0 +1,49 @@ +
+
+
+
+

Cosmos DB JSON Editor

+

Query SimpleChat Cosmos DB containers, inspect one document, and save JSON changes with ETag protection.

+
+ +
+ +
+ The Cosmos DB JSON editor is locked. Acknowledge the danger prompt before querying or editing data. +
+
+
+
+ + +
Choose a known SimpleChat Cosmos DB container.
+
+
+ + +
Max 100 per request.
+
+
+ + +
Empty query returns only the first 100 documents. Custom SELECT queries can page beyond 100 with Next Page.
+
+
+
+ + No query has run yet. +
+
+ Query results and the JSON editor open in a modal so the Data Management page stays compact. +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/cosmos.html b/application/single_app/templates/admin/_panes/cosmos.html new file mode 100644 index 000000000..6163be982 --- /dev/null +++ b/application/single_app/templates/admin/_panes/cosmos.html @@ -0,0 +1,1141 @@ +
+
+
+
+
+ Cosmos Document Access Index +
+

+ Monitor the document access projection used to remove expensive cross-partition document access queries. +

+
+
+ + {% if enable_dai_debug %} + + + {% endif %} +
+
+ +
+ + Document access projection maintenance is automatic. The background scheduler repairs fail-open projection records first, then runs bounded backfill batches repeatedly while work remains. Production read metrics below show DAI-served reads, Redis cache hits, source fallbacks, RU, and latency without requiring shadow validation. + {% if enable_dai_debug %}Debug controls and shadow validation diagnostics are visible because enable_dai_debug is enabled in app settings.{% endif %} +
+ +
+ +
+
+
+
Container
+ Not loaded +
+
+
+
+
Write-through
+ Not loaded +
+
+
+
+
Read Path
+ Not loaded +
+
+
+
+
Redis List Cache
+ Not loaded +
+
+ {% if enable_dai_debug %} +
+
+
Shadow Validation
+ Not loaded +
+
+ {% endif %} +
+
+
Auto Maintenance
+ Not loaded +
+
+
+
+
Next Maintenance Action
+
Not loaded
+
+
+
+
+
More Work Pending
+
Not loaded
+
+
+
+
+
Active Loop Interval
+
Not loaded
+
+
+
+ + {% if enable_dai_debug %} +
+
Automatic Maintenance and Diagnostics
+
+
+
+ + +
+
Always on. New and changed documents synchronize into the access index and fail open to repair records if projection updates fail.
+
+
+
+ + +
+
Always on. Maintenance keeps running bounded repair and backfill batches until DAI is healthy.
+
+
+
+ + +
+
Compares source list results to projection rows and logs mismatches without changing reads.
+
+
+ + +
Documents processed per manual or scheduled batch.
+
+
+ + +
Fail-open repair records reconciled before each backfill batch.
+
+
+
+ +
+
Default Read Path
+
+
+
+ + +
+
Always on. DAI-backed document and tag list reads are the normal path; source-container fallback remains automatic when backfill is not ready, repairs are pending, or a DAI query fails.
+
+
+
+ + +
+
Uses Redis read-through caching for DAI document, tag, and legacy-count reads. If Redis is unavailable, reads bypass cache and use DAI directly.
+
+
+ + +
Default 900 seconds. Scope-version invalidation makes document changes visible immediately; TTL clears unreachable old entries.
+
+
+
+ {% endif %} + +
+
+
+
Backfill State
+ Not loaded +
+
+
+
+
Repair Backlog
+
Not loaded
+
+
+
+
+
15m DAI Read Attempts
+
Not loaded
+
+
+
+
+
15m Redis Cache Hit Rate
+
Not loaded
+
+
+
+
+
15m Cache Hits / Misses
+
Not loaded
+
+
+
+
+
15m Cache Bypasses / Errors
+
Not loaded
+
+
+
+
+
15m Cache Invalidations
+
Not loaded
+
+
+
+
+
15m Served from DAI
+
Not loaded
+
+
+
+
+
15m Source Fallbacks
+
Not loaded
+
+
+
+
+
15m Fallback Rate
+
Not loaded
+
+
+
+
+
15m DAI Read RU
+
Not loaded
+
+
+
+
+
15m Avg / P95 Latency
+
Not loaded
+
+
+
+
+
Last Fallback Reason
+
Not loaded
+
+
+
+
+
Last DAI Read Metric
+
Not loaded
+
+
+
+
+
Last Cache Event
+
Not loaded
+
+
+
+
+ + Production read metrics are lightweight in-process counters for the current app worker. Application Insights logs remain the durable fleet-wide source for fallback warnings and query failures. +
+
+ {% if enable_dai_debug %} +
+
+
Last Shadow Result
+ Not run +
+
+
+
+
Shadow Mismatches
+
Not loaded
+
+
+
+
+
Source / Validation RU
+
Not loaded
+
+
+
+
+
Validation Index RU
+
Not loaded
+
+
+
+
+
Candidate Read RU
+
Not loaded
+
+
+
+
+
Estimated Wave 5 Savings
+
Not loaded
+
+
+
+
+
Source / Candidate Latency
+
Not loaded
+
+
+
+
+
Estimated Wave 5 Latency
+
Not loaded
+
+
+
+
+ + Rolling decision metrics aggregate shadow-validation samples over recent windows. Use these totals to compare source container RU with candidate access-index RU before enabling the future read path or Redis document access cache. +
+
+
+
+
5m Source / Candidate RU
+
Not loaded
+
+
+
+
+
5m Estimated Wave 5 Savings
+
Not loaded
+
+
+
+
+
15m Source / Candidate RU
+
Not loaded
+
+
+
+
+
15m Estimated Wave 5 Savings
+
Not loaded
+
+
+
+
+
15m Validation Overhead
+
Not loaded
+
+
+
+
+
15m Shadow Samples
+
Not loaded
+
+
+ {% endif %} +
+
+
Current Scope
+
Not loaded
+
+
+
+
+
Completed Scopes
+
Not loaded
+
+
+
+
+
Total Documents Processed
+
Not loaded
+
+
+
+
+
Total Documents Failed
+
Not loaded
+
+
+
+
+
Rows Upserted
+
Not loaded
+
+
+
+
+
Rows Deleted
+
Not loaded
+
+
+
+
+
Last Batch Completed
+
Not loaded
+
+
+
+
+
Last Error
+
Not loaded
+
+
+
+
+ +
+
+
+
+ Cosmos Maintenance +
+

+ Review expected indexing policies and clean up stale operational cache documents without touching source configuration or user data. +

+
+
+ + + + +
+
+ +
+ + Indexing maintenance only adds missing expected composite indexes and preserves existing policy paths. Composite indexes can increase write-index overhead while improving supported lookup and ordered-query speed. Stale cleanup is allowlisted to obsolete cache artifacts, supports dry-run review, and deletes at most one bounded batch per run. +
+
+ +
+
+
+
Indexing Policy Status
+ Not loaded +
+
+
+
+
Indexing Mode
+
Not loaded
+
+
+
+
+
Containers Checked
+
Not loaded
+
+
+
+
+
Missing Expected Indexes
+
Not loaded
+
+
+
+
+
Updated Containers
+
Not loaded
+
+
+
+
+
Indexing Failures
+
Not loaded
+
+
+
+
+
Last Indexing Evaluation
+
Not loaded
+
+
+
+
+
Stale Cleanup Status
+ Not loaded +
+
+
+
+
Cleanup Mode
+
Not loaded
+
+
+
+
+
Cleanup Candidates
+
Not loaded
+
+
+
+
+
Deleted Docs
+
Not loaded
+
+
+
+
+
Cleanup Failures
+
Not loaded
+
+
+
+
+
More Candidates
+
Not loaded
+
+
+
+
+
Cleanup Categories
+
Not loaded
+
+
+
+
+
Last Cleanup Evaluation
+
Not loaded
+
+
+
+
+ + + + + + {% if enable_dai_debug %} + + {% endif %} + +
+
+
+
+ Cosmos DB Throughput +
+

+ Monitor RU utilization and automatically adjust shared database or dedicated container throughput. +

+
+ +
+ +
+ + + + + + +
+ +
+ + Automation checks Cosmos throughput on the Metrics Window cadence while enabled and saves the last observed database or container view. SimpleChat can scale throughput up or down at 10,000 RU/s or lower. Above 10,000 RU/s, SimpleChat monitors utilization only; use the Azure portal for capacity changes, which can take 4 to 6 hours. Native Cosmos autoscale conversion is separate from SimpleChat scale-up and scale-down automation. +
+ + + +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
Metrics Window
+
+
+ +
+ + min +
+
+
+
+ +
+
Scale Up Policy
+
+
+
+ + +
+
+
+ +
+ + % +
+
+
+ +
+ + RU/s +
+
+
+ +
+ + min +
+
+
+ + +
SimpleChat-managed scaling stops at 10,000 RU/s. Use the Azure portal above this limit.
+
+
+
+ + +
+
+
+
+ +
+
Scale Down Policy
+
+
+
+ + +
+
+
+ +
+ + % +
+
+
+ +
+ + RU/s +
+
+
+ +
+ + min +
+
+
+ + +
+
+
+ + +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ Use this when container throughput should follow one standard automation policy instead of per-container overrides. Global policy also applies the Cosmos autoscale conversion setting above to current and future dedicated-throughput containers. +
+
+
+ +
+ +
+
+
+
Mode
+
Not loaded
+
+
+
+
+
Current RU/s
+
Not loaded
+
+
+
+
+
RU Utilization
+
Not loaded
+
+
+
+
+
Last Checked
+
Not loaded
+
+
+
+ +
+
+ +
+ + + + +
+
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + +
+ + Mode + + + + + + + + + + Actions
Refresh to load Cosmos metrics.
+
+
+ + + + +
diff --git a/application/single_app/templates/admin/_panes/custom-pages.html b/application/single_app/templates/admin/_panes/custom-pages.html new file mode 100644 index 000000000..00fc68987 --- /dev/null +++ b/application/single_app/templates/admin/_panes/custom-pages.html @@ -0,0 +1,133 @@ +
+

+ Enable trusted deployment-time custom pages and manage metadata for simple static pages served from the application custom_pages folders. +

+ +
+
+
+ Custom Pages +
+ +
+
+ + + + +
+

When disabled, /custom routes return Not Found before loading custom metadata, files, or Python extensions.

+ +
+
+ + + This name appears when custom pages are grouped into a menu. +
+ +
+ + + When disabled, 1-2 custom pages show as top-level nav items and 3+ pages show as a menu. +
+ +
+
+
Static Page Metadata
+

Create metadata contracts for simple pages that use files from custom_pages/html, css, js, assets, and json.

+
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
SlugTitleTypeAccessRolesStatusNavActions
Loading custom pages...
+
+
+
+ +
diff --git a/application/single_app/templates/admin/_panes/embeddings.html b/application/single_app/templates/admin/_panes/embeddings.html new file mode 100644 index 000000000..f05c7d66f --- /dev/null +++ b/application/single_app/templates/admin/_panes/embeddings.html @@ -0,0 +1,148 @@ +
+
+
+ Embeddings Configuration +
+

+ Configure your embeddings settings. These are used for semantic search, knowledge-base lookups, etc. +

+ + +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ +
+ Save pending changes to settings before clicking Fetch Embedding Models
+ + + + + + + + +
+ +
+
+
+ + +
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+
diff --git a/application/single_app/templates/admin/_panes/extraction.html b/application/single_app/templates/admin/_panes/extraction.html new file mode 100644 index 000000000..9a056e8db --- /dev/null +++ b/application/single_app/templates/admin/_panes/extraction.html @@ -0,0 +1,724 @@ +
+
+
Document Intelligence
+

+ Configure Azure Document Intelligence settings. Document Intelligence always powers + Standard extraction and is required for workspaces and chat file uploads. +

+
+ + + +
+ +
+ {% if content_understanding_supported %} +
+ Enhanced extraction uses Azure AI Content Understanding, which returns tables, + page structure, checkbox states, and AI-generated descriptions of figures, charts, and images. + Configure the connection below. +
+ {% else %} +
+ Azure AI Content Understanding is not available in the + {{ azure_environment }} cloud, so Enhanced extraction uses + Document Intelligence Layout here. There is nothing more to configure — + Enhanced still captures tables, page structure, forms, and checkbox states. +
+ {% endif %} + +
+ + +
+ Enhanced captures more document detail for PDFs and images, including tables, page structure, and checked or unchecked marks. It adds latency and has a 6X increase for every 1000 pages when selected. +
+
+ + +
+ Auto samples this many first PDF pages with Document Intelligence Layout. If it detects tables, selection marks, or figures, the full PDF uses Enhanced; otherwise it finishes with Standard. Images use Enhanced in Auto mode. +
+
+
+
Standard: Document Intelligence Read. Fastest and lowest-cost path for plain text PDFs and images.
+
Enhanced: best for tables, section structure, page layout, forms, checkbox states, and figure descriptions. Expect more latency and higher cost.
+
Auto: samples the first pages with Document Intelligence Layout, then uses Enhanced only when the sample shows tables, selection marks, or figures.
+
+
+ +
+ + + +
+
+ Captures equations in PDFs and images as LaTeX rather than approximate OCR text. This is a + billed Document Intelligence add-on that adds per-page cost to every Enhanced + extraction, so it is off by default. It applies to the Layout model only, so it has no effect + while extraction is set to Standard. +
+ + {% if content_understanding_supported %} +
+
+
Azure AI Content Understanding
+ +
+

+ Required for Enhanced extraction. Deployed from a Microsoft Foundry resource. + If this is left blank, Enhanced falls back to Document Intelligence Layout. +

+ +
+ + +
+ Your Microsoft Foundry resource endpoint, without a trailing path. +
+
+ +
+ + +
+ Managed identity requires the Cognitive Services User role on the Foundry resource. +
+
+ +
+ +
+ + +
+
+ +
+
+ + +
Default: {{ content_understanding_api_version_default }}
+
+
+ + +
Default: {{ content_understanding_document_analyzer_default }}
+
+
+ + +
Default: {{ content_understanding_image_analyzer_default }}
+
+
+ + +
+
+ {% endif %} +
+ +
+
Images Inside Office Files
+

+ Neither extraction engine describes figures inside Word and PowerPoint files. When this is on, + embedded images are pulled out of the file, analyzed with whichever engine backs the selected + extraction mode, and indexed as their own citable chunks. This works with Standard extraction + too, using Document Intelligence. +

+
+ + +
+
+
+ + +
Images narrower or shorter than this are skipped as icons or spacers.
+
+
+ + +
Caps per-document cost. Duplicate images are analyzed once.
+
+
+
+ + + + +
+ + + +
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + + +
+
+
+ +
+
+
Chunk Sizes
+ Cap: {{ chunk_size_cap }} +
+

Custom chunk sizes apply to new uploads only. Existing documents keep their current chunks.

+
+ Heads up: Overrides are capped at {{ chunk_size_cap }} (2x embedding context window, fallback 16,384). +
+
+ + +
+ +
+ + + {% set chunk_settings = chunk_size_settings or {} %} + {% set chunk_defaults = chunk_size_defaults or {} %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Minimum enforced at 50% of target on merge.
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
Applies to new audio transcripts.
+
+
+ + +
Pages per chunk after extraction.
+
+
+ + +
Slides per chunk after extraction.
+
+
+
+
+ + +
+
+ Metadata Extraction +
+

+ Enable this to automatically parse and store file metadata for advanced indexing and search. +

+
+ + + +
+ +
+ + + +
+ Uses Global Endpoints when multi-endpoint model management is enabled; otherwise uses the legacy GPT/APIM deployment settings. +
+
+
+ + +
+
+ Multi-Modal Vision Analysis +
+

+ Enable AI-powered vision analysis for images uploaded to chat or workspace. When enabled alongside Document Intelligence OCR, images will receive both text extraction (OCR) and semantic understanding (vision AI). +

+ +
+ How it works: +
    +
  • Document Intelligence: Extracts text from images (OCR)
  • +
  • Vision Model: Provides semantic analysis, object detection, and contextual understanding
  • +
  • Both analyses are combined and available in citations when Enhanced Citations is enabled
  • +
+
+ +
+ + + +
+ +
+ + +
Select a GPT model with vision capabilities (for example, gpt-4o or supported GPT 5 and later models). Only vision-capable models are shown.
+ + +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/feature-governance.html b/application/single_app/templates/admin/_panes/feature-governance.html new file mode 100644 index 000000000..0b7dcfed1 --- /dev/null +++ b/application/single_app/templates/admin/_panes/feature-governance.html @@ -0,0 +1,67 @@ +
+
+

+ Configure governance enforcement for endpoint, agent, and action creation or usage. Governance is disabled by default and only enforced when toggled on. +

+ +
+ +
+
+ Governance Feature Toggles +
+

Turn on governance checks for each feature area. Save with the main Settings button to persist these toggles.

+ +
+
+
Personal Scope
+
+ + +
+
+ + +
+
+ + +
+
+
+
Group Scope
+
+ + +
+
+ + +
+
+ + +
+
+
+
Global Scope
+
+ + + Always On +
+
+ + +
+
+ + +
+
+
+
+ +
diff --git a/application/single_app/templates/admin/_panes/feedback-alerts.html b/application/single_app/templates/admin/_panes/feedback-alerts.html new file mode 100644 index 000000000..5488e7242 --- /dev/null +++ b/application/single_app/templates/admin/_panes/feedback-alerts.html @@ -0,0 +1,44 @@ +
+
+
+ User Feedback +
+

Enable user feedback (thumbs up/down) for AI responses.

+
+ + + +
+
+
+
+ Desktop Conversation Notifications +
+

+ Allow users to receive an operating system notification when an AI response finishes while SimpleChat is open in a hidden or unfocused browser tab. +

+
+ + +
+

+ Users can turn notifications off from Profile. Browser permission is required, and notifications stop when the SimpleChat tab is closed. +

+
+
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/file-sync.html b/application/single_app/templates/admin/_panes/file-sync.html new file mode 100644 index 000000000..eb440758a --- /dev/null +++ b/application/single_app/templates/admin/_panes/file-sync.html @@ -0,0 +1,416 @@ +
+

+ Configure file synchronization for personal, group, and public workspaces. +

+ +
+
+
+ File Sync +
+ +
+

+ Enable File Sync globally, set conservative run limits, and choose whether workspace managers or SimpleChat admins manage sync sources. +

+ + + + {% if settings.enable_redis_cache and not settings.file_sync_redis_ready %} + + {% elif settings.requested_enable_file_sync and not settings.file_sync_effective_enabled %} + + {% endif %} + +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
+ + +
+
+
+ + {% set file_sync_visible_source_types = settings.file_sync_visible_source_types|default(['smb', 'azure_files']) %} +
+
Visible Source Types
+

+ Choose which source types appear in the Add Source workflow. OneDrive, SharePoint, and Google Workspace connectors are coming soon while validation continues. +

+
+
+
+ + +
Available now.
+
+
+
+
+ + +
Available now.
+
+
+
+
+ + +
Available now.
+
+
+
+
+ + +
Coming Soon.
+
+
+
+
+ + +
Coming Soon.
+
+
+
+
+ + +
Coming Soon.
+
+
+
+
+ + + +
+
+
+
Personal Workspace Sync
+
+ + +
+
+ + +
+
+ + +
+
Required app role value: PersonalFileSyncUser. Assign this role to users or groups in the Enterprise App before enabling the requirement for personal sync.
+
+
+ +
+ + +
+
+
+ + +
+
No user selected.
+
+
+
+ +
+
+
Group Workspace Sync
+
+ + +
+
+ + +
+
+ + +
+
+ + No groups assigned. +
+ +
+
+ +
+ + +
+
+
+ + +
+
No group selected.
+
+
+
+ +
+
+
Public Workspace Sync
+
+ + +
+
+ + +
+
+ + +
+
+ + No public workspaces assigned. +
+ +
+
+ +
+ + +
+
+
+ + +
+
No public workspace selected.
+
+
+
+
+ +
+ SMB source credentials are stored in Azure Key Vault when Key Vault secret storage is enabled; otherwise they use the app's existing encrypted settings path. +
+
+
+ + + + + + + + +
diff --git a/application/single_app/templates/admin/_panes/files-sharing.html b/application/single_app/templates/admin/_panes/files-sharing.html new file mode 100644 index 000000000..a856abdff --- /dev/null +++ b/application/single_app/templates/admin/_panes/files-sharing.html @@ -0,0 +1,237 @@ +
+
+
+ File Downloads +
+

+ Control when users can download original workspace document files from personal, group, and public workspaces. +

+
+ + + +
+
+
+ + + +
+
+ + + +
+
+ + No groups assigned. +
+ +
+
+ + + +
+
+ + + +
+
+ + No public workspaces assigned. +
+ +
+ + + + + + +
+
+ File Sharing +
+

+ Turn this on to enable file sharing capabilities between users and workspaces. +

+
+ + + +
+
+ + + + + +
+
+ Shared Conversation File Approvals +
+

Files generated by participants in a shared conversation are saved into the conversation owner's storage. When enabled, those files are held until an approver releases them.

+
+ + + +
+
+ +
+
+ Maximum File Size +
+

+ The largest file a user may upload into a workspace. +

+
+ + +
+
+ +
diff --git a/application/single_app/templates/admin/_panes/governance-policies.html b/application/single_app/templates/admin/_panes/governance-policies.html new file mode 100644 index 000000000..da2b94fd1 --- /dev/null +++ b/application/single_app/templates/admin/_panes/governance-policies.html @@ -0,0 +1,94 @@ +
+
+
+
Feature Policies
+ +
+

Set allow-all, explicit allow lists, and block lists for each governed feature. Block lists override allow settings.

+
+ + + + + + + + + + +
FeatureAllow AllAllow ListBlock List
+
+
+ +
+
+
Delegated Item Policies
+
+ + +
+
+

Manage delegated governance for configured global resources and action type entitlements that admins assign to specific users or groups.

+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + + + + + + + + + +
PolicyEntity TypeItemAllow AllActions
+
+ +
+
+
+ + +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/image-generation.html b/application/single_app/templates/admin/_panes/image-generation.html new file mode 100644 index 000000000..ee9293278 --- /dev/null +++ b/application/single_app/templates/admin/_panes/image-generation.html @@ -0,0 +1,165 @@ +
+
+
+ Image Generation Configuration +
+

+ Configure image generation settings. Enable/disable, set endpoints, and choose a model. +

+ + +
+ + + +
+
+ +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ +
+ + Save pending changes to settings before clicking Fetch Image Generation Models
+ + + + + + + + +
+ +
+
+
+ + +
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ + +
+
+
diff --git a/application/single_app/templates/admin/_panes/inbound-mcp.html b/application/single_app/templates/admin/_panes/inbound-mcp.html new file mode 100644 index 000000000..3676ea641 --- /dev/null +++ b/application/single_app/templates/admin/_panes/inbound-mcp.html @@ -0,0 +1,535 @@ +
+ {% if mcp_ui_enabled %} +
+
+
+
+ Inbound MCP Server +
+

+ Configure the governed SimpleChat MCP endpoint used by external MCP clients. Personal tools use delegated user tokens by default; tool access remains deny-by-default until authentication, source, client, and governance policy all allow the request. +

+
+
+ Preview + +
+
+ + + +
+
+
+
Runtime Gate
+
+ + + +
+
+ + +
Default: DelegatedMcpServerAccess. VS Code and other user clients must present this delegated scope.
+
+
+ + +
Default: InboundMCPUserAccess. Governance determines which users/groups can use tools after this Entra role and delegated scope pass.
+
+
+ + +
Default: InboundMCPAppAccess. Reserved for future app-only MCP tools and still governed separately.
+
+
+
+ +
+
+
Endpoint Contract
+
+
MCP endpoint
+
{{ inbound_mcp_resource_path }}
+
Protected resource metadata
+
{{ inbound_mcp_prm_path }}
+
Transport
+
Streamable HTTP JSON-RPC
+
Current tool surface
+
Personal delegated tools only.
+
Tool identity
+
Delegated user token required for personal data.
+
+ +
+
+ +
+
+
+
+
Request Size & Throttling
+
Protect the inbound MCP endpoint from oversized payloads and noisy clients. Rate limits are enforced per caller and tool category across app instances.
+
+
+ + +
+
+
+
+ + +
Default: 65536. Range: 1 KB to 1 MB.
+
+
+ + +
Default: 60. Applies to each throttle category.
+
+
+ + +
Default: 120.
+
+
+ + +
Default: 30.
+
+
+ + +
Default: 10.
+
+
+ +
+
+ +
+ + + +
+ +
+
+
+
+
Allowed client app IDs
+
Required allowlist. Empty means no MCP clients can connect.
+
+ +
+
+ + + + + + + + + +
Client app IDDescriptionActions
+
+
+
+
+ +
+
+
+ + +
+
+ Off allows only the configured SimpleChat tenant. Turning this on auto-includes the SimpleChat tenant and lets admins add more tenants. +
+
+
+
Allowed tenant IDs
+ +
+
+ + + + + + + + + +
Tenant IDDescriptionActions
+
+
+
+
+
+ +
+
+
+ + +
+
+ Default on accepts any source signal at the runtime allowlist layer. Turn off to require explicit source values here. In both modes, admins must still create an inbound MCP source governance policy before tools are returned. +
+
+ + +
Default: X-SimpleChat-MCP-Source.
+
+ + + +
+
+
Allowed source IDs
+ +
+
+ + + + + + + + + +
Source valueDescriptionActions
+
+
+
+
+
+
+
+ + + + {% endif %} +
diff --git a/application/single_app/templates/admin/_panes/jobs.html b/application/single_app/templates/admin/_panes/jobs.html new file mode 100644 index 000000000..98c939eed --- /dev/null +++ b/application/single_app/templates/admin/_panes/jobs.html @@ -0,0 +1,89 @@ +
+
+
+

Job History

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + +
CreatedOperationStatusProgressMessageActions
Job history has not loaded yet.
+
+ +
+
diff --git a/application/single_app/templates/admin/_panes/latest-features.html b/application/single_app/templates/admin/_panes/latest-features.html new file mode 100644 index 000000000..a5d8c5f5d --- /dev/null +++ b/application/single_app/templates/admin/_panes/latest-features.html @@ -0,0 +1,1253 @@ +
+

+ Review the main capabilities introduced in v{{ config['VERSION'] }} and use this page as an admin-facing overview for what to highlight with your teams. +

+ + + {% for release_group in admin_latest_feature_release_groups %} + {% if release_group.id == 'current_release' %} +
+
+
{{ release_group.label }}
+

{{ release_group.description }}

+
+ {% if release_group.release_version %} + v{{ release_group.release_version }} + {% endif %} +
+ + {% for feature in release_group.features %} + {% set feature_card_id = 'latest-features-' ~ feature.id|replace('_', '-') ~ '-card' %} + {% set feature_collapse_id = feature_card_id ~ '-details' %} +
+
+
+
{{ feature.title }}
+

{{ feature.summary }}

+
+ +
+
+
+ {% if feature.details %} +

{{ feature.details }}

+ {% endif %} + {% if feature.why %} + + {% endif %} + {% if feature.guidance %} +
{% if feature.images %}Screenshot and rollout notes{% else %}Rollout notes{% endif %}
+
    + {% for tip in feature.guidance %} +
  • {{ tip }}
  • + {% endfor %} +
+ {% endif %} + {% if feature.images %} + + {% endif %} + {% if feature.actions %} +
+ {% for action in feature.actions %} + + {{ action.label }} + + {% endfor %} +
+ {% endif %} +
+
+
+ {% endfor %} + {% else %} + {% set release_card_id = 'latest-features-' ~ release_group.id|replace('_', '-') ~ '-card' %} + {% set release_collapse_id = release_group.collapse_id %} +
+
+
+
{{ release_group.label }}
+

{{ release_group.description }}

+
+ +
+
+
+ {% if release_group.release_version %} + + {% endif %} +
+ {% for feature in release_group.features %} +
+
+
+
+
{{ feature.title }}
+

{{ feature.summary }}

+
+

{{ feature.details }}

+ {% if feature.guidance %} +
    + {% for tip in feature.guidance %} +
  • {{ tip }}
  • + {% endfor %} +
+ {% endif %} + {% if feature.images %} + + {% endif %} + {% if feature.actions %} +
+ {% for action in feature.actions %} + + {{ action.label }} + + {% endfor %} +
+ {% endif %} +
+
+
+ {% endfor %} +
+
+
+
+ {% endif %} + {% endfor %} + + {% if false %} +
+
+
+
+ Registered / Unregistered Badge +
+

The badge next to the Admin Settings version number shows whether this admin instance is registered for latest release and community call notifications.

+
+ +
+
+
+ +

Use this badge as the quick status check for whether your environment is already registered to receive SimpleChat release updates.

+
    +
  • Unregistered means this environment has not saved release notification registration details yet.
  • +
  • Registered means the environment already has saved contact details for release and community call notifications.
  • +
  • Clicking the badge opens the registration modal where admins can add or edit the name, email, and organization for this instance.
  • +
  • Submitting the form opens a prefilled email draft to simplechat@microsoft.com and stores the registration details back in Admin Settings.
  • +
  • The badge lives beside the version number at the top of Admin Settings so admins can confirm status without hunting through tabs.
  • +
+
+
+
+ +
+
+
+
+ Performance Improvements for Apps running Native Python (not containers) +
+

Native Python App Service deployments should use Gunicorn as the startup command. Container deployments already do this for you.

+
+ +
+
+
+

If you run the native Python App Service deployment instead of the container deployment, set the App Service Startup command to the following:

+
python -m gunicorn -c gunicorn.conf.py app:app
+

Use container deployments when possible if you want the runtime defaults and startup behavior handled by the shipped image.

+ + +
+
+
+ +
+
+
+
+ Guided Tutorials +
+

New guided walkthroughs help users learn the live chat and personal workspace interfaces without leaving the app.

+
+ +
+
+
+

Admins can use these walkthroughs to accelerate onboarding for new users and reduce orientation time across the primary product surfaces.

+
    +
  • Guided Chat Tutorial covers the active chat experience, message actions, search flows, citations, thoughts, and export-related actions.
  • +
  • Personal Workspace Guided Tutorial walks through uploads, search, filters, list and grid views, prompts, agents, actions, and tags.
  • +
+ +
+
+
+ +
+
+
+
+ Background Chat Completion +
+

Personal chats can continue finishing after a user leaves the chat page, and completed responses surface as notifications with unread indicators.

+
+ +
+
+
+

This makes long-running responses more reliable for users who navigate into other areas of the app before generation finishes.

+
    +
  • Completion notifications deep-link back into the finished conversation.
  • +
  • Unread markers remain visible until the response is actually opened.
  • +
  • Personal, workspace, and navigation flows now behave more consistently while chat completion continues in the background.
  • +
+ +
+
+
+ +
+
+
+
+ Multi-Endpoint GPT Selection +
+

Admins can expose multiple GPT choices across Azure OpenAI and Azure AI Foundry, set a saved default fallback model, and now review agent rebinding in a modal before applying changes.

+
+ +
+
+
+

This is the high-value admin control for teams that want broader GPT choice without locking every workflow to a single deployment.

+
    +
  • Configure multiple model endpoints and choose which GPT deployments are available to users.
  • +
  • Set a default fallback model so summarization, agent requests, and other background flows still resolve a usable GPT when an explicit selection is missing.
  • +
  • Use the admin-only Agent Default Model Review modal to inspect inherited agents, search and filter large agent sets, and explicitly choose which review rows should be overridden to the saved default.
  • +
+ + + +
+
+
+ +
+
+
+
+ Tabular Data Analysis +
+

Enhanced citations now unlock a more consistent tabular analysis experience for normal chat and workspace-backed files, not just dedicated agent scenarios.

+
+ +
+
+
+

This is the highest-impact settings area to mirror here because it combines storage-backed citations, tabular previews, and consistent behavior across workspace and chat uploads.

+
    +
  • Tabular files can be analyzed with the same core approach whether they come from chat uploads or any workspace container.
  • +
  • Enhanced citations provide the blob-backed dependency for richer tabular processing and citation transparency.
  • +
  • Admins can right-size tabular preview limits based on the memory profile of their runtime.
  • +
+ +
+ + +
+ +
+
+
All filetypes
+
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +
+
Tabular Preview Limits
+
+ + + + Mirror of the Citations setting. Larger values support bigger previews but increase runtime memory pressure. + +
+
+
+ + +
+
+
+ +
+
+
+
+ Citation Improvements +
+

Conversation history citation replay and citation amplification keep more grounded evidence available across follow-up turns without making the chat history unwieldy.

+
+ +
+
+
+

These improvements make follow-up questions more reliable because the system can reuse prior grounded evidence instead of relying only on the assistant's last prose response.

+
    +
  • Conversation history citations carry forward prior citation summaries so later prompts can reuse earlier grounded results.
  • +
  • Citation amplification keeps richer supporting payloads available for inspection and export while keeping prompt history compact.
  • +
  • This is especially useful for larger or tabular outputs where users often ask multiple follow-up questions against the same evidence chain.
  • +
+ + +
+
+
+ +
+
+
+
+ Document Revisioning and Management +
+

Uploading a document with the same name now creates a new revision instead of overwriting the previous file, while older versions remain available for traceability and later analysis.

+
+ +
+
+
+

This improves both document management and citation integrity for long-lived conversations that span multiple document updates.

+
    +
  • Same-name uploads create a revision family with an incremented current version instead of destroying prior history.
  • +
  • Previous versions are retained for analysis or comparison, while workspace and search views focus on the current revision by default.
  • +
  • Older conversations can still resolve citations against the correct historical document version.
  • +
  • Updated revisions inherit document classifications and tags so admins do not have to reapply metadata after every refresh.
  • +
+ +
+
+
+ +
+
+
+
+ Conversation Summaries and Export +
+

Conversation export now includes persistent summaries, PDF output, and easier per-message reuse and sharing actions.

+
+ +
+
+
+
    +
  • Conversation details now surface a summary card with generate and regenerate actions.
  • +
  • Export supports PDF alongside existing export options.
  • +
  • Per-message actions support Markdown export, Word export, open in email, and use as prompt.
  • +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+ Agent and Action Operations +
+

Recent updates improve manageability for agents and actions with richer views, easier SQL connectivity checks, and better operational tracking.

+
+ +
+
+
+
    +
  • List and grid view toggles give admins and users a better way to browse agents and actions.
  • +
  • SQL actions now include an inline Test Connection button before save.
  • +
  • User tracking and activity logging improve auditability for agent and action changes.
  • +
+ +
+
+
+ +
+
+
+
+ AI Transparency +
+

Processing Thoughts provide a clearer view into model activity, duration, and execution flow while responses are being generated.

+
+ +
+
+
+

Admins can enable this once and let users see more of the model pipeline, including sending, generating, and responded states with clearer timing information.

+ +
+ + +
+ +
+
+
+ +
+
+
+
+ Fact Memory +
+

Users can now save Instructions and Facts on their profile so the assistant can apply durable preferences every time and recall relevant personal context only when needed.

+
+ +
+
+
+

Fact Memory gives users a compact profile experience for managing two distinct memory types: always-on Instructions and relevance-based Facts.

+
    +
  • Instructions act like stable user preferences and are applied on every prompt unless the current message overrides them.
  • +
  • Facts are recalled only when relevant and now appear as dedicated thought and citation entries in chat.
  • +
  • The profile page includes a compact add flow plus a popup manager for search, paging, edit, retag, and delete actions.
  • +
  • Admins can choose whether this announcement is visible on the user-facing Latest Features page from General > User-Facing Latest Features.
  • +
+ + +
+
+
+ +
+
+
+
+ Redis and Key Vault +
+

Redis configuration now supports using a Key Vault secret name instead of storing the access key directly in settings.

+
+ +
+
+
+

This is useful for teams standardizing on Key Vault-backed secret storage while still using Redis for cache and session scenarios.

+
    +
  • Use Redis authentication type Key Vault when you want the stored value to represent a secret name instead of a raw Redis key.
  • +
  • This works best when Key Vault is already configured for the app.
  • +
+ +
+ + +
+ +
+
+
Redis Cache Settings
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ When using Key Vault, store the secret name here instead of the raw Redis key. +
+
+
+
+ +
+
+
+ +
+
+
+
+ Send Feedback to the SimpleChat Team +
+

Admins now have a dedicated Send Feedback tab for bug reports and feature requests that opens a prefilled email draft and writes an audit record to the activity log.

+
+ +
+
+
+

Use the Send Feedback tab when you want to report a bug or suggest a feature directly from Admin Settings without leaving the page.

+
    +
  • Opens a prefilled email draft addressed to simplechat@microsoft.com.
  • +
  • Logs the action to the activity log so admins can audit when feedback emails were prepared.
  • +
  • Keeps the workflow simple by using text-only email content with no confusing pseudo-attachment step.
  • +
+ + Open Send Feedback + +
+
+
+ +
+
+
+
+ Support Menu +
+

Support can now expose user-facing Latest Features and Send Feedback workflows directly in everyday navigation.

+
+ +
+
+
+ +
    +
  • Admins can enable or disable the Support menu and rename it for user navigation.
  • +
  • Send Feedback now routes users to an internal recipient email address defined by admins.
  • +
  • The user-facing Latest Features page can be curated feature-by-feature so teams only share the items they want visible.
  • +
  • The General tab now includes a dedicated User-Facing Latest Features checklist so admins can quickly confirm which announcements will be visible to end users.
  • +
+ + Open General Settings + + +
+
+
+ {% endif %} + + {% for release_group in support_latest_feature_release_groups_preview %} + {% if release_group.id != 'current_release' %} + {% set preview_card_id = 'latest-features-user-preview-' ~ release_group.id|replace('_', '-') ~ '-card' %} + {% set preview_collapse_id = 'latestFeaturesUserPreview' ~ release_group.id|replace('_', ' ')|title|replace(' ', '') %} +
+
+
+
+ {{ release_group.label }} +
+

{{ release_group.description }}

+
+ +
+
+
+ +
+ {% for feature in release_group.features %} +
+
+
+
+
+
{{ feature.title }}
+

{{ feature.summary }}

+
+ + {% if settings.support_latest_features_visibility.get(feature.id, true) %}Shared with Users{% else %}Hidden from Users{% endif %} + +
+

{{ feature.details }}

+ {% if feature.guidance %} +
    + {% for tip in feature.guidance %} +
  • {{ tip }}
  • + {% endfor %} +
+ {% endif %} + {% if feature.images %} + + {% endif %} + {% if feature.actions %} +
+ {% for action in feature.actions %} + + {{ action.label }} + + {% endfor %} +
+ {% endif %} +
+
+
+ {% endfor %} +
+

Visibility still comes from General > User-Facing Latest Features, so admins can keep previous and archived release content available without promoting every item forever.

+
+
+
+ {% endif %} + {% endfor %} +
diff --git a/application/single_app/templates/admin/_panes/logging.html b/application/single_app/templates/admin/_panes/logging.html new file mode 100644 index 000000000..a32454471 --- /dev/null +++ b/application/single_app/templates/admin/_panes/logging.html @@ -0,0 +1,274 @@ +
+

+ Configure logging settings for monitoring, debugging, and auditing purposes. These settings control various types of logging throughout the application including application insights, debug messages, and file processing events. +

+ +
+
+ Application Insights Logging +
+

Enable global logging to Application Insights for all agents and orchestration events.

+
+ + + +
+ +
+ +
+
+ Debug Logging +
+

Control debug print statements across the application for development and troubleshooting.

+
+ + + +
+ + +
+
+ + + +
+ +
+
+ + +
+
+ + +
+
+ +
+ {% if settings.debug_logging_turnoff_time %} + {% if settings.debug_logging_turnoff_time is string %} + {{ settings.debug_logging_turnoff_time }} + {% else %} + {{ settings.debug_logging_turnoff_time.strftime('%Y-%m-%d %H:%M:%S') }} + {% endif %} + {% else %} + Will be calculated when saved + {% endif %} +
+
+
+
+ + +
+ +
+
+ File Process Logging +
+

Enable logging of file processing events for debugging and auditing purposes. Logs are stored in the file_processing container in Cosmos DB.

+
+ + + +
+ + +
+
+ + + +
+ +
+
+ + +
+
+ + +
+
+ +
+ {% if settings.file_processing_logs_turnoff_time %} + {% if settings.file_processing_logs_turnoff_time is string %} + {{ settings.file_processing_logs_turnoff_time }} + {% else %} + {{ settings.file_processing_logs_turnoff_time.strftime('%Y-%m-%d %H:%M:%S') }} + {% endif %} + {% else %} + Will be calculated when saved + {% endif %} +
+
+
+
+ +
+
+
Delete stored logs
+

+ Permanently remove file processing logs from Cosmos DB. One month is treated as 30 days. +

+
+
+ + +
+ Enter a whole number greater than zero. +
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+ Health Check +
+ +
+

Configure health check endpoint for external monitoring systems.

+ +
+
+
+
+
+
Authenticated Endpoint
+

Use when your monitoring system can reach a protected route and you want the endpoint governed by the normal app access boundary.

+
+ Protected +
+
+ + + +
+

Best for internal monitors or diagnostics tooling that already authenticates to the application.

+
+
+
+
+
+
+
Unauthenticated Endpoint
+

Use when a platform health probe cannot sign in and only needs a lightweight availability response.

+
+ No Auth +
+
+ + + +
+ +
+
+
+
+
+
+
+ API Documentation +
+
+ + + Open Swagger UI + +
+
+

Configure automatic OpenAPI/Swagger documentation for API endpoints.

+ +
+ + + +
+

+ Provides interactive API documentation, endpoint testing, and schema validation. + Useful for developers, API integration, and system troubleshooting. +

+
+
diff --git a/application/single_app/templates/admin/_panes/mcp-governance.html b/application/single_app/templates/admin/_panes/mcp-governance.html new file mode 100644 index 000000000..8a57389de --- /dev/null +++ b/application/single_app/templates/admin/_panes/mcp-governance.html @@ -0,0 +1,91 @@ +
+
+
+ MCP Action Destination Governance +
+

+ Restrict outbound remote MCP servers by destination pattern and scope. Destination policies reuse delegated item policies, so admins can allow all users, specific users, or workspace groups for each approved destination. +

+
+
+
+ + +
+
+ + +
+
+ Save with the main Settings button. Unsafe literal-IP blocking can be enabled even before the allowlist is enforced. +
+
+
+
Supported destination patterns
+
    +
  • * for any remote MCP destination after identity/auth checks.
  • +
  • preconfiguration:microsoft_learn or preset:generic.
  • +
  • *.contoso.com, https://example.com/mcp*, or transport:streamable-http.
  • +
  • group:<group-id>::preconfiguration:github for one group-specific override.
  • +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ Inbound MCP Source Governance +
+

+ Control which delegated users and groups can use SimpleChat as an inbound MCP server through source-scoped policies. +

+ +
+
+
Policy required for inbound MCP
+
    +
  • inbound_mcp_source with delegated item *: allows selected users or groups to use any accepted source ID.
  • +
  • inbound_mcp_source with a configured source ID: allows selected users or groups to use only requests with that source value.
  • +
+
+ Client, tenant, delegated scope, and Entra role checks remain in Inbound MCP configuration. The source ID comes from the configured header when present and is advisory unless a trusted gateway sets or validates it. +
+
+
+
+
+ + +
+
+
+
+
+ +
diff --git a/application/single_app/templates/admin/_panes/migrate.html b/application/single_app/templates/admin/_panes/migrate.html new file mode 100644 index 000000000..ad6290a15 --- /dev/null +++ b/application/single_app/templates/admin/_panes/migrate.html @@ -0,0 +1,578 @@ +
+
+
+
+

+ Migration +

+

Move SimpleChat data through a reviewed, recoverable environment transfer.

+
+
+ + Not reviewed +
+
+ +
+ + + + + +
+
+
+
Connect the destination
+

Configure the services this migration will write to. Stored credentials remain redacted.

+
+ Destination database: SimpleChat +
+ +
+
+
+
+
Target Cosmos Database
+

Required for every migration.

+
+ +
+
+ Managed identity requires Cosmos DB Data Contributor and target network access. +
+
+
+ + +
+
+ + +
+
+ + +
Fixed app contract.
+
+
+
+ + +
+
+ +
+
+
+
Target Search
+

Required when AI Search documents are included.

+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
Target Enhanced Citation Storage
+

Required only when source document blobs are included.

+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
Choose who and what moves
+

Selections persist while you search and page. “All” always uses the exhaustive server count.

+
+
0 principal scopes selected
+
+ +
+ + + +
+ +
+ +
+ Migration mode +
+ + + +
+
+ +
+
+
+
+
Available users
+

Search the server catalog.

+
+ +
+
+
+ + Page 1 + +
+
+ +
+ +
+ +
+ Loading exhaustive count… + Every current user record will be resolved by the server when the job starts. +
+
+ +
+ + +
+
+ +
+ +
+ Migration mode +
+ + + +
+
+
+
+
+
+
Available groups
+

Search the server catalog.

+
+ +
+
+
+ + Page 1 + +
+
+ +
+
+ +
+ Loading exhaustive count… + Every current group record will be resolved by the server when the job starts. +
+
+
+ + +
+
+ +
+ +
+ Migration mode +
+ + + +
+
+
+
+
+
+
Available public workspaces
+

Search the server catalog.

+
+ +
+
+
+ + Page 1 + +
+
+ +
+
+ +
+ Loading exhaustive count… + Every current public workspace record will be resolved by the server when the job starts. +
+
+
+ + +
+
+
+ + +
+
+
+
Choose what happens at the destination
+

Choose whether to copy only missing items, catch up changes, or make migrated destination data match the source.

+
+
+ +
+ Destination behavior +
+ + + + + + +
+

+ Copies source items that are absent from the destination. Existing destination data is never updated or deleted. +

+
+
+ + +
Leave blank to let SimpleChat choose the latest compatible completed migration as the starting point for this catch-up run.
+
+
+
+ +
+ Data surfaces +
+
+
+ + +
+
+ + +
SimpleChat pauses its own target indexing. Freeze other writers before review.
+
+
+
+
+ + +
Requires Enhanced Citation storage at both source and destination.
+
+
+
+
+ +
+ Performance and resume +

Migration uses durable resource checkpoints and retains the same migration ID after Retry or Resume.

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
RU Boost validates Azure management-plane throughput permissions separately from Cosmos data-copy access. Eligible capacity is raised only up to 10,000 RU/s during execution and restored after completion or failure.
+
+
+
+
+ + +
+
+
+
Prove the plan is ready
+

Preflight runs server-owned access probes and inventory. Any earlier change makes this review stale.

+
+
+ + +
+
+
+ +
+ Review has not run. + Run preflight to verify target access, counts, collisions, locks, and capacity policy. +
+
+
+
+ +
+ + +
+
+
+
Confirm execution
+

Review the final server-normalized plan. Submission is guarded against duplicate requests.

+
+
+
+

Complete preflight review before confirmation.

+
+ +
+ + +
+ +
+ + +
+
+
+
Operate the migration
+

Progress comes from the durable job record. Cancel, Retry, and Resume retain verified checkpoints.

+
+
+ + +
+
+
+ +
+ No migration is attached to this workflow yet. + After execution, this stage follows the queued job and exposes its recovery actions. +
+
+
+
+
+
+
+
+ +
+
+ + +
+ Step 1 of 6 +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/model-endpoints.html b/application/single_app/templates/admin/_panes/model-endpoints.html new file mode 100644 index 000000000..5c9683ad4 --- /dev/null +++ b/application/single_app/templates/admin/_panes/model-endpoints.html @@ -0,0 +1,401 @@ +
+ +

+ Configure all AI model settings including GPT for text generation, embeddings for semantic search, and image generation capabilities. +

+ +
+
+ Model Endpoints +
+

+ Manage multiple AI model endpoints (Azure OpenAI and Azure AI Foundry). When enabled, model selection in chat is driven by these endpoints. +

+ + {% if not settings.enable_multi_model_endpoints %} +
+ + +
+ {% endif %} + + {% if settings.enable_semantic_kernel %} +
+
+
+
Agent Default Model Review
+

+ Review local agents in a modal, bulk-bind inherited agents to the saved default model endpoint, and explicitly override selected agent model choices when you need to manage cost or move to newer models. +

+
+
+ +
+
+
+ {% if settings.enable_multi_model_endpoints %} + Save your AI model settings before reviewing or migrating agents. + {% else %} + Enable multi-endpoint model management to review and rebind agents to a saved default model. + {% endif %} +
+ +
+ +
+
+ Ready: 0 + Needs Default: 0 + Manual Review: 0 + Already Migrated: 0 +
+
+
+ Open the review modal to search, filter, and selectively rebind agents to the saved default model. +
+
+ + + {% else %} + + {% endif %} + +
+
+
+
Model Endpoint Identity Header
+

+ Send a stable HMAC-hashed user key to model endpoint backends for APIM counters, quota policies, or routing policies. +

+
+
+ + +
+
+
+
+ + +
Reserved auth/protocol headers cannot be used.
+
+
+ + +
The selected identity is HMAC-hashed before leaving SimpleChat. Missing identity values omit the header.
+
+
+
+ +
+ + +
+ Used for tasks such as conversation summarization, fallback, and other operations when an agent is selected. +
+
+ + + + +
+
+
Global Endpoints
+
+ + +
+
+
+ + + + + + + + + + + + + +
NameProviderSelected ModelsStatusActions
+
+
+ {% if not settings.enable_multi_model_endpoints %} + + {% endif %} +
+ + + + + {% include '_multiendpoint_modal.html' %} +
diff --git a/application/single_app/templates/admin/_panes/network.html b/application/single_app/templates/admin/_panes/network.html new file mode 100644 index 000000000..0f9216bad --- /dev/null +++ b/application/single_app/templates/admin/_panes/network.html @@ -0,0 +1,41 @@ +
+
+
+
+ Front Door +
+ +
+

Configure Front Door URL for authentication and redirect flows.

+ +
+ + + +
+ +
+ + + + The base URL of your Front Door or load balancer. The system will automatically generate: +
    +
  • Home redirect: https://your-frontdoor.azurefd.net
  • +
  • OAuth2 redirect: https://your-frontdoor.azurefd.net/getAToken
  • +
+
+
+
+
diff --git a/application/single_app/templates/admin/_panes/notices.html b/application/single_app/templates/admin/_panes/notices.html new file mode 100644 index 000000000..c05ac2b68 --- /dev/null +++ b/application/single_app/templates/admin/_panes/notices.html @@ -0,0 +1,288 @@ +
+
+
+ Classification Banner +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + {{ settings.classification_banner_text or 'Banner Preview' }} + +
+
+ +
+
+ Chat AI Notice +
+

+ Display an administrator-provided reminder directly below the chat input, such as guidance that responses are AI-generated and should be reviewed. +

+
+ + +
+
+
+ + +
Plain text only. Line breaks are preserved.
+
+
+ + +
Changing the notice text or display behavior creates a new message version and shows it again.
+
+
+
+ +
+
+ Terms of Use +
+

+ Require users to accept the Terms of Use before using SimpleChat. Standard Microsoft sign-in users see it before authentication; Teams SSO and other passive sign-in flows are gated immediately after the app session is created. +

+ +
+ + + +
+ +
+
+ + +
+
+ + + Changing the title, message, or frequency creates a new terms version that users must accept again. +
+
+ +
+ + + Plain text is shown to users with line breaks preserved. +
+ +
+ + + Use a local path such as / or an admin-approved HTTP(S) URL. Signed-in users are locally logged out before this redirect. +
+ +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+ User Agreement +
+

+ Configure a user agreement that users must accept before uploading files. + Supports Markdown formatting. +

+ + +
+ + + +
+ + +
+
+ + +
+ +

Select where the user agreement should be shown (at least one required):

+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ + +
+ 0 / 200 words + +
+
+ + +
+ + + +
+ + +
+ +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/redis-caching.html b/application/single_app/templates/admin/_panes/redis-caching.html new file mode 100644 index 000000000..5bbaa8c4d --- /dev/null +++ b/application/single_app/templates/admin/_panes/redis-caching.html @@ -0,0 +1,448 @@ +
+ {% set enable_dai_debug = settings.enable_dai_debug | default(false) %} +

+ Configure Redis cache to improve enterprise scale and performance by caching session data. Enabling Redis allows you to horizontally scale your application across multiple instances without losing session data. +

+
+
+ Redis Cache +
+

+ Enable Redis Cache to store session data in a distributed cache. This is recommended for production and multi-instance deployments. +

+
+ + + +
+
+
+ +

(example: simple-chat.redis.cache.windows.net)

+ +
+
+ + +
+
+ +
+ + +
+
+ Enter the full Key Vault secret name. + Enable Key Vault for Agent and Action Secrets + must be enabled and configured. +
+
+ +
+
+
+
+
+
+ Redis Monitoring +
+

+ Monitor Redis availability, memory pressure, hit rate, evictions, and runtime cache usage before Redis-backed document list caching is enabled. +

+
+
+ + +
+
+
+
+
+
+
Configuration
+ Not loaded +
+
+
+
+
Health
+ Not loaded +
+
+
+
+
App Cache Runtime
+ Not loaded +
+
+
+
+
Session Runtime
+ Not loaded +
+
+
+
+
+
+
Ping Latency
+
Not loaded
+
+
+
+
+
Memory Usage
+
Not loaded
+
Not loaded
+
+
+
+
+
Connected Clients
+
Not loaded
+
+
+
+
+
Ops/sec
+
Not loaded
+
+
+
+
+
Keyspace Hit Rate
+
Not loaded
+
+
+
+
+
Tracked Keys
+
Not loaded
+
+
+
+
+
DAI Version Markers
+
Not loaded
+
Not loaded
+
+
+
+
+
DAI Cache Payloads
+
Not loaded
+
Not loaded
+
+
+
+
+
Expired / Evicted Keys
+
+ Not loaded + / + Not loaded +
+
+
+
+
+
Fragmentation Ratio
+
Not loaded
+
+
+
+
+
Error Replies
+
Not loaded
+
+
+
+
+
Rejected Connections
+
Not loaded
+
+
+
+
+
Redis Version
+
Not loaded
+
+
+
+
+
Monitoring Source
+
Not loaded
+
+
+
+
+
Last Checked
+
Not loaded
+
+
+
+
+
Last Error
+
Not loaded
+
+
+
+
+
+ + + +
+
+
+
+ Conversation Cache +
+

+ Cache conversation list, feed, and advanced-search results per user. Redis is optional; cache misses and disabled cache paths continue using source Cosmos queries. +

+
+ +
+
+
+
+ + +
+
When enabled, list/feed/search payloads are cached by user and version. Disabling this bypasses cache reads and writes without requiring Redis.
+
+
+ + +
Default 120 seconds. User-scoped version invalidation refreshes changed conversations; set to 0 to skip writing new entries.
+
+
+
+
+
+
Runtime Status
+ Not loaded +
+
+
+
+
15m Cache Hit Rate
+
Not loaded
+
+
+
+
+
15m Cache Hits / Misses
+
Not loaded
+
+
+
+
+
15m Cache Bypasses / Errors
+
Not loaded
+
+
+
+
+
15m Writes / Invalidations
+
Not loaded
+
+
+
+
+
15m Operation Mix
+
Not loaded
+
+
+
+
+
Last Cache Event
+
Not loaded
+
+
+
+
+
Last Invalidation
+
Not loaded
+
+
+
+
+ + Conversation cache metrics are lightweight in-process counters for the current app worker. Application Insights logs remain the durable fleet-wide source for cache warnings and fallback behavior. +
+
+
+
+ +
diff --git a/application/single_app/templates/admin/_panes/restore.html b/application/single_app/templates/admin/_panes/restore.html new file mode 100644 index 000000000..6b8e1ff5a --- /dev/null +++ b/application/single_app/templates/admin/_panes/restore.html @@ -0,0 +1,147 @@ +
+
+
+
+

Backup Inventory

+

Track completed full and partial backups created by Data Management jobs.

+
+
+ + + +
+
+
+
+
What does Run Retention Cleanup do?
+

+ It permanently deletes backups whose age exceeds the retention period configured in Data Management settings, + and removes their stored artifacts from the backup container. Backups newer than the retention cutoff are left alone. +

+
    +
  • Only backups in a finished state are eligible; running or queued jobs are skipped.
  • +
  • When Keep latest full backup is enabled, the most recent successful full backup is protected even if it is past the cutoff.
  • +
  • Each run deletes at most 25 backups, so very large cleanups may need several runs.
  • +
  • Cleanup also runs automatically on the configured schedule; this button just runs it now.
  • +
+

+ Seeing “found no expired backups to delete” means every backup is still inside the retention window. That is expected, not an error. +

+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + +
BackupCompletedContentsStorageProtectionWarningsActions
Backup inventory has not loaded yet.
+
+ +
+
diff --git a/application/single_app/templates/admin/_panes/retention.html b/application/single_app/templates/admin/_panes/retention.html new file mode 100644 index 000000000..1e90230a4 --- /dev/null +++ b/application/single_app/templates/admin/_panes/retention.html @@ -0,0 +1,268 @@ +
+
+
+ Retention Policy +
+

Automatically delete aged conversations and documents based on configurable retention periods. Users, group owners, and public workspace admins can set their own retention policies.

+ + + + +
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+ + +
+
Default Retention Policies
+

Set organization-wide default retention periods for each workspace type. Users can override these defaults with their own preferences. Setting a default here means new users/workspaces will start with this retention period.

+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + Force Push: Override all user/group/workspace custom retention policies with the organization defaults above. Users will then use the organization default until they set their own preference. +
+ +
+
+
+ + +
+ + + Retention policy will run once daily at this hour (UTC timezone). +
+ + +
+
+ +
+ {% if settings.retention_policy_last_run %} + {{ settings.retention_policy_last_run }} + {% else %} + Never run + {% endif %} +
+
+
+ +
+ {% if settings.retention_policy_next_run %} + {{ settings.retention_policy_next_run }} + {% else %} + Not scheduled + {% endif %} +
+
+
+ + +
+ + + Trigger retention policy execution immediately for selected workspace types, bypassing the scheduled time. + +
+ + +
+
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/search-index.html b/application/single_app/templates/admin/_panes/search-index.html new file mode 100644 index 000000000..285f96da9 --- /dev/null +++ b/application/single_app/templates/admin/_panes/search-index.html @@ -0,0 +1,119 @@ +
+
+
Azure AI Search
+

+ Configure Azure AI Search settings. +

+
+ + + +
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + + +
+
+
+ + +
diff --git a/application/single_app/templates/admin/_panes/secrets.html b/application/single_app/templates/admin/_panes/secrets.html new file mode 100644 index 000000000..dbf36ddc0 --- /dev/null +++ b/application/single_app/templates/admin/_panes/secrets.html @@ -0,0 +1,230 @@ +
+

+ Manage security settings for key vault and other security configurations. +

+
+
+
+ Key Vault +
+ +
+ +

+ Configure Key Vault settings. +

+
+ + + +
+ +
+
+ ⚠️ Warning: Once you enable Key Vault, you should NOT disable it. Disabling Key Vault after enabling WILL cause loss of access to secrets and break application functionality. +
+
+ + + +
+
+ + + +
+ +
+
+ +
+
+
+
+ Secret Expiration Reminders +
+

+ Track SimpleChat-owned Key Vault secrets with expiration dates, in-app reminders, and an admin inventory that maps opaque secret names back to their owner and source. +

+
+ +
+ + + +
+ External automation: SimpleChat emits a queryable Application Insights event named + key_vault_expiration_reminder_triggered whenever a reminder notification is created. + Use an Azure Monitor scheduled query alert with an action group, Logic App, Function, or webhook to send external notifications. Enable the routing email option below if downstream automation needs to send directly to the configured reminder contact. +
traces
+| where customDimensions.sc_event_name == 'key_vault_expiration_reminder_triggered'
+| project timestamp,
+          reminder_id = tostring(customDimensions.sc_event_reminder_id),
+          scope = tostring(customDimensions.sc_event_scope),
+          source_type = tostring(customDimensions.sc_event_source_type),
+          days_until_expiry = toint(customDimensions.sc_event_days_until_expiry),
+          expires_on = tostring(customDimensions.sc_event_expires_on),
+          contact_email = tostring(customDimensions.sc_event_contact_email)
+ The contact_email column is populated only when the opt-in below is enabled. For workspace-based Log Analytics queries, use the equivalent Application Insights traces table and dimensions/properties fields available in that workspace. +
+ +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
Comma-separated roles for global-scope reminder notifications.
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+ Default off. Enable only when Azure Monitor, Logic Apps, Functions, or webhook automation needs the email address to route notifications directly. +
+
+
+
+
+ +
+
+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + +
ExpiresScopeSourceFieldContactStatusReminder IDSecret
Refresh inventory to load tracked secrets.
+
+
+
+
+
+
diff --git a/application/single_app/templates/admin/_panes/send-feedback.html b/application/single_app/templates/admin/_panes/send-feedback.html new file mode 100644 index 000000000..5acd28833 --- /dev/null +++ b/application/single_app/templates/admin/_panes/send-feedback.html @@ -0,0 +1,72 @@ +
+

+ Prepare a prefilled email draft for bug reports or feature requests. This utility is separate from saved settings and records each submission intent in the activity log before opening your local email client. +

+ + +
+
+
+
+ Report a Bug +
+

Something isn’t working as expected.

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+
+ +
+
+
+ Request a Feature +
+

Suggest an improvement or new capability.

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+
+
+
diff --git a/application/single_app/templates/admin/_panes/session.html b/application/single_app/templates/admin/_panes/session.html new file mode 100644 index 000000000..d5bc9c36f --- /dev/null +++ b/application/single_app/templates/admin/_panes/session.html @@ -0,0 +1,63 @@ +
+
+
+ Idle Session Timeout +
+

+ Warn inactive users and sign them out after a period of inactivity. +

+
+ + + +
+
+
+ + + Users are logged out locally after this many minutes of inactivity. Minimum value: 10 minutes. +
+
+ + + Show the warning modal after this many minutes of inactivity. Set this equal to the logout timeout to disable the warning dialog window. +
+
+ + + Custom text shown at the top of the idle warning dialog. +
+
+
+
diff --git a/application/single_app/templates/admin/_panes/support-menu.html b/application/single_app/templates/admin/_panes/support-menu.html new file mode 100644 index 000000000..57c11877b --- /dev/null +++ b/application/single_app/templates/admin/_panes/support-menu.html @@ -0,0 +1,105 @@ +
+
+
+ Support +
+
+ + + +
+

When enabled, users can access Support destinations like Send Feedback and Latest Features directly from navigation.

+ +
+
+ + + This name will appear in user navigation as the Support menu title. +
+ +
+ + + +
+ +
+ + + User Send Feedback drafts will be addressed to this internal email address. +
+ +
+ + + +
+ +
+
User-Facing Latest Features
+

Choose which recent features are shared on the user Latest Features page. Deployment and Redis start unchecked because they are mainly admin-facing rollout and infrastructure topics.

+
+ + + + When enabled, user-facing Latest Features cards can show public guide buttons in addition to the direct in-app shortcuts. +
+ {% for release_group in support_latest_feature_release_groups %} + {% set checklist_collapse_id = release_group.collapse_id ~ 'Checklist' %} +
+
+
+
+
{{ release_group.label }}{% if release_group.release_version %}v{{ release_group.release_version }}{% endif %}
+

{{ release_group.description }}

+
+ {% if not release_group.default_expanded %} + + {% endif %} +
+
+
+ {% for feature in release_group.features %} +
+
+ + +
+
+ {% endfor %} +
+
+
+
+ {% endfor %} +
+
+
+
\ No newline at end of file diff --git a/application/single_app/templates/admin/_panes/web-research.html b/application/single_app/templates/admin/_panes/web-research.html new file mode 100644 index 000000000..7ad547075 --- /dev/null +++ b/application/single_app/templates/admin/_panes/web-research.html @@ -0,0 +1,519 @@ +
+ +

+ Configure Azure AI Search, Document Intelligence, and multimedia support settings. +

+ +
+
+
Web Search (Azure AI Foundry Agent)
+ +
+

Enable web search by routing queries through an Azure AI Foundry agent configured by admins.

+
+ + +
+ + + +
+ +
+
+
URL Access
+ Shared Policy +
+

Control direct URL content fetching for pasted chat links, workflow prompts, and Deep Research source review.

+
+ + +
+
+ + +
Required app role value: UrlAccessUser. Assign this role to users or groups in the Enterprise App before enabling the requirement. When enabled, only assigned users can use URL Access in chat or enable it for workflows.
+
+ +
+
+ URL Access uses the same server-side URL protections as Deep Research: non-HTTP(S) URLs, credentialed URLs, literal IP targets, localhost, metadata hosts, unsafe redirects, unsupported content types, and oversized pages are blocked before fetch. +
+
+
+ + +
Hard limit: 100 direct URLs per chat message.
+
+
+ + +
Hard limit: 500 direct URLs per workflow prompt.
+
+
+
+ +
+
+
+ + +
+ + +
+
+
+
Leave blank to allow any public domain that passes safety checks. Deep Research uses this same allow list.
+
+
+
+ + +
+ + +
+
+
+
Blocked domains apply to URL Access and Deep Research source-page review.
+
+
+ +
+
+
+
URL Policy Test
+
Check a URL against the current allowed and blocked domain rules before saving.
+
+ +
+
+
+ +
+
+
Deep Research
+
+ Optional + +
+
+

Plan bounded web searches, inspect source pages, and keep an internal research ledger for evidence coverage. Direct pasted URLs use the shared URL Access controls above.

+
+ + +
+ +
+
+ Server-side protections always block non-HTTP(S) URLs, literal IP URL targets, localhost, metadata hosts, credentialed URLs, excessive redirects, unsupported content types, oversized pages, and requests beyond the configured page budget. DNS hostnames that resolve to private/internal addresses require the internal-host opt-in below. +
+ +
+ + +
Required app role value: DeepResearchUser. Assign this role to users or groups in the Enterprise App before enabling the requirement. When enabled, only assigned users can use Deep Research.
+
+ +
+ + +
Allows DNS hostnames that resolve to private/internal addresses. Literal IP URL targets, localhost, metadata hosts, link-local addresses, and reserved addresses remain blocked.
+
+ +
+
+ + + +
Deep Research runs only when the user selects it for the message.
+
+
+ + +
Hard limit: 10 pages.
+
+
+ + +
Limits initial search-result and direct URL pages so budget remains for child pages.
+
+
+ + +
Direct URLs beyond this cap are recorded as omitted in the ledger.
+
+
+ + +
Includes the original current-message query.
+
+
+ + +
Hard limit: 30 seconds.
+
+
+ + +
Every redirect target is revalidated.
+
+
+ + +
Hard limit: 5 MB.
+
+
+ + +
Depth 2 follows selected links from seed and child pages.
+
+
+ +
+
+ {% set js_runtime = source_review_runtime_capabilities or {} %} +
+ + +
+
Deep Research follows only scored, policy-approved links within the page and depth budgets.
+
+
+
+ + +
+
The selected chat model can propose bounded current-message-only query variants before page review.
+
+
+
+ + +
+
Creates a Markdown chat artifact with search queries, reviewed sources, skipped URLs, and coverage.
+
+
+
+ + +
+
When source pages expose candidate links, the selected chat model can rank those candidates before the server fetches them.
+
+
+
+ + +
+
Requires verified Playwright browser runtime support on the app host.
+
+ {{ js_runtime.message or 'Runtime support has not been checked yet.' }} +
+ {% if not js_runtime.js_rendering_available %} +
Install/enable the Playwright Chromium runtime, then reload Admin Settings to enable this option.
+ {% endif %} + {% if js_runtime.sandbox_disabled %} +
Chromium sandbox is disabled by environment configuration.
+ {% endif %} +
+
+ + +
When JavaScript rendering is enabled, Deep Research can click visible Load More controls until this cap is reached.
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + + + + + + + +
diff --git a/application/single_app/templates/admin/_panes/workflow.html b/application/single_app/templates/admin/_panes/workflow.html new file mode 100644 index 000000000..242119643 --- /dev/null +++ b/application/single_app/templates/admin/_panes/workflow.html @@ -0,0 +1,157 @@ +
+
+
+ Workflow +
+

+ Control personal and group workflow authoring and execution for users. +

+
+ + + +
+
+ + + +
+

+ Required app role value: WorkflowUser. Assign this role to users or groups in the Enterprise App before enabling the requirement. +

+ +
+ + +
+ Maximum automatic tool or action calls an agent can make during one workflow run. Default is 60; increase for large document sets. +
+ +
+ +
+ + +
+ Maximum ordered instruction tasks users can add to one workflow. Default is 50; supported range is 1-100. +
+
+ +
+ +
+ + + +
+
+ + + +
+
+ + No groups assigned. +
+ +

+ Group workflow authoring is available to group Owners and Admins by default. Enable the owner-only setting below to limit group agent, action, and workflow management to Owners. +

+
+ + + + +
diff --git a/application/single_app/templates/admin/_panes/workspace-identities.html b/application/single_app/templates/admin/_panes/workspace-identities.html new file mode 100644 index 000000000..7c3569ebd --- /dev/null +++ b/application/single_app/templates/admin/_panes/workspace-identities.html @@ -0,0 +1,21 @@ +
+

+ Manage the global authentication profiles that File Sync sources and Actions reuse, so credentials stay scoped and are resolved server-side. +

+
+
+ Global Identities +
+

+ Identities defined here are available across the deployment. Each one stores its secret in Key Vault when Key Vault is configured, and is referenced by name rather than by value wherever it is used. +

+
+
+
diff --git a/application/single_app/templates/admin/_panes/workspace-types.html b/application/single_app/templates/admin/_panes/workspace-types.html new file mode 100644 index 000000000..83123fd7a --- /dev/null +++ b/application/single_app/templates/admin/_panes/workspace-types.html @@ -0,0 +1,159 @@ +
+

+ Configure workspace settings like personal/group access, multimedia support, metadata, and document classification. +

+ + +
+
+ Personal Workspaces +
+

+ Turn this on to allow access and management of your personal workspace. +

+
+ + + +
+
+ +
+
+ Group Workspaces +
+

+ Turn this on to allow access and management of group workspaces, as well as group collaboration features. +

+
+ + + +
+ + +
+
+ + +
+ + + +
+

+ When enabled, no users will be able to create new groups, regardless of app role assignment. This global setting overrides the Require CreateGroups App Role setting below. +

+ +
+ + + +
+

+ Required app role value: CreateGroups. Assign this role to users or groups in the Enterprise App before enabling the requirement. If disabled, any authenticated user can create groups while group workspaces and group creation are enabled. +

+ +
+ + +
+ + + +
+

+ If enabled, only the group Owner can create, edit, and delete group agents, group actions, and group workflows. Admins and other roles will only be able to view them. +

+
+ +
+ +
+
+ Public Workspaces +
+

+ Turn this on to enable public workspaces that are visible to everyone in the organization. +

+
+ + + +
+ +
+ + +
+ Optional. End users will see this label instead of Public Workspace. Admin settings and internal references continue to use Public Workspace. +
+
+ +
+
+
+ + + +
+

+ Required app role value: CreatePublicWorkspaces. Assign this role to users or groups in the Enterprise App before enabling the requirement. If disabled, any authenticated user can create public workspaces while public workspaces are enabled. +

+
+ +
+ + +
diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html index 947f7f1ba..f2daba2bc 100644 --- a/application/single_app/templates/admin_settings.html +++ b/application/single_app/templates/admin_settings.html @@ -39,6 +39,14 @@ vertical-align: middle; } + /* Brief highlight when a data-admin-link jumps to a card, so the + destination is obvious after the tab switch and scroll. */ + .admin-card-link-target { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 3px; + transition: outline-color 0.4s ease-out; + } + /* Floating Save Button */ .floating-save-btn { position: fixed; @@ -928,12005 +936,694 @@

12. Enhanced Citations and Image Generation

{% set nav_layout = user_settings.get('settings', {}).get('navLayout') %} {% if not (nav_layout == 'sidebar' or (not nav_layout and app_settings.enable_left_nav_default)) %} - - {% endif %} -
-
-

- Manage security settings for key vault and other security configurations. -

-
-
-
- Key Vault -
- -
+ + {% include "admin/_panes/feature-governance.html" %} + {% include "admin/_panes/governance-policies.html" %} + {% include "admin/_panes/mcp-governance.html" %} -

- Configure Key Vault settings. -

-
- - - -
+ + {% include "admin/_panes/logging.html" %} -
-
- ⚠️ Warning: Once you enable Key Vault, you should NOT disable it. Disabling Key Vault after enabling WILL cause loss of access to secrets and break application functionality. -
-
- - - -
-
- - - -
- -
-
+ {% include "admin/_panes/branding.html" %} + {% include "admin/_panes/notices.html" %} -
-
-
-
- Secret Expiration Reminders -
-

- Track SimpleChat-owned Key Vault secrets with expiration dates, in-app reminders, and an admin inventory that maps opaque secret names back to their owner and source. -

-
- -
+ {% include "admin/_panes/custom-pages.html" %} - -
- External automation: SimpleChat emits a queryable Application Insights event named - key_vault_expiration_reminder_triggered whenever a reminder notification is created. - Use an Azure Monitor scheduled query alert with an action group, Logic App, Function, or webhook to send external notifications. Enable the routing email option below if downstream automation needs to send directly to the configured reminder contact. -
traces
-| where customDimensions.sc_event_name == 'key_vault_expiration_reminder_triggered'
-| project timestamp,
-          reminder_id = tostring(customDimensions.sc_event_reminder_id),
-          scope = tostring(customDimensions.sc_event_scope),
-          source_type = tostring(customDimensions.sc_event_source_type),
-          days_until_expiry = toint(customDimensions.sc_event_days_until_expiry),
-          expires_on = tostring(customDimensions.sc_event_expires_on),
-          contact_email = tostring(customDimensions.sc_event_contact_email)
- The contact_email column is populated only when the opt-in below is enabled. For workspace-based Log Analytics queries, use the equivalent Application Insights traces table and dimensions/properties fields available in that workspace. -
+ {% include "admin/_panes/model-endpoints.html" %} + {% include "admin/_panes/embeddings.html" %} + {% include "admin/_panes/image-generation.html" %} -
- - -
+ + {% include "admin/_panes/control-center-config.html" %} -
-
-
- - -
-
- - -
-
- - -
Comma-separated roles for global-scope reminder notifications.
-
-
- - -
-
-
- - -
-
-
-
- - -
- Default off. Enable only when Azure Monitor, Logic Apps, Functions, or webhook automation needs the email address to route notifications directly. -
-
-
-
-
+ {% include "admin/_panes/backup.html" %} + {% include "admin/_panes/migrate.html" %} + {% include "admin/_panes/restore.html" %} + {% include "admin/_panes/cosmos-editor.html" %} + {% include "admin/_panes/jobs.html" %} -
-
- - - -
+ {% include "admin/_panes/redis-caching.html" %} + {% include "admin/_panes/cosmos.html" %} + {% include "admin/_panes/network.html" %} - - -
- - - - - - - - - - - - - - - - - - -
ExpiresScopeSourceFieldContactStatusReminder IDSecret
Refresh inventory to load tracked secrets.
-
-
-
-
-
-
+ {% include "admin/_panes/workspace-identities.html" %} -
-

- Configure AI agents and actions for enhanced functionality. Agents provide AI-driven task automation while Actions extend functionality with custom tools and integrations. -

+ {% include "admin/_panes/file-sync.html" %} - + {# Data Lifecycle: what happens to user data over time #} + {% include "admin/_panes/retention.html" %} + {% include "admin/_panes/classification.html" %} + {% include "admin/_panes/archiving.html" %} - - {% set analyze_capability = settings.document_action_capabilities.analyze %} - {% set comparison_capability = settings.document_action_capabilities.comparison %} -
-
-
-
Document Action Capabilities
-

Configure the document actions shown in the Action dropdown in Chat and Workflow. These controls live in the Agents and Actions tab, but remain separate from the global agent and custom action cards below.

-
- Chat: 2-300 | Workflow: 2-1000 -
- -
-
-
-
- - - -
-
-
- - {{ analyze_capability.chat_max_documents }} -
-
-
- -
-
- -
-
-
- 2 - 300 -
-
-
-
- - {{ analyze_capability.workflow_max_documents }} -
-
-
- -
-
- -
-
-
- 2 - 1000 -
-
-
-
-
-
-
- - - -
-
-
- - {{ comparison_capability.chat_max_documents }} -
-
-
- -
-
- -
-
-
- 2 - 300 -
-
-
-
- - {{ comparison_capability.workflow_max_documents }} -
-
-
- -
-
- -
-
-
- 2 - 1000 -
-
-
-
-
-
+ {% include "admin/_panes/citation.html" %} - -
-
- Agents Configuration -
-

- Configure AI agents powered by Semantic Kernel for task automation and orchestration. -

-
-
- Agents Page Customization -
-

Customize the public Agents page hero, color treatment, and optional guidance message.

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
Used when two tone gradient is selected.
-
-
- - -
Shown below the Agents page hero. Use this for contact details, request guidance, or governance reminders.
-
-
-
- - -
-
When disabled, the Agents tab details popup hides instructions and the catalog API response omits them.
-
-
-
-
Promoted Popular Agents
-

Highlight selected agents in the Popular tab even before they have usage counts. Users only see promoted agents that are already visible to them.

- -
-
- - -
-
- - -
-
-
-
- - -
-
When enabled, promoted agents show the configured tag in the Popular tab.
-
-
-
-
-
- - -
-
- -
-
-
- - - - - - - - - -
AgentShow InActions
-
- - -
-
-
-
-
- - - - - -
- -
- {% if not settings.enable_semantic_kernel %} -
- Agents UI is not available while agents are disabled. -
- {% else %} -
-
- - - -
- {% if settings.per_user_semantic_kernel %} - -
-
Workspace Feature Toggles
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - -
-
- - - -
-
- + {# Chat: the conversation surface itself #} + {% include "admin/_panes/chat-experience.html" %} + {% include "admin/_panes/feedback-alerts.html" %} - {% endif %} - -
-

Agent Orchestration Settings

-
-
-

Configure how the chat system orchestrates agents (single or multi-agent group chat).

-
- - -
- - -
-
-

Global Agents

-
- - -
-
-

Disable a global agent to keep it saved for admins while hiding it from runtime selection until it is re-enabled.

-
- {% if settings.orchestration_type == "default_agent" %} - - {% else %} - - {% endif %} - - -
- - - - - - - {% if settings.orchestration_type == "default_agent" %} - - {% else %} - - {% endif %} - - - - -
NameDisplay NameDescriptionSelected AgentOrchestrator AgentActions
-
- {% endif %} -
- {% if settings.enable_agent_template_gallery %} -
-
-
-
- - - -
-
-
-
- - - -
-
-
-
-
-
- Agent Template Approvals -
-

Template submissions are now reviewed from the shared approvals page.

-
- - Open Approvals Queue - -
-
- {% endif %} - - -
-
- Global Actions Configuration -
-

- Configure custom actions and tools to extend functionality with integrations and specialized capabilities. -

- {% if not settings.enable_semantic_kernel %} -
- Actions are not available while agents are disabled. Enable agents above to configure actions. -
- {% else %} -
-
-
Global Actions
- -
-

Disable a global action to keep the configuration without exposing it to runtime action loading until it is re-enabled.

- {% if settings.per_user_semantic_kernel %} - -
-
Workspace Action Feature Toggles
-
- - - - -
-
- - - - -
-
- {% endif %} - - -
-
Core Action Toggles
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
- Tabular Processing Action - -
- Automatically enabled when Enhanced Citations is enabled -
-
- - - - - - - - - -
Display NameDescriptionActions
-
- {% endif %} -
- {% if mcp_ui_enabled %} -
-
-
-
- Inbound MCP Server -
-

- Configure the governed SimpleChat MCP endpoint used by external MCP clients. Personal tools use delegated user tokens by default; tool access remains deny-by-default until authentication, source, client, and governance policy all allow the request. -

-
-
- Preview - -
-
- + {% include "admin/_panes/web-research.html" %} + {% include "admin/_panes/search-index.html" %} + {% include "admin/_panes/extraction.html" %} + {% include "admin/_panes/audio-video.html" %} -
-
-
-
Runtime Gate
-
- - - -
-
- - -
Default: DelegatedMcpServerAccess. VS Code and other user clients must present this delegated scope.
-
-
- - -
Default: InboundMCPUserAccess. Governance determines which users/groups can use tools after this Entra role and delegated scope pass.
-
-
- - -
Default: InboundMCPAppAccess. Reserved for future app-only MCP tools and still governed separately.
-
-
-
+ {% include "admin/_panes/support-menu.html" %} + {% include "admin/_panes/send-feedback.html" %} -
-
-
Endpoint Contract
-
-
MCP endpoint
-
{{ inbound_mcp_resource_path }}
-
Protected resource metadata
-
{{ inbound_mcp_prm_path }}
-
Transport
-
Streamable HTTP JSON-RPC
-
Current tool surface
-
Personal delegated tools only.
-
Tool identity
-
Delegated user token required for personal data.
-
- -
-
+ {# Latest Features stays last so it never opens by default #} + {% include "admin/_panes/latest-features.html" %} +
+ + -
-
-
-
-
Request Size & Throttling
-
Protect the inbound MCP endpoint from oversized payloads and noisy clients. Rate limits are enforced per caller and tool category across app instances.
-
-
- - -
-
-
-
- - -
Default: 65536. Range: 1 KB to 1 MB.
-
-
- - -
Default: 60. Applies to each throttle category.
-
-
- - -
Default: 120.
-
-
- - -
Default: 30.
-
-
- - -
Default: 10.
-
-
- -
-
- -
- - - -
- -
-
-
-
-
Allowed client app IDs
-
Required allowlist. Empty means no MCP clients can connect.
-
- -
-
- - - - - - - - - -
Client app IDDescriptionActions
-
-
-
-
- -
-
-
- - -
-
- Off allows only the configured SimpleChat tenant. Turning this on auto-includes the SimpleChat tenant and lets admins add more tenants. -
-
-
-
Allowed tenant IDs
- -
-
- - - - - - - - - -
Tenant IDDescriptionActions
-
-
-
-
-
- -
-
-
- - -
-
- Default on accepts any source signal at the runtime allowlist layer. Turn off to require explicit source values here. In both modes, admins must still create an inbound MCP source governance policy before tools are returned. -
-
- - -
Default: X-SimpleChat-MCP-Source.
-
- - - -
-
-
Allowed source IDs
- -
-
- - - - - - - - - -
Source valueDescriptionActions
-
-
-
-
-
-
-
- -