diff --git a/.github/instructions/docs_coverage.instructions.md b/.github/instructions/docs_coverage.instructions.md new file mode 100644 index 000000000..fdc594965 --- /dev/null +++ b/.github/instructions/docs_coverage.instructions.md @@ -0,0 +1,104 @@ +--- +applyTo: '**' +--- + +# Documentation Coverage + +## Why this exists + +The documentation site is kept in sync with the application by a generated +inventory plus tests. Before this was in place, coverage decayed silently: the +app had 111 capability toggles, 18 admin settings tabs, 27 action plugins, and +47 chat controls while the docs described roughly a dozen capabilities and had +no per-tab, per-action, or per-control reference at all. + +## The rule + +When a change adds or renames any of the following, the same change must update +the documentation: + +| You added or renamed | Document it in | +|---|---| +| An `enable_*` settings key | The relevant `docs/admin/.md` settings table, and claim the key in `docs/_data/features.yml` | +| An admin settings tab | A new `docs/admin/.md` page | +| A Semantic Kernel action plugin | A new `docs/reference/actions/.md` page | +| A chat UI control | `docs/reference/chat-controls.md` | +| A user-facing app page or major surface | A guide under `docs/guides/` | + +## Regenerating the inventory + +The application surface inventory is generated, not hand-written: + +```powershell +python .\scripts\build_docs_inventory.py +``` + +This writes `docs/_data/app_surface.yml`. Commit the regenerated file with your +change. The inventory deliberately does not record the application version, so +it only changes when the application surface actually changes. + +## Verifying + +```powershell +python .\functional_tests\test_docs_app_surface_coverage.py +python .\functional_tests\test_docs_site_quality.py +``` + +The coverage test fails when any inventory item is undocumented, and when the +committed inventory is stale relative to the application. + +## Internal-only flags + +A settings key that has no admin-facing UI and is purely an internal rollout, +telemetry, or tuning switch may be exempted. Add it to `CAPABILITY_EXEMPTIONS` +in `functional_tests/test_docs_app_surface_coverage.py` **with a written +justification**. Exemptions are themselves checked for staleness, so a flag that +is later removed from the application will fail the test until the exemption is +deleted. + +Do not use an exemption to avoid writing documentation for a user-facing +capability. + +## Screenshots and videos + +Documentation pages declare media slots that render a visible placeholder card +until the asset exists. To add a screenshot, save the file at the exact path the +placeholder names, for example `docs/images/admin/general-tab.png`. No YAML or +code change is required. + +Videos are never committed to the repository. Upload the recording to YouTube or +Microsoft Stream, save a poster frame under `docs/images/video-posters/`, and add +the watch URL to the slot in `docs/_data/media.yml`. + +Outstanding media is listed at `/contributing/media-status/`. Missing media does +not fail the build, so it never blocks a change, but it stays visible. + +## Writing quality + +Documentation pages must explain **what a capability does and why someone would +use it**, not restate control names. + +Do not write: + +- "Turns X on or off" or "Sets X" as a description. +- The same boilerplate paragraph across multiple sections or pages. +- Procedures that only describe using a form, such as "Open the page, change the + fields, save". +- Prerequisites that would apply to any page. + +Never invent a setting, default, field, or behavior. A shorter accurate page is +better than a longer speculative one. + +## Page structure + +Write plain markdown with real `##` and `###` headings. The site generates its +"On this page" table of contents and heading anchors from rendered `h2`/`h3` +elements, so pages authored as HTML card markup get neither. Do not add +`
`, `
`, or card/badge markup to documentation pages. + +## Browser assets + +The documentation site loads no third-party assets. Everything is vendored under +`docs/assets/vendor/` and pinned by version. Do not add a CDN ` - - - - - - + - - - - - - + + + + + + @@ -113,15 +113,17 @@ + + + {% if page.custom_js %} diff --git a/docs/_layouts/feature.html b/docs/_layouts/feature.html new file mode 100644 index 000000000..222bdeade --- /dev/null +++ b/docs/_layouts/feature.html @@ -0,0 +1,107 @@ +--- +layout: default +--- + +{%- comment -%} +Feature detail page. + +Content comes from docs/_data/features.yml rather than from the page body, so a +capability is described in exactly one place. Pages in the _features collection +are thin stubs generated by scripts/generate_feature_pages.py; anything written +in the page body is rendered after the generated sections as extra detail. +{%- endcomment -%} + +{%- assign feature = site.data.features[page.slug] -%} + +
+
+
+ + + + +
+ {%- if feature == nil -%} +

No entry for {{ page.slug }} exists in docs/_data/features.yml.

+ {%- else -%} + +
+ {%- if feature.area -%} +
Area
{{ feature.area }}
+ {%- endif -%} + {%- if feature.audience -%} +
Who uses it
{{ feature.audience }}
+ {%- endif -%} + {%- if feature.default_state -%} +
Default
{{ feature.default_state }}
+ {%- endif -%} + {%- if feature.admin_tab -%} +
Enable it in
+
{{ feature.admin_tab }} settings
+ {%- endif -%} +
+ + {%- if feature.why -%} +

Why you would use this

+

{{ feature.why }}

+ {%- endif -%} + + {%- if feature.setting_keys and feature.setting_keys.size > 0 -%} +

Settings that control it

+

This capability is governed by the following settings keys.

+
    + {%- for key in feature.setting_keys -%} +
  • {{ key }}
  • + {%- endfor -%} +
+ {%- endif -%} + + {%- if feature.guides and feature.guides.size > 0 -%} +

How to use it

+ + {%- endif -%} + + {%- if feature.reference and feature.reference.size > 0 -%} +

Reference

+ + {%- endif -%} + + {%- endif -%} + + {{ content }} +
+ + +
+
+ + +
diff --git a/docs/_layouts/showcase-page.html b/docs/_layouts/showcase-page.html index 8eeb98af7..9ab32e0bc 100644 --- a/docs/_layouts/showcase-page.html +++ b/docs/_layouts/showcase-page.html @@ -2,142 +2,99 @@ layout: default --- -{% assign accent = page.accent | default: 'blue' %} - -
+
-
-
-
- {% if page.breadcrumb and page.breadcrumb.url and page.breadcrumb.label %} - - {% elsif page.url != '/' %} - + {% endif %} - {% if page.home_search %} - - {% endif %} + {% if page.eyebrow or page.hero_version %} +

+ {% if page.eyebrow %} + {{ page.eyebrow }} + {% endif %} + {% if page.hero_version %} + {{ page.hero_version }} + {% endif %} +

+ {% endif %} - {% if page.hero_pills %} -
- {% for pill in page.hero_pills %} - {{ pill }} - {% endfor %} -
- {% endif %} +

{{ page.title }}

- {% if page.hero_links %} -
- {% for link in page.hero_links %} - {% assign button_class = 'btn-outline-secondary' %} - {% if link.style == 'primary' %} - {% assign button_class = 'btn-primary' %} - {% elsif link.style == 'secondary' %} - {% assign button_class = 'btn-secondary' %} - {% endif %} - {{ link.label }} - {% endfor %} -
- {% endif %} -
+ {% if page.description %} +

{{ page.description }}

+ {% endif %} - -
+ {% if page.home_search %} + + {% endif %} -
-
- {{ content }} -
+ {% if page.hero_pills %} +
    + {% for pill in page.hero_pills %} +
  • {{ pill }}
  • + {% endfor %} +
+ {% endif %} - {% if page.show_nav and page.nav_links %} - + {{ link.label }} + {% endfor %} +
{% endif %} + + +
+ {{ content }}
-
@@ -149,6 +106,4 @@

On this page

{% endunless %} -
- - \ No newline at end of file +
\ No newline at end of file diff --git a/docs/admin/agents-actions.md b/docs/admin/agents-actions.md new file mode 100644 index 000000000..670d6fe3f --- /dev/null +++ b/docs/admin/agents-actions.md @@ -0,0 +1,152 @@ +--- +layout: page +title: "Agents & Actions settings" +description: "Agents & Actions controls the Semantic Kernel runtime, agent marketplace presentation, document actions, built-in actions, workspace permissions, orchestration, and inbound MCP." +section: "Administration" +audience: admin +admin_tab: agents-actions +redirect_from: + - /admin/agents/ +--- + + +# Agents & Actions settings + +## What this group controls + +Agents & Actions controls the Semantic Kernel runtime, agent marketplace presentation, document actions, built-in actions, workspace permissions, orchestration, and inbound MCP. + +## Why it matters + +Agents and actions can call tools, inspect documents, and automate work. Expose only the actions and agent sources that fit your governance model, then verify permissions before users depend on them. + +{% include media.html src="admin-settings/agents-actions.png" alt="Screenshot of the Agents & Actions group in Admin Settings." title="Agents & Actions settings" %} + +{% include media.html type="video" title="Agents & Actions settings walkthrough" poster="video-posters/admin-agents-actions.png" capture="Recording planned. Walk through each tab in the Agents & Actions group and explain when to change each setting." %} + +## Before you change anything + +- Decide whether personal, group, or global agents are allowed. +- Confirm action dependencies before enabling a plugin. +- Configure App Service Authentication excluded paths before exposing inbound MCP. + +## Agents {#agents} + +### Agents Configuration {#agents-config} + +The Agents Configuration section belongs to the Agents tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Agent Template Approvals {#agent-template-approvals-section} + +The Agent Template Approvals section belongs to the Agents tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Hero Title | Provides displayed text that users see in the affected interface. | Find your next AI partner | `agents_page_title` | +| Hero Subtitle | Provides displayed text that users see in the affected interface. | Explore specialized agents built to accelerate how you work. | `agents_page_subtitle` | +| Hero Color Mode | Defines behavior for the related admin workflow; verify the affected feature after saving. | single | `agents_page_hero_color_mode` | +| Primary Color | Defines behavior for the related admin workflow; verify the affected feature after saving. | #0f172a | `agents_page_hero_primary_color` | +| Secondary Color | Defines behavior for the related admin workflow; verify the affected feature after saving. | #1e293b | `agents_page_hero_secondary_color` | +| Disclaimer / Guidance Text (Markdown supported) | Shown below the Agents page hero. Use this for contact details, request guidance, or governance reminders. | Empty | `agents_page_disclaimer_markdown` | +| Show agent instructions in Agents page details | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `agents_page_show_instructions_in_details` | +| Agents Page Promoted Popular Agents Json | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `agents_page_promoted_popular_agents_json` | +| Placement | Defines behavior for the related admin workflow; verify the affected feature after saving. | before | `agents_page_promoted_popular_order` | +| Promoted Tag Label | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `agents_page_promoted_popular_tag_label` | +| Show promoted tag | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `agents_page_promoted_popular_tag_enabled` | +| Add Agent | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Enable Agents | Enables the agent and action runtime so users can work with configured agents instead of only the base chat experience. | Off | `enable_semantic_kernel`; capability toggle | +| Workspace Mode (workspace-specific agents/plugins and disables global configuration) | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `per_user_semantic_kernel` | +| Allow User Agents | Permits user agents when the related workspace or agent feature is enabled. | Off | `allow_user_agents` | +| Allow Group Agents | Permits group agents when the related workspace or agent feature is enabled. | Off | `allow_group_agents` | +| Allow User Custom Endpoints | Permits user custom endpoints when the related workspace or agent feature is enabled. | Off | `allow_user_custom_endpoints` | +| Allow Group Custom Endpoints | Permits group custom endpoints when the related workspace or agent feature is enabled. | Off | `allow_group_custom_endpoints` | +| Merge Global Agents/Plugins into Workspace | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `merge_global_semantic_kernel_with_workspace` | +| Enable Agent Template Gallery | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_agent_template_gallery`; capability toggle | +| Orchestration Type | Defines behavior for the related admin workflow; verify the affected feature after saving. | default_agent | `orchestration_type` | +| Multi-agent orchestration | Derived from **Orchestration Type**; enables group-chat orchestration paths when the selected orchestration mode is multi-agent, so the runtime can coordinate multiple configured agents instead of routing to a single default agent. | Off | `enable_multi_agent_orchestration`; saved by orchestration settings API | +| Max Rounds Per Agent (Group Chat) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 1 | `max_rounds_per_agent` | +| Selected Agent: | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Allow User Template Submissions | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `agent_templates_allow_user_submission` | +| Require Admin Approval | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `agent_templates_require_approval` | +| Enable tool throttles | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_inbound_mcp_rate_limits`; capability toggle | +| Value | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Description | Use descriptions to explain who owns this app, tenant, or source value without making the value easy to guess. | Not specified in defaults | Runtime UI control | +| I have added the required App Service Authentication excluded paths for this SimpleChat App Service. | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | + +## Actions {#actions} + +### Document Action Capabilities {#document-action-capabilities-card} + +The Document Action Capabilities section belongs to the Actions tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Actions Configuration {#actions-config} + +The Actions Configuration section belongs to the Actions tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Analyze | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `document_action_analyze_enabled` | +| Document Action Analyze Chat Max Documents Range | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 3 | `document_action_analyze_chat_max_documents_range` | +| Chat max documents | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 3 | `document_action_analyze_chat_max_documents` | +| Document Action Analyze Workflow Max Documents Range | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `document_action_analyze_workflow_max_documents_range` | +| Workflow max documents | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `document_action_analyze_workflow_max_documents` | +| Enable Document Comparison | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `document_action_comparison_enabled` | +| Document Action Comparison Chat Max Documents Range | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 3 | `document_action_comparison_chat_max_documents_range` | +| Chat max documents | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 3 | `document_action_comparison_chat_max_documents` | +| Document Action Comparison Workflow Max Documents Range | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `document_action_comparison_workflow_max_documents_range` | +| Workflow max documents | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `document_action_comparison_workflow_max_documents` | +| Allow Personal Actions | Permits personal actions when the related workspace or agent feature is enabled. | Off | `allow_user_plugins` | +| Allow Group Actions | Permits group actions when the related workspace or agent feature is enabled. | Off | `allow_group_plugins` | +| Enable Time Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_time_plugin`; capability toggle | +| Enable HTTP Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_http_plugin`; capability toggle | +| Enable Wait Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_wait_plugin`; capability toggle | +| Enable Math Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_math_plugin`; capability toggle | +| Enable Text Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_text_plugin`; capability toggle | +| Enable Default Embedding Model Action | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_default_embedding_model_plugin`; capability toggle | +| Enable Fact Memory Action | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_fact_memory_plugin`; capability toggle | +| Tabular Processing Action | Makes the tabular-processing action available to agents for CSV and XLSX analysis when Enhanced Citations is enabled; the setting is normalized from Enhanced Citations rather than edited directly in the UI. | Off | `enable_tabular_processing_plugin`; effective value follows `enable_enhanced_citations` | + +## Inbound MCP {#inbound-mcp} + +### Inbound MCP {#inbound-mcp-configuration} + +The Inbound MCP section belongs to the Inbound MCP tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable inbound MCP server | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_inbound_mcp_server`; capability toggle | +| Required delegated scope | Default: DelegatedMcpServerAccess. VS Code and other user clients must present this delegated scope. | DelegatedMcpServerAccess | `inbound_mcp_required_scope` | +| Required delegated user role | Default: InboundMCPUserAccess. Governance determines which users/groups can use tools after this Entra role and delegated scope pass. | InboundMCPUserAccess | `inbound_mcp_required_user_role` | +| Required app-only role | Default: InboundMCPAppAccess. Reserved for future app-only MCP tools and still governed separately. | InboundMCPAppAccess | `inbound_mcp_required_app_role` | +| Max request bytes | Default: 65536. Range: 1 KB to 1 MB. | 65536 | `inbound_mcp_max_request_bytes` | +| Rate window seconds | Default: 60. Applies to each throttle category. | 60 | `inbound_mcp_rate_limit_window_seconds` | +| Read limit | Default: 120. | 120 | `inbound_mcp_rate_limit_read_per_window` | +| Search limit | Default: 30. | 30 | `inbound_mcp_rate_limit_search_per_window` | +| Write limit | Default: 10. | 10 | `inbound_mcp_rate_limit_write_per_window` | +| Allow additional tenant IDs | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `inbound_mcp_allow_external_tenants` | +| Allow all source IDs | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `inbound_mcp_allow_all_source_ids` | +| Source header name | Default: X-SimpleChat-MCP-Source. | X-SimpleChat-MCP-Source | `inbound_mcp_source_header` | + +## Common tasks + +1. **Turn on governed agents.** Enable the runtime, choose allowed scopes, and create a test agent. Outcome to verify: Only approved agent scopes are available. +2. **Limit document actions.** Review analyze and comparison limits, then run a small action. Outcome to verify: Document actions complete within configured caps. +3. **Prepare inbound MCP.** Set delegated scope, roles, throttles, and sources, then connect a test client. Outcome to verify: Approved clients can reach tools and blocked sources fail cleanly. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| An action is missing from an agent | The built-in action toggle or workspace action permission is disabled. | Enable the action and confirm the agent scope allows it. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) +- [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) diff --git a/docs/admin/ai-models.md b/docs/admin/ai-models.md new file mode 100644 index 000000000..2f977fc90 --- /dev/null +++ b/docs/admin/ai-models.md @@ -0,0 +1,125 @@ +--- +layout: page +title: "AI Models settings" +description: "AI Models configures chat, embedding, image generation, APIM, multi-endpoint routing, and model endpoint identity behavior." +section: "Administration" +audience: admin +admin_tab: ai-models +--- + + +# AI Models settings + +## What this group controls + +AI Models configures chat, embedding, image generation, APIM, multi-endpoint routing, and model endpoint identity behavior. + +## Why it matters + +Model endpoints are production dependencies for every generated answer, embedding, and image. Keep deployment names, API versions, and authentication choices aligned with the Azure resources operators support. + +{% include media.html src="admin-settings/ai-models.png" alt="Screenshot of the AI Models group in Admin Settings." title="AI Models settings" %} + +{% include media.html type="video" title="AI Models settings walkthrough" poster="video-posters/admin-ai-models.png" capture="Recording planned. Walk through each tab in the AI Models group and explain when to change each setting." %} + +## Before you change anything + +- Provision Azure OpenAI, APIM, and image resources before pointing SimpleChat to them. +- Choose key or managed identity authentication and grant required permissions. +- Name fallback deployments deliberately so background tasks use approved models. + +## Model Endpoints {#model-endpoints} + +### Model Endpoints {#multi-endpoint-configuration} + +The Model Endpoints section belongs to the Model Endpoints tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Chat Model {#gpt-config} + +The Chat Model section belongs to the Model Endpoints tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable multi-endpoint model management | Provides the endpoint or route SimpleChat uses for this service. | Off | `enable_multi_model_endpoints`; capability toggle | +| Search Agents | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Filter | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Enable header | Provides the endpoint or route SimpleChat uses for this service. | Off | `model_endpoint_identity_header_enabled` | +| Header Name | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `model_endpoint_identity_header_name` | +| Identity Value | The selected identity is HMAC-hashed before leaving SimpleChat. Missing identity values omit the header. | Not specified in defaults | `model_endpoint_identity_header_value_type` | +| Default model for fallbacks | Used for tasks such as conversation summarization, fallback, and other operations when an agent is selected. | Not specified in defaults | Runtime UI control | +| Use APIM instead of direct to Azure OpenAI endpoint | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_gpt_apim`; capability toggle | +| Azure OpenAI Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_openai_gpt_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `azure_openai_gpt_authentication_type` | +| Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_gpt_subscription_id` | +| Resource Group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_gpt_resource_group` | +| Azure OpenAI GPT Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_openai_gpt_key` | +| Azure OpenAI API Version | Pins the service API version SimpleChat sends with requests for this feature. | 2024-05-01-preview | `azure_openai_gpt_api_version` | +| Azure APIM Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_gpt_endpoint` | +| Azure APIM API Version | Pins the service API version SimpleChat sends with requests for this feature. | Empty | `azure_apim_gpt_api_version` | +| Azure APIM Deployment | Each model defined here will be available in the Chat UI as an option for the User. You can include multiple models seperated by a comma (example: gpt-4o, o-1, o-3). | Empty | `azure_apim_gpt_deployment` | +| Azure APIM Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_gpt_subscription_key` | + +## Embeddings {#embeddings} + +### Embeddings {#embeddings-config} + +The Embeddings section belongs to the Embeddings tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Use APIM instead of direct to Azure OpenAI endpoint | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_embedding_apim`; capability toggle | +| Azure OpenAI Embedding Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_openai_embedding_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `azure_openai_embedding_authentication_type` | +| Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_embedding_subscription_id` | +| Resource Group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_embedding_resource_group` | +| Azure OpenAI Embedding Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_openai_embedding_key` | +| Azure OpenAI Embedding API Version | Pins the service API version SimpleChat sends with requests for this feature. | 2024-05-01-preview | `azure_openai_embedding_api_version` | +| Azure APIM Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_embedding_endpoint` | +| Azure APIM API Version | Pins the service API version SimpleChat sends with requests for this feature. | Empty | `azure_apim_embedding_api_version` | +| Azure APIM Deployment | Selects the deployment SimpleChat sends requests to for this capability. | Empty | `azure_apim_embedding_deployment` | +| Azure APIM Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_embedding_subscription_key` | + +## Image Generation {#image-generation} + +### Image Generation {#image-config} + +The Image Generation section belongs to the Image Generation tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Image Generation | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_image_generation`; capability toggle | +| Use APIM instead of direct to Azure OpenAI endpoint. | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_image_gen_apim`; capability toggle | +| Azure OpenAI Image Generation Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_openai_image_gen_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `azure_openai_image_gen_authentication_type` | +| Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_image_gen_subscription_id` | +| Resource Group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `azure_openai_image_gen_resource_group` | +| Azure OpenAI Image Generation Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_openai_image_gen_key` | +| Azure OpenAI Image Gen API Version | Pins the service API version SimpleChat sends with requests for this feature. | 2024-12-01-preview | `azure_openai_image_gen_api_version` | +| Azure APIM Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_image_gen_endpoint` | +| Azure APIM API Version | Pins the service API version SimpleChat sends with requests for this feature. | Empty | `azure_apim_image_gen_api_version` | +| Azure APIM Deployment | Selects the deployment SimpleChat sends requests to for this capability. | Empty | `azure_apim_image_gen_deployment` | +| Azure APIM Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_image_gen_subscription_key` | + +## Common tasks + +1. **Route chat through approved endpoints.** Enable multi-endpoint management when needed and run a chat through the intended deployment. Outcome to verify: Requests reach the approved route. +2. **Configure embeddings.** Set endpoint, authentication, API version, and deployment, then index a small document. Outcome to verify: Indexing succeeds with the configured embedding route. +3. **Enable image generation.** Enable the capability, set endpoint values, and generate a low-risk test image. Outcome to verify: The image tool returns output from the configured deployment. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Embeddings fail during indexing | Endpoint, deployment, API version, or authentication does not match the Azure resource. | Validate the embedding route with a small document before bulk indexing. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Agents & Actions settings]({{ '/admin/agents-actions/' | relative_url }}) +- [Chat settings]({{ '/admin/chat/' | relative_url }}) diff --git a/docs/admin/appearance.md b/docs/admin/appearance.md new file mode 100644 index 000000000..c30831a4a --- /dev/null +++ b/docs/admin/appearance.md @@ -0,0 +1,143 @@ +--- +layout: page +title: "Appearance settings" +description: "Appearance controls the public-facing identity of SimpleChat: branding, landing copy, banners, user notices, terms, custom pages, and external navigation links." +section: "Administration" +audience: admin +admin_tab: appearance +redirect_from: + - /admin/general/ + - /admin/custom-pages/ +--- + + +# Appearance settings + +## What this group controls + +Appearance controls the public-facing identity of SimpleChat: branding, landing copy, banners, user notices, terms, custom pages, and external navigation links. + +## Why it matters + +These settings shape trust before a user asks a question. A wrong title, stale terms message, or unapproved external link can confuse users or move them outside approved support and compliance paths. + +{% include media.html src="admin-settings/general.png" alt="Screenshot of the Appearance group in Admin Settings." title="Appearance settings" %} + +{% include media.html type="video" title="Appearance settings walkthrough" poster="video-posters/admin-appearance.png" capture="Recording planned. Walk through each tab in the Appearance group and explain when to change each setting." %} + +## Before you change anything + +- Have approved logo assets, landing-page copy, and notice text ready. +- Review legal wording for Terms of Use and user agreements before publishing. +- Approve external destinations before making them visible in navigation. + +## Branding {#branding} + +### Branding {#branding-section} + +The Branding section belongs to the Branding tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Home Page Text {#home-page-text-section} + +The Home Page Text section belongs to the Branding tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Appearance {#appearance-section} + +The Appearance section belongs to the Branding tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Application Title | Provides displayed text that users see in the affected interface. | Simple Chat | `app_title` | +| Show Logo | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `show_logo` | +| Hide Application Title | Provides displayed text that users see in the affected interface. | Off | `hide_app_title` | +| Main Page Logo Size | Defines behavior for the related admin workflow; verify the affected feature after saving. | 100 | `landing_page_logo_scale_percent` | +| Markdown Alignment | Choose how the landing page markdown is aligned on the home page. | left | `landing_page_alignment` | +| Enable Markdown Editor | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_landing_page_editor`; capability toggle | +| Landing Page Text | Provides displayed text that users see in the affected interface. | You can add text here and it supports Markdown. | `landing_page_text` | +| Enable Dark Mode by Default | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_dark_mode_default`; capability toggle | +| Enable Left Nav by Default | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_left_nav_default`; capability toggle | + +## Notices & Agreements {#notices} + +### Classification Banner {#classification-banner-section} + +The Classification Banner section belongs to the Notices & Agreements tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Chat AI Notice {#ai-notice-section} + +The Chat AI Notice section belongs to the Notices & Agreements tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Terms of Use {#terms-of-use-section} + +The Terms of Use section belongs to the Notices & Agreements tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### User Agreement {#user-agreement-section} + +The User Agreement section belongs to the Notices & Agreements tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Classification Banner | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `classification_banner_enabled` | +| Banner Text | Provides displayed text that users see in the affected interface. | Empty | `classification_banner_text` | +| Banner Color | Defines behavior for the related admin workflow; verify the affected feature after saving. | #000000 | `classification_banner_color` | +| Banner Text Color | Provides displayed text that users see in the affected interface. | #ffffff | `classification_banner_text_color` | +| Show a custom AI notice below the chat input | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_ai_notice`; capability toggle | +| Notice Text | Plain text only. Line breaks are preserved. | Empty | `ai_notice_message` | +| Display Behavior | Changing the notice text or display behavior creates a new message version and shows it again. | non_dismissible | `ai_notice_frequency` | +| Require terms of use | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_terms_of_use`; capability toggle | +| Popup Title | Provides displayed text that users see in the affected interface. | Terms of Use | `terms_of_use_title` | +| Show Frequency | Changing the title, message, or frequency creates a new terms version that users must accept again. | once | `terms_of_use_frequency` | +| Terms of Use Message | Plain text is shown to users with line breaks preserved. | Empty | `terms_of_use_message` | +| Cancel Redirect URL | Use a local path such as / or an admin-approved HTTP(S) URL. Signed-in users are locally logged out before this redirect. | / | `terms_of_use_decline_redirect_url` | +| Accept Button Text | Provides displayed text that users see in the affected interface. | Accept and continue | `terms_of_use_accept_button_text` | +| Cancel Button Text | Provides displayed text that users see in the affected interface. | Cancel | `terms_of_use_decline_button_text` | +| Enable User Agreement | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_user_agreement`; capability toggle | +| Personal Workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `user_agreement_apply_personal` | +| Group Workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `user_agreement_apply_group` | +| Public Workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `user_agreement_apply_public` | +| Chat | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `user_agreement_apply_chat` | +| Agreement Text * (Markdown supported) | Provides displayed text that users see in the affected interface. | Empty | `user_agreement_text` | +| Allow users to accept once per day | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_user_agreement_daily`; capability toggle | + +## Pages & Links {#custom-pages} + +### Static Pages {#custom-pages-section} + +The Static Pages section belongs to the Pages & Links tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### External Links {#external-links-section} + +The External Links section belongs to the Pages & Links tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Custom Pages | Makes published custom pages available in SimpleChat navigation. | Off | `enable_custom_pages`; capability toggle | +| Menu Name | This name appears when custom pages are grouped into a menu. | Custom Pages | `custom_pages_menu_name` | +| Force Menu Display | When disabled, 1-2 custom pages show as top-level nav items and 3+ pages show as a menu. | Off | `custom_pages_force_menu` | +| Enable External Links in Navigation | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_external_links`; capability toggle | +| Menu Name | This name will appear in the navigation bar as the menu title. | External Links | `external_links_menu_name` | +| Force Menu Display | When enabled, external links will always display as a dropdown menu. When disabled, 1-2 links show as top-level nav items, 3+ links show as a dropdown menu. | Off | `external_links_force_menu` | + +## Common tasks + +1. **Publish tenant identity.** Update Branding and Home Page Text, then verify the landing page in light and dark mode. Outcome to verify: Users see the approved name, logo, and message. +2. **Require terms acknowledgement.** Configure the Terms of Use prompt and test with a new sign-in. Outcome to verify: The user is prompted at the configured frequency. +3. **Publish managed navigation.** Enable static pages or external links, add an approved item, and reload navigation. Outcome to verify: Only approved pages and links appear. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A notice keeps appearing | The notice or terms content changed and created a new version. | Confirm the frequency and communicate the refreshed acknowledgement. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Chat settings]({{ '/admin/chat/' | relative_url }}) +- [Help settings]({{ '/admin/help/' | relative_url }}) diff --git a/docs/admin/backup-recovery.md b/docs/admin/backup-recovery.md new file mode 100644 index 000000000..3513b33c8 --- /dev/null +++ b/docs/admin/backup-recovery.md @@ -0,0 +1,200 @@ +--- +layout: page +title: "Backup & Recovery settings" +description: "Backup & Recovery contains backup readiness, scheduled backups, migration, restore, backup inventory, job history, and Cosmos JSON repair tools." +section: "Administration" +audience: admin +admin_tab: backup-recovery +redirect_from: + - /admin/data-management/ +--- + + +# Backup & Recovery settings + +## What this group controls + +Backup & Recovery contains backup readiness, scheduled backups, migration, restore, backup inventory, job history, and Cosmos JSON repair tools. + +## Why it matters + +These settings protect the data plane during maintenance and incidents. A backup configuration is useful only when storage, encryption, restore policy, and job visibility are tested before an emergency. + +{% include media.html src="admin/backup-recovery-overview.png" alt="Screenshot placeholder for the Backup & Recovery group in Admin Settings." title="Backup & Recovery settings" capture="Capture the Backup & Recovery group in Admin Settings showing its tabs." %} + +{% include media.html type="video" title="Backup & Recovery settings walkthrough" poster="video-posters/admin-backup-recovery.png" capture="Recording planned. Walk through each tab in the Backup & Recovery group and explain when to change each setting." %} + +## Before you change anything + +- Prepare storage and encryption values before scheduling backups. +- Review restore collision policy with data owners. +- Limit Cosmos editor use to operators who understand partition keys and ETags. + +## Backup {#backup} + +### Start Here {#data-management-readiness-section} + +The Start Here section belongs to the Backup tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Backup {#data-management-backup-section} + +The Backup section belongs to the Backup tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Schedule {#data-management-schedule-section} + +The Schedule section belongs to the Backup tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Storage {#data-management-storage-section} + +The Storage section belongs to the Backup tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Encryption {#data-management-encryption-section} + +The Encryption section belongs to the Backup tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable scheduled backups | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `data_management_enabled` | +| Full backup frequency | Defines behavior for the related admin workflow; verify the affected feature after saving. | weekly | `data_management_full_frequency` | +| Scheduled time (UTC) | Default is 03:00 UTC. | 03:00 | `data_management_scheduled_time_utc` | +| Delete backups after | Defines behavior for the related admin workflow; verify the affected feature after saving. | 30 | `data_management_retention_value` | +| Retention unit | Defines behavior for the related admin workflow; verify the affected feature after saving. | days | `data_management_retention_unit` | +| Run partial backups daily between full backups | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `data_management_partial_enabled` | +| Include Cosmos DB data | Core application records required for meaningful restore and migration. | On | `data_management_include_cosmos` | +| Include AI Search indexes | Search index schemas and retrievable indexed documents. | On | `data_management_include_ai_search` | +| Include source document blobs | Original source files used by Enhanced Citations. | Off | `data_management_include_source_blobs` | +| Authentication | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | managed_identity | `data_management_storage_auth` | +| Blob endpoint | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `data_management_blob_endpoint` | +| Container | Provides displayed text that users see in the affected interface. | simplechat-backups | `data_management_container_name` | +| Connection string | No connection string saved yet. | Empty | `data_management_connection_string` | +| Blob prefix | Defines behavior for the related admin workflow; verify the affected feature after saving. | simplechat-backups | `data_management_path_prefix` | +| Encrypt backup artifacts | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `data_management_encryption_enabled` | +| Concurrent blob transfers | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4 | `data_management_backup_blob_max_parallel_operations` | +| Transfer chunk size (MiB) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 8 | `data_management_backup_blob_chunk_size_mib` | +| Blob retry attempts | Defines behavior for the related admin workflow; verify the affected feature after saving. | 5 | `data_management_backup_blob_retry_count` | +| Concurrent batch staging | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4 | `data_management_backup_max_parallel_operations` | +| Retry attempts | Defines behavior for the related admin workflow; verify the affected feature after saving. | 5 | `data_management_backup_retry_count` | +| If source RU Boost is unavailable | Defines behavior for the related admin workflow; verify the affected feature after saving. | continue_without_boost | `data_management_backup_capacity_failure_policy` | +| Enable source RU Boost for backups | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `data_management_backup_temporary_source_ru_enabled` | +| Source RU Boost target | Defines behavior for the related admin workflow; verify the affected feature after saving. | 10000 | `data_management_backup_temporary_source_ru` | +| Authentication | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | managed_identity | `data_management_target_cosmos_auth` | +| Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `data_management_target_cosmos_endpoint` | +| Database | Fixed app contract. | Not specified in defaults | `data_management_target_cosmos_database` | +| Account key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `data_management_target_cosmos_key` | +| Authentication | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | managed_identity | `data_management_target_ai_search_auth` | +| Endpoint | Provides the endpoint or route SimpleChat uses for this service. | N/A (runtime control) | `data_management_target_ai_search_endpoint` | +| Admin key | Provides the secret credential used when the selected authentication mode requires one. | N/A (runtime control) | `data_management_target_ai_search_key` | +| Authentication | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | managed_identity | `data_management_target_ec_storage_auth` | +| Blob endpoint | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `data_management_target_ec_blob_endpoint` | +| Connection string | Provides the secret credential used when the selected authentication mode requires one. | Empty | `data_management_target_ec_connection_string` | +| Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_target_cosmos_subscription_id` | +| Resource group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_target_cosmos_resource_group` | +| Status | Narrows the admin list shown for status. | N/A (runtime control) | `data_management_backup_status_filter` | +| Run type | Narrows the admin list shown for run type. | N/A (runtime control) | `data_management_backup_scheduled_filter` | +| Created from | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_backup_created_from` | +| Created through | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_backup_created_to` | +| Rows per page | Defines a capacity or timing boundary that keeps the feature inside supported limits. | Not specified in defaults | `data_management_backup_page_size` | + +## Migrate {#migrate} + +### Migration {#data-management-migration-section} + +The Migration section belongs to the Migrate tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| None Skip users | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_users_mode_choice` | +| Search users | Narrows the admin list shown for search users. | N/A (runtime control) | `data_management_migration_users_search` | +| Include users' documents | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_users_documents` | +| None Skip groups | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_groups_mode_choice` | +| Search groups | Narrows the admin list shown for search groups. | N/A (runtime control) | `data_management_migration_groups_search` | +| Include group documents | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_groups_documents` | +| None Skip public workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_public_workspaces_mode_choice` | +| Search public workspaces | Narrows the admin list shown for search public workspaces. | N/A (runtime control) | `data_management_migration_public_workspaces_search` | +| Include public workspace documents | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_public_workspaces_documents` | +| Copy missing items only | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_migration_mode` | +| Previous completed migration job ID | Leave blank to let SimpleChat choose the latest compatible completed migration as the starting point for this catch-up run. | Not specified in defaults | `data_management_migration_baseline_job_id` | +| Migrate matching AI Search documents | Narrows the admin list shown for migrate matching ai search documents. | N/A (runtime control) | `data_management_migration_include_ai_search` | +| I confirm external destination AI Search writers are frozen | SimpleChat pauses its own target indexing. Freeze other writers before review. | N/A (runtime control) | `data_management_migration_target_search_writes_frozen` | +| Migrate selected source document blobs | Requires Enhanced Citation storage at both source and destination. | Not specified in defaults | `data_management_migration_include_source_blobs` | +| Concurrent operations | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 8 | `data_management_migration_max_parallel_operations` | +| Retry attempts | Defines behavior for the related admin workflow; verify the affected feature after saving. | 5 | `data_management_migration_retry_count` | +| Skip prior successes within hours | Defines behavior for the related admin workflow; verify the affected feature after saving. | Setting | `data_management_migration_skip_recent_within_hours` | +| Enable destination RU Boost for this migration | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `data_management_migration_temporary_destination_ru_enabled` | +| Destination RU Boost target | Defines behavior for the related admin workflow; verify the affected feature after saving. | 10000 | `data_management_migration_temporary_destination_ru` | +| Type MAKE DESTINATION MATCH SOURCE to authorize this run | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | `data_management_migration_mirror_confirmation_phrase` | +| I reviewed the normalized plan, destination checks, warnings, and operational impact. | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | `data_management_migration_final_confirmation` | + +## Restore {#restore} + +### Backup Inventory & Restore {#data-management-backup-inventory-section} + +The Backup Inventory & Restore section belongs to the Restore tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Collision policy | Create-only is non-destructive and recommended for first restore attempts. | create_only | `data_management_restore_policy` | +| Cosmos DB | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_restore_include_cosmos` | +| AI Search | Narrows the admin list shown for ai search. | N/A (runtime control) | `data_management_restore_include_ai_search` | +| Enhanced Citation blobs | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_restore_include_source_blobs` | +| Type the overwrite confirmation phrase | Required phrase: RESTORE WITH OVERWRITE | N/A (runtime control) | `data_management_restore_overwrite_confirmation_phrase` | +| I reviewed the restore target, policy, and preflight result. | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | `data_management_restore_final_confirmation` | + +## Cosmos Editor {#cosmos-editor} + +### Cosmos Editor {#data-management-cosmos-editor-section} + +The Cosmos Editor section belongs to the Cosmos Editor tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Container | Choose a known SimpleChat Cosmos DB container. | Not specified in defaults | `data_management_cosmos_editor_container` | +| Page size | Max 100 per request. | Not specified in defaults | `data_management_cosmos_editor_page_size` | +| SELECT query | Empty query returns only the first 100 documents. Custom SELECT queries can page beyond 100 with Next Page. | Not specified in defaults | `data_management_cosmos_editor_query` | +| I understand this editor can damage overall system health. | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_cosmos_editor_danger_accept` | +| Data Management Cosmos Editor Document Json | The editor blocks id and partition key changes. Saves use the ETag from the loaded document. | Not specified in defaults | `data_management_cosmos_editor_document_json` | +| Type the confirmation phrase to enable saving | Required phrase: I understand this can damage system data | N/A (runtime control) | `data_management_cosmos_editor_confirmation_phrase` | + +## Jobs {#jobs} + +### Jobs {#data-management-jobs-section} + +The Jobs section belongs to the Jobs tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Use low impact mode for scheduled jobs | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `data_management_low_impact_mode` | +| Operation | Narrows the admin list shown for operation. | N/A (runtime control) | `data_management_job_operation_filter` | +| Status | Narrows the admin list shown for status. | N/A (runtime control) | `data_management_job_status_filter` | +| Run type | Narrows the admin list shown for run type. | N/A (runtime control) | `data_management_job_scheduled_filter` | +| Created from | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_job_created_from` | +| Created through | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `data_management_job_created_to` | +| Rows per page | Defines a capacity or timing boundary that keeps the feature inside supported limits. | Not specified in defaults | `data_management_job_page_size` | + +## Common tasks + +1. **Prepare scheduled backups.** Configure backup, schedule, storage, and encryption, then queue a small backup. Outcome to verify: A backup artifact appears in inventory. +2. **Run restore preflight.** Select a backup, choose restore policy, and run review before queueing. Outcome to verify: Preflight reports target access and collision behavior. +3. **Use Cosmos Editor safely.** Query the target document and review the change summary before saving. Outcome to verify: The intended document updates with ETag protection. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A restore cannot be queued | Preflight found target access, manifest, or collision-policy problems. | Resolve the reported check before queueing. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Scale settings]({{ '/admin/scale/' | relative_url }}) +- [Data Lifecycle settings]({{ '/admin/data-lifecycle/' | relative_url }}) diff --git a/docs/admin/chat.md b/docs/admin/chat.md new file mode 100644 index 000000000..181318b41 --- /dev/null +++ b/docs/admin/chat.md @@ -0,0 +1,132 @@ +--- +layout: page +title: "Chat settings" +description: "Chat controls the conversation surface: file uploads, processing indicators, drawers, history, feedback, notifications, citations, and the default prompt." +section: "Administration" +audience: admin +admin_tab: chat +redirect_from: + - /admin/citation/ +--- + + +# Chat settings + +## What this group controls + +Chat controls the conversation surface: file uploads, processing indicators, drawers, history, feedback, notifications, citations, and the default prompt. + +## Why it matters + +These choices affect how users inspect answers and preserve context. Citations and feedback improve reviewability, while upload and history limits decide how much evidence can enter a conversation directly. + +{% include media.html src="admin-settings/citation.png" alt="Screenshot of the Chat group in Admin Settings." title="Chat settings" %} + +{% include media.html type="video" title="Chat settings walkthrough" poster="video-posters/admin-chat.png" capture="Recording planned. Walk through each tab in the Chat group and explain when to change each setting." %} + +## Before you change anything + +- Decide whether chat uploads are allowed outside governed workspace intake. +- Confirm enhanced citation storage before enabling large citation features. +- Prepare a feedback review process before exposing thumbs controls. + +## Chat Experience {#chat-experience} + +### Processing Thoughts {#processing-thoughts-section} + +The Processing Thoughts section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Chat File Uploads {#chat-file-uploads-section} + +The Chat File Uploads section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Conversation Contents Drawer {#conversation-contents-drawer-section} + +The Conversation Contents Drawer section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Workspace Scope Lock {#workspace-scope-lock-section} + +The Workspace Scope Lock section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Conversation History {#conversation-history-section} + +The Conversation History section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Default System Prompt {#default-system-prompt-section} + +The Default System Prompt section belongs to the Chat Experience tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Processing Thoughts | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_thoughts`; capability toggle | +| Conversation History Limit | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `conversation_history_limit` | +| Default System Prompt | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `default_system_prompt` | +| Enable Chat File Uploads | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_chat_file_uploads`; capability toggle | +| Enable Conversation Contents Drawer | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_conversation_contents_drawer`; capability toggle | +| Collaborative conversations | Enables shared conversation records and collaboration endpoints so permitted users can create and participate in collaborative conversations instead of only single-user conversation threads. | On | `enable_collaborative_conversations`; no visible field in `admin_settings.html` | +| Require ChatFileUploadUser App Role | Requires the `ChatFileUploadUser` app role before users can use this capability or view. | Off | `require_member_of_chat_file_upload_user` | +| Enforce Workspace Scope Lock | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `enforce_workspace_scope_lock` | + +## Feedback & Alerts {#feedback-alerts} + +### User Feedback {#user-feedback-section} + +The User Feedback section belongs to the Feedback & Alerts tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Desktop Conversation Notifications {#desktop-notifications-section} + +The Desktop Conversation Notifications section belongs to the Feedback & Alerts tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable User Feedback (Thumbs Up/Down) | Shows thumbs up/down feedback controls on AI responses so users can submit response-level feedback for review. | On | `enable_user_feedback`; capability toggle | +| Enable Desktop Conversation Notifications | Allows browser desktop notifications for conversation events when the user grants browser permission. | Off | `enable_desktop_notifications`; capability toggle | +| Require FeedbackAdmin App Role | Requires the `FeedbackAdmin` app role before users can use this capability or view. | Off | `require_member_of_feedback_admin` | + +## Citations {#citation} + +### Standard {#standard-citations-section} + +The Standard section belongs to the Citations tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Enhanced {#enhanced-citations-section} + +The Enhanced section belongs to the Citations tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Enhanced Citations | Preserves source files for richer citation preview and source-document access in chat answers. | Off | `enable_enhanced_citations`; capability toggle | +| Enhanced Citations mount path | Enables use of the configured Enhanced Citations mount path for document-view routing when Enhanced Citations is on; the saved value is forced off unless Enhanced Citations is enabled. | Off | `enable_enhanced_citations_mount`; no visible field in `admin_settings.html` | +| Storage Account Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `office_docs_authentication_type` | +| Storage Account Connection String | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `office_docs_storage_account_url` | +| Storage Account Blob Service Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `office_docs_storage_account_blob_endpoint` | +| Maximum File Size for Tabular Preview (MB) | Maximum blob size (in MB) allowed for tabular file previews (CSV, XLSX). Files larger than this will not be previewed. Increase for larger files if your compute has sufficient memory, or decrease to protect smaller insta | 200 | `tabular_preview_max_blob_size_mb` | +| Confirm very large row-level runs before starting | When a prompt includes an explicit large row count, users are asked to continue or narrow scope before the run starts. | On | `enable_tabular_durable_run_confirmation`; capability toggle | +| Confirmation Row Threshold | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 500 | `tabular_durable_run_confirmation_threshold_rows` | +| Confirmation Batch Threshold | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 75 | `tabular_durable_run_confirmation_threshold_batches` | +| Chunk Processing Model | Selects the deployment SimpleChat sends requests to for this capability. | current | `tabular_generated_output_chunk_model_mode` | +| Configured Chunk Model Deployment | Selects the deployment SimpleChat sends requests to for this capability. | Empty | `tabular_generated_output_chunk_model_deployment` | + +## Common tasks + +1. **Enable reviewable answers.** Enable citations, ask a document-grounded question, and open the cited source. Outcome to verify: Reviewers can inspect the source behind an answer. +2. **Tune conversation context.** Set history and drawer behavior, then reopen a long test conversation. Outcome to verify: Users can navigate prior turns without unexpected context loss. +3. **Collect response feedback.** Submit test feedback and check that reviewers can find it. Outcome to verify: Feedback reaches the review workflow. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Citation links do not open | Enhanced citation storage or mount settings do not match the running app. | Validate with a newly indexed document after correcting storage settings. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) +- [Appearance settings]({{ '/admin/appearance/' | relative_url }}) diff --git a/docs/admin/data-lifecycle.md b/docs/admin/data-lifecycle.md new file mode 100644 index 000000000..4246c2704 --- /dev/null +++ b/docs/admin/data-lifecycle.md @@ -0,0 +1,92 @@ +--- +layout: page +title: "Data Lifecycle settings" +description: "Data Lifecycle groups retention, classification, and conversation archiving decisions." +section: "Administration" +audience: admin +admin_tab: data-lifecycle +--- + + +# Data Lifecycle settings + +## What this group controls + +Data Lifecycle groups retention, classification, and conversation archiving decisions. + +## Why it matters + +Lifecycle choices shape compliance and recovery expectations. Retention and archiving preserve evidence, while classification helps users and reviewers understand document sensitivity. + +{% include media.html src="admin/data-lifecycle-overview.png" alt="Screenshot placeholder for the Data Lifecycle group in Admin Settings." title="Data Lifecycle settings" capture="Capture the Data Lifecycle group in Admin Settings showing its tabs." %} + +{% include media.html type="video" title="Data Lifecycle settings walkthrough" poster="video-posters/admin-data-lifecycle.png" capture="Recording planned. Walk through each tab in the Data Lifecycle group and explain when to change each setting." %} + +## Before you change anything + +- Confirm retention requirements for every workspace scope. +- Agree on classification labels before enabling classification. +- Decide whether deleted conversations should be recoverable. + +## Retention {#retention} + +### Retention Policy {#retention-policy-section} + +The Retention Policy section belongs to the Retention tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable for Personal Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_retention_policy_personal`; capability toggle | +| Enable for Group Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_retention_policy_group`; capability toggle | +| Enable for Public Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_retention_policy_public`; capability toggle | +| Conversation Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_conversation_personal` | +| Document Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_document_personal` | +| Conversation Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_conversation_group` | +| Document Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_document_group` | +| Conversation Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_conversation_public` | +| Document Retention | Defines behavior for the related admin workflow; verify the affected feature after saving. | none | `default_retention_document_public` | +| Scheduled Execution Time (Hour of Day) | Retention policy will run once daily at this hour (UTC timezone). | 2 | `retention_policy_execution_hour` | + +## Classification {#classification} + +### Document Classification {#document-classification-section} + +The Document Classification section belongs to the Classification tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Document Classification | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_document_classification`; capability toggle | + +## Archiving {#archiving} + +### Conversation Archiving {#conversation-archiving-section} + +The Conversation Archiving section belongs to the Archiving tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Conversation Archiving | Changes conversation deletion behavior to archive conversations instead of permanently deleting them immediately. | Off | `enable_conversation_archiving`; capability toggle | + +## Common tasks + +1. **Apply retention policy.** Enable retention for intended scopes and check a test item after the policy job runs. Outcome to verify: Items follow the configured retention window. +2. **Classify documents.** Enable classification and upload a representative file. Outcome to verify: The document carries expected classification metadata. +3. **Archive conversations.** Enable archiving and delete a test conversation. Outcome to verify: Deletion follows the archive path. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Deleted conversations disappear permanently | Archiving was not enabled when deletion occurred. | Enable archiving before relying on recovery for future deletions. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Backup & Recovery settings]({{ '/admin/backup-recovery/' | relative_url }}) +- [Governance settings]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/admin/governance.md b/docs/admin/governance.md new file mode 100644 index 000000000..e7ac889e3 --- /dev/null +++ b/docs/admin/governance.md @@ -0,0 +1,98 @@ +--- +layout: page +title: "Governance settings" +description: "Governance controls review policy for personal, group, and global endpoints, agents, actions, and MCP destinations." +section: "Administration" +audience: admin +admin_tab: governance +--- + + +# Governance settings + +## What this group controls + +Governance controls review policy for personal, group, and global endpoints, agents, actions, and MCP destinations. + +## Why it matters + +Governance decides how quickly new tools become usable and how much review happens first. Stronger policy slows rollout but prevents unreviewed endpoints and destinations from becoming dependencies. + +{% include media.html src="admin/governance-overview.png" alt="Screenshot placeholder for the Governance group in Admin Settings." title="Governance settings" capture="Capture the Governance group in Admin Settings showing its tabs." %} + +{% include media.html type="video" title="Governance settings walkthrough" poster="video-posters/admin-governance.png" capture="Recording planned. Walk through each tab in the Governance group and explain when to change each setting." %} + +## Before you change anything + +- Choose which item types require review for each scope. +- Define MCP destination and source policy before enforcing it. +- Tell workspace owners how rejected items are remediated. + +## Feature Governance {#feature-governance} + +### Governance Feature Toggles {#governance-feature-toggles-section} + +The Governance Feature Toggles section belongs to the Feature Governance tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +No retired-page setting rows mapped directly to Feature Governance; use the live Admin Settings UI for the current rollout switches. + +## Policies {#governance-policies} + +### Feature Policies {#governance-feature-policies-section} + +The Feature Policies section belongs to the Policies tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Delegated Item Policies {#governance-item-policies-section} + +The Delegated Item Policies section belongs to the Policies tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Govern Personal Endpoints | Requires governance review for personal endpoints before those items are used broadly. | Off | `governance_user_endpoints` | +| Govern Personal Agents | Requires governance review for personal agents before those items are used broadly. | Off | `governance_user_agents` | +| Govern Personal Actions | Requires governance review for personal actions before those items are used broadly. | Off | `governance_user_actions` | +| Govern Group Endpoints | Requires governance review for group endpoints before those items are used broadly. | Off | `governance_group_endpoints` | +| Govern Group Agents | Requires governance review for group agents before those items are used broadly. | Off | `governance_group_agents` | +| Govern Group Actions | Requires governance review for group actions before those items are used broadly. | Off | `governance_group_actions` | +| Govern Global Endpoints | Requires governance review for global endpoints before those items are used broadly. | On | `governance_global_endpoints` | +| Govern Global Agents | Requires governance review for global agents before those items are used broadly. | Off | `governance_global_agents_usage` | +| Govern Global Actions | Requires governance review for global actions before those items are used broadly. | Off | `governance_global_actions_usage` | +| Search | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Entity Type | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Page Size | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | + +## MCP Governance {#mcp-governance} + +### MCP Action Destination Governance {#governance-mcp-destination-section} + +The MCP Action Destination Governance section belongs to the MCP Governance tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Inbound MCP Source Governance {#governance-inbound-mcp-section} + +The Inbound MCP Source Governance section belongs to the MCP Governance tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enforce MCP Destination Allowlist | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_mcp_destination_governance`; capability toggle | +| Block Private/Local Literal IP Destinations | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `mcp_block_unsafe_destinations` | + +## Common tasks + +1. **Require review for a surface.** Enable the relevant governance toggle and create or edit a test item. Outcome to verify: The item requires administrative review. +2. **Enforce MCP destinations.** Enable destination governance and test one allowed and one blocked target. Outcome to verify: MCP actions reach only approved destinations. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| An MCP call is blocked | Destination governance or private-address blocking rejected the target. | Approve the destination or keep it blocked by policy. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Data Lifecycle settings]({{ '/admin/data-lifecycle/' | relative_url }}) +- [Security settings]({{ '/admin/security/' | relative_url }}) diff --git a/docs/admin/help.md b/docs/admin/help.md new file mode 100644 index 000000000..4d4eac87f --- /dev/null +++ b/docs/admin/help.md @@ -0,0 +1,113 @@ +--- +layout: page +title: "Help settings" +description: "Help controls end-user support navigation, Send Feedback destinations, and Latest Features cards." +section: "Administration" +audience: admin +admin_tab: help +redirect_from: + - /admin/send-feedback/ + - /admin/latest-features/ +--- + + +# Help settings + +## What this group controls + +Help controls end-user support navigation, Send Feedback destinations, and Latest Features cards. + +## Why it matters + +These settings give users a sanctioned place to ask for help and learn what changed. Keep addresses, labels, and feature visibility accurate so requests reach owners. + +{% include media.html src="admin-settings/send-feedback.png" alt="Screenshot of the Help group in Admin Settings." title="Help settings" %} + +{% include media.html type="video" title="Help settings walkthrough" poster="video-posters/admin-help.png" capture="Recording planned. Walk through each tab in the Help group and explain when to change each setting." %} + +## Before you change anything + +- Choose the internal mailbox or process for feedback. +- Review which release cards should be visible. +- Confirm Support menu naming with service owners. + +## Support Menu {#support-menu} + +### Support {#support-menu-section} + +The Support section belongs to the Support Menu tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Support Menu for End Users | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_support_menu`; capability toggle | +| Menu Name | This name will appear in user navigation as the Support menu title. | Support | `support_menu_name` | +| Enable Send Feedback Destination | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_support_send_feedback`; capability toggle | +| Support Recipient Email | User Send Feedback drafts will be addressed to this internal email address. | Empty | `support_feedback_recipient_email` | +| Enable Latest Features Destination | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_support_latest_features`; capability toggle | +| Show Simple Chat Documentation Guide Links | When enabled, user-facing Latest Features cards can show public guide buttons in addition to the direct in-app shortcuts. | Off | `enable_support_latest_feature_documentation_links`; capability toggle | + +## Send Feedback {#send-feedback} + +### Overview {#send-feedback-overview-card} + +The Overview section belongs to the Send Feedback tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Report a Bug {#send-feedback-bug-card} + +The Report a Bug section belongs to the Send Feedback tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Request a Feature {#send-feedback-feature-card} + +The Request a Feature section belongs to the Send Feedback tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Name | Provides displayed text that users see in the affected interface. | Not specified in defaults | `send_feedback_bug_name` | +| Email | Supplies the email address used for email. | Not specified in defaults | `send_feedback_bug_email` | +| Organization | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `send_feedback_bug_org` | +| Bug Details | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `send_feedback_bug_details` | +| Name | Provides displayed text that users see in the affected interface. | Not specified in defaults | `send_feedback_feature_name` | +| Email | Supplies the email address used for email. | Not specified in defaults | `send_feedback_feature_email` | +| Organization | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `send_feedback_feature_org` | +| Feature Request Details | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `send_feedback_feature_details` | + +## Latest Features {#latest-features} + +Latest Features is rendered from the release catalog and does not declare static settings sections in `admin_settings_nav.py`. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Enhanced Citations | Preserves source files for richer citation preview and source-document access in chat answers. | Off | Mirrors `enable_enhanced_citations` | +| Storage Account Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | Mirrors `office_docs_authentication_type` | +| Storage Account Connection String | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | Mirrors `office_docs_storage_account_url` | +| Storage Account Blob Service Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | Mirrors `office_docs_storage_account_blob_endpoint` | +| Maximum File Size for Tabular Preview (MB) | Mirror of the Citations setting. Larger values support bigger previews but increase runtime memory pressure. | 200 | Mirrors `tabular_preview_max_blob_size_mb` | +| Enable Processing Thoughts | Exposes the capability after required services, permissions, and rollout policy are ready. | On | Mirrors `enable_thoughts` | +| Enable Redis Cache | Uses Redis for shared cache/session scenarios so multiple app instances can share cached state. | Off | Mirrors `enable_redis_cache` | +| Redis Server Host Name | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | Mirrors `redis_url` | +| Redis Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | Empty | Mirrors `redis_auth_type` | +| Key Vault Secret Name Redis Access Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | Mirrors `redis_key` | + +## Common tasks + +1. **Publish support navigation.** Enable the Support menu and approved destinations, then open the app as a non-admin user. Outcome to verify: The menu appears with only approved destinations. +2. **Route feedback.** Set the recipient email and submit a test feedback draft. Outcome to verify: The draft is addressed correctly. +3. **Review latest cards.** Hide or show entries according to rollout state. Outcome to verify: Users see relevant announcements. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Feedback opens with no recipient | The destination is enabled but recipient email is blank. | Set the recipient and submit another test draft. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Appearance settings]({{ '/admin/appearance/' | relative_url }}) +- [Operations settings]({{ '/admin/operations/' | relative_url }}) diff --git a/docs/admin/index.md b/docs/admin/index.md new file mode 100644 index 000000000..2c69f5cf3 --- /dev/null +++ b/docs/admin/index.md @@ -0,0 +1,35 @@ +--- +layout: page +title: "Administration" +description: "Admin settings guide organized by the grouped SimpleChat Admin Settings information architecture." +section: "Administration" +audience: admin +admin_tab: index +--- + +## Administration settings guide + +Use these pages when configuring SimpleChat from **Admin Settings**. The application now uses **group → tab → section** navigation, so each page below covers one group and each tab has a stable heading anchor. + +## Start here + +For a first-time deployment, configure **Appearance**, **AI Models**, **Knowledge**, **Workspaces**, **Chat**, **Security**, and **Agents & Actions** first. Then review **Scale**, **Operations**, **Governance**, **Data Lifecycle**, **Backup & Recovery**, **Workflow**, and **Help** as the deployment moves into operations. + +## Settings groups + +| Group | What it controls | Tabs | Link | +| --- | --- | --- | --- | +| Appearance | Appearance controls the public-facing identity of SimpleChat: branding, landing copy, banners, user notices, terms, custom pages, and external navigation links. | `branding`, `notices`, `custom-pages` | [Appearance settings]({{ '/admin/appearance/' | relative_url }}) | +| Chat | Chat controls the conversation surface: file uploads, processing indicators, drawers, history, feedback, notifications, citations, and the default prompt. | `chat-experience`, `feedback-alerts`, `citation` | [Chat settings]({{ '/admin/chat/' | relative_url }}) | +| AI Models | AI Models configures chat, embedding, image generation, APIM, multi-endpoint routing, and model endpoint identity behavior. | `model-endpoints`, `embeddings`, `image-generation` | [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) | +| Agents & Actions | Agents & Actions controls the Semantic Kernel runtime, agent marketplace presentation, document actions, built-in actions, workspace permissions, orchestration, and inbound MCP. | `agents`, `actions`, `inbound-mcp` | [Agents & Actions settings]({{ '/admin/agents-actions/' | relative_url }}) | +| Workspaces | Workspaces controls personal, group, and public workspace availability, file downloads, sharing policy, file-size limits, and global identities. | `workspace-types`, `files-sharing`, `workspace-identities` | [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) | +| Workflow | Workflow centralizes task sequence, assignment, and approval behavior used across SimpleChat surfaces. | `workflow` | [Workflow settings]({{ '/admin/workflow/' | relative_url }}) | +| Knowledge | Knowledge controls web research, URL access, Azure AI Search, extraction, chunking, multimodal processing, audio/video processing, and file sync. | `web-research`, `search-index`, `extraction`, `audio-video`, `file-sync` | [Knowledge settings]({{ '/admin/knowledge/' | relative_url }}) | +| Security | Security covers access roles, Key Vault integration, Content Safety, idle session behavior, and Front Door-aware network URLs. | `access-roles`, `secrets`, `content-safety`, `session`, `network` | [Security settings]({{ '/admin/security/' | relative_url }}) | +| Governance | Governance controls review policy for personal, group, and global endpoints, agents, actions, and MCP destinations. | `feature-governance`, `governance-policies`, `mcp-governance` | [Governance settings]({{ '/admin/governance/' | relative_url }}) | +| Data Lifecycle | Data Lifecycle groups retention, classification, and conversation archiving decisions. | `retention`, `classification`, `archiving` | [Data Lifecycle settings]({{ '/admin/data-lifecycle/' | relative_url }}) | +| Backup & Recovery | Backup & Recovery contains backup readiness, scheduled backups, migration, restore, backup inventory, job history, and Cosmos JSON repair tools. | `backup`, `migrate`, `restore`, `cosmos-editor`, `jobs` | [Backup & Recovery settings]({{ '/admin/backup-recovery/' | relative_url }}) | +| Scale | Scale covers Redis, conversation and search caches, document access indexing, Cosmos maintenance, and Cosmos throughput automation. | `redis-caching`, `cosmos` | [Scale settings]({{ '/admin/scale/' | relative_url }}) | +| Operations | Operations collects Control Center access, refresh behavior, Application Insights, debug logging, file-processing logs, health checks, and Swagger documentation. | `control-center-config`, `logging` | [Operations settings]({{ '/admin/operations/' | relative_url }}) | +| Help | Help controls end-user support navigation, Send Feedback destinations, and Latest Features cards. | `support-menu`, `send-feedback`, `latest-features` | [Help settings]({{ '/admin/help/' | relative_url }}) | diff --git a/docs/admin/knowledge.md b/docs/admin/knowledge.md new file mode 100644 index 000000000..a1ceb3f74 --- /dev/null +++ b/docs/admin/knowledge.md @@ -0,0 +1,284 @@ +--- +layout: page +title: "Knowledge settings" +description: "Knowledge controls web research, URL access, Azure AI Search, extraction, chunking, multimodal processing, audio/video processing, and file sync." +section: "Administration" +audience: admin +admin_tab: knowledge +redirect_from: + - /admin/file-sync/ + - /admin/search-extract/ +--- + + +# Knowledge settings + +## What this group controls + +Knowledge controls web research, URL access, Azure AI Search, extraction, chunking, multimodal processing, audio/video processing, and file sync. + +## Why it matters + +These settings decide what evidence enters the system and how it becomes searchable. Endpoint, chunking, and sync choices affect answer quality, indexing cost, and document freshness. + +{% include media.html src="admin-settings/search-extract.png" alt="Screenshot of the Knowledge group in Admin Settings." title="Knowledge settings" %} + +{% include media.html src="admin-settings/file-sync.png" alt="Screenshot of the Knowledge group in Admin Settings." title="Knowledge settings" %} + +{% include media.html type="video" title="Knowledge settings walkthrough" poster="video-posters/admin-knowledge.png" capture="Recording planned. Walk through each tab in the Knowledge group and explain when to change each setting." %} + +## Before you change anything + +- Provision Azure AI Search and Document Intelligence before enabling dependent features. +- Decide whether web, URL, and deep research access is approved. +- Define allowed sync source types and workspace scopes. + +## Web & Research {#web-research} + +### Web Search {#web-search-section} + +The Web Search section belongs to the Web & Research tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### URL Access {#url-access-section} + +The URL Access section belongs to the Web & Research tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Deep Research {#source-review-section} + +The Deep Research section belongs to the Web & Research tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Web Search via Foundry Agent | Adds web search through the configured Azure AI Foundry agent for approved chat flows. | Off | `enable_web_search`; capability toggle | +| Show data notice to users when web search is used | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_web_search_user_notice`; capability toggle | +| Notice Text | This message will be shown to users once per session when they first use web search. | N/A (runtime control) | `web_search_user_notice_text` | +| Foundry Project Endpoint | Project endpoint format: https://.services.ai.azure.com/api/projects/ (not the inference endpoint). | N/A (runtime control) | `web_search_foundry_endpoint` | +| Foundry API Version | Pins the service API version SimpleChat sends with requests for this feature. | N/A (runtime control) | `web_search_foundry_api_version` | +| Foundry Agent ID | Narrows the admin list shown for foundry agent id. | N/A (runtime control) | `web_search_foundry_agent_id` | +| Authentication Type | Identity must have Cognitive Services User and AI Developer roles on the Foundry project. | N/A (runtime control) | `web_search_foundry_auth_type` | +| Cloud | Narrows the admin list shown for cloud. | N/A (runtime control) | `web_search_foundry_cloud` | +| Managed Identity Type | Narrows the admin list shown for managed identity type. | N/A (runtime control) | `web_search_foundry_managed_identity_type` | +| Authority Endpoint (Custom Cloud) | Narrows the admin list shown for authority endpoint (custom cloud). | N/A (runtime control) | `web_search_foundry_authority` | +| Managed Identity Client ID (UAMI) | Narrows the admin list shown for managed identity client id (uami). | N/A (runtime control) | `web_search_foundry_managed_identity_client_id` | +| Tenant ID | Narrows the admin list shown for tenant id. | N/A (runtime control) | `web_search_foundry_tenant_id` | +| Client ID | Narrows the admin list shown for client id. | N/A (runtime control) | `web_search_foundry_client_id` | +| Client Secret | Provides the secret credential used when the selected authentication mode requires one. | N/A (runtime control) | `web_search_foundry_client_secret` | +| Enable URL Access for chat and workflows | Allows chat and workflows to inspect user-provided URLs within the configured URL limits and domain policy. | Off | `enable_url_access`; capability toggle | +| Require UrlAccessUser App Role | 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. | Off | `require_member_of_url_access_user` | +| Chat URL Limit | Hard limit: 100 direct URLs per chat message. | 10 | `url_access_max_chat_urls_per_turn` | +| Workflow URL Limit | Hard limit: 500 direct URLs per workflow prompt. | 50 | `url_access_max_workflow_urls_per_run` | +| Url Access Allowed Domains | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `url_access_allowed_domains` | +| Allowed Domains | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | N/A (runtime control) | `url_access_allowed_domains_new` | +| Url Access Blocked Domains | Lists the domains, users, or destinations that this feature must not use. | Empty list | `url_access_blocked_domains` | +| Blocked Domains | Lists the domains, users, or destinations that this feature must not use. | N/A (runtime control) | `url_access_blocked_domains_new` | +| Enable Deep Research for chat | Enables Deep Research in chat so SimpleChat can inspect search results and linked source pages within the configured crawl limits. | Off | `enable_source_review`; capability toggle | +| Allow internal network hostnames | Allows DNS hostnames that resolve to private/internal addresses. Literal IP URL targets, localhost, metadata hosts, link-local addresses, and reserved addresses remain blocked. | Off | `source_review_allow_internal_hosts` | +| Activation Mode | Defines behavior for the related admin workflow; verify the affected feature after saving. | manual | `source_review_default_mode` | +| Max Pages per Turn | Hard limit: 10 pages. | 10 | `source_review_max_pages_per_turn` | +| Max Seed Pages per Turn | Limits initial search-result and direct URL pages so budget remains for child pages. | 10 | `source_review_max_seed_pages_per_turn` | +| Timeout per Turn | Hard limit: 30 seconds. | 30 | `source_review_timeout_seconds` | +| Max Redirects | Every redirect target is revalidated. | 5 | `source_review_max_redirects` | +| Max MB per Page | Hard limit: 5 MB. | 5 MB | `source_review_max_bytes_per_page` converted from `source_review_max_bytes_per_page` bytes | +| Source Traversal Depth | Depth 2 follows selected links from seed and child pages. | 2 | `source_review_max_depth` | +| Inspect linked source pages | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_deep_source_review`; capability toggle | +| Use model-assisted source link planning | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `source_review_enable_llm_planning` | +| Allow JavaScript rendering fallback | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `source_review_allow_js_rendering` | +| Rendered Load More Clicks | When JavaScript rendering is enabled, Deep Research can click visible Load More controls until this cap is reached. | 12 | `source_review_js_load_more_clicks` | +| Respect robots.txt | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `source_review_respect_robots_txt` | +| Log Deep Research activity | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `source_review_audit_logging` | +| Test Prompt | Narrows the admin list shown for test prompt. | N/A (runtime control) | `web_search_test_query` | +| URL | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `url_access_policy_test_url` | + +## Search Index {#search-index} + +### Azure AI Search {#azure-ai-search-section} + +The Azure AI Search section belongs to the Search Index tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Use APIM instead of direct Azure AI Search | Routes Azure AI Search calls through APIM instead of calling the Search endpoint directly. | Off | `enable_ai_search_apim`; capability toggle | +| Search Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_ai_search_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `azure_ai_search_authentication_type` | +| Search Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_ai_search_key` | +| Azure APIM AI Search Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_ai_search_endpoint` | +| Azure APIM AI Search Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_ai_search_subscription_key` | + +## Document Extraction {#extraction} + +### Document Intelligence {#document-intelligence-section} + +The Document Intelligence section belongs to the Document Extraction tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Chunk Sizes {#chunk-size-section} + +The Chunk Sizes section belongs to the Document Extraction tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Metadata Extraction {#metadata-extraction-section} + +The Metadata Extraction section belongs to the Document Extraction tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Multi-Modal Vision Analysis {#multimodal-vision-section} + +The Multi-Modal Vision Analysis section belongs to the Document Extraction tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Require DeepResearchUser App Role | 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. | Off | `require_member_of_deep_research_user` | +| Max User URLs per Turn | Direct URLs beyond this cap are recorded as omitted in the ledger. | 100 | `deep_research_max_user_urls_per_turn` | +| Max Search Queries per Turn | Includes the original current-message query. | 8 | `deep_research_max_search_queries_per_turn` | +| Plan multiple web search queries | Narrows the admin list shown for plan multiple web search queries. | On | `deep_research_enable_query_planning` | +| Save research ledger artifacts | Narrows the admin list shown for save research ledger artifacts. | On | `deep_research_enable_ledger_artifact` | +| Enable Enhanced extraction | Enables the enhanced extraction path for richer PDF and image structure when the required services are configured. | Off | `enable_enhanced_extraction`; capability toggle | +| PDF and Image Extraction Mode | 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. | read | `document_intelligence_pdf_image_extraction_mode` | +| Auto Sample Pages | 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 mo | Not specified in defaults | `document_intelligence_auto_sample_pages` | +| Extract mathematical formulas | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_document_intelligence_formula_extraction`; capability toggle | +| Foundry Endpoint | Your Microsoft Foundry resource endpoint, without a trailing path. | Empty | `azure_content_understanding_endpoint` | +| Authentication Type | Managed identity requires the Cognitive Services User role on the Foundry resource. | key | `azure_content_understanding_authentication_type` | +| Content Understanding Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_content_understanding_key` | +| API Version | Default: | Not specified in defaults | `azure_content_understanding_api_version` | +| Document Analyzer | Default: | Not specified in defaults | `azure_content_understanding_analyzer_id` | +| Image Analyzer | Default: | Not specified in defaults | `azure_content_understanding_image_analyzer_id` | +| Analyze images embedded in DOCX and PPTX files | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_office_embedded_image_analysis`; capability toggle | +| Minimum Image Size (pixels) | Images narrower or shorter than this are skipped as icons or spacers. | Not specified in defaults | `office_embedded_image_min_pixels` | +| Maximum Images Per Document | Caps per-document cost. Duplicate images are analyzed once. | Not specified in defaults | `office_embedded_image_max_per_document` | +| Use APIM instead of direct Document Intelligence endpoint | Routes Document Intelligence calls through APIM instead of calling the service endpoint directly. | Off | `enable_document_intelligence_apim`; capability toggle | +| Document Intelligence Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_document_intelligence_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `azure_document_intelligence_authentication_type` | +| Document Intelligence Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_document_intelligence_key` | +| Azure APIM Document Intelligence Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_document_intelligence_endpoint` | +| Azure APIM Document Intelligence Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_document_intelligence_subscription_key` | +| Enable custom chunk sizes by file type | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_chunk_size_override`; capability toggle | +| Summarize history for search context | Adds a model-generated summary of recent conversation history into search context when hybrid document search is active, so follow-up searches can use prior conversational context. | Off | `enable_summarize_content_history_for_search` | +| Summarize older history beyond conversation limit | Summarizes older conversation messages that fall outside the configured conversation-history window so long chats can retain condensed context instead of dropping all older content. | Off | `enable_summarize_content_history_beyond_conversation_history_limit` | +| TXT (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 400 words | `chunk_size_txt` | +| LOG (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 1000 words | `chunk_size_log` | +| DOC (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 400 words | `chunk_size_doc` | +| DOCM (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 400 words | `chunk_size_docm` | +| DOCX (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | configured WORD_CHUNK_SIZE words | `chunk_size_docx` | +| HTML (words) | Minimum enforced at 50% of target on merge. | 1200 words | `chunk_size_html` | +| Markdown (words) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 1200 words | `chunk_size_md` | +| XML (characters) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4000 characters | `chunk_size_xml` | +| YAML (characters) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4000 characters | `chunk_size_yaml` | +| YML (characters) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4000 characters | `chunk_size_yml` | +| JSON (characters) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 4000 characters | `chunk_size_json` | +| Transcripts (words) | Applies to new audio transcripts. | 400 words | `chunk_size_transcript` | +| PDF (pages) | Pages per chunk after extraction. | 1 page | `chunk_size_pdf` | +| PPT/PPTX (slides) | Slides per chunk after extraction. | 1 slide | `chunk_size_pptx` | + +## Audio & Video {#audio-video} + +### AI Video Intelligence {#video-intelligence-section} + +The AI Video Intelligence section belongs to the Audio & Video tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### AI Voice Conversations {#ai-voice-chat-section} + +The AI Voice Conversations section belongs to the Audio & Video tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Video File Upload & Processing | Allows users to upload video files for processing through the configured Video Indexer resource. | Off | `enable_video_file_support`; capability toggle | +| Cloud / Endpoint Mode | Choose the endpoint family that matches your deployed cloud. Use Custom only when you need a non-standard Video Indexer endpoint. | Not specified in defaults | `video_indexer_cloud` | +| Custom API Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `video_indexer_custom_endpoint` | +| Effective API Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Not specified in defaults | `video_indexer_endpoint_display` | +| Resource Group * | The Azure resource group containing your Video Indexer account | Empty | `video_indexer_resource_group` | +| Subscription ID * | Your Azure subscription ID | Empty | `video_indexer_subscription_id` | +| Account Name * | The name of your Video Indexer account resource | Empty | `video_indexer_account_name` | +| Location * | Azure region where your Video Indexer account is deployed (e.g., eastus, westus2, northeurope) | Empty | `video_indexer_location` | +| Account ID * | Found in the Video Indexer account Overview page in Azure Portal | Empty | `video_indexer_account_id` | +| ARM API Version | Default for : | Not specified in defaults | `video_indexer_arm_api_version` | +| Timeout (seconds) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 600 | `video_index_timeout` | +| Enable AI Response Completion Audio Cues | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_chat_completion_audio_cues`; capability toggle | +| Enable Audio File Upload & Processing | Allows users to upload audio files for transcription through the configured Speech service. | Off | `enable_audio_file_support`; capability toggle | +| Enable Voice Input (Speech-to-Text) | Shows voice input controls in chat and sends captured speech to the configured Speech service. | Off | `enable_speech_to_text_input`; capability toggle | +| Enable Voice Responses (Text-to-Speech) | Allows voice responses from chat output through the configured Speech service. | Off | `enable_text_to_speech`; capability toggle | +| Endpoint | Use the resource-specific custom-domain endpoint when selecting Managed Identity. | Empty | `speech_service_endpoint` | +| Location | Required for speech recognition locale defaults and for text-to-speech when using Managed Identity. | Empty | `speech_service_location` | +| Speech Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `speech_service_subscription_id` | +| Speech Resource Group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `speech_service_resource_group` | +| Speech Resource Name | If you use a custom-domain Speech endpoint, this is usually the first part of that hostname. | Empty | `speech_service_resource_name` | +| Speech Resource ID | Provide Subscription ID, Resource Group, and Speech Resource Name to auto-build the ARM resource ID. | Empty | `speech_service_resource_id` | +| Locale | Defines behavior for the related admin workflow; verify the affected feature after saving. | en-US | `speech_service_locale` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `speech_service_authentication_type` | +| API Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `speech_service_key` | + +## File Sync {#file-sync} + +### File Sync {#file-sync-section} + +The File Sync section belongs to the File Sync tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Visible Source Types {#file-sync-source-types-section} + +The Visible Source Types section belongs to the File Sync tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Personal Workspace Sync {#file-sync-personal-section} + +The Personal Workspace Sync section belongs to the File Sync tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Group Workspace Sync {#file-sync-group-section} + +The Group Workspace Sync section belongs to the File Sync tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Public Workspace Sync {#file-sync-public-section} + +The Public Workspace Sync section belongs to the File Sync tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable File Sync | Enables the File Sync feature so configured external sources can import files into workspaces. | Off | `enable_file_sync`; capability toggle | +| Max Sources | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 10 | `file_sync_max_sources_per_scope` | +| Min Schedule Minutes | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 15 | `file_sync_min_schedule_interval_minutes` | +| Max Files Per Run | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 1000 | `file_sync_max_files_per_run` | +| Max GB Per Run | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 5 GB | `file_sync_max_gb_per_run` | +| Max Concurrent Runs | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 2 | `file_sync_max_concurrent_runs` | +| Allow Recursive Sources | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `file_sync_allow_recursive_sources` | +| SMB Share | Available now. | On | `file_sync_visible_source_types` | +| OneDrive | Coming Soon. | Off | `file_sync_visible_source_type_onedrive` | +| On-prem SharePoint | Coming Soon. | Off | `file_sync_visible_source_type_sharepoint_on_prem` | +| Google Workspace | Coming Soon. | Off | `file_sync_visible_source_type_google_workspace` | +| Enable personal sync | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_file_sync_personal`; capability toggle | +| Admins manage sources only | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `file_sync_personal_admin_only` | +| Require PersonalFileSyncUser App Role | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `file_sync_personal_require_app_role` | +| Manage User Sources | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Enable group sync | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_file_sync_group`; capability toggle | +| Admins manage sources only | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `file_sync_group_admin_only` | +| Require Group Assignment to Use File Sync | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_group_assignment_for_file_sync` | +| File Sync Allowed Group Ids | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `file_sync_allowed_group_ids` | +| Manage Group Sources | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Enable public sync | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_file_sync_public`; capability toggle | +| Admins manage sources only | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `file_sync_public_admin_only` | +| Require Public Workspace Assignment to Use File Sync | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_public_workspace_assignment_for_file_sync` | +| File Sync Allowed Public Workspace Ids | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `file_sync_allowed_public_workspace_ids` | +| Manage Public Workspace Sources | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Search Groups | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Search Public Workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | + +## Common tasks + +1. **Configure document grounding.** Set search and extraction dependencies, upload a small document, and ask a grounded question. Outcome to verify: The answer cites indexed content. +2. **Enable approved research.** Enable only allowed web, URL, or deep research routes and test allowed and blocked cases. Outcome to verify: Research follows policy. +3. **Publish file sync.** Enable sync, choose source types and scopes, then run a small source. Outcome to verify: Files appear in the intended workspace. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A synced file is not searchable | The source ran but extraction or indexing failed later. | Check sync state, extraction settings, and Azure AI Search before rerunning. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Security settings]({{ '/admin/security/' | relative_url }}) +- [Workflow settings]({{ '/admin/workflow/' | relative_url }}) diff --git a/docs/admin/operations.md b/docs/admin/operations.md new file mode 100644 index 000000000..833d1eed5 --- /dev/null +++ b/docs/admin/operations.md @@ -0,0 +1,112 @@ +--- +layout: page +title: "Operations settings" +description: "Operations collects Control Center access, refresh behavior, Application Insights, debug logging, file-processing logs, health checks, and Swagger documentation." +section: "Administration" +audience: admin +admin_tab: operations +redirect_from: + - /admin/control-center-config/ + - /admin/logging/ +--- + + +# Operations settings + +## What this group controls + +Operations collects Control Center access, refresh behavior, Application Insights, debug logging, file-processing logs, health checks, and Swagger documentation. + +## Why it matters + +Operational settings determine what admins can observe during incidents. Enable enough telemetry to diagnose problems without leaving verbose logging or unauthenticated endpoints exposed longer than needed. + +{% include media.html src="admin-settings/control-center.png" alt="Screenshot of the Operations group in Admin Settings." title="Operations settings" %} + +{% include media.html src="admin-settings/logging.png" alt="Screenshot of the Operations group in Admin Settings." title="Operations settings" %} + +{% include media.html type="video" title="Operations settings walkthrough" poster="video-posters/admin-operations.png" capture="Recording planned. Walk through each tab in the Operations group and explain when to change each setting." %} + +## Before you change anything + +- Create operational roles before restricting Control Center views. +- Confirm Application Insights configuration before relying on telemetry. +- Agree with security teams before exposing health-check or Swagger endpoints. + +## Control Center {#control-center-config} + +### Automatic Data Refresh {#control-center-auto-refresh-section} + +The Automatic Data Refresh section belongs to the Control Center tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Control Center Access {#control-center-overview-section} + +The Control Center Access section belongs to the Control Center tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Daily Control Center Refresh | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `control_center_auto_refresh_enabled` | +| Refresh Time () | Defines behavior for the related admin workflow; verify the affected feature after saving. | 02:00 | `control_center_auto_refresh_time` | +| Require ControlCenterAdmin App Role | Requires the `ControlCenterAdmin` app role before users can use this capability or view. | Off | `require_member_of_control_center_admin` | +| Allow ControlCenterDashboardReader App Role | Requires the `Allow ControlCenterDashboardReader` app role before users can use this capability or view. | Off | `require_member_of_control_center_dashboard_reader` | + +## Logging & Health {#logging} + +### Application Insights {#application-insights-section} + +The Application Insights section belongs to the Logging & Health tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Debug Logging {#debug-logging-section} + +The Debug Logging section belongs to the Logging & Health tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### File Process Logging {#file-processing-logs-section} + +The File Process Logging section belongs to the Logging & Health tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Health Check {#health-check-section} + +The Health Check section belongs to the Logging & Health tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### API Documentation {#swagger-section} + +The API Documentation section belongs to the Logging & Health tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable /external/healthcheck | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_external_healthcheck`; capability toggle | +| Enable /external/healthcheckz | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_no_auth_external_healthcheck`; capability toggle | +| Enable Swagger/OpenAPI Documentation (/swagger) | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_swagger`; capability toggle | +| Enable Application Insights Global Logging | Sends global application, agent, and orchestration logging events to Application Insights. | Off | `enable_appinsights_global_logging`; capability toggle | +| Enable Debug Logging | Captures verbose diagnostic logs for troubleshooting until disabled or the timer turns it off. | Off | `enable_debug_logging`; capability toggle | +| Enable Time-based Auto Turnoff | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `debug_logging_timer_enabled` | +| Duration | Defines behavior for the related admin workflow; verify the affected feature after saving. | 1 | `debug_timer_value` | +| Time Unit | Defines behavior for the related admin workflow; verify the affected feature after saving. | hours | `debug_timer_unit` | +| Enable File Processing Logs | Records upload, extraction, indexing, and file-processing events for administrative troubleshooting. | On | `enable_file_processing_logs`; capability toggle | +| Enable Time-based Auto Turnoff | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `file_processing_logs_timer_enabled` | +| Duration | Defines behavior for the related admin workflow; verify the affected feature after saving. | 1 | `file_timer_value` | +| Time Unit | Defines behavior for the related admin workflow; verify the affected feature after saving. | hours | `file_timer_unit` | +| Delete logs older than | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Age unit | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | + +## Common tasks + +1. **Configure Control Center.** Set refresh behavior and access requirements, then open Control Center. Outcome to verify: Only intended admins can view refreshed operational data. +2. **Enable useful logging.** Enable telemetry needed for a small operation and inspect the result. Outcome to verify: The operation leaves the expected trace. +3. **Expose health endpoints deliberately.** Enable the needed route and test monitoring. Outcome to verify: Monitoring probes the expected endpoint only. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| No telemetry appears | Application Insights or file-processing logs are disabled or misconfigured. | Enable the relevant logging path and rerun a reproducible operation. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Help settings]({{ '/admin/help/' | relative_url }}) +- [Scale settings]({{ '/admin/scale/' | relative_url }}) diff --git a/docs/admin/scale.md b/docs/admin/scale.md new file mode 100644 index 000000000..371aafb9d --- /dev/null +++ b/docs/admin/scale.md @@ -0,0 +1,130 @@ +--- +layout: page +title: "Scale settings" +description: "Scale covers Redis, conversation and search caches, document access indexing, Cosmos maintenance, and Cosmos throughput automation." +section: "Administration" +audience: admin +admin_tab: scale +--- + + +# Scale settings + +## What this group controls + +Scale covers Redis, conversation and search caches, document access indexing, Cosmos maintenance, and Cosmos throughput automation. + +## Why it matters + +Scale settings trade latency, freshness, and Azure spend. Caches can make the app faster, while throughput automation can prevent throttling, but both need guardrails. + +{% include media.html src="admin-settings/scale.png" alt="Screenshot of the Scale group in Admin Settings." title="Scale settings" %} + +{% include media.html type="video" title="Scale settings walkthrough" poster="video-posters/admin-scale.png" capture="Recording planned. Walk through each tab in the Scale group and explain when to change each setting." %} + +## Before you change anything + +- Provision Redis before enabling shared cache behavior. +- Understand Cosmos RU baselines before enabling automatic scale changes. +- Review document access index rollout state before changing cache or repair settings. + +## Redis & Caching {#redis-caching} + +### Redis Cache {#redis-cache-section} + +The Redis Cache section belongs to the Redis & Caching tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Redis Metrics {#redis-monitoring-section} + +The Redis Metrics section belongs to the Redis & Caching tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Conversation Cache {#conversation-cache-section} + +The Conversation Cache section belongs to the Redis & Caching tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Redis Cache | Uses Redis for shared cache/session scenarios so multiple app instances can share cached state. | Off | `enable_redis_cache`; capability toggle | +| Redis Server Host Name | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | `redis_url` | +| Redis Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | Empty | `redis_auth_type` | +| Key Vault Secret Name Redis Access Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `redis_key` | +| Enable conversation cache | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_conversation_cache`; capability toggle | +| Cache TTL Seconds | Default 120 seconds. User-scoped version invalidation refreshes changed conversations; set to 0 to skip writing new entries. | 120 | `conversation_cache_ttl_seconds` | +| Redis document list cache Wave 6 | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_document_access_index_cache`; capability toggle | +| Cache TTL Seconds | Default 900 seconds. Scope-version invalidation makes document changes visible immediately; TTL clears unreachable old entries. | 900 | `document_access_index_cache_ttl_seconds` | + +## Cosmos {#cosmos} + +### DAI Metrics {#document-access-index-section} + +The DAI Metrics section belongs to the Cosmos tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Cosmos Maintenance {#cosmos-maintenance-section} + +The Cosmos Maintenance section belongs to the Cosmos tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Cosmos DB Throughput {#cosmos-throughput-section} + +The Cosmos DB Throughput section belongs to the Cosmos tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Cosmos Metrics {#cosmos-throughput-metrics-table-section} + +The Cosmos Metrics section belongs to the Cosmos tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Key Filter | Blank filter browses all keys. Filters are case sensitive. Redis SCAN order is server-defined, so use Next Page to keep browsing. | N/A (runtime control) | Runtime UI control | +| Page Size | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | Runtime UI control | +| Search result caching | Caches workspace search-result payloads with document-set fingerprints so repeated personal, group, public, or all-scope searches can reuse results until document changes or the TTL invalidate them. | On | `enable_search_result_caching`; no visible field in `admin_settings.html` | +| Write-through projection | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_document_access_index_write_through`; capability toggle | +| Automatic repair/backfill | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_startup_document_access_index_backfill`; capability toggle | +| Enable shadow validation | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_document_access_index_shadow_validation`; capability toggle | +| Backfill Batch Size | Documents processed per manual or scheduled batch. | 200 | `document_access_index_backfill_batch_size` | +| Repair Batch Size | Fail-open repair records reconciled before each backfill batch. | 100 | `document_access_index_repair_batch_size` | +| Document access index reads Wave 5B default | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_document_access_index_reads`; capability toggle | +| Cosmos Throughput Container Policies Json | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `cosmos_throughput_container_policies_json` | +| App maintenance background scheduler | Allows the background maintenance loop to run app maintenance jobs such as Cosmos index policy checks and stale cache cleanup according to the maintenance interval and lease settings. | On | `enable_app_maintenance`; no visible field in `admin_settings.html` | +| Enable Cosmos throughput automation | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `cosmos_throughput_autoscale_enabled` | +| Subscription ID | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `cosmos_throughput_subscription_id` | +| Resource Group | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `cosmos_throughput_resource_group` | +| Cosmos Account | Provides displayed text that users see in the affected interface. | Not specified in defaults | `cosmos_throughput_account_name` | +| Database | Provides displayed text that users see in the affected interface. | Not specified in defaults | `cosmos_throughput_database_name` | +| Metrics Window | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 5 | `cosmos_throughput_metrics_window_minutes` | +| Auto scale up | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `cosmos_throughput_auto_scale_up_enabled` | +| Scale Up At | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 90 | `cosmos_throughput_scale_up_threshold_percent` | +| Scale Up Step | Defines behavior for the related admin workflow; verify the affected feature after saving. | 1000 | `cosmos_throughput_scale_up_step_ru` | +| Scale Up Interval | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 5 | `cosmos_throughput_scale_up_cooldown_minutes` | +| Maximum RU/s | SimpleChat-managed scaling stops at 10,000 RU/s. Use the Azure portal above this limit. | Not specified in defaults | `cosmos_throughput_max_ru` | +| Ignore maximum guardrail | Defines a capacity or timing boundary that keeps the feature inside supported limits. | Off | `cosmos_throughput_ignore_max_limit` | +| Auto scale down | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `cosmos_throughput_auto_scale_down_enabled` | +| Scale Down At | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 70 | `cosmos_throughput_scale_down_threshold_percent` | +| Scale Down Step | Defines behavior for the related admin workflow; verify the affected feature after saving. | 1000 | `cosmos_throughput_scale_down_step_ru` | +| Scale Down Interval | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 20 | `cosmos_throughput_scale_down_cooldown_minutes` | +| Minimum RU/s | Defines behavior for the related admin workflow; verify the affected feature after saving. | Not specified in defaults | `cosmos_throughput_min_ru` | +| Ignore minimum guardrail | Defines a capacity or timing boundary that keeps the feature inside supported limits. | Off | `cosmos_throughput_ignore_min_limit` | +| Convert manual throughput to Cosmos autoscale | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `cosmos_throughput_convert_manual_to_autoscale_enabled` | +| Enforce global policy for all containers | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `cosmos_throughput_enforce_container_defaults` | +| Filter Containers | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Filter Container Policies | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | + +## Common tasks + +1. **Enable Redis caching.** Set Redis host and authentication, then test a cache-dependent page. Outcome to verify: Distributed cache state is available. +2. **Tune cache freshness.** Set TTLs and change a test document. Outcome to verify: Users see expected freshness behavior. +3. **Guard Cosmos throughput.** Set thresholds, steps, cooldowns, and RU guardrails, then refresh metrics. Outcome to verify: Capacity changes stay within guardrails. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Cosmos scaling changes too often | Thresholds or cooldowns are too aggressive. | Adjust thresholds after reviewing metrics. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Operations settings]({{ '/admin/operations/' | relative_url }}) +- [Backup & Recovery settings]({{ '/admin/backup-recovery/' | relative_url }}) diff --git a/docs/admin/security.md b/docs/admin/security.md new file mode 100644 index 000000000..3cccefb16 --- /dev/null +++ b/docs/admin/security.md @@ -0,0 +1,143 @@ +--- +layout: page +title: "Security settings" +description: "Security covers access roles, Key Vault integration, Content Safety, idle session behavior, and Front Door-aware network URLs." +section: "Administration" +audience: admin +admin_tab: security +redirect_from: + - /admin/safety/ +--- + + +# Security settings + +## What this group controls + +Security covers access roles, Key Vault integration, Content Safety, idle session behavior, and Front Door-aware network URLs. + +## Why it matters + +This group protects who can enter the app, what secrets the app can use, what content is blocked, and which hostnames are trusted for redirects. Treat changes as security controls. + +{% include media.html src="admin-settings/security.png" alt="Screenshot of the Security group in Admin Settings." title="Security settings" %} + +{% include media.html src="admin-settings/safety.png" alt="Screenshot of the Security group in Admin Settings." title="Security settings" %} + +{% include media.html type="video" title="Security settings walkthrough" poster="video-posters/admin-security.png" capture="Recording planned. Walk through each tab in the Security group and explain when to change each setting." %} + +## Before you change anything + +- Create Entra app roles before requiring them. +- Provision Key Vault or Content Safety resources before enabling integrations. +- Validate Front Door hostnames and OAuth redirects before switching users to the routed URL. + +## Access & Roles {#access-roles} + +### Permissions {#permissions-section} + +The Permissions section belongs to the Access & Roles tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### App Role Requirements {#app-role-requirements-section} + +The App Role Requirements section belongs to the Access & Roles tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Access Denied Message {#access-denied-message-section} + +The Access Denied Message section belongs to the Access & Roles tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Access Denied Message | Shown to signed-in users who lack the required roles. Use Enter for line breaks. | You are logged in but do not have the required permissions to access this application. Please contact an administrator for access. | `access_denied_message` | +| Require SafetyViolationAdmin App Role | Requires the `SafetyViolationAdmin` app role before users can use this capability or view. | Off | `require_member_of_safety_violation_admin` | + +## Secrets {#secrets} + +### Key Vault {#keyvault-section} + +The Key Vault section belongs to the Secrets tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Key Vault for Agent and Action Secrets | Places agent and action secrets in Azure Key Vault through the configured vault identity. | Off | `enable_key_vault_secret_storage`; capability toggle | +| Key Vault Name | Provides the secret credential used when the selected authentication mode requires one. | Empty | `key_vault_name` | +| Key Vault Managed Identity Client ID | Provides the secret credential used when the selected authentication mode requires one. | Empty | `key_vault_identity` | +| Enable SimpleChat expiration reminder tracking | Tracks expiration metadata for stored secrets so admins can review or route reminders before secrets expire. | Off | `enable_key_vault_secret_expiration_reminders`; capability toggle | +| Default lead days | Provides the secret credential used when the selected authentication mode requires one. | 30 | `key_vault_secret_expiration_default_lead_days` | +| Default reminder email | Provides the secret credential used when the selected authentication mode requires one. | Empty | `key_vault_secret_expiration_default_contact_email` | +| Admin notification roles | Comma-separated roles for global-scope reminder notifications. | Admin | `key_vault_secret_expiration_admin_roles` | +| Scan interval seconds | Provides the secret credential used when the selected authentication mode requires one. | 21600 | `key_vault_secret_expiration_scan_interval_seconds` | +| Require expiration dates when users enable tracking on new secrets | Provides the secret credential used when the selected authentication mode requires one. | Off | `key_vault_secret_expiration_require_expiration` | +| Include reminder contact email in external telemetry | Default off. Enable only when Azure Monitor, Logic Apps, Functions, or webhook automation needs the email address to route notifications directly. | Off | `key_vault_secret_expiration_emit_contact_email_in_telemetry` | +| Key Vault Reminders Search | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Key Vault Reminders Status | Defines behavior for the related admin workflow; verify the affected feature after saving. | Empty | Runtime UI control | + +## Content Safety {#content-safety} + +### Content Safety {#content-safety-section} + +The Content Safety section belongs to the Content Safety tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Content Safety | Routes chat content through Azure AI Content Safety so blocked messages use the configured violation message instead of continuing through the normal chat flow. | Off | `enable_content_safety`; capability toggle | +| Use APIM instead of direct Content Safety endpoint | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_content_safety_apim`; capability toggle | +| Content Safety Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `content_safety_endpoint` | +| Authentication Type | Chooses whether SimpleChat authenticates to this service with a key, managed identity, or another supported method. | key | `content_safety_authentication_type` | +| Content Safety Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `content_safety_key` | +| Azure APIM Content Safety Endpoint | Provides the endpoint or route SimpleChat uses for this service. | Empty | `azure_apim_content_safety_endpoint` | +| Azure APIM Content Safety Subscription Key | Provides the secret credential used when the selected authentication mode requires one. | Empty | `azure_apim_content_safety_subscription_key` | +| Safety Violation Message (Markdown supported) | Displayed when Content Safety blocks a chat message. | Not specified in defaults | `content_safety_violation_message` | +| Include Trigger Information | Defines behavior for the related admin workflow; verify the affected feature after saving. | On | `content_safety_include_trigger_information` | + +## Session {#session} + +### Idle Session Timeout {#idle-timeout-section} + +The Idle Session Timeout section belongs to the Session tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Idle Session Timeout and Warning | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_idle_timeout`; capability toggle | +| Idle Logout Timeout (Minutes) | Users are logged out locally after this many minutes of inactivity. Minimum value: 10 minutes. | 30 | `idle_timeout_minutes` | +| Idle Warning Time (Minutes) | Show the warning modal after this many minutes of inactivity. Set this equal to the logout timeout to disable the warning dialog window. | 28 | `idle_warning_minutes` | +| Idle Warning Message | Custom text shown at the top of the idle warning dialog. | You've been inactive for a while. | `idle_warning_message` | + +## Network {#network} + +### Azure Front Door {#front-door-section} + +The Azure Front Door section belongs to the Network tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Front Door Support | Generates user-facing and OAuth redirect URLs using the configured Front Door or load-balancer base URL. | Off | `enable_front_door`; capability toggle | +| Front Door URL | 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 | Empty | `front_door_url` | + +## Common tasks + +1. **Require roles.** Enable a role requirement and test with assigned and unassigned users. Outcome to verify: Only assigned users can enter the protected surface. +2. **Move secrets to Key Vault.** Enable Key Vault storage, set access details, and rotate a low-risk secret. Outcome to verify: The app reads the secret from Key Vault. +3. **Turn on Content Safety.** Set routing, authentication, and violation message, then submit a blocked test prompt. Outcome to verify: Blocked content shows the configured message. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Sign-in redirects use the wrong host | Front Door support is off or the base URL is wrong. | Correct the URL and test sign-in through the routed domain. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Governance settings]({{ '/admin/governance/' | relative_url }}) +- [Knowledge settings]({{ '/admin/knowledge/' | relative_url }}) diff --git a/docs/admin/workflow.md b/docs/admin/workflow.md new file mode 100644 index 000000000..3388e9c84 --- /dev/null +++ b/docs/admin/workflow.md @@ -0,0 +1,62 @@ +--- +layout: page +title: "Workflow settings" +description: "Workflow centralizes task sequence, assignment, and approval behavior used across SimpleChat surfaces." +section: "Administration" +audience: admin +admin_tab: workflow +--- + + +# Workflow settings + +## What this group controls + +Workflow centralizes task sequence, assignment, and approval behavior used across SimpleChat surfaces. + +## Why it matters + +Workflow settings affect handoffs between people and automated steps. Keep the path understandable so workspace owners can diagnose stalled approvals. + +{% include media.html src="admin/workflow-overview.png" alt="Screenshot placeholder for the Workflow group in Admin Settings." title="Workflow settings" capture="Capture the Workflow group in Admin Settings showing its tabs." %} + +{% include media.html type="video" title="Workflow settings walkthrough" poster="video-posters/admin-workflow.png" capture="Recording planned. Walk through each tab in the Workflow group and explain when to change each setting." %} + +## Before you change anything + +- Document which teams own workflow templates. +- Confirm assignment roles before enabling a workflow path. + +## Workflow {#workflow} + +### Workflow {#workflow-settings-section} + +The Workflow section belongs to the Workflow tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Personal Workflows | Permits enable personal workflows when the related workspace or agent feature is enabled. | Off | `allow_user_workflows` | +| Require WorkflowUser App Role | Requires the `WorkflowUser` app role before users can use this capability or view. | Off | `require_member_of_workflow_user` | +| Workflow Agent Action Limit | Maximum automatic tool or action calls an agent can make during one workflow run. Default is 60; increase for large document sets. | 60 | `workflow_max_auto_invoke_attempts` | +| Workflow Task Limit | Maximum ordered instruction tasks users can add to one workflow. Default is 50; supported range is 1-100. | 50 | `workflow_max_tasks` | +| Enable Group Workflows | Permits enable group workflows when the related workspace or agent feature is enabled. | Off | `allow_group_workflows` | +| Require Group Assignment to Use Workflow | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_group_assignment_for_group_workflows` | +| Group Workflow Allowed Group Ids | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `group_workflow_allowed_group_ids` | + +## Common tasks + +1. **Validate a workflow path.** Review the Workflow tab and run a simple test sequence. Outcome to verify: The sequence reaches the expected state and assignee. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A workflow does not advance | Assignment or approval state is not configured for the path. | Check workflow settings and rerun with a small item. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Knowledge settings]({{ '/admin/knowledge/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/admin/workspaces.md b/docs/admin/workspaces.md new file mode 100644 index 000000000..8ee5fa721 --- /dev/null +++ b/docs/admin/workspaces.md @@ -0,0 +1,122 @@ +--- +layout: page +title: "Workspaces settings" +description: "Workspaces controls personal, group, and public workspace availability, file downloads, sharing policy, file-size limits, and global identities." +section: "Administration" +audience: admin +admin_tab: workspaces +redirect_from: + - /admin/workspace-identities/ +--- + + +# Workspaces settings + +## What this group controls + +Workspaces controls personal, group, and public workspace availability, file downloads, sharing policy, file-size limits, and global identities. + +## Why it matters + +Workspace settings define where documents live and how users can move them. Public workspaces, sharing, and downloads widen the audience for files, so configure them with approval expectations. + +{% include media.html src="admin-settings/workspaces.png" alt="Screenshot of the Workspaces group in Admin Settings." title="Workspaces settings" %} + +{% include media.html src="admin-settings/global-identity.png" alt="Screenshot of the Workspaces group in Admin Settings." title="Workspaces settings" %} + +{% include media.html type="video" title="Workspaces settings walkthrough" poster="video-posters/admin-workspaces.png" capture="Recording planned. Walk through each tab in the Workspaces group and explain when to change each setting." %} + +## Before you change anything + +- Define which workspace types policy supports. +- Choose file-sharing and download rules before inviting broad groups. +- Create shared identity mappings before connectors depend on them. + +## Workspace Types {#workspace-types} + +### Personal Workspaces {#personal-workspaces-section} + +The Personal Workspaces section belongs to the Workspace Types tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Group Workspaces {#group-workspaces-section} + +The Group Workspaces section belongs to the Workspace Types tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Public Workspaces {#public-workspaces-section} + +The Public Workspaces section belongs to the Workspace Types tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Enable Personal Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_user_workspace`; capability toggle | +| Search Groups | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Search Public Workspaces | Defines behavior for the related admin workflow; verify the affected feature after saving. | N/A (runtime control) | Runtime UI control | +| Enable Group Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | On | `enable_group_workspaces`; capability toggle | +| Disable Group Creation | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | Inverse of `enable_group_creation` | +| Require CreateGroups App Role | Requires the `CreateGroups` app role before users can use this capability or view. | Off | `require_member_of_create_group` | +| Require Owner to Manage Group Agents, Actions and Workflows | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_owner_for_group_agent_management` | +| Enable Public Workspaces | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_public_workspaces`; capability toggle | +| End-user display name | Optional. End users will see this label instead of Public Workspace. Admin settings and internal references continue to use Public Workspace. | Empty | `public_workspace_display_name` | +| Require CreatePublicWorkspaces App Role | Requires the `CreatePublicWorkspaces` app role before users can use this capability or view. | Off | `require_member_of_create_public_workspace` | +| Enable Extract Meta Data | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_extract_meta_data`; capability toggle | +| Extraction Model | Selects the deployment SimpleChat sends requests to for this capability. | Empty | `metadata_extraction_model` | +| Enable Multi-Modal Vision Analysis | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_multimodal_vision`; capability toggle | +| Vision Model * | Select a GPT model with vision capabilities (for example, gpt-4o or supported GPT 5 and later models). Only vision-capable models are shown. | Empty | `multimodal_vision_model` | + +## Files & Sharing {#files-sharing} + +### File Downloads {#file-download-settings-section} + +The File Downloads section belongs to the Files & Sharing tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### File Sharing {#file-sharing-section} + +The File Sharing section belongs to the Files & Sharing tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Shared Conversation File Approvals {#shared-conversation-file-approvals-section} + +The Shared Conversation File Approvals section belongs to the Files & Sharing tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +### Maximum File Size {#file-size-limit-section} + +The Maximum File Size section belongs to the Files & Sharing tab. Use it with the adjacent settings in this group so related rollout, access, and operational choices stay aligned. + +#### Settings + +| Setting | What it does | Default | Notes | +| --- | --- | --- | --- | +| Maximum File Size (MB) | Defines a capacity or timing boundary that keeps the feature inside supported limits. | 150 | `max_file_size_mb` | +| Enable Personal Workspace Downloads | Permits enable personal workspace downloads when the related workspace or agent feature is enabled. | Off | `allow_personal_workspace_file_downloads` | +| Enable Group Workspace Downloads | Permits enable group workspace downloads when the related workspace or agent feature is enabled. | Off | `allow_group_workspace_file_downloads` | +| Require Group Assignment for Downloads | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_group_assignment_for_file_downloads` | +| File Download Allowed Group Ids | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `file_download_allowed_group_ids` | +| Enable Public Workspace Downloads | Permits enable public workspace downloads when the related workspace or agent feature is enabled. | Off | `allow_public_workspace_file_downloads` | +| Require Public Workspace Assignment for Downloads | Defines behavior for the related admin workflow; verify the affected feature after saving. | Off | `require_public_workspace_assignment_for_file_downloads` | +| File Download Allowed Public Workspace Ids | Lists the approved IDs, domains, groups, workspaces, or sources that may use this feature. | Empty list | `file_download_allowed_public_workspace_ids` | +| Enable File Sharing | Exposes the capability after required services, permissions, and rollout policy are ready. | Off | `enable_file_sharing`; capability toggle | + +## Global Identities {#workspace-identities} + +Global Identities is rendered dynamically and does not declare static settings sections in `admin_settings_nav.py`. + +No retired-page setting rows mapped to Global Identities; use the live Admin Settings UI to review identity records. + +## Common tasks + +1. **Enable a workspace type.** Enable the desired scope and create a small test workspace. Outcome to verify: The workspace type appears for the intended audience. +2. **Constrain file movement.** Review download, sharing, approval, and size settings, then test a share. Outcome to verify: Files move only through the allowed path. +3. **Review global identities.** Check identity ownership and test a dependent connector. Outcome to verify: Connector activity uses the expected shared identity. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Users cannot create a group workspace | Group workspaces or group creation are disabled. | Enable the correct workspace controls and retry with an eligible user. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Workflow settings]({{ '/admin/workflow/' | relative_url }}) +- [Agents & Actions settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/admin_configuration.md b/docs/admin_configuration.md index 02fb44543..1ea8fc779 100644 --- a/docs/admin_configuration.md +++ b/docs/admin_configuration.md @@ -1,375 +1,260 @@ --- -layout: showcase-page -title: Admin Configuration + +layout: page + +title: "Admin Configuration" + +description: "Concise hub for SimpleChat Admin Settings, setup walkthrough behavior, tab references, and operator notes." + +section: "Administration" + permalink: /admin_configuration/ + menubar: docs_menu -accent: violet -eyebrow: Central Control Surface -description: Admin Settings turns a raw deployment into an operating environment by centralizing branding, model routing, workspace policy, safety controls, scale features, and logging. -hero_icons: - - bi-gear - - bi-sliders2-vertical - - bi-shield-check -hero_pills: - - Guided setup walkthrough included - - Model and workspace configuration in one UI - - Logging, safety, and scale controls available -hero_links: - - label: Getting started guide - url: /setup_instructions/ - style: primary - - label: Manual setup reference - url: /setup_instructions_manual/ - style: secondary + +redirect_from: + + - /reference/admin_configuration/ + --- -Once the application is running and you sign in with the Admin role, the Admin Settings page becomes the main place to configure the product without editing environment variables directly. - -
-
-
-

General

-

Brand the app, tune appearance, manage landing-page content, and control global behavior such as file limits and system prompts.

-
-
-
-

Models and retrieval

-

Configure GPT, embeddings, image generation, Azure AI Search, Document Intelligence, and enhanced citations from the same settings surface.

-
-
-
-

Workspaces and agents

-

Enable personal, group, and public workspaces, define classification behavior, and manage agent and action capabilities.

-
-
-
-

Safety, scale, and logging

-

Turn on Content Safety, Redis, Front Door, Application Insights logging, and other controls that matter once the deployment grows up.

-
-
- -
-

Best starting point

-

The Setup Walkthrough is the fastest way to move a fresh environment from partially configured to usable. It guides admins through the critical dependencies in the right order and skips steps that do not apply.

-
- -
-

Need to change what users see first?

-

Use the branding, home page, and support settings how-to for step-by-step guidance on logos, application title, landing page text, health checks, Swagger, classification banners, Support, external links, and system settings.

-
- -![Admin Settings page](./images/admin_settings_page.png) - -## Setup Walkthrough - -The Admin Settings page includes an interactive **Setup Walkthrough** feature that guides you through the initial configuration process. This is particularly helpful for first-time setup. - -### Starting the Walkthrough - -- The walkthrough automatically appears on first-time setup when critical settings are missing -- You can manually launch it anytime by clicking the **"Start Setup Walkthrough"** button at the top of the Admin Settings page -- The walkthrough will automatically navigate to the relevant configuration tabs as you progress through each step - -### Walkthrough Features - -- **Automatic Tab Navigation**: As you move through steps, the walkthrough automatically switches to the relevant admin settings tab and scrolls to the appropriate section -- **Smart Step Skipping**: Steps that aren't applicable based on your configuration choices (e.g., workspace-dependent features) are automatically skipped -- **Real-time Validation**: The "Next" button becomes available only when required fields for the current step are completed -- **Progress Tracking**: Visual progress bar shows your completion status through the setup process -- **Flexible Navigation**: Use "Previous" and "Next" buttons to move between steps, or close the walkthrough at any time to configure settings manually - -### Walkthrough Steps Overview - -The walkthrough covers these key configuration areas in order: - -1. **Application Basics** (Optional) - App title and logo -2. **GPT API Settings** (Required) - Azure OpenAI GPT endpoint and authentication -3. **GPT Model Selection** (Required) - Select available GPT models for users -4. **Workspaces** (Optional) - Enable personal and/or group workspaces -5. **Embedding API** (Required if workspaces enabled) - Configure embedding service -6. **Azure AI Search** (Required if workspaces enabled) - Configure search indexing -7. **Document Intelligence** (Required if workspaces enabled) - Configure document processing -8. **Video Support** (Optional, workspace-dependent) - Configure video file processing -9. **Shared Speech Service** (Optional) - Configure the shared Speech resource used for audio uploads, voice input, and voice responses -10. **Content Safety** (Optional) - Configure content filtering -11. **User Feedback & Archiving** (Optional) - Enable feedback and conversation archiving -12. **Enhanced Features** (Optional) - Enhanced citations and image generation - -The walkthrough automatically adjusts which steps are required based on your selections. For example, if you don't enable workspaces, embedding and search configuration steps become optional. - -## Configuration Sections - -Key configuration sections include: - -### 1. General -- **Branding**: Application title, custom logo upload (light and dark mode), favicon -- **Home Page Text**: Landing page markdown content with alignment options and optional editor -- **Appearance**: Default theme (light/dark mode) and navigation layout (top nav or left sidebar) -- **Health Check**: External health check endpoint configuration for monitoring systems -- **API Documentation**: Enable/disable Swagger/OpenAPI documentation endpoint -- **Classification Banner**: Security classification banner for data sensitivity indication -- **External Links**: Custom navigation links to external resources with configurable menu behavior -- **System Settings**: Maximum file size, conversation history limit, default system prompt - -For practical operator steps, see [Configure Branding, Home Page, and Support Settings]({{ '/how-to/admin_ui_settings/' | relative_url }}). - -### 2. AI Models -- **Chat Model**: - - Configure Azure OpenAI endpoint(s) for chat models - - Supports Direct endpoint or APIM (API Management) - - Allows Key or Managed Identity authentication - - Test connection button - - Select multiple active deployment(s) - users can choose from available models - - Multi-model selection for users - -- **Embeddings Configuration**: - - Configure Azure OpenAI endpoint(s) for embedding models - - Supports Direct/APIM, Key/Managed Identity - - Test connection - - Select active deployment - -- **Image Generation** *(Optional)*: - - Enable/disable feature - - Configure Azure OpenAI DALL-E endpoint - - Supports Direct/APIM, Key/Managed Identity - - Test connection - - Select active deployment - -### 3. Workspaces -- **Personal Workspaces**: Enable/disable "Your Workspace" (personal docs) -- **Group Workspaces**: - - Enable/disable "Groups" (group docs) - - Option to enforce `CreateGroups` RBAC role for creating new groups -- **Public Workspaces**: - - Enable/disable "Public" (public docs) - - Option to enforce `CreatePublicWorkspaces` RBAC role for creating new public workspaces -- **File Sharing**: - - Enable/disable file sharing capabilities between users and workspaces. -- **Metadata Extraction**: - - Enable/disable metadata extraction from documents - - Select the GPT model used for extraction -- **Multi-Modal Vision Analysis**: - - Enable vision-capable models for image analysis in addition to document OCR - - Automatic filtering of compatible GPT models (GPT-4o, GPT-4 Vision, etc.) -- **Document Classification**: - - Enable/disable classification features - - Define custom classification labels and colors - - Dynamic category management with inline editing - -### 4. Citations -- **Standard Citations**: Basic text references (always enabled) -- **Enhanced Citations**: - - Enable/disable enhanced citation features - - Configure Azure Storage Account Connection String or Service Endpoint with Managed Identity - - Store original files for direct reference and preview - -### 5. Safety -- **Content Safety**: - - Enable/disable content filtering - - Configure endpoint (Direct/APIM) - - Key/Managed Identity authentication - - Test connection -- **User Feedback**: - - Enable/disable thumbs up/down feedback on AI responses -- **Admin Access RBAC**: - - Option to require `SafetyViolationAdmin` role for safety violation admin views - - Option to require `FeedbackAdmin` role for feedback admin views -- **Conversation Archiving**: - - Enable/disable conversation archiving instead of permanent deletion - -### 6. Search & Extract -- **Azure AI Search**: - - Configure connection (Endpoint, Key/Managed Identity) - - Support for Direct or APIM routing - - Test connection -- **Document Intelligence**: - - Configure connection (Endpoint, Key/Managed Identity) - - Support for Direct or APIM routing - - Test connection -- **Multimedia Support** (Video/Audio uploads): - - **Video Files**: Configure Azure Video Indexer with the App Service system-assigned managed identity - - Cloud / Endpoint Mode, Resource Group, Subscription ID, Account Name, Location, Account ID - - Effective API Endpoint, ARM API Version, Timeout - - Video Indexer API keys are not used by the current setup flow - - **Shared Speech Service**: One Speech resource powers audio uploads, speech-to-text input, and text-to-speech - - Endpoint, Location/Region, Locale, Authentication Type - - Key authentication uses the Speech key - - Managed identity uses the custom-domain Speech endpoint; voice responses also require the Speech Resource ID - -### 7. Agents -- **Agents Configuration**: - - Enable/disable Semantic Kernel-powered agents - - Configure workspace mode (per-user vs global agents) - - Agent orchestration settings (single agent vs multi-agent group chat) - - Manage global agents and select default/orchestrator agent -- **Actions Configuration**: - - Enable/disable core plugins (Time, HTTP, Wait, Math, Text, Fact Memory, Embedding) - - Configure user and group plugin permissions - - Manage custom OpenAPI plugins - -### 8. Scale -- **Redis Cache**: - - Enable distributed session storage for horizontal scaling - - Configure Redis endpoint and authentication (Key or Managed Identity) - - Test connection -- **Front Door**: - - Enable Azure Front Door integration - - Configure Front Door URL for authentication flows - - Supports global load balancing and custom domains - -### 9. Logging -- **Application Insights Logging**: - - Enable global logging for agents and orchestration - - Requires application restart to take effect -- **Debug Logging**: - - Enable/disable debug print statements - - Optional time-based auto-disable feature - - Warning: Collects tokens and keys during debug -- **File Processing Logs**: - - Enable logging of file processing events - - Logs stored in Cosmos DB file_processing container - - Optional time-based auto-disable feature - - Permanently delete logs older than a positive number of days, weeks, or fixed 30-day months - - Delete all stored logs through a separate confirmation action -## Admin Settings Execution Guide -Use this section when you need to configure an area, validate it, and know what to check after saving. Open **Admin Settings**, select the named tab, make the change, use the local test buttons where the tab provides them, then click **Save Settings**. +Admin Settings turns a running deployment into an operating environment by centralizing branding, model routing, workspace policy, safety controls, scale features, and logging. Use this page for orientation, then use the tab-specific pages for the full setting-by-setting guidance. -### General -![Annotated General settings controls](./images/admin-settings/general.png) -1. Open **General** and set the app title, light/dark logos, favicon, home page Markdown, default theme, and default navigation layout. -2. Configure operational visibility: authenticated and unauthenticated health checks, Swagger/OpenAPI, classification banner, Support menu, Latest Features visibility, and external links. -3. Set global system behavior such as file size limits, conversation history, and the default system prompt, then save and refresh the home page to confirm branding and navigation changes. +## Accessing Admin Settings -### AI Models +Sign in with an account that has the Admin role, open **Admin Settings**, and use the tab pages below to configure the deployment. -![Annotated AI model controls](./images/admin-settings/ai-models.png) +## Start with the tab guide -1. Open **AI Models** and add the chat model endpoint configuration: direct Azure OpenAI, Foundry (classic), New Foundry, or APIM endpoint, authentication type, deployment names, and any default model choices. For managed identity or service principal RBAC setup, see [Configure Model Endpoint Identity]({{ '/how-to/model_endpoint_identity_setup/' | relative_url }}). -2. Configure **Embeddings** before enabling retrieval-backed workspace features, then use the connection test to verify the endpoint and authentication path. -3. Enable **Processing Thoughts** and **Image Generation** only for deployments where users should see reasoning traces or generate images, then save and confirm the model selector appears in chat. -### Agents And Actions -![Annotated Agents and actions controls](./images/admin-settings/agents-actions.png) +The [Administration settings guide]({{ '/admin/' | relative_url }}) is the canonical tab-by-tab reference. It explains the recommended first-time configuration order and summarizes what each tab controls. -1. Open **Agents and Actions** and choose whether document actions such as Analyze and Document Comparison should appear in Chat and Workflow action menus. -2. Enable agents, choose workspace-specific or global mode, set orchestration behavior, and manage global agents or approvals if admins curate shared agents centrally. -3. Enable action scopes and core plugins users are allowed to invoke. Save global agent/action changes, restart the web app when the tab notes it is required, and then verify the runtime action menus. -Mixed-source rollout is independently reversible. Keep `enable_mixed_source_manifest`, `enable_mixed_source_chat_search`, `enable_mixed_source_analyze`, `enable_cross_format_compare`, and `enable_mixed_source_conversation_continuity` off until the preceding stage is validated. The subordinate relevance, Analyze All, one-to-many Compare, and development telemetry stages also default off. `enable_mixed_source_development_telemetry` records aggregate counts and latency only; it must never be used to capture prompts, evidence, source identifiers, filenames, or storage paths. -### Logging +## Configuration sections -![Annotated Logging controls](./images/admin-settings/logging.png) -1. Open **Logging** and enable Application Insights logging when agent, orchestration, or operational events should flow into Azure monitoring. -2. Use **Debug Logging** for short diagnosis windows only, set an auto-disable time, and avoid leaving token or key capture enabled longer than necessary. -3. Enable **File Processing Logs** when admins need upload, extraction, indexing, or sync troubleshooting history, then save and confirm the expected log container or telemetry stream receives events. -4. Under **Delete stored logs**, choose an age and unit to remove older entries, or use **Delete all logs** for a complete cleanup. Review the permanent-deletion confirmation carefully; months are fixed 30-day periods. -### Scale +- [General]({{ '/admin/appearance/' | relative_url }}) -![Annotated Scale controls](./images/admin-settings/scale.png) +- [AI models]({{ '/admin/ai-models/' | relative_url }}) -1. Open **Scale** and configure Redis when the app will run multiple instances or needs distributed session storage; test the Redis connection before saving. -2. Use the Cosmos throughput card to refresh status, choose database or container scope, set autoscale thresholds, enforce global policy when appropriate, and convert eligible manual throughput to native autoscale. -3. Configure Azure Front Door when authentication redirects and user-facing URLs should use the routed domain, then save and validate sign-in through the Front Door URL. +- [Search and extract]({{ '/admin/knowledge/' | relative_url }}) -### Control Center +- [Workspaces]({{ '/admin/workspaces/' | relative_url }}) -![Annotated Control Center controls](./images/admin-settings/control-center.png) +- [File sync]({{ '/admin/knowledge/' | relative_url }}) -1. Open **Control Center** and set the auto-refresh interval used by dashboard and management views. -2. Choose which overview behavior should be available to admins, especially in larger deployments where refresh frequency affects load. -3. Save the settings, open Control Center, and confirm the dashboard refresh cadence and management tables behave as configured. +- [Global identities]({{ '/admin/workspaces/' | relative_url }}) -### Workspaces +- [Citations]({{ '/admin/chat/' | relative_url }}) -![Annotated Workspace controls](./images/admin-settings/workspaces.png) +- [Safety]({{ '/admin/security/' | relative_url }}) -1. Open **Workspaces** and enable the workspace types your users should see: personal, group, public, workflow, and file sharing options. -2. Configure upload behavior, chat file uploads, metadata extraction, multimodal vision, document classification, and any group or public workspace role requirements. -3. Set retention policies, workspace scope lock, and user agreement text, then save and verify the corresponding workspace navigation and upload actions with a normal user account. +- [Security]({{ '/admin/security/' | relative_url }}) -### File Sync +- [Agents]({{ '/admin/agents-actions/' | relative_url }}) -![Annotated File Sync controls](./images/admin-settings/file-sync.png) +- [Scale]({{ '/admin/scale/' | relative_url }}) -1. Open **File Sync** and enable the connector, visible source types, and the workspace scopes where synced files may appear. -2. Configure personal, group, and public workspace sync behavior before creating sources so each source lands in the correct ownership model. -3. Use **Manage File Sync Sources** and **File Sync App Role Setup** to create source definitions and app roles, then run a sync and verify badges, history, and imported documents. +- [Control center]({{ '/admin/operations/' | relative_url }}) -### Global Identity +- [Backup, migrate and restore]({{ '/admin/backup-recovery/' | relative_url }}) -![Annotated global identity controls](./images/admin-settings/global-identity.png) +- [Governance]({{ '/admin/governance/' | relative_url }}) -1. Open **Workspace Identities** to review reusable identities that File Sync and workspace connectors can share across sources. -2. Create or update identity mappings when multiple sync sources should use the same managed identity, app registration, or delegated access profile. -3. Save the identity configuration, return to File Sync, and assign the identity to a source before running the connector test or sync job. +- [Logging]({{ '/admin/operations/' | relative_url }}) -### Citations +- [Send feedback]({{ '/admin/help/' | relative_url }}) -![Annotated Citation controls](./images/admin-settings/citation.png) +- [Custom pages]({{ '/admin/appearance/' | relative_url }}) -1. Open **Citations** and enable enhanced citations when users need direct file preview, source documents, or richer citation playback. -2. Configure Azure Storage using either connection string or managed identity endpoint, then test access before saving. -3. Upload or reprocess a document after saving and confirm chat citations can open the expected preview or source reference. +- [Latest features]({{ '/admin/help/' | relative_url }}) -### Safety -![Annotated Safety controls](./images/admin-settings/safety.png) -1. Open **Safety** and configure Azure Content Safety endpoint routing, authentication, and connection testing before enabling filtering for users. -2. Enable user feedback, safety violation admin role enforcement, and feedback admin role enforcement according to your review process. -3. Configure conversation archiving versus deletion, save, and verify that feedback and safety review pages respect the selected RBAC controls. +For step-by-step branding and home page changes, see [Configure branding, home page, and support settings]({{ '/guides/configure-branding-and-support-settings/' | relative_url }}). -### Security -![Annotated Security controls](./images/admin-settings/security.png) -1. Open **Security** and review any role-gated or secret-management settings that control access to sensitive admin tools. -2. Enable required app roles before turning on enforcement, then assign those roles in Entra ID to the admin groups that need access. -3. Save and validate with both an authorized admin and a normal user so restricted views remain reachable only to the intended audience. -### Search And Extract +## Related deployment and identity pages + +- [Choose a deployment path]({{ '/start/deployment-options/' | relative_url }}) +- [Manual setup reference]({{ '/setup_instructions_manual/' | relative_url }}) +- [Use managed identity]({{ '/guides/use-managed-identity/' | relative_url }}) + +## Legacy screenshots + +The legacy full configuration reference included annotated screenshots for several tabs. The tab pages remain the primary reference, and these image links are preserved for readers who bookmarked or reviewed the previous visual guide. + +- [Admin Settings page]({{ '/images/admin_settings_page.png' | relative_url }}) +- [General settings screenshot]({{ '/images/admin-settings/general.png' | relative_url }}) +- [AI models screenshot]({{ '/images/admin-settings/ai-models.png' | relative_url }}) +- [Agents and actions screenshot]({{ '/images/admin-settings/agents-actions.png' | relative_url }}) +- [Logging screenshot]({{ '/images/admin-settings/logging.png' | relative_url }}) +- [Scale screenshot]({{ '/images/admin-settings/scale.png' | relative_url }}) +- [Control Center screenshot]({{ '/images/admin-settings/control-center.png' | relative_url }}) +- [Workspaces screenshot]({{ '/images/admin-settings/workspaces.png' | relative_url }}) +- [File Sync screenshot]({{ '/images/admin-settings/file-sync.png' | relative_url }}) +- [Global Identity screenshot]({{ '/images/admin-settings/global-identity.png' | relative_url }}) +- [Citations screenshot]({{ '/images/admin-settings/citation.png' | relative_url }}) +- [Safety screenshot]({{ '/images/admin-settings/safety.png' | relative_url }}) +- [Security screenshot]({{ '/images/admin-settings/security.png' | relative_url }}) +- [Search and Extract screenshot]({{ '/images/admin-settings/search-extract.png' | relative_url }}) +- [Send Feedback screenshot]({{ '/images/admin-settings/send-feedback.png' | relative_url }}) + +## Setup walkthrough + + + +The Admin Settings page includes an interactive setup walkthrough for first-time configuration. It appears automatically when critical settings are missing, and admins can relaunch it with **Start Setup Walkthrough**. + + + +### Walkthrough behavior + + + +- It navigates to the relevant tab as each step starts. -![Annotated Search and Extract controls](./images/admin-settings/search-extract.png) +- It skips steps that do not apply to the current configuration choices. -1. Open **Search and Extract** and configure Web Search, URL Access, and Deep Research only for deployments where admins have approved outbound source review behavior. -2. Configure Azure AI Search and Document Intelligence endpoints, select Read, Layout, or Auto extraction mode, and test each connection. -3. Set chunk sizes and multimedia extraction services such as Video Indexer and Speech, then upload a document or media file to confirm extraction, indexing, and citation behavior. +- It enables **Next** only when required fields for the current step are complete. -### Send Feedback +- It shows progress through the setup flow and supports previous, next, and close actions. + + + +### Walkthrough order + + + +1. Application basics for the app title and logo. + +2. GPT API settings for Azure OpenAI endpoint and authentication. + +3. GPT model selection for user-visible models. + +4. Workspace enablement for personal and group workspaces. + +5. Embedding API, Azure AI Search, and Document Intelligence when workspaces are enabled. + +6. Optional video support and shared Speech service configuration. + +7. Optional Content Safety, user feedback, conversation archiving, enhanced citations, and image generation. + + + +## Admin Settings Execution Guide + +Use the tab-specific pages below when you need to configure an area, validate it, and know what to check after saving. Open **Admin Settings**, select the named tab, make the change, use local test buttons where available, then click **Save Settings**. + +## Operator notes preserved from the legacy reference -![Annotated Send Feedback controls](./images/admin-settings/send-feedback.png) -1. Open **Send Feedback** from Admin Settings or the Support menu after the Support destination is enabled in General. -2. Enter the issue, enhancement request, or operational context with enough detail for admins to reproduce or triage it. -3. Submit the feedback and confirm it arrives through the configured support recipient or review workflow. ## Navigation Options -The Admin Settings page supports two navigation layouts: -1. **Tab Navigation** (Default): Horizontal tabs at the top for switching between configuration sections -2. **Left Sidebar Navigation**: Collapsible left sidebar with grouped navigation items - - Can be set as the default for all users in General → Appearance settings - - Users can toggle between layouts individually - - The Setup Walkthrough works seamlessly with both navigation styles + +Admin Settings supports horizontal tab navigation and a collapsible left-sidebar layout. Admins can set the default in **General** settings, users can toggle layouts individually, and the setup walkthrough works with both layouts. + + ## Tips for Configuration -- **Save Changes**: The floating "Save Settings" button in the bottom-right becomes active (blue) when you make changes -- **Test Connections**: Use the "Test Connection" buttons to verify your service configurations before saving -- **APIM vs Direct**: When using Azure API Management (APIM), you'll need to manually specify model names as automatic model fetching is not available -- **Managed Identity**: When using Managed Identity authentication, ensure your Service Principal has the appropriate roles assigned: - - **Azure OpenAI**: Cognitive Services OpenAI User role - - **Model Endpoints**: Azure OpenAI multi-endpoint discovery also needs Reader on the Azure OpenAI resource. Foundry (classic) and New Foundry endpoints need Foundry User, or Azure AI User where older role names are still shown, on the target project or backing Foundry resource. See [Configure Model Endpoint Identity]({{ '/how-to/model_endpoint_identity_setup/' | relative_url }}). - - **Speech Service**: Start with `Cognitive Services Speech User`; add `Cognitive Services Speech Contributor` if transcription operations still require it. Managed identity also requires the custom-domain Speech endpoint, and text-to-speech needs the Speech Resource ID. - - **Video Indexer**: Grant the App Service system-assigned managed identity `Contributor` on the Video Indexer resource. If Azure asks for a user-assigned managed identity during Video Indexer resource creation, that identity is for the Video Indexer resource itself, not for Simple Chat runtime calls. -- **Dependencies**: The walkthrough will alert you if required services aren't configured when you enable dependent features (e.g., workspaces require embeddings, AI Search, and Document Intelligence) -- **Required vs Optional**: The walkthrough clearly indicates which settings are required vs optional based on your configuration choices \ No newline at end of file +### Save and test before rollout + + + +Use the floating **Save Settings** button after each set of changes. Use local **Test Connection** buttons before saving service endpoints. APIM-routed model settings require manually specified model names because automatic model fetching is not available through APIM. + + + +### Managed identity role reminders + + + +- Azure OpenAI chat access needs the **Cognitive Services OpenAI User** role. + +- Multi-endpoint model discovery also needs **Reader** on the Azure OpenAI resource. + +- Foundry endpoints need **Foundry User** or **Azure AI User** where older role names are still shown. + +- Speech managed identity starts with **Cognitive Services Speech User**; transcription may also require **Cognitive Services Speech Contributor**. Managed identity also requires the custom-domain Speech endpoint, and text-to-speech needs the Speech Resource ID. + +- Video Indexer runtime calls use the App Service system-assigned managed identity with **Contributor** on the Video Indexer resource. If Azure asks for a user-assigned managed identity during Video Indexer resource creation, that identity is for the Video Indexer resource itself, not for SimpleChat runtime calls. + + + +For model endpoint identity details, see [Configure model endpoint identity]({{ '/guides/model-endpoint-identity-setup/' | relative_url }}). + + + +### Mixed-source rollout controls + + + +Mixed-source rollout is independently reversible. Keep `enable_mixed_source_manifest`, `enable_mixed_source_chat_search`, `enable_mixed_source_analyze`, `enable_cross_format_compare`, and `enable_mixed_source_conversation_continuity` off until the preceding stage is validated. The subordinate relevance, Analyze All, one-to-many Compare (`enable_cross_format_compare_one_to_many`), and development telemetry stages also default off. `enable_mixed_source_development_telemetry` records aggregate counts and latency only; it must never capture prompts, evidence, source identifiers, filenames, or storage paths. + + + +### Dependency reminders + + + +The walkthrough warns when dependent features are enabled without required services. For example, workspaces require embeddings, Azure AI Search, and Document Intelligence before retrieval-backed features are usable. + + + +## Configuration Best Practices + +### Security + +Store secrets in the approved secret store where possible, prefer managed identity for Azure services, and validate role assignments before enabling role-gated features. + +### Performance + +Use connection tests, telemetry, and the Scale tab before increasing service tiers or throughput. + +### Operational + +Change one area at a time, save intentionally, and verify with both admin and normal-user accounts when a setting changes user-facing behavior. + +### Cost Management + +Enable optional services only when their features are needed, and monitor Cosmos DB, Search, OpenAI, Speech, Document Intelligence, and storage usage after rollout. + +## Troubleshooting Configuration Issues + +### Service Connection Failures + +Use each tab's test button, verify endpoint URLs and authentication mode, and confirm managed identity or key-based permissions on the Azure resource. + +### Multimedia Support + +Configure video processing and shared Speech settings from the Search and Extract tab before allowing users to upload multimedia files. + +### RBAC Access Control + +Use Entra app roles and the relevant Admin Settings toggles before enforcing role-gated admin or workspace behavior. + +### Feature Configuration Problems + +Review dependencies first. Workspace features need embeddings, Azure AI Search, and Document Intelligence; scale-out needs Redis-backed sessions; APIM-routed models need explicitly configured model names. + +## API Configuration Reference + +### Configuration API Endpoints + +Admin configuration changes are saved through the application's authenticated admin settings routes. Keep using the Admin Settings UI unless you are maintaining an approved automation path. + +### Configuration Schema + +The effective schema is organized by the Admin Settings tabs listed on this page and documented in the tab-specific references. diff --git a/docs/application_scaling.md b/docs/application_scaling.md deleted file mode 100644 index ad21e2070..000000000 --- a/docs/application_scaling.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -layout: showcase-page -title: "Application Scaling" -permalink: /application_scaling/ -menubar: docs_menu -accent: cyan -eyebrow: "Operate With Headroom" -description: "Scale Simple Chat by measuring pressure in the web tier, the data plane, the retrieval plane, and the AI services that sit behind them." -hero_icons: - - bi-graph-up-arrow - - bi-arrows-expand - - bi-speedometer2 -hero_pills: - - Monitor first - - Scale the bottleneck you can prove - - Keep sessions and retrieval consistent -hero_links: - - label: Review setup guidance - url: /setup_instructions/ - style: primary - - label: Manual setup details - url: /setup_instructions_manual/ - style: secondary ---- - -As load grows, Simple Chat usually hits one of four pressure points first: the App Service web tier, Cosmos DB throughput, Azure AI Search capacity, or rate limits on AI services. Treat each one independently so you do not pay to over-scale the wrong component. - -
-
-
-

App Service

-

Scale up when single requests need more memory or CPU. Scale out when concurrent users, long chats, and upload traffic rise.

-
-
-
-

Cosmos DB

-

Watch RU consumption and move to container-level autoscale for the busiest containers so chat and document traffic do not fight each other.

-
-
-
-

Azure AI Search

-

Replicas lift query throughput, partitions lift storage and indexing throughput, and higher tiers raise the service ceiling when S1 runs out.

-
-
-
-

AI Services

-

OpenAI, Document Intelligence, Speech, Content Safety, and Video Indexer are usually quota-driven. Route them through APIM if you need retries, load balancing, or consistent policy enforcement.

-
-
- -
-

General scaling principle

-

Use Azure Monitor and Application Insights to decide when to scale. Track CPU and memory, request latency, queue depth, RU consumption, search query latency, and rate-limit responses before changing service sizes.

-
- -## Azure App Service - -The App Service hosts the Python backend and is the first place to look when the UI feels slow or request concurrency rises. - -- Scale up by moving the App Service Plan to a larger SKU when individual requests are memory-heavy or CPU-heavy. -- Scale out by increasing instance count or using autoscale when you need more concurrent request capacity. -- Enable Redis-backed session storage before serious horizontal scaling so user sessions are not pinned to a single instance. -- Treat Redis as a prerequisite for clean high-availability behavior, not as an afterthought. - -## Azure Cosmos DB - -Cosmos DB stores settings, conversation history, document metadata, and related operational records. The core scaling lever is RU/s. - -- Prefer autoscale so throughput can move between 10% and 100% of the configured maximum. -- Start with database-level throughput during setup, then move critical containers to container-level autoscale once real usage patterns are visible. -- Good starting points for heavier containers are `messages`, `documents`, and `group_documents` at 4000 RU/s max. -- Lower-volume containers such as `settings`, `feedback`, and `archived_conversations` can usually begin around 1000 RU/s max. -- Watch for HTTP 429 responses and sustained RU saturation before increasing limits. - -![Scale - Cosmos DB Container Throughput Settings](./images/scale-cosmos.png) - -If users are distributed across regions, consider global replication as a latency and availability tool. Multi-region deployment improves failover posture, but it also requires testing around consistency and application behavior. - -## Azure AI Search - -Azure AI Search usually becomes the next limiter once retrieval traffic and indexed data volume climb. - -- Increase replicas to improve query throughput and basic high availability. -- Increase partitions to support larger indexes and faster indexing throughput. -- Plan partition count before the index becomes large because major partition changes often imply re-indexing. -- Move from S1 to S2, S3, or higher tiers when storage, replica, partition, or feature limits become the real constraint. - -For production, monitor query latency and indexing backlog rather than scaling on intuition. - -## Azure AI and Cognitive Services - -Azure OpenAI, Document Intelligence, Content Safety, Speech, and Video Indexer are API-driven services with quota and rate-limit ceilings. - -- Monitor tokens-per-minute, requests-per-minute, transactions-per-second, and backend throttling responses. -- Request quota increases when the service is healthy but the regional quota is the limiter. -- Use Azure API Management when you need retry policy, request shaping, throttling, or distribution across multiple backends. - -### APIM as the scaling control plane - -APIM is the recommended pattern when Simple Chat needs a stable gateway in front of AI services. - -- It can spread traffic across multiple Azure OpenAI deployments or regions. -- It can rate-limit callers before they hit backend service quotas. -- It can retry transient failures such as HTTP 429 responses. -- It gives operations teams one place to apply auth, policy, and logging rules. - -Simple Chat already supports APIM-backed configuration for GPT, embeddings, image generation, Content Safety, Document Intelligence, and Azure AI Search through Admin Settings. - -For a strong Azure OpenAI pattern, review the [AzureOpenAI-with-APIM repository](https://github.com/microsoft/AzureOpenAI-with-APIM). diff --git a/docs/assets/css/docs-media.scss b/docs/assets/css/docs-media.scss new file mode 100644 index 000000000..348945557 --- /dev/null +++ b/docs/assets/css/docs-media.scss @@ -0,0 +1,327 @@ +--- +# Compiled by Jekyll to /assets/css/docs-media.css +--- + +// docs-media.scss +// +// Styles for the documentation media system: screenshots, video poster cards, +// and the auto-resolving placeholder cards that show which asset file to create. +// +// Kept separate from main.scss so media styling can evolve independently of the +// site shell. Colors use Bootstrap 5.3 theme variables so light and dark mode +// are handled without duplicate rule sets. + +$docs-media-radius: 0.5rem; +$docs-media-gap: 0.75rem; +$bp-md: 768px; + +.docs-media { + margin: 1.75rem 0; + max-width: 100%; +} + +.docs-media img, +.docs-media video { + max-width: 100%; + height: auto; +} + +.docs-media-caption { + margin-top: 0.6rem; + font-size: 0.875rem; + line-height: 1.5; + color: var(--bs-secondary-color, #6c757d); +} + +// --------------------------------------------------------------------------- +// Rendered screenshot +// --------------------------------------------------------------------------- + +.docs-media-zoom { + display: block; + width: 100%; + padding: 0; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: $docs-media-radius; + background: var(--bs-body-bg, #fff); + cursor: zoom-in; + overflow: hidden; +} + +.docs-media-zoom:hover { + border-color: var(--bs-primary, #0d6efd); +} + +.docs-media-zoom:focus-visible { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 2px; +} + +.docs-media-image { + display: block; + width: 100%; +} + +// --------------------------------------------------------------------------- +// Placeholder card +// +// Shown until the asset file exists. It names the exact path to create so a +// contributor can fill the slot by dropping in one file. +// --------------------------------------------------------------------------- + +.docs-media--placeholder .docs-media-placeholder { + padding: 1.5rem; + border: 2px dashed var(--bs-border-color, #ced4da); + border-radius: $docs-media-radius; + background: var(--bs-tertiary-bg, #f8f9fa); + text-align: left; +} + +.docs-media-placeholder-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2.25rem; + margin-bottom: 0.75rem; + border-radius: 50%; + background: var(--bs-secondary-bg, #e9ecef); + color: var(--bs-secondary-color, #6c757d); + font-size: 1.1rem; +} + +.docs-media-placeholder-kicker { + margin: 0 0 0.2rem; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--bs-secondary-color, #6c757d); +} + +.docs-media-placeholder-title { + margin: 0 0 0.5rem; + font-size: 1rem; + font-weight: 600; + color: var(--bs-body-color, #212529); +} + +.docs-media-placeholder-capture { + margin: 0 0 0.75rem; + font-size: 0.9rem; + line-height: 1.55; + color: var(--bs-body-color, #212529); +} + +.docs-media-placeholder-path { + margin: 0 0 0.35rem; + font-size: 0.875rem; +} + +.docs-media-placeholder-path code { + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + background: var(--bs-secondary-bg, #e9ecef); + word-break: break-all; +} + +.docs-media-placeholder-hint { + margin: 0; + font-size: 0.8125rem; + color: var(--bs-secondary-color, #6c757d); +} + +.docs-media-placeholder-steps { + margin-top: 0.75rem; + font-size: 0.875rem; + color: var(--bs-secondary-color, #6c757d); +} + +.docs-media-placeholder-steps p { + margin: 0 0 0.35rem; +} + +.docs-media-placeholder-steps ol { + margin: 0; + padding-left: 1.25rem; +} + +.docs-media-placeholder-steps code { + word-break: break-all; +} + +// --------------------------------------------------------------------------- +// Unknown slot id +// --------------------------------------------------------------------------- + +.docs-media--error { + padding: 1rem 1.25rem; + border: 1px solid var(--bs-danger-border-subtle, #f5c2c7); + border-radius: $docs-media-radius; + background: var(--bs-danger-bg-subtle, #f8d7da); +} + +.docs-media-error-title { + margin: 0 0 0.25rem; + font-weight: 600; +} + +.docs-media-error-body { + margin: 0; + font-size: 0.875rem; +} + +// --------------------------------------------------------------------------- +// Video poster card +// +// Videos are never iframed and never committed as MP4. A local poster image +// links out to YouTube or Microsoft Stream. +// --------------------------------------------------------------------------- + +.docs-video-card { + display: flex; + flex-direction: column; + gap: $docs-media-gap; + padding: 0; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: $docs-media-radius; + background: var(--bs-body-bg, #fff); + color: inherit; + text-decoration: none; + overflow: hidden; +} + +.docs-video-card:hover { + border-color: var(--bs-primary, #0d6efd); +} + +.docs-video-card:focus-visible { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 2px; +} + +.docs-video-poster { + position: relative; + display: block; + background: var(--bs-secondary-bg, #e9ecef); + aspect-ratio: 16 / 9; +} + +.docs-video-poster-image { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.docs-video-poster-empty { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + font-size: 2rem; + color: var(--bs-secondary-color, #6c757d); +} + +.docs-video-play { + position: absolute; + top: 50%; + left: 50%; + display: flex; + align-items: center; + justify-content: center; + width: 3.25rem; + height: 3.25rem; + transform: translate(-50%, -50%); + border-radius: 50%; + background: rgba(0, 0, 0, 0.72); + color: #fff; + font-size: 1.5rem; +} + +.docs-video-meta { + display: block; + padding: 0 1rem 1rem; +} + +.docs-video-title { + display: block; + font-weight: 600; + line-height: 1.4; +} + +.docs-video-sub { + display: block; + margin-top: 0.2rem; + font-size: 0.8125rem; + color: var(--bs-secondary-color, #6c757d); +} + +// --------------------------------------------------------------------------- +// Lightbox +// --------------------------------------------------------------------------- + +.docs-media-lightbox { + position: fixed; + inset: 0; + z-index: 1080; + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + background: rgba(0, 0, 0, 0.82); +} + +.docs-media-lightbox img { + max-width: 100%; + max-height: 90vh; + border-radius: $docs-media-radius; + object-fit: contain; +} + +.docs-media-lightbox-close { + position: absolute; + top: 1rem; + right: 1rem; + display: flex; + align-items: center; + justify-content: center; + min-width: 44px; + min-height: 44px; + border: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.14); + color: #fff; + font-size: 1.25rem; +} + +.docs-media-lightbox-close:focus-visible { + outline: 2px solid #fff; + outline-offset: 2px; +} + +// --------------------------------------------------------------------------- +// Small viewports +// --------------------------------------------------------------------------- + +@media (max-width: ($bp-md - 0.02px)) { + .docs-media { + margin: 1.25rem 0; + } + + .docs-media--placeholder .docs-media-placeholder { + padding: 1.1rem; + } + + .docs-media-lightbox { + padding: 0.75rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .docs-media-zoom, + .docs-video-card { + transition: none; + } +} diff --git a/docs/assets/css/docs-search.scss b/docs/assets/css/docs-search.scss new file mode 100644 index 000000000..587bd1b6b --- /dev/null +++ b/docs/assets/css/docs-search.scss @@ -0,0 +1,177 @@ +--- +# Compiled by Jekyll to /assets/css/docs-search.css +--- + +// docs-search.scss +// +// Styles for the search results page, result filters, and the pieces of the +// search UI that are not part of the site shell. The mobile search sheet +// container, top bar search box, and mobile toggle are styled in main.scss. +// +// Colors use Bootstrap 5.3 theme variables so light and dark mode both work +// without duplicate rule sets. + +$bp-md: 768px; +$docs-search-radius: 0.5rem; + +// --------------------------------------------------------------------------- +// Results page +// --------------------------------------------------------------------------- + +.docs-search-page { + max-width: 48rem; +} + +.docs-search-page-field { + position: relative; + display: flex; + align-items: center; +} + +.docs-search-page-field .bi-search { + position: absolute; + left: 0.9rem; + color: var(--bs-secondary-color, #6c757d); + pointer-events: none; +} + +.docs-search-page-input { + width: 100%; + min-height: 2.75rem; + padding: 0.6rem 0.9rem 0.6rem 2.4rem; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: $docs-search-radius; + background: var(--bs-body-bg, #fff); + color: var(--bs-body-color, #212529); + font-size: 1rem; +} + +.docs-search-page-input:focus { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 1px; + border-color: var(--bs-primary, #0d6efd); +} + +.docs-search-page-status { + margin: 1rem 0 0.5rem; + font-size: 0.9rem; + color: var(--bs-secondary-color, #6c757d); +} + +.docs-search-page-results { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.docs-search-page-results .docs-search-result { + padding: 0.9rem 1rem; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: $docs-search-radius; + background: var(--bs-body-bg, #fff); +} + +// --------------------------------------------------------------------------- +// Section filters +// --------------------------------------------------------------------------- + +.docs-search-page-filters { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 1rem; +} + +.docs-search-filter { + min-height: 2.25rem; + padding: 0.35rem 0.75rem; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: 999px; + background: var(--bs-body-bg, #fff); + color: var(--bs-body-color, #212529); + font-size: 0.8125rem; + line-height: 1.3; +} + +.docs-search-filter:hover { + border-color: var(--bs-primary, #0d6efd); +} + +.docs-search-filter.is-active { + border-color: var(--bs-primary, #0d6efd); + background: var(--bs-primary, #0d6efd); + color: #fff; +} + +.docs-search-filter:focus-visible { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 2px; +} + +// --------------------------------------------------------------------------- +// Shared result presentation +// --------------------------------------------------------------------------- + +.docs-search-result.is-active { + background: var(--bs-secondary-bg, #e9ecef); + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: -2px; +} + +.docs-search-result mark { + padding: 0 0.1em; + border-radius: 0.15rem; + background: var(--bs-warning-bg-subtle, #fff3cd); + color: inherit; +} + +.docs-search-view-all { + display: block; + padding: 0.65rem 1rem; + border-top: 1px solid var(--bs-border-color, #dee2e6); + font-size: 0.8125rem; + font-weight: 600; + text-align: center; + text-decoration: none; +} + +// --------------------------------------------------------------------------- +// Search sheet field wrapper +// --------------------------------------------------------------------------- + +.docs-search-sheet-field { + position: relative; + display: flex; + flex: 1 1 auto; + align-items: center; +} + +.docs-search-sheet-field .bi-search { + position: absolute; + left: 0.85rem; + color: var(--bs-secondary-color, #6c757d); + pointer-events: none; +} + +.docs-search-sheet-field .docs-search-sheet-input { + padding-left: 2.35rem; +} + +// Prevent background scrolling while the sheet is open. +body.docs-search-sheet-open { + overflow: hidden; +} + +// The mobile trigger is only relevant below the md breakpoint; the inline top +// bar search takes over at and above it. +@media (min-width: $bp-md) { + .docs-mobile-search-toggle { + display: none; + } +} + +@media (max-width: ($bp-md - 0.02px)) { + .docs-search-page { + max-width: 100%; + } +} diff --git a/docs/assets/css/main.scss b/docs/assets/css/main.scss index 83300e60a..30f6cadeb 100644 --- a/docs/assets/css/main.scss +++ b/docs/assets/css/main.scss @@ -1,11 +1,14 @@ --- --- -/* Import Bootstrap (CDN is used in layout, this is for custom variables) */ -@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css'); +$bp-sm: 576px; +$bp-md: 768px; +$bp-lg: 992px; +$bp-xl: 1200px; /* Custom CSS Variables */ :root { + --docs-breakpoint-lg: 992; --simplechat-primary: #0078D4; --simplechat-secondary: #6c757d; --simplechat-success: #198754; @@ -286,7 +289,7 @@ a.citation-link:hover { } /* Responsive adjustments */ -@media (max-width: 768px) { +@media (max-width: ($bp-md - 0.02px)) { .sidebar-padding { padding-left: 0 !important; } @@ -310,18 +313,6 @@ pre[class*="language-"] { border: 1px solid var(--bs-border-color); } -/* DataTables integration */ -.dataTables_wrapper { - font-family: inherit; -} - -.dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter, -.dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_paginate { - color: var(--bs-body-color); -} - /* Utility classes */ .text-truncate-2 { display: -webkit-box; @@ -399,7 +390,7 @@ pre[class*="language-"] { box-shadow: 0 1.1rem 2.6rem rgba(15, 23, 42, 0.08); display: grid; gap: 2rem; - grid-template-columns: minmax(0, 1.65fr) minmax(220px, 0.85fr); + grid-template-columns: 1fr; padding: clamp(1.5rem, 3vw, 2.5rem); } @@ -641,7 +632,7 @@ pre[class*="language-"] { .latest-release-card-grid { display: grid; gap: 1rem; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); } .latest-release-card { @@ -995,43 +986,6 @@ pre[class*="language-"] { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0.75rem 1.8rem rgba(0, 0, 0, 0.28); } -@media (max-width: 991.98px) { - .latest-release-hero { - grid-template-columns: 1fr; - } - - .latest-release-hero-art { - justify-content: flex-start; - } -} - -@media (max-width: 767.98px) { - .latest-release-section-header, - .latest-release-archive-panel summary { - flex-direction: column; - } - - .latest-release-archive-toggle { - justify-content: flex-start; - } - - .latest-release-hero-actions, - .latest-release-card-actions { - width: 100%; - } - - .latest-release-hero-actions .btn, - .latest-release-card-actions .btn { - flex: 1 1 auto; - } - - .latest-release-rich-content > h2 + p, - .latest-release-rich-content > h2 + ul, - .latest-release-rich-content > h2 + ol { - padding-right: 1rem; - } -} - /* GitHub Pages redesign shell */ :root { --docs-bg: #f8f9fa; @@ -1239,6 +1193,14 @@ body.has-classification-banner .docs-topbar { width: min(32vw, 340px); } +.docs-mobile-search-toggle { + display: inline-flex; +} + +.docs-search-sheet { + display: none; +} + .docs-search-results { background: var(--docs-surface); border: 1px solid var(--docs-border); @@ -1287,6 +1249,10 @@ body.has-classification-banner .docs-topbar { padding: 0.75rem; } +.docs-search-sheet-close { + flex: 0 0 auto; +} + .docs-topbar-nav { display: none; gap: 1.4rem; @@ -1517,6 +1483,7 @@ body.has-classification-banner .docs-sidebar-backdrop { gap: 2rem; margin: 0 auto; max-width: 1180px; + min-width: 0; width: 100%; } @@ -1525,7 +1492,6 @@ body.has-classification-banner .docs-sidebar-backdrop { } .docs-article, -.docs-showcase-page, .docs-main-content > .latest-release-page, .docs-main-content > .page-content { margin: 0 auto; @@ -1559,10 +1525,6 @@ body.has-classification-banner .docs-sidebar-backdrop { font-size: 0.75rem; } -.docs-breadcrumb--hero { - margin-bottom: 0; -} - .docs-content-header { border-bottom: 1px solid var(--docs-border); margin-bottom: 1.5rem; @@ -1587,6 +1549,8 @@ body.has-classification-banner .docs-sidebar-backdrop { color: var(--docs-text); font-size: 1rem; line-height: 1.72; + min-width: 0; + overflow-wrap: anywhere; } .docs-prose > h2, @@ -1613,9 +1577,17 @@ body.has-classification-banner .docs-sidebar-backdrop { } .docs-prose a { + overflow-wrap: anywhere; text-underline-offset: 0.16rem; } +.docs-prose img, +.docs-prose video { + display: block; + height: auto; + max-width: 100%; +} + .docs-prose table { background: var(--docs-surface); border: 1px solid var(--docs-border); @@ -1623,10 +1595,29 @@ body.has-classification-banner .docs-sidebar-backdrop { border-radius: var(--docs-radius-md); border-spacing: 0; display: block; + max-width: 100%; overflow-x: auto; + -webkit-overflow-scrolling: touch; width: 100%; } +.docs-prose pre { + max-width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + white-space: pre; +} + +.docs-prose code { + overflow-wrap: anywhere; +} + +.docs-prose pre code { + overflow-wrap: normal; + white-space: inherit; + word-break: normal; +} + .docs-prose th, .docs-prose td { border-bottom: 1px solid var(--docs-border); @@ -1876,15 +1867,173 @@ h4:hover .heading-anchor, left: 1rem; } -@media (min-width: 768px) { +@media (max-width: ($bp-md - 0.02px)) { + .docs-search-sheet { + background: var(--docs-bg); + bottom: 0; + color: var(--docs-text); + display: flex; + flex-direction: column; + gap: 1rem; + left: 0; + opacity: 0; + padding: max(1rem, env(safe-area-inset-top)) 1rem max(1rem, env(safe-area-inset-bottom)); + pointer-events: none; + position: fixed; + right: 0; + top: 0; + transform: translateY(0.75rem); + transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease; + visibility: hidden; + z-index: 1110; + } + + .docs-search-sheet.is-open { + opacity: 1; + pointer-events: auto; + transform: translateY(0); + visibility: visible; + } + + .docs-search-sheet-header { + align-items: center; + display: flex; + gap: 0.75rem; + justify-content: space-between; + } + + .docs-search-sheet-input { + background: var(--docs-surface); + border: 1px solid var(--docs-border); + border-radius: var(--docs-radius-md); + color: var(--docs-text); + font-size: 1rem; + min-height: 3rem; + padding: 0.65rem 0.9rem; + width: 100%; + } + + .docs-search-sheet-input:focus { + border-color: var(--docs-primary-bright); + box-shadow: 0 0 0 0.2rem rgba(0, 120, 212, 0.18); + outline: 0; + } + + .docs-search-sheet-results { + display: grid; + gap: 0.5rem; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 1rem; + } + + .docs-search-sheet-close { + min-height: 2.75rem; + min-width: 2.75rem; + } + + .docs-topbar-inner { + gap: 0.6rem; + } + + .docs-content-header .page-title, + .latest-release-hero-title { + overflow-wrap: anywhere; + } + + .latest-release-section-header, + .latest-release-archive-panel summary { + flex-direction: column; + } + + .latest-release-archive-toggle { + justify-content: flex-start; + } + + .latest-release-hero-actions, + .latest-release-card-actions { + width: 100%; + } + + .latest-release-hero-actions .btn, + .latest-release-card-actions .btn { + flex: 1 1 auto; + } + + .latest-release-rich-content > h2 + p, + .latest-release-rich-content > h2 + ul, + .latest-release-rich-content > h2 + ol { + padding-right: 1rem; + } +} + +@media (min-width: $bp-md) { .docs-footer-inner { flex-direction: row; justify-content: space-between; } + + .docs-mobile-search-toggle { + display: none; + } + + .docs-topbar-search { + display: flex; + } +} + +@media (max-width: ($bp-lg - 0.02px)) { + .docs-brand span { + display: none; + } + + .latest-release-hero-art { + justify-content: flex-start; + } + + .docs-icon-button, + .docs-sidebar-close, + .docs-sidebar-section-toggle, + .docs-sidebar-link, + .docs-topbar-link, + .docs-mobile-search-toggle, + .docs-search-result, + .docs-copy-button, + .docs-shell .btn { + min-height: 2.75rem; + } + + .docs-icon-button, + .docs-sidebar-close, + .docs-mobile-search-toggle, + .docs-copy-button { + min-width: 2.75rem; + } + + .docs-on-this-page:not(.d-none) { + background: var(--docs-surface); + border: 1px solid var(--docs-border); + border-radius: var(--docs-radius-lg); + display: block; + margin-bottom: 1.25rem; + padding: 0.9rem 1rem; + } + + .docs-on-this-page-inner { + border-left: 0; + padding-left: 0; + position: static; + } + + .docs-on-this-page h2 { + margin-bottom: 0.5rem; + } } -@media (min-width: 992px) { +@media (min-width: $bp-lg) { .docs-mobile-nav-toggle, + .docs-mobile-search-toggle, .docs-sidebar-close { display: none; } @@ -1893,7 +2042,6 @@ h4:hover .heading-anchor, padding: 0 1.5rem; } - .docs-topbar-search, .docs-topbar-nav { display: flex; } @@ -1902,9 +2050,19 @@ h4:hover .heading-anchor, transform: translateX(0); } + .latest-release-hero { + grid-template-columns: minmax(0, 1.65fr) minmax(min(100%, 220px), 0.85fr); + } + .docs-main-content { + // The base rule sets width: 100%. Combined with the sidebar offset below + // that produced a viewport-width + sidebar-width box, which overflowed the + // page horizontally by exactly the sidebar width on every desktop screen. + // Letting the width resolve automatically keeps the content inside the + // remaining space next to the fixed sidebar. margin-left: var(--sidebar-width); padding: 2rem clamp(2rem, 7vw, 9.375rem) 3rem; + width: auto; } .docs-footer { @@ -1914,7 +2072,7 @@ h4:hover .heading-anchor, } } -@media (min-width: 1200px) { +@media (min-width: $bp-xl) { .docs-page-shell:not(.docs-page-shell--home) { grid-template-columns: minmax(0, 1fr) 230px; max-width: 1320px; @@ -1925,17 +2083,7 @@ h4:hover .heading-anchor, } } -@media (max-width: 991.98px) { - .docs-brand span { - display: none; - } - - .latest-release-hero { - grid-template-columns: 1fr; - } -} - -@media (max-width: 575.98px) { +@media (max-width: ($bp-sm - 0.02px)) { .docs-main-content { padding-left: 0.85rem; padding-right: 0.85rem; @@ -1944,4 +2092,15 @@ h4:hover .heading-anchor, .latest-release-hero { padding: 1.25rem; } -} \ No newline at end of file +} + +@media (prefers-reduced-motion: reduce) { + .docs-search-sheet, + #sidebar-nav.docs-sidebar, + .docs-sidebar-section-toggle i, + .docs-icon-button, + .heading-anchor, + .latest-release-thumbnail-media { + transition: none; + } +} diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index 85b5bb145..cedf9f55b 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -6,9 +6,6 @@ (function() { "use strict"; - let cachedSearchIndex = null; - let searchIndexPromise = null; - function createIcon(iconClass) { const icon = document.createElement("i"); icon.className = iconClass; @@ -194,206 +191,6 @@ }); } - function normalizeSearchIndex(items) { - if (!Array.isArray(items)) { - return []; - } - - return items.filter(function(item) { - return item && item.title && item.url; - }); - } - - function getNavigationSearchIndex() { - return Array.from(document.querySelectorAll(".docs-sidebar-link, .docs-topbar-link")).map(function(link) { - return { - title: link.textContent.trim(), - description: "", - keywords: "", - section: "Navigation", - url: link.href - }; - }); - } - - function getEmbeddedSearchIndex() { - const searchDataElement = document.getElementById("docs-search-data"); - if (!searchDataElement) { - return []; - } - - try { - return normalizeSearchIndex(JSON.parse(searchDataElement.textContent)); - } catch (error) { - return []; - } - } - - function getSearchIndex() { - if (cachedSearchIndex) { - return Promise.resolve(cachedSearchIndex); - } - - const embeddedIndex = getEmbeddedSearchIndex(); - if (embeddedIndex.length > 0) { - cachedSearchIndex = embeddedIndex; - return Promise.resolve(cachedSearchIndex); - } - - if (searchIndexPromise) { - return searchIndexPromise; - } - - const searchIndexUrl = window.siteSettings?.searchIndexUrl; - if (!searchIndexUrl || typeof fetch !== "function") { - cachedSearchIndex = getNavigationSearchIndex(); - return Promise.resolve(cachedSearchIndex); - } - - searchIndexPromise = fetch(searchIndexUrl, { headers: { Accept: "application/json" } }) - .then(function(response) { - if (!response.ok) { - throw new Error("Search index request failed"); - } - return response.json(); - }) - .then(function(items) { - cachedSearchIndex = normalizeSearchIndex(items); - return cachedSearchIndex; - }) - .catch(function() { - cachedSearchIndex = getNavigationSearchIndex(); - return cachedSearchIndex; - }); - - return searchIndexPromise; - } - - function createSearchResult(item) { - const result = document.createElement("a"); - result.className = "docs-search-result"; - result.href = item.url; - result.setAttribute("role", "option"); - - const title = document.createElement("span"); - title.className = "docs-search-result-title"; - title.textContent = item.title; - - const meta = document.createElement("span"); - meta.className = "docs-search-result-meta"; - meta.textContent = item.section || "Docs"; - - const description = document.createElement("span"); - description.className = "docs-search-result-description"; - description.textContent = item.description || "Open this documentation page."; - - result.appendChild(title); - result.appendChild(meta); - result.appendChild(description); - return result; - } - - function renderSearchResults(searchInput, resultsContainer) { - const query = searchInput.value.trim().toLowerCase(); - resultsContainer.replaceChildren(); - - if (query.length < 2) { - resultsContainer.classList.add("d-none"); - return; - } - - getSearchIndex().then(function(searchIndex) { - if (searchInput.value.trim().toLowerCase() !== query) { - return; - } - - resultsContainer.replaceChildren(); - const matches = searchIndex.map(function(item) { - const title = item.title.toLowerCase(); - const description = (item.description || "").toLowerCase(); - const keywords = (item.keywords || "").toLowerCase(); - const section = (item.section || "").toLowerCase(); - const searchableText = `${title} ${description} ${keywords} ${section}`; - let score = 10; - - if (title === query) { - score = 0; - } else if (title.startsWith(query)) { - score = 1; - } else if (title.includes(query)) { - score = 2; - } else if (keywords.includes(query)) { - score = 3; - } else if (section.includes(query)) { - score = 4; - } else if (description.includes(query)) { - score = 5; - } - - return { item, score, searchableText }; - }).filter(function(result) { - return result.searchableText.includes(query); - }).sort(function(firstResult, secondResult) { - if (firstResult.score !== secondResult.score) { - return firstResult.score - secondResult.score; - } - return firstResult.item.title.localeCompare(secondResult.item.title); - }).slice(0, 8).map(function(result) { - return result.item; - }); - - if (matches.length === 0) { - const emptyState = document.createElement("div"); - emptyState.className = "docs-search-empty"; - emptyState.textContent = "No matching docs found."; - resultsContainer.appendChild(emptyState); - } else { - matches.forEach(function(item) { - resultsContainer.appendChild(createSearchResult(item)); - }); - } - - resultsContainer.classList.remove("d-none"); - }); - } - - function initSearch() { - const searchInputs = document.querySelectorAll("[data-docs-search='true']"); - - searchInputs.forEach(function(searchInput) { - const searchRoot = searchInput.closest(".docs-search"); - const resultsContainer = searchRoot ? searchRoot.querySelector("[data-docs-search-results='true']") : null; - - if (!resultsContainer) { - return; - } - - let searchTimeout; - - searchInput.addEventListener("input", function() { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(function() { - renderSearchResults(searchInput, resultsContainer); - }, 120); - }); - - searchInput.addEventListener("keydown", function(event) { - if (event.key === "Escape") { - searchInput.value = ""; - resultsContainer.classList.add("d-none"); - } - }); - }); - - document.addEventListener("click", function(event) { - if (!event.target.closest(".docs-search")) { - document.querySelectorAll("[data-docs-search-results='true']").forEach(function(resultsContainer) { - resultsContainer.classList.add("d-none"); - }); - } - }); - } - function buildOnThisPage() { const tocContainers = document.querySelectorAll("[data-docs-toc='true']"); if (tocContainers.length === 0) { @@ -437,7 +234,6 @@ initSmoothScrolling(); addHeadingAnchors(); addCopyButtonsToCodeBlocks(); - initSearch(); buildOnThisPage(); document.addEventListener("themeChanged", function() { @@ -463,7 +259,6 @@ addCopyButtonsToCodeBlocks, initTooltips, initPopovers, - initSearch, buildOnThisPage }; })(); \ No newline at end of file diff --git a/docs/assets/js/media.js b/docs/assets/js/media.js new file mode 100644 index 000000000..6ada3cf21 --- /dev/null +++ b/docs/assets/js/media.js @@ -0,0 +1,182 @@ +// media.js +/** + * Lightbox for documentation screenshots. + * + * Screenshots rendered by _includes/media.html are wrapped in a button with + * data-docs-media-zoom. Clicking one opens a full-viewport overlay so readers + * can inspect UI detail without leaving the page. + * + * Implemented with plain DOM APIs so the docs site carries no third-party + * lightbox dependency. + */ + +(function() { + "use strict"; + + let overlay = null; + let overlayImage = null; + let lastFocusedElement = null; + + function buildOverlay() { + if (overlay) { + return overlay; + } + + overlay = document.createElement("div"); + overlay.className = "docs-media-lightbox d-none"; + overlay.setAttribute("role", "dialog"); + overlay.setAttribute("aria-modal", "true"); + overlay.setAttribute("aria-label", "Enlarged screenshot"); + + overlayImage = document.createElement("img"); + overlayImage.alt = ""; + + const closeButton = document.createElement("button"); + closeButton.type = "button"; + closeButton.className = "docs-media-lightbox-close"; + closeButton.setAttribute("aria-label", "Close enlarged screenshot"); + + const closeIcon = document.createElement("i"); + closeIcon.className = "bi bi-x-lg"; + closeIcon.setAttribute("aria-hidden", "true"); + closeButton.appendChild(closeIcon); + + closeButton.addEventListener("click", closeOverlay); + + overlay.addEventListener("click", function(event) { + if (event.target === overlay) { + closeOverlay(); + } + }); + + overlay.appendChild(overlayImage); + overlay.appendChild(closeButton); + document.body.appendChild(overlay); + + return overlay; + } + + function isOpen() { + return overlay !== null && !overlay.classList.contains("d-none"); + } + + /** + * Resolve a media source to a safe, same-origin URL. + * + * The value arrives from a data attribute in the rendered page, so it is DOM + * text flowing into a URL sink. Rather than trusting it, resolve it against + * the document and require an http(s) same-origin result with an image + * extension. That rejects scheme-based payloads such as javascript: and + * data: URLs, and rejects off-site sources, which the documentation site + * never uses because all media is local. + */ + function safeMediaUrl(rawSource) { + if (typeof rawSource !== "string" || rawSource.length === 0) { + return null; + } + + let resolved; + try { + resolved = new URL(rawSource, document.baseURI); + } catch (error) { + return null; + } + + if (resolved.protocol !== "http:" && resolved.protocol !== "https:") { + return null; + } + + if (resolved.origin !== window.location.origin) { + return null; + } + + if (!/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(resolved.pathname)) { + return null; + } + + return resolved.href; + } + + function openOverlay(source, altText) { + const safeSource = safeMediaUrl(source); + if (safeSource === null) { + return; + } + + lastFocusedElement = document.activeElement; + + buildOverlay(); + overlayImage.src = safeSource; + overlayImage.alt = typeof altText === "string" ? altText : ""; + overlay.classList.remove("d-none"); + document.body.classList.add("docs-media-lightbox-open"); + + const closeButton = overlay.querySelector(".docs-media-lightbox-close"); + if (closeButton) { + closeButton.focus(); + } + } + + function closeOverlay() { + if (!isOpen()) { + return; + } + + overlay.classList.add("d-none"); + overlayImage.src = ""; + document.body.classList.remove("docs-media-lightbox-open"); + + if (lastFocusedElement && typeof lastFocusedElement.focus === "function") { + lastFocusedElement.focus(); + } + + lastFocusedElement = null; + } + + function trapFocus(event) { + if (!isOpen() || event.key !== "Tab") { + return; + } + + // The overlay only contains the close button, so keep focus pinned to it. + const closeButton = overlay.querySelector(".docs-media-lightbox-close"); + if (closeButton) { + event.preventDefault(); + closeButton.focus(); + } + } + + function initMediaZoom() { + const triggers = document.querySelectorAll("[data-docs-media-zoom='true']"); + + triggers.forEach(function(trigger) { + trigger.addEventListener("click", function() { + const source = trigger.getAttribute("data-docs-media-src"); + if (!source) { + return; + } + openOverlay(source, trigger.getAttribute("data-docs-media-alt")); + }); + }); + + document.addEventListener("keydown", function(event) { + if (event.key === "Escape") { + closeOverlay(); + return; + } + trapFocus(event); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initMediaZoom); + } else { + initMediaZoom(); + } + + window.SimpleChat = window.SimpleChat || {}; + window.SimpleChat.Media = { + openOverlay, + closeOverlay + }; +})(); diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js new file mode 100644 index 000000000..f62b94c7b --- /dev/null +++ b/docs/assets/js/search.js @@ -0,0 +1,650 @@ +// search.js +/** + * Full-text documentation search. + * + * Replaces the previous title-only substring match. The index at + * /search-index.json carries page body text, so queries match what readers + * actually read rather than only page titles. + * + * Provides three surfaces backed by one index: + * - a typeahead dropdown in the desktop top bar + * - a full-screen search sheet on small viewports + * - a full results page at /search/ with section filters + * + * Lunr is vendored locally at assets/js/vendor/lunr-2.3.9/ because the repo + * forbids CDN-hosted browser runtime code. + */ + +(function() { + "use strict"; + + const MAX_DROPDOWN_RESULTS = 8; + const MAX_PAGE_RESULTS = 50; + const EXCERPT_RADIUS = 90; + + let documents = []; + let documentsByUrl = {}; + let lunrIndex = null; + let loadPromise = null; + let activeSectionFilter = ""; + + function getIndexUrl() { + const configured = window.siteSettings && window.siteSettings.searchIndexUrl; + return configured || "/search-index.json"; + } + + function buildLunrIndex(entries) { + if (!window.lunr) { + return null; + } + + return window.lunr(function() { + this.ref("url"); + this.field("title", { boost: 12 }); + this.field("description", { boost: 5 }); + this.field("section", { boost: 3 }); + this.field("body"); + + entries.forEach(function(entry) { + this.add(entry); + }, this); + }); + } + + function loadIndex() { + if (loadPromise) { + return loadPromise; + } + + loadPromise = fetch(getIndexUrl(), { headers: { Accept: "application/json" } }) + .then(function(response) { + if (!response.ok) { + throw new Error("Search index request failed"); + } + return response.json(); + }) + .then(function(entries) { + documents = Array.isArray(entries) ? entries.filter(function(entry) { + return entry && entry.title && entry.url; + }) : []; + + documentsByUrl = {}; + documents.forEach(function(entry) { + documentsByUrl[entry.url] = entry; + }); + + lunrIndex = buildLunrIndex(documents); + return documents; + }) + .catch(function() { + documents = []; + documentsByUrl = {}; + lunrIndex = null; + return documents; + }); + + return loadPromise; + } + + /** + * Strip characters that are operators in Lunr query syntax so a user typing + * something like "c++" or "a:b" does not throw a query parse error. + */ + function sanitizeTerm(term) { + return term.replace(/[~^:*+\-()]/g, ""); + } + + function substringSearch(query) { + const needle = query.toLowerCase(); + + return documents.filter(function(entry) { + const haystack = `${entry.title} ${entry.description} ${entry.section} ${entry.body}`.toLowerCase(); + return haystack.indexOf(needle) !== -1; + }).map(function(entry) { + const title = entry.title.toLowerCase(); + let score = 1; + if (title === needle) { + score = 1000; + } else if (title.indexOf(needle) === 0) { + score = 500; + } else if (title.indexOf(needle) !== -1) { + score = 250; + } + return { ref: entry.url, score: score }; + }).sort(function(a, b) { + return b.score - a.score; + }); + } + + function runQuery(rawQuery) { + const query = rawQuery.trim(); + if (query.length < 2) { + return []; + } + + if (!lunrIndex) { + return substringSearch(query); + } + + const terms = query.split(/\s+/).map(sanitizeTerm).filter(function(term) { + return term.length > 0; + }); + + if (terms.length === 0) { + return substringSearch(query); + } + + // Match the exact term strongly, and also allow prefix matches so results + // appear while the reader is still typing. + const lunrQuery = terms.map(function(term) { + return `${term}^3 ${term}*`; + }).join(" "); + + let results = []; + try { + results = lunrIndex.search(lunrQuery); + } catch (error) { + results = []; + } + + if (results.length === 0) { + results = substringSearch(query); + } + + return results; + } + + /** + * Build a short excerpt around the first match and highlight matched terms. + * Uses DOM nodes rather than HTML strings so page content can never be + * injected as markup. + */ + function buildExcerpt(entry, query) { + const source = entry.body || entry.description || ""; + const container = document.createElement("span"); + container.className = "docs-search-result-description"; + + if (!source) { + container.textContent = "Open this documentation page."; + return container; + } + + const lowerSource = source.toLowerCase(); + const firstTerm = query.trim().split(/\s+/)[0].toLowerCase(); + let matchIndex = firstTerm ? lowerSource.indexOf(firstTerm) : -1; + + if (matchIndex === -1) { + matchIndex = 0; + } + + const start = Math.max(0, matchIndex - EXCERPT_RADIUS); + const end = Math.min(source.length, matchIndex + EXCERPT_RADIUS * 2); + const snippet = source.slice(start, end); + + if (start > 0) { + container.appendChild(document.createTextNode("\u2026")); + } + + const terms = query.trim().split(/\s+/).map(function(term) { + return term.toLowerCase(); + }).filter(function(term) { + return term.length > 1; + }); + + let cursor = 0; + const lowerSnippet = snippet.toLowerCase(); + + while (cursor < snippet.length) { + let nextIndex = -1; + let nextTerm = ""; + + terms.forEach(function(term) { + const found = lowerSnippet.indexOf(term, cursor); + if (found !== -1 && (nextIndex === -1 || found < nextIndex)) { + nextIndex = found; + nextTerm = term; + } + }); + + if (nextIndex === -1) { + container.appendChild(document.createTextNode(snippet.slice(cursor))); + break; + } + + container.appendChild(document.createTextNode(snippet.slice(cursor, nextIndex))); + + const mark = document.createElement("mark"); + mark.textContent = snippet.slice(nextIndex, nextIndex + nextTerm.length); + container.appendChild(mark); + + cursor = nextIndex + nextTerm.length; + } + + if (end < source.length) { + container.appendChild(document.createTextNode("\u2026")); + } + + return container; + } + + function createResultElement(entry, query) { + const result = document.createElement("a"); + result.className = "docs-search-result"; + result.href = entry.url; + result.setAttribute("role", "option"); + + const title = document.createElement("span"); + title.className = "docs-search-result-title"; + title.textContent = entry.title; + + const meta = document.createElement("span"); + meta.className = "docs-search-result-meta"; + meta.textContent = entry.section || "Docs"; + + result.appendChild(title); + result.appendChild(meta); + result.appendChild(buildExcerpt(entry, query)); + return result; + } + + function resolveEntries(results) { + return results.map(function(result) { + return documentsByUrl[result.ref]; + }).filter(Boolean); + } + + // ----------------------------------------------------------------------- + // Typeahead (top bar and mobile sheet) + // ----------------------------------------------------------------------- + + function renderDropdown(input, resultsContainer) { + const query = input.value.trim(); + resultsContainer.replaceChildren(); + + if (query.length < 2) { + resultsContainer.classList.add("d-none"); + return; + } + + loadIndex().then(function() { + if (input.value.trim() !== query) { + return; + } + + resultsContainer.replaceChildren(); + const entries = resolveEntries(runQuery(query)).slice(0, MAX_DROPDOWN_RESULTS); + + if (entries.length === 0) { + const empty = document.createElement("div"); + empty.className = "docs-search-empty"; + empty.textContent = "No matching docs found."; + resultsContainer.appendChild(empty); + } else { + entries.forEach(function(entry) { + resultsContainer.appendChild(createResultElement(entry, query)); + }); + + const viewAll = document.createElement("a"); + viewAll.className = "docs-search-view-all"; + viewAll.href = `${window.siteSettings && window.siteSettings.baseurl ? window.siteSettings.baseurl : ""}/search/?q=${encodeURIComponent(query)}`; + viewAll.textContent = "See all results"; + resultsContainer.appendChild(viewAll); + } + + resultsContainer.classList.remove("d-none"); + }); + } + + function moveActiveResult(resultsContainer, direction) { + const items = Array.from(resultsContainer.querySelectorAll(".docs-search-result")); + if (items.length === 0) { + return; + } + + const currentIndex = items.findIndex(function(item) { + return item.classList.contains("is-active"); + }); + + let nextIndex = currentIndex + direction; + if (nextIndex < 0) { + nextIndex = items.length - 1; + } else if (nextIndex >= items.length) { + nextIndex = 0; + } + + items.forEach(function(item) { + item.classList.remove("is-active"); + }); + + items[nextIndex].classList.add("is-active"); + items[nextIndex].scrollIntoView({ block: "nearest" }); + } + + function bindTypeahead(input, resultsContainer) { + let debounceTimer = null; + + input.addEventListener("input", function() { + window.clearTimeout(debounceTimer); + debounceTimer = window.setTimeout(function() { + renderDropdown(input, resultsContainer); + }, 120); + }); + + input.addEventListener("focus", function() { + loadIndex(); + }); + + input.addEventListener("keydown", function(event) { + if (event.key === "Escape") { + input.value = ""; + resultsContainer.classList.add("d-none"); + return; + } + + if (event.key === "ArrowDown") { + event.preventDefault(); + moveActiveResult(resultsContainer, 1); + return; + } + + if (event.key === "ArrowUp") { + event.preventDefault(); + moveActiveResult(resultsContainer, -1); + return; + } + + if (event.key === "Enter") { + const active = resultsContainer.querySelector(".docs-search-result.is-active"); + if (active) { + event.preventDefault(); + window.location.href = active.href; + return; + } + + const query = input.value.trim(); + if (query.length >= 2) { + event.preventDefault(); + const base = window.siteSettings && window.siteSettings.baseurl ? window.siteSettings.baseurl : ""; + window.location.href = `${base}/search/?q=${encodeURIComponent(query)}`; + } + } + }); + } + + function initTypeaheads() { + document.querySelectorAll("[data-docs-search='true']").forEach(function(input) { + const root = input.closest(".docs-search") || input.closest(".docs-search-sheet"); + const resultsContainer = root ? root.querySelector("[data-docs-search-results='true']") : null; + + if (resultsContainer) { + bindTypeahead(input, resultsContainer); + } + }); + + document.addEventListener("click", function(event) { + if (!event.target.closest(".docs-search") && !event.target.closest(".docs-search-sheet")) { + document.querySelectorAll(".docs-topbar-search [data-docs-search-results='true']").forEach(function(container) { + container.classList.add("d-none"); + }); + } + }); + } + + // ----------------------------------------------------------------------- + // Mobile search sheet + // ----------------------------------------------------------------------- + + function initMobileSheet() { + const sheet = document.getElementById("docs-search-sheet"); + const openButton = document.getElementById("docs-mobile-search-toggle"); + + if (!sheet || !openButton) { + return; + } + + const closeButton = sheet.querySelector("[data-docs-search-sheet-close='true']"); + const input = sheet.querySelector("[data-docs-search='true']"); + let lastFocused = null; + + function openSheet() { + lastFocused = document.activeElement; + sheet.classList.remove("d-none"); + sheet.classList.add("is-open"); + document.body.classList.add("docs-search-sheet-open"); + loadIndex(); + if (input) { + input.focus(); + } + } + + function closeSheet() { + sheet.classList.add("d-none"); + sheet.classList.remove("is-open"); + document.body.classList.remove("docs-search-sheet-open"); + if (lastFocused && typeof lastFocused.focus === "function") { + lastFocused.focus(); + } + } + + openButton.addEventListener("click", openSheet); + + if (closeButton) { + closeButton.addEventListener("click", closeSheet); + } + + sheet.addEventListener("keydown", function(event) { + if (event.key === "Escape") { + closeSheet(); + return; + } + + if (event.key !== "Tab") { + return; + } + + // Keep focus inside the sheet while it is open. + const focusable = Array.from(sheet.querySelectorAll("input, button, a[href]")).filter(function(element) { + return element.offsetParent !== null; + }); + + if (focusable.length === 0) { + return; + } + + const firstElement = focusable[0]; + const lastElement = focusable[focusable.length - 1]; + + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + } else if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } + }); + + window.SimpleChat = window.SimpleChat || {}; + window.SimpleChat.SearchSheet = { openSheet, closeSheet }; + } + + // ----------------------------------------------------------------------- + // Results page + // ----------------------------------------------------------------------- + + function getQueryParam(name) { + const params = new URLSearchParams(window.location.search); + return params.get(name) || ""; + } + + function renderFilters(entries, filtersContainer, onChange) { + const counts = {}; + entries.forEach(function(entry) { + const section = entry.section || "Docs"; + counts[section] = (counts[section] || 0) + 1; + }); + + const sections = Object.keys(counts).sort(); + filtersContainer.replaceChildren(); + + if (sections.length < 2) { + filtersContainer.classList.add("d-none"); + return; + } + + function makeButton(label, value, count) { + const button = document.createElement("button"); + button.type = "button"; + button.className = "docs-search-filter"; + if (activeSectionFilter === value) { + button.classList.add("is-active"); + } + button.textContent = count === null ? label : `${label} (${count})`; + button.addEventListener("click", function() { + activeSectionFilter = value; + onChange(); + }); + return button; + } + + filtersContainer.appendChild(makeButton("All", "", entries.length)); + sections.forEach(function(section) { + filtersContainer.appendChild(makeButton(section, section, counts[section])); + }); + + filtersContainer.classList.remove("d-none"); + } + + function initSearchPage() { + const page = document.querySelector("[data-docs-search-page='true']"); + if (!page) { + return; + } + + const input = page.querySelector("[data-docs-search-page-input='true']"); + const resultsContainer = page.querySelector("[data-docs-search-page-results='true']"); + const statusElement = page.querySelector("[data-docs-search-status='true']"); + const filtersContainer = page.querySelector("[data-docs-search-filters='true']"); + const form = page.querySelector("[data-docs-search-form='true']"); + + if (!input || !resultsContainer) { + return; + } + + function render() { + const query = input.value.trim(); + resultsContainer.replaceChildren(); + + if (query.length < 2) { + statusElement.textContent = "Type at least two characters to search."; + filtersContainer.classList.add("d-none"); + return; + } + + loadIndex().then(function() { + const allEntries = resolveEntries(runQuery(query)); + + renderFilters(allEntries, filtersContainer, render); + + const entries = activeSectionFilter + ? allEntries.filter(function(entry) { + return (entry.section || "Docs") === activeSectionFilter; + }) + : allEntries; + + resultsContainer.replaceChildren(); + + if (entries.length === 0) { + statusElement.textContent = `No results for "${query}".`; + return; + } + + statusElement.textContent = `${entries.length} result${entries.length === 1 ? "" : "s"} for "${query}".`; + + entries.slice(0, MAX_PAGE_RESULTS).forEach(function(entry) { + resultsContainer.appendChild(createResultElement(entry, query)); + }); + }); + } + + if (form) { + form.addEventListener("submit", function(event) { + event.preventDefault(); + render(); + }); + } + + let debounceTimer = null; + input.addEventListener("input", function() { + window.clearTimeout(debounceTimer); + debounceTimer = window.setTimeout(render, 150); + }); + + const initialQuery = getQueryParam("q"); + if (initialQuery) { + input.value = initialQuery; + } + + input.focus(); + render(); + } + + // ----------------------------------------------------------------------- + // Keyboard shortcuts + // ----------------------------------------------------------------------- + + function focusPrimarySearch() { + const sheetToggle = document.getElementById("docs-mobile-search-toggle"); + const topbarInput = document.getElementById("docs-topbar-search-input"); + + if (topbarInput && topbarInput.offsetParent !== null) { + topbarInput.focus(); + topbarInput.select(); + return; + } + + if (sheetToggle && window.SimpleChat && window.SimpleChat.SearchSheet) { + window.SimpleChat.SearchSheet.openSheet(); + } + } + + function initShortcuts() { + document.addEventListener("keydown", function(event) { + const target = event.target; + const isTypingContext = target && ( + target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable + ); + + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") { + event.preventDefault(); + focusPrimarySearch(); + return; + } + + if (event.key === "/" && !isTypingContext) { + event.preventDefault(); + focusPrimarySearch(); + } + }); + } + + function init() { + initTypeaheads(); + initMobileSheet(); + initSearchPage(); + initShortcuts(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + window.SimpleChat = window.SimpleChat || {}; + window.SimpleChat.Search = { + loadIndex, + runQuery + }; +})(); diff --git a/docs/assets/js/sidebar.js b/docs/assets/js/sidebar.js index ad4ed9fbb..673aa654b 100644 --- a/docs/assets/js/sidebar.js +++ b/docs/assets/js/sidebar.js @@ -6,10 +6,33 @@ (function() { "use strict"; - const DESKTOP_BREAKPOINT = 992; + const FALLBACK_DESKTOP_BREAKPOINT = 992; + const FOCUSABLE_SELECTOR = [ + "a[href]", + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "[tabindex]:not([tabindex='-1'])" + ].join(","); + + let lastFocusedElement = null; + + function getDesktopBreakpoint() { + const rawBreakpoint = getComputedStyle(document.documentElement) + .getPropertyValue("--docs-breakpoint-lg") + .trim(); + const parsedBreakpoint = Number.parseFloat(rawBreakpoint); + + if (Number.isFinite(parsedBreakpoint) && parsedBreakpoint > 0) { + return parsedBreakpoint; + } + + return FALLBACK_DESKTOP_BREAKPOINT; + } function isDesktop() { - return window.innerWidth >= DESKTOP_BREAKPOINT; + return window.innerWidth >= getDesktopBreakpoint(); } function getElements() { @@ -21,6 +44,78 @@ }; } + function getFocusableElements(container) { + if (!container) { + return []; + } + + return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(function(element) { + return element.getClientRects().length > 0 || element === document.activeElement; + }); + } + + function focusSidebar() { + const { sidebar, closeButton } = getElements(); + + if (!sidebar || isDesktop()) { + return; + } + + window.requestAnimationFrame(function() { + const focusableElements = getFocusableElements(sidebar); + const target = closeButton || focusableElements[0] || sidebar; + + if (!target.hasAttribute("tabindex") && target === sidebar) { + target.setAttribute("tabindex", "-1"); + } + + target.focus({ preventScroll: true }); + }); + } + + function restoreSidebarFocus() { + const { openButton } = getElements(); + const focusTarget = openButton || lastFocusedElement; + + if (focusTarget && typeof focusTarget.focus === "function") { + focusTarget.focus({ preventScroll: true }); + } + + lastFocusedElement = null; + } + + function isMobileSidebarOpen() { + const { sidebar } = getElements(); + + return Boolean(sidebar && sidebar.classList.contains("is-open") && !isDesktop()); + } + + function trapSidebarFocus(event) { + if (event.key !== "Tab" || !isMobileSidebarOpen()) { + return; + } + + const { sidebar } = getElements(); + const focusableElements = getFocusableElements(sidebar); + + if (focusableElements.length === 0) { + event.preventDefault(); + sidebar.focus({ preventScroll: true }); + return; + } + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus({ preventScroll: true }); + } else if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus({ preventScroll: true }); + } + } + function setSidebarOpen(isOpen) { const { sidebar, openButton, backdrop } = getElements(); @@ -28,10 +123,22 @@ return; } + const wasOpen = sidebar.classList.contains("is-open"); + + if (isOpen && !wasOpen) { + lastFocusedElement = document.activeElement; + } + sidebar.classList.toggle("is-open", isOpen); openButton.setAttribute("aria-expanded", isOpen ? "true" : "false"); backdrop.classList.toggle("d-none", !isOpen || isDesktop()); document.body.classList.toggle("docs-nav-open", isOpen && !isDesktop()); + + if (isOpen) { + focusSidebar(); + } else if (wasOpen && !isDesktop()) { + restoreSidebarFocus(); + } } function closeSidebar() { @@ -112,9 +219,11 @@ } document.addEventListener("keydown", function(event) { - if (event.key === "Escape") { + if (event.key === "Escape" && isMobileSidebarOpen()) { closeSidebar(); } + + trapSidebarFocus(event); }); window.addEventListener("resize", syncSidebarForViewport); diff --git a/docs/assets/js/vendor/lunr-2.3.9/LICENSE b/docs/assets/js/vendor/lunr-2.3.9/LICENSE new file mode 100644 index 000000000..e6e4e21b2 --- /dev/null +++ b/docs/assets/js/vendor/lunr-2.3.9/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2013 by Oliver Nightingale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/assets/js/vendor/lunr-2.3.9/lunr.min.js b/docs/assets/js/vendor/lunr-2.3.9/lunr.min.js new file mode 100644 index 000000000..cdc94cd39 --- /dev/null +++ b/docs/assets/js/vendor/lunr-2.3.9/lunr.min.js @@ -0,0 +1,6 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ +!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.3.9",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/docs/assets/vendor/bootstrap-5.3.0/LICENSE b/docs/assets/vendor/bootstrap-5.3.0/LICENSE new file mode 100644 index 000000000..6633b55fe --- /dev/null +++ b/docs/assets/vendor/bootstrap-5.3.0/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-2023 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/assets/vendor/bootstrap-5.3.0/bootstrap.bundle.min.js b/docs/assets/vendor/bootstrap-5.3.0/bootstrap.bundle.min.js new file mode 100644 index 000000000..fe19d88ae --- /dev/null +++ b/docs/assets/vendor/bootstrap-5.3.0/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.0 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return M(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return M(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(y,""),T[t]||t}const P={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))I(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==N(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=M(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function M(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function j(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const H={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=j(t.dataset[n])}return e},getDataAttribute:(t,e)=>j(t.getAttribute(`data-bs-${F(e)}`))};class ${static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?H.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?H.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends ${constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.0"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;P.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))};class q extends W{static get NAME(){return"alert"}close(){if(P.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),P.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(q,"close"),m(q);const V='[data-bs-toggle="button"]';class K extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=K.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}P.on(document,"click.bs.button.data-api",V,(t=>{t.preventDefault();const e=t.target.closest(V);K.getOrCreateInstance(e).toggle()})),m(K);const Q={endCallback:null,leftCallback:null,rightCallback:null},X={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Y extends ${constructor(t,e){super(),this._element=t,t&&Y.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Q}static get DefaultType(){return X}static get NAME(){return"swipe"}dispose(){P.off(this._element,".bs.swipe")}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(P.on(this._element,"pointerdown.bs.swipe",(t=>this._start(t))),P.on(this._element,"pointerup.bs.swipe",(t=>this._end(t))),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.swipe",(t=>this._start(t))),P.on(this._element,"touchmove.bs.swipe",(t=>this._move(t))),P.on(this._element,"touchend.bs.swipe",(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const U="next",G="prev",J="left",Z="right",tt="slid.bs.carousel",et="carousel",it="active",nt={ArrowLeft:Z,ArrowRight:J},st={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class rt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===et&&this.cycle()}static get Default(){return st}static get DefaultType(){return ot}static get NAME(){return"carousel"}next(){this._slide(U)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(G)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?P.one(this._element,tt,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void P.one(this._element,tt,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?U:G;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",(()=>this.pause())),P.on(this._element,"mouseleave.bs.carousel",(()=>this._maybeEnableCycle()))),this._config.touch&&Y.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))P.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(J)),rightCallback:()=>this._slide(this._directionToOrder(Z)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new Y(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=nt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(it),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===U,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>P.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r("slide.bs.carousel").defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(it),i.classList.remove(it,c,l),this._isSliding=!1,r(tt)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(".active.carousel-item",this._element)}_getItems(){return z.find(".carousel-item",this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===J?G:U:t===J?U:G}_orderToDirection(t){return p()?t===G?J:Z:t===G?Z:J}static jQueryInterface(t){return this.each((function(){const e=rt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(et))return;t.preventDefault();const i=rt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===H.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),P.on(window,"load.bs.carousel.data-api",(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)rt.getOrCreateInstance(e)})),m(rt);const at="show",lt="collapse",ct="collapsing",ht='[data-bs-toggle="collapse"]',dt={parent:null,toggle:!0},ut={parent:"(null|element)",toggle:"boolean"};class ft extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(ht);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return dt}static get DefaultType(){return ut}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>ft.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(lt),this._element.classList.add(ct),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ct),this._element.classList.add(lt,at),this._element.style[e]="",P.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(ct),this._element.classList.remove(lt,at);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ct),this._element.classList.add(lt),P.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(at)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(ht);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(":scope .collapse .collapse",this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=ft.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}P.on(document,"click.bs.collapse.data-api",ht,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))ft.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(ft);var pt="top",mt="bottom",gt="right",_t="left",bt="auto",vt=[pt,mt,gt,_t],yt="start",wt="end",At="clippingParents",Et="viewport",Tt="popper",Ct="reference",Ot=vt.reduce((function(t,e){return t.concat([e+"-"+yt,e+"-"+wt])}),[]),xt=[].concat(vt,[bt]).reduce((function(t,e){return t.concat([e,e+"-"+yt,e+"-"+wt])}),[]),kt="beforeRead",Lt="read",St="afterRead",Dt="beforeMain",It="main",Nt="afterMain",Pt="beforeWrite",Mt="write",jt="afterWrite",Ft=[kt,Lt,St,Dt,It,Nt,Pt,Mt,jt];function Ht(t){return t?(t.nodeName||"").toLowerCase():null}function $t(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function Wt(t){return t instanceof $t(t).Element||t instanceof Element}function Bt(t){return t instanceof $t(t).HTMLElement||t instanceof HTMLElement}function zt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof $t(t).ShadowRoot||t instanceof ShadowRoot)}const Rt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];Bt(s)&&Ht(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});Bt(n)&&Ht(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function qt(t){return t.split("-")[0]}var Vt=Math.max,Kt=Math.min,Qt=Math.round;function Xt(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Yt(){return!/^((?!chrome|android).)*safari/i.test(Xt())}function Ut(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&Bt(t)&&(s=t.offsetWidth>0&&Qt(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Qt(n.height)/t.offsetHeight||1);var r=(Wt(t)?$t(t):window).visualViewport,a=!Yt()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Gt(t){var e=Ut(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Jt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&zt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Zt(t){return $t(t).getComputedStyle(t)}function te(t){return["table","td","th"].indexOf(Ht(t))>=0}function ee(t){return((Wt(t)?t.ownerDocument:t.document)||window.document).documentElement}function ie(t){return"html"===Ht(t)?t:t.assignedSlot||t.parentNode||(zt(t)?t.host:null)||ee(t)}function ne(t){return Bt(t)&&"fixed"!==Zt(t).position?t.offsetParent:null}function se(t){for(var e=$t(t),i=ne(t);i&&te(i)&&"static"===Zt(i).position;)i=ne(i);return i&&("html"===Ht(i)||"body"===Ht(i)&&"static"===Zt(i).position)?e:i||function(t){var e=/firefox/i.test(Xt());if(/Trident/i.test(Xt())&&Bt(t)&&"fixed"===Zt(t).position)return null;var i=ie(t);for(zt(i)&&(i=i.host);Bt(i)&&["html","body"].indexOf(Ht(i))<0;){var n=Zt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function oe(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function re(t,e,i){return Vt(t,Kt(e,i))}function ae(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function le(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const ce={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=qt(i.placement),l=oe(a),c=[_t,gt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return ae("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:le(t,vt))}(s.padding,i),d=Gt(o),u="y"===l?pt:_t,f="y"===l?mt:gt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=se(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=re(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Jt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(t){return t.split("-")[1]}var de={top:"auto",right:"auto",bottom:"auto",left:"auto"};function ue(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=_t,y=pt,w=window;if(c){var A=se(i),E="clientHeight",T="clientWidth";A===$t(i)&&"static"!==Zt(A=ee(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===pt||(s===_t||s===gt)&&o===wt)&&(y=mt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==_t&&(s!==pt&&s!==mt||o!==wt)||(v=gt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&de),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:Qt(i*s)/s||0,y:Qt(n*s)/s||0}}({x:f,y:m},$t(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const fe={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:qt(e.placement),variation:he(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,ue(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,ue(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var pe={passive:!0};const me={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=$t(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,pe)})),a&&l.addEventListener("resize",i.update,pe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,pe)})),a&&l.removeEventListener("resize",i.update,pe)}},data:{}};var ge={left:"right",right:"left",bottom:"top",top:"bottom"};function _e(t){return t.replace(/left|right|bottom|top/g,(function(t){return ge[t]}))}var be={start:"end",end:"start"};function ve(t){return t.replace(/start|end/g,(function(t){return be[t]}))}function ye(t){var e=$t(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function we(t){return Ut(ee(t)).left+ye(t).scrollLeft}function Ae(t){var e=Zt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Ht(t))>=0?t.ownerDocument.body:Bt(t)&&Ae(t)?t:Ee(ie(t))}function Te(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=$t(n),r=s?[o].concat(o.visualViewport||[],Ae(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Te(ie(r)))}function Ce(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e,i){return e===Et?Ce(function(t,e){var i=$t(t),n=ee(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Yt();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+we(t),y:l}}(t,i)):Wt(e)?function(t,e){var i=Ut(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ce(function(t){var e,i=ee(t),n=ye(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=Vt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=Vt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+we(t),l=-n.scrollTop;return"rtl"===Zt(s||i).direction&&(a+=Vt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(ee(t)))}function xe(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?qt(s):null,r=s?he(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case pt:e={x:a,y:i.y-n.height};break;case mt:e={x:a,y:i.y+i.height};break;case gt:e={x:i.x+i.width,y:l};break;case _t:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?oe(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case yt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case wt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?At:a,c=i.rootBoundary,h=void 0===c?Et:c,d=i.elementContext,u=void 0===d?Tt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=ae("number"!=typeof g?g:le(g,vt)),b=u===Tt?Ct:Tt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Te(ie(t)),i=["absolute","fixed"].indexOf(Zt(t).position)>=0&&Bt(t)?se(t):t;return Wt(i)?e.filter((function(t){return Wt(t)&&Jt(t,i)&&"body"!==Ht(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=Oe(t,i,n);return e.top=Vt(s.top,e.top),e.right=Kt(s.right,e.right),e.bottom=Kt(s.bottom,e.bottom),e.left=Vt(s.left,e.left),e}),Oe(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(Wt(y)?y:y.contextElement||ee(t.elements.popper),l,h,r),A=Ut(t.elements.reference),E=xe({reference:A,element:v,strategy:"absolute",placement:s}),T=Ce(Object.assign({},v,E)),C=u===Tt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Tt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[gt,mt].indexOf(t)>=0?1:-1,i=[pt,mt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?xt:l,h=he(n),d=h?a?Ot:Ot.filter((function(t){return he(t)===h})):vt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[qt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const Se={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=qt(g),b=l||(_!==g&&p?function(t){if(qt(t)===bt)return[];var e=_e(t);return[ve(t),e,ve(e)]}(g):[_e(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(qt(i)===bt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ke(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=L?k?gt:_t:k?mt:pt;y[S]>w[S]&&(I=_e(I));var N=_e(I),P=[];if(o&&P.push(D[x]<=0),a&&P.push(D[I]<=0,D[N]<=0),P.every((function(t){return t}))){T=O,E=!1;break}A.set(O,P)}if(E)for(var M=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},j=p?3:1;j>0&&"break"!==M(j);j--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Ie(t){return[pt,gt,mt,_t].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Ie(l),d=Ie(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Pe={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=xt.reduce((function(t,i){return t[i]=function(t,e,i){var n=qt(t),s=[_t,pt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[_t,gt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Me={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=xe({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=qt(e.placement),b=he(e.placement),v=!b,y=oe(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?pt:_t,D="y"===y?mt:gt,I="y"===y?"height":"width",N=A[y],P=N+g[S],M=N-g[D],j=f?-T[I]/2:0,F=b===yt?E[I]:T[I],H=b===yt?-T[I]:-E[I],$=e.elements.arrow,W=f&&$?Gt($):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=re(0,E[I],W[I]),V=v?E[I]/2-j-q-z-O.mainAxis:F-q-z-O.mainAxis,K=v?-E[I]/2+j+q+R+O.mainAxis:H+q+R+O.mainAxis,Q=e.elements.arrow&&se(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=N+K-Y,G=re(f?Kt(P,N+V-Y-X):P,N,f?Vt(M,U):M);A[y]=G,k[y]=G-N}if(a){var J,Z="x"===y?pt:_t,tt="x"===y?mt:gt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[pt,_t].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=re(t,e,i);return n>i?i:n}(at,et,lt):re(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function Fe(t,e,i){void 0===i&&(i=!1);var n,s,o=Bt(e),r=Bt(e)&&function(t){var e=t.getBoundingClientRect(),i=Qt(e.width)/t.offsetWidth||1,n=Qt(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=ee(e),l=Ut(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==Ht(e)||Ae(a))&&(c=(n=e)!==$t(n)&&Bt(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ye(n)),Bt(e)?((h=Ut(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=we(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var $e={placement:"bottom",modifiers:[],strategy:"absolute"};function We(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(H.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Xe,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=ci.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ze);for(const i of e){const e=ci.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Qe,Xe].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Je)?this:z.prev(this,Je)[0]||z.next(this,Je)[0]||z.findOne(Je,t.delegateTarget.parentNode),o=ci.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}P.on(document,Ue,Je,ci.dataApiKeydownHandler),P.on(document,Ue,ti,ci.dataApiKeydownHandler),P.on(document,Ye,ci.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",ci.clearMenus),P.on(document,Ye,Je,(function(t){t.preventDefault(),ci.getOrCreateInstance(this).toggle()})),m(ci);const hi="show",di="mousedown.bs.backdrop",ui={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},fi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class pi extends ${constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return ui}static get DefaultType(){return fi}static get NAME(){return"backdrop"}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(hi),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(hi),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(P.off(this._element,di),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),P.on(t,di,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const mi=".bs.focustrap",gi="backward",_i={autofocus:!0,trapElement:null},bi={autofocus:"boolean",trapElement:"element"};class vi extends ${constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return _i}static get DefaultType(){return bi}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),P.off(document,mi),P.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),P.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,P.off(document,mi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===gi?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?gi:"forward")}}const yi=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",wi=".sticky-top",Ai="padding-right",Ei="margin-right";class Ti{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Ai,(e=>e+t)),this._setElementAttributes(yi,Ai,(e=>e+t)),this._setElementAttributes(wi,Ei,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Ai),this._resetElementAttributes(yi,Ai),this._resetElementAttributes(wi,Ei)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&H.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=H.getDataAttribute(t,e);null!==i?(H.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const Ci=".bs.modal",Oi="hidden.bs.modal",xi="show.bs.modal",ki="modal-open",Li="show",Si="modal-static",Di={backdrop:!0,focus:!0,keyboard:!0},Ii={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ni extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Ti,this._addEventListeners()}static get Default(){return Di}static get DefaultType(){return Ii}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ki),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(P.trigger(this._element,"hide.bs.modal").defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Li),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){P.off(window,Ci),P.off(this._dialog,Ci),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new pi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new vi({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(Li),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.modal",(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),P.on(window,"resize.bs.modal",(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),P.on(this._element,"mousedown.dismiss.bs.modal",(t=>{P.one(this._element,"click.dismiss.bs.modal",(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(ki),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,Oi)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Si)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Si),this._queueCallback((()=>{this._element.classList.remove(Si),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,xi,(t=>{t.defaultPrevented||P.one(e,Oi,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&Ni.getInstance(i).hide(),Ni.getOrCreateInstance(e).toggle(this)})),R(Ni),m(Ni);const Pi="show",Mi="showing",ji="hiding",Fi=".offcanvas.show",Hi="hidePrevented.bs.offcanvas",$i="hidden.bs.offcanvas",Wi={backdrop:!0,keyboard:!0,scroll:!1},Bi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class zi extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Wi}static get DefaultType(){return Bi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Ti).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Mi),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Pi),this._element.classList.remove(Mi),P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(ji),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Pi,ji),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Ti).reset(),P.trigger(this._element,$i)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new pi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():P.trigger(this._element,Hi)}:null})}_initializeFocusTrap(){return new vi({trapElement:this._element})}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():P.trigger(this._element,Hi))}))}static jQueryInterface(t){return this.each((function(){const e=zi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;P.one(e,$i,(()=>{a(this)&&this.focus()}));const i=z.findOne(Fi);i&&i!==e&&zi.getInstance(i).hide(),zi.getOrCreateInstance(e).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",(()=>{for(const t of z.find(Fi))zi.getOrCreateInstance(t).show()})),P.on(window,"resize.bs.offcanvas",(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&zi.getOrCreateInstance(t).hide()})),R(zi),m(zi);const Ri={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},qi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Ki=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!qi.has(i)||Boolean(Vi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Qi={allowList:Ri,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Xi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Yi={entry:"(string|element|function|null)",selector:"(string|element)"};class Ui extends ${constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Qi}static get DefaultType(){return Xi}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Yi)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Ki(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Gi=new Set(["sanitize","allowList","sanitizeFn"]),Ji="fade",Zi="show",tn=".modal",en="hide.bs.modal",nn="hover",sn="focus",on={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},rn={allowList:Ri,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},an={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class ln extends W{constructor(t,e){if(void 0===Ve)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return rn}static get DefaultType(){return an}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(tn),en,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),P.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(Zi),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.on(t,"mouseover",h);this._queueCallback((()=>{P.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!P.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(Zi),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(Ji,Zi),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(Ji),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Ui({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Ji)}_isShown(){return this.tip&&this.tip.classList.contains(Zi)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=on[e.toUpperCase()];return qe(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)P.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===nn?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===nn?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");P.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?sn:nn]=!0,e._enter()})),P.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?sn:nn]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(tn),en,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=H.getDataAttributes(this._element);for(const t of Object.keys(e))Gi.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(ln);const cn={...ln.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},hn={...ln.DefaultType,content:"(null|string|element|function)"};class dn extends ln{static get Default(){return cn}static get DefaultType(){return hn}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=dn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(dn);const un="click.bs.scrollspy",fn="active",pn="[href]",mn={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},gn={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class _n extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return mn}static get DefaultType(){return gn}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(P.off(this._config.target,un),P.on(this._config.target,un,pn,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(pn,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(fn),this._activateParents(t),P.trigger(this._element,"activate.bs.scrollspy",{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(fn);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,".nav-link, .nav-item > .nav-link, .list-group-item"))t.classList.add(fn)}_clearActiveClass(t){t.classList.remove(fn);const e=z.find("[href].active",t);for(const t of e)t.classList.remove(fn)}static jQueryInterface(t){return this.each((function(){const e=_n.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))_n.getOrCreateInstance(t)})),m(_n);const bn="ArrowLeft",vn="ArrowRight",yn="ArrowUp",wn="ArrowDown",An="active",En="fade",Tn="show",Cn='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',On=`.nav-link:not(.dropdown-toggle), .list-group-item:not(.dropdown-toggle), [role="tab"]:not(.dropdown-toggle), ${Cn}`;class xn extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),P.on(this._element,"keydown.bs.tab",(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?P.trigger(e,"hide.bs.tab",{relatedTarget:t}):null;P.trigger(t,"show.bs.tab",{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(An),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),P.trigger(t,"shown.bs.tab",{relatedTarget:e})):t.classList.add(Tn)}),t,t.classList.contains(En)))}_deactivate(t,e){t&&(t.classList.remove(An),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),P.trigger(t,"hidden.bs.tab",{relatedTarget:e})):t.classList.remove(Tn)}),t,t.classList.contains(En)))}_keydown(t){if(![bn,vn,yn,wn].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=[vn,wn].includes(t.key),i=b(this._getChildren().filter((t=>!l(t))),t.target,e,!0);i&&(i.focus({preventScroll:!0}),xn.getOrCreateInstance(i).show())}_getChildren(){return z.find(On,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",An),n(".dropdown-menu",Tn),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(An)}_getInnerElement(t){return t.matches(On)?t:z.findOne(On,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab",Cn,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||xn.getOrCreateInstance(this).show()})),P.on(window,"load.bs.tab",(()=>{for(const t of z.find('.active[data-bs-toggle="tab"], .active[data-bs-toggle="pill"], .active[data-bs-toggle="list"]'))xn.getOrCreateInstance(t)})),m(xn);const kn="hide",Ln="show",Sn="showing",Dn={animation:"boolean",autohide:"boolean",delay:"number"},In={animation:!0,autohide:!0,delay:5e3};class Nn extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return In}static get DefaultType(){return Dn}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(kn),d(this._element),this._element.classList.add(Ln,Sn),this._queueCallback((()=>{this._element.classList.remove(Sn),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(Sn),this._queueCallback((()=>{this._element.classList.add(kn),this._element.classList.remove(Sn,Ln),P.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Ln),super.dispose()}isShown(){return this._element.classList.contains(Ln)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),P.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Nn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Nn),m(Nn),{Alert:q,Button:K,Carousel:rt,Collapse:ft,Dropdown:ci,Modal:Ni,Offcanvas:zi,Popover:dn,ScrollSpy:_n,Tab:xn,Toast:Nn,Tooltip:ln}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/assets/vendor/bootstrap-5.3.0/bootstrap.min.css b/docs/assets/vendor/bootstrap-5.3.0/bootstrap.min.css new file mode 100644 index 000000000..f94bf475c --- /dev/null +++ b/docs/assets/vendor/bootstrap-5.3.0/bootstrap.min.css @@ -0,0 +1,6 @@ +@charset "UTF-8";/*! + * Bootstrap v5.3.0 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#adb5bd;--bs-body-color-rgb:173,181,189;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(173, 181, 189, 0.75);--bs-secondary-color-rgb:173,181,189;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(173, 181, 189, 0.5);--bs-tertiary-color-rgb:173,181,189;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-body-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-body-color);--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:var(--bs-body-color);--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:var(--bs-body-color);--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#bacbe6;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#cbccce;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#bcd0c7;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#badce3;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#e6dbb9;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#dfc2c4;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#dfe0e1;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#373b3e;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-tertiary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-tertiary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label{color:#6c757d}.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color:#86b7fe;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(13,110,253,var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(108,117,125,var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(25,135,84,var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(13,202,240,var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(255,193,7,var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(220,53,69,var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(248,249,250,var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(33,37,41,var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(10,88,202,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(86,94,100,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(20,108,67,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(61,213,243,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(255,205,57,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(249,250,251,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/docs/assets/vendor/bootstrap-icons-1.11.3/LICENSE b/docs/assets/vendor/bootstrap-icons-1.11.3/LICENSE new file mode 100644 index 000000000..f95243911 --- /dev/null +++ b/docs/assets/vendor/bootstrap-icons-1.11.3/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2024 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/assets/vendor/bootstrap-icons-1.11.3/bootstrap-icons.css b/docs/assets/vendor/bootstrap-icons-1.11.3/bootstrap-icons.css new file mode 100644 index 000000000..387cb168c --- /dev/null +++ b/docs/assets/vendor/bootstrap-icons-1.11.3/bootstrap-icons.css @@ -0,0 +1,2078 @@ +/*! + * Bootstrap Icons v1.11.3 (https://icons.getbootstrap.com/) + * Copyright 2019-2024 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) + */ + +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: url("fonts/bootstrap-icons.woff2") format("woff2"), +url("fonts/bootstrap-icons.woff") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } +.bi-1-circle-fill::before { content: "\f796"; } +.bi-1-circle::before { content: "\f797"; } +.bi-1-square-fill::before { content: "\f798"; } +.bi-1-square::before { content: "\f799"; } +.bi-2-circle-fill::before { content: "\f79c"; } +.bi-2-circle::before { content: "\f79d"; } +.bi-2-square-fill::before { content: "\f79e"; } +.bi-2-square::before { content: "\f79f"; } +.bi-3-circle-fill::before { content: "\f7a2"; } +.bi-3-circle::before { content: "\f7a3"; } +.bi-3-square-fill::before { content: "\f7a4"; } +.bi-3-square::before { content: "\f7a5"; } +.bi-4-circle-fill::before { content: "\f7a8"; } +.bi-4-circle::before { content: "\f7a9"; } +.bi-4-square-fill::before { content: "\f7aa"; } +.bi-4-square::before { content: "\f7ab"; } +.bi-5-circle-fill::before { content: "\f7ae"; } +.bi-5-circle::before { content: "\f7af"; } +.bi-5-square-fill::before { content: "\f7b0"; } +.bi-5-square::before { content: "\f7b1"; } +.bi-6-circle-fill::before { content: "\f7b4"; } +.bi-6-circle::before { content: "\f7b5"; } +.bi-6-square-fill::before { content: "\f7b6"; } +.bi-6-square::before { content: "\f7b7"; } +.bi-7-circle-fill::before { content: "\f7ba"; } +.bi-7-circle::before { content: "\f7bb"; } +.bi-7-square-fill::before { content: "\f7bc"; } +.bi-7-square::before { content: "\f7bd"; } +.bi-8-circle-fill::before { content: "\f7c0"; } +.bi-8-circle::before { content: "\f7c1"; } +.bi-8-square-fill::before { content: "\f7c2"; } +.bi-8-square::before { content: "\f7c3"; } +.bi-9-circle-fill::before { content: "\f7c6"; } +.bi-9-circle::before { content: "\f7c7"; } +.bi-9-square-fill::before { content: "\f7c8"; } +.bi-9-square::before { content: "\f7c9"; } +.bi-airplane-engines-fill::before { content: "\f7ca"; } +.bi-airplane-engines::before { content: "\f7cb"; } +.bi-airplane-fill::before { content: "\f7cc"; } +.bi-airplane::before { content: "\f7cd"; } +.bi-alexa::before { content: "\f7ce"; } +.bi-alipay::before { content: "\f7cf"; } +.bi-android::before { content: "\f7d0"; } +.bi-android2::before { content: "\f7d1"; } +.bi-box-fill::before { content: "\f7d2"; } +.bi-box-seam-fill::before { content: "\f7d3"; } +.bi-browser-chrome::before { content: "\f7d4"; } +.bi-browser-edge::before { content: "\f7d5"; } +.bi-browser-firefox::before { content: "\f7d6"; } +.bi-browser-safari::before { content: "\f7d7"; } +.bi-c-circle-fill::before { content: "\f7da"; } +.bi-c-circle::before { content: "\f7db"; } +.bi-c-square-fill::before { content: "\f7dc"; } +.bi-c-square::before { content: "\f7dd"; } +.bi-capsule-pill::before { content: "\f7de"; } +.bi-capsule::before { content: "\f7df"; } +.bi-car-front-fill::before { content: "\f7e0"; } +.bi-car-front::before { content: "\f7e1"; } +.bi-cassette-fill::before { content: "\f7e2"; } +.bi-cassette::before { content: "\f7e3"; } +.bi-cc-circle-fill::before { content: "\f7e6"; } +.bi-cc-circle::before { content: "\f7e7"; } +.bi-cc-square-fill::before { content: "\f7e8"; } +.bi-cc-square::before { content: "\f7e9"; } +.bi-cup-hot-fill::before { content: "\f7ea"; } +.bi-cup-hot::before { content: "\f7eb"; } +.bi-currency-rupee::before { content: "\f7ec"; } +.bi-dropbox::before { content: "\f7ed"; } +.bi-escape::before { content: "\f7ee"; } +.bi-fast-forward-btn-fill::before { content: "\f7ef"; } +.bi-fast-forward-btn::before { content: "\f7f0"; } +.bi-fast-forward-circle-fill::before { content: "\f7f1"; } +.bi-fast-forward-circle::before { content: "\f7f2"; } +.bi-fast-forward-fill::before { content: "\f7f3"; } +.bi-fast-forward::before { content: "\f7f4"; } +.bi-filetype-sql::before { content: "\f7f5"; } +.bi-fire::before { content: "\f7f6"; } +.bi-google-play::before { content: "\f7f7"; } +.bi-h-circle-fill::before { content: "\f7fa"; } +.bi-h-circle::before { content: "\f7fb"; } +.bi-h-square-fill::before { content: "\f7fc"; } +.bi-h-square::before { content: "\f7fd"; } +.bi-indent::before { content: "\f7fe"; } +.bi-lungs-fill::before { content: "\f7ff"; } +.bi-lungs::before { content: "\f800"; } +.bi-microsoft-teams::before { content: "\f801"; } +.bi-p-circle-fill::before { content: "\f804"; } +.bi-p-circle::before { content: "\f805"; } +.bi-p-square-fill::before { content: "\f806"; } +.bi-p-square::before { content: "\f807"; } +.bi-pass-fill::before { content: "\f808"; } +.bi-pass::before { content: "\f809"; } +.bi-prescription::before { content: "\f80a"; } +.bi-prescription2::before { content: "\f80b"; } +.bi-r-circle-fill::before { content: "\f80e"; } +.bi-r-circle::before { content: "\f80f"; } +.bi-r-square-fill::before { content: "\f810"; } +.bi-r-square::before { content: "\f811"; } +.bi-repeat-1::before { content: "\f812"; } +.bi-repeat::before { content: "\f813"; } +.bi-rewind-btn-fill::before { content: "\f814"; } +.bi-rewind-btn::before { content: "\f815"; } +.bi-rewind-circle-fill::before { content: "\f816"; } +.bi-rewind-circle::before { content: "\f817"; } +.bi-rewind-fill::before { content: "\f818"; } +.bi-rewind::before { content: "\f819"; } +.bi-train-freight-front-fill::before { content: "\f81a"; } +.bi-train-freight-front::before { content: "\f81b"; } +.bi-train-front-fill::before { content: "\f81c"; } +.bi-train-front::before { content: "\f81d"; } +.bi-train-lightrail-front-fill::before { content: "\f81e"; } +.bi-train-lightrail-front::before { content: "\f81f"; } +.bi-truck-front-fill::before { content: "\f820"; } +.bi-truck-front::before { content: "\f821"; } +.bi-ubuntu::before { content: "\f822"; } +.bi-unindent::before { content: "\f823"; } +.bi-unity::before { content: "\f824"; } +.bi-universal-access-circle::before { content: "\f825"; } +.bi-universal-access::before { content: "\f826"; } +.bi-virus::before { content: "\f827"; } +.bi-virus2::before { content: "\f828"; } +.bi-wechat::before { content: "\f829"; } +.bi-yelp::before { content: "\f82a"; } +.bi-sign-stop-fill::before { content: "\f82b"; } +.bi-sign-stop-lights-fill::before { content: "\f82c"; } +.bi-sign-stop-lights::before { content: "\f82d"; } +.bi-sign-stop::before { content: "\f82e"; } +.bi-sign-turn-left-fill::before { content: "\f82f"; } +.bi-sign-turn-left::before { content: "\f830"; } +.bi-sign-turn-right-fill::before { content: "\f831"; } +.bi-sign-turn-right::before { content: "\f832"; } +.bi-sign-turn-slight-left-fill::before { content: "\f833"; } +.bi-sign-turn-slight-left::before { content: "\f834"; } +.bi-sign-turn-slight-right-fill::before { content: "\f835"; } +.bi-sign-turn-slight-right::before { content: "\f836"; } +.bi-sign-yield-fill::before { content: "\f837"; } +.bi-sign-yield::before { content: "\f838"; } +.bi-ev-station-fill::before { content: "\f839"; } +.bi-ev-station::before { content: "\f83a"; } +.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } +.bi-fuel-pump-diesel::before { content: "\f83c"; } +.bi-fuel-pump-fill::before { content: "\f83d"; } +.bi-fuel-pump::before { content: "\f83e"; } +.bi-0-circle-fill::before { content: "\f83f"; } +.bi-0-circle::before { content: "\f840"; } +.bi-0-square-fill::before { content: "\f841"; } +.bi-0-square::before { content: "\f842"; } +.bi-rocket-fill::before { content: "\f843"; } +.bi-rocket-takeoff-fill::before { content: "\f844"; } +.bi-rocket-takeoff::before { content: "\f845"; } +.bi-rocket::before { content: "\f846"; } +.bi-stripe::before { content: "\f847"; } +.bi-subscript::before { content: "\f848"; } +.bi-superscript::before { content: "\f849"; } +.bi-trello::before { content: "\f84a"; } +.bi-envelope-at-fill::before { content: "\f84b"; } +.bi-envelope-at::before { content: "\f84c"; } +.bi-regex::before { content: "\f84d"; } +.bi-text-wrap::before { content: "\f84e"; } +.bi-sign-dead-end-fill::before { content: "\f84f"; } +.bi-sign-dead-end::before { content: "\f850"; } +.bi-sign-do-not-enter-fill::before { content: "\f851"; } +.bi-sign-do-not-enter::before { content: "\f852"; } +.bi-sign-intersection-fill::before { content: "\f853"; } +.bi-sign-intersection-side-fill::before { content: "\f854"; } +.bi-sign-intersection-side::before { content: "\f855"; } +.bi-sign-intersection-t-fill::before { content: "\f856"; } +.bi-sign-intersection-t::before { content: "\f857"; } +.bi-sign-intersection-y-fill::before { content: "\f858"; } +.bi-sign-intersection-y::before { content: "\f859"; } +.bi-sign-intersection::before { content: "\f85a"; } +.bi-sign-merge-left-fill::before { content: "\f85b"; } +.bi-sign-merge-left::before { content: "\f85c"; } +.bi-sign-merge-right-fill::before { content: "\f85d"; } +.bi-sign-merge-right::before { content: "\f85e"; } +.bi-sign-no-left-turn-fill::before { content: "\f85f"; } +.bi-sign-no-left-turn::before { content: "\f860"; } +.bi-sign-no-parking-fill::before { content: "\f861"; } +.bi-sign-no-parking::before { content: "\f862"; } +.bi-sign-no-right-turn-fill::before { content: "\f863"; } +.bi-sign-no-right-turn::before { content: "\f864"; } +.bi-sign-railroad-fill::before { content: "\f865"; } +.bi-sign-railroad::before { content: "\f866"; } +.bi-building-add::before { content: "\f867"; } +.bi-building-check::before { content: "\f868"; } +.bi-building-dash::before { content: "\f869"; } +.bi-building-down::before { content: "\f86a"; } +.bi-building-exclamation::before { content: "\f86b"; } +.bi-building-fill-add::before { content: "\f86c"; } +.bi-building-fill-check::before { content: "\f86d"; } +.bi-building-fill-dash::before { content: "\f86e"; } +.bi-building-fill-down::before { content: "\f86f"; } +.bi-building-fill-exclamation::before { content: "\f870"; } +.bi-building-fill-gear::before { content: "\f871"; } +.bi-building-fill-lock::before { content: "\f872"; } +.bi-building-fill-slash::before { content: "\f873"; } +.bi-building-fill-up::before { content: "\f874"; } +.bi-building-fill-x::before { content: "\f875"; } +.bi-building-fill::before { content: "\f876"; } +.bi-building-gear::before { content: "\f877"; } +.bi-building-lock::before { content: "\f878"; } +.bi-building-slash::before { content: "\f879"; } +.bi-building-up::before { content: "\f87a"; } +.bi-building-x::before { content: "\f87b"; } +.bi-buildings-fill::before { content: "\f87c"; } +.bi-buildings::before { content: "\f87d"; } +.bi-bus-front-fill::before { content: "\f87e"; } +.bi-bus-front::before { content: "\f87f"; } +.bi-ev-front-fill::before { content: "\f880"; } +.bi-ev-front::before { content: "\f881"; } +.bi-globe-americas::before { content: "\f882"; } +.bi-globe-asia-australia::before { content: "\f883"; } +.bi-globe-central-south-asia::before { content: "\f884"; } +.bi-globe-europe-africa::before { content: "\f885"; } +.bi-house-add-fill::before { content: "\f886"; } +.bi-house-add::before { content: "\f887"; } +.bi-house-check-fill::before { content: "\f888"; } +.bi-house-check::before { content: "\f889"; } +.bi-house-dash-fill::before { content: "\f88a"; } +.bi-house-dash::before { content: "\f88b"; } +.bi-house-down-fill::before { content: "\f88c"; } +.bi-house-down::before { content: "\f88d"; } +.bi-house-exclamation-fill::before { content: "\f88e"; } +.bi-house-exclamation::before { content: "\f88f"; } +.bi-house-gear-fill::before { content: "\f890"; } +.bi-house-gear::before { content: "\f891"; } +.bi-house-lock-fill::before { content: "\f892"; } +.bi-house-lock::before { content: "\f893"; } +.bi-house-slash-fill::before { content: "\f894"; } +.bi-house-slash::before { content: "\f895"; } +.bi-house-up-fill::before { content: "\f896"; } +.bi-house-up::before { content: "\f897"; } +.bi-house-x-fill::before { content: "\f898"; } +.bi-house-x::before { content: "\f899"; } +.bi-person-add::before { content: "\f89a"; } +.bi-person-down::before { content: "\f89b"; } +.bi-person-exclamation::before { content: "\f89c"; } +.bi-person-fill-add::before { content: "\f89d"; } +.bi-person-fill-check::before { content: "\f89e"; } +.bi-person-fill-dash::before { content: "\f89f"; } +.bi-person-fill-down::before { content: "\f8a0"; } +.bi-person-fill-exclamation::before { content: "\f8a1"; } +.bi-person-fill-gear::before { content: "\f8a2"; } +.bi-person-fill-lock::before { content: "\f8a3"; } +.bi-person-fill-slash::before { content: "\f8a4"; } +.bi-person-fill-up::before { content: "\f8a5"; } +.bi-person-fill-x::before { content: "\f8a6"; } +.bi-person-gear::before { content: "\f8a7"; } +.bi-person-lock::before { content: "\f8a8"; } +.bi-person-slash::before { content: "\f8a9"; } +.bi-person-up::before { content: "\f8aa"; } +.bi-scooter::before { content: "\f8ab"; } +.bi-taxi-front-fill::before { content: "\f8ac"; } +.bi-taxi-front::before { content: "\f8ad"; } +.bi-amd::before { content: "\f8ae"; } +.bi-database-add::before { content: "\f8af"; } +.bi-database-check::before { content: "\f8b0"; } +.bi-database-dash::before { content: "\f8b1"; } +.bi-database-down::before { content: "\f8b2"; } +.bi-database-exclamation::before { content: "\f8b3"; } +.bi-database-fill-add::before { content: "\f8b4"; } +.bi-database-fill-check::before { content: "\f8b5"; } +.bi-database-fill-dash::before { content: "\f8b6"; } +.bi-database-fill-down::before { content: "\f8b7"; } +.bi-database-fill-exclamation::before { content: "\f8b8"; } +.bi-database-fill-gear::before { content: "\f8b9"; } +.bi-database-fill-lock::before { content: "\f8ba"; } +.bi-database-fill-slash::before { content: "\f8bb"; } +.bi-database-fill-up::before { content: "\f8bc"; } +.bi-database-fill-x::before { content: "\f8bd"; } +.bi-database-fill::before { content: "\f8be"; } +.bi-database-gear::before { content: "\f8bf"; } +.bi-database-lock::before { content: "\f8c0"; } +.bi-database-slash::before { content: "\f8c1"; } +.bi-database-up::before { content: "\f8c2"; } +.bi-database-x::before { content: "\f8c3"; } +.bi-database::before { content: "\f8c4"; } +.bi-houses-fill::before { content: "\f8c5"; } +.bi-houses::before { content: "\f8c6"; } +.bi-nvidia::before { content: "\f8c7"; } +.bi-person-vcard-fill::before { content: "\f8c8"; } +.bi-person-vcard::before { content: "\f8c9"; } +.bi-sina-weibo::before { content: "\f8ca"; } +.bi-tencent-qq::before { content: "\f8cb"; } +.bi-wikipedia::before { content: "\f8cc"; } +.bi-alphabet-uppercase::before { content: "\f2a5"; } +.bi-alphabet::before { content: "\f68a"; } +.bi-amazon::before { content: "\f68d"; } +.bi-arrows-collapse-vertical::before { content: "\f690"; } +.bi-arrows-expand-vertical::before { content: "\f695"; } +.bi-arrows-vertical::before { content: "\f698"; } +.bi-arrows::before { content: "\f6a2"; } +.bi-ban-fill::before { content: "\f6a3"; } +.bi-ban::before { content: "\f6b6"; } +.bi-bing::before { content: "\f6c2"; } +.bi-cake::before { content: "\f6e0"; } +.bi-cake2::before { content: "\f6ed"; } +.bi-cookie::before { content: "\f6ee"; } +.bi-copy::before { content: "\f759"; } +.bi-crosshair::before { content: "\f769"; } +.bi-crosshair2::before { content: "\f794"; } +.bi-emoji-astonished-fill::before { content: "\f795"; } +.bi-emoji-astonished::before { content: "\f79a"; } +.bi-emoji-grimace-fill::before { content: "\f79b"; } +.bi-emoji-grimace::before { content: "\f7a0"; } +.bi-emoji-grin-fill::before { content: "\f7a1"; } +.bi-emoji-grin::before { content: "\f7a6"; } +.bi-emoji-surprise-fill::before { content: "\f7a7"; } +.bi-emoji-surprise::before { content: "\f7ac"; } +.bi-emoji-tear-fill::before { content: "\f7ad"; } +.bi-emoji-tear::before { content: "\f7b2"; } +.bi-envelope-arrow-down-fill::before { content: "\f7b3"; } +.bi-envelope-arrow-down::before { content: "\f7b8"; } +.bi-envelope-arrow-up-fill::before { content: "\f7b9"; } +.bi-envelope-arrow-up::before { content: "\f7be"; } +.bi-feather::before { content: "\f7bf"; } +.bi-feather2::before { content: "\f7c4"; } +.bi-floppy-fill::before { content: "\f7c5"; } +.bi-floppy::before { content: "\f7d8"; } +.bi-floppy2-fill::before { content: "\f7d9"; } +.bi-floppy2::before { content: "\f7e4"; } +.bi-gitlab::before { content: "\f7e5"; } +.bi-highlighter::before { content: "\f7f8"; } +.bi-marker-tip::before { content: "\f802"; } +.bi-nvme-fill::before { content: "\f803"; } +.bi-nvme::before { content: "\f80c"; } +.bi-opencollective::before { content: "\f80d"; } +.bi-pci-card-network::before { content: "\f8cd"; } +.bi-pci-card-sound::before { content: "\f8ce"; } +.bi-radar::before { content: "\f8cf"; } +.bi-send-arrow-down-fill::before { content: "\f8d0"; } +.bi-send-arrow-down::before { content: "\f8d1"; } +.bi-send-arrow-up-fill::before { content: "\f8d2"; } +.bi-send-arrow-up::before { content: "\f8d3"; } +.bi-sim-slash-fill::before { content: "\f8d4"; } +.bi-sim-slash::before { content: "\f8d5"; } +.bi-sourceforge::before { content: "\f8d6"; } +.bi-substack::before { content: "\f8d7"; } +.bi-threads-fill::before { content: "\f8d8"; } +.bi-threads::before { content: "\f8d9"; } +.bi-transparency::before { content: "\f8da"; } +.bi-twitter-x::before { content: "\f8db"; } +.bi-type-h4::before { content: "\f8dc"; } +.bi-type-h5::before { content: "\f8dd"; } +.bi-type-h6::before { content: "\f8de"; } +.bi-backpack-fill::before { content: "\f8df"; } +.bi-backpack::before { content: "\f8e0"; } +.bi-backpack2-fill::before { content: "\f8e1"; } +.bi-backpack2::before { content: "\f8e2"; } +.bi-backpack3-fill::before { content: "\f8e3"; } +.bi-backpack3::before { content: "\f8e4"; } +.bi-backpack4-fill::before { content: "\f8e5"; } +.bi-backpack4::before { content: "\f8e6"; } +.bi-brilliance::before { content: "\f8e7"; } +.bi-cake-fill::before { content: "\f8e8"; } +.bi-cake2-fill::before { content: "\f8e9"; } +.bi-duffle-fill::before { content: "\f8ea"; } +.bi-duffle::before { content: "\f8eb"; } +.bi-exposure::before { content: "\f8ec"; } +.bi-gender-neuter::before { content: "\f8ed"; } +.bi-highlights::before { content: "\f8ee"; } +.bi-luggage-fill::before { content: "\f8ef"; } +.bi-luggage::before { content: "\f8f0"; } +.bi-mailbox-flag::before { content: "\f8f1"; } +.bi-mailbox2-flag::before { content: "\f8f2"; } +.bi-noise-reduction::before { content: "\f8f3"; } +.bi-passport-fill::before { content: "\f8f4"; } +.bi-passport::before { content: "\f8f5"; } +.bi-person-arms-up::before { content: "\f8f6"; } +.bi-person-raised-hand::before { content: "\f8f7"; } +.bi-person-standing-dress::before { content: "\f8f8"; } +.bi-person-standing::before { content: "\f8f9"; } +.bi-person-walking::before { content: "\f8fa"; } +.bi-person-wheelchair::before { content: "\f8fb"; } +.bi-shadows::before { content: "\f8fc"; } +.bi-suitcase-fill::before { content: "\f8fd"; } +.bi-suitcase-lg-fill::before { content: "\f8fe"; } +.bi-suitcase-lg::before { content: "\f8ff"; } +.bi-suitcase::before { content: "\f900"; } +.bi-suitcase2-fill::before { content: "\f901"; } +.bi-suitcase2::before { content: "\f902"; } +.bi-vignette::before { content: "\f903"; } diff --git a/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff b/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff new file mode 100644 index 000000000..51204d27d Binary files /dev/null and b/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff differ diff --git a/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff2 b/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff2 new file mode 100644 index 000000000..92c483021 Binary files /dev/null and b/docs/assets/vendor/bootstrap-icons-1.11.3/fonts/bootstrap-icons.woff2 differ diff --git a/docs/assets/vendor/fonts/LICENSE-OFL.txt b/docs/assets/vendor/fonts/LICENSE-OFL.txt new file mode 100644 index 000000000..b8c9aa178 --- /dev/null +++ b/docs/assets/vendor/fonts/LICENSE-OFL.txt @@ -0,0 +1,291 @@ +This file preserves the SIL Open Font License 1.1 notices for the self-hosted documentation fonts vendored in this directory. + +Covered font families: +- Inter (weights 400, 500, 600, 700; latin and latin-ext subsets) +- JetBrains Mono (weights 400, 500; latin and latin-ext subsets) +- Work Sans (weights 600, 700; latin and latin-ext subsets) + +--- Inter OFL.txt --- +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +--- JetBrains Mono OFL.txt --- +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +--- Work Sans OFL.txt --- +Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/vendor/fonts/fonts.css b/docs/assets/vendor/fonts/fonts.css new file mode 100644 index 000000000..9fbbaec0b --- /dev/null +++ b/docs/assets/vendor/fonts/fonts.css @@ -0,0 +1,160 @@ +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(inter-400-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(inter-400-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(inter-500-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(inter-500-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(inter-600-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(inter-600-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(inter-700-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(inter-700-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(jetbrains-mono-400-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(jetbrains-mono-400-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(jetbrains-mono-500-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(jetbrains-mono-500-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(work-sans-600-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(work-sans-600-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(work-sans-700-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(work-sans-700-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + diff --git a/docs/assets/vendor/fonts/inter-400-latin-ext.woff2 b/docs/assets/vendor/fonts/inter-400-latin-ext.woff2 new file mode 100644 index 000000000..479d010d2 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-400-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-400-latin.woff2 b/docs/assets/vendor/fonts/inter-400-latin.woff2 new file mode 100644 index 000000000..d15208de0 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-400-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-500-latin-ext.woff2 b/docs/assets/vendor/fonts/inter-500-latin-ext.woff2 new file mode 100644 index 000000000..479d010d2 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-500-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-500-latin.woff2 b/docs/assets/vendor/fonts/inter-500-latin.woff2 new file mode 100644 index 000000000..d15208de0 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-500-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-600-latin-ext.woff2 b/docs/assets/vendor/fonts/inter-600-latin-ext.woff2 new file mode 100644 index 000000000..479d010d2 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-600-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-600-latin.woff2 b/docs/assets/vendor/fonts/inter-600-latin.woff2 new file mode 100644 index 000000000..d15208de0 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-600-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-700-latin-ext.woff2 b/docs/assets/vendor/fonts/inter-700-latin-ext.woff2 new file mode 100644 index 000000000..479d010d2 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-700-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/inter-700-latin.woff2 b/docs/assets/vendor/fonts/inter-700-latin.woff2 new file mode 100644 index 000000000..d15208de0 Binary files /dev/null and b/docs/assets/vendor/fonts/inter-700-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/jetbrains-mono-400-latin-ext.woff2 b/docs/assets/vendor/fonts/jetbrains-mono-400-latin-ext.woff2 new file mode 100644 index 000000000..82f96681c Binary files /dev/null and b/docs/assets/vendor/fonts/jetbrains-mono-400-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/jetbrains-mono-400-latin.woff2 b/docs/assets/vendor/fonts/jetbrains-mono-400-latin.woff2 new file mode 100644 index 000000000..4d09cda4a Binary files /dev/null and b/docs/assets/vendor/fonts/jetbrains-mono-400-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/jetbrains-mono-500-latin-ext.woff2 b/docs/assets/vendor/fonts/jetbrains-mono-500-latin-ext.woff2 new file mode 100644 index 000000000..82f96681c Binary files /dev/null and b/docs/assets/vendor/fonts/jetbrains-mono-500-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/jetbrains-mono-500-latin.woff2 b/docs/assets/vendor/fonts/jetbrains-mono-500-latin.woff2 new file mode 100644 index 000000000..4d09cda4a Binary files /dev/null and b/docs/assets/vendor/fonts/jetbrains-mono-500-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/work-sans-600-latin-ext.woff2 b/docs/assets/vendor/fonts/work-sans-600-latin-ext.woff2 new file mode 100644 index 000000000..62068414f Binary files /dev/null and b/docs/assets/vendor/fonts/work-sans-600-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/work-sans-600-latin.woff2 b/docs/assets/vendor/fonts/work-sans-600-latin.woff2 new file mode 100644 index 000000000..3a70b258d Binary files /dev/null and b/docs/assets/vendor/fonts/work-sans-600-latin.woff2 differ diff --git a/docs/assets/vendor/fonts/work-sans-700-latin-ext.woff2 b/docs/assets/vendor/fonts/work-sans-700-latin-ext.woff2 new file mode 100644 index 000000000..62068414f Binary files /dev/null and b/docs/assets/vendor/fonts/work-sans-700-latin-ext.woff2 differ diff --git a/docs/assets/vendor/fonts/work-sans-700-latin.woff2 b/docs/assets/vendor/fonts/work-sans-700-latin.woff2 new file mode 100644 index 000000000..3a70b258d Binary files /dev/null and b/docs/assets/vendor/fonts/work-sans-700-latin.woff2 differ diff --git a/docs/assets/vendor/prism-1.29.0/LICENSE b/docs/assets/vendor/prism-1.29.0/LICENSE new file mode 100644 index 000000000..528949f42 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/LICENSE @@ -0,0 +1,21 @@ +MIT LICENSE + +Copyright (c) 2012 Lea Verou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/assets/vendor/prism-1.29.0/components/prism-bash.min.js b/docs/assets/vendor/prism-1.29.0/components/prism-bash.min.js new file mode 100644 index 000000000..f1659f1e3 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/components/prism-bash.min.js @@ -0,0 +1 @@ +!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/components/prism-json.min.js b/docs/assets/vendor/prism-1.29.0/components/prism-json.min.js new file mode 100644 index 000000000..4256f8200 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/components/prism-json.min.js @@ -0,0 +1 @@ +Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json; \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/components/prism-python.min.js b/docs/assets/vendor/prism-1.29.0/components/prism-python.min.js new file mode 100644 index 000000000..96932b01e --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/components/prism-python.min.js @@ -0,0 +1 @@ +Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/components/prism-yaml.min.js b/docs/assets/vendor/prism-1.29.0/components/prism-yaml.min.js new file mode 100644 index 000000000..5cd16d3d7 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/components/prism-yaml.min.js @@ -0,0 +1 @@ +!function(e){var n=/[*&][^\s[\]{},]+/,r=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,t="(?:"+r.source+"(?:[ \t]+"+n.source+")?|"+n.source+"(?:[ \t]+"+r.source+")?)",a="(?:[^\\s\\x00-\\x08\\x0e-\\x1f!\"#%&'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*".replace(//g,(function(){return"[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]"})),d="\"(?:[^\"\\\\\r\n]|\\\\.)*\"|'(?:[^'\\\\\r\n]|\\\\.)*'";function o(e,n){n=(n||"").replace(/m/g,"")+"m";var r="([:\\-,[{]\\s*(?:\\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\\]|\\}|(?:[\r\n]\\s*)?#))".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return e}));return RegExp(r,n)}e.languages.yaml={scalar:{pattern:RegExp("([\\-:]\\s*(?:\\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\\S[^\r\n]*(?:\\2[^\r\n]+)*)".replace(/<>/g,(function(){return t}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp("((?:^|[:\\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\\s*:\\s)".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return"(?:"+a+"|"+d+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:o("\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?"),lookbehind:!0,alias:"number"},boolean:{pattern:o("false|true","i"),lookbehind:!0,alias:"important"},null:{pattern:o("null|~","i"),lookbehind:!0,alias:"important"},string:{pattern:o(d),lookbehind:!0,greedy:!0},number:{pattern:o("[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)","i"),lookbehind:!0},tag:r,important:n,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism); \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/prism-tomorrow.min.css b/docs/assets/vendor/prism-1.29.0/prism-tomorrow.min.css new file mode 100644 index 000000000..8fce55009 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/prism-tomorrow.min.css @@ -0,0 +1 @@ +code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/prism.min.css b/docs/assets/vendor/prism-1.29.0/prism.min.css new file mode 100644 index 000000000..8c4cc0576 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/prism.min.css @@ -0,0 +1 @@ +code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} \ No newline at end of file diff --git a/docs/assets/vendor/prism-1.29.0/prism.min.js b/docs/assets/vendor/prism-1.29.0/prism.min.js new file mode 100644 index 000000000..054ce1e78 --- /dev/null +++ b/docs/assets/vendor/prism-1.29.0/prism.min.js @@ -0,0 +1 @@ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(l){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,e={},j={manual:l.Prism&&l.Prism.manual,disableWorkerMessageHandler:l.Prism&&l.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof C?new C(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=i.reach);y+=b.value.length,b=b.next){var v=b.value;if(n.length>t.length)return;if(!(v instanceof C)){var F,x=1;if(m){if(!(F=L(f,y,t,p))||F.index>=t.length)break;var k=F.index,w=F.index+F[0].length,A=y;for(A+=b.value.length;A<=k;)b=b.next,A+=b.value.length;if(A-=b.value.length,y=A,b.value instanceof C)continue;for(var P=b;P!==n.tail&&(Ai.reach&&(i.reach=v),b.prev),S=(S&&(_=z(n,_,S),y+=S.length),O(n,_,x),new C(o,d?j.tokenize($,d):$,h,$));b=z(n,_,S),E&&z(n,b,E),1i.reach&&(i.reach=$.reach))}}}}}(e,r,t,r.head,0),r),i=[],o=s.head.next;o!==s.tail;)i.push(o.value),o=o.next;return i},hooks:{all:{},add:function(e,t){var n=j.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=j.hooks.all[e];if(n&&n.length)for(var a,r=0;a=n[r++];)a(t)}},Token:C};function C(e,t,n,a){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length}function L(e,t,n,a){e.lastIndex=t;t=e.exec(n);return t&&a&&t[1]&&(e=t[1].length,t.index+=e,t[0]=t[0].slice(e)),t}function u(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function z(e,t,n){var a=t.next,n={value:n,prev:t,next:a};return t.next=n,a.prev=n,e.length++,n}function O(e,t,n){for(var a=t.next,r=0;r"+s.content+""},!l.document)return l.addEventListener&&(j.disableWorkerMessageHandler||l.addEventListener("message",function(e){var e=JSON.parse(e.data),t=e.language,n=e.code,e=e.immediateClose;l.postMessage(j.highlight(n,j.languages[t],t)),e&&l.close()},!1)),j;var a,e=j.util.currentScript();function r(){j.manual||j.highlightAll()}return e&&(j.filename=e.src,e.hasAttribute("data-manual")&&(j.manual=!0)),j.manual||("loading"===(a=document.readyState)||"interactive"===a&&e&&e.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)),j}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism),Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={},n=(n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^$/i,{"included-cdata":{pattern://i,inside:n}}),t=(n["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]},{});t[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,t){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:Prism.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml,function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/,t=(e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css,e.languages.markup);t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism),Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),Prism.languages.js=Prism.languages.javascript,function(){var l,u,g,c,e;void 0!==Prism&&"undefined"!=typeof document&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),l={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},c="pre[data-src]:not(["+(u="data-src-status")+'="loaded"]):not(['+u+'="'+(g="loading")+'"])',Prism.hooks.add("before-highlightall",function(e){e.selector+=", "+c}),Prism.hooks.add("before-sanity-check",function(e){var r,t,n,a,s,i,o=e.element;o.matches(c)&&(e.code="",o.setAttribute(u,g),(r=o.appendChild(document.createElement("CODE"))).textContent="Loading…",t=o.getAttribute("data-src"),"none"===(e=e.language)&&(n=(/\.(\w+)$/.exec(t)||[,"none"])[1],e=l[n]||n),Prism.util.setLanguage(r,e),Prism.util.setLanguage(o,e),(n=Prism.plugins.autoloader)&&n.loadLanguages(e),n=t,a=function(e){o.setAttribute(u,"loaded");var t,n,a=function(e){var t,n;if(e=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||""))return t=Number(e[1]),n=e[2],e=e[3],n?e?[t,Number(e)]:[t,void 0]:[t,t]}(o.getAttribute("data-range"));a&&(t=e.split(/\r\n?|\n/g),n=a[0],a=null==a[1]?t.length:a[1],n<0&&(n+=t.length),n=Math.max(0,Math.min(n-1,t.length)),a<0&&(a+=t.length),a=Math.max(0,Math.min(a,t.length)),e=t.slice(n,a).join("\n"),o.hasAttribute("data-start")||o.setAttribute("data-start",String(n+1))),r.textContent=e,Prism.highlightElement(r)},s=function(e){o.setAttribute(u,"failed"),r.textContent=e},(i=new XMLHttpRequest).open("GET",n,!0),i.onreadystatechange=function(){4==i.readyState&&(i.status<400&&i.responseText?a(i.responseText):400<=i.status?s("✖ Error "+i.status+" while fetching file: "+i.statusText):s("✖ Error: File does not exist or is empty"))},i.send(null))}),e=!(Prism.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(c),a=0;t=n[a++];)Prism.highlightElement(t)}}),Prism.fileHighlight=function(){e||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),e=!0),Prism.plugins.fileHighlight.highlight.apply(this,arguments)})}(); \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index c9fa1b28c..cdb7593a3 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -30,7 +30,7 @@ After a contribution is merged into `Development`, the SimpleChat team handles p Add a branch-flow diagram here later if you want a visual version of the process. Example: -![SimpleChat contribution flow](./images/contribution-flow.png) +![SimpleChat contribution flow]({{ '/images/contribution-flow.png' | relative_url }}) --> ## Suggested Git Commands @@ -73,7 +73,7 @@ python app.py For the full local workflow, environment guidance, and notes about Docker, WSL2, and Gunicorn validation, see: -- [Setup Instructions](./setup_instructions.md) +- [Setup Instructions]({{ '/start/deployment-options/' | relative_url }}) - [Manual Setup](./setup_instructions_manual.md) - [Running Simple Chat Locally](./explanation/running_simplechat_locally.md) diff --git a/docs/contributing/media-status.md b/docs/contributing/media-status.md new file mode 100644 index 000000000..b66c64460 --- /dev/null +++ b/docs/contributing/media-status.md @@ -0,0 +1,189 @@ +--- +layout: page +title: "Media Status" +description: "Every screenshot and video slot in the documentation, and which ones still need to be captured." +section: "Contributing" +permalink: /contributing/media-status/ +--- + +This is the capture worklist. Every screenshot and video slot registered in +`docs/_data/media.yml` is listed here with the exact file path it expects. + +## How to fill a slot + +1. Find the slot below, or find a placeholder card on any documentation page. +2. Capture the screenshot or record the video. +3. Save the image at the exact path shown. For videos, save the poster frame at + the poster path and add the watch URL to the slot in `docs/_data/media.yml`. +4. Commit. The placeholder is replaced automatically on the next build — there + is nothing else to change. + +Screenshots should be captured at roughly 1600px wide, cropped to the relevant +region of the interface rather than the whole desktop. + +Videos are never committed to the repository. Upload them to YouTube or +Microsoft Stream and link them from the slot, keeping only a small local poster +image in the repo. + +{% assign all_slots = site.data.media %} +{% assign filled_count = 0 %} +{% assign missing_count = 0 %} +{% for slot in all_slots %} + {% assign entry = slot[1] %} + {% if entry.type == 'video' %} + {% if entry.url and entry.url != '' %} + {% assign filled_count = filled_count | plus: 1 %} + {% else %} + {% assign missing_count = missing_count | plus: 1 %} + {% endif %} + {% else %} + {% assign check_path = '/images/' | append: entry.file %} + {% assign found = site.static_files | where: 'path', check_path | first %} + {% if found %} + {% assign filled_count = filled_count | plus: 1 %} + {% else %} + {% assign missing_count = missing_count | plus: 1 %} + {% endif %} + {% endif %} +{% endfor %} + +## Summary + +| Status | Count | +| --- | --- | +| Registered slots | {{ all_slots.size }} | +| Filled | {{ filled_count }} | +| Still needed | {{ missing_count }} | + +## Slots by area + +{% assign groups = "" | split: "" %} +{% for slot in all_slots %} + {% assign entry = slot[1] %} + {% assign group_name = entry.group | default: 'ungrouped' %} + {% unless groups contains group_name %} + {% assign groups = groups | push: group_name %} + {% endunless %} +{% endfor %} +{% assign groups = groups | sort %} + +{% for group_name in groups %} +### {{ group_name | capitalize }} + + + + + + +{%- for slot in all_slots -%} + {%- assign slot_id = slot[0] -%} + {%- assign entry = slot[1] -%} + {%- assign entry_group = entry.group | default: 'ungrouped' -%} + {%- if entry_group == group_name -%} + {%- assign is_filled = false -%} + {%- assign expected = '' -%} + {%- if entry.type == 'video' -%} + {%- if entry.poster -%}{%- assign expected = 'docs/images/' | append: entry.poster -%}{%- endif -%} + {%- if entry.url and entry.url != '' -%}{%- assign is_filled = true -%}{%- endif -%} + {%- else -%} + {%- assign expected = 'docs/images/' | append: entry.file -%} + {%- assign check_path = '/images/' | append: entry.file -%} + {%- assign found = site.static_files | where: 'path', check_path | first -%} + {%- if found -%}{%- assign is_filled = true -%}{%- endif -%} + {%- endif -%} + + + + + + + {%- endif -%} +{%- endfor -%} + +
SlotTypeExpected fileStatus
{{ slot_id }}
{{ entry.title }}
{{ entry.type }}{{ expected }}{% if is_filled %}Filled{% else %}Needed{% endif %}
+ +{% endfor %} + +## Placeholder images awaiting real captures + +Some images exist and render, but are branded "Screenshot pending" graphics +rather than real captures. They ship so pages do not show broken images, but +they still need replacing. + +To resolve one, capture the real screenshot, overwrite the file at the listed +path, and remove its entry from `docs/_data/media_pending.yml`. + +{% assign pending_groups = site.data.media_pending %} +{% assign pending_total = 0 %} +{% for group in pending_groups %} + {% for item in group[1].items %} + {% assign pending_total = pending_total | plus: item.images %} + {% endfor %} +{% endfor %} + +**{{ pending_total }} placeholder images** across {{ pending_groups.size }} release group{% if pending_groups.size != 1 %}s{% endif %}. + +{% for group in pending_groups %} +{% assign meta = group[1] %} +### {{ meta.label }} + +{{ meta.note }} + + + + + + +{%- for item in meta.items -%} + + + + + +{%- endfor -%} + +
PageImagesFiles to replace
{{ item.page }}{{ item.images }}{{ item.path_pattern }}
+{% endfor %} + +## Adding a new slot + +Register it in `docs/_data/media.yml`: + +```yaml +my-slot-id: + type: image + title: "What this shows" + file: "guides/my-screenshot.png" + alt: "Accessible description of what the reader learns from the image." + capture: "Instruction for whoever takes the screenshot." + group: guides + width: 1600 + height: 900 +``` + +Then reference it from any page: + +{% raw %} +```liquid +{% include media.html id="my-slot-id" %} +``` +{% endraw %} + +For a one-off image that does not need tracking, skip the manifest entirely: + +{% raw %} +```liquid +{% include media.html src="guides/my-screenshot.png" + alt="Accessible description." + capture="What to capture." %} +``` +{% endraw %} + +## Live examples + +The three registered seed slots render below. Because their asset files do not +exist yet, each shows the placeholder state a contributor sees. + +{% include media.html id="admin-settings-overview" %} + +{% include media.html id="admin-video-series" %} diff --git a/docs/deploy/manual/application-specific-configuration.md b/docs/deploy/manual/application-specific-configuration.md new file mode 100644 index 000000000..4fae7ff9c --- /dev/null +++ b/docs/deploy/manual/application-specific-configuration.md @@ -0,0 +1,639 @@ +--- + +layout: page + +title: "Application-Specific Configuration Steps" + +description: "Manual setup reference for application-specific configuration steps." + +section: "Deploy & Operate" + +permalink: /deploy/manual/application-specific-configuration/ + +menubar: docs_menu + +--- + + + +[Back to manual setup hub]({{ '/setup_instructions_manual/' | relative_url }}) + + + +## Application-Specific Configuration Steps + + + +With the Azure resources provisioned, proceed with configuring the application itself. Perform these steps in order. + + + +### Setting Up Authentication (Azure AD / Entra ID) + + + +The application uses Azure Active Directory (Entra ID) for user authentication and role management. + + + +1. **Register an Application in Azure AD**: + + * Navigate to **Azure Active Directory** > **App registrations** > **+ New registration**. + + * Give it a name (e.g., `SimpleChatApp-Prod`). + + * Select **Accounts in this organizational directory only** (or adjust if multi-tenant access is needed). + + * Set the **Redirect URI**: + + * Select **Web** platform. + + * Enter the URI: `https://.azurewebsites.net/.auth/login/aad/callback` (Replace `` with your actual App Service name). + + * Click **Register**. + + * Note the **Application (client) ID** and **Directory (tenant) ID**. These are needed for the `.env` file (`CLIENT_ID`, `TENANT_ID`). + + * Next, click the **Authentication** link in the Manage section + + * In the Web **Redirect URIs** section of the page click **Add URI** + + * Enter the URI: `https://.azurewebsites.net/getAToken` (Replace `` with your actual App Service name). + + * Next in the **Front-channel logout URL** section of the page + + * Enter the URI: `https://.azurewebsites.net/logout` (Replace `` with your actual App Service name). + + * Now look at the **Implicit grant and hybrid flows** section + + * Make sure the checked for ***ID tokens (used for implicit and hybrid flows)** is checked + + * Click the **Save** button + + ![App Registration Settings]({{ '/images/app_reg_settings.png' | relative_url }}) *(Note: Image shows general area, details might differ slightly)* + + * Click on the **Certificates and secrets link** in the manage section + + * Click on **Client Secrets** + + * Verify that there is a Secret Named **MICROSOFT_PROVIDER_AUTHENTICATION_SECRET** + + * If there is not, click on **New Client Secret** to create a new secret using **MICROSOFT_PROVIDER_AUTHENTICATION_SECRET** as the name. + + * Make sure you copy the **Value** before you leave this page. + + ![App Registration Settings]({{ '/images/app_reg_secrets.png' | relative_url }}) *(Note: Image shows general area, details might differ slightly)* + + + +2. **Configure App Service Authentication**: + + * Go to your **App Service** in the Azure portal. + + * Navigate to **Settings** > **Authentication**. + + * Click **Add identity provider**. + + * **Identity provider**: Microsoft + + * **App registration type**: Pick an existing app registration in this directory. + + * Select the **App registration** you just created. + + * **Restrict access**: Require authentication. + + * **Unauthenticated requests**: HTTP 302 Found redirect: recommended for web apps. + + * Click **Add**. This configures the built-in App Service Authentication (Easy Auth). + + * ⚠️ **Important** ⚠️: After adding the provider, go back into the **Authentication** settings for the App Service, click **Edit** on the Microsoft provider. + + * Ensure the **Issuer URL** is correct (usually `https://login.microsoftonline.com//v2.0` or `https://sts.windows.net//v2.0`). + + * Note the **Client Secret Setting Name** value shown here. This secret (`MICROSOFT_PROVIDER_AUTHENTICATION_SECRET`) is often automatically added to App Service Application Settings. If the name is not there (or a different name is there) click on **Click to edit secret value** + + * Click on **Add**, for the name use `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET` for the value enter the Key that you copied in the previous step + + * Click **Apply**, the click **Apply** again + + * Return to the **Edit identity provider** page and now select `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET` for the Client Secret setting name. (It may take a minute for that name to appear) + + + + ![App Registration - Authentication Configuration in App Service]({{ '/images/app_reg_edit_identity.png' | relative_url }}) *(Note: Image shows general area, details might differ slightly)* + + + +3. **Configure API Permissions**: + + * Go back to your **App Registration** in Azure AD. + + * Navigate to **API permissions**. + + * Click **+ Add a permission**. + + * Select **Microsoft Graph**. + + * Select **Delegated permissions**. + + * Add the following permissions: + + * `email` + + * `offline_access` + + * `openid` + + * `profile` + + * `User.Read` (Allows sign-in and reading the user's profile) + + * `User.ReadBasic.All` (Allows reading basic profiles of all users - often needed for people pickers if not using `People.Read.All`) + + * **(Conditional)** `People.Read.All`: **Required if** you enable the **My Groups** feature, as it's used to search for users within your tenant to add to groups. Add this permission if needed. + + * **(Conditional)** `Group.Read.All`: **Required if** you enable the **My Groups** feature or need to read group memberships and group details for group workspaces. This permission allows the app to list groups and read group properties and memberships in your organization. Add this permission if group-based collaboration or group document access is needed. + + * After adding permissions, click **Grant admin consent for [Your Tenant Name]**. This is crucial, especially for `*.All` permissions. + + + + ![App Registration - API Permissions]({{ '/images/app_reg-api_permissions.png' | relative_url }}) + + + +4. **Configure App Roles**: + + * In your **App Registration**, navigate to **App roles**. + + * Click **+ Create app role**. + + * Create roles based on the following table. Repeat for each role: + + + + | Display Name | Allowed member types | Value | Description | Do you want to enable this app role? | + + | :------------------------- | :------------------- | :--------------------- | :----------------------------------------------- | :----------------------------------- | + + | **Admins** | Users/Groups | `Admin` | Allows access to Admin Settings page. | Yes | + + | **Users** | Users/Groups | `User` | Standard user access to chat features. | Yes | + + | **Create Group** | Users/Groups | `CreateGroups` | Allows user to create new groups (if enabled). | Yes | + + | **Chat File Upload User** | Users/Groups | `ChatFileUploadUser` | Allows chat file uploads when role enforcement is enabled. | Yes | + + | **Workflow User** | Users/Groups | `WorkflowUser` | Allows personal workflow access when role enforcement is enabled. | Yes | + + | **Safety Violation Admin** | Users/Groups | `SafetyViolationAdmin` | Allows access to view content safety violations. | Yes | + + | **Feedback Admin** | Users/Groups | `FeedbackAdmin` | Allows access to view user feedback admin page. | Yes | + + + + ![App Registration - App Roles]({{ '/images/app_reg-app_roles.png' | relative_url }}) + + + +5. **Assign Users/Groups to Roles via Enterprise Application**: + + * App Roles are *assigned* through the **Enterprise Application** associated with your App Registration. + + * Navigate to **Azure Active Directory** > **Enterprise applications**. + + * Find the application with the same name as your App Registration (or search by Application ID). + + * Select your Enterprise Application. + + * Go to **Users and groups**. + + * Click **+ Add user/group**. + + * Select the users or security groups you want to grant access. + + * Under **Select a role**, choose the appropriate App Role (`Admins`, `Users`, etc.) you defined. + + * Click **Assign**. Only assigned users/groups will be able to log in (if "Assignment required?" is enabled on the Enterprise App, which is recommended). + + + +### Grant App Registration Access to Azure OpenAI (for Model Fetching) + + + +The application needs permission to list the available models deployed in your Azure OpenAI resource(s). This uses the *App Registration's Service Principal*. + + + +1. Go to each **Azure OpenAI** service resource in the Azure portal. + +2. Select **Access control (IAM)**. + +3. Click **+ Add** > **Add role assignment**. + +4. Search for and select the role **Cognitive Services OpenAI User**. Click Next. + +5. **Assign access to**: Select **User, group, or service principal**. + +6. **Members**: Click **+ Select members**. + +7. Search for the **name of your App Registration** (e.g., `SimpleChatApp-Prod`). Select it. + +8. Click **Select**, then **Next**. + +9. Click **Review + assign**. + +10. Repeat for *all* Azure OpenAI resources used by the application (GPT, Embedding, Image Gen if separate). + + + +![Add role assignment - Job function role selected]({{ '/images/add_role_assignment-job_function.png' | relative_url }}) + +![Add role assignment - Selecting the Service Principal (App Registration)]({{ '/images/add_role_assignment-select_member-service_principal.png' | relative_url }}) + + + +### Clone the Repository + + + +Get the application code onto your local machine. + + + +1. Open a terminal or command prompt. + +2. Use Git to clone the repository: + + ```bash + + git clone + + cd + + ``` + + (Replace `` and `` accordingly). + + *Alternatively*, use GitHub Desktop or download the ZIP and extract it. + + + +![Clone the repo options in GitHub UI]({{ '/images/clone_the_repo.png' | relative_url }}) + + + +### Configure Environment Variables (`.env` File) + + + +Core configuration values are managed via environment variables, typically set in the Azure App Service Application Settings. A `.env` file is used locally and can be uploaded to populate these settings. + + + +1. **Create `.env` from Example**: + + * Find the `example.env` file in the cloned repository. + + * Rename or copy it to `.env`. + +2. **Edit `.env`**: + + * Open the `.env` file in a text editor (like VS Code). + + * Fill in the placeholder values with your actual service details: + + + + ```dotenv + + # Azure Cosmos DB + + # Use connection string OR endpoint/key OR managed identity + + # e.g., https://mycosmosdb.documents.azure.com:443/ + + AZURE_COSMOS_ENDPOINT="" + + AZURE_COSMOS_KEY="" + + # Options: "key", "connection_string", "managed_identity" + + AZURE_COSMOS_AUTHENTICATION_TYPE="key" + + + + # Azure AD Authentication (Required) + + CLIENT_ID="" + + TENANT_ID="" + + # SECRET_KEY should be a long, random, secret string (e.g., 32+ chars) used for Flask session signing. Generate one securely. + + SECRET_KEY="Generate-A-Strong-Random-Secret-Key-Here!" + + # AZURE_ENVIRONMENT: Set based on your cloud environment + + # Options: "public", "usgovernment", "custom" + + AZURE_ENVIRONMENT="public" + + ``` + + + +3. **Upload Settings to Azure App Service (Recommended using VS Code)**: + + + + * Ensure the `.env` file is saved and **closed**. + + * In VS Code, with the Azure App Service extension installed and signed in: + + * **Option 1 (Command Palette)**: Press `Ctrl+Shift+P` (or `Cmd+Shift+P`), type `Azure App Service: Upload Local Settings`, select your subscription and App Service instance, then choose the `.env` file. + + * **Option 2 (File Explorer)**: Right-click the `.env` file in the VS Code explorer, select `Azure App Service: Upload Local Settings`, and follow the prompts. + + * This action reads your `.env` file and sets the corresponding **Application Settings** in the Azure App Service configuration blade. + + + + ![Upload local settings - Option 1 (Command Palette)]({{ '/images/upload_local_settings_1.png' | relative_url }}) + + ![Upload local settings - Option 2 (Right-click)]({{ '/images/upload_local_settings_2.png' | relative_url }}) + + + +4. **(Optional) Download Settings from Azure App Service**: + + * To verify or synchronize settings from Azure back to a local `.env` file: + + * Press `Ctrl+Shift+P`, type `Azure App Service: Download Remote Settings`, select your App Service, and choose where to save the file (e.g., overwrite your local `.env`). This is useful to capture settings automatically added by Azure (like `APPLICATIONINSIGHTS_CONNECTION_STRING` or `WEBSITE_AUTH_AAD_ALLOWED_TENANTS`). + + + + ![Download remote settings command]({{ '/images/download_remote_settings.png' | relative_url }}) + + + +5. **First-Time Configuration Wizard**: + + * When you first access the admin settings page, a configuration wizard will guide you through the required and optional settings. + + * The wizard will help you configure: + + * **Application basics**: Title and logo customization + + * **GPT API settings**: Configure Azure OpenAI endpoints and models + + * **Workspace settings**: Enable personal and group workspaces + + * **Additional services**: Configure embedding, AI Search, Document Intelligence, and other required services + + * **Optional features**: Content safety, user feedback, conversation archiving, and other optional features + + * Required settings are clearly marked, ensuring that you configure all necessary components for your deployment scenario. + + + +### Local VS Code Developer Environment + + + +If you want to run Simple Chat locally in VS Code before deploying to Azure App Service, use a repo-local `.venv` created with Python 3.12. + + + +From the repo root on Windows: + + + +```powershell + +py -3.12 -m venv .venv + +.\.venv\Scripts\Activate.ps1 + +pip install --upgrade pip + +pip install -r application/single_app/requirements.txt + +``` + + + +Then in VS Code run `Python: Select Interpreter` and choose the Python 3.12 interpreter inside `.venv`. + + + +For the full local development workflow, including `FLASK_DEBUG` guidance and when to use Docker or WSL2 for Gunicorn validation, see [Running Simple Chat Locally]({{ '/explanation/running_simplechat_locally/' | relative_url }}). + + + +### Alternate Method: Update App Settings via JSON (Advanced) + + + +You can directly edit Application Settings in the Azure portal using the "Advanced edit" feature, pasting a JSON array. This is useful for bulk updates but requires care not to overwrite essential settings added by Azure. + + + +1. Navigate to your **App Service** > **Settings** > **Configuration** > **Application settings**. + +2. **Backup Existing Values**: Before pasting, **copy** the current values for critical settings like `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET`, `APPLICATIONINSIGHTS_CONNECTION_STRING`, and `WEBSITE_AUTH_AAD_ALLOWED_TENANTS`. + +3. **Prepare JSON**: Create a JSON array similar to the example below, inserting your specific values and the backed-up Azure-managed values. + +4. Click **Advanced edit**. + +5. **Carefully replace** the existing JSON content with your prepared JSON. + +6. Click **OK**, then **Save**. + + + +**Example JSON Structure:** + + + +```json + +[ + + // --- Azure Managed / Essential Settings --- + + { "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", "value": "", "slotSetting": false }, + + { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", "value": "", "slotSetting": false }, // Often same key as connection string contains + + { "name": "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET", "value": "", "slotSetting": true }, // CRITICAL - Get from portal if unsure + + { "name": "WEBSITE_AUTH_AAD_ALLOWED_TENANTS", "value": "", "slotSetting": false }, // Usually set by Auth config + + { "name": "WEBSITE_AUTH_ENABLED", "value": "True", "slotSetting": true }, // Should be set by Auth config + + { "name": "WEBSITE_AUTH_DEFAULT_PROVIDER", "value": "AzureActiveDirectory", "slotSetting": true }, // Should be set by Auth config + + + + // --- Your Application Settings (from .env) --- + + { "name": "AZURE_COSMOS_ENDPOINT", "value": "", "slotSetting": false }, + + { "name": "AZURE_COSMOS_KEY", "value": "", "slotSetting": false }, + + { "name": "AZURE_COSMOS_DATABASE", "value": "SimpleChat", "slotSetting": false }, + + { "name": "AZURE_COSMOS_AUTHENTICATION_TYPE", "value": "key", "slotSetting": false }, // or "managed_identity" + + { "name": "CLIENT_ID", "value": "", "slotSetting": false }, + + { "name": "TENANT_ID", "value": "", "slotSetting": false }, + + { "name": "SECRET_KEY", "value": "", "slotSetting": false }, + + { "name": "AZURE_ENVIRONMENT", "value": "public", "slotSetting": false }, // or "usgovernment", or "custom" + + + + // --- Build & Runtime Settings --- + + { "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "true", "slotSetting": false }, // Ensures requirements.txt is processed + + { "name": "WEBSITE_HTTPLOGGING_RETENTION_DAYS", "value": "7", "slotSetting": false }, + + + + // --- Optional App Insights Advanced Settings (Defaults usually fine) --- + + { "name": "ApplicationInsightsAgent_EXTENSION_VERSION", "value": "~3", "slotSetting": false }, + + { "name": "APPLICATIONINSIGHTSAGENT_EXTENSION_ENABLED", "value": "true", "slotSetting": false }, + + { "name": "XDT_MicrosoftApplicationInsights_Mode", "value": "default", "slotSetting": false }, + + { "name": "APPINSIGHTS_PROFILERFEATURE_VERSION", "value": "1.0.0", "slotSetting": false }, + + { "name": "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", "value": "1.0.0", "slotSetting": false }, + + { "name": "SnapshotDebugger_EXTENSION_VERSION", "value": "disabled", "slotSetting": false }, + + { "name": "InstrumentationEngine_EXTENSION_VERSION", "value": "disabled", "slotSetting": false }, + + { "name": "XDT_MicrosoftApplicationInsights_BaseExtensions", "value": "disabled", "slotSetting": false }, + + { "name": "XDT_MicrosoftApplicationInsights_PreemptSdk", "value": "disabled", "slotSetting": false } + +] + +``` + + + +> [!WARNING] + +> + +> Editing Application Settings via JSON is powerful but risky. Incorrectly modifying or omitting settings managed by Azure (especially Authentication or App Insights integration) can break functionality. Proceed with caution and always back up existing values. Using the .env upload method is generally safer. + + + +![alt text]({{ '/images/advanced_edit_env.png' | relative_url }}) + + + +### Initializing Indexes in Azure AI Search + + + +The application requires two Azure AI Search indexes: one for personal user documents and one for shared group documents. The schemas are defined in JSON files within the repository. + + + +1. **Locate Index Schema Files**: + + + + - In your cloned repository, find the artifacts/ai_search_index/ directory. + + - It contains ai_search-index-user.json and ai_search-index-group.json. + + + + ``` + + 📁 SimpleChat + + └── 📁 artifacts + + └── 📁 ai_search_index + + ├── ai_search-index-group.json + + └── ai_search-index-user.json + + ``` + + + +2. **Access Azure AI Search in Azure Portal**: + + + + - Navigate to your **Azure AI Search** service resource. + + - Under **Search management**, select **Indexes**. + + + +3. **Create Indexes from JSON**: + + + + - Click **+ Add index**. + + - Change the creation method from Enter index name to **Import from JSON**. + + - **User Index**: + + - Open ai_search-index-user.json locally, copy its entire content. + + - Paste the JSON into the **Index definition (JSON)** editor in the portal. + + - The Index Name should automatically populate as simplechat-user-index. + + - Click **Save**. + + - **Group Index**: + + - Click **+ Add index** again and choose **Import from JSON**. + + - Open ai_search-index-group.json locally, copy its content. + + - Paste the JSON into the editor. + + - The Index Name should populate as simplechat-group-index. + + - Click **Save**. + + + +4. **Verify Indexes**: + + + + - You should now see simplechat-user-index and simplechat-group-index listed under **Indexes**. + + + +> [!NOTE] + +> + +> **Automatic Schema Update Feature**: If you happen to miss this step or deploy an updated version of the application with new required index fields, the application includes a mechanism to help. When an Admin user navigates to the **Admin > App Settings** page, the application backend checks the schemas of the existing simplechat-user-index and simplechat-group-index against the expected schema. If missing fields are detected, notification buttons will appear at the top of the Admin Settings page: "**Add missing user fields**" and "**Add missing group fields**". Clicking these buttons will automatically add the missing fields to your Azure AI Search indexes without data loss. While this feature provides resilience, it's still recommended to create the indexes correctly using the JSON definitions initially. + + + +![alt text]({{ '/images/ai_search-missing_index_fields.png' | relative_url }}) diff --git a/docs/deploy/manual/installing-and-deploying-application-code.md b/docs/deploy/manual/installing-and-deploying-application-code.md new file mode 100644 index 000000000..5f7a906d9 --- /dev/null +++ b/docs/deploy/manual/installing-and-deploying-application-code.md @@ -0,0 +1,135 @@ +--- + +layout: page + +title: "Installing and Deploying the Application Code" + +description: "Manual setup reference for installing and deploying the application code." + +section: "Deploy & Operate" + +permalink: /deploy/manual/installing-and-deploying-application-code/ + +menubar: docs_menu + +--- + + + +[Back to manual setup hub]({{ '/setup_instructions_manual/' | relative_url }}) + + + +## Installing and Deploying the Application Code + + + +Deploy the application code from your local repository to the Azure App Service. + + + +### Deploying via VS Code (Recommended for Simplicity) + + + +1. **Ensure Azure Extensions are Installed**: You need the **Azure Tools Extension Pack** and the **Azure App Service** extension in VS Code. + +2. **Sign In to Azure**: Use the Azure extension to sign in to your Azure account. + +3. **Deploy**: + + - In the VS Code Activity Bar, click the Azure icon. + + - Expand **App Service**, find your subscription and the App Service instance you created. + + - **Right-click** on the App Service name. + + - Select **Deploy to Web App...**. + + - Browse and select the `application/single_app` folder from the repository. + + - VS Code will prompt to confirm the deployment, potentially warning about overwriting existing content. Click **Deploy**. + + - Make sure your requirements.txt file is up-to-date before deploying. The deployment process (SCM_DO_BUILD_DURING_DEPLOYMENT=true) will use this file to install dependencies on the App Service. + + - Monitor the deployment progress in the VS Code Output window. + + + +### Deploying via Azure CLI (Zip Deploy) + + + +This method involves creating a zip file of the application code and uploading it using the Azure CLI. Refer to the official documentation for detailed steps: [Quickstart: Deploy a Python web app to Azure App Service](https://www.google.com/url?sa=E&q=https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Czip-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli). + + + +**Key Steps:** + + + +1. **Create the ZIP file**: + + + + - Navigate into `application/single_app` in your terminal. + + - Create a zip file containing **only** the necessary application files and folders. **Crucially, zip the contents, not the parent folder itself.** + + - **Include**: + + - static/ folder + + - templates/ folder + + - requirements.txt file + + - All Python files (*.py) at the root level (e.g., app.py, utils.py, etc.). + + - Any other necessary support files or directories at the root level. + + - **Exclude**: + + - .git/ folder and .gitignore + + - .vscode/ folder + + - __pycache__/ directories + + - .env, example.env (environment variables are set in App Settings) + + - .deployment, Dockerfile, .dockerignore (unless specifically using Docker deployment) + + - README.md, LICENSE, .DS_Store, etc. + + - Any local virtual environment folders (e.g., .venv, env). + + + + ![alt text]({{ '/images/files_to_zip.png' | relative_url }}) + + + + ![alt text]({{ '/images/zip_the_files.png' | relative_url }}) + + + + **Ensure SCM_DO_BUILD_DURING_DEPLOYMENT is Set**: Verify this application setting is true in your App Service configuration to ensure dependencies are installed from requirements.txt during deployment. + + + +2. **Deploy using Azure CLI**: + + + + ``` + + az login # Sign in if you haven't already + + az account set --subscription "" + + + + az webapp deploy --resource-group --name --src-path ../deployment.zip --type zip + + ``` diff --git a/docs/deploy/manual/provision-azure-resources.md b/docs/deploy/manual/provision-azure-resources.md new file mode 100644 index 000000000..e91db1720 --- /dev/null +++ b/docs/deploy/manual/provision-azure-resources.md @@ -0,0 +1,307 @@ +--- + +layout: page + +title: "Provision Azure Resources" + +description: "Manual setup reference for provision azure resources." + +section: "Deploy & Operate" + +permalink: /deploy/manual/provision-azure-resources/ + +menubar: docs_menu + +--- + + + +[Back to manual setup hub]({{ '/setup_instructions_manual/' | relative_url }}) + + + +## Provision Azure Resources + + + +Deploy the necessary Azure services. For a quick estimate of monthly costs based on recommended baseline SKUs for a Demo/Proof-of-Concept (POC)/Minimum Viable Product (MVP) solution, refer to the [Azure Pricing Calculator Link](https://azure.com/e/86504dd2857343ae80bda654ae4cc2f4). The services and SKUs below are reflected in that estimate. + + + +> [!IMPORTANT] + +> The following recommended SKUs are intended for **Development, Demo, POC, or MVP purposes only**. You **must** scale these services appropriately based on expected user load, data volume, and performance requirements when moving to a Production environment. Factors like concurrent users, document ingestion rate, and query complexity will influence the required tiers and instance counts. + + + +| Service Type | Recommended Minimum SKU (for Dev/Demo/POC/MVP) | Description / Notes | + +| :--------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | + +| **App Service (Frontend)** | Premium V3 P0v3 (1 Core, 4 GB RAM, 250 GB Storage), Linux | Hosts the Python Flask web application. Consider scaling up (P1v3+) or out (multiple instances) for production. | + +| **Azure OpenAI (GPT)** | Standard S0, `gpt-4o` deployment | Powers core chat functionality and optional Metadata Extraction. Choose model based on cost/performance needs. Pay-as-you-go pricing. | + +| **Azure OpenAI (Embedding)** | Standard S0, `text-embedding-3-small` deployment | Required for RAG (Your Workspace, My Groups). Generates vector embeddings. Pay-as-you-go. | + +| **Azure OpenAI (Image Gen)** | Standard S0, `dall-e-3` deployment (Optional) | Required only if Image Generation feature is enabled. Pay-as-you-go per image. | + +| **Azure AI Search** | Standard S1 (consider S2/S3 for larger scale/HA) | Stores and indexes document chunks for RAG. Includes Semantic Ranker capacity. Scale units/replicas/partitions for performance/HA. | + +| **Content Safety** | Standard S0 (Optional) | Required only if Content Safety feature is enabled. Pay-as-you-go per 1k text records / 1k images. | + +| **Document Intelligence** | Standard S0 | Used for text/layout extraction from various file types during ingestion. Pay-as-you-go per page processed. | + +| **Cosmos DB (NoSQL)** | Autoscale provisioned throughput (Start ~1000 RU/s), Single-Region Write | Stores metadata, conversations, settings. Autoscale helps manage costs, but monitor RU consumption and adjust max RU for production loads. | + +| **Video Indexer** | Standard Tier (Optional) | Required only if Video Extraction feature is enabled. Pay-as-you-go per input content minute (All Insights). | + +| **Speech Service** | Standard S0 (Optional) | Required only if Audio Extraction feature is enabled. Pay-as-you-go per audio hour (Standard fast transcription). | + +| **Storage Account** | General Purpose V2, LRS, Hot Tier (Optional) | Required if Enhanced Citations feature is enabled. Stores processed files. Hierarchical Namespace (ADLS Gen2) recommended. - OR - Required if you want to use Azure Storage for temporaty file storage which is recommend for scalability and better performance | + +| **Azure Cache for Redis** | Standard Tier, C0 cache size (Optional) | Required only if you need the performance, scalability, and distributed session support provided by Redis Cache. | + + + +> **Note**: Pricing is subject to change and varies significantly based on usage, region, specific configurations (e.g., network security, backup policies), and selected tiers. Always use the official Azure Pricing Calculator and monitor your Azure costs closely. + + + +**Deployment Steps:** + + + +1. **Create or Select a Resource Group**: + + * Group all related resources within a single Azure Resource Group (e.g., `rg-simple-chat-prod`, `rg-simple-chat-dev`). + + * Deploy resources in the same Azure region where possible to minimize latency, unless specific service availability or compliance dictates otherwise (e.g., Azure OpenAI model availability). + +2. **Deploy App Service**: + + * Create an Azure App Service instance. + + * **Publish**: Code + + * **Runtime stack**: Python 3.12 + + * **Operating System**: Linux + + * **Region**: Choose your desired region. + + * **App Service Plan**: Create a new Linux plan using the **Premium V3 (P0v3)** tier (or higher for production). Zone redundancy typically **Disabled** for baseline, enable for HA if needed. + + * Review Networking (Public access defaults), Deployment, Monitoring settings. Modify based on organizational security/operational requirements. + + * Note the default **App Name** and **URL** (e.g., `https://my-simplechat-app.azurewebsites.net`). This URL will be needed for AAD App Registration redirects. + +3. **Deploy Azure OpenAI Service(s)**: + + * You can deploy a single Azure OpenAI resource hosting all models or separate resources (e.g., one for GPT, one for Embeddings) based on regional availability or management preference. + + * Create an **Azure OpenAI** resource. Select **Standard S0** pricing tier. + + * **Deploy Models**: Within the Azure OpenAI Studio for your resource(s), deploy the required models with custom deployment names: + + * **GPT Model**: e.g., `gpt-4o` (Required for chat, optional for metadata). Note the **Deployment Name**. + + * **Embedding Model**: e.g., `text-embedding-3-small` (Required for Workspaces/RAG). Note the **Deployment Name**. + + * **Image Generation Model**: e.g., `dall-e-3` (Required for optional Image Generation). Note the **Deployment Name**. + + * Review Networking settings (default public access, modify as needed). + + * If using **Managed Identity** authentication later, you will need to grant the App Service's Managed Identity the `Cognitive Services OpenAI User` role on this resource(s). + +4. **Deploy Azure AI Search**: + + * Create an **Azure AI Search** service. + + * Select the **Standard S1** tier (or higher based on scale/HA needs). Consider replicas/partitions for production. + + * Review Networking settings. + + * You will initialize indexes later ([Initializing indexes]({{ '/deploy/manual/application-specific-configuration/#initializing-indexes-in-azure-ai-search' | relative_url }})). + + * If using **Managed Identity**, grant the App Service's Managed Identity the `Search Index Data Contributor` role on this resource. + +5. **Deploy Azure Cosmos DB**: + + * Create an **Azure Cosmos DB** account. + + * Select the **Azure Cosmos DB for NoSQL** API. + + * **Capacity mode**: Provisioned throughput. Choose **Autoscale**. + + * Set **Max throughput** at the database level initially (e.g., start with 1000 RU/s, monitor and adjust). + + - Note: Autoscale automatically adjusts the provisioned Request Units (RU/s) between 10% and 100% of this maximum value based on usage (e.g., 1000 max RU/s scales between 100 - 1000 RU/s). + + - **Container-Level Scaling (Recommended Post-Setup)**: While you set an initial database-level throughput, it's highly recommended to configure Autoscale throughput **per container** after the application creates them (or manually create them with these settings). For optimal performance and cost-efficiency, consider setting the *maximum* Autoscale throughput for key containers as follows: + + - messages container: **4000 RU/s** (will scale between 400 - 4000 RU/s) + + - documents container: **4000 RU/s** (will scale between 400 - 4000 RU/s) + + - group_documents container: **4000 RU/s** (will scale between 400 - 4000 RU/s) + + - Other containers (like settings, feedback, archived_conversations) often have lower usage and can typically start with a lower maximum (e.g., 1000 RU/s, scaling 100-1000 RU/s), but monitor their consumption. + + * **Apply Free Tier Discount**: **DO NOT APPLY** (Free tier throughput is insufficient). + + * **Limit total account throughput**: **Uncheck** (DISABLE). + + * Review Networking, Backup Policy, Encryption settings. + + * If using **Managed Identity**, grant the App Service's Managed Identity the `Cosmos DB Built-in Data Contributor` role (or create custom roles for least privilege). *Note: Managed Identity support for Cosmos DB data plane might require specific configurations.* Key-based auth is simpler initially. + +6. **Deploy Azure AI Document Intelligence**: + + * Create an **Azure AI Document Intelligence** (formerly Form Recognizer) resource. + + * Select the **Standard S0** pricing tier. + + * Review Networking settings. + + * If using **Managed Identity**, grant the App Service's Managed Identity the `Cognitive Services User` role on this resource. + +7. **Deploy Azure AI Content Safety (Optional)**: + + * If using the Content Safety feature, create an **Azure AI Content Safety** resource. + + * Select the **Standard S0** pricing tier. + + * Review Networking settings. + + * If using **Managed Identity**, grant the App Service's Managed Identity the `Cognitive Services Contributor` role on this resource. + +8. **Deploy Azure Video Indexer (Optional)**: + + * If using the Video Extraction feature, create an **Azure Video Indexer** resource in the Azure Portal. + + * You'll need to associate it with an Azure Media Services account (can be created during VI setup) and a Storage Account (used for temporary processing, can be new or existing). + + * **Enable System-assigned Managed Identity** on your App Service if not already enabled (Identity > System assigned > Status: On). + + * **Grant the App Service's Managed Identity the `Contributor` role** on the Video Indexer resource: + + - Navigate to your Video Indexer resource > Access control (IAM) + + - Add role assignment > Select `Contributor` role + + - Assign access to "Managed Identity" + + - Select your App Service's managed identity + + * Note the **Account ID**, **Account Name**, **Resource Group**, **Subscription ID**, and **Location** (e.g., eastus). These will be configured in Admin Settings. + + * See [Azure Video Indexer documentation](https://learn.microsoft.com/azure/azure-video-indexer/connect-to-azure) for detailed setup instructions. + +9. **Deploy Azure Speech Service (Optional)**: + + * If using the Audio Extraction feature, create an **Azure AI Speech** resource. + + * Select the **Standard S0** pricing tier. + + * Review Networking and Identity settings. + + * Note the **Endpoint**, **Region/Location**, and one of the **Keys**. These will be configured in Admin Settings. + +11. **Deploy Storage Account (Optional)**: + + * If using the Enhanced Citations feature, create an **Azure Storage Account**. + + * **Performance**: Standard. + + * **Redundancy**: LRS (or higher based on requirements). + + * **Account Kind**: StorageV2 (general purpose v2). + + * **Enable hierarchical namespace** (Azure Data Lake Storage Gen2) is recommended for better organization if storing large volumes. + + * Review Networking, Data protection, Encryption settings. + + * Note the **Connection String** (under Access Keys or SAS token). This will be configured in Admin Settings. If using Managed Identity, grant the App Service's Managed Identity the `Storage Blob Data Contributor` role. + + * After deployment, note the **Connection String** (under Access Keys or SAS token). This will be configured in Admin Settings. If using Managed Identity, grant the App Service's Managed Identity the `Storage Blob Data Contributor` role. + + * Navigate to **Data Storage** > **Containers** > **+ Container**. Add two new containers - `user-documents` and `group-documents + +10. **Deploy Azure Cache for Redis (Optional)**: + + * Create an **Azure Cache for Redis** service. + + * **Name**: Choose a unique name for your Redis instance (e.g., `simplechat-redis`). + + * **Region**: Select the same region as your App Service for lowest latency. + + * **Cache SKU**: Standard. + + * **Cache Size**: C0 (or higher based on requirements). + + * **Networking**: Set to **Public** for initial setup (can be made private later for enhanced security). + + * **Advanced**: + + - Enable **Access Keys Authentication** (required for key-based access). + + - All other advanced settings can remain at their defaults unless you have specific requirements. + + * After Redis is created, note the **Host Name** and **Access Keys** (if using key authentication). + + * If using managed identities, enable Entra Authentication and select the App Service managed identity. + + * The Redis service can take 15-30 minutes to fully deploy. + +13. **Use Azure Storage for temporary data data (Optional)**: + + * Create an **Azure Storage Account** if you previously created Enhanced Citations you can use it. Otherwise look at step 11 for recommendations on settings. + + * **Enable Storage Account Key Access** + + - Goto the storage account + + - Click on Configuration (in the Settings section) + + - Click on Enable Key Access and click Save + + * **Create a FileShare**: + + - Cick on File Shares in the Data Storage section + + - Click Add File Share + + - Give it a name (write it down for use later) + + - Click Next: Backup + + - Turn off enable backup, unless you are using this share for other files + + - Click Review and Create, then Create + + * **Create the Share in your App Service**: + + - Return to your App Service + + - Click on Configuration (the in Settings Section) + + - Click on Path Mappings + + - Click on Add New Azure Storage Mount + + - Give it a name + + - Use Basic for Configuration Options + + - Select your storage account + + - Select Azure Files for Storage Type + + - Select SMB for Protocal + + - Select the FileShare your created for the Storage Container + + - Set the mount path **/sc-temp-files** - Important to use this name + + - Click OK and then click Save diff --git a/docs/deploy/manual/running-the-application.md b/docs/deploy/manual/running-the-application.md new file mode 100644 index 000000000..baa137df4 --- /dev/null +++ b/docs/deploy/manual/running-the-application.md @@ -0,0 +1,37 @@ +--- + +layout: page + +title: "Running the Application" + +description: "Manual setup reference for running the application." + +section: "Deploy & Operate" + +permalink: /deploy/manual/running-the-application/ + +menubar: docs_menu + +--- + + + +[Back to manual setup hub]({{ '/setup_instructions_manual/' | relative_url }}) + + + +## Running the Application + + + +1. Navigate to your **App Service** in the Azure Portal. + +2. On the **Overview** blade, find the **Default domain** URL (e.g., https://my-simplechat-app.azurewebsites.net). + +3. Click the URL to open the application in your browser. + +4. You should be redirected to the Microsoft login page to authenticate via Azure AD. Log in with a user account that has been assigned a role in the Enterprise Application. + + + +![alt text]({{ '/images/visit_app.png' | relative_url }}) diff --git a/docs/deploy/manual/upgrading-the-application.md b/docs/deploy/manual/upgrading-the-application.md new file mode 100644 index 000000000..07d63eb6d --- /dev/null +++ b/docs/deploy/manual/upgrading-the-application.md @@ -0,0 +1,195 @@ +--- + +layout: page + +title: "Upgrading the Application" + +description: "Manual setup reference for upgrading the application." + +section: "Deploy & Operate" + +permalink: /deploy/manual/upgrading-the-application/ + +menubar: docs_menu + +--- + + + +[Back to manual setup hub]({{ '/setup_instructions_manual/' | relative_url }}) + + + +## Upgrading the Application + + + +This section covers **native Python Azure App Service** upgrades for the manual deployment path. + + + +Before upgrading a native Python deployment, confirm that the App Service Stack Settings Startup command is set correctly and is not blank. + + + +Deploy and run the `application/single_app` folder in App Service. + + + +Use this Startup command: + + + +```bash + +python -m gunicorn -c gunicorn.conf.py app:app + +``` + + + +For a shorter decision guide that also covers container-based upgrades, see [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}). + + + +Keeping your Simple Chat application up-to-date involves deploying the newer version of the code. Using **Deployment Slots** is the recommended approach for production environments to ensure zero downtime and provide easy rollback capabilities. + + + +![alt text]({{ '/images/admin_settings-upgrade_available_notification.png' | relative_url }}) + + + +### Using Deployment Slots (Recommended for Production/Staging) + + + +1. **Create a Deployment Slot**: + + + + - In your App Service, go to **Deployment** > **Deployment slots**. + + - Click **+ Add Slot**. Give it a name (e.g., staging). + + - Choose to **clone settings** from the production slot initially. + + - This creates a fully functional, independent instance of your app connected to the same App Service Plan. + + + +2. **Deploy New Version to Staging Slot**: + + + + - Deploy the updated application code (using VS Code deployment or az webapp deploy) specifically targeting the **staging slot**. + + + + - **VS Code**: When deploying, VS Code will prompt you to select the target slot (production or staging). Choose staging. + + + + - **Azure CLI**: Add the --slot staging parameter to your az webapp deploy command: + + + + ``` + + az webapp deploy --resource-group --name --src-path --type zip --slot staging + + ``` + + + +3. **Test the Staging Slot**: + + + + - The staging slot has its own unique URL (e.g., https://my-simplechat-app-staging.azurewebsites.net). Access this URL directly. + + - Thoroughly test all application functionality, including new features and critical paths, in the staging environment. This slot typically uses the same backend resources (Cosmos DB, AI Search, etc.) as production unless configured otherwise (e.g., using slot-specific Application Settings). + + + +4. **Swap Staging to Production**: + + + + - Once confident the new version in staging is stable, go back to **Deployment slots** in the Azure portal. + + + + - Click the **Swap** button. + + + + - Configure the swap: + + + + - **Source**: staging + + - **Target**: production + + + + - Azure performs a "warm-up" of the staging slot instance before redirecting production traffic to it. The previous production code is simultaneously moved to the staging slot. This swap happens near-instantly from a user perspective. + + + + - **Azure CLI Swap Command**: + + + + ``` + + az webapp deployment slot swap --resource-group --name --slot staging --target-slot production + + ``` + + + +5. **Monitor and Rollback (If Necessary)**: + + + + - Monitor the application closely after the swap using Application Insights and user feedback. + + - If critical issues arise, you can perform another **Swap** operation, this time swapping production (which now contains the problematic code) back with staging (which now contains the previous stable code). This provides an immediate rollback. + + + +### Using Direct Deployment to Production (Simpler, for Dev/Test or Low Impact Changes) + + + +You can deploy directly to the production slot using the same VS Code or Azure CLI methods described in the initial deployment section, simply omitting the --slot parameter or choosing the production slot in VS Code. + + + +> [!WARNING] + +> + +> Deploying directly to production overwrites the live code. This will cause a brief application restart and offers no immediate rollback capability (you would need to redeploy the previous version). This method is generally **not recommended** for production environments or significant updates due to the downtime and risk involved. + + + +### Automate via CI/CD + + + +For mature development practices, set up a Continuous Integration/Continuous Deployment (CI/CD) pipeline using tools like GitHub Actions or Azure DevOps Pipelines. A typical pipeline would: + + + +1. Trigger on code commits/merges to specific branches (e.g., main, release/*). + +2. Build the application artifact (e.g., create the zip file). + +3. Deploy the artifact to the staging slot. + +4. (Optional) Run automated tests against the staging slot. + +5. Require manual approval (or automatically trigger based on test results) to perform the swap operation to production. diff --git a/docs/setup_instructions_special.md b/docs/deploy/special-scenarios.md similarity index 91% rename from docs/setup_instructions_special.md rename to docs/deploy/special-scenarios.md index c94da512f..64f8444bc 100644 --- a/docs/setup_instructions_special.md +++ b/docs/deploy/special-scenarios.md @@ -1,7 +1,8 @@ --- layout: showcase-page title: "Special Setup Scenarios" -permalink: /setup_instructions_special/ +section: "Deploy & Operate" +permalink: /deploy/special-scenarios/ menubar: docs_menu accent: teal eyebrow: "Advanced Deployment Conditions" @@ -16,7 +17,7 @@ hero_pills: - Enterprise private networking hero_links: - label: Back to getting started - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: Manual setup reference url: /setup_instructions_manual/ @@ -26,6 +27,8 @@ nav_links: prev: title: Manual Setup url: /setup_instructions_manual/ +redirect_from: + - /setup_instructions_special/ --- This page covers the deployment conditions that usually force teams off the quickest path: sovereign cloud requirements, secretless runtime access, and private network topologies. @@ -89,7 +92,7 @@ Using Managed Identity allows the App Service to authenticate to other Azure res - Under the **System assigned** tab, switch **Status** to **On**. - Click **Save**. Azure creates an identity for your App Service in Azure AD. Note the **Object (principal) ID**. - ![enable managed identity](./images/enable_managed_identity.png) + ![enable managed identity]({{ '/images/enable_managed_identity.png' | relative_url }}) 2. **Assign Roles to the Managed Identity**: @@ -103,9 +106,9 @@ Using Managed Identity allows the App Service to authenticate to other Azure res - Choose the **Subscription**, select **App Service** for the Managed identity type, and then find and select the **name of your App Service instance**. Click **Select**. - Click **Review + assign**. - ![add role assignment - job function](./images/add_role_assignment-job_function.png) + ![add role assignment - job function]({{ '/images/add_role_assignment-job_function.png' | relative_url }}) - ![add role assignment - managed identity](./images/add_role_assignment-select_member-managed_identity.png) + ![add role assignment - managed identity]({{ '/images/add_role_assignment-select_member-managed_identity.png' | relative_url }}) **Required Roles for Managed Identity Access:** @@ -129,7 +132,15 @@ Using Managed Identity allows the App Service to authenticate to other Azure res ## Enterprise Networking -![Architecture with Private Endpoints](./images/architecture-private-endpoints.png) +{% include media.html src="architecture-private-endpoints-commercial.png" + alt="Reference architecture for Azure Commercial showing Simple Chat connected to Azure services through private endpoints inside a virtual network." + title="Private endpoint architecture (Azure Commercial)" + caption="Azure Commercial reference architecture with private endpoints." %} + +{% include media.html src="architecture-private-endpoints-government.png" + alt="Reference architecture for Azure Government showing Simple Chat connected to Azure services through private endpoints inside a virtual network." + title="Private endpoint architecture (Azure Government)" + caption="Azure Government reference architecture with private endpoints." %} ### Private Endpoints, Virtual Networks, Subnets, and Private DNS Zones diff --git a/docs/explanation/archive_release_notes.md b/docs/explanation/archive_release_notes.md index 2c5f0274a..ba812b55a 100644 --- a/docs/explanation/archive_release_notes.md +++ b/docs/explanation/archive_release_notes.md @@ -2,7 +2,7 @@ Release notes for versions **before v0.230.001**, split out of the [current release notes](/explanation/release_notes/) to keep that page readable. Entries below are unchanged from when they were published, running from v0.229.063 back to v0.190.1. -For anything newer, see the [current release notes](/explanation/release_notes/). For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/). +For anything newer, see the [current release notes](/explanation/release_notes/). For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). ### **(v0.229.063)** diff --git a/docs/explanation/features/DOCUMENTATION_SITE_REDESIGN.md b/docs/explanation/features/DOCUMENTATION_SITE_REDESIGN.md new file mode 100644 index 000000000..b7b43e259 --- /dev/null +++ b/docs/explanation/features/DOCUMENTATION_SITE_REDESIGN.md @@ -0,0 +1,276 @@ +# Documentation Site Redesign + +## Overview + +The documentation site at was rebuilt to +fix search, navigation, page complexity, mobile support, and content coverage, +and to add a maintainable place for screenshots and videos. + +**Implemented in version:** 0.250.230 + +## The problems this addressed + +Every item below was measured against the live site or the `docs/` tree before +the work started. + +### Search was unusable + +| Measurement | Value | +| --- | --- | +| Entries in `search.json` | 986 | +| Entries that were internal engineering notes | 830 (84%) | +| Entries with an empty description | 867 (88%) | +| Entries carrying page body text | 0 | +| Entries titled with a raw filename | 37 | + +Search matched a substring against page titles only. Because 594 bug-fix notes +and 236 versioned feature notes were published, a query for "agent" returned 83 +mostly-internal results such as `ACR_BUILD_WORKFLOW_FOR_DEPLOYERS`. + +Search was also entirely absent on phones: `main.scss` set +`.docs-topbar-search { display: none; }` and only revealed it at `min-width: 768px`. + +### Pages were visually complex and had no on-page navigation + +Landing pages were hand-written HTML card markup with **zero markdown headings**. + +| Page | HTML blocks | Markdown headings | +| --- | --- | --- | +| `index.md` | 82 | 0 | +| `features.md` | 119 | 0 | +| `faqs.md` | 138 | 0 | +| `setup_instructions.md` | 101 | 0 | + +The "On this page" table of contents and heading anchors are generated from +`.docs-prose h2[id]` elements, so these pages had neither. + +### Navigation contradicted itself + +A four-item top bar did not match the five sidebar groups. Navigation covered 27 +URLs while roughly 130 published pages were unreachable from it, including every +`latest-release/` page. Internal theme documentation (`/README-THEME/` and +`/theme-documentation/`) was published publicly. + +### The site did not work on phones + +| Measurement | Value | +| --- | --- | +| Media queries in 1,947 lines of CSS | 8 | +| Breakpoint values, inconsistently mixed | `768px`, `767.98px`, `991.98px`, `575.98px` | +| `overflow-x` declarations | 1 | +| Table containment rules | 1 | +| Images with `loading="lazy"` | 0 of 210 (41.5 MB) | + +`sidebar.js` also hardcoded `DESKTOP_BREAKPOINT = 992` separately from the CSS. + +A pre-existing desktop bug compounded this: `.docs-main-content` combined +`width: 100%` with `margin-left: var(--sidebar-width)`, overflowing every desktop +viewport horizontally by exactly the sidebar width. + +### Content coverage was incomplete + +The application exposes 111 capability toggles, 18 admin settings tabs, 27 action +plugins, 47 chat controls, and 28 user-facing pages. The features page described +roughly a dozen capabilities, and there was no per-tab admin guidance, no action +reference, and no chat interface reference. + +There was also no convention for screenshots beyond two folders wired into a +single layout, and videos were effectively unused (2 incidental references). + +## What changed + +### Search + +- Engineering notes under `docs/explanation/features/` and + `docs/explanation/fixes/` are excluded from the Jekyll build. They remain in + the repository and readable on GitHub. +- `search.json` was replaced with `search-index.json`, which indexes page body + text, headings, section, and audience, and guarantees a description for every + entry. +- Search is powered by Lunr, vendored locally at + `docs/assets/js/vendor/lunr-2.3.9/`. +- Added a `/search/` results page with section filters, result counts, and + highlighted excerpts, plus a top bar typeahead, `Ctrl+K` and `/` shortcuts, + arrow-key navigation, and a full-screen mobile search sheet. + +| Search index | Before | After | +| --- | --- | --- | +| Entries | 986 | 165 | +| Engineering notes | 830 | 0 | +| Entries with no description | 867 | 0 | +| Entries with body text | 0 | all content pages | +| Filename titles | 37 | 0 | +| Reachable on mobile | No | Yes | + +### Media placeholder system + +`docs/_includes/media.html` renders a screenshot or video slot. When the asset +file does not exist it renders a visible card naming the exact path to create. +When someone adds the file at that path, the placeholder is replaced +automatically on the next build, with no YAML or code change. + +- Images: `docs/images//.png`, lazy-loaded, intrinsically sized, + click to enlarge. +- Videos: a local poster card that links out to YouTube or Microsoft Stream. No + iframes and no committed MP4 files. A slot with no URL renders a "Video + planned" state. +- `/contributing/media-status/` lists every registered slot and whether it is + filled, as a capture worklist. +- Slots can be registered in `docs/_data/media.yml` for tracking, or used inline + with `src`, `alt`, and `capture` for one-off images. + +### Information architecture + +Top bar and sidebar now expose the same six sections: Start, Guides, Features, +Administration, Deploy and operate, Reference. Navigation coverage went from 27 +URLs to 67, all verified to resolve. + +Path-scoped Jekyll defaults were fixed. They previously used collection names as +the `type`, so they never applied and every page fell back to a generic "Docs" +section, which is why search facets were meaningless. + +### Responsive design + +- One canonical breakpoint scale (`576 / 768 / 992 / 1200`), exported to + JavaScript through the `--docs-breakpoint-lg` custom property so `sidebar.js` + no longer hardcodes it. +- Tables and code blocks are contained in horizontal scroll regions. +- Card grids use `repeat(auto-fit, minmax(min(100%, 280px), 1fr))`. +- Minimum 44px tap targets, focus trap and focus restore on the mobile drawer + and search sheet. +- Fixed the pre-existing desktop horizontal overflow. + +### Local browser assets + +Removed jQuery, DataTables, marked, DOMPurify, and split.js, none of which the +site used. Vendored Bootstrap 5.3.0, Bootstrap Icons 1.11.3, Prism 1.29.0, Lunr +2.3.9, and the Inter, JetBrains Mono, and Work Sans fonts under +`docs/assets/vendor/` with their licenses. The site now issues zero external +requests, satisfying `local_browser_assets.instructions.md`. + +### Content + +- 19 administration pages, one per admin settings tab, each covering what the tab + controls, why it matters, every setting with its default and the governing + `enable_*` key, prerequisites, real procedures with an outcome to verify, and + screenshot and video placeholders. +- Task guides under `docs/guides/`, each opening with what the task does and why + before the steps. +- A chat interface reference and an action reference. +- A data-driven feature catalog where every capability toggle is claimed by + exactly one entry. +- The 452 KB release notes page was split into per-series pages while preserving + the `/explanation/release_notes/` permalink the application links to. + +## Keeping documentation current + +This is the part designed to prevent the coverage from decaying again. + +`scripts/build_docs_inventory.py` performs static analysis of the application and +writes `docs/_data/app_surface.yml`: + +| Inventory | Source | Count | +| --- | --- | --- | +| `capabilities` | `functions_settings.py` | 111 | +| `admin_tabs` | `templates/admin_settings.html` | 18 | +| `actions` | `semantic_kernel_plugins/*.py` | 27 | +| `chat_controls` | `templates/chats.html` | 47 | +| `app_pages` | `templates/*.html` | 28 | +| `feature_surfaces` | `templates/_*.html` | 21 | + +`functional_tests/test_docs_app_surface_coverage.py` then fails when any of those +is undocumented, and verifies the inventory itself is still in sync so a stale +inventory cannot hide a gap. Internal-only flags may be exempted, but each +exemption must carry a written justification and is itself checked for staleness. + +The inventory deliberately does not record the application version. SimpleChat +bumps `VERSION` on every change, so embedding it would make the file differ on +every pull request and turn the sync check into noise. + +## File structure + +``` +docs/ + _data/ + app_surface.yml Generated application inventory + features.yml Feature catalog source of truth + media.yml Screenshot and video slot registry + _includes/ + media.html Auto-resolving media placeholder + _layouts/ + feature.html Feature detail page + assets/ + css/docs-media.scss Media and placeholder styling + css/docs-search.scss Search page and filter styling + js/media.js Screenshot lightbox + js/search.js Lunr-backed search + js/vendor/lunr-2.3.9/ Vendored search library + vendor/ Vendored Bootstrap, Bootstrap Icons, Prism, fonts + admin/ One page per admin settings tab + guides/ Task guides + reference/actions/ Action reference + search-index.json Content-bearing search index + search.md Search results page + contributing/media-status.md Screenshot and video worklist + +scripts/ + build_docs_inventory.py Generates app_surface.yml + generate_feature_pages.py Generates feature collection stubs + build_release_notes_pages.py Splits release notes into series pages + +functional_tests/ + test_docs_app_surface_coverage.py + test_docs_site_quality.py + +ui_tests/ + test_docs_site_responsive.js +``` + +## Testing and validation + +`ui_tests/test_docs_site_responsive.js` drives the built site with Playwright at +360x640, 390x844, 768x1024, 1280x800, and 1920x1080, asserting no horizontal +overflow, reachable search and navigation at every size, working search +relevance, a functioning mobile search sheet, rendered media placeholders, and +zero external asset requests. All 100 checks pass. + +`ui_tests/check_docs_links.js` walks the built site and verifies every internal +`href` and `src` resolves, resolving relative links against the containing page +rather than only checking absolute URLs. That distinction matters: page moves +break relative links, and a checker that only inspects absolute hrefs reports a +clean run while the site is quietly broken. + +The site currently has **zero broken internal links across 31,650 checked**. +Sample-code pages under `custom_pages_examples/` are skipped, because they +reference application runtime paths such as `/custom/assets/...` and unrendered +template variables such as `{{ script_url }}` that only resolve when the sample +runs inside SimpleChat. + +Run the suite locally with: + +```powershell +cd docs +bundle exec jekyll build +cd _site; python -m http.server 4111 # serve so that /simplechat maps to the built site +node ..\..\ui_tests\test_docs_site_responsive.js +node ..\..\ui_tests\check_docs_links.js ..\..\docs\_site + +python .\functional_tests\test_docs_app_surface_coverage.py +python .\functional_tests\test_docs_site_quality.py +``` + +## Known limitations + +- Screenshot and video slots are scaffolded but the assets themselves still need + to be captured. `/contributing/media-status/` tracks what is outstanding, and + every unfilled slot renders a visible placeholder rather than failing silently. +- Action reference pages are written to full depth for the ten most-used actions; + the remaining actions have shorter overview pages. +- A small number of internal capability flags are exempted from the coverage test + with written justifications. +- Release highlight screenshots are duplicated between + `application/single_app/static/images/features/` and + `docs/images/latest-release/`. The application serves its own copy for the + in-app Latest Features gallery, and GitHub Pages can only publish files inside + `docs/`, so both copies are required. When a release screenshot is updated, + update both. diff --git a/docs/explanation/index.md b/docs/explanation/index.md index abb87ef6e..b1bc5ec77 100644 --- a/docs/explanation/index.md +++ b/docs/explanation/index.md @@ -1,63 +1,53 @@ --- -layout: showcase-page +layout: page title: "Explanation" permalink: /explanation/ menubar: docs_menu -accent: violet -eyebrow: "Understand The Platform" description: "Use the explanation section when you want the reasoning behind the product: architecture, operating principles, rollout guidance, and runtime patterns." -hero_icons: ["bi-diagram-3", "bi-lightbulb", "bi-journal-richtext"] -hero_pills: ["Architecture and principles", "Feature rollout guidance", "Local and production runtime patterns"] -hero_links: [{ label: "Architecture", url: "/explanation/architecture/", style: "primary" }, { label: "Feature guidance", url: "/explanation/feature_guidance/", style: "secondary" }] --- This section is for decisions, not just steps. Use it when you need to understand why Simple Chat is put together the way it is and how that affects deployment, operations, and feature rollout. -
-
-
-

Architecture

-

Understand how App Service, Azure OpenAI, AI Search, Cosmos DB, Storage, and optional services combine into one application surface.

-

Open architecture

-
-
-
-

Design Principles

-

See the operating philosophy behind the product, including security, enterprise readiness, modularity, observability, and user-centered design.

-

Open design principles

-
-
-
-

Feature Guidance

-

Use the feature guidance page to decide which capabilities belong in a first rollout and which ones should be layered in after the platform stabilizes.

-

Open feature guidance

-
-
-
-

Runtime Patterns

-

Compare the recommended local developer loop with the Azure production runtime model so startup decisions stay consistent with the deployment model.

-

Local runtime · Azure production

-
-
- -
-

Use these pages differently from how-to guides

-

How-to guides are task-oriented. The explanation section is where you go when you want to understand the tradeoffs behind deployment models, feature boundaries, or operational choices before you commit to one path.

-
+## Architecture + +Understand how App Service, Azure OpenAI, AI Search, Cosmos DB, Storage, and optional services combine into one application surface. + +[Open architecture]({{ '/explanation/architecture/' | relative_url }}) + +## Design principles + +See the operating philosophy behind the product, including security, enterprise readiness, modularity, observability, and user-centered design. + +[Open design principles]({{ '/explanation/design_principles/' | relative_url }}) + +## Feature guidance + +Use the feature guidance page to decide which capabilities belong in a first rollout and which ones should be layered in after the platform stabilizes. + +[Open feature guidance]({{ '/explanation/feature_guidance/' | relative_url }}) + +## Runtime patterns + +Compare the recommended local developer loop with the Azure production runtime model so startup decisions stay consistent with the deployment model. + +[Local runtime]({{ '/explanation/running_simplechat_locally/' | relative_url }}) · [Azure production]({{ '/explanation/running_simplechat_azure_production/' | relative_url }}) + +## Use these pages differently from how-to guides + +How-to guides are task-oriented. The explanation section is where you go when you want to understand the tradeoffs behind deployment models, feature boundaries, or operational choices before you commit to one path. ## Continue into deeper references -
-
-
-

Scenarios

-

Example workspace and agent scenarios show how the product can be applied in concrete business contexts.

-

Agent examples · Workspace examples

-
-
-
-

Version history

-

Track long-lived feature and fix documentation by version when you need historical context for rollout planning or regression analysis.

-

Features by version · Fixes by version

-
-
\ No newline at end of file +Use these references when you need examples or historical context after the conceptual overview. + +### Scenarios + +Example workspace and agent scenarios show how the product can be applied in concrete business contexts. + +[Agent examples]({{ '/explanation/scenarios/agents/' | relative_url }}) · [Workspace examples]({{ '/explanation/scenarios/workspaces/' | relative_url }}) + +### Version history + +Track release history when you need historical context for rollout planning or regression analysis. + +[Open release notes]({{ '/explanation/release_notes/' | relative_url }}) diff --git a/docs/explanation/release-notes/index.md b/docs/explanation/release-notes/index.md new file mode 100644 index 000000000..ecdf05aeb --- /dev/null +++ b/docs/explanation/release-notes/index.md @@ -0,0 +1,367 @@ +--- +title: "Release notes" +description: "SimpleChat release notes index with latest updates and links to archived release pages." +section: "Reference" +layout: page +permalink: /explanation/release_notes/ +--- + + + +# Release notes + + + +For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). + +This page includes the latest release notes inline. Older release sections are split into smaller pages by minor series. + +## Version index + +| Version | Page | +| --- | --- | +| v0.260.021 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.020 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.019 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.018 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.017 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.016 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.015 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.014 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.013 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.012 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.011 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.010 | [Release notes index]({{ '/explanation/release_notes/' | relative_url }}) | +| v0.260.009 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.008 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.007 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.006 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.005 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.004 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.003 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.002 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.260.001 | [Release notes 0.260 series]({{ '/explanation/release-notes/v0.260/' | relative_url }}) | +| v0.250.231 | [Release notes 0.250 series]({{ '/explanation/release-notes/v0.250/' | relative_url }}) | +| v0.250.230 | [Release notes 0.250 series]({{ '/explanation/release-notes/v0.250/' | relative_url }}) | +| v0.250.229 | [Release notes 0.250 series]({{ '/explanation/release-notes/v0.250/' | relative_url }}) | +| v0.250.001 | [Release notes 0.250 series]({{ '/explanation/release-notes/v0.250/' | relative_url }}) | +| v0.241.007 | [Release notes 0.241 series]({{ '/explanation/release-notes/v0.241/' | relative_url }}) | +| v0.241.006 | [Release notes 0.241 series]({{ '/explanation/release-notes/v0.241/' | relative_url }}) | +| v0.241.002 | [Release notes 0.241 series]({{ '/explanation/release-notes/v0.241/' | relative_url }}) | +| v0.241.001 | [Release notes 0.241 series]({{ '/explanation/release-notes/v0.241/' | relative_url }}) | +| v0.239.002 | [Release notes 0.239 series]({{ '/explanation/release-notes/v0.239/' | relative_url }}) | +| v0.237.049 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.011 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.009 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.007 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.006 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.005 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.004 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.003 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.237.001 | [Release notes 0.237 series]({{ '/explanation/release-notes/v0.237/' | relative_url }}) | +| v0.235.025 | [Release notes 0.235 series]({{ '/explanation/release-notes/v0.235/' | relative_url }}) | +| v0.235.012 | [Release notes 0.235 series]({{ '/explanation/release-notes/v0.235/' | relative_url }}) | +| v0.235.003 | [Release notes 0.235 series]({{ '/explanation/release-notes/v0.235/' | relative_url }}) | + +## Latest release notes + +### **(v0.260.021)** + +#### Bug Fixes + +* **Documentation Screenshot Viewer Validates Its Image Source** + * The documentation site's click-to-enlarge screenshot viewer assigned an image URL taken from a data attribute in the page. Because that value flows from page content into a URL, CodeQL flagged it as a potential DOM-based cross-site scripting sink. + * The viewer now resolves the value and requires a same-origin `http` or `https` URL ending in an image extension before using it, so scheme-based payloads such as `javascript:` and `data:` URLs, and any off-site source, are rejected. All documentation media is local, so no legitimate image is affected. + * (Ref: `docs/assets/js/media.js`, `safeMediaUrl`, `ui_tests/test_docs_media_lightbox_source_validation.js`, CodeQL `js/xss-through-dom`) + +### **(v0.260.020)** + +#### New Features + +* **Admin Documentation Rebuilt For The Grouped Settings Layout** + * Admin Settings was reorganized from 18 flat tabs into 14 groups containing 44 tabs and 93 settings sections. The documentation was still written against the old flat layout, so it described tabs that no longer exist and omitted the new ones. + * The admin documentation is now one page per group, with every tab reachable by its own anchor so links to a specific tab keep working. Every retired tab URL redirects to the group that now owns its settings, so existing links and bookmarks continue to resolve. + * (Ref: `docs/admin/`, `application/single_app/admin_settings_nav.py`, `docs/_data/app_surface.yml`) + +* **Collaborating In A Conversation Is Now Documented** + * Added a guide covering shared conversations end to end: sharing a conversation, mentioning a participant with `@` and Tab completion, how shared files are approved before they become available, and what participants can and cannot do. + * The Blob Storage action reference now explains its managed identity and account key options. + * (Ref: `docs/guides/collaborate-in-a-conversation/`, `docs/reference/actions/blob-storage/`, `enable_collaborative_conversations`) + +* **Documentation Site Now Reflects the v0.260.001 Release** + * The documentation site's Latest Release section was a full release behind, still presenting v0.250.001 as current. It now mirrors the same three-tier model the application uses: v0.260.001 as the current release, v0.250.001 as the previous release, and v0.239.001-v0.241.007 in the archive. + * Added 20 feature guides for the v0.260.001 release covering enhanced extraction, embedded Office images, workflow task sequences, the MCP platform, the Yamcs and RocksDB actions, agent instruction references, action test connections, Azure Blob file sync, terms of use, audio file support, completion notifications, the chat AI notice, conversation context grounding, used documents on fork, the conversation contents drawer, font size and zoom, message audio export, public workspace display names, and chat scroll accessibility. + * (Ref: `docs/_data/latest_release_features.yml`, `docs/latest-release/release-260-*`, `application/single_app/support_menu_config.py`) + +* **Placeholder Screenshots Are Now Tracked** + * The v0.260.001 release ships branded "Screenshot pending" placeholder graphics so feature cards render while final captures are pending. Those placeholders are now listed on the documentation media status page with the exact file paths to overwrite, so they are visible work rather than a silent gap. + * (Ref: `docs/_data/media_pending.yml`, `/contributing/media-status/`) + +#### User Interface Enhancements + +* **Admin Settings Pages Show Real Screenshots** + * Fourteen admin settings tab pages were rendering "screenshot needed" placeholders even though real screenshots already existed in the repository. Those pages now display the actual screenshots for the General, AI Models, Search and Extract, Workspaces, File Sync, Workspace Identities, Citation, Safety, Security, Agents, Scale, Control Center, Logging, and Send Feedback tabs. + * The four tabs with no captured screenshot still show a placeholder naming the exact file to create, so genuine gaps stay visible. + * (Ref: `docs/admin/`, `docs/images/admin-settings/`) + +#### Bug Fixes + +* **Release Notes Pages No Longer Break On Quoted Template Syntax** + * Release notes legitimately quote template syntax when describing template work, such as a Jinja `block` tag. The page generator emitted that verbatim, so the site build failed with an unknown tag error. Quoted template syntax is now escaped in generated pages and renders as literal text. + * (Ref: `scripts/build_release_notes_pages.py`) + +* **Release Notes Links To Internal Engineering Notes** + * Some release note entries linked to the internal feature and fix note trees, which are intentionally not published on the documentation site. Those links now point at the repository. + * (Ref: `docs/explanation/release_notes.md`) + +* **Release Notes Index No Longer Exceeds Its Page Budget** + * The release notes page generator inlined a fixed number of recent releases on its index. The consolidated v0.260.001 rollup is large enough on its own that this pushed the index past the maximum page size and failed generation. The index now fills its inline section by size rather than by count, so a single large rollup cannot break it. + * (Ref: `scripts/build_release_notes_pages.py`) + +* **Archived Release Notes Links** + * The archived release notes page linked to the internal feature and fix note trees, which are intentionally not published on the documentation site. Those links now point at the repository instead. + * (Ref: `docs/explanation/archive_release_notes.md`) + +### **(v0.260.019)** + +#### Bug Fixes + +* **Admin Settings Loads Again** + * Admin Settings returned a 500 error on every request after the settings restructure. The Document Action Capabilities card moved to the Actions tab but the two values it reads stayed behind in the Agents tab, and each tab is rendered separately, so those values were never there when the card asked for them. + * Both values are now defined in the tab that uses them, and a new test renders the two tabs together to keep them there. + * (Ref: `admin/_panes/actions.html`, `admin/_panes/agents.html`, document action capabilities) + +* **Server Errors Are Visible In The App Service Log Again** + * Once Application Insights was configured it took ownership of logging, which had the side effect of stopping Flask writing unhandled errors to the container log. A failing page left nothing behind but its access-log line, so diagnosing it meant querying Application Insights. + * Unhandled errors are now written to both, so the reason for a failure is visible in the App Service log stream. + * (Ref: `functions_appinsights.py`, `ensure_console_error_logging`, App Service console logs) + +* **Document Access Index Diagnostics Appear When Enabled** + * The Cosmos DB tab checked the wrong thing for the debug setting, so the backfill controls, shadow validation metrics and reset option stayed hidden even after an admin turned the setting on. + * (Ref: `admin/_panes/cosmos.html`, `enable_dai_debug`) + +### **(v0.260.018)** + +#### Bug Fixes + +* **Setup Walkthrough Lands On The Right Settings Again** + * The guided setup walkthrough sent each step to a named tab. After the Admin Settings restructure, eleven of its twelve steps named tabs that no longer existed, so those steps would have moved nowhere and left the admin looking at whatever was already on screen. + * Each step now names the setting it is about and the tab is worked out from the page, so the walkthrough follows settings wherever they live. + * (Ref: setup walkthrough, `admin_settings.js`, `admin_card_links.js`) + +* **Cosmos Throughput Validation Reveals The Invalid Field** + * When Cosmos throughput values failed validation, the page tried to switch to a tab that no longer exists, so the field needing attention could be left on a hidden tab with no indication of where to look. + * Validation now jumps to wherever the invalid field actually is. + * (Ref: Cosmos throughput validation, `admin_settings.js`) + +#### User Interface Enhancements + +* **Admin Settings Restructure Merged With Current Development** + * Version bump covering the merge of the Admin Settings information architecture work with the generated file output fixes developed in parallel. Both reached v0.260.011 independently, so their release notes are combined under that version. + * (Ref: Admin Settings navigation, generated file exports) + +### **(v0.260.017)** + +#### New Features + +* **All App Role Requirements In One Place** + * Ten settings across seven tabs can each require an Entra app role, which made the overall access policy impossible to read without hunting through the whole of Admin Settings. + * **Security → Access & Roles** now lists every one of them with a switch and a link to the setting in its own tab. Changing a switch here changes the setting itself. + * The list is built from the page, so a new role requirement added anywhere appears here automatically. + * (Ref: `app-role-requirements-section`, `admin_access_roles_roster.js`) + +#### User Interface Enhancements + +* **System Settings Card Split To Where Each Setting Belongs** + * One card mixed maximum file size, conversation history, idle timeout, the default system prompt and the access denied message — five unrelated concerns under one heading. + * Maximum File Size is now in **Workspaces → Files & Sharing**, Conversation History and Default System Prompt in **Chat → Chat Experience**, and Access Denied Message in **Security → Access & Roles**. + * What remains in **Security → Session** is the idle timeout, and the card is now named for it. + * Every setting keeps its saved value; nothing needs re-entering. + * (Ref: `idle-timeout-section`, `file-size-limit-section`, `conversation-history-section`, `default-system-prompt-section`, `access-denied-message-section`) + +### **(v0.260.016)** + +#### User Interface Enhancements + +* **Backup, Migrate & Restore Split Into Five Tabs** + * One tab carried the entire backup, migration, restore, Cosmos editing and job history surface — over 1,600 lines in a single scroll. + * Backup & Recovery now has **Backup** (readiness, backup, schedule, storage, encryption), **Migrate**, **Restore**, **Cosmos Editor** and **Jobs**. + * The save button, status line and operational-hours warning are shared by all five tabs, so they sit above the tabs and stay available wherever you are in the group. + * This completes the Admin Settings restructure: **14 groups and 44 tabs**, from an original 17 flat tabs. + * (Ref: `backup`, `migrate`, `restore`, `cosmos-editor`, `jobs`) + +#### Bug Fixes + +* **Backup Dialogs Remain Available From Every Tab** + * The eleven Backup & Recovery dialogs are opened from more than one place and several are opened from code rather than a button. Left inside a tab, a dialog cannot appear while a different tab is showing. + * They now sit outside the tabs, so restore, migration cancel, job detail, the Cosmos editor dialogs and the five setup guides all open wherever they are triggered from. + * (Ref: Backup & Recovery dialogs, `admin_data_management.js`) + +* **Shared Controls Work In Both Navigation Layouts** + * Shared group controls resolve their group from whichever navigation is on screen, so the Backup & Recovery save button is present in the sidebar layout as well as the tab layout. + * (Ref: `data-admin-group-shared`, `admin_sidebar_nav.js`) + +### **(v0.260.015)** + +#### User Interface Enhancements + +* **AI Models Split By Model Purpose** + * AI Models presented every model setting on one tab. It is now **Model Endpoints** (endpoint and fallback configuration, plus the Chat Model dialog opened from it), **Embeddings** and **Image Generation**. + * (Ref: `model-endpoints`, `embeddings`, `image-generation`) + +* **Agents And Actions Are Now Separate Tabs** + * A single "Agents and Actions" tab carried agent configuration, template approvals, document action capabilities, action configuration and the whole inbound MCP surface. + * It is now **Agents**, **Actions** and **Inbound MCP**. + * Inbound MCP is a large area with its own dialogs and diagnostics, and the whole tab is hidden when the inbound MCP interface is turned off rather than showing an empty tab. + * (Ref: `agents`, `actions`, `inbound-mcp`) + +#### Bug Fixes + +* **Model Setup Guide Available From Every Model Tab** + * The Azure OpenAI Model Setup Guide dialog is opened from the endpoints, embeddings and image generation cards. Once those moved to separate tabs it could only have opened from one of them. + * The dialog now sits outside the tabs, so it opens from all three. + * (Ref: `legacyModelDiscoveryIdentityGuideModal`) + +* **Dangling Section Comments Removed** + * Seven tabs ended with a comment labelling a card that had since moved to another tab. + * (Ref: admin settings tab panes) + +### **(v0.260.014)** + +#### User Interface Enhancements + +* **Knowledge Settings Split By What They Actually Do** + * Search & Extract held eight cards spanning four unrelated jobs, from Bing consent to voice transcription. + * Knowledge now has **Web & Research** (web search, URL access, deep research), **Search Index** (Azure AI Search), **Document Extraction** (document intelligence, chunk sizes, plus metadata extraction and multi-modal vision brought over from Workspaces) and **Audio & Video** (video intelligence, voice conversations), alongside the existing File Sync. + * Voice and video sit under Knowledge rather than Chat because they are extraction pipelines that turn recordings into searchable content. + * (Ref: `web-research`, `search-index`, `extraction`, `audio-video`) + +* **Workspaces Focused On Workspaces** + * Workspaces mixed workspace types with file rules, workflow and extraction settings. + * It is now **Workspace Types** (personal, group, public), **Files & Sharing** (downloads, sharing, and shared conversation file approvals brought over from AI Models) and the existing Global Identities. + * (Ref: `workspace-types`, `files-sharing`) + +* **Workflow Is Its Own Area** + * Workflow drives approvals and assignment across every workspace type and was too large to sit as one card inside Workspaces. It now has its own group. + * (Ref: `workflow`, `workflow-settings-section`) + +#### Bug Fixes + +* **Group Workflow Assignment Dialog Could Not Open** + * The Group Workflow Assignment dialog ended up in a different tab from the button that opens it. Because an inactive tab is hidden, the dialog would not have appeared at all. + * The dialog now sits with its button, and a new check verifies this for every dialog in Admin Settings so it cannot happen again. + * (Ref: `groupWorkflowAssignmentModal`, `test_admin_settings_modal_placement.py`) + +* **Misplaced Section Comments In AI Models** + * Two section comments had drifted onto the wrong cards while settings were being regrouped, labelling the embeddings card as processing thoughts. + * (Ref: `ai-models` pane) + +### **(v0.260.013)** + +#### User Interface Enhancements + +* **General Tab Broken Up Into Focused Tabs** + * General had grown into a catch-all of eleven unrelated cards: branding sat next to health checks, API documentation, terms of use and system settings. + * Appearance now has **Branding** (branding, home page text, appearance), **Notices & Agreements** (classification banner, chat AI notice, terms of use and the user agreement pulled across from Workspaces) and **Pages & Links** (static pages plus external links). + * Health Check and API Documentation moved to Operations, which is now **Logging & Health** — they report on how the app is running rather than how it looks. + * Support moved to Help as its own **Support Menu** tab, next to Send Feedback. + * (Ref: `branding`, `notices`, `custom-pages`, `logging`, `support-menu`) + +* **Security Split Into Five Purposeful Tabs** + * Security held a single Key Vault card while an unrelated Safety tab mixed content filtering with role permissions, which are different jobs. + * Security is now **Access & Roles** (who gets in and with what role), **Secrets** (Key Vault), **Content Safety** (what may be said once you are in), **Session** (idle timeout and related system settings) and **Network** (Azure Front Door). + * (Ref: `access-roles`, `secrets`, `content-safety`, `session`, `network`) + +#### Bug Fixes + +* **"Open Key Vault Settings" Link No Longer Depends On A Hardcoded Tab** + * The link from Data Management to Key Vault switched tabs by a hardcoded id, so it silently stopped working whenever that tab was renamed. + * It now uses the standard card link, which finds the owning tab from the page itself and stays correct however the settings are grouped. + * (Ref: `data-management-key-vault-link`, `admin_card_links.js`, `admin_data_management.js`) + +* **Admin Settings Always Opens On A Real Tab** + * The tab shown on arrival was pinned to a specific id in both the markup and the sidebar script. Regrouping settings could leave Admin Settings opening with no tab selected at all. + * The landing tab is now taken from the navigation map, so it follows the settings and can never be Latest Features. + * (Ref: `admin_landing_tab`, `get_landing_tab_id`, `admin_sidebar_nav.js`) + +* **Stale Tab Names In Latest Features** + * Several Latest Features entries pointed readers at tabs by their old names after the settings moved. + * (Ref: `latest-features` pane) + +### **(v0.260.012)** + +#### User Interface Enhancements + +* **New Data Lifecycle Group For Retention, Classification And Archiving** + * Retention policy, document classification and conversation archiving all decide how long content lives and how it is labelled, but they were split across Workspaces and Safety. They now sit together in a **Data Lifecycle** group with a tab each: **Retention**, **Classification** and **Archiving**. + * Conversation archiving in particular was buried under Safety, which described what it protects against rather than what it does. + * (Ref: navigation map, `retention-policy-section`, `document-classification-section`, `conversation-archiving-section`) + +* **Chat Group Gathers The Settings That Shape A Conversation** + * Settings that change what a conversation looks and behaves like were spread across AI Models, Workspaces and Safety. The **Chat** group now holds them in two tabs. + * **Chat Experience** collects model thought display, chat file uploads (with the conversation contents drawer) and workspace scope lock. + * **Feedback & Alerts** collects user feedback and desktop notifications, which are both about how the app talks back to the user rather than about safety enforcement. + * (Ref: `chat-experience`, `feedback-alerts`, `processing-thoughts-section`, `chat-file-uploads-section`, `workspace-scope-lock-section`, `user-feedback-section`, `desktop-notifications-section`) + +* **Settings Keep Their Values Through The Move** + * Cards were relocated between tabs without renaming a single field, so every saved value is preserved and the form submits exactly the payload it did before. + * Sidebar search still finds a setting by group, tab or card name, so you can reach anything without knowing where it now lives. + * (Ref: admin settings field contract, `admin_settings_nav.py`) + +### **(v0.260.011)** + +#### User Interface Enhancements + +* **Governance And Scale Split Into Focused Tabs** + * Governance held five cards covering three different jobs. It is now **Feature Governance** (which features are governed), **Policies** (the policies themselves), and **MCP Governance**. + * Scale mixed cache configuration with Cosmos capacity, and is now **Redis & Caching** and **Cosmos**. + * **Azure Front Door** moved out of Scale into Security, under a new **Network** tab. It configures authentication and redirect flows rather than throughput, so it never belonged with capacity settings. + * Existing links and bookmarks to `#governance` and `#scale` still work and land on the first tab of each group. + * No settings changed. Every option keeps its name and its saved value. + * (Ref: navigation map, `feature-governance`, `governance-policies`, `mcp-governance`, `redis-caching`, `cosmos`, `network`) + +#### Bug Fixes + +* **Governance Status Messages No Longer Get Stuck On One Tab** + * The inline governance status message lived inside the Governance pane, so a message raised while working in one area could end up rendered on a tab you were not looking at. + * It now sits outside the tabs and is visible wherever you are in Governance. + * (Ref: `governance-status`, `admin_governance.js`) + +#### Bug Fixes + +* **Reliable File Generation From Agent Action Results** + * Asking an agent for a downloadable file built from action results now produces the complete dataset in the requested format. Previously these requests could fail outright, publish a three-row sample of a large result, overwrite the assistant's written answer, or return nothing at all. Delivered across v0.260.004 through v0.260.011. + * **Files no longer fail to generate.** A CSV built from several actions in one turn could stop with `Generated output schema mismatch at row 2`, because each action returned a different set of columns. The export now pins a union of every column before the run starts and pads the missing cells, so mixed-shape results serialize instead of failing. + * **The written answer is no longer replaced by the file card.** CSV replies were suppressed alongside JSON and XML, but only JSON and XML withhold their payload from the response. CSV, DOCX, and PDF now keep the assistant's answer and append the file card beneath it. + * **Files contain the retrieved data, not a sample of it.** When the assistant pasted a few example rows above its answer, that excerpt outranked the real result set, producing a 3-row file from a 900-row query. Pasted rows are now used only when they are not an excerpt of the data actually retrieved. + * **Discovery calls no longer dilute the dataset.** A turn that lists instances, lists parameters, then retrieves history used to blend all three into one file. Rows are grouped by the action that produced them, and the action holding the substantive dataset wins. + * **Follow-up requests reuse data already gathered.** Asking "now make that a CSV" after the data was retrieved in an earlier turn no longer returns an empty result. The export reaches back through stored conversation citations, bounded by the **conversation history limit** in Admin Settings, and reuses the rows already collected instead of re-querying the source. + * **Answering a clarifying question now delivers the file.** When the assistant asks which rows and columns to include, replying "yes, all columns" now publishes the file that was originally requested. The clarification turn itself no longer publishes a placeholder file built from the question text. + * **The assistant no longer claims it cannot create files.** Every format now states the publication contract to the model, including on the turn that only answers a clarification, so replies stop saying "I cannot create or attach a file in this interface" and then producing one anyway. + * **Overlapping result pages no longer double the row count.** Agents frequently re-request a range from the same start time rather than paging forward, which produced a 1,000-row file for a window holding roughly 500 distinct records. Rows an earlier page of the same action already returned are dropped, while genuinely repeated records inside a single response are preserved. + * **Partial data is now labeled.** When an action reports that it truncated its own results, the file carries a **Partial** badge and a note explaining that it covers only the rows the action returned. Agents are also instructed to request the remainder starting after the last row they already hold, rather than repeating the original range. + * **CSV, DOCX, PDF, JSON, and XML now behave identically.** All five formats resolve rows the same way, reach back to earlier turns, decline to publish on a clarification turn, and report truncation. + * (Ref: `functions_generated_file_exports.py`, `functions_tabular_generated_exports.py`, `route_backend_chats.py`, `chat-messages.js`, [Generated Artifact Paging, Truncation, and Guidance Carry-Forward Fix](https://github.com/microsoft/simplechat/blob/main/docs/explanation/fixes/GENERATED_ARTIFACT_PAGING_AND_GUIDANCE_FIX.md), Refs #1071) + +### **(v0.260.010)** + +#### New Features + +* **Admin Settings Navigation Is Now Grouped** + * Admin Settings presented 18 tabs in one flat list. Related tabs are now collected under 12 groups such as Appearance, Knowledge, Security and Operations, so the list is scannable and has room to grow. + * In the sidebar, groups are collapsible and remember whether you left them open. In the tab layout, a row of group pills filters the tab strip to one group at a time. + * Opening a tab always reveals its group first, so a deep link or a cross-reference can never land you on a pane whose tab is hidden. + * Sidebar search now matches group names as well as tab and setting names, and expands whatever it needs to show a result. + * No settings moved in this release. Every tab keeps its contents; only the navigation around them changed. + * (Ref: `admin_settings_nav.py`, `_sidebar_nav.html`, `admin_settings.html`, `admin_sidebar_nav.js`) + +#### Bug Fixes + +* **Shared Conversation File Approvals Is Reachable From The Sidebar** + * The Shared Conversation File Approvals card had no navigation entry, so it could only be found by scrolling the AI Models tab. It is now listed like every other setting. + * (Ref: `shared-conversation-file-approvals-section`, navigation map) + +* **Navigation Labels And Order Can No Longer Drift** + * The tab strip and the sidebar each maintained the same structure by hand and had diverged: tab order differed between them, and Agents, Custom Pages and Search and Extract each showed a different name depending on which navigation you used. + * Both now render from one definition, so a change is made once and appears in both. + * (Ref: `admin_settings_nav.py`, `test_admin_settings_nav_map.py`) diff --git a/docs/explanation/release-notes/v0.235.md b/docs/explanation/release-notes/v0.235.md new file mode 100644 index 000000000..ae7b3235c --- /dev/null +++ b/docs/explanation/release-notes/v0.235.md @@ -0,0 +1,397 @@ +--- +title: "Release notes 0.235 series" +description: "SimpleChat release notes for 0.235.025 – 0.235.003." +section: "Reference" +layout: page +--- + + + +# Release notes 0.235 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.235.025)** + +#### Bug Fixes + +* **Retention Policy Document Deletion Fix** + * Fixed critical bug where retention policy execution failed when attempting to delete aged documents, while conversation deletion worked correctly. + * **Root Cause 1**: Documents use `last_updated` field, but query was looking for `last_activity_at` (used by conversations). + * **Root Cause 2**: Date format mismatch - documents store `YYYY-MM-DDTHH:MM:SSZ` but query used Python's `.isoformat()` with `+00:00` suffix. + * **Root Cause 3**: Duplicate column in SELECT clause when `partition_field='user_id'` caused query errors. + * **Root Cause 4**: Activity logging called with incorrect `deletion_reason` parameter instead of `additional_context`. + * **Files Modified**: `functions_retention_policy.py` (query field names, date format, SELECT clause, activity logging). + * (Ref: `delete_aged_documents()`, retention policy execution, Cosmos DB queries) + +* **Retention Policy Scheduler Fix** + * Fixed automated retention policy scheduler not executing at the scheduled time. + * **Root Cause 1**: Hour-matching approach was unreliable - only ran if check happened exactly during the execution hour (e.g., 2 AM), but 1-hour sleep intervals could miss the entire window. + * **Root Cause 2**: Check interval too long (1 hour) meant poor responsiveness and high probability of missing scheduled time. + * **Root Cause 3**: Code ignored the stored `retention_policy_next_run` timestamp, instead relying solely on hour matching. + * **Solution**: Now uses `retention_policy_next_run` timestamp for comparison, reduced check interval from 1 hour to 5 minutes, added fallback logic for missed executions. + * **Files Modified**: `app.py` (`check_retention_policy()` background task). + * (Ref: retention policy scheduler, background task, scheduled execution) + +### **(v0.235.012)** + +#### Bug Fixes + +* **Control Center Access Control Logic Fix** + * Fixed access control discrepancy where users with `ControlCenterAdmin` role were incorrectly granted access when the role requirement setting was disabled. + * **Correct Behavior**: When `require_member_of_control_center_admin` is DISABLED (default), only the regular `Admin` role grants access. The `ControlCenterAdmin` role is only checked when the setting is ENABLED. + * **Files Modified**: `functions_authentication.py` (decorator logic), `route_frontend_control_center.py` (frontend access computation), `_sidebar_nav.html` and `_top_nav.html` (menu visibility). + * (Ref: `control_center_required` decorator, role-based access control) + +* **Disable Group Creation Setting Fix** + * Fixed issue where "Disable Group Creation" setting was not being saved from Admin Settings or Control Center pages. + * **Root Cause 1**: Form field name mismatch - HTML used `disable_group_creation` but backend expected `enable_group_creation`. + * **Root Cause 2**: Missing onclick handler on Control Center's "Save Settings" button. + * **Files Modified**: `route_frontend_admin_settings.py` (form field reading), `control_center.html` (button handler). + * (Ref: group creation permissions, admin settings form handling) + +### **(v0.235.003)** + +#### New Features + +* **Approval Workflow System** + * Comprehensive approval process for sensitive Control Center operations requiring review and approval before execution. + * **Protected Operations**: Take ownership, transfer ownership, delete documents, and delete group operations now require approval. + * **Approval Features**: Documented justification, review process by group owners/admins, complete audit trail, auto-expiration after 3 days, notification integration. + * **Database**: New `approvals` container with TTL-based expiration. + * (Ref: `route_backend_control_center.py`, `route_frontend_control_center.py`, `control_center.html`, approval workflow UI) + +* **Agent Streaming Support** + * Real-time streaming support for Semantic Kernel agents with incremental response display. + * **Features**: Agent responses stream word-by-word, plugin citation capture during streaming, async generator pattern for efficient streaming, proper async/await handling. + * **User Experience**: Matches existing chat streaming experience, see agent thinking in real-time, immediate visual feedback. + * (Ref: `route_backend_chats.py`, agent streaming implementation, Semantic Kernel integration) + +* **Control Center** + * Comprehensive administrative interface for data and workspace management. + * **User Management**: View all users with search/filtering, grant/deny access with time-based restrictions, manage file upload permissions, monitor user engagement and storage. + * **Activity Trends**: Visual analytics with Chart.js showing daily activity metrics (chats, uploads, logins, document actions) across 7/30/90-day periods. + * **Group Management**: Approval workflow integration, group status management, member activity monitoring. + * **Dashboard**: Real-time statistics, key alerts, activity insights. + * (Ref: `route_frontend_control_center.py`, `route_backend_control_center.py`, `control_center.html`) + +* **Control Center Application Roles** + * Added two new application roles for finer-grained Control Center access control. + * **Control Center Admin**: Full administrative access to Control Center functionality including user management, administrative operations, and workflow approvals. + * **Control Center Dashboard Reader**: Read-only access to Control Center dashboards and metrics for monitoring and auditing purposes. + * **Use Cases**: IT operations monitoring, delegated administration, compliance auditing with appropriate access levels. + * **Files Modified**: `appRegistrationRoles.json` (new role definitions). + * (Ref: Entra ID app roles, role-based access control, Control Center permissions) + +* **Message Threading System** + * Linked-list threading system establishing proper message relationships throughout conversations. + * **Thread Fields**: `thread_id` (unique identifier), `previous_thread_id` (links to previous message), `active_thread` (thread active status), `thread_attempt` (retry tracking). + * **Benefits**: Proper message ordering, file upload tracking, image generation association, legacy message support. + * **Message Flow**: Links user messages to AI responses, system augmentations, file uploads, and image generations. + * (Ref: `route_backend_chats.py`, message schema updates, thread chain implementation) + +* **User Profile Dashboard** + * Complete redesign into modern dashboard with personalized analytics and visualizations. + * **Metrics Display**: Login statistics, chat activity, document usage, storage consumption, token tracking. + * **Visualizations**: Chart.js-powered activity trends, 30-day time-series data, interactive charts. + * **Features**: Cached metrics for performance, real-time data aggregation, responsive design. + * (Ref: `route_frontend_profile.py`, `profile.html`, Chart.js integration) + +* **Speech-to-Text Chat Input** + * Voice recording up to 90 seconds directly in chat interface with Azure Speech Service transcription. + * **Features**: Visual waveform display during recording, 90-second countdown timer, review before send, cancel anytime, responsive design. + * **Browser Support**: Chrome 49+, Edge 79+, Firefox 25+, Safari 14.1+. + * **Integration**: Uses existing Azure Speech Service configuration, MediaRecorder API, Web Audio API. + * (Ref: `route_backend_settings.py`, `chat-speech-to-text.js`, Speech Service integration) + +* **Text-to-Speech AI Responses** + * AI messages read aloud using Azure Speech Service with high-quality DragonHD voices. + * **Features**: 27 DragonHD Latest Neural Voices across languages, voice preview in profile, customizable speech speed (0.5x-2.0x), play/pause/stop controls. + * **Playback**: Inline "Listen" button per message, visual feedback during playback, auto-play mode option, prevents multiple simultaneous playbacks. + * **Integration**: Automatically disables streaming when auto-play enabled, per-user profile settings. + * (Ref: `route_backend_tts.py`, `chat-tts.js`, Azure Speech Service) + +* **Message Edit Functionality** + * Comprehensive message editing system allowing users to modify their sent messages and regenerate AI responses. + * **Features**: Modal interface for editing message text, preserves conversation context and settings, automatically regenerates AI response with edited content, maintains message metadata and threading. + * **User Experience**: Edit button on user messages, inline editing workflow, real-time validation, preserves agent/model selection. + * **Integration**: Works with `/api/message//edit` endpoint, updates conversation history, maintains thread relationships. + * (Ref: `chat-edit.js`, `route_backend_chats.py`, message edit modal) + +* **Message Delete Capability** + * One-click message deletion with proper conversation thread cleanup and metadata updates. + * **Features**: Delete button on user messages, ownership validation (author-only), updates message threading chains, removes associated metadata. + * **Safety**: Confirmation prompt, author verification, cascading thread updates, preserves conversation integrity. + * **Integration**: API endpoint for message deletion, updates conversation message count, maintains thread consistency. + * (Ref: `chat-messages.js`, message deletion handlers, thread management) + +* **Message Retry/Regenerate System** + * Powerful message regeneration system allowing users to retry AI responses with different models, agents, or settings. + * **Features**: Modal interface with agent/model selection, adjustable reasoning effort for o-series models, preserves original user message, generates new AI response with selected configuration. + * **Configuration Options**: Switch between agents, change model deployments, adjust reasoning effort (low/medium/high), modify generation parameters. + * **User Experience**: Retry button on AI messages, dropdown selection for agents/models, real-time configuration updates. + * (Ref: `chat-retry.js`, retry modal interface, agent/model switching) + +* **Message Masking System** + * Privacy-focused message masking capability for hiding sensitive information with visual overlays and PII protection. + * **Features**: Visual mask overlay on message content, `masked_ranges` metadata tracking character positions, mask/unmask toggle buttons, preserves original content while displaying masked state. + * **Privacy Protection**: Masks sensitive data in UI, tracks masked regions in database, supports partial message masking, reversible masking for authorized users. + * **Integration**: `/api/message//mask` endpoint, `masked` and `masked_ranges` metadata fields, visual indicators (bi-front/bi-back icons). + * **User Experience**: Mask button on messages, visual overlay showing masked content, toggle between masked and unmasked states. + * (Ref: `chat-messages.js`, `route_backend_chats.py`, masked content handling, `applyMaskedState()` function) + +* **Conversation Pinning** + * Pin important conversations to the top of the conversation list for quick access and improved organization. + * **Features**: Single conversation pinning, bulk pin operations for multiple conversations, persistent pin state in database, visual pin indicators in sidebar. + * **Operations**: Pin/unpin toggle, bulk selection interface, priority sorting (pinned conversations appear first), `is_pinned` metadata field. + * **API Endpoints**: `/api/conversations//pin` (POST), `/api/conversations/bulk-pin` (POST). + * **User Experience**: Pin icon in conversation list, bulk selection checkboxes, immediate visual feedback. + * (Ref: `chat-conversations.js`, `toggleConversationPin()`, `bulkPinConversations()`, conversation state management) + +* **Conversation Hiding** + * Hide conversations from the main list to declutter the sidebar without permanent deletion. + * **Features**: Single conversation hiding, bulk hide operations, toggle visibility without data loss, `is_hidden` metadata field for state persistence. + * **Benefits**: Declutter conversation list, temporary archiving without deletion, reversible operation, maintains conversation data. + * **API Endpoints**: `/api/conversations//hide` (POST), `/api/conversations/bulk-hide` (POST). + * **User Experience**: Hide button in conversation list, bulk selection interface, show hidden conversations toggle. + * (Ref: `chat-conversations.js`, `toggleConversationHide()`, `bulkHideConversations()`, visibility management) + +* **Quick Search for Conversations** + * Real-time client-side conversation filtering for instant search results without server roundtrips. + * **Features**: Real-time text filtering, searches conversation titles, client-side performance, keyboard shortcut support (Ctrl+K). + * **Search Scope**: Filters visible conversations in current workspace, highlights matching conversations, instant results as you type. + * **User Experience**: Search input in sidebar header, keyboard shortcut, clear button, responsive filtering. + * (Ref: `chat-conversations.js`, `toggleQuickSearch()`, client-side filtering) + +* **Advanced Search Modal** + * Comprehensive search functionality with filters, pagination, and search history for finding conversations across all workspaces. + * **Features**: Full-text search across conversation content, classification filters, date range selection, pagination support, search history tracking. + * **Search Capabilities**: Search conversation titles and content, filter by workspace scope, filter by date range, view search history, export results. + * **User Experience**: Modal interface with filter controls, results pagination, search history dropdown, results summary display. + * **Integration**: Server-side search API, search history persistence, results caching. + * (Ref: `chat-search-modal.js`, `openAdvancedSearchModal()`, `performAdvancedSearch()`, search history management) + +* **Automated Retention Policy System** + * Scheduled automatic deletion of aged conversations and documents based on configurable retention policies. + * **Features**: User-configurable retention periods, separate policies for conversations and documents, scheduled execution via daemon thread, exemption support for protected conversations/documents. + * **Configuration Options**: Retention periods by workspace scope (personal/group/public), auto-deletion scheduling (daily execution), user opt-in/opt-out controls, admin override capabilities. + * **Scopes**: Personal workspace retention, group workspace retention, public workspace retention, per-user policy settings. + * **Safety Features**: User exemption lists, dry-run mode for testing, deletion audit logging, grace period before deletion. + * **Integration**: Background daemon thread, admin configuration interface, user profile settings, Cosmos DB TTL-based cleanup. + * (Ref: `functions_retention_policy.py`, `execute_retention_policy()`, scheduled execution, user settings integration) + +* **Embedding Token Tracking** + * Comprehensive token tracking for document embedding generation in personal workspaces. + * **Tracking**: Captures token usage per document chunk, accumulates total tokens, stores embedding tokens and model deployment name in document metadata. + * **Benefits**: Embedding cost tracking, usage pattern analysis, document-level token metrics. + * (Ref: `functions_content.py`, `functions_documents.py`, embedding token capture) + +* **Search Result Caching** + * Ensures consistent search results across identical queries with Cosmos DB-based distributed caching. + * **Features**: Document set fingerprinting for cache invalidation, score normalization across indexes, 5-minute TTL, multi-instance deployment support. + * **Architecture**: Cosmos DB `search_cache` container, SHA256 cache keys, automatic expiration, cache sharing across instances. + * **Benefits**: Consistent user experience, reduced Azure AI Search costs, improved performance. + * (Ref: `functions_search.py`, `search_cache` container, fingerprint-based invalidation) + +* **Activity Trends Visualization** + * Interactive Chart.js visualization of daily activity metrics in Control Center. + * **Categories**: Chats, uploads, logins, document actions tracked separately. + * **Time Periods**: 7-day, 30-day, and 90-day views. + * **Data Sources**: Real data from Cosmos DB containers with sample data fallback. + * (Ref: `route_backend_control_center.py`, `control_center.html`, Chart.js implementation) + +* **Group Activity Timeline** + * Comprehensive real-time view of all group workspace activities. + * **Activity Types**: Document creation/deletion/updates, member additions/removals, status changes, conversations. + * **Features**: Icon-based activity display, timestamp tracking, member attribution, detailed metadata. + * **Benefits**: Group usage monitoring, audit trail, compliance tracking. + * (Ref: `route_frontend_groups.py`, activity timeline UI, activity logs integration) + +* **Dynamic OpenAPI Schema Generation** + * Dynamic schema generation reducing hardcoded OpenAPI definitions. + * **Features**: Analyzes Flask routes to generate schemas, maps routes to appropriate references, minimal required schemas for common patterns. + * **Benefits**: Reduced maintenance overhead, automatic schema updates, comprehensive API documentation. + * (Ref: `route_external_openapi_spec.py`, dynamic schema functions) + +* **Enhanced User Management** + * Comprehensive user activity metrics and analytics in Control Center. + * **Profile Features**: Profile image display with Base64 support, chat metrics (conversations, messages, 3-month activity), document metrics (count, storage, AI search size). + * **Analytics**: Last chat activity timestamps, storage estimations, feature status indicators. + * (Ref: `route_backend_control_center.py`, enhanced user metrics) + +* **Group Status Management** + * Fine-grained control over group workspace operations through status-based access controls. + * **Status Types**: Active (full functionality), Locked (read-only), Upload Disabled (no new uploads), Inactive (disabled). + * **Features**: Full audit trail logging, operation-level restrictions, compliance support. + * **Use Cases**: Legal holds, storage management, project lifecycle, risk mitigation. + * (Ref: `functions_groups.py`, `route_backend_groups.py`, status enforcement) + +* **Workflow System** + * Document processing workflows including PII analysis and approval workflows. + * **Features**: PDF document display in modals, workflow summary generation, approval routing, activity logging. + * (Ref: `route_frontend_workflow.py`, workflow templates, CSP configuration) + +* **Full Width Chat Support** + * Option to expand chat interface to full browser width for better screen utilization. + * (Ref: `chats.html`, responsive layout updates) + +* **Enhanced Document Metrics** + * Comprehensive document metadata tracking with enhanced analytics. + * (Ref: document metrics implementation across containers) + +* **Group Member Activity Logging** + * Detailed logging when group members are added or removed. + * (Ref: activity logging system, group member operations) + +* **Enable Group Creation Setting** + * Admin toggle to control whether users can create new groups. + * (Ref: admin settings, group creation permissions) + +* **YAML OpenAPI Specification Support** + * Support for YAML format OpenAPI specifications alongside JSON. + * (Ref: OpenAPI plugin system, YAML parsing) + +* **Inline OpenAPI Schema Generation** + * Generate OpenAPI schemas inline during plugin configuration. + * (Ref: plugin configuration UI, schema generation) + +* **Microphone Permission Management** + * Improved handling of browser microphone permissions for speech-to-text. + * (Ref: speech-to-text implementation, browser permissions) + +#### Bug Fixes + +* **Agent Streaming Plugin Execution Fix** + * Fixed agent streaming failure when agents execute plugins during streaming. + * **Root Cause**: Event loop conflicts from `loop.run_until_complete(async_gen.__anext__())` pattern breaking async generator protocol. + * **Solution**: Proper async/await pattern with `asyncio.run()` for complete async context. + * **Impact**: Plugins like SmartHttpPlugin now work correctly in streaming mode. + * (Ref: `route_backend_chats.py`, async generator handling, plugin execution) + +* **Search Cache Cosmos DB Migration** + * Migrated search caching from in-memory to Cosmos DB for multi-instance deployment support. + * **Problem**: In-memory cache didn't share across App Service instances causing inconsistent results. + * **Solution**: Cosmos DB `search_cache` container with 5-minute TTL, partition key on `user_id`. + * **Benefits**: Cache sharing across instances, consistent user experience, distributed invalidation. + * (Ref: `functions_search.py`, `search_cache` container, TTL configuration) + +* **Vision Model Parameter Fix** + * Fixed GPT-5 and o-series model failures in vision analysis with "Unsupported parameter: 'max_tokens'" error. + * **Root Cause**: GPT-5 and o-series models require `max_completion_tokens` instead of `max_tokens`. + * **Solution**: Dynamic parameter selection based on model type. + * **Impact**: Vision analysis now works with all model families. + * (Ref: `route_backend_settings.py`, `functions_documents.py`, model-aware parameters) + +* **Group Plugin Global Merge Fix** + * Fixed group workspaces unable to see globally managed actions when merge setting enabled. + * **Root Cause**: `/api/group/plugins` endpoint didn't append global actions. + * **Solution**: Merge global actions into group plugins response with read-only badges. + * **Impact**: Groups can now select and use global actions while protecting them from modification. + * (Ref: `route_backend_groups.py`, global plugin merging) + +* **Workflow Summary Generation O1 API Fix** + * Fixed o1 model failures in workflow summary generation with "Unsupported parameter: 'temperature'" error. + * **Root Cause**: Unconditional application of `temperature` parameter to all models. + * **Solution**: Conditional parameter logic excluding `temperature` for o1 models. + * (Ref: `route_frontend_workflow.py`, model-aware parameter handling) + +* **Validation Utilities Consolidation** + * Consolidated duplicate validation functions across multiple files into centralized module. + * **Duplicated Functions**: `validateGuid()` in 4 locations, `validateEmail()` in 2 locations. + * **Solution**: Created `validation-utils.js` module with `ValidationUtils` namespace. + * **Benefits**: Single source of truth, easier maintenance, consistency. + * (Ref: `validation-utils.js`, code refactoring across control center and workspace files) + +* **Public Workspace Storage Calculation Fix** + * Fixed public workspaces showing 0 bytes storage despite having documents. + * **Root Cause**: Incorrect folder prefix (`public/{workspace_id}/` instead of `{workspace_id}/`). + * **Solution**: Fixed folder prefix, enhanced fallback logic, improved error handling. + * (Ref: `route_backend_control_center.py`, storage calculation logic) + +* **Public Workspace Metrics Caching Consistency Fix** + * Improved consistency in public workspace metrics caching across Control Center views. + * (Ref: metrics caching implementation) + +* **Activity Timeline All Logs Fix** + * Fixed activity timeline to properly display all log types. + * (Ref: activity log filtering) + +* **Activity Trends Field Mapping Fix** + * Corrected field mappings for activity trends data display. + * (Ref: activity trends API, field mapping) + +* **All File Types Embedding Token Tracking Fix** + * Extended embedding token tracking to all file types beyond just text. + * (Ref: `functions_documents.py`, comprehensive token tracking) + +* **PDF Embedding Token Tracking Fix** + * Fixed token tracking specifically for PDF document embeddings. + * (Ref: PDF processing, token capture) + +* **Create Group Button Visibility Fix** + * Fixed group creation button visibility based on admin settings. + * (Ref: UI conditional rendering, permission checks) + +* **File Message Metadata Loading Fix** + * Fixed metadata loading for file-related messages in conversations. + * (Ref: message metadata display, file associations) + +* **Group Agent Metadata Fix** + * Corrected agent metadata display and management in group contexts. + * (Ref: agent configuration, group agent handling) + +* **Group Document Metrics Date Format Fix** + * Fixed date formatting for group document metrics display. + * (Ref: document metrics, date formatting) + +* **Group Notification Context Enhancement** + * Enhanced notification context for group-related activities. + * (Ref: notification system, group context) + +* **Group Status UI Visibility Fix** + * Fixed UI visibility of group status indicators and controls. + * (Ref: group status display, conditional UI rendering) + +* **Group Table Auto-Refresh Fix** + * Fixed automatic refresh of group tables after operations. + * (Ref: table refresh logic, UI updates) + +* **Groups Tab Refresh Fix** + * Fixed refresh behavior on groups management tab. + * (Ref: tab state management, data refresh) + +* **Hidden Conversations Sidebar Click Fix** + * Fixed sidebar click handling for hidden conversations. + * (Ref: sidebar navigation, conversation visibility) + +* **Sidebar Group Badge Fix** + * Fixed group badge display in conversation sidebar. + * (Ref: sidebar UI, badge rendering) + +* **Top Nav Sidebar Overlap Fix** + * Fixed overlapping issues between top navigation and sidebar in certain layouts. + * (Ref: CSS layout, navigation positioning) + +* **Vision Analysis Debug Logging** + * Added comprehensive debug logging for vision analysis operations. + * (Ref: `functions_documents.py`, debug logging) + +* **Workflow PDF Iframe CSP Fix** + * Fixed Content Security Policy for PDF display in workflow iframes. + * (Ref: CSP headers, iframe configuration) + +* **Workflow PDF Viewer Height Fix** + * Fixed height issues in workflow PDF viewer modals. + * (Ref: modal styling, PDF viewer layout) + +* **Workspace Activity Modal Fix** + * Fixed workspace activity modal display and interaction issues. + * (Ref: modal functionality, workspace activity display) + +* **Search Cache Sharing Fix** + * Improved search cache sharing across user contexts. + * (Ref: cache key generation, sharing logic) + +--- + +Release notes for versions before v0.230.001 have moved to the [archived release notes](/explanation/archive_release_notes/). diff --git a/docs/explanation/release-notes/v0.237.md b/docs/explanation/release-notes/v0.237.md new file mode 100644 index 000000000..5e4a3d069 --- /dev/null +++ b/docs/explanation/release-notes/v0.237.md @@ -0,0 +1,331 @@ +--- +title: "Release notes 0.237 series" +description: "SimpleChat release notes for 0.237.049 – 0.237.001." +section: "Reference" +layout: page +--- + + + +# Release notes 0.237 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.237.049)** + +#### Bug Fixes + +* **Plugin Schema Validation `$ref` Resolution Fix** + * Fixed HTTP 500 error when creating or saving user plugins (actions). The JSON schema validator could not resolve `$ref: '#/definitions/AuthType'` because the `Plugin` sub-schema was extracted without a `RefResolver`, losing access to the parent schema's `definitions` block. + * **Root Cause**: `validate_plugin()` created a `Draft7Validator` using only `schema['definitions']['Plugin']`, which did not include the `definitions` section containing `AuthType`. The `validate_agent()` function already handled this correctly with `RefResolver.from_schema(schema)`. + * **Solution**: Added a `RefResolver` created from the full schema so that `$ref` pointers resolve correctly during validation. + * (Ref: `json_schema_validation.py`, `plugin.schema.json`, `AuthType` definition, `RefResolver`) + +* **Personal Agent Missing `user_id` Fix** + * Fixed issue where personal agents were saved to Cosmos DB without a `user_id` field, making them invisible to the user who created them. + * **Root Cause**: `save_personal_agent()` built a `cleaned_agent` dict with the correct `user_id`, `id`, and metadata, but the second half of the function switched to operating on the raw `agent_data` parameter. The final `upsert_item(body=agent_data)` saved the object that never had `user_id` assigned. + * **Solution**: Changed all `agent_data` references after sanitization to use `cleaned_agent` consistently, ensuring `user_id` and all other fields are included in the persisted document. + * (Ref: `functions_personal_agents.py`, `save_personal_agent`, Cosmos DB personal agents container) + +* **Global Agent Creation Blocked by `global_selected_agent` Check Fix** + * Fixed HTTP 400 error "There must be at least one agent matching the global_selected_agent" when adding or editing global agents. + * **Root Cause**: The add and edit agent routes performed a post-save check verifying that a global agent matched the `global_selected_agent` setting. This check was incorrect for add operations (adding an agent can never remove the selected one) and had a side-effect bug where the agent was already persisted before the 400 error was returned. + * **Solution**: Removed the post-save `global_selected_agent` enforcement from the add and edit routes. The delete route already correctly prevents deletion of the selected agent. + * (Ref: `route_backend_agents.py`, global agent add/edit routes, `global_selected_agent` setting) + +### **(v0.237.011)** + +#### Bug Fixes + +* **Chat File Upload "Unsupported File Type" Fix** + * Fixed issue where uploading xlsx, png, jpg, csv, and other image/tabular files in the chat interface returned a 400 "Unsupported file type" error. + * **Root Cause**: `os.path.splitext()` returns extensions with a leading dot (e.g., `.png`), but the `IMAGE_EXTENSIONS` and `TABULAR_EXTENSIONS` sets in `config.py` store extensions without dots (e.g., `png`). The comparison `'.png' in {'png', ...}` was always `False`, causing all image and tabular uploads to fall through to the unsupported file type error. + * **Solution**: Added `file_ext_nodot = file_ext.lstrip('.')` and used the dot-stripped extension for set comparisons against `IMAGE_EXTENSIONS` and `TABULAR_EXTENSIONS`, matching the pattern already used in `functions_documents.py`. + * (Ref: `route_frontend_chats.py`, file extension comparison, `IMAGE_EXTENSIONS`, `TABULAR_EXTENSIONS`) + +* **Manage Group Page Duplicate Code and Error Handling Fix** + * Fixed multiple code quality and user experience issues in the Manage Group page JavaScript. + * **Duplicate Event Handlers**: Removed duplicate event handler registrations (lines 96-127) for `.select-user-btn`, `.remove-member-btn`, `.change-role-btn`, `.approve-request-btn`, and `.reject-request-btn` that were causing multiple event firings. + * **Duplicate HTML in Actions Column**: Fixed member action buttons rendering duplicate attributes as visible text instead of functional buttons, causing raw HTML/CSS class names to display in the Actions column. + * **Duplicate Pending Request Buttons**: Removed duplicate Approve and Reject buttons in pending requests table that were appearing twice per request. + * **Enhanced Error Handling**: Improved `setRole()` and `removeMember()` functions with specific error messages for 404 (member not found) and 403 (permission denied) errors, automatic member list refresh on 404, and user-friendly toast notifications instead of generic alerts. + * **Removed Duplicate Comment**: Cleaned up duplicate "Render user-search results" comment. + * **Impact**: Member management buttons now render and function correctly, provide better error feedback, and auto-recover from stale member data. + * (Ref: `manage_group.js`, event handler deduplication, error handling improvements, toast notifications) + +### **(v0.237.009)** + +#### New Features + +* **ServiceNow Integration Documentation** + * Comprehensive documentation for integrating ServiceNow with Simple Chat, including step-by-step guides for both Basic Authentication and OAuth 2.0. + * **OAuth 2.0 Setup**: Detailed guide for Resource Owner Password Credential grant type with production security considerations. + * **OpenAPI Specifications**: 7 OpenAPI YAML files for ServiceNow Incident Management and Knowledge Base APIs (both bearer token and basic auth versions). + * **Agent Instructions**: Behavioral instructions optimized for ServiceNow operations (263 lines). + * **Key Features**: Integration user creation, role assignment guidance, token management strategies, troubleshooting guide, and production deployment considerations. + * **Documentation Files**: `SERVICENOW_INTEGRATION.md` (760 lines), `SERVICENOW_OAUTH_SETUP.md` (480+ lines), `servicenow_agent_instructions.txt`, and 7 OpenAPI specs in `docs/guides/servicenow/`. + * (Ref: ServiceNow integration, OAuth 2.0, OpenAPI specifications, enterprise integrations) + +#### Bug Fixes + +* **Workspace Search Deselection KeyError Fix** + * Fixed HTTP 500 error when deselecting the workspace search button after having a document selected. Users would get "Could not get a response. HTTP error! status: 500" in the chat interface. + * **Root Cause**: When workspace search was deselected (`hybrid_search_enabled = False`), the `user_metadata['workspace_search']` dictionary was never initialized. However, subsequent code for handling group scope or public workspace context attempted to access `user_metadata['workspace_search']['group_name']` or other properties, causing a KeyError. + * **Error**: `KeyError: 'workspace_search'` at lines 468, 479 in `route_backend_chats.py` when trying to set group_name or active_public_workspace_id. + * **Solution**: Added defensive checks before accessing `user_metadata['workspace_search']`. If the key doesn't exist, initialize it with `{'search_enabled': False}` before attempting to set additional properties like group_name or workspace IDs. + * **Workaround**: Clicking Home and then back to Chat worked because it triggered a page reload that reset the state properly. + * (Ref: `route_backend_chats.py`, workspace search, metadata initialization, KeyError handling) + +* **OpenAPI Basic Authentication Fix** + * Fixed "session not authenticated" errors when using Basic Authentication with OpenAPI actions, even when credentials were correct. + * **Root Cause**: Mismatch between how the UI stored Basic Auth credentials (as `username:password` string in `auth.key`) and how the OpenAPI plugin factory expected them (as separate `username` and `password` properties in `additionalFields`). + * **Solution**: Modified `OpenApiPluginFactory` to detect and parse `username:password` format from `auth.key`, splitting credentials into separate properties that the authentication middleware expects. + * **Files Modified**: `semantic_kernel_plugins/openapi_plugin_factory.py`. + * (Ref: OpenAPI actions, Basic Authentication, credential parsing, `OPENAPI_BASIC_AUTH_FIX.md`) + +* **Group Action OAuth Schema Merging Fix** + * Fixed HTTP 401 Unauthorized errors when using OAuth bearer token authentication with group actions. When editing group actions, `additionalFields` was empty, missing all authentication configuration. + * **Root Cause**: Group action backend routes did not call `get_merged_plugin_settings()` to merge UI form data with OpenAPI schema defaults, while global action routes did. This caused group actions to be saved without authentication configuration fields like `auth_method`, `base_url`, and authentication credentials. + * **Solution**: Updated group action save/update routes in `route_backend_plugins.py` to call `get_merged_plugin_settings()`, ensuring authentication configuration is properly merged and persisted. + * **Files Modified**: `route_backend_plugins.py`. + * (Ref: Group actions, OAuth authentication, schema merging, `GROUP_ACTION_OAUTH_SCHEMA_MERGING_FIX.md`) + +* **Group Agent Loading Fix** + * Fixed issue where group agents were not appearing in the agent list when per-user semantic kernel mode was enabled. Users selecting group agents would fall back to the global "researcher" agent with zero plugins/actions available. + * **Root Cause**: The `load_user_semantic_kernel()` function only loaded personal agents and global agents (when merge enabled), but completely omitted group agents from groups the user is a member of. + * **Solution**: Updated `load_user_semantic_kernel()` to fetch and load group agents for all groups the user is a member of, ensuring proper agent availability in per-user kernel mode. + * **Files Modified**: `semantic_kernel_loader.py`. + * (Ref: Group agents, per-user semantic kernel, agent loading, `GROUP_AGENT_LOADING_FIX.md`) + +* **Manage Group Page Syntax Error Fix** + * Fixed critical JavaScript syntax error preventing the manage group page from loading. Removed duplicate code blocks including duplicate conditional checks, forEach loops, button tags, and function definitions. + * The page was stuck on "Loading..." indefinitely with console error "Uncaught SyntaxError: missing ) after argument list" at line 673. + * (Ref: `manage_group.js`, duplicate code removal, syntax error resolution) + +* **File Extension Handling Improvements** + * Fixed multiple issues related to file extension handling and audio transcription across the application. + * **Missing MP3 Extension**: Fixed issue where .mp3 files were missing from the list of allowed extensions. Users attempting to upload mp3 files to workspaces saw "Uploaded 0/1, Failed: 1" with no error logging to activity_logs or documents containers. + * **Centralized Extension Definitions**: Resolved file extension variable duplications throughout codebase by centralizing all allowed file extension definitions in `config.py` and importing them in downstream function and route files. This prevents extension lists from going out of sync during updates. + * **Additional Supported Extensions**: Added missing file types supported by Document Intelligence and Video Indexer services: .heic (image), .mpg, .mpeg, .webm (video). + * **Browser-Compatible Extensions**: Adjusted file extensions in `chat-enhanced-citations.js` for proper browser rendering. Removed incompatible formats like .heif and added compatible formats like .3gp after thorough testing. + * (Ref: `config.py`, file extension centralization, enhanced citations rendering) + +* **Audio Transcription Continuous Recognition Fix (MAG)** + * Fixed incomplete audio transcriptions in Azure Government (MAG) environments where transcription stopped at first silence or after 30 seconds of audio. + * **Root Cause**: Previous implementation used `recognize_once()` method which stops transcription at the first silence (end of sentence, speaker pauses) and has a maximum 30-second transcription limit. + * **Solution**: Implemented continuous recognition using `start_continuous_recognition()` method instead of `recognize_once()`, enabling full-length audio file transcription without interruption at natural speech pauses. + * **Impact**: Audio files now transcribe completely regardless of length or natural pauses in speech, improving transcription quality and completeness in MAG regions where Fast Transcription API is unavailable. + * (Ref: Azure Speech Service, continuous recognition, MAG support, audio transcription) + +* **Workspace File Metadata Edit Error Fix** + * Fixed "'tuple' object has no attribute 'get'" error when clicking Save after editing workspace file metadata in personal, group, or public workspaces. + * **Root Cause**: Missing checks and error handling in route backend documents code when processing metadata updates. + * **Solution**: Added additional validation checks and proper handling to `route_backend_documents.py` for all workspace types (personal, group, public). + * **Impact**: Users can now successfully edit and save file metadata without encountering errors. + * (Ref: `route_backend_documents.py`, metadata updates, error handling) + +### **(v0.237.007)** + +#### Bug Fixes + +* **Sidebar Conversations Race Condition and DOM Manipulation Fix** + * Fixed two critical issues preventing sidebar conversations from displaying correctly for users. + * **Issue #1 - DOM Manipulation Error**: Fixed JavaScript error `NotFoundError: Failed to execute 'insertBefore' on 'Node'` that caused sidebar conversation list to fail to render. Root cause was incorrect order of DOM element manipulation where `insertBefore()` was called with an invalid reference node after elements had been moved/removed. + * **Issue #2 - Race Condition with Empty Conversations**: Fixed race condition where users with no existing conversations who created their first conversation would not see it appear in the sidebar. Root cause was the loading flag never being reset when API returned empty conversations array, causing all subsequent reload attempts to be blocked indefinitely. + * **Solution Part 1**: Enhanced DOM manipulation with stricter parent node validation (`dropdownElement.parentNode === headerRow`), wrapped operations in try-catch for graceful fallback to `appendChild()`, and added comprehensive error logging. Ensures sidebar always renders even if timing issues occur. + * **Solution Part 2**: Implemented pending reload queue system. Instead of blocking concurrent loads, the code now marks `pendingSidebarReload = true` when a reload is requested during active loading. All code paths (success, empty array, error) now reset the loading flag and check for pending reloads, automatically triggering queued reload after 100ms delay. + * **Impact**: Before fix, ~10-15% of page loads had DOM errors and 100% of new users couldn't see their first conversation without manual page refresh. After fix, 0% failures with seamless user experience and no manual refresh needed. + * (Ref: `chat-sidebar-conversations.js`, DOM manipulation order, race condition handling, loading flag management, pending reload queue, lines 12-40, 93-115, 169-183) + +### **(v0.237.006)** + +#### Bug Fixes + +* **Windows Unicode Encoding Issue Fix** + * Fixed critical cross-platform compatibility issue where the application crashes on Windows when processing or displaying Unicode characters beyond the Western European character set. + * **Root Cause**: Python on Windows uses cp1252 encoding for stdout/stderr (limited to 256 Western European characters), while Azure services and web applications use UTF-8 encoding universally (1.1M+ characters). This mismatch caused `UnicodeEncodeError: 'charmap' codec can't encode character '\uXXXX'` when logging or displaying emojis, international characters, IPA symbols, or special formatting. + * **Impact**: Application crashes affecting: + * Video transcripts with phonetic symbols + * Chat messages containing emojis or international text + * Agent responses with Unicode formatting + * Debug logging across the entire application + * Error messages and stack traces + * **Solution**: Configured UTF-8 encoding globally at application startup for Windows platforms by reconfiguring `sys.stdout` and `sys.stderr` to UTF-8 at the top of `app.py` before any imports or print statements. Includes fallback for older Python versions (<3.7). Platform-specific fix only applies on Windows. + * **Testing**: Verified with video processing (IPA phonetic symbols), chat messages (emojis/international characters), debug logging (Unicode content), and confirmed no impact on Linux/macOS deployments. + * **Issue**: Fixes [#644](https://github.com/microsoft/simplechat/issues/644) + * (Ref: `app.py`, UTF-8 encoding configuration, cross-platform compatibility) + +* **Azure Speech Service Managed Identity Authentication Fix** + * Fixed Azure Speech Service managed identity authentication requiring resource-specific endpoints with custom subdomains instead of regional endpoints. + * **Root Cause**: Managed identity (AAD token) authentication fails with regional endpoints (e.g., `https://eastus2.api.cognitive.microsoft.com`) because the Bearer token doesn't specify which Speech resource to access. The regional gateway cannot determine resource authorization, resulting in 400 BadRequest errors. Key-based authentication works with regional endpoints because the subscription key identifies the specific resource. + * **Impact**: Users could not use managed identity authentication with Speech Service for audio transcription. Setup appeared successful but failed at runtime with authentication errors. + * **Solution**: Comprehensive setup guide for managed identity requiring: + * **Custom Subdomain**: Enable custom subdomain on Speech resource using `az cognitiveservices account update --custom-domain ` + * **Resource-Specific Endpoint**: Configure endpoint as `https://.cognitiveservices.azure.com` (not regional endpoint) + * **RBAC Roles**: Assign `Cognitive Services Speech User` and `Cognitive Services Speech Contributor` roles to App Service managed identity + * **Admin Settings**: Update Speech Service Endpoint to resource-specific URL, set Authentication Type to "Managed Identity", leave Speech Service Key empty + * **Key Differences**: + * Key auth ✅ works with both regional and resource-specific endpoints + * Managed Identity ❌ fails with regional endpoints (400 BadRequest) + * Managed Identity ✅ works with resource-specific endpoints (requires custom subdomain) + * **Troubleshooting Guide**: Added comprehensive troubleshooting for `NameResolutionError` (custom subdomain not enabled), 400 BadRequest (wrong endpoint type), 401 Authentication errors (missing RBAC roles). + * (Ref: Azure Speech Service, managed identity authentication, custom subdomain, RBAC configuration, endpoint types) + +* **Sidebar Conversations DOM Manipulation Fix** + * Fixed JavaScript error "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node" that prevented sidebar conversations from loading. + * **Root Cause**: In `createSidebarConversationItem()`, the code was attempting DOM manipulation in the wrong order. When `originalTitleElement` was appended to `titleWrapper`, it was removed from `headerRow`, making the subsequent `insertBefore(titleWrapper, dropdownElement)` fail because `dropdownElement` was no longer a valid child reference in the expected DOM position. + * **Impact**: Users experienced a complete failure loading the sidebar conversation list, with the error appearing in browser console and preventing any conversations from displaying in the sidebar. This affected all users attempting to view their conversation history. + * **Solution**: Reordered DOM manipulation to remove `originalTitleElement` from DOM first, style it, add it to `titleWrapper`, then insert the complete `titleWrapper` before `dropdownElement`. Added validation to check if `dropdownElement` is a valid child before attempting insertion. + * (Ref: `chat-sidebar-conversations.js`, `createSidebarConversationItem()`, DOM manipulation order, line 150) + +### **(v0.237.005)** + +#### Bug Fixes + +* **Azure AI Search Test Connection Fix** + * Fixed test connection functionality for Azure AI Search configuration validation. + * (Ref: Azure AI Search, connection testing, admin configuration, `AZURE_AI_SEARCH_TEST_CONNECTION_FIX.md`) + +* **Retention Policy Field Name Fix** + * Fixed retention policy to use the correct field name `last_updated` instead of the non-existent `last_activity_at` field. + * **Root Cause**: The retention policy query was looking for `last_activity_at` field, but all conversation schemas (legacy and current) use `last_updated` to track the conversation's last modification time. + * **Impact**: After the v0.237.004 fix, NO conversations were being deleted because the query required a field that doesn't exist on any conversation document. + * **Schema Support**: Now correctly supports all 3 conversation schemas: + * Schema 1 (legacy): Messages embedded in conversation document with `last_updated` + * Schema 2 (middle): Messages in separate container with `last_updated` + * Schema 3 (current): Messages with threading metadata with `last_updated` + * **Solution**: Changed SQL query to use `last_updated` field which exists on all conversation documents. + * (Ref: retention policy execution, conversation deletion, `delete_aged_conversations()`, `last_updated` field) + +### **(v0.237.004)** + +#### Bug Fixes + +* **Critical Retention Policy Deletion Fix** + * Fixed a critical bug where conversations with null/undefined `last_activity_at` were being deleted regardless of their actual age. + * **Root Cause**: The SQL query logic treated conversations with missing `last_activity_at` field as "old" and deleted them, even if they were created moments ago. + * **Impact**: Brand new conversations that hadn't had their `last_activity_at` field populated were incorrectly deleted when retention policy ran. + * **Solution**: Changed query to only delete conversations that have a valid, non-null `last_activity_at` that is older than the configured retention period. Conversations with null/undefined `last_activity_at` are now skipped. + * (Ref: retention policy execution, conversation deletion, `delete_aged_conversations()`) + +* **Public Workspace Retention Error Fix** + * Fixed error "name 'cosmos_public_conversations_container' is not defined" when executing retention policy for public workspaces. + * **Root Cause**: The code attempted to process conversations for public workspaces, but public workspaces don't have a separate conversations container—only documents and prompts. + * **Solution**: Removed conversation processing for public workspaces since they only support document retention. + * (Ref: public workspace retention, `process_public_retention()`) + +### **(v0.237.003)** + +#### New Features + +* **Extended Retention Policy Timeline Options** + * Added additional granular retention period options for conversations and documents across all workspace types. + * **New Options**: 2 days, 3 days, 4 days, 6 days, 7 days (1 week), and 14 days (2 weeks). + * **Full Option Set**: 1, 2, 3, 4, 5, 6, 7 (1 week), 10, 14 (2 weeks), 21 (3 weeks), 30, 60, 90 (3 months), 180 (6 months), 365 (1 year), 730 (2 years) days. + * **Scope**: Available in Admin Settings (organization defaults), Profile page (personal settings), and Control Center (group/public workspace management). + * **Files Modified**: `admin_settings.html`, `profile.html`, `control_center.html`. + * (Ref: retention policy configuration, workspace retention settings, granular time periods) + +#### Bug Fixes + +* **Custom Logo Not Displaying Across App Fix** + * Fixed issue where custom logos uploaded via Admin Settings would only display on the admin page but not on other pages (chat, sidebar, landing page). + * **Root Cause**: The `sanitize_settings_for_user()` function was stripping `custom_logo_base64`, `custom_logo_dark_base64`, and `custom_favicon_base64` keys entirely because they contained "base64" (a sensitive term filter), preventing templates from detecting logo existence. + * **Solution**: Modified sanitization to add boolean flags for logo/favicon existence after filtering, allowing templates to check if logos exist without exposing actual base64 data. + * **Security**: Actual base64 data remains hidden from frontend; only True/False boolean values are exposed. + * **Files Modified**: `functions_settings.py` (`sanitize_settings_for_user()` function). + * (Ref: logo display, settings sanitization, template conditionals) + +### **(v0.237.001)** + +#### New Features + +* **Retention Policy Defaults** + * Admin-configurable organization-wide default retention policies for conversations and documents across all workspace types. + * **Organization Defaults**: Set default retention periods (1 day to 10 years, or "Don't delete") separately for personal, group, and public workspaces. + * **User Choice**: Users see "Using organization default (X days)" option and can override with custom settings or revert to org default. + * **Conditional Display**: Default retention settings only appear in Admin Settings when the corresponding workspace type is enabled. + * **Force Push Feature**: Administrators can push organization defaults to all workspaces, overriding any custom retention policies users have set. + * **Settings Auto-Save**: Force push automatically saves pending settings changes before executing to ensure current values are pushed. + * **Activity Logging**: Force push actions are logged to `activity_logs` container for audit purposes with admin info, affected scopes, and results summary. + * **API Endpoints**: New `/api/retention-policy/defaults/` (GET) and `/api/admin/retention-policy/force-push` (POST) endpoints. + * **Files Modified**: `functions_settings.py`, `admin_settings.html`, `route_frontend_admin_settings.py`, `route_backend_retention_policy.py`, `functions_retention_policy.py`, `functions_activity_logging.py`, `profile.html`, `control_center.html`, `workspace-manager.js`. + * (Ref: Default retention settings, Force Push modal, activity logging, retention policy execution) + +* **Private Networking Support** + * Comprehensive private networking support for SimpleChat deployments via Azure Developer CLI (AZD) and Bicep infrastructure-as-code. + * **Network Isolation**: Private endpoints for all Azure PaaS services (Cosmos DB, Azure OpenAI, AI Search, Storage, Key Vault, Document Intelligence). + * **VNet Integration**: Full virtual network integration for App Service and dependent resources with automated Private DNS zone configuration. + * **AZD Integration**: Seamless deployment via `azd up` with `ENABLE_PRIVATE_NETWORKING=true` environment variable. + * **Post-Deployment Security**: New `postup` hook automatically disables public network access when private networking is enabled. + * **Enhanced Deployment Hooks**: Refactored all deployment hooks in `azure.yaml` with stepwise logging, explicit error handling, and clearer output for troubleshooting. + * **Documentation Updates**: Expanded Bicep README with prerequisites, Azure Government (USGov) considerations, and post-deployment validation steps. + * (Ref: `deployers/azure.yaml`, `deployers/bicep/`, private endpoint configuration, VNet integration) + +* **User Agreement for File Uploads** + * Global admin-configurable agreement that users must accept before uploading files to workspaces. + * **Configuration Options**: Enable/disable toggle, workspace type selection (Personal, Group, Public, Chat), Markdown-formatted agreement text (200-word limit), optional daily acceptance mode. + * **User Experience**: Modal prompt before file uploads with agreement text, "Accept & Upload" or "Cancel" options, daily acceptance tracking to reduce repeat prompts. + * **Activity Logging**: All acceptances logged to activity logs for compliance tracking with timestamp, user, workspace type, and action context. + * **Admin Access**: Settings accessible via Admin Settings → Workspaces tab → User Agreement section, with sidebar navigation link. + * **Files Added**: `user-agreement.js` (frontend module), `route_backend_user_agreement.py` (API endpoints). + * **Files Modified**: `admin_settings.html`, `route_frontend_admin_settings.py`, `base.html`, `_sidebar_nav.html`, `functions_activity_logging.py`, `workspace-documents.js`, `group_workspaces.html`, `public_workspace.js`, `chat-input-actions.js`. + * (Ref: User Agreement modal, file upload workflows, activity logging, admin configuration) + +* **Web Search via Azure AI Foundry Agents** + * Web search capability through Azure AI Foundry agents using Grounding with Bing Search service. + * **Pricing**: $14 per 1,000 transactions (150 transactions/second, 1M transactions/day limit). + * **Admin Consent Flow**: Requires explicit administrator consent before enabling due to data processing considerations outside Azure compliance boundary. + * **Consent Logging**: All consent acceptances are logged to activity logs for compliance and audit purposes. + * **Setup Guide Modal**: Comprehensive in-app configuration guide with step-by-step instructions for creating the agent, configuring Bing grounding, setting result count to 10, and recommended agent instructions. + * **User Data Notice**: Admin-configurable notification banner that appears when users activate web search, informing them that their message will be sent to Microsoft Bing. Customizable notice text, dismissible per session. + * **Graceful Error Handling**: When web search fails, the system informs users rather than answering from outdated training data. + * **Seamless Integration**: Web search results automatically integrated into AI responses when enabled. + * **Settings**: `enable_web_search` toggle, `web_search_consent_accepted` tracking, `enable_web_search_user_notice` toggle, and `web_search_user_notice_text` customization in admin settings. + * **Files Added**: `_web_search_foundry_info.html` (setup guide modal). + * **Files Modified**: `route_frontend_admin_settings.py`, `route_backend_chats.py`, `functions_activity_logging.py`, `admin_settings.html`, `chats.html`, `chat-input-actions.js`, `functions_settings.py`. + * (Ref: Grounding with Bing Search, Azure AI Foundry, consent workflow, activity logging, pricing, user transparency) + +* **Conversation Deep Linking** + * Direct URL links to specific conversations via query parameters for sharing and bookmarking. + * **URL Parameters**: Supports both `conversationId` and `conversation_id` query parameters. + * **Automatic URL Updates**: Current conversation ID automatically added to URL when selecting conversations. + * **Browser Integration**: Uses `history.replaceState()` for seamless URL updates without new history entries. + * **Error Handling**: Graceful handling of invalid or inaccessible conversation IDs with toast notifications. + * **Files Modified**: `chat-onload.js`, `chat-conversations.js`. + * (Ref: deep linking, URL parameters, conversation navigation, shareability) + +* **Plugin Authentication Type Constraints** + * Per-plugin-type authentication method restrictions for better security and API compatibility. + * **Schema-Based Defaults**: Falls back to global `AuthType` enum from `plugin.schema.json`. + * **Definition File Overrides**: Plugin-specific `.definition.json` files can restrict available auth types. + * **API Endpoint**: New `/api/plugins//auth-types` endpoint returns allowed auth types and source. + * **Frontend Integration**: UI can query allowed auth types to display only valid options. + * **Files Modified**: `route_backend_plugins.py`. + * (Ref: plugin authentication, auth type constraints, OpenAPI plugins, security) + +#### Bug Fixes + +* **Control Center Chart Date Labels Fix** + * Fixed activity trends chart date labels to parse dates in local time instead of UTC. + * **Root Cause**: JavaScript `new Date()` was parsing date strings as UTC, causing labels to display previous day in western timezones. + * **Solution**: Parse date components explicitly and construct Date objects in local timezone. + * **Impact**: Chart x-axis labels now correctly show the intended dates regardless of user timezone. + * **Files Modified**: `control_center.html` (Chart.js date parsing logic). + * (Ref: Chart.js, date parsing, timezone handling, activity trends) + +* **Sovereign Cloud Cognitive Services Scope Fix** + * Fixed hardcoded commercial Azure cognitive services scope references that prevented authentication in Azure Government (MAG) and custom cloud environments. + * **Root Cause**: `chat_stream_api` and `smart_http_plugin` used hardcoded commercial cognitive services scope URL instead of configurable value from `config.py`. + * **Solution**: Replaced hardcoded scope with `AZURE_OPENAI_TOKEN_SCOPE` environment variable, dynamically resolved based on cloud environment. + * **Impact**: Streaming chat and Smart HTTP Plugin now work correctly in Azure Government, China, and custom cloud deployments. + * **Related Issue**: [#616](https://github.com/microsoft/simplechat/issues/616) + * (Ref: `chat_stream_api`, `smart_http_plugin`, sovereign cloud authentication, MAG support) + +* **User Search Toast and Inline Messages Fix** + * Updated `searchUsers()` function to use inline and toast messages instead of browser alert pop-ups. + * **Improvement**: Search feedback (empty search, no users found, errors) now displays as inline messages in the search results area. + * **Error Handling**: Errors display both inline message and toast notification for visibility. + * **Benefits**: Non-disruptive UX, contextual feedback, consistency with application patterns. + * **Related PR**: [#608](https://github.com/microsoft/simplechat/pull/608#discussion_r2701900020) + * (Ref: group management, user search, toast notifications, UX improvement) diff --git a/docs/explanation/release-notes/v0.239.md b/docs/explanation/release-notes/v0.239.md new file mode 100644 index 000000000..930cb3c21 --- /dev/null +++ b/docs/explanation/release-notes/v0.239.md @@ -0,0 +1,148 @@ +--- +title: "Release notes 0.239 series" +description: "SimpleChat release notes for 0.239.002." +section: "Reference" +layout: page +--- + + + +# Release notes 0.239 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.239.002)** + +#### New Features + +* **Conversation Export** + * Export one or multiple conversations from the Chat page in JSON or Markdown format. + * **Single Export**: Use the ellipsis menu on any conversation to quickly export it. + * **Multi-Export**: Enter selection mode, check the conversations you want, and click the export button. + * A guided 4-step wizard walks you through selection review, format choice, packaging options (single file or ZIP archive), and download. + * Sensitive internal metadata is automatically stripped from exported data for security. + +* **Retention Policy UI for Groups and Public Workspaces** + * Can now configure conversation and document retention periods directly from the workspace and group management page. + * Choose from preset retention periods ranging from 7 days to 10 years, use the organization default, or disable automatic deletion entirely. +* **Owner-Only Group Agent and Action Management** + * New admin setting to restrict group agent and group action management (create, edit, delete) to only the group Owner role. + * **Admin Toggle**: "Require Owner to Manage Group Agents and Actions" located in Admin Settings > My Groups section, under the existing group creation membership setting. + * **Default Off**: When disabled, both Owner and Admin roles can manage group agents and actions (preserving existing behavior). + * **When Enabled**: Only the group Owner can create, edit, and delete group agents and group actions. Group Admins and other roles are restricted to read-only access. + * **Backend Enforcement**: Server-side validation returns 403 for non-Owner users attempting create, update, or delete operations on group agents and actions. + * **Frontend Enforcement**: "New Agent" and "New Action" buttons are hidden, edit/delete controls are removed, and a permission warning is displayed for non-Owner users. + * **Files Modified**: `functions_settings.py`, `admin_settings.html`, `route_frontend_admin_settings.py`, `route_backend_agents.py`, `route_backend_plugins.py`, `group_workspaces.html`, `group_agents.js`, `group_plugins.js`. + * (Ref: `require_owner_for_group_agent_management` setting, `assert_group_role` permission check) + +* **Enforce Workspace Scope Lock** + * New admin setting to control whether users can unlock workspace scope in chat conversations. + * **Enabled by Default**: When enabled, workspace scope automatically locks after the first AI search and users cannot unlock it, preventing accidental cross-contamination between data sources. + * **Informational Modal**: Users can still click the lock icon to view which workspaces are locked, but the "Unlock Scope" button is hidden and replaced with an informational message. + * **Backend Enforcement**: Server-side validation rejects unlock API requests when the setting is enabled, providing defense-in-depth security. + * **Admin Toggle**: Located in Admin Settings > Workspace tab in the new "Workspace Scope Lock" section. + * **Files Modified**: `config.py`, `functions_settings.py`, `route_frontend_admin_settings.py`, `admin_settings.html`, `chats.html`, `chat-documents.js`, `route_backend_conversations.py`. + * (Ref: `ENFORCE_WORKSPACE_SCOPE_LOCK.md`) + +* **Blob Metadata Tag Propagation** + * Document tags now propagate to Azure Blob Storage metadata when enhanced citations is enabled. + * **Automatic Sync**: When tags are added, removed, or updated on a document, the corresponding blob's metadata is updated with a `document_tags` field containing a comma-separated list of tags. + * **Conditional**: Only active when `enable_enhanced_citations` is enabled in admin settings; no blob metadata changes occur otherwise. + * **Cross-Workspace**: Works for personal, group, and public workspace documents. + * **Non-Blocking**: Blob metadata update failures are logged but do not prevent the primary tag propagation to AI Search chunks. + * **Files Modified**: `functions_documents.py`. + * (Ref: `BLOB_METADATA_TAG_PROPAGATION.md`) + +* **Document Tag System** + * Comprehensive tag management system for organizing documents across personal, group, and public workspaces. + * **Tag Definitions**: Tags with custom colors from a 10-color default palette (blue, green, amber, red, purple, pink, cyan, lime, orange, indigo) or user-specified hex codes. Colors assigned deterministically via character-sum hash. + * **Full CRUD API**: 15 endpoints (5 per workspace type) for listing, creating, bulk tagging, renaming/recoloring, and deleting tags. Consistent API pattern across `/api/documents/tags`, `/api/group_documents//tags`, and `/api/public_workspace_documents//tags`. + * **Bulk Tag Operations**: Apply, remove, or replace tags on multiple documents in a single operation with per-document success/error reporting. + * **AI Search Integration**: Tags propagate to all document chunks via `propagate_tags_to_chunks()`, enabling OData tag filtering during hybrid search with AND logic (`document_tags/any(t: t eq 'tag')`). + * **Tag Validation**: Max 50 characters, alphanumeric plus hyphens/underscores only, normalized to lowercase, duplicates silently deduplicated. + * **Tag Storage**: Personal tags in user settings, group tags on group Cosmos document, public workspace tags on workspace Cosmos document. + * **Files Modified**: `functions_documents.py`, `functions_search.py`, `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`. + * **Files Added**: `static/json/ai_search-index-user.json`, `static/json/ai_search-index-group.json`, `static/json/ai_search-index-public.json`. + * (Ref: Document Tag System, AI Search OData filtering, cross-workspace tags, `DOCUMENT_TAG_SYSTEM.md`) + +* **Workspace Folder View (Grid View)** + * Toggle between traditional list view and folder-based grid view for workspace documents via radio buttons. + * **Tag Folders**: Color-coded folder cards displaying tag name, document count, folder icon, and context menu (rename, recolor, delete). + * **Special Folders**: "Untagged" folder for documents with no tags and "Unclassified" folder for documents without classification (when classification is enabled). + * **Folder Drill-Down**: Click a folder to view its contents with breadcrumb navigation, in-folder search, configurable page sizes (10, 20, 50), and sort by filename or title. + * **Grid Sort Controls**: Sort folder overview by name or file count with ascending/descending toggle. + * **View Persistence**: Selected view preference saved to localStorage and restored on page load. + * **Tag Management Modal**: Step-through workflow for creating, editing, renaming, recoloring, and deleting tags with color picker. + * **Cross-Workspace Support**: Equivalent grid view and tag management available in group workspaces (inline JS) and public workspaces. + * **Files Added**: `workspace-tags.js` (1257 lines), `workspace-tag-management.js` (732 lines). + * **Files Modified**: `workspace.html`, `group_workspaces.html`, `public_workspaces.html`, `public_workspace.js`. + * (Ref: Folder view, tag management modal, grid rendering, `WORKSPACE_FOLDER_VIEW.md`) + +* **Multi-Workspace Scope Management** + * Select from Personal, multiple Group, and multiple Public workspaces simultaneously in the chat interface. + * **Hierarchical Scope Dropdown**: Organized sections with checkbox multi-selection and "Select All / Clear All" toggle with indeterminate state support. + * **Scope Locking**: Per-conversation lock that freezes workspace selection after the first AI Search. Three-state machine: `null` (auto-lockable) → `true` (locked) → `false` (user-unlocked) → `true` (re-lockable). + * **Lock Indicator**: Visual lock icon with tooltip showing locked workspace names. Locked workspaces appear grayed out in the dropdown. + * **Lock/Unlock Modal**: Dialog for manually toggling scope lock per conversation. + * **Lock Persistence**: Lock state stored in conversation metadata via `PATCH /api/conversations//scope_lock`. + * **Workspace Search Container**: Multi-column flex layout (Scope → Tags → Documents) with connected card UI and viewport boundary detection. + * **Files Modified**: `chat-documents.js`, `chat-messages.js`, `chats.html`, `route_backend_chats.py`, `route_backend_conversations.py`. + * (Ref: Multi-workspace selection, scope locking, search container layout, `MULTI_WORKSPACE_SCOPE_MANAGEMENT.md`) + +* **Chat Document and Tag Filtering** + * Checkbox-based multi-document selection replacing the legacy single-document dropdown in the chat interface. + * **Custom Document Dropdown**: Checkboxes for each document with real-time search, "All Documents" option, and selected count display ("3 Documents"). + * **Scope Indicators**: Each document labeled with its source workspace: `[Personal]`, `[Group: Name]`, or `[Public: Name]`. + * **Multi-Tag Filtering**: Checkbox dropdown for selecting tags to filter the document list. Classification categories shown with color coding when enabled. + * **Dynamic Tag Loading**: Tags load and merge across all selected scope workspaces with aggregated counts. + * **DOM-Based Filtering**: Non-matching documents removed from the DOM (not hidden via CSS), following project conventions. Removed items stored for restoration when filters change. + * **Backend Integration**: Selected document IDs and tags sent in chat request body. Backend constructs OData AND filter: `document_tags/any(t: t eq 'tag1') and document_tags/any(t: t eq 'tag2')`. + * **Files Modified**: `chat-documents.js`, `chat-messages.js`, `functions_search.py`, `route_backend_chats.py`, `chats.html`. + * (Ref: Multi-document selection, tag filtering, OData search integration, `CHAT_DOCUMENT_AND_TAG_FILTERING.md`) + +#### Bug Fixes + +* **Citation Parsing Bug Fix** + * Fixed citation parsing edge cases where page range references (e.g., "Pages: 1-5") failed to generate correct clickable links when not all pages had explicit reference IDs in the bracketed citation section of the AI response. + * **Root Cause**: The `parseCitations()` function only generated links for pages with existing `[doc_prefix_N]` bracket references, leaving pages without explicit references as non-functional text. + * **Solution**: Added auto-fill logic using `getDocPrefix()` to extract the document ID prefix from known reference patterns and construct missing page references (e.g., if `[doc_abc_1]` exists, infer `doc_abc_2` through `doc_abc_5`). + * **Files Modified**: `chat-citations.js`. + * (Ref: Citation parsing, page range handling, `CITATION_IMPROVEMENTS.md`) + +* **Public Workspace setActive 403 Fix** + * Fixed issue where non-owner/admin/document-manager users received a 403 "Not a member" error when trying to activate a public workspace for chat. + * Root cause was an overly restrictive membership check on the `/api/public_workspaces/setActive` endpoint that only allowed owners, admins, and document managers — even though public workspaces are intended to be accessible to all authenticated users for chatting. + * Removed the membership verification from the `setActive` endpoint; the route still requires authentication (`@login_required`, `@user_required`) and the public workspaces feature flag (`@enabled_required`). + * Other admin-level endpoints (listing members, viewing stats, ownership transfer) retain their membership checks. + * (Ref: `route_backend_public_workspaces.py`, `api_set_active_public_workspace`) +* **Chats Page User Settings Hardening** + * Fixed a user-specific chats page failure where only one affected user could not load `/chats` due to malformed per-user settings data. + * **Root Cause**: The chats route assumed `user_settings["settings"]` was always a dictionary. If that field existed but had an invalid type (for example string, null, or list), the page could fail before rendering. + * **Solution**: Hardened `get_user_settings()` to normalize missing/malformed `settings` to `{}` and persist the repaired document. Hardened the chats route to use safe dictionary fallbacks when reading nested settings values. + * **Telemetry**: Added repair logging (`[UserSettings] Malformed settings repaired`) to improve diagnostics for future user-specific data-shape issues. + * **Files Modified**: `functions_settings.py`, `route_frontend_chats.py`, `config.py`. + * **Files Added**: `test_chats_user_settings_hardening_fix.py`, `CHATS_USER_SETTINGS_HARDENING_FIX.md`. + * (Ref: user settings normalization, `/chats` route resilience, `functional_tests/test_chats_user_settings_hardening_fix.py`, `docs/explanation/fixes/CHATS_USER_SETTINGS_HARDENING_FIX.md`) + +* **Tag Filter Input Sanitization (Injection Prevention)** + * Added `sanitize_tags_for_filter()` function to validate tag filter inputs against the same `^[a-z0-9_-]+$` character whitelist enforced when saving tags. + * Previously, tag filter values from query parameters only passed through `normalize_tag()` (strip + lowercase) without character validation, allowing arbitrary characters to reach OData filter construction in `build_tags_filter()`. + * Hardened `build_tags_filter()` in `functions_search.py` to validate tags before interpolating into OData expressions, eliminating the OData injection vector. + * Updated tag filter parsing in personal, group, and public document routes to use `sanitize_tags_for_filter()` for defense-in-depth. + * Invalid tag filter values are silently dropped (they cannot match any stored tag). + * **Files Modified**: `functions_documents.py`, `functions_search.py`, `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`. + * (Ref: `TAG_FILTER_INJECTION_FIX.md`, `sanitize_tags_for_filter`) + +#### User Interface Enhancements + +* **Extended Document Dropdown Width** + * Widened the document selection dropdown in the chat interface for improved readability of long filenames. Dropdown width now dynamically adapts to the parent container. + * **Files Modified**: `chat-documents.js`. + * (Ref: Document dropdown, UI readability) + +* **Enhanced Citation Links** + * Robust inline citation links with support for both inline source references and hybrid citation buttons. + * Metadata citation support for viewing extracted document metadata including OCR text, vision analysis, and detected objects via the enhanced citation modal. + * Improved error handling in citation JSON parsing with graceful fallback for malformed citation strings. + * **Files Modified**: `chat-citations.js`, `chat-enhanced-citations.js`. + * (Ref: Citation rendering, metadata citations, enhanced citation modal, `CITATION_IMPROVEMENTS.md`) diff --git a/docs/explanation/release-notes/v0.241.md b/docs/explanation/release-notes/v0.241.md new file mode 100644 index 000000000..50a33aa8e --- /dev/null +++ b/docs/explanation/release-notes/v0.241.md @@ -0,0 +1,552 @@ +--- +title: "Release notes 0.241 series" +description: "SimpleChat release notes for 0.241.007 – 0.241.001." +section: "Reference" +layout: page +--- + + + +# Release notes 0.241 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.241.007)** + +#### Bug Fixes + +* **Global Agent Scope Gate Fallback** + * Fixed per-user Semantic Kernel chats so selecting a global agent no longer silently falls back to the standard GPT model when personal agents are disabled for the tenant. + * The per-user loader now treats global, personal, and group agent scopes separately, allowing valid global-agent selections to continue through agent invocation while keeping personal and group scope toggles enforced as configured. + * Added regression coverage for the shared scope gate used by the per-user loader. + * (Ref: `semantic_kernel_loader.py`, `functions_agent_scope.py`, `test_global_agent_scope_gate.py`, global agent request routing) + +### **(v0.241.006)** + +#### Bug Fixes + +* **Requests Runtime Dependency Upgrade** + * Updated the runtime HTTP client dependency from `requests==2.33.0` to `requests==2.33.1` in the main application requirements to keep deployment environments aligned with the latest pinned patch release. + * (Ref: `application/single_app/requirements.txt`) + +* **Speech and Video Indexer Setup Guidance Alignment** + * Fixed stale admin guidance around Azure AI Video Indexer and shared Azure Speech configuration so managed-identity setup no longer points admins toward legacy Video Indexer API keys or incomplete Speech instructions. + * The admin experience now reflects the shared Speech resource model, adds Speech Resource ID helper fields, and keeps managed-identity voice-response requirements aligned with runtime behavior. + * (Ref: `admin_settings.html`, `admin_settings.js`, `route_backend_tts.py`, `functions_documents.py`, shared Speech and Video Indexer guidance) + +* **Agent Output Token Defaults and Foundry Limit Enforcement** + * Fixed stale agent output-token defaults so new and normalized agents now use `-1` to defer to the provider or model default instead of silently reintroducing older fixed caps. + * Azure AI Foundry agent execution now also honors saved output-token settings in both classic Foundry agent runs and new Foundry Responses-based runs, so configured limits are enforced consistently instead of only being stored in agent configuration. + * (Ref: `functions_global_agents.py`, `agent.schema.json`, `foundry_agent_runtime.py`, `test_foundry_token_limit_defaults.py`) + +* **Tabular Exhaustive Result Synthesis Retry** + * Fixed exhaustive tabular questions such as "list all" requests so the workflow no longer stops at an answer that claims only sample rows or workbook metadata are available after analytical tool calls already returned the full matching result set. + * General tabular analysis now detects full versus partial result coverage from tool metadata, retries incomplete synthesis when necessary, and adds stronger prompt guidance so the final answer uses the returned analytical results directly. + * (Ref: `route_backend_chats.py`, `test_tabular_exhaustive_result_synthesis_fix.py`, `TABULAR_EXHAUSTIVE_RESULT_SYNTHESIS_FIX.md`) + +* **Group Workspace Documents and Prompts Load Recovery** + * Fixed a Group Workspace page-load regression where active-group initialization could fail on a missing prompt-role UI container and stop the rest of the page from rendering correctly. + * Group document and prompt content now continue loading even if the prompt permission banner or create-button container is unavailable during startup, preventing blank content areas caused by a JavaScript null-reference error. + * Added functional and UI regression coverage for the guarded prompt-role path so future changes do not reintroduce the same startup failure. + * (Ref: `group_workspaces.html`, `test_group_workspace_prompt_role_ui_guard.py`, `test_group_workspace_prompt_role_containers_ui.py`) + +* **Audio and Video Enhanced Citation Badge Consistency** + * Fixed blob-backed audio and video documents showing Standard citations in workspace details even when Enhanced Citations was enabled and the same files already opened through the enhanced citation experience on the chat page. + * Document metadata now persists and normalizes the `enhanced_citations` flag from blob-backed storage state so existing media uploads and new uploads both render the correct Enhanced badge across workspace and chat flows. + * Added regression coverage and fix documentation for the metadata normalization path. + * (Ref: `functions_documents.py`, `route_enhanced_citations.py`, `test_media_enhanced_citations_metadata_flag.py`, `MEDIA_ENHANCED_CITATION_BADGE_FIX.md`) + +#### User Interface Enhancements + +* **AI Voice Conversations Setup Guide** + * Added an in-app Setup Guide modal to the AI Voice Conversations admin card so admins can configure Azure Speech without leaving Admin Settings. + * The guide includes a live snapshot of the current Speech configuration, explains key versus managed-identity authentication, and now walks admins through enabling the required custom domain in Azure portal before verifying the endpoint on Keys and Endpoint. + * (Ref: `admin_settings.html`, `_speech_service_info.html`, `azure_speech_managed_identity_manul_setup.md`, `test_admin_multimedia_guidance.py`) + +### **(v0.241.002)** + +#### Bug Fixes + +* **Support Pages Respect Custom Application Titles** + * Fixed user-facing Support copy so Latest Features, Previous Release Features, and Send Feedback no longer fall back to the default `SimpleChat` name in customized deployments. + * Support feedback email drafts now also use the configured application title, keeping the user-facing support flow consistent with branded environments. + * (Ref: `support_menu_config.py`, `support_send_feedback.html`, `route_backend_settings.py`, support application-title personalization) + +* **Streaming Retry and Edit Thought Tracking** + * Fixed retry and edit requests in streaming chat when they fall back to the compatibility bridge and continue through the legacy `/api/chat` path. + * Assistant response tracking is now initialized for both new-message and retry/edit flows before content safety runs, preventing compatibility-mode failures caused by an uninitialized `ThoughtTracker`. + * (Ref: `route_backend_chats.py`, `ThoughtTracker`, `/api/chat/stream`, `/api/chat`, retry/edit compatibility bridge) + +* **Streaming Retry and Edit Multi-Endpoint Model Resolution** + * Fixed streaming retry and edit requests that route through the compatibility bridge so they no longer fail during AI model initialization in multi-endpoint environments. + * The compatibility path now reuses the in-app multi-endpoint GPT resolver and Foundry fallback helpers instead of depending on script-only helper functions that were not available inside the Flask runtime. + * (Ref: `route_backend_chats.py`, `/api/chat/stream`, `/api/chat`, multi-endpoint model resolution, Foundry fallback helpers) + +* **Profile Fact Memory Script Deduplication** + * Fixed a profile-page load failure where duplicate inline Fact Memory and tutorial script blocks could trigger browser parse errors such as `Identifier 'factMemorySearchInput' has already been declared`. + * Removed duplicated profile sections, modal markup, and shadowing helper definitions so Fact Memory, tutorial preferences, and retention settings now initialize from one canonical script path. + * Added source-level and UI regression coverage so duplicate profile blocks and page-load JavaScript errors are caught earlier. + * (Ref: `profile.html`, `test_profile_fact_memory_script_dedup.py`, `test_profile_fact_memory_editor.py`, profile page script initialization) + +### **(v0.241.001)** + +#### New Features + +* **Fact Memory Instructions and Facts** + * Added a clearer Fact Memory experience that distinguishes always-on Instructions from relevance-based Facts on the profile page and in chat-time recall. + * Chat responses now surface saved-memory usage more clearly through separate Instruction Memory and Fact Memory Recall thoughts and citations. + * Admin Settings Latest Features and the user-facing Support > Latest Features page now include Fact Memory guidance and screenshots, and admins can show or hide that announcement from General > User-Facing Latest Features. + * (Ref: `semantic_kernel_fact_memory_store.py`, `route_backend_chats.py`, `route_frontend_profile.py`, `profile.html`, `support_menu_config.py`, `admin_settings.html`, `latest_features.html`, fact memory guidance and latest-features coverage) + +* **Support Menu and User-Facing Latest Features** + * Added a configurable Support menu for signed-in app users so teams can expose Latest Features and Send Feedback directly in everyday navigation. + * Admins can rename the Support menu, control the internal feedback-recipient email address, and choose exactly which latest-feature cards are shared with end users from the General tab. + * The user-facing Latest Features page now mirrors the available admin screenshots more closely, includes clearer guidance about why each feature matters, and adds direct links into Chat, Personal Workspace, or Support destinations where users can try the feature. + * The Admin Settings Latest Features tab now also calls out the General-tab User-Facing Latest Features checklist so admins can see where feature sharing is configured. + * (Ref: `support_menu_config.py`, `route_frontend_support.py`, `latest_features.html`, `support_send_feedback.html`, `admin_settings.html`, `test_support_menu_user_feature.py`, support menu configuration and user-facing latest features) + +* **MultiGPT Endpoint Management** + * Added multi-endpoint model management so admins can define multiple global model endpoints and users can add personal or group-scoped endpoints when those workspace features are enabled. + * Personal Workspace and Group Workspace now surface dedicated model endpoint management cards, and agent/model selection can use combined global plus workspace endpoint lists instead of relying on a single shared deployment. + * The endpoint workflow supports Azure OpenAI and Azure AI Foundry discovery flows, including model fetch/test operations and endpoint-based Foundry agent import. + * (Ref: `route_backend_models.py`, `route_frontend_admin_settings.py`, `workspace_model_endpoints.js`, `admin_model_endpoints.js`, `workspace.html`, `group_workspaces.html`, `test_workspace_multi_endpoints.py`) + +* **Guided Chat Tutorial** + * Expanded the in-app chat tutorial into a fuller guided walkthrough of the current chat experience so new users can learn the live interface in context. + * The tutorial now walks through the main chat toolbar, workspace and scope controls, conversation search, advanced search, selection mode, bulk actions, export-related flows, and message-level actions such as retry, edit, feedback, thoughts, and citations. + * The walkthrough also includes reliability improvements for dynamic chat UI elements, including sidebar expansion, popup alignment, and tutorial-owned surfaces for steps that depend on transient menus. + * (Ref: `chat-tutorial.js`, `chats.html`, `chat-sidebar-conversations.js`, `test_chat_tutorial_selector_coverage.py`, chat tutorial walkthrough) + +* **Personal Workspace Guided Tutorial** + * Added a dedicated in-app tutorial for Personal Workspace so users can learn document, prompt, agent, action, and tag workflows directly inside the workspace page. + * The walkthrough covers uploads, search and filters, list and grid views, document details, row actions, bulk selection flows, tag management, prompt management, agent management, and action management. + * It also includes layout-aware positioning and state-restoration behavior so the overlay remains aligned while tabs, filters, menus, and collapsible sections change during the walkthrough. + * (Ref: `workspace.html`, `workspace-tutorial.js`, `test_personal_workspace_tutorial_selector_coverage.py`, `test_personal_workspace_tutorial_document_flow.py`, `test_workspace_tutorial_reposition_fix.py`, `test_workspace_tutorial_layer_order_fix.py`) + +* **Conversation Completion Notifications** + * Added personal chat completion notifications so users who leave a conversation before the assistant finishes can still see that a response is ready. + * Notification clicks deep-link back into the completed conversation, and personal conversations now show a green unread dot until the assistant response is opened. + * The unread state and notification lifecycle are wired into the chat conversation list, sidebar list, and mark-read flow so the indicator clears once the conversation is actually viewed. + * (Ref: conversation notifications, unread assistant responses, `route_backend_chats.py`, `route_backend_conversations.py`, `functions_notifications.py`, `functions_conversation_unread.py`, `chat-conversations.js`, `chat-sidebar-conversations.js`) + +* **Background Chat Completion Away From Chat Page** + * Updated streaming chat execution so assistant responses can continue running after the user leaves the chat page instead of stopping when the browser disconnects from the stream. + * This keeps final assistant persistence, unread markers, and completion notifications reachable even when users navigate into Personal, Group, or other pages while a reply is still generating. + * (Ref: background stream execution, `BackgroundStreamBridge`, `route_backend_chats.py`, `test_chat_stream_background_execution.py`, `test_streaming_only_chat_path.py`) + +* **SimpleChat Startup and Scheduler Separation** + * Added deployment guidance for local development, Azure App Service native Python startup, and container runtimes so administrators can choose between direct Gunicorn startup and optional `python app.py` handoff behavior with clear environment-variable guidance. + * Extracted the scheduler-style logging timer, approval expiration, and retention loops into a shared background task module and added a dedicated `simplechat_scheduler.py` entrypoint so scheduled work can run in a separate process or job. + * This allows the web app to use Gunicorn with `workers=2` without duplicating scheduler loops inside every worker process, while keeping a legacy override available for single-process environments. + * (Ref: `app.py`, `background_tasks.py`, `simplechat_scheduler.py`, `SIMPLECHAT_STARTUP.md`, `test_startup_scheduler_support.py`) + +* **Deployment, Setup, and Upgrade Documentation Refresh** + * Expanded the deployment guidance so teams can more quickly choose between manual deployment, Azure CLI, Bicep, Terraform, and special-environment setup paths from the main setup documentation. + * Added a dedicated upgrade guide for existing deployments that separates native Python App Service upgrades from container-based App Service upgrades, including when to use VS Code deployment, ZIP deploy, deployment slots, `azd deploy`, `azd provision`, or `azd up`. + * Clarified developer and production runtime documentation with explicit local-development guidance, Azure production startup expectations, Gunicorn startup rules, container entrypoint behavior, and scheduler-separation recommendations. + * (Ref: `setup_instructions.md`, `setup_instructions_manual.md`, `how-to/upgrade_paths.md`, `running_simplechat_azure_production.md`, `running_simplechat_locally.md`, `SIMPLECHAT_STARTUP.md`, deployment and developer documentation) + +* **Chat Completion Notifications** + * Added personal chat completion notifications so users who leave a streaming conversation before the assistant finishes now receive a notification when the AI response is ready. + * Notification clicks deep-link directly back to the completed conversation, and personal conversations now show a green unread dot in both chat conversation lists until that response is opened. + * The unread state is cleared automatically when the conversation is opened or when the user stays on the chat page through stream completion, keeping the active-view experience clean without adding heartbeat tracking. + * (Ref: `route_backend_chats.py`, `route_backend_conversations.py`, `functions_notifications.py`, `functions_conversation_unread.py`, `chat-conversations.js`, `chat-sidebar-conversations.js`, `chat-streaming.js`, `test_chat_completion_notifications.py`) + +* **Configurable Tabular Preview Blob Size Limit** + * Added an admin-configurable maximum blob size for tabular file previews, replacing the previous hardcoded limit. Default is 200 MB. + * New **Tabular Preview Limits** card in the Enhanced Citations section of Admin Settings (Citations tab) lets admins increase or decrease the limit based on their compute resources and user population. + * Setting is stored as `tabular_preview_max_blob_size_mb` and accepts values from 1 to 1024 MB. + * (Ref: `route_enhanced_citations.py`, `functions_settings.py`, `admin_settings.html`) + +* **Tabular Preview Memory Optimization** + * The `/api/enhanced_citations/tabular_preview` endpoint no longer loads entire files into a DataFrame. It now uses `nrows` limits in `pandas.read_csv`/`read_excel` to read only the rows needed for the preview, and checks blob size before downloading to reject oversized files early. + * (Ref: `route_enhanced_citations.py`) + +* **Persistent Conversation Summaries** + * Summaries generated during conversation export are now saved to the conversation document in Cosmos DB for future reuse. + * Cached summaries include `message_time_start` and `message_time_end` — when a conversation has new messages beyond the cached range, a fresh summary is generated automatically. + * The conversation details modal now shows a **Summary** card at the top. If a summary exists it displays the content, generation date, and model used. If no summary exists a **Generate Summary** button with model selector lets users create one on demand. + * A **Regenerate** button is available on existing summaries to force a refresh with the currently selected model. + * New `POST /api/conversations//summary` endpoint accepts an optional `model_deployment` and returns the generated summary. + * The `GET /api/conversations//metadata` response now includes a `summary` field. + * Extracted `generate_conversation_summary()` as a shared helper used by both the export pipeline and the new API endpoint. + * (Ref: `route_backend_conversation_export.py`, `route_backend_conversations.py`, `chat-conversation-details.js`, `functions_conversation_metadata.py`) + +* **PDF Conversation Export** + * Added PDF as a third export format option alongside JSON and Markdown, giving users a print-ready, visually styled conversation archive. + * PDF output renders chat messages with colored bubbles that mirror the live chat UI: blue for user messages, gray for assistant messages, green for file messages, and amber for system messages. + * Message content is converted from Markdown to HTML for rich formatting (bold, italic, code blocks, lists, tables) inside the PDF. + * Full appendix structure is included (metadata, message details, references, processing thoughts, supplemental messages), matching the Markdown export layout. + * Rendering uses PyMuPDF's Story API on US Letter paper with 0.5-inch margins and automatic multi-page overflow. + * Works with both single-file and ZIP packaging; intro summaries are supported in PDF as well. + * Frontend format step updated to a 3-column card grid with a new PDF card using the `bi-filetype-pdf` icon. + * (Ref: `route_backend_conversation_export.py`, `chat-export.js`, PyMuPDF Story API, conversation export workflow) + +* **Conversation Export Intro Summaries** + * Added an optional AI-generated intro summary step to the conversation export workflow, so each exported chat can begin with a short abstract before the full transcript. + * Summary model selection now reuses the same model list shown in the chat composer, keeping the export flow aligned with the main chat experience. + * Works for both JSON and Markdown exports, including ZIP exports where each conversation keeps its own summary metadata. + * (Ref: `route_backend_conversation_export.py`, `chat-export.js`, conversation export workflow) + +* **Agent & Action User Tracking (created_by / modified_by)** + * All agent and action documents (personal, group, and global) now include `created_by`, `created_at`, `modified_by`, and `modified_at` fields that track which user created or last modified the entity. + * On updates, the original `created_by` and `created_at` values are preserved while `modified_by` and `modified_at` are refreshed with the current user and timestamp. + * New optional `user_id` parameter added to `save_group_agent`, `save_global_agent`, `save_group_action`, and `save_global_action` for caller-supplied user tracking (backward-compatible, defaults to `None`). + * (Ref: `functions_personal_agents.py`, `functions_group_agents.py`, `functions_global_agents.py`, `functions_personal_actions.py`, `functions_group_actions.py`, `functions_global_actions.py`) + +* **Activity Logging for Agent & Action CRUD Operations** + * Every create, update, and delete operation on agents and actions now generates an activity log record in the `activity_logs` Cosmos DB container and Application Insights. + * Six new logging functions: `log_agent_creation`, `log_agent_update`, `log_agent_deletion`, `log_action_creation`, `log_action_update`, `log_action_deletion`. + * Activity records include: `user_id`, `activity_type`, `entity_type` (agent/action), `operation` (create/update/delete), `workspace_type` (personal/group/global), and `workspace_context` (group_id when applicable). + * Logging is fire-and-forget — failures never break the CRUD operation. + * All personal, group, and admin routes for both agents and actions are wired up. + * (Ref: `functions_activity_logging.py`, `route_backend_agents.py`, `route_backend_plugins.py`) + +* **Tabular Data Analysis — SK Mini-Agent for Normal Chat** + * Tabular files (CSV, XLSX, XLS, XLSM) detected in search results now trigger a lightweight Semantic Kernel mini-agent that pre-computes data analysis before the main LLM response. This brings the same analytical depth previously only available in full agent mode to every normal chat conversation. + * **Automatic Detection**: When AI Search results include tabular files from any workspace (personal, group, or public) or chat-uploaded documents, the system automatically identifies them via the `TABULAR_EXTENSIONS` configuration and routes the query through the SK mini-agent pipeline. + * **Unified Workspace and Chat Handling**: Tabular files are processed identically regardless of their storage location. The plugin resolves blob paths across all four container types (`user-documents`, `group-documents`, `public-documents`, `personal-chat`) with automatic fallback resolution if the primary source lookup fails. A user asking about an Excel file in their personal workspace gets the same analytical treatment as one asking about a CSV uploaded directly to a chat. + * **Six Data Analysis Functions**: The `TabularProcessingPlugin` exposes `describe_tabular_file`, `aggregate_column` (sum, mean, count, min, max, median, std, nunique, value_counts), `filter_rows` (==, !=, >, <, >=, <=, contains, startswith, endswith), `query_tabular_data` (pandas query syntax), `group_by_aggregate`, and `list_tabular_files` — all registered as Semantic Kernel functions that the mini-agent orchestrates autonomously. + * **Pre-Computed Results Injected as Context**: The mini-agent's computed analysis (exact numerical results, aggregations, filtered data) is injected into the main LLM's system context so it can present accurate, citation-backed answers without hallucinating numbers. + * **Graceful Degradation**: If the mini-agent analysis fails for any reason, the system falls back to instructing the main LLM to use the tabular processing plugin functions directly, preserving full functionality. + * **Non-Streaming and Streaming Support**: Both chat modes are supported. The mini-agent runs synchronously before the main LLM call in both paths. + * **Requires Enhanced Citations**: The tabular processing plugin depends on the blob storage client initialized by the enhanced citations system. The `enable_enhanced_citations` admin setting must be enabled for tabular data analysis to activate. + * (Ref: `run_tabular_sk_analysis()`, `TabularProcessingPlugin`, `collect_tabular_sk_citations()`, `TABULAR_EXTENSIONS`) + +* **Tabular Tool Execution Citations** + * Every tool call made by the SK mini-agent during tabular analysis is captured and surfaced as an agent citation, providing full transparency into the data analysis pipeline. + * **Automatic Capture**: The existing `@plugin_function_logger` decorator on all `TabularProcessingPlugin` functions records each invocation including function name, input parameters, returned results, execution duration, and success/failure status. + * **Citation Format**: Tool execution citations appear in the same "Agent Tool Execution" modal used by full agent mode, showing `tool_name` (e.g., `TabularProcessingPlugin.aggregate_column`), `function_arguments` (the exact parameters passed), and `function_result` (the computed data returned). + * **End-to-End Auditability**: Users can verify exactly which aggregations, filters, or queries were run against their data, what parameters were used, and what raw results were returned — before the LLM summarized them into the final response. + * (Ref: `collect_tabular_sk_citations()`, `plugin_invocation_logger.py`) + +* **Assistant Citation Artifact Storage for Large Tabular Payloads** + * Moved large raw tabular and tool citation payloads off the main assistant message document and into linked child artifact records so tool-heavy answers stay compact in primary chat storage. + * Added helper flows in `functions_message_artifacts.py` to keep a compact citation summary on the assistant message, externalize the full raw citation payload into `assistant_artifact` records with `assistant_artifact_chunk` support for larger payloads, and rehydrate those raw payloads later for exports or deeper inspection. + * Assistant messages now keep compact summaries such as tool name, reduced arguments, counts, and a few sample rows while the heavy raw citation payload is referenced through `artifact_id` and `raw_payload_externalized=True`. + * Updated chat persistence to store the linked artifact records during message save, excluded those artifact records from normal chat history and conversation views, and updated export flows to stitch the preserved raw payloads back together when needed. + * This reduced primary assistant message size, lowered the risk of hitting Cosmos DB per-item limits on large tabular responses, reduced heavy citation data carried through normal chat reads, and preserved the full raw evidence for export and debugging. + * Additional size reductions in the same phase compacted stored citation summaries, dropped noisy tabular citation arguments such as `user_id`, `conversation_id`, and `source`, and removed the duplicate `user_message` field from assistant message documents. + * (Ref: `functions_message_artifacts.py`, `route_backend_chats.py`, `route_backend_conversations.py`, `route_frontend_conversations.py`, `route_backend_conversation_export.py`, `test_assistant_citation_artifact_storage.py`, `ASSISTANT_CITATION_ARTIFACT_STORAGE_FIX.md`) + +* **SK Mini-Agent Performance Optimization** + * Reduced typical tabular analysis time from ~74 seconds to an estimated ~30-33 seconds (55-60% reduction) through three complementary optimizations. + * **DataFrame Caching**: Per-request in-memory cache eliminates redundant blob downloads. Previously, each of the ~8 tool calls in a typical analysis downloaded and parsed the same file independently. Now the file is downloaded once and subsequent calls read from cache. Cache is automatically scoped to the request (new plugin instance per analysis) and garbage-collected afterward. + * **Pre-Dispatch Schema Injection**: File schemas (columns, data types, row counts, and a 3-row preview) are pre-loaded and injected into the SK mini-agent's system prompt before execution begins. This eliminates 2 LLM round-trips that were previously spent on file discovery (`list_tabular_files`) and schema inspection (`describe_tabular_file`), allowing the model to jump directly to analysis tool calls. + * **Async Plugin Functions**: All six `@kernel_function` methods converted to `async def` using `asyncio.to_thread()`. This enables Semantic Kernel's built-in `asyncio.gather()` to truly parallelize batched tool calls (e.g., 3 simultaneous `aggregate_column` calls) instead of executing them serially on the event loop. + * **Batching Instructions**: The system prompt now instructs the model to batch multiple independent function calls in a single response, reducing LLM round-trips further. + * (Ref: `_df_cache`, `asyncio.to_thread`, pre-dispatch schema injection in `run_tabular_sk_analysis()`) + +* **SQL Test Connection Button** + * Added a "Test Connection" button to the SQL Database Configuration section (Step 3) of the action wizard, allowing users to validate database connectivity before saving. + * Supports all database types: SQL Server, Azure SQL (with managed identity), PostgreSQL, MySQL, and SQLite. + * Shows inline success/failure alerts with a 15-second timeout cap and sanitized error messages. + * New backend endpoint: `POST /api/plugins/test-sql-connection`. + * (Ref: `route_backend_plugins.py`, `plugin_modal_stepper.js`, `_plugin_modal.html`) + +* **Per-Message Export** + * Added export and action options to the three-dots dropdown menu on individual chat messages (both AI and user messages). + * **Export to Markdown**: Downloads the message as a `.md` file with a role header. Entirely client-side. + * **Export to Word**: Generates a styled `.docx` document via a new backend endpoint (`POST /api/message/export-word`). Includes Markdown-to-Word formatting (headings, bold, italic, code blocks, lists) and a citations section when present. + * **Use as Prompt**: Inserts the raw message content directly into the chat input box for reuse — no clipboard, one click and it's ready to edit and send. + * **Open in Email**: Opens the user's default email client with the message pre-filled in the subject and body via `mailto:`. + * New options appear below a divider in the dropdown, preserving existing actions (Delete, Retry, Edit, Feedback). + * (Ref: `chat-message-export.js`, `chat-messages.js`, `route_backend_conversation_export.py`, per-message export) + +* **Custom Azure Environment Support in Bicep Deployment** + * Added `custom` as a supported `cloudEnvironment` value alongside `public` and `usgovernment`, enabling deployment to sovereign or custom Azure environments via Bicep. + * New Bicep parameters for custom environments: `customBlobStorageSuffix`, `customGraphUrl`, `customIdentityUrl`, `customResourceManagerUrl`, `customCognitiveServicesScope`, and `customSearchResourceUrl`. All of these are automatically populated from `az.environment()` defaults except `customGraphUrl`, which must be explicitly provided for custom cloud environments and can be overridden as needed. + * The `cloudEnvironment` parameter now defaults intelligently based on `az.environment().name`, and legacy values (`AzureCloud`, `AzureUSGovernment`) are mapped to SimpleChat's expected values (`public`, `usgovernment`). + * Custom environment app settings (`CUSTOM_GRAPH_URL_VALUE`, `CUSTOM_IDENTITY_URL_VALUE`, `CUSTOM_RESOURCE_MANAGER_URL_VALUE`, etc.) are conditionally injected only when `azurePlatform == 'custom'`. + * Replaced hardcoded ACR domain logic and auth issuer URLs with dynamic `az.environment()` lookups for better cross-cloud compatibility. + * Fixed trailing slash handling in `AUTHORITY` URL construction in `config.py` using `rstrip('/')`. + * (Ref: `deployers/bicep/main.bicep`, `deployers/bicep/modules/appService.bicep`, `config.py`, sovereign cloud support) + +* **Redis Key Vault Authentication** + * Added a new `key_vault` authentication type for Redis, allowing the Redis access key to be retrieved securely from Azure Key Vault at runtime rather than stored directly in settings. + * Applies across all Redis usage paths: app settings cache (`app_settings_cache.py`), session management (`app.py`), and the Redis test connection flow (`route_backend_settings.py`). + * Uses `retrieve_secret_direct()` from `functions_keyvault.py` to fetch the Redis key by its Key Vault secret name. Respects `key_vault_identity` for a user-assigned managed identity on the Key Vault client. + * New admin setting fields: `redis_auth_type` (values: `key`, `managed_identity`, `key_vault`) and `redis_key` (used as the Key Vault secret name when `key_vault` auth type is selected). + * **Files Modified**: `app_settings_cache.py`, `app.py` `configure_sessions`, `route_backend_settings.py` `_test_redis_connection`, `functions_keyvault.py` `retrieve_secret_direct` + +* **Cross-Cloud Deployment Improvements** + * Updated the Azure CLI, AZD, Bicep, and Terraform deployment paths to better align with the current SimpleChat runtime configuration and reduce post-deployment manual fixes. + * Added optional Azure Video Indexer deployment support with cloud-aware defaults, including the correct endpoint and ARM API version handling for Azure Commercial, Azure Government, and registered custom clouds. + * (Ref: `deployers/azure.yaml`, `deployers/azurecli/deploy-simplechat.ps1`, `deployers/bicep/main.bicep`, `deployers/bicep/modules/videoIndexer.bicep`, `deployers/terraform/main.tf`, `application/single_app/functions_settings.py`) + +* **Idle Session Timeout Feature** + * Added a new idle timer that automatically clears the user session after a configurable set time and redirects to the main chat login page. + * Added a frontend idle warning modal that pops up after a configurable set time, but disappears if the user moves the mouse over the chat window or interacts with the app in any way. + * Default values are used if the idle logout and warning values are not set. + * Idle logout and idle warning values are validated and auto-fixed as needed. + * Added a new admin switch to enable or disable idle session timeout and warning behavior. + * Timeout and warning inputs are grouped under a toggleable section in General > System Settings. + * (Ref: `application/single_app/templates/admin_settings.html`, `application/single_app/static/js/admin/admin_settings.js`, `application/single_app/route_frontend_admin_settings.py`, `application/single_app/functions_settings.py`, `application/single_app/app.py`, `application/single_app/templates/base.html`, `application/single_app/static/js/idle-logout-warning.js`, `application/single_app/config.py`, `functional_tests/test_idle_logout_timeout.py`, `application/single_app/route_frontend_authentication.py`) + +#### User Interface Enhancements + +* **Agent Responded Thought — Seconds & Total Duration** + * The "responded" thought now shows time in **seconds** instead of milliseconds, and clarifies it is the total time from the initial user message (e.g., `'gpt-5-nano' responded (16.3s from initial message)`). + * A `request_start_time` is now captured at the top of both the non-streaming and streaming chat handlers, so the duration reflects the full request lifecycle — including content safety, hybrid search, and agent invocation — not just the model response time. + * Applies to all three agent paths: local SK agents (non-streaming), Azure AI Foundry agents, and streaming SK agents. + * (Ref: `route_backend_chats.py`, `request_start_time`, agent responded thoughts) + +* **Enhanced Agent Execution Thoughts** + * Added detailed model-level status messages during agent execution, giving users full visibility into each stage of the AI pipeline. + * **Model Identification**: A new "Sending to '{deployment_name}'" thought appears immediately after "Sending to agent", showing the exact model deployment being used (e.g., `gpt-5-nano`). + * **Generating Response**: A "Generating response..." thought now appears before the agent begins its invocation loop, matching the existing behavior for non-agent GPT calls. + * **Model Responded with Duration**: A "'{deployment_name}' responded ({duration}ms)" thought appears after the agent completes, showing total wall-clock execution time. + * Applies to all three agent paths: local SK agents (streaming and non-streaming) and Azure AI Foundry agents. + * Uses the existing `generation` step type (lightning bolt icon) — no frontend changes required. + * (Ref: `route_backend_chats.py`, `ThoughtTracker`, agent execution pipeline) + +* **List/Grid View Toggle for Agents and Actions** + * Added a list/grid view toggle to all four workspace areas: personal agents, personal actions, group agents, and group actions. + * **Grid View**: Large cards with type icon, humanized name, truncated description, and action buttons (Chat, View, Edit, Delete as applicable). + * **List View**: Improved table layout with fixed column widths (28%/47%/25%), humanized display names, and truncated descriptions with hover tooltips for full text. + * **View Button**: New eye-icon button on every agent and action that opens a read-only detail modal with gradient-header summary cards (Basic Information, Model Configuration, Instructions for agents; Basic Information, Configuration for actions). + * **Name Humanization**: Display names are now automatically parsed — underscores and camelCase/PascalCase boundaries are converted to properly spaced, title-cased words (e.g., `myCustomAgent` → `My Custom Agent`). + * **Persistent Preference**: View mode selection (list/grid) is saved per area in localStorage and restored on page load. + * New shared utility module `view-utils.js` provides reusable functions for all four workspace areas. + * (Ref: `view-utils.js`, `workspace_agents.js`, `workspace_plugins.js`, `plugin_common.js`, `group_agents.js`, `group_plugins.js`, `workspace.html`, `group_workspaces.html`, `styles.css`) + +* **Chat with Agent Button for Group Agents** + * Added a "Chat" button to each group agent row, allowing users to quickly select a group agent and navigate to the chat page. + * (Ref: `group_agents.js`, `group_workspaces.html`) + +* **Hidden Deprecated Action Types** + * Deprecated action types (`sql_schema`, `ui_test`, `queue_storage`, `blob_storage`, `embedding_model`) are now hidden from the action creation wizard type selector. Existing actions of these types remain functional. + * (Ref: `plugin_modal_stepper.js`) + +* **Advanced Settings Collapse Toggle** + * Step 4 (Advanced) content is now hidden behind a collapsible toggle button ("Show Advanced Settings") instead of being displayed by default. Reduces visual noise for most users. + * For SQL action types, the redundant additional fields UI in Step 4 is hidden entirely since all SQL configuration is already handled in Step 3. + * Step 5 (Summary) no longer shows the raw additional fields JSON dump for SQL types, since that data is already shown in the SQL Database Configuration summary card. + * (Ref: `_plugin_modal.html`, `plugin_modal_stepper.js`) + +#### Bug Fixes + +* **Chat History Citation Replay Improvements** + * Fixed follow-up prompts so prior assistant turns can reuse stored citation results, including tabular tool outputs, instead of relying only on the visible assistant message text. + * Assistant history replay now hydrates stored citation artifacts and deduplicates repeated cross-sheet tabular calls so later file results, such as Licensing workbook values, remain available to the next turn. + * History-context diagnostics remain available in message metadata and optional debug citations, while the thoughts timeline stays compact. + * (Ref: `route_backend_chats.py`, `functions_message_artifacts.py`, `chat-thoughts.js`, `chat-messages.js`, `test_chat_stream_history_context_fix.py`, `CHAT_STREAM_HISTORY_CONTEXT_FIX.md`) + +* **Document Revision Visibility and Storage Preservation** + * Fixed same-name document uploads so new revisions now inherit the previous document's editable metadata, including classification, tags, title, abstract, keywords, publication date, authors, and sharing state. + * Workspace lists and chat search now only use the current revision, while older revisions remain retained for future comparison work instead of staying active in normal workspace flows. + * Document deletion now offers a choice between deleting only the current revision or deleting all stored revisions for that document family. + * Blob storage now preserves older source files by keeping the active document at the existing alias path and archiving prior current revisions into a revision-family hierarchy before the alias path is overwritten. + * (Ref: document revision families, current-only workspace visibility, hybrid blob alias plus archived revision storage, `functions_documents.py`, `functions_search.py`, `route_enhanced_citations.py`, workspace/group/public document flows) + +* **Python Runtime Dependency Refresh and Supply-Chain Hardening** + * Continued the requirements hardening work from `v0.240.014` by tightening the main application runtime to exact package pins, reducing dependency drift across local development, CI, and Azure deployments to help mitigate supply-chain exposure. + * Upgraded the Flask runtime stack to `Flask==3.1.3` and `Werkzeug==3.1.6`, and updated the shared `Markup` import path to `markupsafe` so the app starts correctly with Flask 3's package boundary changes. + * Refreshed key runtime dependencies including `gunicorn`, `requests`, `openai`, `Markdown`, `markdown2`, `azure-ai-projects`, `azure-ai-agents`, `pyjwt`, `pypdf`, `semantic-kernel`, `protobuf`, `redis`, `pyodbc`, `PyMySQL`, `cython`, and `aiohttp` to pick up current security, compatibility, and capability improvements while keeping builds reproducible. + * (Ref: `application/single_app/requirements.txt`, `application/single_app/config.py`, `functional_tests/test_flask_markup_import_fix.py`, `docs/explanation/fixes/FLASK_31_MARKUP_IMPORT_FIX.md`) + +* **Dependency Pinning and Requirements Hardening** + * Pinned previously floating Python package requirements to exact versions across the main app, UI test, deployer, and external app requirement files to reduce unexpected dependency drift and tighten supply-chain control. + * Corrected stale external app dependency entries by replacing `dotenv` with `python-dotenv`, removing the stdlib-only `logging` package, removing an unused `Flask` requirement from the databaseseeder utility, and adding `pytest-playwright` so the UI test dependency set matches the pytest fixture usage in the test suite. + * (Ref: `application/single_app/requirements.txt`, `ui_tests/requirements.txt`, `deployers/bicep/requirements.txt`, `application/external_apps/databaseseeder/requirements.txt`, `application/external_apps/bulkloader/requirements.txt`) + +* **Settings Default Merge Persistence Fix** + * Fixed app settings merge detection in `get_settings()` where `deep_merge_dicts()` mutates the existing settings object in place, causing change detection to always evaluate as unchanged. + * Updated `deep_merge_dicts()` to return a boolean `changed` flag and wired `get_settings()` to call `upsert_item()` when `settings_changed` is `True`, so missing default keys correctly trigger persistence back to Cosmos DB. + * Added a functional regression test to validate the merge detection and persistence markers. + * (Ref: `application/single_app/functions_settings.py`, `application/single_app/config.py`, `functional_tests/test_settings_deep_merge_persistence_fix.py`) + +* **Legacy Office Binary Upload Support** + * Added native OLE-based support for older Word `.doc` and PowerPoint `.ppt` files instead of relying on OOXML-only assumptions during processing. + * Legacy `.doc` uploads now extract available metadata and follow the same shared document-processing workflow used for richer Office files, so enhanced citations and final metadata extraction stay consistent when those features are enabled. + * Legacy `.ppt` uploads now extract slide text and available summary metadata from the OLE presentation streams while keeping the same enhanced-citation and final-metadata workflow used by `.pptx` uploads. + * `.pptx` uploads now also populate presentation metadata such as title, author, subject, and keywords during the initial metadata update when metadata extraction is enabled. + * (Ref: `functions_content.py`, `functions_documents.py`, `test_legacy_doc_ole_extraction.py`, `test_legacy_ppt_ole_extraction.py`, legacy Office OLE support and metadata parity) + +* **Pillow PSD Upload Hardening** + * Updated the application to use `pillow==12.1.1`, moving the app off the vulnerable Pillow range for specially crafted PSD image parsing. + * Hardened admin logo and favicon uploads so Pillow now only opens the PNG and JPEG formats already allowed by the route, preventing disguised PSD content from being decoded during upload processing. + * (Ref: `application/single_app/requirements.txt`, `application/single_app/route_frontend_admin_settings.py`, `functional_tests/test_pillow_psd_upload_hardening.py`) + +* **Changed-Files GitHub Action Supply Chain Remediation** + * Updated the release-notes pull request workflow to use the patched `tj-actions/changed-files@v46.0.1` release after the March 2025 supply chain compromise affecting older tag families. + * Added a functional regression check to ensure the workflow does not drift back to the known malicious commit or an older vulnerable action reference. + * (Ref: `release-notes-check.yml`, `test_changed_files_action_version.py`, GitHub Actions workflow security, CI dependency pinning) + +* **Personal Conversation Notification Scope Detection** + * Fixed a scope-detection bug where personal chat completions could save successfully without creating a completion notification or unread dot when unrelated active workspace state was still present in session. + * Personal completion-side effects are now determined from the saved conversation type instead of active workspace session values. + * (Ref: personal chat scope gating, `route_backend_chats.py`, `test_chat_completion_notifications.py`) + +* **Distributed Background Task Locks** + * Added Cosmos-backed distributed lock documents for approval expiry and retention policy background jobs so duplicate execution is reduced across multiple Gunicorn workers and App Service instances. + * Kept the current web-app-hosted scheduler model intact so teams can continue running these jobs from the existing App Service while improving cross-worker coordination. + * Updated the startup documentation and added functional validation for the distributed lock wiring. + * (Ref: `background_tasks.py`, `SIMPLECHAT_STARTUP.md`, `test_background_task_distributed_locks.py`, `test_startup_scheduler_support.py`) + +* **Background Task Default-On Gating** + * Updated the web runtime background task gate so scheduler loops now start by default even when `SIMPLECHAT_RUN_BACKGROUND_TASKS` is unset. + * Only explicit false-like values such as `0`, `false`, `no`, or `off` now disable the background loops, which matches the requested deployment behavior. + * Updated the startup guide and Gunicorn runtime validation test to reflect the new default-on behavior. + * (Ref: `app.py`, `SIMPLECHAT_STARTUP.md`, `test_gunicorn_startup_support.py`) + +* **Gunicorn Production Startup Support** + * Updated the app bootstrap so production deployments can run cleanly under Gunicorn instead of relying on Flask's built-in server, which is a poor fit for long-lived streaming chat requests on App Service. + * Added a shared Gunicorn config, switched the container entrypoint to Gunicorn, and made application initialization idempotent so startup logic can run safely in multi-worker web processes. + * Background timer and retention loops are now disabled by default under Gunicorn workers to avoid duplicating scheduler-style threads across workers, while local debug startup continues to use the Flask development server. + * (Ref: `app.py`, `gunicorn.conf.py`, `Dockerfile`, `test_gunicorn_startup_support.py`) + +* **Streaming-Only Chat Path** + * Updated the first-party chat experience so normal sends, retries, and message edits now use the streaming chat path instead of maintaining a separate non-streaming UI path. + * Preserved parity-sensitive behavior by extending the streaming flow to finalize image-generation responses correctly and by adding a backend compatibility bridge for retry, edit, and image-generation requests while the legacy `/api/chat` route remains in transition. + * Removed the chat-page streaming toggle, updated the UI to treat streaming as required behavior, and added regression coverage to prevent first-party chat modules from drifting back to direct `/api/chat` calls. + * (Ref: `route_backend_chats.py`, `chat-messages.js`, `chat-streaming.js`, `chat-retry.js`, `chat-edit.js`, `chats.html`, `test_streaming_only_chat_path.py`) + +* **Embedding Retry-After Wait Time Handling** + * Fixed embedding retries so `429 Too Many Requests` responses now honor server-provided wait times from `Retry-After` style headers instead of always using local backoff timing. + * This reduces avoidable repeat throttling during document processing, batched embedding generation, and search embedding requests when Azure OpenAI asks the client to wait. + * The existing exponential backoff behavior remains in place as a fallback when the service does not provide a usable retry delay. + * (Ref: `functions_content.py`, embedding retry logic, `test_embedding_rate_limit_wait_time.py`) + +* **SQL Plugin Key Vault Secret Storage** + * New and updated SQL Query and SQL Schema actions now store sensitive values such as connection strings and passwords in Azure Key Vault when Key Vault secret storage is enabled. + * Editing an existing SQL action now preserves stored Key Vault-backed credentials, including the SQL test connection flow, so users do not need to re-enter unchanged secrets just to validate or save the action. + * Personal, group, and global action flows now preserve existing secret references during updates, clean them up correctly on delete, and redact secret-bearing plugin values from logs. + * Existing plaintext SQL action credentials are not backfilled automatically; they move to Key Vault the next time the action is saved while Key Vault storage is enabled. + * (Ref: `functions_keyvault.py`, `route_backend_plugins.py`, `plugin_modal_stepper.js`, `workspace_plugins.js`, SQL action configuration) + +* **Group/Public Expanded Document Tags** + * Fixed group and public workspace list views so expanding a document now shows its tags, matching the personal workspace experience. + * The fix adds color-coded tag badges with a `No tags` fallback in expanded document details without changing the existing backend document APIs. + * (Ref: `group_workspaces.html`, `public_workspace.js`, expanded document details, workspace tag rendering) + +* **Agent Save Validation for Round-Tripped Metadata** + * Fixed agent saves failing when an existing personal, group, or global agent was edited and the browser sent back backend-managed audit fields such as `created_at`, `created_by`, `modified_at`, and `modified_by`. + * Agent payload sanitization now strips backend-managed audit and Cosmos metadata before schema validation, while preserving server-side tracking during persistence. + * (Ref: `functions_agent_payload.py`, `route_backend_agents.py`, agent schema validation, functional test coverage) + +* **Live Tool Invocation Thoughts During Streaming** + * Updated plugin thought handling so the chat can surface an immediate `Invoking Plugin.Function` thought as soon as a tool starts, instead of waiting until the tool completes. + * Streaming chat now polls pending thoughts while the response is still in flight, allowing the active status badge to switch from model-sending text to the currently executing plugin call during long-running tools such as `WaitPlugin.wait`. + * Completed plugin thoughts still include the richer human-readable summaries for wait, math, and generic plugin executions, and broader plugin coverage remains enabled through auto-wrapping for manifest-loaded plugins. + * (Ref: `plugin_invocation_logger.py`, `plugin_invocation_thoughts.py`, `chat-thoughts.js`, `chat-streaming.js`, `logged_plugin_loader.py`, `test_logged_core_plugins.py`) + +* **Multi-Sheet Workbook Tabular Analysis** + * Fixed multi-sheet Excel workbooks being analyzed from the wrong worksheet during tabular chat responses. Questions that clearly target a specific tab, such as asset values in a workbook with `Assets`, `Balance`, and `Income` sheets, no longer silently default to the first sheet. + * Tabular runtime analysis now requires explicit `sheet_name` or `sheet_index` selection for analytical calls on multi-sheet workbooks, and the SK mini-agent preload now includes workbook sheet inventory and per-sheet schemas so the model can choose the correct worksheet before computing results. + * Enhanced citations and tabular previews now preserve worksheet context, using `Sheet: ` for sheet-specific references and `Location: Workbook Schema` for workbook-level schema citations instead of generic `Page 1` labels. The tabular preview modal also supports switching between workbook sheets. + * (Ref: `tabular_processing_plugin.py`, `route_backend_chats.py`, `route_enhanced_citations.py`, `chat-enhanced-citations.js`, `chat-citations.js`, `chat-messages.js`) + +* **Tabular Citation Conversation Ownership Check** + * Fixed an IDOR vulnerability on `/api/enhanced_citations/tabular` where any authenticated user who could guess a `conversation_id` and `file_id` could download another user's chat-uploaded tabular files. + * The endpoint now reads the conversation document from Cosmos DB and verifies that `conversation.user_id` matches the current user before serving the blob. Returns 403 Forbidden on mismatch and 404 if the conversation does not exist. + * (Ref: `route_enhanced_citations.py`, `cosmos_conversations_container`) + +* **Tabular Preview `max_rows` Parameter Validation** + * The `max_rows` query parameter on `/api/enhanced_citations/tabular_preview` was parsed with bare `int()`, causing a 500 error on non-integer input. Switched to Flask's `request.args.get(..., type=int)` which silently falls back to the default on invalid input, matching the pattern used by other endpoints. + * (Ref: `route_enhanced_citations.py`) + +* **Streaming Chat Post-Finalization JSON Sanitization** + * Fixed a repeatable late-stream failure where assistant responses could appear nearly complete and then end with a `Stream interrupted` warning during final persistence. + * Normalized non-finite numeric values from citation payloads before assistant messages, assistant artifacts, and terminal chat payloads are written, preventing Cosmos DB from rejecting invalid JSON. + * This improves reliability for streaming chat, compatibility streaming, and the standard JSON response path when tool or search citations include sparse or tabular numeric values. + * (Ref: `functions_message_artifacts.py`, `route_backend_chats.py`, `test_chat_post_stream_json_sanitization.py`, post-stream citation sanitization) + +* **On-Demand Summary Generation — Content Normalization Fix** + * Fixed the `POST /api/conversations//summary` endpoint failing with an error when generating summaries from the conversation details modal. + * Root cause: message `content` in Cosmos DB can be a list of content parts (e.g., `[{type: "text", text: "..."}]`) rather than a plain string. The endpoint was passing the raw list as `content_text`, which either stringified incorrectly or produced empty transcript text. + * Now uses `_normalize_content()` to properly flatten list/dict content into plain text, matching the export pipeline's behavior. + * (Ref: `route_backend_conversations.py`, `_normalize_content`, `generate_conversation_summary`) + +* **Export Summary Reasoning-Model Compatibility** + * Fixed export intro summary generation failing or returning empty content with reasoning-series models (gpt-5, o1, o3) through a series of incremental fixes: using `developer` role instead of `system` for instruction messages, removing all `max_tokens` / `max_completion_tokens` caps so the model decides output length naturally, and adding null-safe content extraction for `None` responses. + * Summary now includes ALL messages (user, assistant, system, file, image analysis) for full context, with a simplified prompt producing 1-2 factual paragraphs. + * Added detailed debug logging showing message count, character count, model name, role, and finish reason. + * (Ref: `route_backend_conversation_export.py`, `_build_summary_intro`, `generate_conversation_summary`) + +* **Conversation Export Schema and Markdown Refresh** + * Fixed conversation exports lagging behind the live chat schema. JSON exports now include processing thoughts, normalized citations, and the raw document/web/tool citation buckets stored with assistant messages. + * Fixed Markdown exports being too flat and text-heavy by reorganizing them into a transcript-first layout with appendices for metadata, message details, references, thoughts, and supplemental records. + * Fixed exported conversations including content that no longer matched the visible chat by filtering deleted messages and inactive-thread retries, then reapplying thread-aware ordering before export. + * (Ref: `route_backend_conversation_export.py`, `test_conversation_export.py`, conversation export rendering) + +* **Export Tag/Classification Rendering Fix** + * Fixed conversation tags and classifications rendering as raw Python dicts (e.g., `{'category': 'model', 'value': 'gpt-5'}`) in both Markdown and PDF exports. + * Tags now display as readable `category: value` strings, with smart handling for participant names, document titles, and generic category/value pairs. + * (Ref: `route_backend_conversation_export.py`, `_format_tag` helper, Markdown/PDF metadata rendering) + +* **Export Summary Error Visibility** + * Added `debug_print` and `log_event` logging to all summary generation error paths, including the empty-response path that previously failed silently. + * The actual error detail is now shown in both Markdown and PDF exports when summary generation fails, replacing the generic "could not be generated" message. + * (Ref: `route_backend_conversation_export.py`, `_build_summary_intro`, export error rendering) + +* **Content Safety for Streaming Chat Path** + * Added full Azure AI Content Safety checking to the streaming (`/api/chat/stream`) SSE path, matching the existing non-streaming (`/api/chat`) implementation. + * Previously, only the non-streaming path performed content safety analysis; streaming conversations bypassed safety checks entirely. + * Implementation includes: `AnalyzeTextOptions` analysis, severity threshold checking (severity ≥ 4 blocks the message), blocklist matching, persistence of blocked messages to `cosmos_safety_container`, creation of safety-role message documents, and proper SSE event delivery of blocked status to the client. + * On block, the streaming generator yields the safety message and `[DONE]` event, then stops — preventing any further LLM invocation. + * Errors in the content safety call are caught and logged without breaking the chat flow, consistent with the non-streaming behavior. + * (Ref: `route_backend_chats.py`, streaming SSE generator, `AnalyzeTextOptions`, `cosmos_safety_container`) + +* **SQL Schema Plugin — Eliminate Redundant Schema Calls** + * Fixed agent calling `get_database_schema` twice per query even though the full schema was already injected into the agent's instructions at load time. + * Root cause: The `@kernel_function` descriptions in `sql_schema_plugin.py` said "ALWAYS call this function FIRST," which overrode the schema context already available in the instructions. + * Updated all four function descriptions (`get_database_schema`, `get_table_schema`, `get_table_list`, `get_relationships`) to use the resilient pattern: "If the database schema is already provided in your instructions, use that directly and do NOT call this function." + * This eliminates ~400ms+ of unnecessary database round trips per query and aligns with the same pattern already used in `sql_query_plugin.py`. + * (Ref: `sql_schema_plugin.py`, `@kernel_function` descriptions, schema injection) + +* **SQL Schema Plugin — Empty Tables from INFORMATION_SCHEMA** + * Fixed `get_database_schema` returning `'tables': {}` (empty) despite the database having tables, while relationships were returned correctly. + * Root cause: SQL Server table/column enumeration used `INFORMATION_SCHEMA.TABLES` and `INFORMATION_SCHEMA.COLUMNS` views, which returned empty results in the Azure SQL environment. Meanwhile, the relationships query used `sys.foreign_keys`/`sys.tables`/`sys.columns` catalog views which worked perfectly. + * Migrated all SQL Server schema queries to use `sys.*` catalog views consistently: `sys.tables`/`sys.schemas` for table enumeration, `sys.columns` with `TYPE_NAME()` for column details, and `sys.indexes`/`sys.index_columns` for primary key detection. + * Fixed `pyodbc.Row` handling throughout the plugin — removed all `isinstance(table, tuple)` checks that could fail with pyodbc Row objects, replaced with robust try/except indexing. + * This enables the full schema (tables, columns, types, PKs, FKs) to be injected into agent instructions, allowing agents to construct complex multi-table JOINs for analytical queries. + * (Ref: `sql_schema_plugin.py`, `sys.tables`, `sys.columns`, `sys.indexes`, pyodbc.Row handling) + +* **SQL Query Plugin — Auto-Create Companion Schema Plugin** + * Fixed the remaining issue where SQL-connected agents still asked for clarification instead of querying the database, even after description improvements. + * Root cause: Agents configured with only a `sql_query` action never had a `SQLSchemaPlugin` loaded in the kernel. The descriptions demanded calling `get_database_schema` — a function that didn't exist — creating an impossible dependency that caused the LLM to ask for clarification. + * `LoggedPluginLoader` now automatically creates a companion `SQLSchemaPlugin` whenever a `SQLQueryPlugin` is loaded, using the same connection details. This ensures schema discovery is always available. + * Updated `@kernel_function` descriptions to be resilient: "If the database schema is provided in your instructions, use it directly. Otherwise, call get_database_schema." This dual-path approach works whether schema is injected via instructions or available via plugin functions. + * Added fallback in `_extract_sql_schema_for_instructions()` to also detect `SQLQueryPlugin` instances and create a temporary schema extractor if no `SQLSchemaPlugin` is found. + * (Ref: `logged_plugin_loader.py`, `sql_query_plugin.py`, `semantic_kernel_loader.py`) + +* **SQL Query Plugin Schema Awareness** + * Fixed agents connected to SQL databases asking users for clarification about table/column names instead of querying the database directly. + * Root cause: SQL Query and SQL Schema plugin `@kernel_function` descriptions were generic with no workflow guidance, agent instructions had no database schema context, and the two plugins operated independently with no linkage. + * Rewrote all `@kernel_function` descriptions in both SQL plugins to be prescriptive workflow guides (modeled after the working LogAnalyticsPlugin), explicitly instructing the LLM to discover schema first before generating queries. + * Added auto-injection of database schema into agent instructions at load time — when SQL Schema plugins are detected, the full schema (tables, columns, types, relationships) is fetched and appended to the agent's system prompt. + * Added new `query_database(question, query)` convenience function to `SQLQueryPlugin` for intent-aligned tool calling. + * Enabled the SQL-specific plugin creation path in `logged_plugin_loader.py` (was previously commented out). + * (Ref: `sql_query_plugin.py`, `sql_schema_plugin.py`, `semantic_kernel_loader.py`, `logged_plugin_loader.py`) + +* **Chat-Uploaded Tabular Files Now Trigger SK Mini-Agent in Model-Only Mode** + * Fixed an issue where tabular files (CSV, XLSX, XLS, XLSM) uploaded directly to a chat conversation were not analyzed by the SK mini-agent when no agent was selected. The model would describe what analysis it would perform instead of returning actual computed results. + * **Root Cause**: The mini SK agent only triggered from search results, but chat-uploaded files are stored in blob storage and not indexed in Azure AI Search. Additionally, the streaming path completely ignored `file` role messages in conversation history. + * **Fix**: Both streaming and non-streaming chat paths now detect chat-uploaded tabular files during conversation history building and trigger `run_tabular_sk_analysis(source_hint="chat")` to pre-compute results. The streaming path also now properly handles `file` role messages (tabular and non-tabular) matching the non-streaming path's behavior. + * (Ref: `route_backend_chats.py`, `run_tabular_sk_analysis()`, `collect_tabular_sk_citations()`) + +* **Group SQL Action/Plugin Save Failure** + * Fixed group SQL actions (sql_query and sql_schema types) failing to save correctly due to missing endpoint placeholder. Group routes now apply the same `sql://sql_query` / `sql://sql_schema` endpoint logic as personal action routes. + * Fixed Step 4 (Advanced) dynamic fields overwriting Step 3 (Configuration) SQL values with empty strings during form data collection. SQL types now skip the dynamic field merge entirely since Step 3 already provides all necessary configuration. + * Fixed auth type definition schemas (`sql_query.definition.json`, `sql_schema.definition.json`) only allowing `connection_string` auth type, blocking `user`, `identity`, and `servicePrincipal` types that the UI and runtime support. + * Fixed `__Secret` key suffix mismatch in additional settings schemas where `connection_string__Secret` and `password__Secret` didn't match the runtime's expected `connection_string` and `password` field names. Also removed duplicate `azuresql` enum value. + * (Ref: `route_backend_plugins.py`, `plugin_modal_stepper.js`, `sql_query.definition.json`, `sql_schema.definition.json`, `sql_query_plugin.additional_settings.schema.json`, `sql_schema_plugin.additional_settings.schema.json`) + +* **Workspace Model Endpoint Scope Gate Enforcement** + * Fixed personal and group workspace model discovery and model test routes so they now enforce the same custom-endpoint feature gates as the corresponding endpoint management routes. + * Restored the intended endpoint modal workflow so users can still fetch and test models before saving a new personal or group endpoint when those scope features are enabled. + * Requests that reference a saved endpoint now resolve against the caller's authorized persisted endpoint configuration instead of allowing raw request payloads to override stored settings. + * (Ref: `route_backend_models.py`, `workspace_model_endpoints.js`, `test_model_endpoint_scope_gate_enforcement.py`, model endpoint scope gating) + +* **Workspace Agent View Consistency** + * Fixed personal and group workspace agent lists so table-view actions now use the same button order, making agent management behavior more predictable across both workspaces. + * Fixed group workspace agent grid cards so editable group agents once again show Edit and Delete actions when the current user has permission to manage them. + * Fixed personal workspace agent table layout so action buttons stay inside the table instead of overflowing past the Actions column. + * (Ref: `workspace.html`, `workspace_agents.js`, `group_agents.js`, `view-utils.js`, `test_workspace_agent_views_consistency.py`) + +* **MultiGPT Endpoint Key Vault Secret Storage and Foundry Fetch Reliability** + * MultiGPT endpoint secrets such as API keys and service principal client secrets now move into Azure Key Vault when Key Vault secret storage is enabled, instead of remaining in saved endpoint payloads. + * Endpoint fetch, test, Foundry listing, and runtime execution now resolve stored secrets server-side by endpoint ID, so reopening an endpoint no longer depends on the browser still holding plaintext credentials. + * Fixed a follow-up regression in Foundry model discovery where sync fetch routes could fail with `'coroutine' object has no attribute 'token'` because async credentials were being reused in a synchronous token acquisition path. + * (Ref: `functions_keyvault.py`, `functions_settings.py`, `route_backend_models.py`, `route_frontend_admin_settings.py`, `semantic_kernel_loader.py`, `foundry_agent_runtime.py`, `admin_model_endpoints.js`, `workspace_model_endpoints.js`, `test_model_endpoints_key_vault_secret_storage.py`, `test_foundry_model_fetch_sync_credentials.py`) diff --git a/docs/explanation/release-notes/v0.250.md b/docs/explanation/release-notes/v0.250.md new file mode 100644 index 000000000..a68bd0502 --- /dev/null +++ b/docs/explanation/release-notes/v0.250.md @@ -0,0 +1,2558 @@ +--- +title: "Release notes 0.250 series" +description: "SimpleChat release notes for 0.250.231 – 0.250.001." +section: "Reference" +layout: page +--- + + + +# Release notes 0.250 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.250.231)** + +#### Bug Fixes + +* **Missing Release Highlight Screenshots Now Display** + * The Latest Release pages referenced 24 screenshots that were never present in the documentation site, so every one of them rendered as a broken image. + * The images already existed in the application at `application/single_app/static/images/features/`, where the in-app Latest Features gallery reads them. They are now also published with the documentation site, so the release highlight pages show the same screenshots users see in the product. + * (Ref: `docs/images/latest-release/`, `docs/_data/latest_release_features.yml`, Latest Release highlight pages) + +* **Broken Documentation Links Repaired** + * Fixed the remaining broken internal links on the documentation site. Links that pointed at renamed pages now resolve, and links that target files kept in the repository rather than published on the site, such as the Custom Pages developer guide, the Teams app manifest, and a CI workflow, now open on GitHub instead of returning a missing page. + * Removed two references to a ServiceNow multi-action setup guide that was never written. + * The documentation site now has zero broken internal links across 31,649 checked links. + * (Ref: `ui_tests/check_docs_links.js`, ServiceNow guides, Custom Pages guide, upgrade paths guide) + +### **(v0.250.230)** + +#### New Features + +* **Documentation Site Redesign** + * The documentation site was rebuilt for search, navigation, page simplicity, mobile support, and content coverage. + * Search now indexes page content instead of titles only. Previously 84% of the 986 indexed pages were internal engineering notes, 88% of entries had no description, and no page body text was indexed at all, so a search for "agent" returned mostly internal fix notes. The index is now 165 entries with a description on every one and no engineering notes. + * Added a dedicated search results page with section filters and highlighted excerpts, a `Ctrl+K` shortcut, keyboard navigation, and a full-screen mobile search sheet. Search was previously hidden entirely on phones. + * Navigation was rebuilt so the top bar and sidebar expose the same six sections: Start, Guides, Features, Administration, Deploy and operate, and Reference. Coverage went from 27 links to 74, all verified to resolve. + * (Ref: `docs/search-index.json`, `docs/assets/js/search.js`, `docs/_config.yml` navigation, `docs/search.md`) + +* **Screenshot and Video Placeholders for Documentation** + * Documentation pages can now declare a screenshot or video slot. When the asset does not exist yet the page renders a visible card naming the exact file path to create; adding the file at that path replaces the placeholder automatically on the next build with no configuration or code change. + * Videos render as a local poster card that links out to YouTube or Microsoft Stream, so no video files are committed to the repository and no third-party embed scripts are loaded. + * Added a media status page listing every slot and whether it is filled, as a capture worklist for contributors. + * (Ref: `docs/_includes/media.html`, `docs/_data/media.yml`, `docs/contributing/media-status.md`) + +* **Complete Documentation Coverage of the Application** + * Added one page per admin settings tab covering what the tab controls, why it matters, every setting with its default and governing settings key, prerequisites, and the common tasks admins perform there. + * Added task guides for creating actions, agents, agents with actions, multi-task workflows, triggering workflows, file sync connectors, tags, tags in chat, tags on conversations, and exporting conversations, plus further guides derived from the application surface. Each guide explains what the task does and why before the steps. + * Added a chat interface reference covering all 47 chat controls and an action reference covering all 27 actions. + * Added a feature catalog in which every one of the 111 capability toggles is claimed by exactly one capability entry. + * (Ref: `docs/admin/`, `docs/guides/`, `docs/reference/chat-controls.md`, `docs/reference/actions/`, `docs/_data/features.yml`) + +* **Documentation Coverage Enforcement** + * Added a generated inventory of the application surface and functional tests that fail when a new capability toggle, admin settings tab, action plugin, or chat control ships without documentation, so coverage stays complete as changes land. + * (Ref: `scripts/build_docs_inventory.py`, `functional_tests/test_docs_app_surface_coverage.py`, `functional_tests/test_docs_site_quality.py`) + +#### User Interface Enhancements + +* **Documentation Site Works on Phones and Tablets** + * Standardized the responsive breakpoints, which previously mixed `768px` and `767.98px` and left gaps, and exported the desktop breakpoint to JavaScript so it is no longer duplicated by hand. + * Wide tables and long code blocks are now contained in horizontal scroll regions instead of widening the page, images are lazy-loaded with intrinsic sizing, touch targets meet a 44px minimum, and the mobile navigation drawer and search sheet trap and restore focus. + * Verified with browser tests at 360x640, 390x844, 768x1024, 1280x800, and 1920x1080. + * (Ref: `docs/assets/css/main.scss`, `docs/assets/js/sidebar.js`, `ui_tests/test_docs_site_responsive.js`) + +* **Simpler Documentation Pages** + * Landing pages were rewritten from hand-written HTML card markup into plain markdown. The home page previously had 82 blocks of card markup and zero markdown headings, and the features page 119 blocks and zero headings, which meant neither page had a working "On this page" table of contents or heading anchors. + * The FAQ was rebuilt so every question is its own heading with a linkable anchor. + * The decorative page hero, with its gradient banner, pill row, and icon orb, was replaced with a plain documentation header across the 38 pages that used it. + * Split the 452 KB release notes page into per-version-series pages while keeping the existing release notes URL working. + * (Ref: `docs/index.md`, `docs/features.md`, `docs/start/faqs.md`, `scripts/build_release_notes_pages.py`) + +* **Documentation URLs Now Match Their Section** + * Guides previously lived under three different URL spaces that all meant the same thing. Tutorials and how-to guides are consolidated under `/guides/`, orientation pages moved under `/start/`, deployment scenarios under `/deploy/`, and reference pages under `/reference/`. + * **Existing links and bookmarks continue to work.** Every moved page redirects from its old URL, and the URLs the application itself links to were deliberately left unchanged. + * (Ref: documentation navigation, `jekyll-redirect-from`, `ui_tests/check_docs_links.js`) + +#### Bug Fixes + +* **Documentation Site No Longer Overflows Horizontally on Desktop** + * The main content region combined a full-width rule with a sidebar offset, so every desktop viewport scrolled sideways by exactly the sidebar width. This was a long-standing defect on the published site. + * (Ref: `.docs-main-content`, `docs/assets/css/main.scss`) + +* **Documentation Section Labels and Page Titles** + * Path-scoped Jekyll defaults used collection names as their type and therefore never applied, so nearly every page fell back to a generic "Docs" section and search facets were meaningless. Three scenario index pages also had a comment above their front matter, so it was never parsed and they were titled with their own file path and rendered through an empty layout. + * (Ref: `docs/_config.yml` defaults, `docs/explanation/scenarios/`) + +* **Documentation Site Loads No Third-Party Assets** + * Removed jQuery, DataTables, marked, DOMPurify, and split.js, none of which the site used, and vendored Bootstrap, Bootstrap Icons, Prism, Lunr, and the site fonts locally with their licenses. The site now makes zero external requests. + * (Ref: `docs/assets/vendor/`, `docs/_layouts/default.html`, local browser asset policy) + +### **(v0.250.229)** + +#### Bug Fixes + +* **Admin Latest Features Previous and Archive Preview Restored** + * The read-only user-facing preview panel in Admin Settings > Latest Features never rendered, because it sat inside a disabled template block that also holds the legacy hardcoded feature cards from before the tab became data-driven. + * The admin route was already computing and passing `support_latest_feature_release_groups_preview` for a panel that could never display, so admins had no way to review previous and archive release cards alongside their sharing status. + * Closed the disabled block after the legacy cards so the preview panel renders again, and namespaced its element ids to avoid colliding with the admin release-group cards. + * (Ref: `admin_settings.html` Latest Features tab, `support_latest_feature_release_groups_preview`, `route_frontend_admin_settings.py`) +* **Latest Features Sidebar Card Id Special Case Removed** + * The admin sidebar built the previous-release section link through a redundant conditional that produced the same id as the general dynamic expression. + * Simplified to the dynamic form so every non-current tier, including the new archive tier, is handled the same way. + * (Ref: `_sidebar_nav.html`, admin Latest Features navigation) +* **Orphaned Latest Features Metadata Tables Removed** + * Removed `_SUPPORT_CURRENT_FEATURE_IMAGE_METADATA` and `_SUPPORT_CURRENT_FEATURE_USER_METADATA` along with the two helpers that consumed them. Their keys matched no feature id in any release tier, so both helpers were no-ops, and the `_CURRENT_` naming became misleading after the release tiers shifted. + * Verified behavior-neutral: the full serialized catalog output across every accessor and several settings permutations is byte-identical before and after removal. + * Added a regression test that generically detects orphaned per-feature metadata tables, so this class of drift is caught in future rather than only these two names. + * (Ref: `support_menu_config.py`, `test_support_menu_config_dead_metadata_removal.py`) +* **Citations and Source Rendering** + * Citation parsing preserves line breaks after inline citations, agent document search results render as document sources, retrieved and cited sources remain separated, prior grounded references resolve in follow-up turns, and source-reading intent is no longer misclassified as artifact generation. + * (Ref: citation parser, agent document search, grounded source references, generated artifacts) +* **Shared Conversations and Conversation Forks** + * Shared conversations load, render messages, generate AI responses, refresh uploads/task documents, and survive Blueprint security hardening. + * Forking from group/public workspace knowledge no longer returns HTTP 500, and structured logging normalization prevents logger errors from replacing HTTP responses. + * (Ref: shared conversations, streaming bridge, conversation fork, structured logging) +* **Workflow Execution Reliability** + * File Sync summaries reach task-based workflow models, task instructions scope document search queries, run history preserves per-task document status, zero-retry settings persist, and invalid task document actions are contained. + * (Ref: workflow execution, task document status, retries, File Sync summaries) +* **Backup, Restore, and Data Management Reliability** + * Backup ETag normalization, Cosmos pagination, checkpoint batching, and provider status diagnostics improve backup completeness and troubleshooting. + * (Ref: Data Management backup, Backup Inventory, Cosmos pagination, checkpoint batching) +* **Tabular Analyze/Search Durability** + * Exhaustive Markdown output includes all rows, line-phrased requests route to the durable pipeline, artifact lifecycle finishes before run completion, stale settings migrations correct disabled preflight flags, and `filter_rows contains` semantics match across foreground and durable paths. + * (Ref: tabular analyze, durable preflight, generated artifacts, filter_rows) +* **Document Extraction and Grounded Search** + * Legacy DOC/PPT embedded images are analyzed, image chunks merge into the correct surrounding-text chunk, document picker search matches file names, and multi-word grounded queries support punctuation word breaks. + * (Ref: embedded images, chunk placement, document picker search, grounded search) +* **Cosmos, Redis, and Cache Performance** + * Settings container idle RU usage is reduced, no-op read invalidation is skipped, Docker multi-worker startup conflicts recover, DAI Redis TTLs are bounded/refreshed, cache invalidation fails closed on unknown safety state, and cache parity improves for pending shared documents, legacy revisions, and generated-artifact identity. + * (Ref: Cosmos RU usage, conversation cache invalidation, DAI Redis cache, public workspace artifacts) +* **Authentication and Security** + * Credential-like field names are no longer logged in clear text, action secret references are scoped to their owning action, Terms of Use redirects happen server-side with HTTPS enforcement, and `/getAToken` without an authorization code redirects to sign-in. + * (Ref: credential redaction, action secrets, Terms of Use, authentication redirects) +* **Model Endpoints and MCP Outbound** + * Managed identity cloud values are normalized, vision test connection uses the correct multi-endpoint target, GPT 5.6+ models appear in the multi-modal vision selector, and MCP tool arguments no longer get wrapped in incompatible `kwargs` payloads. + * (Ref: model endpoints, vision selector, managed identity, MCP outbound) +* **Governance, Navigation, and Admin Settings** + * Retargeting policies no longer creates duplicates, block-list modals stack correctly, Group Workflows appears in the sidebar, hidden tabs hide sidebar links, logo/favicon save paths no longer 500, and update banner version comparison no longer shows stale releases as newer. + * (Ref: governance policies, workspace sidebar, admin settings, update banner) +* **Retention, Notifications, Logging, and Public Workspace Edge Cases** + * Group and collaboration conversations now use correct retention policy/activity timestamps, unauthenticated pages avoid notification polling 401 noise, Application Insights events carry sanitized diagnostic values with standardized tags, custom Databricks-prefixed plugin discovery avoids built-in defaults, and hidden public workspace documents can ground chat searches. + * (Ref: retention policy, notification polling, Application Insights logging, plugin discovery, public workspace search) + +#### Detailed Change Log (v0.250.003 - v0.250.229) + +The individual patch builds consolidated into v0.260.001 are preserved below for traceability. + +##### **(v0.250.229)** + +###### Bug Fixes + +* **Citations No Longer Eat the Line Break After Them** + * Text that came after an inline document citation was jammed onto the end of the closing parenthesis instead of starting a new paragraph — you would see `(Source: uploading_documents.md, Page: 1)Thank you, Paul.` with no break at all. + * The citation parser matched the `[#citation-id]` marker along with the whitespace that followed it, then rebuilt the citation without putting that whitespace back. Because this runs on the raw markdown before it is rendered, a deleted blank line did not just remove a space — it changed how the rest of the block was read, so a paragraph after a cited list item got absorbed into the list item itself. + * Spacing is now restored exactly as the model wrote it. Paragraphs, bullets, and numbered lists after a citation render in their intended structure, a citation followed by more text on the same line keeps its space, and back-to-back citations stop colliding. Copied and exported message text keeps its line breaks for the same reason. + * The cleanup pass for leftover citation markers had the same flaw in reverse and could swallow the blank line *before* a stray marker. It now only removes horizontal spacing, or the marker's whole line when it sits on one. + * (Ref: [#1289](https://github.com/microsoft/simplechat/issues/1289), `chat-citations.js`, `parseCitations()`, chat message rendering) + +##### **(v0.250.228)** + +###### Bug Fixes + +* **Figures Now Stay in the Chunk They Came From** + * Images extracted from Word and PowerPoint files were appended as extra chunks at the end of the document, with page numbers continuing past the real content. A figure on page 5 of a 15-page document became chunk 16, so a search hit on the figure lost its surrounding text and citations pointed at a page that did not exist. + * Embedded images are now merged into the chunk containing the text they appear with. PowerPoint images follow the slide that references them; Word images are placed by their position in reading order; and legacy `.doc` and `.ppt` images, which carry no recoverable position, anchor to the final chunk instead of creating a page beyond the document. + * Merging rather than adding a chunk also removes a latent indexing hazard: chunk ids are derived from the page number, so a second chunk sharing a page number would have overwritten the first in the search index. + * PDFs were already correct — Content Understanding attributes each figure to its page by span, and Document Intelligence Layout inlines tables and figures into the page markdown. That behavior is unchanged and now covered by a regression test. + * **Existing documents keep their current chunks until they are extracted again.** Use *Change Extraction* or re-upload to pick up the new placement. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_documents.py`, `functions_office_media.py`, figure chunk association) + +##### **(v0.250.227)** + +###### Bug Fixes + +* **Shared Conversation Stream Errors Stay Attached to the Shared Conversation** + * Follow-up hardening to the v0.250.224 shared conversation fix. When an AI request in a shared conversation failed, the error the browser received did not say which kind of conversation it belonged to, so the recovery path could have reloaded from the personal endpoint and produced the same "Conversation not found" error that was just fixed. + * It could not actually happen yet because of an unrelated guard, but it would have come back the moment anyone added a message id to those errors. All shared stream failures now go through a single serializer that always tags the conversation, and a test walks the code to prove no failure path can skip it. + * (Ref: [#1281](https://github.com/microsoft/simplechat/issues/1281), `route_backend_collaboration.py`, `chat-streaming.js`, collaborative AI streaming) + +* **Repaired Route Assertions Across the Test Suite** + * The recent Blueprint security hardening renamed how routes are declared, but 82 assertions across 40 test files still checked for the old form. Those tests were failing on the rename before they ever reached the behavior they were written to protect. + * This is how the shared conversation streaming bug reached users: the test guarding that exact code path was already red for an unrelated reason. 59 assertions across 32 files were corrected, each verified against a real route first. 14 were deliberately left alone because they point at routes that no longer exist, which is a separate issue worth investigating rather than hiding. + * (Ref: [#1281](https://github.com/microsoft/simplechat/issues/1281), `functional_tests/`, Blueprint route registration) + +##### **(v0.250.226)** + +###### Bug Fixes + +* **File Sync Now Tells the Workflow What Changed** + * File Sync builds a summary of each run — the scan counts plus every new or changed document — but that summary never reached the model in any workflow that uses tasks, which is every workflow the builder creates. + * The failure was silent and misleading: the summary *was* written into the conversation, so the transcript showed the changed-document list as though the model had received it. In practice the model saw only the raw task instructions and usually replied that it knew nothing about any documents. + * This hit **Monitor File Sync Changes** workflows hardest, along with any workflow using Search or no document action, or with **Use changed documents** turned off. The first task in the sequence now receives the summary, and later tasks get it through the first task's response. + * The summary is also bounded now, with a clear truncation notice, so a very large sync cannot crowd out the actual instructions. + * (Ref: [#1285](https://github.com/microsoft/simplechat/issues/1285), `functions_workflow_runner.py`, File Sync prompt context) + +* **Document Search Queries Are No Longer Diluted by Injected Context** + * A workflow's document search used the entire task prompt as its search query, including the File Sync summary and the previous task's full response. A search for "find the renewal clause" could end up querying 50 lines of file paths. + * Search queries now use the task's own instructions. Retrieved content and context still reach the model exactly as before — only the query is scoped. + * (Ref: [#1285](https://github.com/microsoft/simplechat/issues/1285), `functions_workflow_runner.py`, workflow document search) + +##### **(v0.250.225)** + +###### New Features + +* **Workspace Documents Are Now Configured Per Workflow Task** + * Each task in a workflow now owns its own **Workspace documents** setup — document action, document target, selected documents, Compare source and targets, per-document analysis, and windowing — instead of sharing one configuration across the whole workflow. + * Adding a task resets the document fields, and returning to a previously configured task restores that task's setup, so tasks are self-contained. + * Every task now executes with its own document action at run time. Previously the single workflow-level action only ever applied to task 1, and every later task ran with no document context. + * Existing workflows keep working: their saved document action is inherited by task 1 only, matching how they actually ran. Group workflows still force every task into the owning group workspace. + * Task cards and the Review step now summarize which documents each task uses. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `workspace_workflows.js`, `functions_personal_workflows.py`, `functions_group_workflows.py`, `functions_workflow_runner.py`, per-task document actions) + +###### Bug Fixes + +* **Workflow Document Picker No Longer Hangs on "Loading tags..."** + * Choosing a Document action of Search, Analyze, or Compare in the workflow builder revealed the document picker but never loaded it. Tags stayed disabled showing `Loading tags...` forever, the document list stayed empty, and no console error appeared. + * The picker was only ever loaded when the modal opened, and that path returned early whenever the action was `No document action` — which is always true for a new workflow. The Document action dropdown's change handler only toggled visibility and never triggered a load. + * Changing the Document action or Document Target now loads the picker, and the tags control always resolves to the available tags or `No tags available for this scope`. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `workspace_workflows.js`, `chat-documents.js`, workflow document picker) + +* **Workflow Run History No Longer Masks a Failed Document** + * When two tasks in the same run process the same document, the later task's status used to overwrite the earlier one's, so a document that failed in one task could be shown as succeeded. + * Document run items are now recorded per task, and each item records which task produced it. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `functions_workflow_runner.py`, workflow run history) + +* **Resume Failed Items Respects Per-Task Documents** + * Resuming failed documents narrowed only the workflow-level document action. Now that tasks own their own documents, it also narrows each task's analyze action to the documents that failed in that task, and group resumes keep every task inside the owning group workspace. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `route_backend_workflows.py`, resume failed items) + +* **A Workflow Task Configured for Zero Retries Per Window Stays at Zero** + * Saving a multi-task workflow rewrote a stored `Retries Per Window` of `0` to `1` on any task other than the one being edited. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `workspace_workflows.js`, retries per window) + +* **An Invalid Task Document Action No Longer Aborts the Whole Run** + * If a workflow's document action stopped validating between runs — for example an administrator disabled Analyze or Compare, or lowered the workflow document limit — the run failed outright with no task-level error recorded. + * The failure is now contained to that task and follows the workflow's retry and failure-handling settings. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `functions_workflow_runner.py`, workflow task error handling) + +###### User Interface Enhancements + +* **"Refresh documents" Now Actually Refreshes** + * The **Refresh selected documents** button previously warned `Select one or more workspace documents in the picker first.` even though the picker was empty and nothing could be selected. + * It is now labeled **Refresh documents**, reloads the document list while preserving the current selection, and reports what it found — including a clear message when the selected scope has no documents. + * (Ref: [#1282](https://github.com/microsoft/simplechat/issues/1282), `workspace.html`, `group_workspaces.html`, `workspace_workflows.js`) + +##### **(v0.250.224)** + +###### Bug Fixes + +* **Shared Conversations Load and Answer Again** + * Sharing a personal conversation left it unusable. Every reload or click on the shared conversation raised a "Conversation not found" error, because the chat page was still asking for its messages from the personal conversation endpoint — and a shared conversation is stored separately, under its own id. + * Shared conversations now load their messages only from the collaboration endpoint, so the failed request and the error banner are gone. + * (Ref: [#1281](https://github.com/microsoft/simplechat/issues/1281), `chat-conversations.js`, `chat-collaboration.js`, shared conversation loading) + +* **AI Responses Work Again in Shared Conversations** + * Asking the AI anything in a shared conversation failed immediately with "Stream interrupted: Chat streaming endpoint is unavailable" and no answer was ever generated. + * The recent Blueprint security hardening renamed the internal chat streaming endpoint, and the shared-conversation bridge was still looking for the old name. The bridge now resolves the endpoint correctly and logs a diagnostic if it ever cannot, so this fails loudly instead of silently. Group shared conversations are restored by the same fix. + * (Ref: [#1281](https://github.com/microsoft/simplechat/issues/1281), `route_backend_collaboration.py`, `app.py`, collaborative AI streaming) + +* **Chat Uploads and Task Documents in Shared Conversations** + * Files uploaded inside a shared conversation never showed up in the Analyze and Compare document pickers, and task documents from the previously opened conversation stayed attached after switching to a shared one. + * Shared conversations now refresh both when their messages load, matching personal conversation behavior. + * (Ref: [#1281](https://github.com/microsoft/simplechat/issues/1281), `chat-collaboration.js`, `chat-messages.js`, Compare and Analyze document pickers) + +###### New Features + +* **Optional Mathematical Formula Extraction** + * Added an **Extract mathematical formulas** toggle to the Document Intelligence settings. When enabled, equations in PDFs and images are captured as LaTeX instead of being approximated as OCR text. + * This requests a **billed Document Intelligence add-on**, so it is off by default and must be turned on deliberately. It applies to the Layout model only, so it has no effect while extraction is set to Standard. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_content.py`, `functions_settings.py`, `admin_settings.html`, Document Intelligence formulas add-on) + +###### Bug Fixes + +* **Images in Legacy `.doc` and `.ppt` Files Are Now Analyzed** + * Embedded image analysis previously covered only DOCX and PPTX, because legacy Office files are OLE compound documents rather than zip packages and have no media parts to enumerate. + * Pictures and embedded equation previews are now carved out of the legacy container by metafile signature, using the length recorded in the metafile's own header, then rasterized and analyzed like any other embedded image. + * Validation is strict — record type, signature position, and a length that fits the remaining bytes — so a coincidental byte sequence is not mistaken for an image. Duplicate images are still collapsed and the per-document cap still applies. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_office_media.py`, `functions_documents.py`, legacy Office image extraction) + +##### **(v0.250.223)** + +###### Bug Fixes + +* **Diagrams in Word and PowerPoint Files Are Now Analyzed** + * Images embedded in Office documents as EMF or WMF metafiles were silently skipped. Word stores pasted diagrams, SmartArt, Visio drawings, and charts in this format, so architecture diagrams — often the most information-dense figures in a document — were never analyzed or indexed. + * Metafiles are now rasterized in-process and sent to the configured extraction engine like any other image. Text drawn inside the diagram is recovered as well, so figure labels such as service and resource names become searchable even when the vision engine returns no description. + * The renderer is pure Python on top of Pillow, with no system packages or external converters, so it behaves the same in the Linux container as it does locally. Fidelity is intentionally a description aid rather than a pixel-accurate reproduction; unsupported drawing records are skipped rather than failing the document. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_emf_render.py`, `functions_office_media.py`, embedded Office image analysis) + +* **Embedded Image Processing Is Now Visible in the Workspace Log** + * A document whose images were all skipped looked exactly like a document with no images at all, so there was no way to tell whether embedded image analysis had run. + * Processing now reports how many embedded images were found, how many were analyzed, and why any were skipped — too small, duplicates, unsupported format, or over the per-document cap. Progress is reported per image rather than only once at the start. + * The found, analyzed, and skipped counts are stored on the document so the outcome can be confirmed after processing completes. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_documents.py`, `functions_office_media.py`, embedded image diagnostics) + +##### **(v0.250.222)** + +###### New Features + +* **Index Auto-Login** + * Added an opt-in `ENABLE_AUTO_LOGIN_ON_INDEX` setting that redirects unauthenticated home-page visits to the existing Microsoft Entra sign-in flow. + * Supports government tenant SSO scenarios where users already have a browser session and should enter SimpleChat without first clicking the sign-in link. + * (Ref: `app.py`, `config.py`, `INDEX_AUTO_LOGIN.md`, Microsoft Entra sign-in) + +##### **(v0.250.221)** + +###### New Features + +* **Enhanced Extraction Now Uses Azure AI Content Understanding** + * Enhanced extraction for PDFs and images now uses Azure AI Content Understanding (`prebuilt-documentSearch`) instead of Document Intelligence Layout. In addition to tables, page structure, and checkbox states, it returns AI-generated descriptions of figures, charts, and diagrams — structure Document Intelligence never produced. + * Standard extraction is unchanged and always uses Document Intelligence, which remains required for workspaces and chat file uploads. + * A new **Enable Enhanced extraction** toggle in Admin Settings reveals the Content Understanding configuration. Turning it on defaults the extraction mode to **Auto**, so documents are only upgraded when the sample shows structure worth paying for. + * Content Understanding supports both key and managed identity authentication, with a **Test Connection** button and an in-app setup guide covering Foundry resource creation, supported regions, required model deployment defaults, and the Cognitive Services User role. + * Enhanced never becomes a hard dependency. Content Understanding is not offered in Azure Government, so Enhanced automatically uses Document Intelligence Layout in Government and custom clouds — the admin UI says so plainly and there is nothing to configure there. Enhanced also falls back when Content Understanding is unconfigured or a request fails, and the reason is recorded on the document and shown in workspace tooltips. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_content_understanding.py`, `functions_content.py`, `functions_settings.py`, `route_backend_settings.py`, `admin_settings.html`, `CONTENT_UNDERSTANDING_ENHANCED_EXTRACTION.md`) + +* **Images Inside Word and PowerPoint Files Are Now Analyzed** + * Neither extraction engine describes figures inside Office files, so SimpleChat now pulls embedded images out of DOCX and PPTX packages and analyzes them with whichever engine backs the selected extraction mode — Content Understanding when Enhanced is active, Document Intelligence otherwise. This works with Standard extraction too. + * Each analyzed image is indexed as its own citable chunk, and PowerPoint images are attributed to the slide that references them. + * Cost is bounded by design: icons, bullets, and spacer graphics are filtered out by a configurable minimum size, byte-identical images such as repeated header logos are analyzed once, and a per-document cap limits the total. + * Uploaded Office files are treated as untrusted: extracted file names are generated rather than reused from the archive, entries are streamed with a hard byte ceiling instead of trusting the archive's declared size, compression methods and entry counts are bounded, and slide relationship parts are parsed with a hardened XML parser. + * Can be turned off entirely with **Analyze images embedded in DOCX and PPTX files**. Image analysis failures never fail the document. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_office_media.py`, `functions_documents.py`, embedded Office image analysis) + +###### Upgrade Notes + +* **Existing Enhanced and Auto Deployments Keep Their Setting** + * The new **Enable Enhanced extraction** toggle defaults to off, so deployments already set to Enhanced or Auto are migrated automatically on first settings read: the toggle is switched on and persisted, preserving the previously selected mode. + * Without this migration an upgrade would have silently downgraded those deployments to Standard and then overwritten the stored mode on the next settings save. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_settings.py`, `get_settings()` migration) + +###### User Interface Enhancements + +* **Extraction Badges Name the Engine That Actually Ran** + * Extraction tooltips in personal, group, and public workspaces now say whether a document was processed with Azure AI Content Understanding or Document Intelligence Layout, and explain any fallback that occurred. + * The **Change Extraction** action now works for images as well as PDFs, and refuses a change to Enhanced while Enhanced extraction is disabled. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `workspace-documents.js`, `public_workspace.js`, `group_workspaces.html`, `functions_documents.py`) + +* **Auto Mode Also Upgrades for Figures** + * Auto mode still samples the first pages with Document Intelligence Layout as the cheaper detector, but now upgrades to Enhanced when it finds figures or images, not just tables and selection marks. This matters because figure description is the main reason to use Enhanced. + * (Ref: [#1277](https://github.com/microsoft/simplechat/issues/1277), `functions_documents.py`, Auto mode detection) +##### **(v0.250.220)** + +###### Bug Fixes + +* **Data Management History Failure Diagnostics** + * Backup Inventory and Job History failures returned a generic 503 telling admins to review application logs, while the logs recorded only the exception class name. The provider status code and message were discarded, making the failure impossible to diagnose. + * Failures now log the Cosmos status code and sanitized provider message. Provider text stays in operator logs and is never returned to the browser. + * (Ref: [#1275](https://github.com/microsoft/simplechat/issues/1275), `functions_data_management.py`, `route_backend_data_management.py`, Data Management history) + +* **Data Management History Throttle Handling** + * Throttled history reads previously produced the same opaque error as a permanent failure. + * Cosmos throttling is now detected, retried up to three times with jittered backoff, and reported as temporary busy guidance with a retryable flag instead of a generic error. + * (Ref: [#1275](https://github.com/microsoft/simplechat/issues/1275), `functions_data_management.py`, Cosmos history query retry) + +* **Data Management History Index Guidance** + * Missing-index detection required the exact phrase "composite index", so equivalent provider wording fell through to the generic error. + * Detection now also matches `ORDER BY` failures reported as having no corresponding index, keeping the Cosmos indexing maintenance guidance actionable. + * (Ref: [#1275](https://github.com/microsoft/simplechat/issues/1275), `functions_data_management.py`, Cosmos indexing maintenance) + +* **Source Blob Backup ETag Failure** + * Fixed every source blob failing backup with "Source blob changed while it was being backed up", which meant user documents, group documents, public documents, and chat attachments were never actually backed up. + * Root cause was comparing an ETag from `list_blobs()` (unquoted XML element) against one from `get_blob_properties()` (RFC-quoted HTTP header); the two never matched, so the post-transfer consistency check always failed after the blob had already been downloaded and uploaded. + * Both values are now normalized before comparison. The precondition sent to Azure is unchanged, and a genuine mid-transfer source change is still rejected. + * (Ref: [#1271](https://github.com/microsoft/simplechat/issues/1271), `functions_data_management.py`, source blob transfer verification) + +* **Source Blob Backup Checkpoint Throughput** + * Source blob backups previously wrote one Cosmos checkpoint per blob, capping throughput at roughly six items per second and stretching a single container to over an hour. + * Checkpoints are now batched per 100 items or 15 seconds, whichever comes first, while still asserting the job lease on every item. + * (Ref: [#1271](https://github.com/microsoft/simplechat/issues/1271), `functions_data_management.py`, source blob checkpointing) + +* **Functional Tests Silently Passing Under pytest** + * Backup functional tests written with the try/except and `return False` template were reported as passed by pytest because they returned a value instead of raising. + * The backup ETag and Cosmos pagination test files now assert directly, so real failures are reported by both pytest and standalone execution. + * (Ref: [#1271](https://github.com/microsoft/simplechat/issues/1271), `test_data_management_backup_source_blob_etag.py`, `test_data_management_backup_cosmos_pagination.py`) + +##### **(v0.250.219)** + +###### Bug Fixes + +* **Agent Document Search Now Produces Real Document Citations** + * Documents an agent retrieved through the document search action are now recorded as document sources instead of only as an agent tool call. Previously they appeared solely as a raw JSON tool modal, so the documents were missing from the message Sources disclosure, were not clickable, never opened in the enhanced citation viewer, and could never reach the Used documents drawer. + * Covers all three document search functions — relevance-ranked search, ordered chunk retrieval, and document summarization — across personal, group, and public workspaces. + * Document search results now carry a ready-to-copy citation value, and the action instructs the model to reuse it verbatim. When the answer cites a document, it is correctly separated from the retrieved sources and recorded in the conversation's used documents. + * Applies to streaming and non-streaming chat, document actions, cancelled and interrupted streams, and scheduled workflow runs. + * Retrieved sources are deliberately not capped, so a search that sources hundreds of chunks records all of them. Chunks retrieved by both the document search toggle and an agent are listed once. + * Cancelled and interrupted streams keep the documents the agent had already retrieved, and citation locations no longer relabel a valid page or sequence of `0` as page 1, which affected video chunks keyed by second. + * Workspace capability metadata now reports document usage for agent-only document turns, which previously under-reported as unused. + * (Ref: [#1239](https://github.com/microsoft/simplechat/issues/1239), `functions_agent_document_citations.py`, `route_backend_chats.py`, `functions_workflow_runner.py`, `document_search_plugin.py`, `AGENT_DOCUMENT_SEARCH_CITATION_FIX.md`) + +###### User Interface Enhancements + +* **Collapsed Long Source Lists** + * The per-message Sources disclosure now shows the first 25 document sources and collapses the rest behind a **Show N more sources** control, so an agent that retrieves hundreds of chunks no longer floods the panel. + * No source data is discarded — the full set is still stored, exported, and available for citation matching. + * (Ref: [#1239](https://github.com/microsoft/simplechat/issues/1239), `chat-messages.js`, `chat-citations.js`) + +##### **(v0.250.218)** + +###### Bug Fixes + +* **Credential Field Names Logged in Clear Text** + * Fixed a gap where credential values could be written to application logs and Application Insights in clear text. The log redactor matched only a fixed list of key-name substrings, so field names this codebase actually uses for secrets were missed. The most significant were `auth_key`, used by the action connection-test routes for the caller-supplied secret, and the plugin manifest's `auth.key`, which holds connection strings and service principal passwords. + * Eighteen credential key names were affected in total, including `pwd`, `key_pair`, `master_key`, `primary_key`, `secondary_key`, `encryption_key`, `signing_key`, `session_key`, and `storage_key`. + * Benign configuration keys that merely contain the word "key", such as `key_encoding`, `key_prefix_hints`, and `partition_key_path`, deliberately stay visible so logs keep their diagnostic value. + * (Ref: `functions_appinsights.py`, `test_log_credential_key_redaction.py`, `LOG_CREDENTIAL_KEY_REDACTION_FIX.md`) + +* **CosmosClient Import Bindings in Helper Scripts** + * Completed the v0.250.047 import-binding cleanup by updating the two remaining scripts that bound `CosmosClient` directly, so patching `azure.cosmos.CosmosClient` is observed consistently. No direct `CosmosClient` imports remain in the repository. + * (Ref: `scripts/resolve_multiendpoint_gpt.py`, `deployers/bicep/postconfig.py`) + +* **Privacy Logging Audit Test Restored** + * The privacy logging and telemetry audit had been failing since v0.242.072 because it asserted an exact `config.py` version and never reached its assertions. It now asserts a version floor, per the repository's version-assertion guidance, so the audit runs again. + * (Ref: `test_privacy_logging_telemetry_audit.py`) + +##### **(v0.250.217)** + +###### New Features + +* **Test Connection for Eight More Action Types** + * Added a **Test Connection** button to the Step 3 configuration for OpenAPI, Azure Maps, Blob Storage, Databricks, Log Analytics, MCP, Snowflake, and Tableau actions. Previously only SQL, Cosmos DB, Yamcs, and RocksDB actions could be validated before saving. + * Each test authenticates with the credentials entered in the modal and performs one lightweight read against the configured resource, so a wrong warehouse ID, container name, subscription key, personal access token, or MCP endpoint is caught immediately instead of failing later during a chat. + * Failures name the cause — rejected credentials, a missing warehouse or container, an unreachable host, or a driver that is not installed — and successes report useful detail such as the Databricks warehouse state, the Snowflake version, the Tableau API version, or the MCP tool count. + * Editing an existing action works without retyping credentials: masked secrets and reusable workspace identities are resolved server-side for the test only, and no credential value is ever returned to the browser. + * The MCP test enforces the same stdio scope restriction and outbound destination policy as MCP tool discovery, and it does not overwrite discovered tool metadata. + * (Ref: [#1267](https://github.com/microsoft/simplechat/issues/1267), `functions_action_connection_tests.py`, `route_backend_plugins.py`, `_plugin_modal.html`, `plugin_modal_stepper.js`, `ACTION_TEST_CONNECTION.md`) + +###### User Interface Enhancements + +* **Dedicated Log Analytics Configuration Section** + * Log Analytics actions now have their own Step 3 configuration section instead of reusing the generic endpoint and authentication form. + * Workspace ID, Cloud, API Endpoint, and the authentication method moved out of *Advanced → Additional Fields* and into the main configuration step, next to the new Test Connection button. Authority Host and Endpoint Override appear only when the Custom cloud is selected. + * Existing Log Analytics actions are unaffected — the section reads and writes the same manifest fields and preserves stored values such as `query_history`. + * (Ref: [#1267](https://github.com/microsoft/simplechat/issues/1267), `_plugin_modal.html`, `plugin_modal_stepper.js`, Log Analytics action configuration) + +###### Bug Fixes + +* **Action Secret References Now Resolved Only Within Their Own Scope** + * Action connection tests resolve a stored Key Vault secret reference strictly against the scope of the action being tested, instead of resolving any reference name supplied in the request. + * The unscoped resolver has been removed, and the existing MCP tool discovery, Cosmos DB, SQL, Yamcs, and RocksDB test paths now share the same scope-checked resolution used by the new connection tests. A reference that does not match the action's scope is rejected instead of resolved. + * Loading a global action for a connection test now requires the Admin role at the shared loader, so every test route inherits the check rather than relying on each route to gate it. + * Only affects deployments with Key Vault secret storage enabled. Normal editing is unchanged — testing an existing action still works without retyping stored credentials. + * (Ref: [#1267](https://github.com/microsoft/simplechat/issues/1267), `route_backend_plugins.py`, `functions_keyvault.py`, action secret scoping) + +##### **(v0.250.216)** + +###### New Features + +* **RocksDB Action** + * Added a new `rocksdb` action type so agents can read an ordered [RocksDB](https://github.com/facebook/rocksdb) key-value store, with a dedicated configuration card and Test Connection button in the action modal. + * RocksDB is an embedded library with no network protocol, so the action calls a RocksDB-backed HTTP/JSON service that you operate alongside your data. SimpleChat never runs RocksDB locally or opens a database directory on the application host. + * Supports no-auth, bearer token, and API key header authentication with a configurable header name. TLS certificate validation is always enforced. + * Exposes `get_value`, `get_values`, `key_exists`, `scan_prefix`, `scan_range`, `list_column_families`, and `get_database_stats` for reads, plus `put_value`, `delete_value`, and `write_batch` that stay blocked until an action explicitly allows writes. + * Handles binary data through configurable UTF-8, base64, and JSON key and value encodings that are sent to the service on every request, caps returned records, and flags values truncated by the size limit. + * The RocksDB HTTP service contract is fully documented so operators can implement a conforming service. + * (Ref: `rocksdb_plugin.py`, `route_backend_plugins.py`, `plugin_health_checker.py`, `_plugin_modal.html`, `plugin_modal_stepper.js`, `rocksdb.definition.json`, `test_rocksdb_plugin.py`, `test_workspace_rocksdb_action_modal.py`, `docs/explanation/features/v0.250.216/ROCKSDB_ACTION.md`) + +##### **(v0.250.215)** + +###### Bug Fixes + +* **Retrieved Sources and Cited References** + * Separated complete document/web retrieval results from the exact references used in final assistant responses, while preserving all returned results under **Sources**. + * Used documents now follows active cited responses, conversation details marks cited items within the full source inventory, and conversation/message export references exclude retrieved-only sources. + * Historical conversations retain their previous fallback without a migration or ordinary read-time history parsing. + * (Ref: [#1249](https://github.com/microsoft/simplechat/issues/1249), `functions_citation_tracking.py`, `route_backend_chats.py`, `route_backend_conversation_export.py`, `SOURCE_AND_CITED_REFERENCE_DISTINCTION_FIX.md`) + +##### **(v0.250.214)** + +###### New Features + +* **Agent Instruction Context References (`#action` and `#knowledge`)** + * Instructions can now reference the exact actions, action capabilities, and assigned knowledge that were selected for the agent, so authors can spell out *when* and *why* each capability or document should be used. + * Typing `#` in the Instruction Brief or the instructions editor opens an autocomplete that drills down from the `action` / `knowledge` namespace, to the actions selected in the Actions step, to that action's enabled capabilities. `#knowledge:` lists the assigned documents, workspaces, tag limits, and web sources with type badges. + * Tokens such as `#action:"Simple Chat":create_group` and `#knowledge:doc:"Employee Handbook.pdf"` are stored literally with the instructions so they stay editable and round-trip unchanged when an agent is edited. Values containing a space or colon are quoted automatically. + * Navigate with the arrow keys, insert with `Tab` or `Enter`, dismiss with `Esc`, or use the mouse. Document titles containing spaces stay searchable while typing. + * Foundry agents manage their instructions and tools in Foundry, so the references stay inert for Classic Foundry, New Foundry, and Foundry Workflow agents. + * (Ref: [#1257](https://github.com/microsoft/simplechat/issues/1257), [#1263](https://github.com/microsoft/simplechat/pull/1263), `agent_instruction_mentions.js`, `agent_modal_stepper.js`, `_agent_modal.html`) + +* **Context-Aware Draft Instructions** + * The **Draft Instructions** helper now receives the selected actions with their enabled capabilities and the assigned knowledge configuration, instead of only the agent name, description, and brief. + * Drafts reference only real, selected actions and documents, and use the new `#action:` / `#knowledge:` token convention. + * Client-supplied context is normalized, length-capped, count-capped, and bounded by a shared total character budget on the backend. It is used purely as prompt text and never affects authorization, and whitespace collapsing prevents newline-based prompt injection through action or document names. + * (Ref: [#1257](https://github.com/microsoft/simplechat/issues/1257), [#1263](https://github.com/microsoft/simplechat/pull/1263), `route_backend_agents.py`, `POST /api/agents/draft-instructions`) + +###### User Interface Enhancements + +* **Agent Modal Step Reorder: Instructions After Actions and Knowledge** + * The agent modal now runs Basic Info → Model & Connection → **Actions** → **Knowledge** → **Instructions** → Advanced → Summary, so instructions are written once the agent's real capabilities are known. + * Added a collapsible **Selected Actions & Knowledge** panel at the top of the Instructions step listing the selected actions with badges for their enabled capabilities, plus the assigned workspaces, documents, tags, and web sources, each with its reference token. + * Step navigation, validation, and Foundry agent-type visibility now key off named steps rather than hard-coded step numbers. + * (Ref: [#1257](https://github.com/microsoft/simplechat/issues/1257), [#1263](https://github.com/microsoft/simplechat/pull/1263), `_agent_modal.html`, `agent_modal_stepper.js`) + +###### Bug Fixes + +* **Agent Summary Step Referenced the Wrong Step Number** + * The Summary step's empty-actions message pointed authors at "step 4" to add actions. Actions is step 3 under the new order, and step 4 is now Assigned Knowledge. + * (Ref: [#1263](https://github.com/microsoft/simplechat/pull/1263), `_agent_modal.html`, agent modal summary step) + +##### **(v0.250.213)** + +###### New Features + +* **Workflow Alert Rules** + * Workflow alerts are now conditional. Instead of a single Pop-up Alert Priority that notified on every run, a workflow can define rules that describe *why* it should notify you, and a run that matches nothing stays completely silent. + * Conditions cover run status, task status, output text (contains, does not contain, or regex), File Sync results, empty output, an agent-raised signal, and a plain-English condition judged by a model, such as "any certificate expires within 14 days." + * Each rule can be scoped to the final output, any task output, or one specific task. + * Model-judged conditions are batched into a single call per run and skipped entirely when a deterministic rule already matched at a higher severity, so workflows that use only deterministic conditions add no model calls. + * (Ref: `functions_workflow_alerts.py`, `functions_workflow_runner.py`, `functions_personal_workflows.py`, `functions_group_workflows.py`, `workspace_workflows.js`, `WORKFLOW_ALERT_RULES.md`) + +* **Expanded Alert Severities and a Distinct Failure Style** + * The severity ladder grew from low/medium/high to **info, low, medium, high and critical**. + * Info and low alerts land quietly in the notification bell, while medium and above open the pop-up. Any rule can override this. + * Runs that error now carry a separate *failure* category that changes the icon and wording independently of severity, so "the workflow broke" is visually distinct from "the workflow found something." + * When several rules match the same run, the highest severity wins and the alert lists every matched rule with its reason under a new "Triggered by" section. + * (Ref: `functions_notifications.py`, `notifications.js`, `base.html`, workflow alert modal) + +* **Agent-Raised Workflow Alerts** + * Agents running inside a workflow can now raise an alert signal mid-run with severity, title and reason through the new `raise_workflow_alert` SimpleChat capability, and an `agent_signal` rule decides whether it notifies anyone. + * The rule's severity acts as a floor the agent can escalate above but never quiet below, and named signals can route to their own rules. + * The capability is opt-in and refuses outside an active workflow run, so existing agents do not gain the ability to create notifications and a normal chat cannot fabricate one. + * (Ref: `simplechat_plugin.py`, `functions_simplechat_operations.py`, `agent_modal_stepper.js`, `plugin_modal_stepper.js`) + +* **Alert Decision Visibility** + * Each run now records why it did or did not alert, including the winning severity and every matched rule, surfaced through the workflow activity view so noisy or silent workflows can be diagnosed. + * (Ref: `functions_workflow_activity.py`, `functions_workflow_runner.py`) + +###### User Interface Enhancements + +* **Workflow Alert Rules Editor** + * The Review step of the personal and group workflow builders replaces the single Pop-up Alert Priority dropdown with an alert mode selector and a rule editor for adding, editing, enabling and removing alert rules. + * Each rule row exposes its name, condition, severity, delivery and, where relevant, the task or output it should watch, with condition-specific fields appearing as the condition is chosen. + * The workflow list now summarizes alerts as the number of active rules, and the Review summary names the rules that will notify you. + * Invalid rules are caught before saving, such as a missing regex pattern, empty match values, an unwritten model condition, or a task-scoped rule with no task selected. + * (Ref: `workspace.html`, `group_workspaces.html`, `workspace_workflows.js`, workflow builder review step) + +###### Breaking Changes + +* **Workflow Alert Configuration Model** + * The single `alert_priority` field is superseded by `alert_mode`, `alert_rules` and `alert_evaluation`. The old field is retained and still honored. + * **Migration**: None required. Workflows that only carry `alert_priority` are migrated on read into two editable rules, `Run failed → high` and `Run completed → `, which reproduce the previous behavior exactly, including always opening the pop-up and staying silent on cancelled runs. Owners can then prune the noisy rule. + +##### **(v0.250.212)** + +###### New Features + +* **Yamcs Mission Control Action** + * Added a first-class, read-only `yamcs` action type that connects agents to a Yamcs mission control server using the official `yamcs-client` Python package. + * Exposes eleven read-only tools: instances, data links, mission database parameters and parameter detail, command *definitions*, live parameter values, parameter history, events, packets, alarms, and an optional guarded archive SQL query. + * Strictly read-only by design. The action cannot issue commands, set parameter values, run scripts, or enable/disable data links, and command listing returns definitions only. + * Archive SQL is disabled by default and, when enabled, is restricted to `SELECT`, `SHOW`, `DESC`, and `DESCRIBE` statements with a forbidden-keyword guard and an automatic row limit. + * Every retrieval is bounded by a row limit, a serialized byte limit, and a request timeout so a broad query cannot walk an entire archive, and error text is scrubbed of credentials. + * (Ref: `functions_yamcs_operations.py`, `semantic_kernel_plugins/yamcs_plugin.py`, `semantic_kernel_plugins/yamcs_plugin_factory.py`, `docs/explanation/features/YAMCS_ACTION.md`) + +* **Yamcs Action Configuration Panel and Test Connection** + * Added a dedicated Yamcs configuration section to the Add/Edit Action modal covering server URL, instance, processor, authentication, TLS verification, archive SQL opt-in, and retrieval limits. + * Supports username/password, API key, bearer token, and unauthenticated Yamcs servers, plus reusable workspace identities using `api_key`, `bearer_token`, or `username_password`. + * Added a **Test Yamcs Connection** button backed by `POST /api/plugins/test-yamcs-connection`, which verifies reachability and credentials and confirms the configured instance exists. Saved actions resolve their stored credential from Key Vault, so secrets do not need to be re-entered to run a test. + * (Ref: `_plugin_modal.html`, `plugin_modal_stepper.js`, `route_backend_plugins.py`, `workspace/view-utils.js`) + +###### Breaking Changes + +* **New `yamcs-client` Dependency** + * Added `yamcs-client==2.1.0` to `application/single_app/requirements.txt`. + * This package is licensed **LGPL-3.0**, the first LGPL dependency in this repository. It is used as an unmodified, dynamically linked pip dependency. + * It vendorizes its own protobuf runtime, so it does not conflict with the pinned `protobuf==6.33.5`. + * **Migration**: run `pip install -r requirements.txt` when upgrading. Deployments that do not install it can still run SimpleChat; Yamcs actions will return an actionable dependency error until the package is present. + * (Ref: `requirements.txt`, `semantic_kernel_plugins/yamcs_plugin.py`) + +##### **(v0.250.211)** + +###### User Interface Enhancements + +* **Consistent Workspace Section Order** + * Workspace sections now follow a single order of operations everywhere they are listed: Documents, Prompts, Identities, Sync, Endpoints, Actions, Agents, Workflows. + * The order reflects how a workspace is actually built up, so it is clearer that Identities feed both Sync and Actions, that Actions belong to Agents, and that Workflows run Agents. + * Applied to the tab strip, the collapsed Section dropdown, and the left-hand sidebar submenus for personal and group workspaces. Public workspaces already matched this order and were left unchanged. + * Sections that an admin has disabled stay hidden; the remaining sections simply close up while keeping their relative positions. + * (Ref: [#1255](https://github.com/microsoft/simplechat/issues/1255), `workspace.html`, `group_workspaces.html`, `_sidebar_nav.html`, `WORKSPACE_SECTION_ORDER.md`) + +###### Bug Fixes + +* **Group Workflows Missing From Sidebar Navigation** + * Added the missing Group Workflows link to the left-hand group workspace submenu. Group workflows previously had a working tab but no way to reach it from the sidebar. + * (Ref: [#1255](https://github.com/microsoft/simplechat/issues/1255), `_sidebar_nav.html`, group workflows navigation) + +* **Sidebar Links Pointing At Unrendered Workspace Tabs** + * Fixed left-hand navigation links whose visibility rules did not match the tabs they opened, so a link could appear for a section that was never rendered. + * Personal Agents and Actions links now respect the user agent and plugin permissions, group Agents and Actions links now respect per-user Semantic Kernel and group plugin permissions, and both Identities links now match their tab's File Sync and Semantic Kernel conditions. + * (Ref: [#1255](https://github.com/microsoft/simplechat/issues/1255), `_sidebar_nav.html`, `test_workspace_section_order.py`) + +##### **(v0.250.210)** + +###### Bug Fixes + +* **Chat Document Search Now Matches File Names** + * Fixed the chat grounded-search document picker only matching on a document's title, which made file names completely unsearchable for any document that had extracted title metadata. + * Typing any fragment of a file name now surfaces the document, anywhere in the name — searching `200` finds `Quarterly_Report_200_final.pdf`. + * Multi-word queries are also supported, with `_`, `-`, and `.` treated as word breaks, so `report 200` matches `Quarterly_Report_200_final.pdf`. The same improvement applies to the scope, tags, prompt, model, and agent selectors. + * (Ref: [#1256](https://github.com/microsoft/simplechat/issues/1256), `chat-documents.js`, `chat-searchable-select.js`, chat grounded search, document picker) + +* **Leftover Separator Lines in Filtered Dropdowns** + * Fixed filtered dropdowns leaving orphaned workspace separator lines behind — commonly two stacked horizontal rules directly under the "Select All" / "Clear All" row — when a search removed the leading sections. + * Divider visibility now follows the section it separates instead of the nearest visible row, and separator lines can no longer be leading, trailing, or stacked. Affects the Document, Scope, and Tags dropdowns, plus the Compare modal document picker. + * (Ref: [#1256](https://github.com/microsoft/simplechat/issues/1256), `chat-searchable-select.js`, dropdown filtering, section dividers) + +###### User Interface Enhancements + +* **File Name Shown in Document Picker Rows** + * Document rows in the chat grounded-search picker now show the file name as a smaller muted line beneath the title whenever the two differ, so it is clear which file a search matched. + * Rows without distinct titles are unchanged, and the row tooltip carries both the title and the file name. + * (Ref: [#1256](https://github.com/microsoft/simplechat/issues/1256), `chat-documents.js`, `chats.css`, document picker rows) + +##### **(v0.250.209)** + +###### Bug Fixes + +* **Cosmos Backup Continuation Token Failure** + * Fixed Data Management backups silently omitting every Cosmos container that held more than one page of documents, which in most deployments meant personal conversations and personal messages were never backed up. + * Affected containers failed with `BadRequest: Invalid Continuation Token` and were dropped from the backup artifact set while the job still reported completion with warnings. + * Root cause was rebuilding the cross-partition query for each page and replaying the previous pager's continuation token; the backup now drains a single pager so the SDK's cross-partition execution context is preserved. + * (Ref: [#1258](https://github.com/microsoft/simplechat/issues/1258), `functions_data_management.py`, Cosmos backup source paging) + +* **Missing Backup Failure Diagnostics** + * Source blob transfer failures previously produced no log output at all, so a run with nearly 20,000 failed blobs left no trace in App Service logs. + * Backups now log the first failure for each resource plus a bounded rollup of distinct failure reasons and counts when the resource finishes. + * (Ref: [#1258](https://github.com/microsoft/simplechat/issues/1258), `functions_data_management.py`, source blob backup logging) + +* **Application Insights Log Message Text** + * Structured log events reached Application Insights as the constant `[SIMPLE_CHAT_LOG_EVENT]` with every string property reduced to a character count, making traces unusable for diagnosis. + * Traces now carry the sanitized message text and an allowlist of non-sensitive diagnostic values such as job ID, resource, container, status code, and error. Sensitive keys still collapse to a presence flag and secret redaction is unchanged. + * (Ref: [#1258](https://github.com/microsoft/simplechat/issues/1258), `functions_appinsights.py`, log event properties) + +##### **(v0.250.208)** + +###### User Interface Enhancements + +* **Governance Policy Copy and Principal Review Actions** + * Added Duplicate and Inverse actions for delegated item governance policies so admins can quickly clone a policy or create an allow/block-list-swapped version before saving it as a new policy. + * Added a Show Users modal and removed the allowed/blocked user and group columns from the main delegated policy table, keeping the table easier to scan while preserving principal detail access. + * (Ref: [#1252](https://github.com/microsoft/simplechat/issues/1252), `admin_governance.js`, `admin_settings.html`, governance delegated item policies) + +##### **(v0.250.207)** + +###### Bug Fixes + +* **Intentional Governance Item Policy Retargeting** + * Updated delegated item policy edits so admins can intentionally move an existing policy to a different delegated item without creating a duplicate policy document. + * The admin UI keeps the policy ID stable, warns that changing the target will move the policy, and the backend saves the new target while deleting the original source document when original target metadata is supplied. + * Also prevents ambiguous policy-ID reuse and keeps feature-policy saves and item-policy deletes out of the retarget conflict path. + * (Ref: [#1252](https://github.com/microsoft/simplechat/issues/1252), `functions_governance.py`, `route_backend_governance.py`, `admin_governance.js`) + +##### **(v0.250.206)** + +###### Bug Fixes + +* **Governance Item Policy Retarget Protection** + * Fixed delegated item policy edits so changing the selected target no longer creates a duplicate policy document for the new item while leaving the old policy behind. + * Locks the target controls during existing policy edits and rejects conflicting backend saves when an existing policy ID is reused for a different delegated item. + * (Ref: [#1252](https://github.com/microsoft/simplechat/issues/1252), `admin_governance.js`, `route_backend_governance.py`, `functions_governance.py`) + +##### **(v0.250.205)** + +###### Bug Fixes + +* **Governance Block List Modal Handoff** + * Fixed the delegated item block-list editor opening behind the item policy editor by hiding the parent modal before opening the shared principal editor, then restoring the item editor after the principal editor closes. + * Keeps Bootstrap modal focus, backdrop, and scroll handling consistent by ensuring only one governance modal is visible at a time. + * (Ref: [#1252](https://github.com/microsoft/simplechat/issues/1252), `admin_governance.js`, `test_admin_governance_tab.py`) + +##### **(v0.250.204)** + +###### New Features + +* **Governance Policy Block Lists** + * Added admin-managed block lists for feature and delegated item governance policies so specific users or groups can be denied even when allow-all or allow-list rules would otherwise grant access. + * Enables administrator-friendly APIM quota-tier separation, such as allowing a high-threshold group to a high endpoint while blocking that group from a default low-threshold endpoint without maintaining a large low-user allow list. + * (Ref: [#1252](https://github.com/microsoft/simplechat/issues/1252), `functions_governance.py`, `route_backend_governance.py`, `admin_governance.js`, MCP governance) + +##### **(v0.250.203)** + +###### New Features + +* **Model Endpoint Identity Header** + * Added admin controls to send a stable HMAC-hashed user identity key with model endpoint requests for APIM counters, quota policies, and backend routing policies. + * Supports global enablement, custom safe header names, selectable identity inputs, and per-endpoint inherit/enable/disable overrides without exposing raw UPN, object ID, or tenant ID values. + * (Ref: [#1250](https://github.com/microsoft/simplechat/issues/1250), Model Endpoint Identity Header, `functions_model_endpoint_identity_header.py`, model endpoint runtime, Admin Settings) + +##### **(v0.250.202)** + +###### Bug Fixes + +* **Live User Message Metadata During Streaming** + * Made submitted user-message metadata available as soon as storage is acknowledged, without waiting for the assistant response to finish or requiring a page refresh. + * Preserved finalized metadata across success, server errors, cancellation, disconnect, recovery, image generation, document actions, and shared-chat streams while keeping in-flight message mutations gated until terminal completion. + * (Ref: [#1244](https://github.com/microsoft/simplechat/issues/1244), `functions_chat_stream_events.py`, `chat-streaming.js`, `chat-messages.js`, `USER_MESSAGE_METADATA_STREAMING_FIX.md`) + +##### **(v0.250.201)** + +###### Bug Fixes + +* **Exhaustive Row-by-Row Markdown Output** + * Fixed line-by-line Markdown analysis reading every source row but publishing only 12 summarized findings because the previous hierarchical lane intentionally bounded findings and notable rows. + * Search now produces one exhaustive Markdown artifact containing every source row and every requested answer; Analyze produces a concise Markdown summary plus a separate exhaustive row-by-row Markdown artifact. + * Exact-row Markdown uses ordered checkpoints, output-aware batching, consecutive answer-field validation, non-empty answer enforcement, final row-count/source-order checks, and literal Markdown escaping for untrusted content. + * (Ref: [#1233](https://github.com/microsoft/simplechat/issues/1233), `functions_tabular_orchestration.py`, `functions_tabular_generated_exports.py`, `route_backend_chats.py`, `TABULAR_EXHAUSTIVE_ROW_MARKDOWN_FIX.md`) + +##### **(v0.250.200)** + +###### Bug Fixes + +* **Hidden Public Workspace Document Chat Grounding** + * Fixed document chat handoffs from accessible public workspaces that users had hidden from the public directory, so the selected document is now available to grounded search instead of appearing selected while being silently excluded. + * Adds the selected workspace to the user's visible Chat workspaces without hiding any existing choices and revalidates the requested public workspace before updating user settings. + * (Ref: [#1245](https://github.com/microsoft/simplechat/issues/1245), `route_frontend_chats.py`, `test_public_workspace_hidden_document_chat_visibility.py`, `PUBLIC_WORKSPACE_HIDDEN_DOCUMENT_CHAT_VISIBILITY_FIX.md`) + +##### **(v0.250.199)** + +###### Bug Fixes + +* **Tabular Analyze/Search Artifact Lifecycle Completion** + * Preserved the selected model endpoint for pure-tabular Analyze background work, preventing non-default model selections from falling back to an unavailable deployment on the default Azure OpenAI resource. + * Enforced one artifact contract across Search and Analyze: Search CSV produces CSV; Analyze CSV produces Markdown plus CSV; exhaustive requests without an explicit output format produce Markdown in either mode. + * Made artifact publication complete before run completion, repaired previously uploaded-but-hidden Markdown artifacts during status reconciliation, and preserved original generation failures instead of masking them as schema errors. + * Removed misleading one-row Analyze CSV handoff artifacts and added sanitized user-visible failure reasons without exposing provider errors or endpoint details. + * (Ref: `functions_tabular_orchestration.py`, `functions_workflow_runner.py`, `functions_tabular_generated_exports.py`, `TABULAR_DURABLE_ARTIFACT_LIFECYCLE_FIX.md`) + +##### **(v0.250.198)** + +###### Bug Fixes + +* **Tabular Parity Stale Settings Migration** + * Fixed the four backend-only tabular durable-preflight parity flags (`tabular_request_planner_mode`, `enable_tabular_search_shared_preflight`, `enable_tabular_analyze_durable_preflight`, `enable_tabular_hierarchical_analysis`) silently staying disabled on any deployment whose Cosmos settings document already stored them from before their defaults were raised to active. + * `get_settings()` merges code defaults into the persisted document via `deep_merge_dicts()`, which only fills in missing keys and never overwrites an existing one, so raising a default in code alone never took effect for upgraded-in-place deployments. + * Both Analyze and Search durable preflight now self-correct to the active defaults on the next settings load and persist the fix back to Cosmos DB; the `SIMPLECHAT_DISABLE_TABULAR_PARITY_DURABLE_PREFLIGHT` emergency rollback env var continues to work unchanged. + * (Ref: `functions_settings.py`, `normalize_tabular_parity_durable_preflight_defaults()`, `TABULAR_PARITY_STALE_SETTINGS_MIGRATION_FIX.md`) + +##### **(v0.250.197)** + +###### Bug Fixes + +* **Tabular "Line" Terminology Routing** + * Recognized "line"-phrased exhaustive tabular requests (for example, "for each line," "line by line," "one line per") as equivalent to "row"-phrased requests across eight duplicated keyword-detection functions, so they route through the durable generated-output/analysis pipeline instead of the bounded foreground tool-calling path. + * Activated `enable_tabular_hierarchical_analysis` by default so narrative (non-export) exhaustive whole-dataset Analyze/Search requests can resolve to the durable `hierarchical_analysis` task type, extending the existing emergency env kill switch to also cover this flag. + * (Ref: `functions_tabular_orchestration.py`, `functions_tabular_parity_contract.py`, `route_backend_chats.py`, `functions_document_analysis.py`, `TABULAR_LINE_TERMINOLOGY_ROUTING_FIX.md`) + +##### **(v0.250.196)** + +###### Bug Fixes + +* **Top Navigation Public Workspace Lockout** + * Fixed a server-rendering failure that could lock users out after they selected top navigation while Public Workspaces was enabled. + * Preserved the saved navigation preference and default or customized Public Workspace labels without requiring a Cosmos profile repair. + * (Ref: `_top_nav.html`, `test_public_workspace_display_name_settings.py`, `TOP_NAV_PUBLIC_WORKSPACE_LABEL_CRASH_FIX.md`) + +##### **(v0.250.185)** + +###### Bug Fixes + +* **Analyze Combined Generated Output Routing** + * Treats Analyze requests that ask for row-level answers plus CSV/JSON/XML output as first-class combined durable work, producing both the Markdown analysis artifact and requested structured output artifacts. + * Queues planner-approved combined tabular Analyze work before foreground tabular tools run, preventing empty inline tool output from becoming a stream-level 500. + * Carries the selected model endpoint context into background generated-output runs so non-default endpoints do not fall back to an Azure OpenAI deployment name lookup. + * (Ref: [#1233](https://github.com/microsoft/simplechat/issues/1233), `functions_tabular_orchestration.py`, `functions_workflow_runner.py`, Analyze deliverable contract, combined durable generated output) + +* **Data Management Scheduler Context Guard** + * Prevented the background Data Management scheduler from using request-context-copying executor APIs when no Flask request context exists. + * Scheduler-submitted jobs now use the existing worker-thread path outside request handling, while route-triggered submissions can still use the configured executor. + * (Ref: `functions_data_management.py`, Data Management scheduler, background job submission) + +##### **(v0.250.182)** + +###### Bug Fixes + +* **Analyze Artifact Copilot Review Cleanup** + * Preserved explicit request order for combined JSON/XML artifact requests when both formats share the same action phrase. + * Kept explicit unchanged-copy requests eligible even when source field names include descriptive terms such as risk or status. + * Made semantic validation shadow mode fail open on verifier errors and prevented the chat UI from falling back to withheld legacy artifacts when `generated_artifacts` is explicitly empty. + * (Ref: PR [#1238](https://github.com/microsoft/simplechat/pull/1238), Copilot review comments, generated artifact ordering, semantic validation shadow mode, plural artifact UI) + +##### **(v0.250.181)** + +###### Bug Fixes + +* **Analyze Artifact Advanced Security Cleanup** + * Replaced a self-comparison float finite check in the tabular transformation validator with an explicit finite-number check. + * Simplified an unnecessary callable wrapper in the Phase 7B production-correctness functional test harness. + * (Ref: PR [#1238](https://github.com/microsoft/simplechat/pull/1238), GitHub Advanced Security comments, tabular transformation validation) + +##### **(v0.250.180)** + +###### Bug Fixes + +* **Analyze Artifact Output Contract Closure** + * Made Analyze generated-output delivery Markdown-first and contract-faithful across durable tabular execution by adding reviewed transformation planning, deterministic server-side rules, bounded semantic verification and repair, and exact Search/Analyze 200-row parity validation. + * Hardened artifact-set publication so new staged generated artifacts are not downloadable or promotable until the completed run manifest commits every required member, while preserving legacy generated artifact compatibility. + * Restored explicit Word/DOCX current-turn function-result serialization and repaired cumulative lifecycle, scale, route, and UI validation harnesses through 30,000-row bounded finalization and 100,000-row deterministic planning/hardening contracts. + * (Ref: [#1233](https://github.com/microsoft/simplechat/issues/1233), PR [#1234](https://github.com/microsoft/simplechat/pull/1234), PR [#1235](https://github.com/microsoft/simplechat/pull/1235), PR [#1236](https://github.com/microsoft/simplechat/pull/1236), Analyze deliverable contract, tabular transformation contract, artifact-set publication lifecycle) + +##### **(v0.250.170)** + +###### Bug Fixes + +* **Rendered Admin Tabular Run Controls Coverage** + * Replaced source-only coverage with an authenticated browser regression that verifies the Admin Settings controls render, submit, survive reload, and restore their original values. + * Guarded shared settings mutation behind an explicit isolated-environment opt-in and limited configured-model testing to routable legacy direct or APIM deployments. + * (Ref: [#1201](https://github.com/microsoft/simplechat/issues/1201), `ui_tests/test_admin_tabular_run_controls.py`, Admin Settings tabular run controls) + +##### **(v0.250.169)** + +###### Bug Fixes + +* **Large Tabular Run Confirmation Deduplication** + * Prevented repeated Send clicks or Enter presses from opening concurrent confirmation waiters and starting the same expensive tabular run more than once. + * Restored normal sending after the user continues, narrows scope, dismisses the dialog, or an unexpected confirmation error occurs. + * (Ref: Fixes [#1200](https://github.com/microsoft/simplechat/issues/1200), `chat-messages.js`, `test_chat_background_generated_export_status.py`) + +##### **(v0.250.168)** + +###### Bug Fixes + +* **Tabular Execution Settings Sanitization** + * Prevented normal user-facing settings responses from exposing admin-only hierarchical-analysis, chunk-model deployment, and model-validation retry controls. + * Preserved the durable-run confirmation settings required by chat so users continue to receive prompts before very large tabular runs. + * (Ref: [#1199](https://github.com/microsoft/simplechat/issues/1199), `sanitize_settings_for_user()`, `TABULAR_GENERATION_BACKEND_SETTING_KEYS`) + +##### **(v0.250.167)** + +###### Bug Fixes + +* **Tabular Parity Rollout and Lifecycle Hardening** + * Enforced parity canary assignment before durable execution and included authorized source versions in request and unit fingerprints. + * Preserved failed and canceled durable outputs as terminal incomplete evidence, including all-canceled per-document Analyze results, and corrected Analyze parity telemetry classification. + * Renamed incomplete multi-file and deferred-composition controls as planning-only and exposed that durable fan-out and automatic continuation are unavailable without changing working single-source or per-document behavior. + * (Ref: PR [#1219](https://github.com/microsoft/simplechat/pull/1219), [#1031](https://github.com/microsoft/simplechat/issues/1031), [#1055](https://github.com/microsoft/simplechat/issues/1055), [#1058](https://github.com/microsoft/simplechat/issues/1058), `functions_tabular_orchestration.py`, `functions_workflow_runner.py`, `route_backend_chats.py`) + +##### **(v0.250.166)** + +###### New Features + +* **Tabular Analyze/Search Durable Preflight Parity** + * Unified exhaustive tabular Search and Analyze requests behind a shared route-neutral planner that can queue durable work before bounded foreground tools or immediate synthesis run. + * Preserved truthful pending, failed, canceled, and completed evidence across pure tabular, mixed-source, per-document, and multi-table workflows, including deferred mixed-source composition and public lifecycle coverage. + * Added backend-only shadow and canary controls, privacy-safe telemetry and status metadata, and evidence-backed legacy fallback retirement while reusing existing authorization, source-version, rollback, and artifact-card contracts. + * (Ref: [#1031](https://github.com/microsoft/simplechat/issues/1031), [#1055](https://github.com/microsoft/simplechat/issues/1055), [#1058](https://github.com/microsoft/simplechat/issues/1058), `functions_tabular_orchestration.py`, `functions_workflow_runner.py`, `route_backend_chats.py`) + +##### **(v0.250.160)** + +###### Bug Fixes + +* **getAToken Missing Authorization Code Redirect** + * Redirects direct `/getAToken` browser visits without an OAuth authorization code back to the home sign-in page instead of showing a technical callback error. + * Preserves the normal Microsoft Entra authorization-code callback flow and keeps `/getATokenApi` explicit error behavior unchanged for API token callbacks. + * (Ref: `/getAToken` OAuth callback, `route_frontend_authentication.py`, `test_getatoken_missing_code_redirect.py`) + +##### **(v0.250.159)** + +###### New Features + +* **Chat Used Documents Pane** + * Added a Used Documents mode to the existing chat conversation side pane so users can review documents that were actually cited in the conversation without opening the full details modal. + * Reuses the same conversation metadata document tags as the details modal, excludes selected-but-unused documents, and auto-opens once when cited documents first appear. + * (Ref: [#1209](https://github.com/microsoft/simplechat/issues/1209), conversation contents drawer, cited document metadata, `chat-conversation-contents.js`, `chat-conversation-details.js`) + +##### **(v0.250.157)** + +###### Bug Fixes + +* **Prior Grounded Source Continuity** + * Follow-up mixed-source turns can now detect references such as "that XML file," "same template," or "previous spreadsheet" and merge reauthorized prior grounded sources with the current selected sources. + * Preserved authorization boundaries by deriving prior sources from `last_grounded_document_refs` and revalidating scope before use. + * (Ref: [#1204](https://github.com/microsoft/simplechat/issues/1204), mixed-source source continuity, `route_backend_chats.py`, `test_chat_history_grounded_follow_up_fix.py`) + +##### **(v0.250.156)** + +###### Bug Fixes + +* **JSON/XML Source-Only Intent Guardrails** + * Prevented source-reading prompts such as "Summarize this XML document" and "Validate this JSON object" from being misclassified as generated artifact requests. + * Kept explicit output requests such as "Export as JSON" and "Create an XML file" routed to generated artifact workflows. + * (Ref: [#1198](https://github.com/microsoft/simplechat/issues/1198), structured artifact intent detection, `functions_generated_file_exports.py`, `test_generated_json_xml_exports.py`) + +##### **(v0.250.155)** + +###### Bug Fixes + +* **Tabular Contains Replay Semantics** + * Aligned foreground `filter_rows contains` matching with durable CSV replay by using literal, case-insensitive containment in both paths. + * Added regression coverage for regex-shaped values such as `A.*` so previews and generated export replays select the same row cohort. + * (Ref: [#1197](https://github.com/microsoft/simplechat/issues/1197), tabular durable replay descriptors, `tabular_processing_plugin.py`, `test_tabular_large_result_pagination.py`) + +##### **(v0.250.129)** + +###### New Features + +* **Configurable Workflow Task Limit** + * Added an admin setting that controls how many ordered instruction tasks users can add to a workflow. + * The default is 50 tasks, with backend and browser enforcement clamped to a supported range of 1-100 tasks. + * (Ref: workflow task sequences, Admin Settings Workflow section, `functions_personal_workflows.py`, `workspace_workflows.js`) + +##### **(v0.250.128)** + +###### Bug Fixes + +* **Outbound MCP Tool Argument Normalization** + * Fixed outbound MCP tool calls that could wrap parameters inside a `kwargs` object, preventing standards-compliant MCP servers from seeing required top-level fields such as `type`. + * Added schema-aware normalization before MCP argument validation and invocation while preserving tools that explicitly define a real top-level `kwargs` property. + * (Ref: [#1163](https://github.com/microsoft/simplechat/issues/1163), MCP `tools/call` arguments, `functions_mcp_operations.py`, `mcp_plugin.py`, `mcp_plugin_factory.py`) + +##### **(v0.250.127)** + +###### Bug Fixes + +* **Replayable Exhaustive Tabular Exports** + * Generalized version-pinned CSV source descriptors so exhaustive `filter_rows` and `search_rows` requests can replay the complete authorized cohort through the existing durable export runner instead of failing on bounded preview gaps. + * Added exhaustive per-row request routing for natural phrases such as "for each row," "every row," and "one row per," while preserving direct deterministic aggregation behavior. + * Non-replayable semantics such as normalized entity matching now fail closed with an explicit reason and never publish a partial CSV. + * (Ref: [#1031](https://github.com/microsoft/simplechat/issues/1031), tabular source descriptors, durable generated exports, `functions_tabular_csv_query.py`, `tabular_processing_plugin.py`, `route_backend_chats.py`) + +##### **(v0.250.126)** + +###### Bug Fixes + +* **Enhanced Citations Startup Storage Degradation** + * Prevented Enhanced Citations storage connectivity problems from blocking application startup when Cosmos DB is otherwise available. + * Deferred live storage container checks to upload/admin-test paths and added Admin Settings diagnostics for explicit storage validation. + * (Ref: [#1155](https://github.com/microsoft/simplechat/issues/1155), PR [#1161](https://github.com/microsoft/simplechat/pull/1161), Enhanced Citations storage startup, `config.py`, `functions_documents.py`, Admin Settings) + +* **Functional Test Version Assertion Resilience** + * Added shared functional-test version helpers so tests can assert the app version is at least the feature implementation version instead of exactly equal to an older release. + * Migrated brittle exact `config.py` version checks and added a guardrail test to prevent reintroducing exact app-version assertions. + * (Ref: functional test version helpers, `test_support/versioning.py`, `test_app_version_assertion_guardrails.py`) + +##### **(v0.250.125)** + +###### Bug Fixes + +* **Logging Tag Standardization** + * Standardized Python logging prefixes to `[UPPERCASE_WITH_UNDERSCORES]` so Application Insights, debug logs, and operational searches use consistent tag names. + * Added a logging tag reference inventory and functional coverage to keep future logging tags normalized and documented. + * (Ref: logging tag inventory, `docs/reference/logging-tags.md`, `test_logging_tag_standardization.py`) + +##### **(v0.250.124)** + +###### Bug Fixes + +* **Key Vault Reminder PR Security Hardening** + * Replaced raw exception text returned from plugin/action Key Vault save paths with stable user-safe messages while preserving server-side logging for diagnostics. + * Renamed the external Key Vault reminder telemetry event to avoid security scanner false positives on secret-related terminology while keeping queryable Application Insights dimensions. + * (Ref: [#1156](https://github.com/microsoft/simplechat/issues/1156), PR [#1157](https://github.com/microsoft/simplechat/pull/1157), `route_backend_plugins.py`, `functions_appinsights.py`, CodeQL findings) + +##### **(v0.250.123)** + +###### New Features + +* **Key Vault Reminder Contact Email Telemetry Opt-In** + * Added a default-off admin setting that allows Key Vault reminder contact email addresses to be included in the external Application Insights telemetry event for direct Azure Monitor, Logic App, Function, or webhook routing. + * Kept raw secret names redacted from external telemetry and added Reminder ID visibility/search in the admin inventory for fixed admin-channel alert workflows. + * (Ref: [#1156](https://github.com/microsoft/simplechat/issues/1156), `functions_appinsights.py`, `functions_keyvault_reminders.py`, Admin Key Vault reminder external alert guidance) + +##### **(v0.250.122)** + +###### New Features + +* **Key Vault Reminder External Telemetry** + * Added a privacy-safe, queryable Application Insights event when Key Vault expiration reminder notifications are created, enabling Azure Monitor scheduled query alerts, action groups, Logic Apps, Functions, or webhooks for external notification workflows. + * Added admin and feature documentation guidance with a sample KQL query while avoiding raw secret names and email values in telemetry dimensions. + * (Ref: [#1156](https://github.com/microsoft/simplechat/issues/1156), `functions_appinsights.py`, `functions_keyvault_reminders.py`, Azure Monitor external notification guidance) + +##### **(v0.250.121)** + +###### New Features + +* **Key Vault Expiration Reminder Inventory** + * Added SimpleChat-managed Key Vault secret expiration reminder tracking with per-action reminder metadata, expiration dates, lead days, reminder contact email, friendly labels, and rotation notes. + * Added an admin Key Vault reminder dashboard that maps generated Key Vault secret names back to SimpleChat scope, source action, field, owner/contact context, sync status, and remediation details. + * Added a background reminder sweep and `key_vault_secret_expiring` in-app notifications while preserving Azure Monitor/Event Grid as the recommended email alert path. + * (Ref: [#1156](https://github.com/microsoft/simplechat/issues/1156), `functions_keyvault_reminders.py`, `admin_settings.html`, `plugin_modal_stepper.js`, Key Vault reminder inventory) + +###### Bug Fixes + +* **Reliable Key Vault Secret Rotation** + * Fixed action secret save behavior so replacing a Key Vault-backed secret with a new literal value writes a new Key Vault version for global, group, and personal actions. + * `Stored_In_KeyVault` placeholders now only preserve validated existing references; placeholder-only saves without an existing secret are rejected instead of creating dead references. + * Key Vault write failures now surface as errors instead of falling back to raw secret persistence. + * (Ref: [#1156](https://github.com/microsoft/simplechat/issues/1156), `functions_keyvault.py`, action save helpers, Key Vault secret reference validation) + +##### **(v0.250.119)** + +###### Bug Fixes + +* **Duplicate Chat Stream JSON Import Cleanup** + * Removed the redundant local `json` import from the chat streaming route while keeping the existing module-level import, clearing the PR #1145 CodeQL duplicate-module-import notice without changing streaming behavior. + * Updated the PR 1145 remediation plan with the implementation version and validation results. + * (Ref: [#1145](https://github.com/microsoft/simplechat/pull/1145), `route_backend_chats.py`, CodeQL alert 30) + +##### **(v0.250.118)** + +###### Bug Fixes + +* **Semantic Kernel Return Contract Cleanup** + * Made the nested chat Semantic Kernel invocation helper return `None` explicitly when an async generator completes without yielding, clearing the PR #1145 CodeQL mixed explicit/implicit return alert without changing runtime behavior. + * Added focused functional coverage for direct values, coroutine results, yielded async-generator values, and empty async generators. + * (Ref: [#1145](https://github.com/microsoft/simplechat/pull/1145), `route_backend_chats.py`, `test_chat_semantic_kernel_return_contract.py`) + +##### **(v0.250.115)** + +###### Bug Fixes + +* **Token Usage Aggregation Fixture Cleanup** + * Removed duplicate mocked helper keys from the document action token usage aggregation functional test so the fixture intent is explicit and CodeQL no longer reports overwritten dictionary entries. + * Kept comparison coverage focused on cross-format compare behavior while preserving aggregate token usage assertions for analysis, comparison, workflow assistant persistence, and chat persistence markers. + * (Ref: [#1145](https://github.com/microsoft/simplechat/pull/1145), `test_document_action_token_usage_aggregation.py`, token usage aggregation fixtures) + +##### **(v0.250.114)** + +###### Bug Fixes + +* **Foundry Citation Thought Detail Cleanup** + * Fixed a CodeQL finding where Foundry citation thoughts iterated citations without using the citation value, causing duplicate generic thought messages. + * Foundry citation thoughts now include safe citation-specific labels when available while avoiding raw payloads, URL query strings, userinfo, and long unbounded text. + * (Ref: [#1145](https://github.com/microsoft/simplechat/pull/1145), `route_backend_chats.py`, `test_foundry_citation_thoughts.py`) + +##### **(v0.250.114)** + +###### New Features + +* **Generated JSON and XML Export Artifacts** + * JSON and XML generation requests can now save valid generated output as downloadable chat artifacts instead of leaving large file-shaped content in the assistant response. + * Document Analyze and generated export flows now recognize natural JSON/XML conversion and XML template-population phrasing, with XML serialization support added to durable generated exports. + * XML document processing now uses a consolidated token-aware pipeline for more reliable analysis and export workflows. + * (Ref: [#1071](https://github.com/microsoft/simplechat/issues/1071), `functions_generated_file_exports.py`, generated analysis artifacts, XML document processing) + +##### **(v0.250.112)** + +###### New Features + +* **Model Capability Catalog** + * Added an initial JSON source of truth for model feature capabilities across OpenAI GPT-5+, recent Claude models, Meta Llama and Code Llama, xAI Grok, and Microsoft Phi/MAI models. + * Catalog entries track support for text, image, audio, video, binary/file input, coding optimization, tool calling, and structured output so future multimodal routing can move away from regex-only model-name checks. + * This release is data-only and does not change backend or frontend runtime behavior. + * (Ref: Closes [#1147](https://github.com/microsoft/simplechat/issues/1147), `model_capabilities.json`, model capability detection) + +##### **(v0.250.111)** + +###### Bug Fixes + +* **Data Management Restore Route Registration** + * Fixed application startup failure caused by duplicate Data Management restore review route and endpoint registrations. + * Preserved the authorization-aware restore review workflow and added regression coverage requiring unique Blueprint endpoint names. + * (Ref: `route_backend_data_management.py`, `test_data_management_security_patterns.py`, `DATA_MANAGEMENT_RESTORE_ROUTE_ENDPOINT_COLLISION_FIX.md`) + +##### **(v0.250.110)** + +###### New Features + +* **Configurable Public Workspace Display Name** + * Admins can now set an optional end-user display name for Public Workspace, capped at 32 characters, so organizations can present tenant-specific terms such as "Domain Knowledge". + * End users see the configured label across navigation, Profile, Public Directory, Public Workspace pages, chat scope selection, and related browser messages while admin settings and internal identifiers continue to use Public Workspace/public_workspace. + * Empty or unset values preserve the existing Public Workspace/Public Workspaces defaults. + * (Ref: [#1146](https://github.com/microsoft/simplechat/issues/1146), `functions_settings.py`, `admin_settings.html`, public workspace templates and JavaScript, `PUBLIC_WORKSPACE_DISPLAY_NAME.md`) + +##### **(v0.250.109)** + +###### New Features + +* **Per-Model Response Length Overrides** + * Administrators can now set an optional response-length/output-token ceiling on each model in global multi-endpoint GPT configuration. + * Standard chat applies the selected model's configured ceiling with the correct backend token parameter for GPT-5/o-series aliases and other OpenAI-compatible chat models. + * Existing endpoint model records remain compatible when the field is blank or absent. + * (Ref: Closes [#1143](https://github.com/microsoft/simplechat/issues/1143), related [#1047](https://github.com/microsoft/simplechat/issues/1047) and [#358](https://github.com/microsoft/simplechat/issues/358), `functions_settings.py`, `route_backend_chats.py`, `admin_model_endpoints.js`) + +##### **(v0.250.108)** + +###### User Interface Enhancements + +* **Backup, Migrate & Restore Admin Refresh** + * Reworked the Admin Settings data-management tab into a clearer Backup, Migrate & Restore control center with start-here guidance, setup modals, and plain-language migration choices. + * Separated destination Cosmos **RU Boost** configuration and testing from Cosmos data-copy access validation so admins can verify the correct Azure management-plane permissions before migration. + * Aligned the refresh with the restore workflow from Backup Inventory so admins can review backup readiness, choose restore policy/surfaces, run preflight, and queue supported restore jobs. + * (Ref: [#1140](https://github.com/microsoft/simplechat/issues/1140), `admin_settings.html`, `admin_data_management.js`, `functions_data_management.py`, Data Management docs and tests) + +##### **(v0.250.107)** + +###### Bug Fixes + +* **Mixed Source Manifest Storage Locator Preservation** + * Preserved explicit blob storage locators for authorized non-chat mixed-source manifest entries when archived-revision document metadata already contains a resolved container and blob path. + * Updated focused mixed-source Analyze and conversation-continuity tests for the current rollout/version contract. + * (Ref: [#1055](https://github.com/microsoft/simplechat/issues/1055), [#1056](https://github.com/microsoft/simplechat/issues/1056), mixed-source manifests, `functions_mixed_source_orchestration.py`, `test_mixed_source_manifest_contracts.py`) + +##### **(v0.250.106)** + +###### New Features + +* **Backup Cleanup and Retention Policy Controls** + * Added Data Management backup cleanup controls so administrators can manually delete backup artifacts and metadata from Backup Inventory. + * Added unit-based backup retention settings for days, weeks, months, and years, with automatic cleanup that preserves the newest successful full backup as a restore safety baseline. + * Cleanup removes stored backup blobs, job timeline records, and differential sidecar state so future partial backups re-export affected unchanged items instead of pointing to deleted artifacts. + * (Ref: Closes [#1130](https://github.com/microsoft/simplechat/issues/1130), `functions_data_management.py`, `route_backend_data_management.py`, `admin_settings.html`, `admin_data_management.js`) + +* **Multi-Select Metadata Extraction** + * Personal, group, and public workspace document multi-select bars now include an **Extract Metadata** action when metadata extraction is enabled. + * Selected documents are queued through the shared metadata extraction background workflow, preserving generated titles along with authors, abstracts, keywords, publication dates, and organization metadata. + * (Ref: Closes [#1134](https://github.com/microsoft/simplechat/issues/1134), `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`, workspace document multi-select actions) + +* **Data Management Backup Restore Workflow** + * Added an admin-only restore workflow for completed Data Management backups, with manifest preflight, create-only default policy, explicit overwrite confirmation, durable restore jobs, cancellation/retry support, and sanitized progress in Job History. + * Restore supports configured target Cosmos DB, AI Search, and Enhanced Citation blob targets while preserving secret-safe review and job responses. + * (Ref: Closes [#1091](https://github.com/microsoft/simplechat/issues/1091), `functions_data_management.py`, `functions_data_management_restore_state.py`, `route_backend_data_management.py`, `admin_settings.html`, `admin_data_management.js`, `DATA_MANAGEMENT_RESTORE.md`) + +###### Bug Fixes + +* **Retry and Edit Streaming Parity** + * Retry and edit chat flows now use the same full SSE streaming path as first-send chat, restoring live token updates, streamed thoughts, stop controls, and recovery behavior. + * The stream path reuses the retry/edit user message and thread metadata created by the preparation endpoints, preserving carousel attempt history without duplicating user messages. + * (Ref: Fixes [#963](https://github.com/microsoft/simplechat/issues/963), `route_backend_chats.py`, `chat-retry.js`, `chat-edit.js`, `test_chat_retry_edit_streaming_parity.py`) + +###### User Interface Enhancements + +* **Custom Pages Admin Open Action** + * Added an Open action to the Admin Settings Custom Pages table so administrators can launch enabled static or Python-backed custom pages directly from their metadata row. + * The action opens encoded `/custom/` URLs in a new tab while preserving existing Custom Pages route authorization, enabled-state checks, access-level rules, role restrictions, and `.html` alias compatibility. + * Disabled or unavailable pages now show a disabled Open action with explanatory tooltip copy instead of silently omitting the action. + * (Ref: Closes [#951](https://github.com/microsoft/simplechat/issues/951), PR [#1131](https://github.com/microsoft/simplechat/pull/1131), `admin_custom_pages.js`, `CUSTOM_PAGES.md`) + +##### **(v0.250.105)** + +###### User Interface Enhancements + +* **Reviewed, Scalable Data Migration Workflow** + * Replaced the Admin Data Management migration form with a six-stage Target, Scope, Content & Options, Review, Confirm, and Progress workflow. + * Added server-paginated principal catalogs, exhaustive all-mode counts, persistent cross-page selections, sanitized preflight checks, single-use administrator-bound review authorization, settings-drift protection, separate destructive confirmation, duplicate-submit prevention, and inline durable job recovery controls. + * (Ref: Closes [#1097](https://github.com/microsoft/simplechat/issues/1097), `functions_data_management.py`, `route_backend_data_management.py`, `admin_settings.html`, `admin_data_management.js`) + +##### **(v0.250.103)** + +###### New Features + +* **Configurable AI Response Completion Audio Cues** + * Administrators can enable locally bundled completion sounds, while each user can opt in, choose and preview one of ten cues, set volume, or mute cues without losing their preferences. + * Cues play once for newly completed personal-chat responses outside the active visible conversation, with server-authoritative gating, cross-tab preference synchronization, and historical/duplicate suppression. + * (Ref: Closes [#1062](https://github.com/microsoft/simplechat/issues/1062), `completion-audio-cues.js`, notification polling, Profile and Admin Settings, `AI_RESPONSE_COMPLETION_AUDIO_CUES.md`) + +###### Bug Fixes + +* **Retention Coverage Across Group and Collaborative Conversations** + * Group-scoped private conversations now follow their primary group's retention policy instead of the creator's personal policy. + * Personal and group collaborative conversations now use their correct governing policy and activity timestamp, while linked conversion sources are cleaned once without duplicate counting. + * Collaboration cleanup now covers messages, per-user state, linked sources, blob-backed files, thoughts, activity logs, and conversation caches; new groups also persist explicit default retention values. + * (Ref: Closes [#1054](https://github.com/microsoft/simplechat/issues/1054), `functions_retention_policy.py`, `functions_collaboration.py`, `functions_group.py`, `RETENTION_POLICY_CONVERSATION_SCOPE_COVERAGE_FIX.md`) + +* **Custom Databricks-Prefixed Action Discovery** + * Fixed action type discovery so custom plugin types such as `databricks_table_dscmo` no longer inherit the built-in Databricks discovery defaults. + * Custom Databricks-prefixed plugin types now stay on the standard plugin configuration path and visual treatment unless their type is exactly `databricks` or `databricks_table`. + * Added a regression test that scaffolds a temporary fake custom Databricks-prefixed plugin, schema, and definition file to validate discovery and settings merge behavior. + * (Ref: [#1124](https://github.com/microsoft/simplechat/issues/1124), `functions_databricks_operations.py`, `route_backend_plugins.py`, `view-utils.js`, `test_plugin_type_discovery_custom_databricks.py`) + +##### **(v0.250.102)** + +###### New Features + +* **High-Throughput Resumable Source Blob Backups** + * Source document backups now stream bounded Azure SDK blocks with configurable file concurrency and chunk size instead of buffering complete blobs or copying files serially. + * Added durable per-file verification and resume, source/target generation fencing, adaptive Retry-After-aware throttling, isolated file failures, authenticated chunked encryption, throughput telemetry, and a reproducible AzCopy/server-copy/SDK benchmark harness. + * (Ref: Closes [#1095](https://github.com/microsoft/simplechat/issues/1095), `functions_data_management.py`, `test_data_management_blob_backup_transfers.py`, `benchmark_data_management_blob_backup.py`, `DATA_MANAGEMENT_BLOB_BACKUP_THROUGHPUT.md`) + +* **Desktop Conversation Notifications** + * Administrators can enable operating system notifications for completed AI responses, and users can manage their own preference from Profile. + * Notifications appear only while SimpleChat is open in a hidden or unfocused tab, show the application and conversation titles without response content, and focus the existing tab when selected. + * (Ref: Fixes [#866](https://github.com/microsoft/simplechat/issues/866), `chat-desktop-notifications.js`, `chat-streaming.js`, Profile and Admin Settings) + +* **Automatic Overnight Control Center Statistics Refresh** + * Added an enabled-by-default daily Control Center metrics refresh at 2:00 AM Eastern, with an administrator toggle and configurable time under Admin Settings > Control Center. + * The recurring schedule follows Eastern daylight-saving changes, stores concrete execution timestamps in UTC, and shows last-run and next-run values in each administrator's browser timezone. + * (Ref: Closes [#706](https://github.com/microsoft/simplechat/issues/706), `functions_control_center.py`, `background_tasks.py`, `admin_settings.html`, `control-center.js`) + +* **Configurable Chat AI Notice** + * Administrators can display custom plain-text AI guidance directly below the chat composer. + * Supports non-dismissible, per-session, daily, and once-per-message-version behavior with validated dismissal persistence and automatic redisplay when the configured notice changes. + * (Ref: [#715](https://github.com/microsoft/simplechat/issues/715), `functions_ai_notice.py`, `admin_settings.html`, `chats.html`, `chat-ai-notice.js`) + +* **Per-Message Audio Export** + * Users can export completed user and assistant chat messages as MP3 audio when text-to-speech is enabled. + * Downloads reuse the active Azure Speech voice and speed, include only visible message text, and remain transient without storing generated audio in SimpleChat. + * (Ref: [#628](https://github.com/microsoft/simplechat/issues/628), `chat-tts.js`, `chat-message-export.js`, `chat-messages.js`, `MESSAGE_AUDIO_EXPORT.md`) + +###### User Interface Enhancements + +* **External Link Ordering Controls** + * Admins can now move saved external links up or down and save the resulting navigation order without deleting and recreating links. + * The first and last links expose disabled boundary controls, and the visible order stays synchronized with the Admin Settings save payload. + * (Ref: Closes [#793](https://github.com/microsoft/simplechat/issues/793), `admin_settings.js`, `test_admin_external_link_ordering.py`, `EXTERNAL_LINK_ORDERING_FIX.md`) + +* **Application-Wide Non-Blocking Toast Notifications** + * Replaced native browser alerts across admin, group, public, personal workspace, profile, feedback, safety, and control-center workflows with consistent Bootstrap toast notifications. + * Added a shared, accessible toast utility that safely renders dynamic messages as text and preserves specialized chat toast positioning. + * (Ref: Closes [#739](https://github.com/microsoft/simplechat/issues/739), `toast.js`, `chat-toast.js`, first-party templates and workspace scripts) + +##### **(v0.250.101)** + +###### New Features + +* **Adaptive Exhaustive Azure AI Search Backups** + * Azure AI Search backups now export personal, group, and public indexes through deterministic keyset-paged artifacts with durable checkpoints, exact resume behavior, schema validation, and restore-readiness integrity status. + * Added fair bounded concurrency, Retry-After-aware handling for throttling and service interruptions, adaptive pressure reduction and recovery, and sanitized per-index throughput and failure metrics. + * (Ref: Closes [#1094](https://github.com/microsoft/simplechat/issues/1094), `functions_data_management.py`, `test_data_management_ai_search_backup_export.py`, `DATA_MANAGEMENT_BACKUP_MIGRATION.md`) + +* **Conversation Context Grounding** + * Models and agents now receive bounded, credential-sanitized metadata for every user turn, including the active model, SimpleChat version, workspace scope, selected documents, agent, and capability state. + * Each assistant response exposes the identical snapshot as a visible Conversation Context citation across streaming, non-streaming, retry, fallback, collaboration, and document-action paths. + * (Ref: [#508](https://github.com/microsoft/simplechat/issues/508), `functions_conversation_context.py`, `route_backend_chats.py`, `functions_workflow_runner.py`) + +###### Bug Fixes + +* **Conversation Fork Workspace Context and HTTP 500 Fix** + * Fixed conversation forks returning HTTP 500 when an owned single-user conversation used group or public workspace knowledge. + * Forking now revalidates current workspace access, preserves the authorized context and chat type, and returns controlled conflicts when access is stale or unavailable. + * Corrected fork-specific structured logging so validation, conflict, cleanup, and cache errors retain their intended response behavior. + * (Ref: [#1025](https://github.com/microsoft/simplechat/issues/1025), `functions_simplechat_operations.py`, `route_backend_conversations.py`, `chat-messages.js`, `CONVERSATION_FORK_HTTP_500_FIX.md`) + +* **Application-Wide Log Event Contract Guard** + * Fixed conversation fork conflict and recovery logging that used unsupported metadata keywords, preventing logger errors from replacing intended HTTP responses such as eligibility conflicts with HTTP 500. + * Standardized structured metadata on `extra=` and added an application-wide call-signature check plus route regression coverage for the HTTP 409 conflict path. + * (Ref: [#1112](https://github.com/microsoft/simplechat/issues/1112), `functions_simplechat_operations.py`, `route_backend_conversations.py`, `test_log_event_call_contract.py`) + +##### **(v0.250.100)** + +###### Bug Fixes + +* **MCP PR CodeQL Cleanup** + * Resolved CodeQL findings from the MCP pull request by replacing exception text returned to clients with stable public messages, tightening inbound MCP correlation IDs, and removing raw query text from search/cache logs. + * Cleaned up MCP catalog imports, test stubs, and JavaScript defaults that produced CodeQL note-level findings. + * (Ref: [#1013](https://github.com/microsoft/simplechat/issues/1013), CodeQL scan, MCP PR readiness, `functions_appinsights.py`, `route_inbound_mcp.py`, `route_backend_plugins.py`) + +##### **(v0.250.098)** + +###### New Features + +* **MCP Current-State Platform** + * Added the governed inbound SimpleChat MCP server with a bounded personal tool surface for conversations, documents, prompts, tags, workflow discovery, and workflow execution. + * Hardened outbound MCP actions with presets, server-side preconfiguration catalogs, destination governance, custom headers, result policy controls, and redaction-safe discovery/runtime telemetry. + * (Ref: [#1013](https://github.com/microsoft/simplechat/issues/1013), [#1014](https://github.com/microsoft/simplechat/issues/1014), [#1015](https://github.com/microsoft/simplechat/issues/1015), [#1017](https://github.com/microsoft/simplechat/issues/1017), [#1018](https://github.com/microsoft/simplechat/issues/1018), MCP current-state roadmap) + +###### User Interface Enhancements + +* **MCP Admin And Observability Surfaces** + * Added Admin Settings controls for inbound MCP runtime settings, source governance guidance, Easy Auth setup verification, request-size and throttle tuning, tool registry visibility, and copyable Application Insights starter queries. + * Added Governance controls for outbound MCP destination policies and inbound MCP source policies using the current source-first access model. + * (Ref: [#1020](https://github.com/microsoft/simplechat/issues/1020), MCP governance/admin UX, `admin_settings.html`, `admin_settings.js`, `admin_governance.js`) + +###### Bug Fixes + +* **MCP Enterprise Hardening** + * Added inbound MCP request correlation, bounded payloads, Cosmos-backed tool throttles, clear JSON-RPC tool error transport, and OAuth/PRM discovery compatibility for MCP clients. + * Added outbound MCP discovery/factory telemetry with safe destination metadata and redaction to make connector failures easier to diagnose. + * (Ref: [#1015](https://github.com/microsoft/simplechat/issues/1015), [#1017](https://github.com/microsoft/simplechat/issues/1017), [#1020](https://github.com/microsoft/simplechat/issues/1020), MCP observability and enterprise readiness) + +##### **(v0.250.076)** + +###### New Features + +* **Bounded Parallel Cosmos Backup Export and Source Capacity Recovery** + * Cosmos backup export now streams deterministic JSONL checkpoint batches through configurable bounded concurrency, preserving durable fencing, latest-item state, cancellation, retry/resume, and recovery semantics without materializing complete containers in memory. + * Added bounded `408`, `429`, `449`, and `5xx` retry with Retry-After-aware jittered backoff, adaptive staging pressure, sanitized per-container and aggregate RU/rate/retry telemetry, and deterministic no-replay checkpoint outcomes. + * Added opt-in local/source Cosmos throughput boosts capped at 10,000 RU/s with topology discovery, immutable pre-mutation snapshots, fenced restore-pending recovery, safe external-change protection, minimum ARM role support in Terraform, and explicit fail-or-continue policy for unsupported or denied capacity mutations. + * (Ref: Closes [#1093](https://github.com/microsoft/simplechat/issues/1093), `functions_data_management.py`, `admin_data_management.js`, `test_data_management_backup_parallelism.py`, `DATA_MANAGEMENT_BACKUP_MIGRATION.md`) + +##### **(v0.250.075)** + +###### New Features + +* **Admin Feedback and Safety Record Lifecycle** + * Added archive, unarchive, and permanently delete actions to the Feedback Review and Safety Violations admin pages, with active/archived filtering across lists, cards, statistics, pagination, and CSV exports. + * Archived records are hidden from user profile history, destructive deletion requires confirmation, and safety violations with pending remediation approvals cannot be deleted. + * Archive, unarchive, and delete actions create non-sensitive admin activity audit records, while audit persistence failures are surfaced without undoing successful lifecycle changes. + * (Ref: [#991](https://github.com/microsoft/simplechat/issues/991), `functions_review_lifecycle.py`, `route_backend_feedback.py`, `route_backend_safety.py`, `ADMIN_REVIEW_RECORD_LIFECYCLE.md`) + +* **File Processing Log Cleanup** + * Added admin controls to permanently delete file-processing logs older than a chosen number of days, weeks, or fixed 30-day months, or delete every stored log through a separate action. + * Added explicit confirmation, exact and partial deletion counts, admin activity logging, validation, and secured cross-partition Cosmos DB cleanup. + * (Ref: [#398](https://github.com/microsoft/simplechat/issues/398), `functions_logging.py`, `route_frontend_admin_settings.py`, `admin_settings.js`, `FILE_PROCESSING_LOG_CLEANUP.md`) + +##### **(v0.250.074)** + +###### New Features + +* **Conversation Contents Drawer** + * Added an admin-controlled, default-on conversation contents drawer that indexes persisted user messages and lets users jump directly to earlier prompts in long chats. + * Added a default-on user profile preference so each user can hide the drawer while the global admin feature remains enabled. + * (Ref: [#1026](https://github.com/microsoft/simplechat/issues/1026), `chat-conversation-contents.js`, `admin_settings.html`, `profile.html`) + +* **Fork Personal Conversations from Assistant Responses** + * Added a Fork conversation action for persisted assistant messages, creating an independent personal conversation containing the active history through the selected response while leaving the source unchanged. + * Forks remap conversation, message, thread, reply, and artifact identifiers; copy blob-backed attachments to independent paths; reject unauthorized or changed sources; and clean up failed copies before they become visible. + * Added confirmation, duplicate-click prevention, failure feedback, immediate fork navigation, backend regression coverage, and browser workflow coverage. + * (Ref: [#1025](https://github.com/microsoft/simplechat/issues/1025), `functions_simplechat_operations.py`, `route_backend_conversations.py`, `chat-messages.js`, `FORK_CONVERSATION.md`) + +###### User Interface Enhancements + +* **Responsive Long-Chat Navigation** + * Added safe plain-text labels, active-location tracking, keyboard focus management, destination highlighting, and persistent desktop or off-canvas mobile layouts. + * (Ref: [#1026](https://github.com/microsoft/simplechat/issues/1026), `chats.html`, `chats.css`, `test_chat_conversation_contents_drawer.py`) + +##### **(v0.250.073)** + +###### New Features + +* **User Font Size Preferences** + * Added persisted XS, S, M, L, and XL font-size choices to the user profile, ranging from 75% to 200% with medium as the default. + * Font-size selections preview immediately and apply across SimpleChat after the user saves the preference. + * (Ref: [#1099](https://github.com/microsoft/simplechat/issues/1099), `profile.html`, `functions_settings.py`, `FONT_SIZE_AND_200_PERCENT_ZOOM_FIX.md`) + +* **Durable Data Management Backup Jobs** + * Full and partial backups now persist immutable plans and source cutoffs, fenced attempts, resource/batch checkpoints, and latest-only Cosmos, AI Search, and Blob item state without mutating source records or metadata. + * Added source-scoped overlap protection, authenticated cancellation and focused retry/resume controls, stale/queued worker recovery, bounded sanitized progress, and explicit non-destructive differential/deletion semantics in backup manifests. + * (Ref: Closes [#1092](https://github.com/microsoft/simplechat/issues/1092), `functions_data_management.py`, `functions_data_management_backup_state.py`, `DATA_MANAGEMENT_BACKUP_MIGRATION.md`) + +###### User Interface Enhancements + +* **200% Zoom and Large-Text Layout Support** + * Updated Chat, top navigation, classification banners, and sidebar scrolling to reserve font-relative space and keep messages, navigation, tools, and the composer reachable at 200% browser zoom and large saved font sizes. + * (Ref: [#1099](https://github.com/microsoft/simplechat/issues/1099), `chats.css`, `navigation.css`, `sidebar.css`) + +##### **(v0.250.072)** + +###### Bug Fixes + +* **Selected Public Workspace Prompt Migration** + * Selected public-workspace Data Management migrations now copy current prompts owned through `public_id` while retaining compatibility with legacy `public_workspace_id` records. + * Prompts outside the selected workspaces remain excluded, transitional records migrate once, and copied prompt artifact counts are accurate. All-workspaces migration behavior is unchanged. + * (Ref: [#1033](https://github.com/microsoft/simplechat/issues/1033), `functions_data_management.py`, `test_data_management_public_prompt_migration.py`) + +##### **(v0.250.071)** + +###### New Features + +* **Resilient Data Management Migrations** + * Added durable migration provenance, per-resource checkpoints, bounded concurrent transfers, retry/resume controls, and long-running Cosmos, Search, Blob, inventory, and reconciliation heartbeats. + * Added New only, Delta / upsert, and explicitly confirmed Mirror with deletions modes with durable baseline watermarks, migration-owned update/delete protection, and server-owned live previews. + * Added post-copy Cosmos, AI Search, and Blob reconciliation with cutover readiness, actual outcome totals, and preview-versus-actual divergence reporting. + * Replaced deep AI Search pagination with persisted `id` keyset cursors, including selected-scope filter batching and coverage beyond 100,000 documents. + * Added memory-bounded reconciliation, two-phase mirror deletion with current-source/ETag revalidation, target-side coordination across independent SimpleChat sources, and retryable `not_ready` cutover failures. + * Preserved Blob content settings, metadata, tags, tier, type, and available content verification while adding mid-stream lease heartbeats, cooperative cancellation, source ETag stability, and pending-to-succeeded provenance. + * Added durable privacy-safe per-item outcome batches plus admin JSONL manifest and failure-list downloads. + * Made unresolved temporary Cosmos capacity restoration a retryable terminal state; retry restores the saved snapshot without reapplying the boost. + * Added a durable target AI Search write fence that drains normal SimpleChat indexing before transfer or mirror deletion, bounds Search requests, rejects self-targeting migrations, and requires explicit external-writer freeze acknowledgement before Search writes begin. + * Added target-side coordinator fencing across independent SimpleChat source deployments and retained uncertain target Search write slots through their full quarantine window. Authorization-reducing unshare requests now defer safely while target Search ACL writes are frozen. + * (Ref: [#1043](https://github.com/microsoft/simplechat/issues/1043), `functions_data_management.py`, `functions_migration_provenance.py`, `admin_data_management.js`, `DATA_MANAGEMENT_MIGRATION_RESILIENCE.md`) + +##### **(v0.250.070)** + +###### Bug Fixes + +* **Azure Blob Container SAS Support and Credential Guidance** + * Added support for storage connection strings, full container SAS URLs, and standalone SAS tokens. Pasted SAS URLs derive the canonical account, selected container, and default source name without persisting the token in connection metadata. + * Validates required Read and List permissions, HTTPS-only protocol, account-SAS Blob resource scope, start time, and expiry. Extra permissions and broader account credentials remain usable but produce least-privilege warnings. + * Shows non-secret SAS scope, named permissions, exact expiry, days remaining, stored-policy status, IP restrictions, and warnings in connection tests and source rows. + * Supports saving Blob credentials with or without Azure Key Vault; Key Vault is used when enabled and existing File Sync credential persistence is used otherwise. + * (Ref: [#1027](https://github.com/microsoft/simplechat/issues/1027), `functions_file_sync.py`, `workspace-file-sync.js`, `AZURE_BLOB_CONTAINER_SAS_SUPPORT_FIX.md`) + +##### **(v0.250.068)** + +###### Bug Fixes + +* **Azure Blob File Sync Endpoint and Error Hardening** + * Restricted Azure Blob File Sync URLs and connection strings to validated HTTPS Azure Blob endpoints, blocking arbitrary, internal, development-storage, and credential-bearing endpoint forms before SDK requests are created. + * Replaced raw File Sync route, run-history, activity, and item exception text with fixed client-safe messages while retaining detailed sanitized diagnostics in server logs. + * (Ref: [#1027](https://github.com/microsoft/simplechat/issues/1027), PR [#1088](https://github.com/microsoft/simplechat/pull/1088) security review, `functions_file_sync.py`, `route_backend_file_sync.py`) + +##### **(v0.250.067)** + +###### New Features + +* **Azure Blob Storage File Sync** + * Added Azure Blob Storage as an admin-controlled File Sync source for personal, group, and public workspaces, with account, container, prefix, selected-path, filter, tag, schedule, and remote-delete controls. + * Added managed identity, Key Vault-backed service principal and connection string authentication, connection testing, virtual-folder browsing, ETag change detection, and streamed ingestion through the existing document pipeline. + * (Ref: [#1027](https://github.com/microsoft/simplechat/issues/1027), `functions_file_sync.py`, `workspace-file-sync.js`, `AZURE_BLOB_STORAGE_FILE_SYNC.md`) +##### **(v0.250.066)** + +###### Bug Fixes + +* **GPT 5.6+ Multi-Modal Vision Model Selection** + * Enabled GPT 5.6 Luna, Sol, Terra, and later supported GPT deployments to appear in the Multi-Modal Vision Analysis selector across Azure OpenAI and Foundry endpoints. + * Model detection now evaluates model, display, and deployment names with normalized separators while preserving disabled-model and unsupported-family filtering. + * (Ref: [#1086](https://github.com/microsoft/simplechat/issues/1086), `admin_settings.js`, `test_admin_multimodal_vision_model_options.py`) + +##### **(v0.250.065)** + +###### New Features + +* **Task-Level Workflow Model and Agent Selection** + * Each ordered workflow task can now inherit the workflow's Default Runner or select its own authorized Direct Model or Agent. + * Task runners are normalized on save and revalidated before execution, including current personal/group/global agent scope, group membership, and enabled model endpoint/model availability. + * Unavailable runners follow the workflow's retry and stop-or-continue strategy, while task run items record non-secret runner audit details, execution deployment/provider, output preview, and token usage when available. + * Existing tasks without runner configuration inherit the workflow default, and workflows without task sequences retain the legacy execution path. + * (Ref: [#1084](https://github.com/microsoft/simplechat/issues/1084), `functions_personal_workflows.py`, `functions_group_workflows.py`, `functions_workflow_runner.py`) + +###### User Interface Enhancements + +* **Per-Task Runner Controls** + * Renamed the workflow-level Runner field to Default Runner and added Workflow default, Direct Model, and Agent selection to each task editor. + * Task rows and Review now show the resolved runner, with responsive conditional model/agent controls and text-safe rendering for endpoint, model, and agent labels. + * (Ref: [#1084](https://github.com/microsoft/simplechat/issues/1084), `workspace.html`, `group_workspaces.html`, `workspace_workflows.js`) + +##### **(v0.250.064)** + +###### New Features + +* **Repeatable AI Workflow Task Sequences** + * Personal and group workflows can now run with only instructions and a selected model or agent; workspace documents, File Sync, URL access, schedules, and completion alerts remain optional. + * Workflows support ordered instruction tasks that share the selected runner and receive bounded prior-task output as context. + * Added per-task retries and stop-or-continue error handling, with task outcomes recorded in run history and workflow activity. + * Existing document Search, Analyze, and Compare behavior remains available as optional input for the first task, while existing workflows without task sequences retain their prior execution path. + * (Ref: [#1082](https://github.com/microsoft/simplechat/issues/1082), `functions_personal_workflows.py`, `functions_group_workflows.py`, `functions_workflow_runner.py`) + +###### User Interface Enhancements + +* **Stepped Workflow Builder** + * Replaced the single-pane personal and group workflow form with a five-step General, Trigger, Tasks, Reliability, and Review builder. + * Users can add, edit, remove, and reorder tasks, configure retry and failure behavior, and review runner, trigger, document input, File Sync, and pop-up alert settings before saving. + * (Ref: [#1082](https://github.com/microsoft/simplechat/issues/1082), `workspace.html`, `group_workspaces.html`, `workspace_workflows.js`, `workspace-responsive.css`) + +##### **(v0.250.062)** + +###### Bug Fixes + +* **Cosmos Container Startup Conflict Recovery** + * Fixed a local Docker startup failure where multiple gunicorn workers could race while creating first-run Cosmos containers, causing a `NotFound` followed by a `Conflict` during app import. + * Container initialization now re-reads and returns the existing container when another worker creates it first, preserving normal startup behavior for already-provisioned environments. + * (Ref: `config.py`, `test_cosmos_container_conflict_recovery.py`, `COSMOS_CONTAINER_STARTUP_CONFLICT_FIX.md`) + +###### New Features + +* **Workflow Run Cancellation** + * Personal and group workflows can now be cancelled from workspace rows and cards, run history, or the workflow activity view while a run is active. + * Cancellation is persisted for the active run and cooperatively stops further File Sync, document action, model, agent, artifact, notification, and scheduling work after any in-flight external request returns. + * Cancelled scheduled and File Sync workflows return to an idle state and advance to their next scheduled run instead of immediately restarting. + * (Ref: [#990](https://github.com/microsoft/simplechat/issues/990), `route_backend_workflows.py`, `functions_workflow_runner.py`, `workspace_workflows.js`) + +##### **(v0.250.061)** + +###### New Features + +* **Configurable Content Safety Violation Messages** + * Administrators can now configure the Markdown message shown when Content Safety blocks a chat request using the standard Markdown editor toolbar. + * A new setting controls whether the block reason, detected categories and severities, and blocklist matches are included beneath the custom message. + * The editor now renders correctly when the hidden Safety tab opens, and Markdown-only edits activate Save Settings before submission. + * (Ref: [#989](https://github.com/microsoft/simplechat/issues/989), `functions_content_safety.py`, `admin_settings.html`, `route_backend_chats.py`) + +##### **(v0.250.059)** + +###### New Features + +* **Versioned Latest Features Navigation Hide Preference** + * Users can now hide Latest Features navigation entries for the current SimpleChat version from the ellipsis action and restore them from Profile Settings. + * The hidden state is version-aware, so Latest Features automatically appears again after the app version changes. + * Added a development-only `is_development=true` environment override that hides Latest Features nav entries without affecting production behavior when unset or false. + * (Ref: [#987](https://github.com/microsoft/simplechat/issues/987), `latestFeaturesHiddenVersion`, `_sidebar_nav.html`, `_top_nav.html`, `profile.html`, `latest-features-nav.js`) + +##### **(v0.250.057)** + +###### Bug Fixes + +* **Terms of Use Redirect Hardening** + * Replaced wildcard config imports in the Terms of Use route with explicit Flask imports. + * Moved post-acceptance return paths from hidden form values to server-side session storage, keeping user-controlled return targets local-only. + * Restricted admin-configured external decline redirects to HTTPS URLs without embedded credentials while preserving local-path redirects. + * (Ref: [#504](https://github.com/microsoft/simplechat/issues/504), `route_frontend_terms_of_use.py`, `functions_terms_of_use.py`, `terms_of_use.html`) + +##### **(v0.250.056)** + +###### New Features + +* **Optional Terms of Use Gate** + * Added an admin-configurable Terms of Use prompt that can require users to accept rules of behavior, terms, or an entry notice before using SimpleChat. + * Supports every-session, once-per-day, and once-per-version recurrence modes, with server-side browser/API enforcement and activity logging for accept/decline events. + * (Ref: [#504](https://github.com/microsoft/simplechat/issues/504), `TERMS_OF_USE.md`, `functions_terms_of_use.py`, `route_frontend_terms_of_use.py`, `terms_of_use.html`) + +##### **(v0.250.052)** + +###### Bug Fixes + +* **Control Center Left Nav Endpoint Fix** + * Fixed an issue where admins could open Control Center while the left navigation Control Center section stayed hidden when ControlCenterAdmin enforcement was disabled. + * Updated the sidebar endpoint check to use the blueprint-qualified `frontend_control_center.control_center` route and added regression coverage for the regular Admin fallback. + * (Ref: [#1009](https://github.com/microsoft/simplechat/issues/1009), `_sidebar_nav.html`, `test_control_center_left_nav_endpoint.py`) + +##### **(v0.250.051)** + +###### User Interface Enhancements + +* **Cosmos Editor Independent Results Scrolling** + * Cosmos editor query results now use a dedicated scroll area inside the results modal, so long result lists do not stretch the rest of the modal. + * Added a smaller results-list cap for narrower screens while keeping the document editor and modal footer accessible. + * (Ref: [#1006](https://github.com/microsoft/simplechat/issues/1006), `admin_settings.html`, `styles.css`, Cosmos DB JSON Editor) + +##### **(v0.250.049)** + +###### Bug Fixes + +* **Cosmos Editor Page Size Enforcement** + * Empty Cosmos editor browse mode now respects the selected page size up to the 100-document cap instead of always requesting 100 items. + * This keeps small page-size selections useful for compact validation and targeted inspection. + * (Ref: [#1006](https://github.com/microsoft/simplechat/issues/1006), Cosmos DB JSON Editor, `functions_data_management.py`) + +###### User Interface Enhancements + +* **Cosmos Editor Results Modal** + * Moved Cosmos editor query results and the JSON document editor into a large scrollable modal opened by Run Query. + * Kept the Data Management card focused on unlock, container selection, page size, and query setup while the modal provides compact result rows, a scrollable results pane, document refresh, Next Page, and Save JSON controls. + * (Ref: [#1006](https://github.com/microsoft/simplechat/issues/1006), `admin_settings.html`, `admin_data_management.js`, `COSMOS_DB_JSON_EDITOR.md`) + +##### **(v0.250.048)** + +###### New Features + +* **Admin Cosmos DB JSON Editor** + * Added an admin-only Data Management tool for selecting SimpleChat Cosmos DB containers, running paged SELECT queries, opening individual documents, editing JSON, and saving changes with ETag concurrency protection. + * Empty browse mode is capped at the first 100 documents, while custom SELECT queries page beyond 100 through continuation tokens without returning oversized result sets in one request. + * The interface is protected by danger acknowledgements, blocks `id` and partition key edits, and records editor actions plus save summaries in Activity Logs. + * (Ref: [#1006](https://github.com/microsoft/simplechat/issues/1006), `COSMOS_DB_JSON_EDITOR.md`, `functions_data_management.py`, `route_backend_data_management.py`, `admin_settings.html`, `admin_data_management.js`) + +##### **(v0.250.047)** + +###### Bug Fixes + +* **CosmosClient Import Binding CodeQL Cleanup** + * Replaced direct `CosmosClient` imports with module-qualified `azure_cosmos.CosmosClient` lookups so tests and diagnostics that patch `azure.cosmos.CosmosClient` are observed consistently. + * Updated the Cosmos query plugin functional test to patch the module-qualified SDK client and avoid live Cosmos connections during app-module imports. + * (Ref: `config.py`, `functions_data_management.py`, `route_backend_plugins.py`, `cosmos_query_plugin.py`, `test_cosmos_query_plugin.py`) + +##### **(v0.250.046)** + +###### Bug Fixes + +* **Conversation Cache Invalidation Authorization** + * Route-level message mutation cache invalidation now loads personal conversations through the existing ownership authorization helper instead of directly reading a request-derived conversation id. + * Updated PR-readiness functional test fixtures to match the current document access index config imports and avoid live Cosmos connections during notification regression tests. + * (Ref: `route_backend_conversations.py`, `test_chat_completion_notifications.py`, DAI functional test fixtures) + +##### **(v0.250.044)** + +###### New Features + +* **Development Requirements Overlay** + * Added a root `requirements-dev.txt` for test and development-only Python dependencies that are not already included in the base app requirements. + * The overlay includes pytest, pytest Playwright integration, and Azure Playwright management support so local validation can be installed without duplicating the production dependency set. + * (Ref: `requirements-dev.txt`, testing dependencies, local development setup) + +##### **(v0.250.043)** + +###### New Features + +* **Redis Explorer SimpleChat Resolution** + * Redis Explorer now resolves `DAI_LIST_CACHE_VERSION:{hash}` keys to safe SimpleChat scope metadata when possible, including user/group/public workspace identity, workspace name/status, DAI row counts, and source/access-role summaries. + * Added DAI cache hygiene counters to Redis Monitoring for payload keys, version markers, no-expiry markers, and the active version-marker TTL policy. + * (Ref: `functions_redis_monitoring.py`, `functions_document_access_index.py`, `admin_settings.html`, `admin_settings.js`, `REDIS_EXPLORER.md`) + +###### Bug Fixes + +* **DAI Redis Version Marker TTL Hygiene** + * DAI Redis version marker keys now receive a bounded TTL that outlives DAI payload cache entries instead of remaining in Redis indefinitely. + * Marker TTLs refresh on DAI cache reads, cache invalidations, and app-maintenance hygiene; existing no-expiry markers are repaired in place rather than deleted. + * With the current 900-second DAI payload TTL, version markers are refreshed to 3,600 seconds. + * (Ref: DAI Redis cache version markers, `refresh_document_access_cache_version_marker_ttls`, app maintenance) + +##### **(v0.250.041)** + +###### User Interface Enhancements + +* **Redis Explorer Browse-All Layout** + * Improved Redis Explorer with a fixed-height modal where the key list and sanitized preview pane scroll independently, keeping pagination controls and selected preview context easier to use. + * Added an explicit Browse All action, clearer Apply Filter and Previous Page/Next Page controls, page/scope status text, and guidance for app settings cache key names such as `APP_SETTINGS_CACHE`. + * (Ref: `REDIS_EXPLORER.md`, `admin_settings.html`, `admin_settings.js`, Redis Explorer) + +##### **(v0.250.040)** + +###### New Features + +* **Redis Explorer** + * Added an admin-only Redis Explorer in Admin Settings > Scale > Redis Monitoring for read-only, cursor-paginated Redis key browsing with substring filtering and page-size controls. + * Admins can select a key to view sanitized metadata and bounded preview content; session, token, cookie, credential, password, secret, authorization, and CSRF-like keys return restricted previews. + * JSON previews redact sensitive fields and all browser rendering uses text-safe DOM updates. + * (Ref: `REDIS_EXPLORER.md`, `functions_redis_monitoring.py`, `route_backend_settings.py`, `admin_settings.html`, `admin_settings.js`) + +##### **(v0.250.039)** + +###### New Features + +* **Phase 9 Cosmos Performance Runbooks** + * Added support guidance for rebuilding caches, rebuilding the Document Access Index, cleaning stale operational cache documents, applying expected Cosmos composite indexes, interpreting shadow validation diffs, and interpreting DAI fallback/cache metrics. + * Documented when broad source fallback should remain available while DAI repair, backfill, Redis cache, and production fallback telemetry stabilize. + * (Ref: `COSMOS_PERFORMANCE_OPTIMIZATION_PLAN.md`, Cosmos maintenance, DAI runbooks) + +###### User Interface Enhancements + +* **Guarded Cosmos Index Apply Action** + * Added an Admin Settings > Scale > Cosmos Maintenance action for applying missing expected Cosmos composite indexes through the existing app-maintenance endpoint. + * The confirmation modal explains that updates are additive and preserve existing indexing policy paths, but can add write-index overhead and trigger asynchronous Cosmos index transformation. + * (Ref: `admin_settings.html`, `admin_settings.js`, `functions_cosmos_indexing.py`, app maintenance) + +##### **(v0.250.037)** + +###### Bug Fixes + +* **Settings Container RU Write Suppression** + * Reduced idle Cosmos DB `settings` container RU consumption by stopping routine status refreshes, no-op autoscale checks, and generic settings writes from creating unrelated cache/version churn. + * Conversation cache versioning and volatile chat bootstrap/conversation payloads now require Redis; when Redis is unavailable, the app bypasses those caches and falls back to source Cosmos query behavior instead of writing cache artifacts to `settings`. + * Document Access Index status now uses short-lived in-process state caching and skips shadow-validation state reads when shadow validation is disabled. + * Post-deploy validation showed `settings` dropped out of the top normalized RU consumers during idle monitoring. + * (Ref: `functions_settings.py`, `functions_cosmos_throughput.py`, `functions_shared_cache.py`, `functions_conversation_cache.py`, `functions_document_access_index.py`, `SETTINGS_CONTAINER_RU_WRITE_SUPPRESSION_FIX.md`) + +##### **(v0.250.035)** + +###### Bug Fixes + +* **Conversation Cache Mark-Read Invalidation Tuning** + * Normal conversation switching now skips mark-read requests when the client has no unread assistant-response state, reducing unnecessary conversation feed cache invalidations during page reloads and navigation. + * The mark-read API now only upserts the conversation and bumps the user-scoped conversation cache version when unread conversation fields actually changed; notification clearing remains intact. + * (Ref: conversation cache invalidation, mark-read flow, `chat-conversations.js`, `route_backend_conversations.py`, `test_conversations_read_ownership_authorization.py`) + +##### **(v0.250.034)** + +###### New Features + +* **Conversation Cache Metrics Dashboard** + * Added DAI-style rolling metrics for conversation list, feed, and advanced-search cache activity, including 15-minute hit rate, hits/misses, bypasses/errors, writes/invalidations, operation mix, last cache event, and last invalidation. + * Exposed normalized conversation cache settings and metrics through app maintenance status without adding Cosmos reads to the conversation hot path. + * Removed the Phase 4 badge from the Conversation Cache card now that the feature is part of the operational dashboard. + * (Ref: conversation cache metrics, `functions_conversation_cache.py`, `functions_app_maintenance.py`, `admin_settings.html`, `admin_settings.js`) + +##### **(v0.250.033)** + +###### New Features + +* **Phase 4 Conversation Cache Hardening** + * Hardened conversation list, feed, and advanced-search caching behind an explicit `enable_conversation_cache` setting. + * Added Admin Settings > Scale controls for enabling conversation cache and adjusting the conversation cache TTL without requiring direct Cosmos settings edits. + * Redis remains optional: enabled cache paths use the shared Redis-first/Cosmos-fallback helper, while disabled cache paths bypass cache reads and writes and continue using source Cosmos queries. + * Conversation cache entries remain user-scoped and versioned, with collaboration-aware feed/search fingerprints based on the user's group-access state. + * (Ref: conversation cache, optional Redis fallback, `functions_conversation_cache.py`, `route_backend_conversations.py`, `admin_settings.html`) + +###### Bug Fixes + +* **Conversation Cache Invalidation Coverage** + * Added cache invalidation after metadata reads normalize and persist legacy/missing `chat_type` values so cached list/search payloads do not remain stale. + * Updated conversation cache and ownership authorization regressions to cover disabled-cache bypass, source fallback, and route wiring. + * (Ref: conversation metadata normalization, cache invalidation, `test_cosmos_wave2b_conversation_cache.py`, `test_conversations_read_ownership_authorization.py`) + +##### **(v0.250.032)** + +###### New Features + +* **Phase 3 Low-Churn Cache Hardening** + * Hardened the shared low-churn cache foundation for custom pages/navigation and chat bootstrap data. + * Added safe shared-cache diagnostics for hit, miss, write, delete, and version-bump activity using hashed cache-key context, and exposed those metrics through app maintenance status. + * Expanded chat bootstrap cache invalidation coverage across group and public workspace metadata, membership, role, ownership, status, model endpoint, Control Center, and SimpleChat operation mutations. + * (Ref: shared cache metrics, chat bootstrap cache, custom pages cache, `functions_shared_cache.py`, `functions_app_maintenance.py`) + +###### Bug Fixes + +* **Low-Churn Cache Invalidation Coverage** + * Fixed stale chat bootstrap labels after group rename/update paths by invalidating the global chat bootstrap cache after successful group metadata writes. + * Custom pages/navigation cache now invalidates after app settings writes so menu and feature-setting changes do not wait for TTL expiry. + * (Ref: group metadata updates, custom page settings invalidation, `route_backend_groups.py`, `functions_settings.py`) + +* **Public Workspace Document Manager Approval** + * Fixed document-manager request approval so the manager addition and pending-request cleanup are persisted in a single workspace update instead of risking a stale overwrite. + * The approval and rejection flows now invalidate chat bootstrap cache state for public workspace visibility updates. + * (Ref: public workspace document managers, `functions_public_workspaces.py`, `test_cosmos_wave2a_chat_bootstrap_cache.py`) + +##### **(v0.250.030)** + +###### New Features + +* **Redis Document Access Index Cache** + * Added Redis read-through caching for DAI-backed document list, tag list, and legacy-count reads with scope-version invalidation and bounded TTL controls. + * Admin Settings now shows Redis DAI cache health, hit/miss/bypass/error metrics, invalidations, and the latest cache event alongside DAI read and maintenance status. + * (Ref: DAI Redis cache, `functions_document_access_index.py`, `admin_settings.html`, `admin_settings.js`) + +###### Bug Fixes + +* **DAI Cache Invalidation and Repair Safety** + * Hardened DAI cache invalidation so access changes fail closed when Redis invalidation cannot be proven safe, including revoked-share, delete, partial projection failure, and untracked repair-state scenarios. + * Preserved historical revoked scopes through repair and blocked DAI/cache reads while repair safety is unknown. + * (Ref: DAI cache invalidation, projection repair backlog, `test_cosmos_wave6_document_access_cache.py`) + +* **DAI List Parity Improvements** + * DAI document-list reads now include pending shared documents needed by approval UI, preserve generated-artifact requester identity for public workspace actions, collapse legacy revisions like source reads, and match source exact/case-sensitive classification and array filters. + * DAI tag-list reads now project file names for legacy rows so distinct legacy documents without revision-family metadata do not collapse into a single tag-count identity. + * External public DAI lists now use unfiltered DAI legacy counts so legacy-update prompts match source-backed behavior. + * (Ref: DAI list parity, public workspace documents, external public documents) + +* **Maintenance Failure Visibility** + * Manual app maintenance runs now surface failed step status instead of reporting full success, and Admin Settings keeps detailed DAI status visible when a run completes with errors. + * The DAI maintenance status now reports automatic maintenance only when the app-maintenance scheduler gates are enabled and startup maintenance can actually run. + * Optional shadow-validation source queries now fail open after successful DAI reads so diagnostics cannot break served list responses. + * (Ref: app maintenance, DAI shadow validation, `functions_app_maintenance.py`, document list routes) + +##### **(v0.250.015)** + +###### Bug Fixes + +* **Anonymous Notification Polling 401 Log Reduction** + * Stopped loading the notification polling script for unauthenticated sessions, preventing the login screen from repeatedly calling `/api/notifications/count` after idle timeout or before sign-in. + * This reduces expected 401 noise in App Service log streams and Application Insights while preserving notification badge polling for authenticated users. + * (Ref: notification polling, idle timeout, `base.html`, `notifications.js`, `test_notification_polling_redirect_guard.py`) + +##### **(v0.250.010)** + +###### New Features + +* **Audio File Runtime Support** + * Added default-on FFmpeg and FFprobe packaging for container builds so SimpleChat can transcode a much broader set of audio files before Azure Speech transcription. + * Expanded recognized audio upload extensions to include common containers and codecs such as 3GA, AAC, AC3, AIFF, AMR, AU, CAF, FLAC, M4A/M4B/M4R, Matroska audio, MP2/MP3/MPA, OGG/Opus/Speex, WAV, WebM audio, WMA, and WavPack. + * Added Admin Settings runtime guidance showing whether FFmpeg broad transcoding is available in the current app runtime and which audio upload extensions are recognized. + * Added `SIMPLECHAT_INSTALL_FFMPEG` / `INSTALL_AUDIO_FFMPEG` build controls for deployments that need to opt out of bundling FFmpeg. + * (Ref: audio uploads, FFmpeg runtime, `Dockerfile`, `functions_documents.py`, `admin_settings.html`, `AUDIO_FILE_RUNTIME_SUPPORT.md`) + +###### Bug Fixes + +* **iPhone M4A Upload FFmpeg Fallback** + * Fixed supported iPhone `.m4a` audio uploads failing before transcription when the app runtime could not resolve a local `ffmpeg` executable. + * Public Azure environments can now fall back to Azure Speech fast transcription using the original supported source audio file and content type when local segmentation fails because FFmpeg is missing. + * FFmpeg segmentation now targets the first audio stream and emits mono 16 kHz PCM WAV chunks for Speech when FFmpeg is available. + * (Ref: [#974](https://github.com/microsoft/simplechat/issues/974), `.m4a` upload processing, Azure Speech fast transcription, `IPHONE_M4A_FFMPEG_FALLBACK_FIX.md`) + +##### **(v0.250.008)** + +###### Bug Fixes + +* **Multi-Endpoint Vision Test Connection** + * Fixed the Admin Settings Vision Model test button so multi-endpoint models are tested against their configured endpoint instead of always using the legacy GPT endpoint. + * Vision model options now preserve endpoint and model metadata for the test call while keeping the saved deployment-name value compatible with existing settings. + * Removed duplicate backend Vision test connection logic and preserved GPT-5/o-series token handling for Vision test requests. + * (Ref: Vision Model test, multi-endpoint model endpoints, `admin_settings.js`, `admin_settings.html`, `route_backend_settings.py`, `MULTI_ENDPOINT_VISION_TEST_CONNECTION_FIX.md`) + +##### **(v0.250.007)** + +* **Malicious PR Security Review Workflow** + * Added a static malicious-change review workflow for pull requests into `Development`, with manual dispatch options for custom review ranges and full-file scans. + * Added a reusable security review prompt and focused functional coverage for dependency pinning policy, hidden Unicode detection, suspicious egress markers, and workflow wiring. + * (Ref: malicious PR security review, `.github/workflows/malicious-pr-security-review.yml`, `scripts/check_malicious_pr_security_review.py`) + +##### **(v0.250.006)** + +###### New Features + +* **Chat Scroll Behavior and 508 Usability** + * Updated chat message rendering so the viewport no longer jumps to the very bottom of long assistant responses when they finish loading while the user is reading near the top. + * Auto-scroll now only occurs when the user is already near the bottom of the conversation, and a floating "scroll to latest message" button appears when new content arrives below the current view. + * This aligns the chat experience more closely with other AI chat tools and reduces unexpected motion for 508 testers and keyboard users. + * (Ref: `chats.html`, `chat-global.js`, `chat-messages.js`) + +##### **(v0.250.005)** + +###### Bug Fixes + +* **Admin Settings Save 500 Fix** + * Fixed an issue where saving Admin Settings returned an HTTP 500 error even though configuration changes were successfully persisted. + * The `/admin/settings` POST handler now uses Flask's `current_app` when regenerating custom logo and favicon files after a successful settings update, eliminating the `NameError: name 'app' is not defined` in the post-save path. + * (Ref: admin settings save, logo/favicon regeneration, `route_frontend_admin_settings.py`, `ADMIN_SETTINGS_SAVE_500_FIX.md`) + +##### **(v0.250.004)** + +###### Bug Fixes + +* **Model Endpoint Management Cloud Normalization** + * Fixed model endpoint saves so managed identity and other non-editable cloud paths derive `management_cloud` from `AZURE_ENVIRONMENT` instead of persisting the hidden UI default of `public`. + * Added custom-cloud handling for inherited model endpoint authority and Foundry scope defaults while preserving explicit Foundry service-principal cross-cloud selections. + * (Ref: model endpoint authentication, `normalize_model_endpoints`, `AZURE_ENVIRONMENT`, `test_model_endpoint_management_cloud_environment.py`) + +##### **(v0.250.003)** + +###### Bug Fixes + +* **Admin Settings Update Banner Version Comparison** + * Fixed stale cached update-check settings so Admin Settings no longer displays an older release such as `v0.250.001` as available when the running app version is newer. + * The render path now recomputes `update_available` from the cached latest version and the current app version before showing the banner. + * (Ref: Admin Settings update banner, `compare_versions`, `test_admin_update_banner_version_comparison.py`) + +### **(v0.250.001)** + +#### New Features + +* **Azure Billing Dynamic Charts** + * Updated the Azure Billing action chart path to return SimpleChat `simplechart` Markdown instead of server-rendered matplotlib PNG image payloads. + * Preserved existing `plot_chart` / `plot_custom_chart` inputs while returning `chart_payload`, `chart_markdown`, summary, and renderer metadata for interactive chat display. + * Added regression coverage for stacked and pie Azure Billing chart output. + * (Ref: Azure Billing action, dynamic inline charts, `chart_markdown`, `test_azure_billing_dynamic_charts.py`) + +* **Blueprint Route Security Policies** + * Migrated SimpleChat route registration to Blueprint-backed route groups with explicit `before_request` authentication policies. + * Added reusable Blueprint auth helpers that compose the existing login, user, admin, and external bearer-token decorators. + * Added route policy tests covering Blueprint registration, unauthenticated access expectations, public/external route exceptions, and route-test completeness. + * Updated route-authentication prompts, Python route instructions, PR-prep guidance, and CI route validation workflow so future route work must update and run the route policy tests. + * Preserved Custom Pages as login-required with page metadata role checks layered inside the dispatcher. + * (Ref: Blueprint route policies, route auth guardrails, `functional_tests/route_tests/`, `functions_authentication.py`, `app.py`) + +* **Azure OpenAI Identity Setup Guidance** + * Added admin setup guidance explaining which identity is used for legacy Azure OpenAI model discovery and which identity or key is used for runtime GPT, embedding, and image generation calls. + * Clarifies that `Fetch Models` uses Azure Resource Manager deployment listing while chat generation, embeddings, file-upload embedding generation, and image generation use the Azure OpenAI data plane. + * (Ref: Azure OpenAI setup guide, Admin Settings model guidance, `AZURE_OPENAI_IDENTITY_SETUP_GUIDE.md`) + +* **Model Endpoint Setup Guidance** + * Added in-product **Setup Guide** buttons beside global, personal, and group model endpoint actions, plus inline setup guidance inside the shared Model Endpoint modal. + * Guidance covers Azure OpenAI, Foundry (classic), and New Foundry provider selection, managed identity and service principal RBAC, and API-key inference-only limitations. + * (Ref: model endpoint modal, Admin Settings model endpoints, workspace endpoints, Foundry RBAC setup) + +* **Tabular SK Large Result Pagination** + * Added continuation metadata for row-returning tabular Semantic Kernel tools, including `start_row`, `page_size`, `has_more`, and `next_start_row`. + * Added safe row payload trimming for oversized tool results, while preserving explicit `return_columns` projection and protected row metadata used for sheet context, matched values, and row-linked document evidence. + * Raised tabular computed-results handoff guardrails to 100K characters with warning logs when truncation is still required. + * Inspired by and adapted from PR #894 by @vivche. + * (Ref: tabular SK pagination, `return_columns`, large-result handoff, `tabular_processing_plugin.py`, `route_backend_chats.py`) + +* **Beta Feature Integration with Development Governance** + * Prepared the beta feature branch for integration with the latest `Development` governance, custom pages, and user settings cache changes. + * Preserved beta capabilities for agent catalog customization, file sync, workflows, workspace identities, Data Management, and chat/workspace productivity while applying Development governance gates where required. + * (Ref: Development merge resolution, governance integration, PR readiness) + +* **User Settings Cache Optimization** + * Added request-scoped memoization for full user settings reads and a lightweight user UI settings cache that works with Redis-enabled and no-Redis deployments. + * Shared page scripts now reuse injected UI preferences for dark mode and navigation layout before falling back to the full user settings API. + * (Ref: user settings cache, user UI settings cache, `functions_settings.py`, `app_settings_cache.py`, `dark-mode.js`, `sidebar.js`) + +* **Custom Pages** + * Added administrator-managed custom pages with static HTML/CSS/JS assets, optional Python-backed page extensions, and authenticated host routes for publishing internal experiences inside SimpleChat. + * Added Admin Settings controls, navigation wiring, example page templates, documentation, and functional coverage for disabled-by-default fail-closed behavior. + * (Ref: custom pages, Admin Settings Custom Pages tab, `route_custom_pages.py`, `functions_custom_pages.py`) + +* **Governance Controls for Endpoints, Agents, and Actions** + * Added in-app governance policies that let administrators control access to personal, group, and global endpoints, agents, and actions. + * Added feature-level policies, delegated item policies, review workflows, backend enforcement, and Admin Settings UI for managing governance allowlists. + * (Ref: governance policies, delegated item policies, Admin Settings Governance tab) + +* **Governance and App Settings Cache Versioning** + * Added cache-version coordination so settings and governance policy changes can invalidate stale worker process caches across Redis-enabled and non-Redis deployments. + * Keeps hot-path settings and governance checks fast while reducing stale reads after admin changes. + * (Ref: app settings cache, governance cache versioning) + +* **Pull Request Preparation Prompt** + * Added a reusable Copilot prompt for preparing SimpleChat branches for pull requests into `Development`. + * The workflow verifies branch freshness against `Development`, runs repo-aligned validation checks, updates release notes when needed, and gates push or PR creation behind explicit user confirmation. + * Optional merge or rebase from `Development` is supported only when requested, with conflicts resolved interactively by the agent after explaining each side of the conflict. + * (Ref: `.github/prompts/prepare-for-pull-request.prompt.md`, PR readiness workflow, Development branch validation) + +* **Tableau Action** + * Added a first-class, read-only Tableau action powered by `tableauserverclient` for discovering Tableau Server and Tableau Cloud projects, workbooks, views, datasources, and workbook details. + * Added a dedicated Tableau action configuration workflow with server/site fields, PAT and username/password authentication, reusable workspace identity support, discovery limits, schemas, health validation, and Semantic Kernel loader integration. + * (Ref: `tableau_plugin.py`, `tableau_plugin_factory.py`, `functions_tableau_operations.py`, `plugin_modal_stepper.js`, `TABLEAU_ACTION.md`) + +* **Snowflake Action** + * Added a first-class Snowflake action type for querying Snowflake data warehouses through the Snowflake Python Connector, focused on read-only data retrieval for agent analysis, charts, generated documents, and exports. + * Added tailored action configuration for account, warehouse, default database/schema, role, password/key-pair/OAuth authentication, reusable workspace identities, and query execution limits. + * Added read-only SQL enforcement, automatic result limiting, structured column/row responses, Semantic Kernel loader integration, Key Vault secret handling, governance labels, schemas, feature documentation, and functional coverage. + * Uses `snowflake-connector-python[pandas]==3.18.0` so the local Python 3.13 development environment can install from wheels without requiring native C++ build tooling. + * (Ref: `SnowflakePlugin`, `SnowflakePluginFactory`, Snowflake action modal, `SNOWFLAKE_ACTION.md`) + +* **Workflow Per-Document Analysis and Generated Office Exports** + * Added a workflow Analyze mode that runs the same prompt against each selected document separately, then combines the per-document replies, coverage, citations, generated artifacts, and alert targets into the workflow result. + * Added SimpleChat action tools for generated Word documents and PowerPoint presentations, with group workflow uploads defaulting to the current group workspace while preserving existing group access checks. + * (Ref: `functions_document_actions.py`, `functions_workflow_runner.py`, `functions_simplechat_operations.py`, `simplechat_plugin.py`, `WORKFLOW_PER_DOCUMENT_ANALYSIS_AND_EXPORTS.md`) + +* **Group Workflows** + * Added group-scoped workflows with dedicated Cosmos containers for workflow definitions, run history, and per-document run items. + * Group workflow APIs now support create, edit, delete, run, history, resume-failed, agent selection, File Sync sources, and activity streaming while revalidating group membership and assignment gating. + * Added Admin Settings controls for enabling group workflows, requiring group assignment, managing assigned groups, and applying the existing owner-only management policy to group workflows. + * Added a Group Workflows tab to group workspaces and updated the shared workflow UI/activity page to support personal and group workflow scopes. + * (Ref: `functions_group_workflows.py`, `route_backend_workflows.py`, `functions_workflow_runner.py`, `group_workspaces.html`, `workspace_workflows.js`, `GROUP_WORKFLOWS.md`) + +* **Voice-Assisted Form Inputs and Agent Instruction Drafting** + * Added speech-to-text microphone controls to supported agent, group, public workspace, document metadata, and tag-name fields when speech input is enabled. + * Added an agent Instruction Brief field and Draft Instructions action that sends typed or dictated context to the configured GPT/APIM model, then inserts editable Markdown instructions before save. + * Dictated tag names are normalized to lowercase safe tag values, and dictated document keywords are normalized to comma-separated values. + * (Ref: `form-voice-input.js`, `agent_modal_stepper.js`, `/api/agents/draft-instructions`, `VOICE_ASSISTED_FORM_INPUTS.md`) + +* **Microsoft Graph Send Mail Action** + * Microsoft Graph actions can now create manual drafts, prepare delayed-delivery drafts from 5 to 600 seconds, or send mail automatically from the signed-in user's mailbox. + * Added plugin configuration for default delivery mode and delay seconds, with runtime validation and Graph scopes for draft creation, delayed draft submission, and immediate send flows. + * (Ref: Microsoft Graph action, `MSGraphPlugin.send_mail`, `plugin_modal_stepper.js`, `MSGRAPH_SEND_MAIL_ACTION.md`) + +* **Workspace-Backed Chat Upload Replacement** + * Eligible chat uploads now use the personal workspace document as the source of truth instead of also running the legacy chat-local extraction and chat blob storage path. + * Chat creates a lightweight workspace-backed file message with processing progress and automatically includes ready linked workspace documents in regular and streaming chat search context for enhanced citations. + * Fixed the chat handoff queue helper to use the configured Flask executor extension, preventing orphaned personal workspace rows from remaining at queued 0% when background processing was not submitted. + * (Ref: `route_frontend_chats.py`, `route_backend_chats.py`, `functions_documents.py`, `CHAT_UPLOAD_PERSONAL_WORKSPACE_HANDOFF.md`) + +* **Chat Upload Personal Workspace Handoff** + * Eligible chat uploads now queue a personal workspace document while preserving the existing chat attachment/image message behavior and fallback flow. + * Chat-uploaded workspace documents receive the `conversations` tag plus the conversation ID tag, store explicit source metadata, and surface processing progress in the chat message with the same document status fields used by workspace uploads. + * Workspace metadata and delete flows now show when a document is linked to a conversation. + * (Ref: `route_frontend_chats.py`, `functions_documents.py`, `workspace-documents.js`, `chat-messages.js`, `CHAT_UPLOAD_PERSONAL_WORKSPACE_HANDOFF.md`) + +* **Chat Upload Personal Workspace Handoff Design** + * Added a proposed implementation plan for routing eligible chat file uploads into the user's personal workspace while preserving the existing chat attachment experience and fallback flow. + * Documented the recommended metadata, conversation tags, delete lifecycle, search/analyze/compare implications, security checks, failure modes, testing plan, and staged rollout for the handoff. + * (Ref: `CHAT_UPLOAD_PERSONAL_WORKSPACE_HANDOFF.md`, chat uploads, personal workspace documents, conversation-linked document lifecycle) + +* **Document Intelligence Auto Mode and PDF Reprocessing** + * Added **Auto** extraction for PDFs and images so admins can sample the first PDF pages with Layout and let SimpleChat finish with Read or Layout based on detected tables or selection marks. + * Expanded Search & Extract guidance with a Read/Layout/Auto help modal, Auto sample-page control, and clearer Layout benefit/cost copy including the 6X increase for every 1000 pages. + * Added Read/Layout extraction badges plus single-document and bulk PDF reprocess actions in personal, group, and public workspaces. New PDF/image uploads preserve their source blob so PDFs can be reprocessed later when available. + * (Ref: `DOCUMENT_INTELLIGENCE_PDF_IMAGE_EXTRACTION_MODE.md`, `functions_documents.py`, Admin Settings Search & Extract, workspace document actions) + +* **Cosmos Native Autoscale Conversion** + * Added global and per-container policy controls that let admins convert dedicated manual Cosmos throughput to native Cosmos autoscale. + * Added manual Convert actions for database and container throughput so admins can move eligible manual throughput to autoscale from the Admin Settings Scale tab. + * Background throughput automation can now prioritize eligible manual-to-autoscale conversions before utilization-based RU scale decisions, while preserving configured min and max guardrails. + * (Ref: `functions_cosmos_throughput.py`, Admin Settings Scale tab, `COSMOS_NATIVE_AUTOSCALE_CONVERSION.md`) + +* **Document Intelligence PDF and Image Extraction Mode** + * Added a Search & Extract admin setting that lets administrators choose Read or Layout extraction for PDF and image uploads. + * Read keeps the faster text-extraction path, while Layout captures richer structure such as tables, document layout, and checked or unchecked selection marks with some added parsing latency. + * New PDF and image ingestion records `document_intelligence_extraction_mode` metadata so extracted documents identify whether Read or Layout was used. + * (Ref: `admin_settings.html`, `functions_content.py`, `functions_documents.py`, `DOCUMENT_INTELLIGENCE_PDF_IMAGE_EXTRACTION_MODE.md`) + +* **Cosmos Container Policy Enforcement** + * Added an Admin Settings option to enforce the global Cosmos throughput automation policy across every dedicated-throughput container. + * New containers discovered by Refresh or the background autoscale loop inherit the same global thresholds, intervals, RU step sizes, and guardrails automatically. + * Added an Apply Global Policy action in the Containers modal to stage the current global policy onto all currently discovered containers while preserving per-container cooldown timestamps. + * (Ref: `functions_cosmos_throughput.py`, `admin_settings.html`, `admin_settings.js`, `COSMOS_CONTAINER_POLICY_ENFORCEMENT.md`) + +* **Cosmos DB Throughput Autoscale Controls** + * Added Cosmos DB RU monitoring to the Admin Settings Scale tab, including database throughput status, recent normalized RU utilization, and per-container request-unit visibility. + * Added guarded manual Scale Up and Scale Down actions plus optional background automation with separate up/down thresholds, intervals, RU step sizes, and minimum/maximum guardrails. + * Added deployment metadata app settings and a custom Cosmos throughput operator role so the app identity can adjust throughput and read metrics without exposing Cosmos data-plane access to agents or user actions. + * (Ref: `functions_cosmos_throughput.py`, Admin Settings Scale tab, Cosmos throughput autoscale, `COSMOS_THROUGHPUT_AUTOSCALE.md`) + +* **Workflow File Sync Triggers and Batch Resume** + * Added File Sync Before Run controls so workflows can trigger selected personal, group, or public File Sync sources before the workflow prompt executes. + * Added Monitor File Sync Changes mode, which checks selected sync sources on the configured interval and only runs the workflow when new or changed files are detected. + * Added dynamic Analyze targeting for changed synced documents, per-document workflow run item tracking, and a Resume failed action that reruns failed document items from a previous Analyze workflow run. + * (Ref: `functions_personal_workflows.py`, `functions_workflow_runner.py`, `functions_file_sync.py`, `route_backend_workflows.py`, `workspace_workflows.js`, `WORKFLOW_FILE_SYNC_TRIGGERS.md`) + +* **OneDrive File Sync and Source Selection UX** + * Added OneDrive as a personal-workspace File Sync source that pulls selected OneDrive files and folders into the existing SimpleChat document processing, chunking, embedding, and Azure AI Search indexing pipeline. + * Added provider browsing and selected folder/file controls so users can sync the source root, specific folders, or specific files, with Include subfolders moved into the source-selection and filter workflow. + * Added remote change-token handling for provider-native IDs and eTags/cTags before content checksum fallback, improving change detection for cloud-drive files. + * (Ref: `functions_file_sync.py`, `route_backend_file_sync.py`, `workspace-file-sync.js`, `ONEDRIVE_FILE_SYNC.md`, `test_file_sync_onedrive_personal.py`) + +* **Global Cloud Drive Connector Identities** + * Extended global workspace identities so admins can manage File Sync cloud-drive connector credentials separately from personal user sync choices. + * OneDrive File Sync now resolves an admin-managed global File Sync client-secret identity before falling back to legacy app registration configuration, keeping tenant-level Graph credentials out of the personal source setup flow. + * Updated Admin Settings and workspace identity UI guidance to clarify that users choose what to sync while admins own tenant cloud-drive connector permissions. + * (Ref: `functions_workspace_identities.py`, `functions_file_sync.py`, `workspace-identities.js`, `admin_settings.html`, `WORKSPACE_IDENTITIES.md`) + +* **Azure Files File Sync Source** + * Added Azure Files as a first-class File Sync source type so workspaces can sync from Azure Storage file shares using a file service URL, share name, and optional directory path. + * Added Azure Files-compatible reusable identity support for managed identity, service principal client secret, and storage connection string authentication while keeping SMB sources on username/password or anonymous authentication. + * Updated File Sync source selection, admin source-type visibility controls, synced-document badges, documentation, and regression coverage for the new Azure Files connector. + * (Ref: `functions_file_sync.py`, `functions_workspace_identities.py`, `workspace-file-sync.js`, `workspace-identities.js`, `AZURE_FILES_FILE_SYNC.md`, `test_file_sync_azure_files_identity.py`) + +* **Conversation Feed Pagination** + * Added a paged conversation feed so chat startup loads pinned conversations, unread conversations, and the first 20 recent conversations instead of pulling every accessible conversation into the browser. + * Added load-more and near-bottom scroll loading for both the main conversation list and docked sidebar, with backend-driven title search that is not limited to the currently loaded page. + * Hidden conversations are excluded from the default feed and reloaded only when users enable the hidden-conversation toggle. + * (Ref: `route_backend_conversations.py`, `functions_conversation_feed.py`, `chat-conversations.js`, `chat-sidebar-conversations.js`, `CONVERSATION_FEED_PAGINATION.md`) + +* **Group File Share Approval Notifications** + * Added notifications when personal and group documents are shared, approved, or denied so recipients know when a file needs review and share owners know the outcome. + * Group document shares now require approval by the receiving group's Owners, Admins, or Document Managers before the file becomes searchable in that group. + * Receiving groups now see Approve or Remove actions for shared files, cannot delete the owner group's document, and cannot view the owner group's shared-recipient list. + * (Ref: group document sharing approval, `route_backend_group_documents.py`, `route_backend_documents.py`, `functions_notifications.py`, `group_workspaces.html`, `GROUP_FILE_SHARE_APPROVAL_NOTIFICATIONS.md`) + +* **Stats Time Windows and CSV Exports** + * Added 7-day, 30-day, 90-day, and custom date windows to personal profile stats, group stats, and public workspace stats so these pages match the Control Center activity-trends experience. + * Added CSV export actions for personal, group, and public stats with selectable metric sections and matching predefined or custom export windows. + * Centralized stats window parsing and daily bucket generation for consistent labels, chart ranges, and backend filtering across all three stats surfaces. + * (Ref: profile stats, group stats, public workspace stats, `functions_stats_windows.py`, `route_frontend_profile.py`, `route_backend_groups.py`, `route_backend_public_workspaces.py`) + +* **Personal Workflow Access Governance** + * Added a dedicated Admin Settings Workflow section so personal workflows can be explicitly enabled or disabled. + * Added optional `WorkflowUser` Enterprise App role enforcement for workflow UI access, API routes, manual runs, activity views, and SimpleChat workflow creation operations. + * Added `WorkflowUser` app role definitions to Azure CLI and Terraform deployer assets, with deployer version tracking updated. + * (Ref: workflow access control, `functions_settings.py`, `route_backend_workflows.py`, `route_frontend_admin_settings.py`, `PERSONAL_WORKFLOWS.md`, `WORKFLOW_ACCESS_CONTROL_FIX.md`) + +* **Azure Commercial Databricks Action** + * Added a first-class Databricks action type for Azure Commercial workspaces, using the Databricks SQL Statement Execution API rather than an ODBC driver. + * Added action modal configuration for workspace URL, SQL Warehouse ID, catalog/schema defaults, token/service-principal/managed-identity auth, execution limits, and reusable identity selection. + * Added read-only SQL enforcement, factory-based Semantic Kernel loading, manifest validation, schemas, feature documentation, and functional/UI coverage. + * (Ref: `DatabricksPlugin`, `DatabricksPluginFactory`, Databricks action modal, `DATABRICKS_ACTION_CONFIGURATION.md`) + +* **Model Context Protocol Actions** + * Added first-class MCP action support with transport, authentication, timeout, tool allowlist, and cached tool metadata configuration in the shared action modal. + * Added server-side MCP tool discovery plus runtime tool invocation through Semantic Kernel's MCP connector, including dynamic tool function registration for agents. + * Restricted stdio MCP transport to admin-managed global actions because it launches server-side commands, while remote transports support streamable HTTP, SSE, and WebSocket. + * (Ref: MCP actions, Semantic Kernel MCP connector, `functions_mcp_operations.py`, `mcp_plugin.py`, `mcp_plugin_factory.py`, `route_backend_plugins.py`, `plugin_modal_stepper.js`, `MCP_ACTION_CONFIGURATION.md`) + +* **Layered Message Masking** + * Added mask-plus and mask-minus controls so users can add multiple selected-text masks to the same chat message. + * Full-message masks now layer independently from selected-text masks, allowing users to remove the full-message mask while preserving prior selected ranges. + * Extended masking support to collaborative personal and group conversations, including shared event updates and source-message metadata sync. + * (Ref: message masking, collaborative conversations, `functions_message_masking.py`, `route_backend_chats.py`, `route_backend_collaboration.py`, `chat-messages.js`, `chat-collaboration.js`) + +* **Workspace and Global Identities** + * Promoted reusable identities into first-class personal, group, and public workspace tabs instead of managing them from the File Sync source list. + * Added an admin-managed Global Identities tab for credentials shared by global agents, actions/plugins, model endpoints, and future global integrations. + * Added a dedicated global identity Cosmos DB container while keeping public workspace identities limited to File Sync usage and excluding File Sync from global identities. + * (Ref: workspace identities, global identities, `functions_workspace_identities.py`, `route_backend_workspace_identities.py`, `workspace-identities.js`, `_sidebar_nav.html`) + +* **Visio Ingestion and Citation Previews** + * Added native `.vsdx` upload support that parses Visio package XML and indexes each diagram page as a structured searchable chunk. + * Enhanced citations now render a lightweight PNG preview for the cited Visio page and keep the original `.vsdx` available for download. + * Added functional and UI coverage for Visio parsing, preview rendering, and chat citation modal behavior. + * (Ref: Visio ingestion, enhanced citations, `functions_visio.py`, `functions_documents.py`, `route_enhanced_citations.py`, `chat-enhanced-citations.js`, `test_visio_ingestion_preview.py`, `VISIO_INGESTION.md`) + +* **Outlook MSG File Ingestion** + * Added support for Outlook `.msg` files so saved email messages can be uploaded into workspaces and participate in document processing, metadata extraction, chunking, search, and chat grounding. + * Email files now fit the same workspace knowledge workflow as other supported business documents, helping users bring message-based context into conversations without manually copying mail content. + * (Ref: Outlook MSG ingestion, workspace uploads, document processing, `functions_content.py`, `functions_documents.py`) + +* **Assigned Knowledge for Agents** + * Added agent-level Assigned Knowledge so agent creators can bind agents to governed workspace sources, documents, and tags. + * Chat now resolves the selected agent from trusted server-side records and enforces its assigned search scope for both regular and streaming chat, including personal, group, and public workspace boundaries. + * When an Assigned Knowledge agent is selected in chat, document search is forced on and the workspace, document, and tag controls become read-only while displaying the agent's configured knowledge context. + * (Ref: Assigned Knowledge, agent modal Knowledge step, chat document search enforcement, `functions_assigned_knowledge.py`, `route_backend_agents.py`, `route_backend_chats.py`, `agent_modal_stepper.js`, `chat-agents.js`, `chat-documents.js`) + +* **Deep Research Distroless JavaScript Rendering Runtime** + * Added Playwright Chromium packaging for the existing Azure Linux distroless app image so Deep Research can optionally render JavaScript-heavy source pages without changing the final container base image. + * Added a runtime capability check that verifies Chromium launch support and surfaces the status in Admin Settings before admins rely on rendered-page fallback. + * Kept Chromium sandboxing enabled by default, added an explicit `SOURCE_REVIEW_CHROMIUM_NO_SANDBOX` escape hatch for reviewed deployments, and capped rendered fetch concurrency with `SOURCE_REVIEW_JS_RENDER_MAX_CONCURRENCY`. + * (Ref: Deep Research JavaScript rendering, distroless container runtime, `Dockerfile`, `requirements.txt`, `functions_source_review.py`, `admin_settings.html`) + +* **Source Review Load More Support** + * Source Review can now use the optional rendered-page path to click visible Load More, Show More, View More, and related archive controls on source pages before extracting links and evidence. + * Load More clicks are bounded by a configurable admin cap, stop early when no new content appears, and can stop when requested date-range evidence is visible for prompts such as past three years. + * Existing SSRF, redirect, timeout, page-budget, and content-type protections remain enforced. + * (Ref: Source Review Load More, `functions_source_review.py`, `admin_settings.html`, `test_source_review_security.py`) + +* **Source Review Model-Assisted Link Planning** + * Deep Source Review can now ask the selected chat model to rank server-extracted child links before additional source pages are fetched, improving general multi-source research without adding question-specific heuristics. + * The planner is bounded to already extracted, policy-approved candidate URLs; invented URLs are ignored and deterministic ordering remains the fallback. + * Added admin control for enabling model-assisted link planning and functional coverage for candidate validation and planner-driven ordering. + * (Ref: Deep Source Review, Source Review link planning, `functions_source_review.py`, `route_backend_chats.py`, `admin_settings.html`, `test_source_review_deep_traversal.py`) + +* **File Sync for Workspace Documents** + * Added an optional SMB-based File Sync capability for personal, group, and public workspaces, with scope-specific enablement, allow/block controls, and Redis readiness gating before sync can be enabled. + * Sync sources support UNC paths, credentials with optional Azure Key Vault storage, fixed and parent-folder-derived tags, include/exclude filters, file type filters, manual runs, scheduled runs, history, counts, and debug logging. + * Synced remote file changes reuse the existing same-name document upload behavior to create document versions, and synced-document deletes now ask whether to delete locally only or ignore the remote path for future runs. + * Added Control Center activity-log support for File Sync events and admin warnings for scale and performance considerations. + * (Ref: File Sync, SMB sync sources, workspace Sync tab, `functions_file_sync.py`, `route_backend_file_sync.py`, `workspace-file-sync.js`, `test_file_sync_capability.py`, `FILE_SYNC.md`) + +* **Source Review for Web Evidence** + * Added an optional chat **Sources** toggle that reviews source pages from pasted URLs and Web Search citations before the final model response is generated. + * Deep Source Review can follow a bounded set of relevant links from source indexes while enforcing SSRF protections, redirect/page-size/time limits, robots.txt handling, and prompt-injection isolation for fetched page text. + * Added admin controls for Source Review defaults, page budgets, domain/user allowlists and blocklists, optional JavaScript rendering fallback, and audit logging. + * (Ref: Source Review, `functions_source_review.py`, `route_backend_chats.py`, `admin_settings.html`, `chats.html`, `test_source_review_security.py`, `SOURCE_REVIEW.md`) + +* **Conversation Charts and Workflow Tabular Reuse** + * Added chart creation as a core conversation ability so users can request inline charts directly in chat while agents and workflows can still use assigned chart actions. + * Added a reusable tabular analysis import surface for workflow document analysis and comparison, reducing workflow coupling to the chat route while preserving existing chat tabular behavior. + * Enabled Semantic Kernel auto tool invocation in the model-only fallback path so core conversation tools can be called when chart requests are routed through the kernel. + * (Ref: conversation charts, workflow tabular analysis, `semantic_kernel_loader.py`, `route_backend_chats.py`, `functions_tabular_analysis.py`, `functions_workflow_runner.py`, `test_conversation_chart_and_tabular_reuse.py`) + +* **Generated Markdown Artifact Viewer** + * Added a `View MD` action beside `Download MD` on generated Markdown artifact cards so users can inspect rendered Markdown directly in Chats before downloading the file. + * Reused the citation modal for the rendered view and improved Markdown citation handling so `.md` and `.markdown` citations display as sanitized rendered Markdown instead of raw source text. + * Added UI regression coverage for rendered previews, rendered artifact modal content, and unsafe attribute stripping. + * (Ref: generated Markdown artifacts, citation modal Markdown rendering, `chat-messages.js`, `chat-citations.js`, `test_chat_generated_tabular_output_card.py`, `GENERATED_ARTIFACT_MARKDOWN_VIEW.md`) + +* **Tabular Related Document Evidence** + * Added generic row-level related-document resolution for workspace tabular analysis, so when a CSV or workbook row explicitly references a supporting non-tabular file, the tabular path can pull excerpts from that document and use them alongside the computed row results. + * Related document evidence now flows into both the outer tabular handoff and generated structured exports, which helps responses use supporting file context without treating those files as isolated search-only results. + * Added focused regression coverage and versioned feature documentation for the related-document matching, evidence summary, and export prompt wiring. + * (Ref: tabular related-document evidence, `route_backend_chats.py`, `functions_search_service.py`, `test_tabular_related_document_evidence.py`, `test_tabular_computed_results_prompt_priority.py`, `test_tabular_generated_output_exports.py`, `TABULAR_RELATED_DOCUMENT_EVIDENCE.md`) + +* **Generated Artifact Workspace Promotion Approval** + * Added an `Add to Workspace` action to generated analysis artifact cards in Chats so users can move reusable exports out of the conversation and into workspace documents. + * Personal promotions now queue immediately, while group and public promotions create a visible pending workspace file that must be approved before it becomes usable for search and chat. + * Group and public workspace document lists now show an `Approve` action for pending generated artifacts, and the requester receives approval workflow notifications as the file moves through review and processing. + * (Ref: generated artifact promotion, `route_enhanced_citations.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`, `chat-messages.js`, `group_workspaces.html`, `public_workspace.js`, `test_generated_artifact_workspace_promotion.py`, `test_chat_generated_tabular_output_card.py`) + +* **Chat Clipboard Paste Uploads** + * Added direct clipboard upload support in Chats so users can paste copied images and browser-exposed files straight into the main chat message box instead of opening the file picker first. + * The pasted upload flow now reuses the existing chat upload pipeline, including automatic conversation creation, upload consent checks, and backend file processing. + * Clipboard files with empty names are normalized before upload so pasted screenshots still reach the existing extension-based processing path. + * (Ref: chat paste uploads, `chat-input-actions.js`, `test_chat_clipboard_paste_upload_support.py`, `test_chat_clipboard_paste_upload_workflow.py`, `CHAT_CLIPBOARD_PASTE_UPLOADS.md`) + +* **Staging Branch UI Test CI/CD** + * Added a protected GitHub Actions workflow for the `Staging` branch that deploys the Azure Developer CLI staging environment, waits for App Service warm-up, and runs live UI smoke coverage before the environment is considered healthy. + * Added a reusable staging bootstrap script for GitHub OIDC app registration, federated credentials, Azure role assignments, GitHub Environment variables, App Service CI authentication settings, and Microsoft Playwright Workspace wiring. + * (Ref: `.github/workflows/staging-azd-ui-tests.yml`, `deployers/Initialize-GitHubActionsStaging.ps1`, `docs/explanation/features/v0.241.014/STAGING_UI_CICD.md`, `test_staging_ui_cicd_workflow.py`) + +* **Microsoft Playwright Workspaces Staging Runner** + * Added Azure-hosted Playwright Workspace support for staging smoke tests, including Node-based Playwright service execution and matching Python smoke coverage for the staging chat experience. + * (Ref: `ui_tests/playwright-workspaces/`, `ui_tests/test_staging_chat_smoke.py`, `PLAYWRIGHT_SERVICE_URL`) + +* **Service Principal Authentication for CI UI Tests** + * Added a disabled-by-default `/ci-auth/session` endpoint so staging UI tests can create a Flask session from a fresh Entra access token minted by the GitHub OIDC service principal. + * (Ref: `functions_authentication.py`, `route_frontend_authentication.py`, `config.py`, `appRegistrationRoles.json`, `SIMPLECHAT_UI_AUTH_RESOURCE`, `ENABLE_CI_BEARER_SESSION_AUTH`) + +* **Profile Sidebar Toggle Style Preference** + * Added a profile navigation preference for large versus compact sidebar hide controls and applied it across full and compact sidebar templates. + * (Ref: `profile.html`, `_sidebar_nav.html`, `_sidebar_short_nav.html`, `sidebar.css`, `route_backend_users.py`, `test_profile_sidebar_toggle_style_preference.py`) + +#### Bug Fixes + +* **Azure OpenAI Model Discovery Identity Split** + * Legacy GPT, embedding, and image `Fetch Models` routes now use the configured app registration/service principal management-plane credential for Azure OpenAI deployment discovery instead of implicitly following runtime data-plane managed identity behavior. + * Deployer assets now grant the app registration/service principal `Cognitive Services User` for model discovery while preserving the App Service managed identity `Cognitive Services OpenAI User` role for data-plane inference. + * (Ref: `route_backend_models.py`, deployer OpenAI RBAC, `AZURE_OPENAI_MODEL_DISCOVERY_IDENTITY_FIX.md`) + +* **Chat Model Icon Avatars** + * Fixed saved model endpoint icons and uploaded model images not appearing on model-only assistant responses in chat. + * Model icon metadata now flows through multi-endpoint model resolution, streaming and non-streaming response metadata, and assistant avatar rendering for model-only responses. + * Preserved agent avatar priority so agent responses never fall through to the model icon when an agent identity is present. + * (Ref: chat assistant avatars, model endpoint icons, `route_backend_chats.py`, `chat-messages.js`) + +* **Tabular SK Python 3.13 Kernel Parameter Compatibility** + * Updated public tabular `@kernel_function` parameters to avoid `Annotated[Optional[str], ...]` so Semantic Kernel argument parsing works on both Python 3.12 and Python 3.13. + * Added a guardrail test that fails if optional string annotations are reintroduced on public tabular tool parameters. + * Preserved current Development model-context routing instead of reintroducing older endpoint-specific route wiring. + * Inspired by and adapted from PR #892 by @vivche. + * (Ref: Python 3.13, Semantic Kernel tool parsing, tabular SK parameters, `test_tabular_kernel_parameter_annotations.py`) + +* **Python 3.12 CI and XSS Guardrail Fix** + * Updated GitHub workflow Python setup from 3.11 to 3.12 to match the supported SimpleChat runtime and prevent valid Python 3.12 f-string syntax from failing CI parse checks. + * Reworked changed Admin Settings, group workspace delete modal, and profile hero rendering paths to satisfy the XSS sink guardrail without broad suppressions. + * (Ref: Python 3.12 CI, XSS sink validation, Admin Settings bootstrap data, group workspace delete modal, profile hero image) + +* **PR Readiness Guardrail Cleanup** + * Fixed pull-request validation blockers by removing trailing whitespace, dropping an unnecessary `|safe` filter from JSON-rendered Admin Settings version data, removing a UTF-8 BOM from the Semantic Kernel loader, and documenting reviewed plugin authorization boundaries for the BAC guardrail. + * Keeps the beta branch aligned with SimpleChat PR hygiene, XSS, route, and broken-access-control validation before draft PR creation. + * (Ref: PR readiness, `check_xss_sinks.py`, `check_broken_access_control.py`, Semantic Kernel plugins) + +* **Governance Admin Rendering XSS Hardening** + * Reworked changed admin governance, model endpoint, agent, and plugin table rendering paths so untrusted names, descriptions, IDs, and labels are populated with DOM APIs and `textContent` instead of interpolated HTML attributes. + * Added narrow reviewed XSS guardrail suppressions only for static Bootstrap modal shells that do not interpolate untrusted values. + * (Ref: governance admin UI, model endpoint table, plugin table rendering, XSS sink validation) + +* **Group Workflow Assignment Cleanup** + * Fixed Admin Settings form bloat caused by malformed nested JSON strings being saved as group workflow assignment IDs. + * Group workflow assignment settings now preserve valid group UUIDs, drop invalid payload fragments, and compact the hidden admin form field before save. + * (Ref: `functions_settings.py`, `admin_settings.js`, `GROUP_WORKFLOW_ASSIGNMENT_CLEANUP_FIX.md`) + +* **Workflow Activity New-Tab Navigation** + * Fixed workflow `Activity` actions so they no longer navigate the current workspace tab after opening the activity view in a new tab. + * Blocked pop-ups now show a warning toast instead of replacing the workflow list page. + * (Ref: `workspace_workflows.js`, workflow Activity button, `WORKFLOW_ACTIVITY_CURRENT_TAB_NAVIGATION_FIX.md`) + +* **Group Workflow Activity View Gate** + * Fixed group workflow activity links so the shared `/workflow-activity` page no longer depends on the personal workflow feature flag when opened with `scope=group`. + * Group activity views now use group-specific authorization, including group workspaces enabled, group workflows enabled, group assignment gating, and current group membership validation. + * Personal workflow activity links still use the existing personal workflow and WorkflowUser app-role policy. + * (Ref: `route_frontend_chats.py`, `workflow-activity.js`, `GROUP_WORKFLOW_ACTIVITY_VIEW_GATE_FIX.md`) + +* **Tabular Inline Chart Handoff** + * Fixed tabular analysis chart requests so successful grouped CSV/XLSX results now produce SimpleChat inline chart citations instead of relying on the model to emit supported chart syntax. + * Workspace-search and chat-uploaded tabular results now share the same deterministic chart handoff in both streaming and non-streaming responses, preventing unsupported Mermaid chart blocks from appearing when users request charts. + * (Ref: `route_backend_chats.py`, `functions_chart_operations.py`, `test_tabular_inline_chart_handoff.py`, `TABULAR_INLINE_CHART_HANDOFF_FIX.md`) + +* **Document Intelligence Upload Normalizer Import** + * Fixed Azure Document Intelligence upload processing so the shared extractor resolves the extraction-mode normalizer through the existing settings module import, preventing Read/Layout/Auto uploads from failing with a missing normalizer name while avoiding the startup circular import path. + * (Ref: `functions_content.py`, Document Intelligence extraction mode) + +* **Cosmos Native Autoscale Migration Action** + * Fixed manual-to-autoscale conversions so manual Cosmos throughput offers call the ARM `migrateToAutoscale` action instead of attempting to write `autoscaleSettings.maxThroughput` directly onto a manual offer. + * Preserved the existing `PUT autoscaleSettings.maxThroughput` path for database or container throughput that is already in autoscale mode. + * Expanded the least-privilege Cosmos throughput operator role with the `migrateToAutoscale/action` and operation-result read permissions required for native conversion without Cosmos data-plane access. + * (Ref: `functions_cosmos_throughput.py`, `setPermissions.bicep`, `COSMOS_NATIVE_AUTOSCALE_MIGRATION_ACTION_FIX.md`) + +* **Cosmos Autoscale Background Cadence** + * Updated the Cosmos throughput background scheduler so the check cadence follows the configured Metrics Window instead of a hard-coded five-minute sleep. + * Added background-specific autoscale start, completion, and sleep logs so scheduler runs are distinguishable from manual Admin Settings Refresh requests. + * Clarified Admin Settings copy that background automation refreshes on the Metrics Window cadence while Scale Up/Down intervals remain cooldowns after scaling. + * (Ref: `background_tasks.py`, `functions_cosmos_throughput.py`, Admin Settings Scale tab, `COSMOS_AUTOSCALE_BACKGROUND_CADENCE_FIX.md`) + +* **Cosmos Container Metrics REST Metadata Parsing** + * Switched Cosmos throughput metric collection from the Azure Monitor Query SDK response model to the raw Azure Monitor Metrics REST response for this feature, because the SDK returned container-split time series without usable metadata names or values. + * Restored per-container RU utilization and request-unit rows by parsing REST `collectionname` and `databasename` metadata from the same metric dimensions shown in Azure Metrics Explorer. + * (Ref: `functions_cosmos_throughput.py`, Azure Monitor Metrics REST, Cosmos `CollectionName` dimensions, `COSMOS_CONTAINER_METRICS_REST_METADATA_FIX.md`) + +* **Cosmos Container Autoscale Metric Accuracy and Refresh Performance** + * Tightened the Azure Monitor query so container-targeted scaling requests `NormalizedRUConsumption` split by the configured database and `CollectionName`, matching the per-container view available in the Azure portal. + * Container autoscale now explicitly waits for per-container utilization rows instead of treating aggregate account-level utilization as eligible input for individual container scaling. + * Reduced Admin Settings refresh latency by reusing one ARM token and reading per-container throughput settings in a bounded parallel scan instead of serial per-container reads. + * (Ref: `functions_cosmos_throughput.py`, Cosmos throughput Azure Monitor dimensions, ARM container throughput reads, `COSMOS_CONTAINER_THROUGHPUT_REFRESH_PERFORMANCE_FIX.md`) + +* **Cosmos Container Metric Dimensions** + * Fixed Cosmos throughput status refreshes so Azure Monitor is asked for per-container metric dimensions instead of only aggregate account-level RU metrics. + * Preserved the aggregate RU utilization card through a fallback query when container-dimensional metrics are delayed or unavailable, and added clearer Admin Settings messaging for that aggregate-only state. + * (Ref: `functions_cosmos_throughput.py`, `admin_settings.js`, Cosmos throughput Azure Monitor metrics, `COSMOS_CONTAINER_METRICS_DIMENSION_FIX.md`) + +* **Cosmos Throughput Cached Status** + * Fixed the Admin Settings Cosmos throughput card so it renders the last saved database or container-targeted view immediately after server restart instead of requiring a manual Refresh to rediscover containers. + * Manual Refresh and background autoscale checks now persist a compact cached status with capacity scope, throughput summary, metrics, container rows, and timestamps. + * Added copy clarifying that background automation checks throughput about every 5 minutes while enabled, and versioned the Admin Settings JavaScript asset to avoid stale browser-side Cosmos UI logic. + * (Ref: `functions_cosmos_throughput.py`, `admin_settings.html`, `admin_settings.js`, `COSMOS_THROUGHPUT_CACHED_STATUS_FIX.md`) + +* **Container Policy Save Button Activation** + * Fixed the Cosmos throughput container policy modal so saving staged container policies enables the main Admin Settings Save button immediately. + * The modal now uses the standard admin form dirty-state handler instead of setting only the internal modified flag. + * (Ref: `admin_settings.js`, Admin Settings Scale tab, `COSMOS_CONTAINER_POLICY_SAVE_BUTTON_FIX.md`) + +* **Cosmos Throughput Refresh Logging** + * Added backend start, completion, failure, and phase timing logs for Admin Settings Cosmos throughput refreshes so admins can see whether the request is waiting on token acquisition, ARM throughput reads, container scans, or Azure Monitor metrics. + * Added a refresh correlation ID across route, ARM, container, and metrics logs to make a single Refresh click traceable in console logs and Application Insights. + * (Ref: `functions_cosmos_throughput.py`, `route_backend_settings.py`, Cosmos throughput refresh diagnostics) + +* **Advanced Conversation Search Matching** + * Fixed the Advanced Search modal so it searches conversation titles and message content across both legacy and collaborative conversation stores. + * Added explicit match modes for partial text, all words, any word, and whole word searches, with partial matching as the default so terms such as `Chase` can match larger tokens like `JPMorganChase`. + * Normalized chat type filters so personal and multi-user conversation types are not silently excluded from advanced search results. + * (Ref: advanced conversation search, chat search modal, `route_backend_conversations.py`, `chat-search-modal.js`, `ADVANCED_CONVERSATION_SEARCH_FIX.md`) + +* **Visio Connector and Arc Fidelity** + * Improved Visio citation previews by approximating `RelEllipticalArcTo` geometry as smooth curves instead of straight endpoint segments. + * Removed duplicate fallback center-to-center connection lines when explicit connector geometry is already available, reducing visual clutter through service icons. + * Added bounded supersampling to smooth rendered PNG previews while avoiding external office-suite dependencies. + * Added regression coverage for curved master stencil geometry in the preview parser path. + * (Ref: Visio arc geometry, connector rendering, master stencil preview expansion, `functions_visio.py`, `test_visio_ingestion_preview.py`) + +* **Visio Path and Master Geometry Rendering** + * Improved the built-in Visio citation preview renderer to draw supported VSDX geometry rows as actual local paths instead of collapsing those shapes to generic rectangles. + * Added preview-only expansion of referenced master stencil geometry so common Azure/service icons render with more recognizable vector structure while indexed Visio chunks stay focused on page content. + * Improved label placement for icon-backed shapes and dashed container labels using the Visio-exported SVG/PDF reference fixtures. + * Added regression coverage to ensure preview master expansion does not pollute default ingestion parsing. + * (Ref: Visio path geometry, master stencil geometry, structural renderer, `functions_visio.py`, `test_visio_ingestion_preview.py`, `architecture.svg`, `architecture.pdf`) + +* **Visio Preview Runtime Simplification** + * Removed the optional LibreOffice conversion branch from Visio citation previews after confirming Azure Linux `tdnf` does not provide LibreOffice packages in the app builder image. + * Visio previews now consistently use the built-in structural renderer with nested shape coordinates, connector endpoint lines, supported embedded media, and page geometry. + * (Ref: Visio previews, structural renderer, `functions_visio.py`, `VISIO_PREVIEW_FIDELITY_FIX.md`) + +* **Visio Citation Preview Fidelity** + * Strengthened the built-in Visio renderer so it preserves nested shape coordinates, connector endpoint lines, supported embedded media, and page geometry more accurately. + * (Ref: Visio previews, structural rendering, `functions_visio.py`, `VISIO_PREVIEW_FIDELITY_FIX.md`) + +* **Source Review Citation Seeding and Second-Hop Traversal** + * Source Review now receives the full Foundry web-search citation set, not only URLs that appeared in the web-search answer text, so official sources returned as raw citations can be reviewed directly. + * Added a configurable seed-page budget so initial search-result pages cannot consume the entire Source Review page budget before child source pages are inspected. + * Raised bounded Deep Source Review depth to support one additional hop, allowing flows such as official news archive -> press-release section -> year/detail page while preserving page, redirect, timeout, type, and SSRF limits. + * (Ref: Web Search citations, Deep Source Review, `route_backend_chats.py`, `functions_source_review.py`, `admin_settings.html`, `test_web_search_current_message_only.py`, `test_source_review_deep_traversal.py`) + +* **Deep Source Review Link Prioritization and Audit Detail** + * Fixed Deep Source Review link extraction so relevant press-release/archive links are scored before link inventory limits are applied, preventing noisy navigation links from crowding out useful source-detail candidates. + * Generic archive traversal now rejects shallow same-domain navigation such as About and Careers pages unless the link has a stronger source/archive signal. + * Source Review audit logs and thought details now expose seed pages, child pages, Deep Source Review usage, planner attempted/used state, planner candidate count, and selected planner URLs. + * (Ref: Deep Source Review, Source Review audit logging, `functions_source_review.py`, `route_backend_chats.py`, `test_source_review_deep_traversal.py`) + +* **Deep Source Review Traversal Balance** + * Improved Deep Source Review so seed/archive pages are reviewed before child links consume the remaining page budget, giving multi-source research requests better coverage across official sources. + * Child-link scoring now favors generic release/detail archive patterns and downranks common navigation pages without adding company-specific heuristics. + * (Ref: Deep Source Review, `functions_source_review.py`, `test_source_review_deep_traversal.py`, `SOURCE_REVIEW_DEEP_TRAVERSAL_FIX.md`) + +* **Authenticated Request Login Activity Tracking** + * Fixed login analytics so passive authenticated browser visits now contribute to login activity even when the user does not explicitly trigger the OAuth callback during that session. + * Added throttled authenticated-request tracking to avoid inflating counts on every page load, while still preserving the explicit `azure_ad` login signal and avoiding an immediate duplicate on the post-login redirect. + * This improves Control Center and profile login visibility for seamless SSO and session-reuse scenarios without changing the user-facing login flow. + * (Ref: authenticated request login activity, `functions_activity_logging.py`, `app.py`, `route_frontend_authentication.py`, `test_authenticated_request_login_activity.py`, `AUTHENTICATED_REQUEST_LOGIN_ACTIVITY_FIX.md`) + +* **Safety Violation Remediation Workflow** + * Fixed the Safety Violations admin flow so `Warn user` now sends a user notification, `Suspend user` applies the same timed access restriction used by Control Center, and `Block user` applies the same indefinite deny path. + * Safety admins who do not hold the required approval role now create a pending approval request instead of applying the remediation immediately, while eligible reviewers can still self-approve and execute their own request when policy allows it. + * The safety review modal and shared approvals page now expose the notification details, suspension restore date, and explicit warn/suspend/block approval labels needed to review and execute those requests cleanly. + * (Ref: `route_backend_safety.py`, `route_backend_control_center.py`, `functions_approvals.py`, `functions_safety_remediation.py`, `functions_notifications.py`, `admin_safety_violations.html`, `admin-safety-violations.js`, `approvals.html`, `test_safety_violation_remediation_approvals.py`) + +* **Group and Public Workspace Hero Color Editing** + * Fixed the group and public workspace manage pages so hero color selections now apply to the saved workspace branding instead of leaving those selectors effectively non-functional. + * The manage-page hero preview now stays in sync with the selected color, and the saved branding metadata flows back through the workspace APIs for consistent rendering. + * (Ref: workspace branding, `manage_group.js`, `manage_public_workspace.js`, `route_backend_groups.py`, `route_backend_public_workspaces.py`) + +* **Chat-Scoped Generated Tabular Exports** + * Fixed large tabular JSON and CSV export requests so the generated file now stays attached to the active chat instead of being pushed through the personal workspace document pipeline. + * Assistant replies now keep the exhaustive dataset in a downloadable chat artifact with the existing preview card, which makes large structured outputs more reliable while keeping the visible answer concise. + * Personal conversation deletion and retention cleanup now remove blob-backed generated chat files when archiving is disabled, closing the lifecycle gap for conversation-scoped exports. + * (Ref: generated tabular exports, `route_backend_chats.py`, `functions_simplechat_operations.py`, `route_enhanced_citations.py`, `route_backend_conversations.py`, `functions_retention_policy.py`, `chat-messages.js`) + +* **Fact Memory Delete Confirmation Layering** + * Fixed the profile fact-memory workflow so the delete confirmation now opens above the Manage Fact Memories editor instead of appearing underneath it. + * Users can now confirm or cancel a delete without closing the editor first, and the manager modal remains active so they can continue reviewing saved memories immediately after the confirmation closes. + * Added focused UI regression coverage for the stacked modal behavior. + * (Ref: fact memory management, `profile.html`, `test_profile_fact_memory_editor.py`) + +* **Live Tabular Analysis Thought Progress** + * Fixed long-running tabular analysis chats so workbook tool activity now streams into the thoughts panel while the answer is still being prepared, instead of waiting until the tabular pass completes. + * Tabular requests now show a dedicated progress card with the current step, running and completed tool-call counts, and a clearer completion state when workbook evidence is ready. + * Added focused UI regression coverage for the live tabular progress card and kept the adjacent agent-progress behavior covered. + * (Ref: tabular analysis streaming, `route_backend_chats.py`, `chat-thoughts.js`, `test_chat_tabular_thought_progress.py`, `test_chat_agent_thought_progress.py`) + +* **Workspace Search Document Action Gating** + * Fixed chat document actions so Review and Compare now only apply while Workspace Search is enabled. + * Turning Workspace Search off now ignores any previously selected Review or Compare mode instead of routing the request through document-action validation and showing stale "select documents before starting a review" warnings. + * Added a focused UI regression test for the workspace-toggle flow so normal chat sends continue using the standard chat stream when workspace search is disabled. + * (Ref: workspace search toggle, `chat-messages.js`, `test_chat_document_action_workspace_toggle.py`) + +* **Chat File Upload Client Enablement** + * Fixed chat file uploads so the effective per-user upload setting is serialized to the browser upload guards. + * Users with chat uploads enabled no longer see the `Chat file uploads are not enabled for your account.` warning caused by a missing client-side flag. + * (Ref: chat upload controls, `chats.html`, `test_chat_file_upload_access_control.py`, `CHAT_FILE_UPLOAD_CLIENT_FLAG_FIX.md`) + +* **Document Auto Metadata Extraction Consistency** + * Fixed upload processing so all supported file types run the same automatic final metadata extraction flow when metadata extraction is enabled. + * Corrected public workspace audio and video chunk scoping so public media files participate correctly in metadata extraction and metadata-to-chunk synchronization. + * Preserved final processing statuses that indicate whether metadata was extracted, yielded no new information, or completed with a metadata warning. + * (Ref: document upload metadata extraction, public workspace media chunks, `functions_documents.py`, `DOCUMENT_AUTO_METADATA_EXTRACTION_FIX.md`) + +* **Chat Stream Lifecycle Observability** + * Improved diagnostics for long-running chat streams so backend status now distinguishes active, detached-but-running, completed, and errored stream states during the replay window. + * Added backend lifecycle logging for keepalive, detach, reattach, queue backpressure, and terminal stream outcomes, plus frontend best-effort telemetry for request failures, read failures, premature endings, aborts, and recovery attempts. + * Added focused regression coverage and versioned fix documentation for the new stream observability path. + * (Ref: `route_backend_chats.py`, `chat-streaming.js`, `test_chat_stream_lifecycle_observability.py`, `CHAT_STREAM_LIFECYCLE_OBSERVABILITY_FIX.md`) + +* **Uploaded File Preview Body XSS Hardening (`f044`)** + * Fixed the uploaded-file preview modal so stored file bodies no longer reach the preview pane through raw HTML sinks. + * Plain-text previews now render as inert preformatted text, CSV-backed previews are built with DOM text nodes, and legacy HTML-backed table payloads now fall back to inert text instead of live markup. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened preview path. + * (Ref: `chat-input-actions.js`, `test_uploaded_file_preview_xss_fix.py`, `test_uploaded_file_preview_escaping.py`, `UPLOADED_FILE_PREVIEW_XSS_FIX.md`) + +* **Public Workspace Tag Color XSS Hardening (`f043`)** + * Fixed the public workspace tag surfaces so stored tag colors no longer reach folder-grid actions, tag badges, tag management rows, or selected-tag chips through inline handler or style interpolation. + * Shared tag helper paths now normalize and validate tag colors on create and update across personal, group, and public routes, and previously stored invalid colors fall back to safe deterministic values on read. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened public tag rendering path. + * (Ref: `functions_documents.py`, `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`, `public_workspace.js`, `test_public_workspace_tag_color_xss_fix.py`, `test_public_workspace_tag_color_rendering.py`, `PUBLIC_WORKSPACE_TAG_COLOR_XSS_FIX.md`) + +* **Agent Template Gallery Actions Escaping (`f045`)** + * Fixed the agent template gallery so stored `actions_to_load` values no longer reach the recommended-actions row through a raw HTML sink. + * Agent template helper paths now normalize `actions_to_load` consistently on read, create, and update flows, and invalid write payload shapes are rejected before they can persist. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened gallery path. + * (Ref: `agent_templates_gallery.js`, `functions_agent_templates.py`, `test_agent_template_gallery_actions_to_load_xss_fix.py`, `test_agent_template_gallery_actions_escaping.py`, `AGENT_TEMPLATE_GALLERY_ACTIONS_TO_LOAD_XSS_FIX.md`) + +* **Stored XSS Share, Activity, and Masking Hardening (`f022`, `f042`, residual `f037`)** + * Fixed the remaining stored-XSS share-modal flows so attacker-controlled user names, group names, descriptions, emails, and toast content no longer render through inline handlers or raw HTML sinks. + * Hardened the group activity timeline and raw-activity modal so stored activity metadata and serialized activity JSON now render as inert text instead of executable markup. + * Rebuilt masked-range rendering with DOM APIs and bound masking display names to the authenticated server-side user instead of trusting browser-supplied identity fields. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened sharing, activity, and masking paths. + * (Ref: `chat-toast.js`, `workspace-documents-sharing.js`, `group-documents-sharing.js`, `manage_group.js`, `chat-messages.js`, `route_backend_chats.py`, `test_stored_xss_share_activity_and_masking_fix.py`, `test_document_share_modal_escaping.py`, `STORED_XSS_SHARE_ACTIVITY_AND_MASKING_FIX.md`) + +* **Chat Scope Picker and Conversation Details XSS Hardening (`f021`)** + * Fixed the chat scope-lock picker so stored group and public workspace names no longer reach the locked-workspaces modal through raw HTML interpolation. + * Hardened the conversation-details modal so attacker-controlled titles, context names, participant labels, document labels, semantic tags, classifications, and scope-lock names render as inert text, and invalid web-source values no longer produce active `javascript:` links. + * Added focused functional and UI regression coverage plus versioned fix documentation for the affected chat modal surfaces. + * (Ref: `chat-documents.js`, `chat-conversation-details.js`, `test_stored_xss_chat_scope_and_conversation_details_fix.py`, `test_chat_scope_lock_and_conversation_details_escaping.py`, `CHAT_SCOPE_LOCK_AND_CONVERSATION_DETAILS_XSS_FIX.md`) + +* **Chat Citation and Uploaded File Modal Filename XSS Hardening (`f020`)** + * Fixed the first-render chat citation modal so attacker-controlled document filenames returned from citation APIs no longer reach the modal header as raw HTML on the first open. + * The uploaded-file preview modal now uses the same safe title-population path, closing the adjacent filename sink before it can regress into the same stored-XSS family. + * Added focused functional and UI regression coverage plus versioned fix documentation for both modal title flows. + * (Ref: `chat-citations.js`, `chat-input-actions.js`, `test_stored_xss_chat_modal_filename_fix.py`, `test_chat_modal_filename_escaping.py`, `CITATION_AND_FILE_MODAL_FILENAME_XSS_FIX.md`) + +* **Stored XSS Agent and Member Rendering Hardening (`f009`, `f010`)** + * Fixed the stored-XSS sink in chat message rendering so agent display names no longer reach the sender header, image header, or metadata drawer as raw HTML. + * Public and group workspace member-management views now escape untrusted member display names and emails before rendering member rows, pending requests, ownership-transfer options, bulk-remove summaries, user-search results, and CSV validation previews, and the public member search no longer embeds untrusted values inside an inline `onclick` handler. + * `/api/userSearch` now escapes Microsoft Graph OData filter literals before composing the `$filter` expression, so apostrophes in search input cannot break the backend Graph query. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened chat, workspace member-management, and Graph filter paths. + * (Ref: `chat-messages.js`, `manage_public_workspace.js`, `manage_group.js`, `route_backend_users.py`, `test_stored_xss_chat_workspace_rendering_fix.py`, `test_public_workspace_member_rendering_escaping.py`, `test_group_workspace_member_rendering_escaping.py`, `STORED_XSS_AGENT_AND_MEMBER_RENDERING_FIX.md`) + +* **Chat Selected Document Metadata Authorization Fix (`f046`)** + * Fixed chat selected-document metadata resolution so `/api/chat`, `/api/chat/stream`, and the selected tabular document helper no longer trust caller-supplied document ids after authentication. + * Personal selected documents now resolve only for the owner or a legitimately shared user, group selected documents now honor authorized owner and shared-group access, and public selected documents now resolve only inside the caller's visible public workspaces. + * Added focused regression coverage for the shared selected-document resolver and updated the existing all-scope tabular regression so the hardened lookup path stays covered. + * (Ref: `route_backend_chats.py`, `test_chat_selected_document_metadata_authorization.py`, `test_tabular_all_scope_group_source_context.py`, `CHAT_SELECTED_DOCUMENT_METADATA_AUTHORIZATION_FIX.md`) + +* **Control Center Public Workspace Members XSS Fix (`f008`)** + * Fixed a stored XSS in the Control Center public workspace members modal where stored member `displayName` and `email` values were rendered into an admin-facing HTML sink. + * The members modal now builds the member row with DOM text nodes instead of injecting those fields through `innerHTML`, so malicious stored markup renders as inert text while the existing role badge styling remains unchanged. + * Added focused regression coverage for the affected modal and documented the hardened sink under the current version line. + * (Ref: `workspace-manager.js`, `test_control_center_public_workspace_members_escaping.py`, `test_stored_xss_admin_rendering_fix.py`, `CONTROL_CENTER_PUBLIC_WORKSPACE_MEMBERS_XSS_FIX.md`) + +* **Plugin Log Recent Feed Admin Authorization Follow-Up** + * Fixed the adjacent plugin logging route so `/api/plugins/invocations/recent` now enforces the `Admin` role instead of exposing the cross-user recent invocation feed to any authenticated user. + * Unauthenticated requests still return `401 Unauthorized`, non-admin users now receive `403 Forbidden`, and the admin response payload remains unchanged for legitimate troubleshooting flows. + * Extended the focused plugin logging regression coverage so both admin-only plugin logging endpoints are exercised under unauthenticated, non-admin, and admin conditions. + * (Ref: `route_plugin_logging.py`, `test_plugin_logging_clear_logs_authorization.py`, `PLUGIN_LOG_RECENT_INVOCATIONS_ADMIN_FIX.md`) + +* **Public Workspace Details Projection Hardening (`f034`)** + * Fixed `GET /api/public_workspaces/` so authenticated non-members no longer receive the full public workspace Cosmos document. + * The route now returns a minimal public summary for non-members and a member-aware payload with explicit `userRole` and `isMember` fields for authorized workspace members, which preserves the manage-page UX without exposing manager lists, pending requests, or other member-only metadata. + * Added focused functional and UI regression coverage to lock down the new payload contract and verify the public directory and non-member workspace page continue to behave correctly. + * (Ref: `route_backend_public_workspaces.py`, `functions_public_workspaces.py`, `manage_public_workspace.js`, `public_directory.js`, `test_security_authorization_hardening.py`, `test_public_workspace_projection_non_member_ui.py`, `PUBLIC_WORKSPACE_DETAILS_DISCLOSURE_FIX.md`) + +* **Approval Route Authorization Guard Consolidation (`f033`)** + * Hardened the approval detail, approve, and deny endpoints so both the admin and non-admin route variants now resolve requests through one shared authorization helper before returning approval data or executing destructive approval actions. + * This reduces the chance of future drift between approval handlers while preserving the existing `403 Forbidden` behavior for callers who are not allowed to view or approve a request. + * Added focused regression coverage to ensure the approval routes continue using the shared authorization path. + * (Ref: `route_backend_control_center.py`, `functions_approvals.py`, `test_security_authorization_hardening.py`) + +* **Feedback Submission Ownership Enforcement (`f038`)** + * Fixed the user feedback submission route so caller-supplied `conversationId` and `messageId` values must resolve inside the authenticated user's own conversation before any feedback row is created. + * Foreign conversation ids now return `403 Forbidden`, missing assistant targets now return `404 Not Found`, and invalid submissions no longer persist copied prompt or AI response content into the caller's feedback history. + * Added focused regression coverage for owner success, foreign-conversation rejection before message lookup, and missing-target rejection without feedback persistence. + * (Ref: `route_backend_feedback.py`, `test_feedback_submission_authorization.py`, `FEEDBACK_AND_PLUGIN_LOG_ACCESS_CONTROL_FIX.md`) + +* **Plugin Log Clear Admin Authorization (`f039`)** + * Fixed the destructive plugin log clear endpoint so only administrators can wipe the shared in-memory plugin invocation history. + * Unauthenticated requests still return `401 Unauthorized`, non-admin authenticated users now receive `403 Forbidden`, and admin behavior remains unchanged for legitimate maintenance flows. + * Added focused regression coverage for unauthenticated, non-admin, and admin clear-log requests against the shared logger state. + * (Ref: `route_plugin_logging.py`, `test_plugin_logging_clear_logs_authorization.py`, `FEEDBACK_AND_PLUGIN_LOG_ACCESS_CONTROL_FIX.md`) + +* **Authorization State Confusion Settings Hardening** + * Completed the remaining settings-boundary hardening so active public workspace selection now validates server-side before it is persisted, instead of accepting arbitrary caller-supplied workspace ids through generic settings updates. + * Public workspace selection routes now share the same validated helper path, and public prompt operations now resolve the active workspace through a canonical authorization check instead of trusting raw stored settings values. + * The generic user-settings update route also now drops unsupported settings keys and returns a client error when a payload contains no valid settings keys, reducing the chance that authorization-sensitive state can bypass dedicated validators in future changes. + * (Ref: `functions_public_workspaces.py`, `route_backend_users.py`, `route_backend_public_workspaces.py`, `route_frontend_public_workspaces.py`, `route_backend_public_prompts.py`, `AUTHORIZATION_STATE_CONFUSION_SETTINGS_FIX.md`) + +* **Key Vault Plugin Secret Scope Enforcement (`f013`)** + * Fixed a plugin Key Vault authorization gap where well-formed full secret names could be stored or replayed across user, group, or global scopes and later resolved with the application's Key Vault identity. + * Plugin secret save, runtime resolution, SQL connection-test resolution, and delete cleanup now verify that stored secret references match the expected scope and source before any Key Vault operation is attempted. + * Added focused regression coverage and versioned fix documentation for the hardened plugin secret boundary. + * (Ref: `functions_keyvault.py`, `semantic_kernel_loader.py`, `route_backend_plugins.py`, `test_keyvault_plugin_secret_scope_enforcement.py`, `KEY_VAULT_PLUGIN_SECRET_SCOPE_ENFORCEMENT_FIX.md`) + +* **Log Analytics Query History User Scope Enforcement (`f016`)** + * Fixed the Log Analytics plugin so query history now binds to the authenticated user on the server instead of accepting an LLM-controlled `user_id` parameter. + * Shared user-settings reads and writes now deny cross-user request access by default unless a reviewed privileged path explicitly opts into a cross-user bypass, and the Control Center admin flows have been updated to use that bypass intentionally. + * Added focused regression coverage and versioned fix documentation for the plugin surface change and the shared user-settings authorization boundary. + * (Ref: `log_analytics_plugin.py`, `functions_settings.py`, `route_backend_control_center.py`, `test_log_analytics_plugin_user_scope_enforcement.py`, `LOG_ANALYTICS_PLUGIN_USER_SCOPE_ENFORCEMENT_FIX.md`) + +* **Personal Conversation Authorization (`f025`, `f027`)** + * Closed personal-conversation authorization gaps so conversation deletion, chat file-content retrieval, and frontend conversation rendering verify ownership before returning or destroying data. + * The chat message loader also now handles `403 Forbidden` and `404 Not Found` conversation-message responses explicitly, so the browser shows a controlled error state instead of assuming every message load succeeds. + * Added focused functional and UI regression coverage plus a separate follow-up fix document under the current release line. + * (Ref: `route_backend_conversations.py`, `route_backend_documents.py`, `route_frontend_conversations.py`, `chat-messages.js`, `test_personal_conversation_followup_authorization.py`, `test_chat_messages_authorization_error.py`, `PERSONAL_CONVERSATION_AUTHORIZATION_FOLLOW_UP_FIX.md`) + +* **Personal Conversation Read Authorization Hardening** + * Fixed authenticated personal conversation read paths so message history and inline image retrieval now verify conversation ownership before returning content. + * Requests that use leaked or foreign conversation identifiers now return `403 Forbidden` instead of disclosing another user's transcript or image content, while the existing missing-resource response contracts remain unchanged. + * Added focused regression coverage and versioned fix documentation for the hardened conversation read boundary. + * (Ref: `f024`, `route_backend_conversations.py`, `test_conversations_read_ownership_authorization.py`, `PERSONAL_CONVERSATION_READ_AUTHORIZATION_FIX.md`) + +* **Broken Access Control IDOR Hardening** + * Closed the authenticated authorization gaps by enforcing personal conversation ownership in chat, binding tabular blob access to the current authorized request context, and binding fact-memory operations to that same canonical scope. + * Request group and public workspace scope is now canonicalized before downstream processing so forged or stale scope identifiers do not survive into plugin execution or grounded-history fallback reuse. + * Added focused regression coverage and versioned fix documentation for the hardened chat and plugin authorization boundary. + * (Ref: `route_backend_chats.py`, `tabular_processing_plugin.py`, `fact_memory_plugin.py`, `test_security_authorization_hardening.py`, `BROKEN_ACCESS_CONTROL_IDOR_HARDENING_FIX.md`) + +* **Stored XSS Admin Rendering Hardening** + * Closed the admin-side stored-XSS findings by escaping stored member and agent metadata before Control Center and Admin Settings HTML row rendering. + * Control Center toast rendering now escapes message content by default and requires an explicit opt-in for the small number of admin success messages that intentionally include formatted HTML. + * Added focused functional and UI regression coverage plus versioned fix documentation for the hardened admin rendering sinks. + * (Ref: `control_center.html`, `control-center.js`, `admin_agents.js`, `test_stored_xss_admin_rendering_fix.py`, `test_control_center_group_members_escaping.py`, `STORED_XSS_ADMIN_RENDERING_FIX.md`) + +* **Web Search Data Egress Hardening** + * Fixed the Bing-grounding web-search path so external web search now sends only the user's current message instead of a query derived from prior conversation context. + * Updated the admin consent copy and user notice text to match the implemented behavior and warn that sensitive content pasted into the current message may still be sent when web search is used. + * Reduced outbound web-search invocation metadata and added focused functional and UI regression coverage for the boundary and disclosure text changes. + * (Ref: `route_backend_chats.py`, `functions_settings.py`, `route_frontend_admin_settings.py`, `admin_settings.html`, `chats.html`, `test_web_search_current_message_only.py`, `test_web_search_notice_copy.py`) + +* **Authorization Boundary Hardening Across Search, Groups, Approvals, and History Fallback** + * Hardened several authenticated workflows that previously trusted caller-supplied identifiers or stale stored scope values, so active group selection, group-scoped prompt access, approval actions, and history-grounded follow-up reuse now revalidate the current user's authorization before proceeding. + * Azure AI Search filter construction now escapes OData literals for document, user, group, shared, and public workspace identifiers, and the Control Center public workspace view now renders untrusted workspace metadata as inert text instead of raw HTML. + * Added focused functional and UI regression coverage for the authorization and escaping paths, plus versioned fix documentation for the full hardening pass. + * (Ref: `functions_search.py`, `functions_group.py`, `route_backend_users.py`, `route_backend_group_prompts.py`, `route_backend_control_center.py`, `route_backend_chats.py`, `control-center.js`, `test_security_authorization_hardening.py`, `test_control_center_public_workspace_escaping.py`) + +* **SQL ODBC Driver 18 Container Support** + * Fixed SQL Server and Azure SQL actions in container deployments by installing Microsoft ODBC Driver 18, copying native driver registration and unixODBC libraries into the distroless runtime, and retrying saved Driver 17 connection strings with Driver 18 only when the failure is a missing-driver error. + * (Ref: `Dockerfile`, `sql_odbc_utils.py`, `sql_schema_plugin.py`, `sql_query_plugin.py`, `route_backend_plugins.py`, `test_sql_odbc_driver_18_support.py`) + +* **Entra Application Deployment Stability** + * Hardened Entra app registration scripts for Microsoft Graph MFA or conditional-access prompts and persisted app registration outputs into the selected AZD environment. + * (Ref: `Initialize-EntraApplication.ps1`, `test_entra_application_graph_mfa_auth.py`, `test_entra_application_azd_env_persistence.py`) + +* **Public Workspace Manage Script Syntax Fix** + * Fixed the public workspace management page script so pending request handlers and delegated member-search selection initialize without parser errors. + * (Ref: `manage_public_workspace.js`, `test_public_workspace_manage_script_syntax_fix.py`, `test_public_workspace_manage_script_parse.py`) + +* **Chat Document Dropdown Viewport Fit Fix** + * Fixed grounded-search document dropdown placement so long document lists stay inside short and mobile-influenced viewports. + * (Ref: `chat-documents.js`, `test_chat_document_dropdown_viewport_fit.py`) + +#### User Interface Enhancements + +* **Workflow Analyze Mode and Conversation Navigation** + * Added a `Run each document separately` switch to personal and group workflow Analyze configuration. + * Workflow history and alert conversation actions now open linked conversations in a new browser tab so users keep their workflow context open. + * Added Word and PowerPoint upload capability toggles to SimpleChat action and agent builders. + * (Ref: `workspace.html`, `group_workspaces.html`, `workspace_workflows.js`, `notifications.js`, `plugin_modal_stepper.js`, `agent_modal_stepper.js`) + +* **Personal Workflow Labeling** + * Renamed the existing workspace workflow surface to `Personal Workflows` so users can distinguish personal workflows from the new group workflow experience. + * Updated personal workflow navigation, modal headings, primary actions, and documentation to use the new wording without changing existing personal workflow IDs or API contracts. + * (Ref: `workspace.html`, `workspace_workflows.js`, `PERSONAL_WORKFLOWS.md`) + +* **Custom Workspace Hero Color Swatches** + * Added a custom color swatch to group and public workspace manage pages so workspace owners can choose any valid hero color in addition to the preset palette. + * Saved custom colors now reselect the custom swatch and update the live hero preview before saving. + * (Ref: `manage_group.html`, `manage_public_workspace.html`, `manage_group.js`, `manage_public_workspace.js`, `GROUP_PUBLIC_WORKSPACE_CUSTOM_HERO_COLORS.md`) + +* **Selectable Conversation-Linked Workspace Document Deletion** + * Conversation delete now lists workspace documents created from chat uploads and lets users select one, many, or all documents to delete with the conversation. + * Leaving all documents unchecked keeps them in the personal workspace so they follow the normal document retention policy. + * Bulk conversation delete no longer removes linked workspace documents automatically. + * (Ref: conversation delete modal, `chat-conversations.js`, `route_backend_conversations.py`, `functions_documents.py`) + +* **Document Intelligence Extraction Terminology** + * Renamed user-facing PDF/image extraction choices from Read/Layout to Standard/Enhanced while preserving the underlying `read` and `layout` settings and API values. + * Added hover text for extraction, citation, and File Sync badges so workspace users can understand Standard, Enhanced, synced, and manually uploaded document states without extra visual clutter. + * (Ref: Admin Settings Search & Extract, personal/group/public workspace document details) + +* **Extraction Badge Placement** + * Removed Read/Layout extraction badges from top-level document rows and cards while preserving them in expanded document details and metadata views. + * (Ref: personal, group, and public workspace document views) + +* **Cosmos Throughput Table Clarity** + * Simplified the Admin Settings Cosmos throughput container table by removing the redundant Database column and replacing the Configure text action with a compact gear button. + * Added tooltips that distinguish RU Utilization from Request Units, plus a Setup Guide modal with a Run Test action that uses the same status checks as Refresh. + * Preserved unavailable container request-unit metrics as unavailable instead of rendering a misleading zero when Azure Monitor does not return a container metric row. + * (Ref: `admin_settings.html`, `admin_settings.js`, Cosmos throughput container metrics, `COSMOS_THROUGHPUT_TABLE_CLARITY_FIX.md`) + +* **Container-Targeted Cosmos Throughput Policies** + * Added a Containers modal to the Admin Settings Scale tab so admins can review every Cosmos container and configure per-container automation settings. + * Each dedicated-throughput container can now have independent min/max RU guardrails, scale-up/down thresholds, RU step sizes, cooldown intervals, and manual scale actions. + * The Cosmos throughput status endpoint now falls back to container-targeted management when database-level throughput settings are absent instead of failing the card with a 404. + * (Ref: `functions_cosmos_throughput.py`, Admin Settings Scale tab, `COSMOS_CONTAINER_THROUGHPUT_FALLBACK_FIX.md`) + +* **File Sync Source Configuration Flow** + * Reworked the File Sync source modal around a combined selection, subfolders, and filters section, including selected path summaries and a browse modal for supported providers. + * OneDrive source configuration now presents a global connector identity notice instead of source-local credential fields. + * (Ref: `workspace-file-sync.js`, File Sync source workflow, selected paths) + +* **Control Center Group Token Totals** + * Added all-time group token usage totals to the Control Center Group Management table so admins can compare group usage alongside members, status, and document metrics. + * Included the same token total in the group management modal and CSV export for consistent reporting. + * (Ref: Control Center group management, group token usage aggregation, `route_backend_control_center.py`, `control-center.js`, `control_center.html`) + +* **Workspace Identity Modal Workflow** + * Simplified workspace and global identity management around real consumers: File Sync, Actions, and Model Endpoints. + * Replaced the inline identity form with Add, View, and Edit modals that group identity details, used-for selection, and authentication. + * Removed the workspace identity page heading and refresh button so the tab starts with a left-aligned Add Identity action and a focused identity table. + * (Ref: workspace identities, identity modal workflow, `workspace-identities.js`, `functions_workspace_identities.py`) + +* **Deep Research Allowed-User Management Modal** + * Replaced inline Deep Research user policy controls with a compact **Manage Users** modal that supports directory search, manual user additions, filtering, removal, CSV upload, and example CSV download. + * Removed blocked-user policy controls from Deep Research and switched runtime behavior to allow-only user access; legacy blocked-user settings are ignored and cleared on admin save. + * Deep Research now applies max/enabled defaults when newly enabled while keeping the master feature toggle off by default. + * (Ref: Deep Research access policy, allowed users modal, `admin_settings.html`, `admin_settings.js`, `functions_source_review.py`, `route_frontend_admin_settings.py`) + +* **Assistant Follow-Up Prompt Actions** + * Chat responses that include visible next-step options can now render those options as prompt buttons under the assistant message. + * Clicking a prompt action stages the text in the chat input and starts a cancelable send countdown, making suggested next steps easier to continue while keeping the user in control. + * (Ref: chat follow-up actions, `chat-messages.js`, `chats.css`, `test_chat_follow_up_prompt_actions.py`) + +* **Workspace Document Cards and Folder-Card Views** + * Added public workspace document cards and aligned public workspace view controls with personal and group workspaces: List, Cards, Folders, and Folders + Cards. + * Folder-card views now let users browse folders first and then review matching documents as cards, while card clicks open the document action menu for quick Chat, Edit, Select, and management actions. + * Improved multi-select controls and visible-only select-all behavior across personal, group, and public list, card, folder, and folder-card views. + * (Ref: workspace document cards, public workspace views, `workspace-documents.js`, `workspace-tags.js`, `public_workspace.js`, `workspace-responsive.css`, `workspace.html`, `group_workspaces.html`, `public_workspaces.html`, `test_public_workspace_document_cards_views.py`) + +* **Control Center Management Pagination** + * Added consistent page-size selectors to User Management, Group Management, and Public Workspace Management in Control Center, with 10, 25, 50, 100, and 250 item options. + * Group and public workspace management now use server-driven pagination instead of loading a fixed first page, so admins can navigate larger result sets with accurate filtered totals. + * Added regression coverage and fix documentation for the shared management pagination behavior. + * (Ref: `route_backend_control_center.py`, `control_center.html`, `control-center.js`, `test_control_center_management_pagination.py`, `CONTROL_CENTER_MANAGEMENT_PAGINATION_FIX.md`) + +* **Workspace Branding Heroes and Shortcuts** + * Added logo upload support for group and public workspace manage pages so owners can brand those spaces with a persistent hero image in addition to the hero color. + * Group and public workspace pages now show the active workspace hero card with the selected color, owner metadata, optional logo, and a direct manage button for the selected workspace. + * Added focused functional and UI regression coverage for the branding metadata, hero rendering, and manage-page flows. + * (Ref: `functions_workspace_branding.py`, `group_workspaces.html`, `public_workspaces.html`, `test_workspace_branding_hero_and_logo.py`, `test_workspace_active_hero_shortcuts.py`, `test_manage_group_page_branding.py`, `test_manage_public_workspace_page_load.py`) + +* **GitHub Pages Documentation Redesign** + * Redesigned the GitHub Pages documentation shell with fixed top navigation, curated sidebar sections, responsive mobile drawer, documentation search, and a right-side page rail. + * (Ref: `docs/_layouts/default.html`, `docs/_includes/sidebar_nav.html`, `docs/assets/css/main.scss`, `docs/assets/js/main.js`, `docs/index.md`, `ui_tests/test_docs_showcase_pages.py`) + +* **Chat and Sidebar Icon-Only Controls** + * Refined the chat conversation info button and compact sidebar toggle so they render as quiet icon-only controls while preserving accessible focus states. + * (Ref: `chats.html`, `_sidebar_nav.html`, `_sidebar_short_nav.html`, `sidebar.css`, `test_chat_sidebar_toggle_controls.py`) diff --git a/docs/explanation/release-notes/v0.260.md b/docs/explanation/release-notes/v0.260.md new file mode 100644 index 000000000..36bd30157 --- /dev/null +++ b/docs/explanation/release-notes/v0.260.md @@ -0,0 +1,332 @@ +--- +title: "Release notes 0.260 series" +description: "SimpleChat release notes for 0.260.009 – 0.260.001." +section: "Reference" +layout: page +--- + + + +# Release notes 0.260 series + +[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }}) + +### **(v0.260.009)** + +#### New Features + +* **Admin Settings Form Field Contract Is Now Enforced** + * Admin Settings submits one form and the backend reads every value by field name, so the set of `name` attributes is the real contract between the template and the settings backend. Renaming or dropping one silently stops that setting from saving, with no error anywhere. + * A new test pins every field name against a committed baseline, and fails the build if one disappears. Adding settings is unaffected; removing one now requires regenerating the baseline in the same commit, which makes it a visible, reviewed decision. + * The same test rejects duplicate field names, which is what prevents a mirrored control from submitting a value twice. + * (Ref: `test_admin_settings_field_contract.py`, `admin_settings_field_baseline.json`) + +### **(v0.260.008)** + +#### New Features + +* **Settings That Need Another Setting Now Say So** + * Some Admin Settings options only work when a different option is enabled, and the two often live in different tabs. That was previously explained only in prose, in a tooltip, or in a warning after saving, so you could switch something on and have nothing happen with no visible reason. + * Affected cards now show an inline notice naming what they need, with a switch to enable the prerequisite without leaving the tab and a link straight to its full configuration. + * **File Sync** announces its **Redis Cache** requirement live, and keeps its save-your-intent behaviour: settings can still be saved and activate once Redis is ready. + * The **FeedbackAdmin** role control is disabled until **User Feedback** is enabled, since the role only governs access to the User Feedback report. The unrelated SafetyViolationAdmin control in the same card stays usable. + * These notices are guidance only. The server still validates every prerequisite. + * (Ref: `admin_settings_dependencies.js`, `data-requires`, File Sync, Permissions) + +#### Bug Fixes + +* **Cross-Tab Links In Admin Settings Now Point At The Right Place** + * Links that send you from one Admin Settings tab to a related setting used to name a tab button directly, so they broke silently whenever a tab was renamed or reorganised: no tab opened, and the address bar was left pointing at nothing. + * Two were already wrong. The **Video File Support** and **Audio File Support** references in Citations sent you to the **Workspaces** tab, but those settings live under **Search and Extract**. Both now open the correct card. + * All twelve cross-tab links now name the card they want, and the owning tab is worked out when you click. The destination card is briefly highlighted so it is obvious where you landed. + * (Ref: `admin_card_links.js`, `data-admin-link`, `openAdminCard`, `test_admin_card_links.py`) + +* **User Agreement Preview Sanitized At The Sink** + * The User Agreement preview rendered Markdown through a guarded reassignment, which reads as unsanitized to static analysis and matched the pattern already corrected for the Home Page Text preview. + * Now sanitized inline with `DOMPurify.sanitize(...)` at the point of rendering. `marked` and DOMPurify are both loaded globally, so the availability guards were redundant. + * (Ref: User Agreement, `admin_settings.html` preview handler, DOMPurify) + +{% raw %} +### **(v0.260.007)** + +#### User Interface Enhancements + +* **Latest Features No Longer Opens Every Time You Visit Admin Settings** + * Latest Features was pinned first in both the top tabs and the admin sidebar, and its pane was hard-coded as the default active tab, so a curated release-notes page behaved like the Admin Settings landing page. + * It now sits last in both navigations, after **Send Feedback**, and **General** is the landing tab instead. + * The Latest Features content is unchanged, including its **New** badge and the hide/unhide option. + * (Ref: `admin_settings.html` top-tab strip, `_sidebar_nav.html`, `admin_sidebar_nav.js` default tab) + +* **Global Identities Is No Longer An Unlabelled Widget** + * The Global Identities tab rendered a bare control with no heading or description, unlike every other Admin Settings tab. + * It now has a heading and explains that identities are deployment-wide and that secrets are stored in Key Vault when Key Vault storage is configured. + * (Ref: Global Identities, `workspace-identities-section`, `functions_workspace_identities.py`) + +* **File Sync Is Now Reachable From The Sidebar** + * File Sync is one of the larger settings surfaces but was the only tab with no sidebar submenu, so its sub-areas could not be jumped to or found with sidebar search. + * **Visible Source Types**, **Personal Workspace Sync**, **Group Workspace Sync**, and **Public Workspace Sync** are now sidebar destinations. + * (Ref: File Sync, `_sidebar_nav.html`, `file-sync-submenu`) + +#### Bug Fixes + +* **Classification Banner Preview Now Updates As You Type** + * The live preview in Admin Settings never updated, because its script sat between template blocks where Jinja discards it, so the code was never rendered to the page. + * The preview now responds to banner text, background colour, and text colour changes. + * (Ref: Classification Banner, `admin_settings.html` `{% block scripts %}`) + +* **Admin Sidebar Section Map Cleaned Up** + * The sidebar's `sectionMap` had grown to 72 entries, but 66 of them mapped a key to itself, which the existing fallback already handled, and one pointed at an element that no longer exists. + * Reduced to the 6 entries that are genuine aliases. A new test now fails if a redundant, dangling, or unreferenced entry is reintroduced. + * (Ref: `admin_sidebar_nav.js`, `scrollToSection`, `test_admin_settings_sidebar_card_parity.py`) + +* **Home Page Text Preview No Longer Reinterprets Editor Text As HTML** + * The Home Page Text preview in Admin Settings assigned the raw editor contents to `innerHTML` when the Markdown editor had not initialized, so text typed into the editor was reinterpreted as HTML. CodeQL flagged this as `js/xss-through-dom` (high severity). + * The raw fallback now uses `textContent`, which is what the code intended by "just show raw text", and the Markdown path is sanitized inline with `DOMPurify.sanitize(...)` at the sink. DOMPurify is loaded globally from the local vendored bundle, so no external asset is introduced. + * (Ref: Home Page Text, `admin_settings.html` `showPreview`, DOMPurify) + +#### Breaking Changes + +* **Admin Settings Template Split Into Per-Tab Partials** + * `admin_settings.html` had grown to 13,526 lines in a single 1 MB file. Each tab pane now lives in `templates/admin/_panes/` and is included by the parent, which keeps the global form, the modals, and the script blocks. + * No settings behaviour changes: every form field name and all 110 configuration card ids are byte-identical, so the submitted payload and the settings backend are untouched. + * **Migration**: code or tests that read `templates/admin_settings.html` directly now see only the parent shell. Use `test_support.templates.read_admin_settings_template()` in functional tests, or `compose_if_admin_settings()` inside a shared file-reading helper. A new contract test fails if a test asserts on a partial-backed card or field without composing the template first. + * (Ref: `templates/admin/_panes/`, `functional_tests/test_support/templates.py`, `test_admin_settings_template_composition.py`) +{% endraw %} + +### **(v0.260.006)** + +#### New Features + +* **Shared Conversation File Approvals** + * Files generated by a participant in a shared conversation are now created immediately and held in a **pending approval** state instead of being refused, because they are saved into the conversation owner's storage. + * The conversation owner approves personal shared conversations; any group **Owner**, **Admin**, or **Document Manager** approves group shared conversations. Requesters can never approve their own file. + * Approvers get an inline **Approve / Deny** card on the pending file plus a notification. Approving releases the file, denying deletes the stored file and records who declined it. + * A staged file is not downloadable by anyone, including the requester, until it is released. + * Only downloadable deliverables are gated (CSV, XLSX, DOCX, PDF, JSON, XML). Generated images and charts are never gated. + * Unapproved files are automatically declined and deleted after 3 days, matching the existing Control Center approval window. + * New Admin Settings toggle **Require approval for participant-generated files**, enabled by default. + * (Ref: `functions_generated_file_approvals.py`, `chat-file-approvals.js`, `require_shared_conversation_file_approval`, `/api/collaboration/file-approvals`) + +#### Bug Fixes + +* **Actions Using the Application Identity Are Now Restricted to Azure Endpoints** + * Actions that authenticate with the application's own managed identity can no longer be pointed at an arbitrary endpoint. Blob Storage, Queue Storage, Cosmos, Databricks, and Log Analytics actions now accept only canonical Azure service hostnames for the public, US Government, China, and Germany clouds. + * Previously a caller holding only the normal **User** role could save a personal action with an attacker-controlled endpoint and application managed-identity authentication, causing the application to send a token minted for its own workload identity to that destination. + * Endpoints are validated when the action is saved and again immediately before the client is built, so actions stored before this release stop working rather than continuing to send credentials. + * Log Analytics custom clouds can no longer choose the Microsoft Entra token authority or the OAuth resource used for delegated tokens. + * Existing actions using standard Azure hostnames are unaffected. Custom domains, development storage, Azure Stack, and direct private-link hostnames are intentionally rejected, matching the Azure Blob File Sync hardening in v0.250.068. + * (Ref: `functions_azure_endpoint_validation.py`, `plugin_health_checker.py`, `blob_storage_plugin.py`, `queue_storage_plugin.py`, `cosmos_query_plugin.py`, `databricks_plugin.py`, `log_analytics_plugin.py`, [Action App-Identity Endpoint Hardening Fix](https://github.com/microsoft/simplechat/blob/main/docs/explanation/fixes/ACTION_APP_IDENTITY_ENDPOINT_HARDENING_FIX.md)) + +* **Action Authentication Types Are Now Enforced on the Server** + * Each action type's supported authentication methods, declared in its schema definition file, are now enforced when an action is saved or tested. Previously the list was only used to populate the action modal and was never checked by the backend. + * This prevents an action type from being configured with an authentication method it was never designed to support, such as requesting application-identity authentication for an OpenAPI or Microsoft Graph action. + * The auth-types API now resolves through the same helper the save paths use, so the modal and the backend cannot drift apart. + * (Ref: `json_schema_validation.py`, `get_allowed_auth_types_for_plugin_type`, `validate_plugin_auth_type_allowed`, `route_backend_plugins.py`) + +#### User Interface Enhancements + +* **Blob Storage Actions Can Now Use Managed Identity or an Account Key** + * The Blob Storage action modal gained an authentication selector offering **Connection String**, **Managed Identity**, and **Account Key**, along with blob service endpoint and account key fields. + * Previously the modal only collected a connection string even though the backend accepted other methods, so managed identity was not reachable through the UI. + * The endpoint field is validated against the Azure Blob hostname allowlist before the action is saved. + * (Ref: `_plugin_modal.html`, `plugin_modal_stepper.js`, `blob_storage.definition.json`) +* **Shared Conversations No Longer Fail With "Stream interrupted: Forbidden"** + * Fixed invited participants being unable to invoke the AI at all in a shared conversation. Any explicit AI request returned `Forbidden` with no content. + * Root cause was the hidden source conversation behind every shared conversation being owned by its creator, so participants failed a plain ownership comparison in the chat streaming route even though they are legitimate members. + * Because shared conversations only call the AI on an explicit mention, this surfaced the first time a participant asked the assistant for something, which made it look file-specific. + * Participants can also now download generated files from a shared conversation, which was blocked by the same comparison. + * Also fixed background CSV exports queued by a participant becoming unreadable for the owner, because publication checks looked up the export run under the wrong user partition. + * (Ref: `build_conversation_participation_context`, `route_backend_chats.py`, `route_enhanced_citations.py`, `functions_simplechat_operations.py`) + +#### User Interface Enhancements + +* **Clearer Group Workspace Save Errors** + * Attempting to save a generated document into a group workspace without document rights now names the roles that can complete it and suggests requesting the content as a downloadable file instead of failing with a bare permission error. + * (Ref: `_resolve_group_upload_target_for_current_user`) + +### **(v0.260.005)** + +#### User Interface Enhancements + +* **Tab Now Completes an @ Mention in Shared Conversations** + * In multi-user conversations, pressing **Tab** while the `@` suggestion menu is open now accepts the highlighted participant, agent, model, or invite suggestion, exactly like **Enter** already did. + * Previously **Tab** moved focus out of the message box and left the half-typed `@par` text behind, which broke the autocomplete habit most people bring from other editors and chat clients. + * **Shift+Tab** is deliberately unchanged and still moves focus backwards, and **Tab** still moves focus normally when the menu is showing "No matching participants...". + * The chat mention menu now matches the agent instruction mention menu, which already accepted **Tab**. + * (Ref: `chat-collaboration.js`, `handleComposerKeydown`, `selectActiveMentionSuggestion`, Fixes #1299) + +* **Mention Menu Is Now Announced Correctly By Screen Readers** + * Each `@` suggestion is now exposed as a proper listbox option with `aria-selected`, and the message box references the highlighted suggestion through `aria-activedescendant` paired with `aria-controls` so assistive technology can resolve it. + * The highlighted suggestion is also scrolled into view while arrowing through a long list, so keyboard navigation no longer highlights an off-screen entry. + * (Ref: `chat-collaboration.js`, `renderMentionMenu`, `updateMentionMenuActiveItem`, `applyMentionComboboxState`, `chats.html`) + +### **(v0.260.004)** + +#### Bug Fixes + +* **New Chat Now Clears The Conversation Documents Side Pane** + * Fixed the conversation side drawer keeping the previous conversation's documents after clicking **New chat**. The stale list, the header documents toggle, and its count badge all stayed visible, and the drawer would not close. + * Root cause was the New chat reset signal carrying a null conversation id while `window.currentConversationId` still pointed at the conversation being left, so the drawer fell back to the old conversation and re-fetched its documents instead of clearing. The **Contents** pane was unaffected because it resets from a separate chatbox observer. + * The **Documents** pane now empties out and the drawer closes, matching **Contents** behavior. Switching between existing conversations is unchanged, and one redundant conversation-metadata request per New chat click is eliminated. + * (Ref: `chat-conversation-contents.js`, `refreshConversationDocuments`, `chat:conversation-context-changed`, `updateDrawerTriggers`, Fixes #1298) + +### **(v0.260.003)** + +#### Bug Fixes + +* **Data Management Timeline Steps Now Show Their Own Status** + * Fixed completed job steps showing a `running` badge on the Data Management job timeline. Events such as "Cosmos DB export step completed" and "Migration reconciliation completed" now read `completed`. + * Root cause was `_set_job_progress` stamping the **job** status onto every step event it recorded, so a finished step inherited `running` because the job itself was still running. + * Step status is now decoupled from job status: steps that start report `running`, steps that finish report `completed`, and the job continues running until it genuinely finishes. + * Applies to backup, restore, and migration timelines. + * (Ref: `functions_data_management.py`, `_set_job_progress`, `_complete_job_step`, `_record_data_management_job_event`) + +#### User Interface Enhancements + +* **Finished Jobs No Longer Look Stuck** + * Completed backup jobs no longer display **Current container: Waiting**, which made a finished job look like it was still churning. + * Migration jobs no longer display a **Liveness: Running** row after reaching a terminal status. + * Live-only telemetry is now hidden once a job is `completed`, `completed_with_warnings`, `failed`, or `canceled`. + * (Ref: `admin_data_management.js`, `getBackupLiveMetrics`, `getMigrationLiveMetrics`, `isTerminalJobStatus`) + +### **(v0.260.002)** + +#### Bug Fixes + +* **Backup Inventory No Longer Fails To Load** + * Fixed the Backup Inventory panel in Admin Settings → Data Management always returning `503` and showing `0` for available, full, and partial backups. + * The global summary used a Cosmos `GROUP BY` with a non-VALUE aggregate (`COUNT(1) AS count`), a combination the `azure-cosmos` Python client does not support. Cosmos rejected the query during plan negotiation with `BadRequest ... GroupBy NonValueAggregate`. + * Counts are now computed with bounded, fully supported `SELECT VALUE COUNT(1)` queries, so the panel renders real numbers without loading backup history into memory. + * This was not a throttling or indexing problem; the composite index was already aligned. Backup Inventory had been broken since the summary shipped. + * (Ref: `functions_data_management.py`, `_get_data_management_backup_global_summary`, `_count_data_management_backups`, `/api/admin/data-management/backups`) + +#### User Interface Enhancements + +* **Run Retention Cleanup Now Explains Itself** + * Added a hover tooltip and an `(i)` toggle that expands inline guidance next to the **Run Retention Cleanup** button. + * Documents that cleanup permanently deletes backups past the retention period along with their artifacts, skips jobs that are still running, honors **Keep latest full backup**, and deletes at most 25 backups per run. + * Clarifies that "found no expired backups to delete" means every backup is still inside the retention window, which is expected rather than a failure. + * (Ref: `admin_settings.html`, backup retention cleanup, Data Management) + +### **(v0.260.001)** + +v0.260.001 consolidates all work released after v0.250.001 into one major release note, spanning 117 incremental patch builds. This rollup highlights the major feature, UI, reliability, security, and operations themes while preserving the full per-build history in the Detailed Change Log at the end of this section. + +#### Breaking Changes + +* **Workflow Alert Configuration Model** + * The legacy single `alert_priority` workflow field is superseded by `alert_mode`, `alert_rules`, and `alert_evaluation` for rules-based workflow notifications. + * Existing workflows are auto-migrated on read into equivalent failed-run and completed-run notification rules. + * **Migration**: Review upgraded workflow alert rules and prune any always-notify completed-run rule that is no longer desired. +* **New Yamcs Client Dependency** + * The Yamcs Mission Control action adds `yamcs-client==2.1.0`, the repository's first LGPL-3.0 dynamically linked pip dependency. + * SimpleChat can still start without it, but Yamcs actions return an actionable dependency error until installed. + * **Migration**: Run `pip install -r requirements.txt` or rebuild deployment images so the Yamcs client dependency is present where Yamcs actions are used. +* **Internal Route Name Hardening** + * Blueprint security hardening changed internal route names and required broad route policy/test updates. + * Shared-conversation streaming regressions from the rename sequence were fixed in the consolidated patch history. + * **Migration**: Update any custom integrations that call SimpleChat by internal endpoint name rather than public route URL. +* **Conversation Cache Fallback Behavior** + * Volatile chat bootstrap and conversation cache payloads no longer fall back to the Cosmos `settings` container when Redis is unavailable. + * Deployments without Redis keep full functionality, but bypass these cache benefits. + * **Migration**: Configure Redis for deployments that depend on chat bootstrap or conversation cache acceleration. + +#### Upgrade Notes + +* **Enhanced Extraction Settings Migration** + * Deployments already configured for Enhanced or Auto extraction are automatically migrated on first settings read to preserve their existing extraction mode. + * No manual change is required unless admins want to revise extraction defaults after upgrade. +* **Embedded Image Chunk Placement** + * Word and PowerPoint figures are now merged into chunks with surrounding text instead of appended as extra chunks. + * Existing documents keep their current chunk layout until re-extracted. + * Use Change Extraction or re-upload documents to benefit from the new placement behavior. +* **Workflow Alert Review** + * Rules-based workflow notifications can express run status, text/regex matches, File Sync results, AI-judged outcomes, and agent signals. + * Review workflow owner expectations after upgrade because migrated rules intentionally preserve prior notification behavior. +* **Redis-Backed Cache Operations** + * DAI document/tag caches and conversation list/feed caches now include Redis-backed invalidation and metrics behavior. + * Monitor Redis availability to keep cache acceleration active; safety-sensitive cache invalidation fails closed when state is unknown. +* **Detailed Patch Traceability** + * The original v0.250.003 through v0.250.229 entries are preserved verbatim below with demoted headings for audit and support lookup. + +#### New Features + +* **Enhanced Document Extraction and Analysis** + * Azure AI Content Understanding supports AI-generated figure descriptions for PDFs/images, with Auto mode figure detection. + * Embedded Office images, including EMF/WMF diagrams and legacy DOC/PPT media, are rasterized, analyzed, and indexed as citable chunks. + * Optional Document Intelligence formula extraction adds LaTeX equation capture for PDFs when enabled. + * (Ref: Azure AI Content Understanding, Document Intelligence, embedded image extraction, formula extraction) +* **Workflow Multi-Task Automation and Alerts** + * Workflows now support ordered instruction tasks with prior-task context chaining, per-task document actions, retry/failure handling, and configurable task limits. + * Conditional alert rules cover run status, text/regex matches, File Sync summaries, AI-judged results, and agent-raised signals across five severity levels. + * Active workflow runs can be cancelled from workspace rows, run history, or activity surfaces. + * (Ref: workflow task sequencing, workflow alert rules, `raise_workflow_alert`, run cancellation) +* **Expanded Agent and Action Integrations** + * Yamcs and RocksDB action types add mission-control and HTTP/JSON data-service integrations. + * Inbound MCP exposes governed SimpleChat capabilities for conversations, documents, prompts, tags, and workflows. + * Action connection testing now covers OpenAPI, Maps, Blob, Databricks, Log Analytics, MCP, Snowflake, Tableau, RocksDB, Yamcs, SQL, and Cosmos DB. + * (Ref: Yamcs action, RocksDB action, MCP inbound server, action test connection) +* **Governance, Security, and Model Administration** + * Governance policies support explicit block lists for feature and delegated item policies alongside allow rules. + * Key Vault secret expiration reminders track per-action secrets with background sweeps, notifications, and telemetry. + * Model requests can include HMAC-hashed user identity headers, and admins can configure per-model output token ceilings. + * (Ref: governance policies, Key Vault secret inventory, model endpoint identity header, output token limits) +* **Chat Productivity, Grounding, and Notifications** + * Users can opt into response completion sounds, desktop notifications, configurable AI notices, and per-message MP3 export. + * Conversation grounding now exposes model/workspace/document/agent context, used-document panes, assistant-response forks, and a contents drawer. + * User font size preferences, generated JSON/XML export artifacts, and smarter scroll behavior improve long-session usability. + * (Ref: chat notifications, grounding citations, used documents pane, conversation fork, contents drawer, export artifacts) +* **Workspace, Sync, and Data Management Operations** + * Azure Blob Storage File Sync adds SAS, managed identity, service principal auth, virtual-folder browsing, and ETag change detection. + * Admin operations add automatic Control Center statistics refresh, backup cleanup/retention, restore workflows, Cosmos JSON editing, Redis Explorer, feedback/safety lifecycle controls, and file-processing log cleanup. + * Multi-select metadata extraction, configurable Public Workspace naming, and index auto-login improve workspace administration. + * (Ref: File Sync, Control Center, Backup Inventory, Data Management, Redis Explorer, metadata extraction) +* **Caching, Runtime, and Durable Processing Capabilities** + * DAI Redis read-through caches document lists, tag lists, and legacy counts with scope-version invalidation. + * Conversation list/feed caching adds Redis hit/miss metrics for Admin Settings visibility. + * Durable tabular analyze/search preflight parity, FFmpeg audio runtime support, and the model capability catalog broaden platform readiness. + * (Ref: DAI Redis cache, conversation cache metrics, tabular durable preflight, FFmpeg, model capability catalog) + +* **Latest Features Release Tiers for v0.260.001** + * Shifted the end-user Latest Features page and Admin Settings tab into current, previous, and archive release tiers for the v0.260.001 rollout. + * Preserved per-tenant visibility choices across the shift. The new v0.260.001 user-facing cards ship hidden until their placeholder screenshots are replaced, so admins publish each card once its real capture is in place. + * (Ref: Latest Features release groups, support catalog, admin catalog, visibility normalization) +* **Deeper End-User Feature Cards** + * Added 20 v0.260.001 end-user cards with seven concrete How To Try It steps and a three-image gallery each. + * Expanded the Latest Features card helper with the `images=[...]` gallery form for multi-image cards. + * (Ref: `_latest_feature_card`, `_SUPPORT_RELEASE_260_FEATURE_CATALOG`, Latest Features image galleries) +* **Admin Latest Features Archive Tier** + * Brought the Admin Settings Latest Features tab to the same three-tier current, previous, and archive model used by the end-user page. + * Keeps v0.250.001 admin cards and older v0.241.x admin highlights available without crowding the current release tier. + * (Ref: `_ADMIN_LATEST_FEATURE_RELEASE_GROUPS`, `_ADMIN_RELEASE_260_FEATURE_CATALOG`, Admin Settings Latest Features tab) +* **Latest Features PR Workflow Hooks** + * Added a Latest Features authoring prompt, PR template checklist, and CI warning path so feature PRs consider release notes, cards, and screenshots together. + * Helps future releases keep in-app Latest Features content aligned with shipped user and admin changes. + * (Ref: `.github/prompts/update-latest-features.prompt.md`, `.github/PULL_REQUEST_TEMPLATE.md`, `release-notes-check.yml`) + +#### User Interface Enhancements + +* **Agent and Workflow Builder Refresh** + * Agent configuration now follows Actions → Knowledge → Instructions, with selected actions visible in the Instructions step. + * Workflows use a stepped General/Trigger/Tasks/Reliability/Review builder with per-task runner controls and alert-rule editing. + * (Ref: agent modal, workflow builder, workflow runner controls, alert rules editor) +* **Administration and Configuration UX Improvements** + * Workspace sections now use a consistent Documents → Prompts → Identities → Sync → Endpoints → Actions → Agents → Workflows order. + * Governance policy copy/inverse/show-users actions, dedicated Log Analytics configuration, refreshed backup/migrate/restore flows, and reviewed data migration steps reduce admin friction. + * External links can be reordered, custom pages can be opened directly, and non-blocking Bootstrap toasts replace browser alerts across admin, workspace, and profile pages. + * (Ref: workspace section order, governance UI, Log Analytics settings, data migration UI, toast notifications) +* **Chat, Navigation, and Accessibility Enhancements** + * Chat, navigation, and sidebar layouts remain usable at 200% zoom and with large text. + * The Conversation Contents drawer adds safe labels, keyboard focus handling, active-location tracking, and responsive desktop/mobile navigation. + * Long source lists collapse behind a disclosure, document picker rows show file-name context, and Refresh Documents preserves selection with clearer status. + * (Ref: 508 usability, conversation contents drawer, source disclosure, document picker, refresh documents) +* **Data Explorer and Extraction Status UX** + * Redis Explorer uses a fixed-height modal with independent key-list and preview scrolling. + * Cosmos query results open in a scrollable modal so the main editor stays focused on query setup. + * Extraction badges identify the engine that ran and show Content Understanding vs. Document Intelligence fallback reasons. + * (Ref: Redis Explorer, Cosmos editor results modal, extraction badges) + +* **Placeholder Screenshots for Pending Captures** + * Added 76 branded "Screenshot pending" placeholders so every v0.260.001 Latest Features card renders a valid local image while final captures are pending. + * Placeholders can be replaced in place with real screenshots without changing the catalog configuration. + * (Ref: `application/single_app/static/images/features/`, Latest Features image galleries) diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index aa5828690..5bd1ffa5f 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -1,6 +1,63 @@ -For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/). +For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). + +### **(v0.260.021)** + +#### Bug Fixes + +* **Documentation Screenshot Viewer Validates Its Image Source** + * The documentation site's click-to-enlarge screenshot viewer assigned an image URL taken from a data attribute in the page. Because that value flows from page content into a URL, CodeQL flagged it as a potential DOM-based cross-site scripting sink. + * The viewer now resolves the value and requires a same-origin `http` or `https` URL ending in an image extension before using it, so scheme-based payloads such as `javascript:` and `data:` URLs, and any off-site source, are rejected. All documentation media is local, so no legitimate image is affected. + * (Ref: `docs/assets/js/media.js`, `safeMediaUrl`, `ui_tests/test_docs_media_lightbox_source_validation.js`, CodeQL `js/xss-through-dom`) + +### **(v0.260.020)** + +#### New Features + +* **Admin Documentation Rebuilt For The Grouped Settings Layout** + * Admin Settings was reorganized from 18 flat tabs into 14 groups containing 44 tabs and 93 settings sections. The documentation was still written against the old flat layout, so it described tabs that no longer exist and omitted the new ones. + * The admin documentation is now one page per group, with every tab reachable by its own anchor so links to a specific tab keep working. Every retired tab URL redirects to the group that now owns its settings, so existing links and bookmarks continue to resolve. + * (Ref: `docs/admin/`, `application/single_app/admin_settings_nav.py`, `docs/_data/app_surface.yml`) + +* **Collaborating In A Conversation Is Now Documented** + * Added a guide covering shared conversations end to end: sharing a conversation, mentioning a participant with `@` and Tab completion, how shared files are approved before they become available, and what participants can and cannot do. + * The Blob Storage action reference now explains its managed identity and account key options. + * (Ref: `docs/guides/collaborate-in-a-conversation/`, `docs/reference/actions/blob-storage/`, `enable_collaborative_conversations`) + +* **Documentation Site Now Reflects the v0.260.001 Release** + * The documentation site's Latest Release section was a full release behind, still presenting v0.250.001 as current. It now mirrors the same three-tier model the application uses: v0.260.001 as the current release, v0.250.001 as the previous release, and v0.239.001-v0.241.007 in the archive. + * Added 20 feature guides for the v0.260.001 release covering enhanced extraction, embedded Office images, workflow task sequences, the MCP platform, the Yamcs and RocksDB actions, agent instruction references, action test connections, Azure Blob file sync, terms of use, audio file support, completion notifications, the chat AI notice, conversation context grounding, used documents on fork, the conversation contents drawer, font size and zoom, message audio export, public workspace display names, and chat scroll accessibility. + * (Ref: `docs/_data/latest_release_features.yml`, `docs/latest-release/release-260-*`, `application/single_app/support_menu_config.py`) + +* **Placeholder Screenshots Are Now Tracked** + * The v0.260.001 release ships branded "Screenshot pending" placeholder graphics so feature cards render while final captures are pending. Those placeholders are now listed on the documentation media status page with the exact file paths to overwrite, so they are visible work rather than a silent gap. + * (Ref: `docs/_data/media_pending.yml`, `/contributing/media-status/`) + +#### User Interface Enhancements + +* **Admin Settings Pages Show Real Screenshots** + * Fourteen admin settings tab pages were rendering "screenshot needed" placeholders even though real screenshots already existed in the repository. Those pages now display the actual screenshots for the General, AI Models, Search and Extract, Workspaces, File Sync, Workspace Identities, Citation, Safety, Security, Agents, Scale, Control Center, Logging, and Send Feedback tabs. + * The four tabs with no captured screenshot still show a placeholder naming the exact file to create, so genuine gaps stay visible. + * (Ref: `docs/admin/`, `docs/images/admin-settings/`) + +#### Bug Fixes + +* **Release Notes Pages No Longer Break On Quoted Template Syntax** + * Release notes legitimately quote template syntax when describing template work, such as a Jinja `block` tag. The page generator emitted that verbatim, so the site build failed with an unknown tag error. Quoted template syntax is now escaped in generated pages and renders as literal text. + * (Ref: `scripts/build_release_notes_pages.py`) + +* **Release Notes Links To Internal Engineering Notes** + * Some release note entries linked to the internal feature and fix note trees, which are intentionally not published on the documentation site. Those links now point at the repository. + * (Ref: `docs/explanation/release_notes.md`) + +* **Release Notes Index No Longer Exceeds Its Page Budget** + * The release notes page generator inlined a fixed number of recent releases on its index. The consolidated v0.260.001 rollup is large enough on its own that this pushed the index past the maximum page size and failed generation. The index now fills its inline section by size rather than by count, so a single large rollup cannot break it. + * (Ref: `scripts/build_release_notes_pages.py`) + +* **Archived Release Notes Links** + * The archived release notes page linked to the internal feature and fix note trees, which are intentionally not published on the documentation site. Those links now point at the repository instead. + * (Ref: `docs/explanation/archive_release_notes.md`) ### **(v0.260.019)** @@ -221,7 +278,7 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Ver * **Overlapping result pages no longer double the row count.** Agents frequently re-request a range from the same start time rather than paging forward, which produced a 1,000-row file for a window holding roughly 500 distinct records. Rows an earlier page of the same action already returned are dropped, while genuinely repeated records inside a single response are preserved. * **Partial data is now labeled.** When an action reports that it truncated its own results, the file carries a **Partial** badge and a note explaining that it covers only the rows the action returned. Agents are also instructed to request the remainder starting after the last row they already hold, rather than repeating the original range. * **CSV, DOCX, PDF, JSON, and XML now behave identically.** All five formats resolve rows the same way, reach back to earlier turns, decline to publish on a clarification turn, and report truncation. - * (Ref: `functions_generated_file_exports.py`, `functions_tabular_generated_exports.py`, `route_backend_chats.py`, `chat-messages.js`, [Generated Artifact Paging, Truncation, and Guidance Carry-Forward Fix](fixes/GENERATED_ARTIFACT_PAGING_AND_GUIDANCE_FIX.md), Refs #1071) + * (Ref: `functions_generated_file_exports.py`, `functions_tabular_generated_exports.py`, `route_backend_chats.py`, `chat-messages.js`, [Generated Artifact Paging, Truncation, and Guidance Carry-Forward Fix](https://github.com/microsoft/simplechat/blob/main/docs/explanation/fixes/GENERATED_ARTIFACT_PAGING_AND_GUIDANCE_FIX.md), Refs #1071) ### **(v0.260.010)** @@ -348,7 +405,7 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Ver * Endpoints are validated when the action is saved and again immediately before the client is built, so actions stored before this release stop working rather than continuing to send credentials. * Log Analytics custom clouds can no longer choose the Microsoft Entra token authority or the OAuth resource used for delegated tokens. * Existing actions using standard Azure hostnames are unaffected. Custom domains, development storage, Azure Stack, and direct private-link hostnames are intentionally rejected, matching the Azure Blob File Sync hardening in v0.250.068. - * (Ref: `functions_azure_endpoint_validation.py`, `plugin_health_checker.py`, `blob_storage_plugin.py`, `queue_storage_plugin.py`, `cosmos_query_plugin.py`, `databricks_plugin.py`, `log_analytics_plugin.py`, [Action App-Identity Endpoint Hardening Fix](fixes/ACTION_APP_IDENTITY_ENDPOINT_HARDENING_FIX.md)) + * (Ref: `functions_azure_endpoint_validation.py`, `plugin_health_checker.py`, `blob_storage_plugin.py`, `queue_storage_plugin.py`, `cosmos_query_plugin.py`, `databricks_plugin.py`, `log_analytics_plugin.py`, [Action App-Identity Endpoint Hardening Fix](https://github.com/microsoft/simplechat/blob/main/docs/explanation/fixes/ACTION_APP_IDENTITY_ENDPOINT_HARDENING_FIX.md)) * **Action Authentication Types Are Now Enforced on the Server** * Each action type's supported authentication methods, declared in its schema definition file, are now enforced when an action is saved or tested. Previously the list was only used to populate the action modal and was never checked by the backend. @@ -563,6 +620,84 @@ v0.260.001 consolidates all work released after v0.250.001 into one major releas * Added 76 branded "Screenshot pending" placeholders so every v0.260.001 Latest Features card renders a valid local image while final captures are pending. * Placeholders can be replaced in place with real screenshots without changing the catalog configuration. * (Ref: `application/single_app/static/images/features/`, Latest Features image galleries) +### **(v0.250.231)** + +#### Bug Fixes + +* **Missing Release Highlight Screenshots Now Display** + * The Latest Release pages referenced 24 screenshots that were never present in the documentation site, so every one of them rendered as a broken image. + * The images already existed in the application at `application/single_app/static/images/features/`, where the in-app Latest Features gallery reads them. They are now also published with the documentation site, so the release highlight pages show the same screenshots users see in the product. + * (Ref: `docs/images/latest-release/`, `docs/_data/latest_release_features.yml`, Latest Release highlight pages) + +* **Broken Documentation Links Repaired** + * Fixed the remaining broken internal links on the documentation site. Links that pointed at renamed pages now resolve, and links that target files kept in the repository rather than published on the site, such as the Custom Pages developer guide, the Teams app manifest, and a CI workflow, now open on GitHub instead of returning a missing page. + * Removed two references to a ServiceNow multi-action setup guide that was never written. + * The documentation site now has zero broken internal links across 31,649 checked links. + * (Ref: `ui_tests/check_docs_links.js`, ServiceNow guides, Custom Pages guide, upgrade paths guide) + +### **(v0.250.230)** + +#### New Features + +* **Documentation Site Redesign** + * The documentation site was rebuilt for search, navigation, page simplicity, mobile support, and content coverage. + * Search now indexes page content instead of titles only. Previously 84% of the 986 indexed pages were internal engineering notes, 88% of entries had no description, and no page body text was indexed at all, so a search for "agent" returned mostly internal fix notes. The index is now 165 entries with a description on every one and no engineering notes. + * Added a dedicated search results page with section filters and highlighted excerpts, a `Ctrl+K` shortcut, keyboard navigation, and a full-screen mobile search sheet. Search was previously hidden entirely on phones. + * Navigation was rebuilt so the top bar and sidebar expose the same six sections: Start, Guides, Features, Administration, Deploy and operate, and Reference. Coverage went from 27 links to 74, all verified to resolve. + * (Ref: `docs/search-index.json`, `docs/assets/js/search.js`, `docs/_config.yml` navigation, `docs/search.md`) + +* **Screenshot and Video Placeholders for Documentation** + * Documentation pages can now declare a screenshot or video slot. When the asset does not exist yet the page renders a visible card naming the exact file path to create; adding the file at that path replaces the placeholder automatically on the next build with no configuration or code change. + * Videos render as a local poster card that links out to YouTube or Microsoft Stream, so no video files are committed to the repository and no third-party embed scripts are loaded. + * Added a media status page listing every slot and whether it is filled, as a capture worklist for contributors. + * (Ref: `docs/_includes/media.html`, `docs/_data/media.yml`, `docs/contributing/media-status.md`) + +* **Complete Documentation Coverage of the Application** + * Added one page per admin settings tab covering what the tab controls, why it matters, every setting with its default and governing settings key, prerequisites, and the common tasks admins perform there. + * Added task guides for creating actions, agents, agents with actions, multi-task workflows, triggering workflows, file sync connectors, tags, tags in chat, tags on conversations, and exporting conversations, plus further guides derived from the application surface. Each guide explains what the task does and why before the steps. + * Added a chat interface reference covering all 47 chat controls and an action reference covering all 27 actions. + * Added a feature catalog in which every one of the 111 capability toggles is claimed by exactly one capability entry. + * (Ref: `docs/admin/`, `docs/guides/`, `docs/reference/chat-controls.md`, `docs/reference/actions/`, `docs/_data/features.yml`) + +* **Documentation Coverage Enforcement** + * Added a generated inventory of the application surface and functional tests that fail when a new capability toggle, admin settings tab, action plugin, or chat control ships without documentation, so coverage stays complete as changes land. + * (Ref: `scripts/build_docs_inventory.py`, `functional_tests/test_docs_app_surface_coverage.py`, `functional_tests/test_docs_site_quality.py`) + +#### User Interface Enhancements + +* **Documentation Site Works on Phones and Tablets** + * Standardized the responsive breakpoints, which previously mixed `768px` and `767.98px` and left gaps, and exported the desktop breakpoint to JavaScript so it is no longer duplicated by hand. + * Wide tables and long code blocks are now contained in horizontal scroll regions instead of widening the page, images are lazy-loaded with intrinsic sizing, touch targets meet a 44px minimum, and the mobile navigation drawer and search sheet trap and restore focus. + * Verified with browser tests at 360x640, 390x844, 768x1024, 1280x800, and 1920x1080. + * (Ref: `docs/assets/css/main.scss`, `docs/assets/js/sidebar.js`, `ui_tests/test_docs_site_responsive.js`) + +* **Simpler Documentation Pages** + * Landing pages were rewritten from hand-written HTML card markup into plain markdown. The home page previously had 82 blocks of card markup and zero markdown headings, and the features page 119 blocks and zero headings, which meant neither page had a working "On this page" table of contents or heading anchors. + * The FAQ was rebuilt so every question is its own heading with a linkable anchor. + * The decorative page hero, with its gradient banner, pill row, and icon orb, was replaced with a plain documentation header across the 38 pages that used it. + * Split the 452 KB release notes page into per-version-series pages while keeping the existing release notes URL working. + * (Ref: `docs/index.md`, `docs/features.md`, `docs/start/faqs.md`, `scripts/build_release_notes_pages.py`) + +* **Documentation URLs Now Match Their Section** + * Guides previously lived under three different URL spaces that all meant the same thing. Tutorials and how-to guides are consolidated under `/guides/`, orientation pages moved under `/start/`, deployment scenarios under `/deploy/`, and reference pages under `/reference/`. + * **Existing links and bookmarks continue to work.** Every moved page redirects from its old URL, and the URLs the application itself links to were deliberately left unchanged. + * (Ref: documentation navigation, `jekyll-redirect-from`, `ui_tests/check_docs_links.js`) + +#### Bug Fixes + +* **Documentation Site No Longer Overflows Horizontally on Desktop** + * The main content region combined a full-width rule with a sidebar offset, so every desktop viewport scrolled sideways by exactly the sidebar width. This was a long-standing defect on the published site. + * (Ref: `.docs-main-content`, `docs/assets/css/main.scss`) + +* **Documentation Section Labels and Page Titles** + * Path-scoped Jekyll defaults used collection names as their type and therefore never applied, so nearly every page fell back to a generic "Docs" section and search facets were meaningless. Three scenario index pages also had a comment above their front matter, so it was never parsed and they were titled with their own file path and rendered through an empty layout. + * (Ref: `docs/_config.yml` defaults, `docs/explanation/scenarios/`) + +* **Documentation Site Loads No Third-Party Assets** + * Removed jQuery, DataTables, marked, DOMPurify, and split.js, none of which the site used, and vendored Bootstrap, Bootstrap Icons, Prism, Lunr, and the site fonts locally with their licenses. The site now makes zero external requests. + * (Ref: `docs/assets/vendor/`, `docs/_layouts/default.html`, local browser asset policy) + +### **(v0.250.229)** #### Bug Fixes @@ -3760,7 +3895,7 @@ The individual patch builds consolidated into v0.260.001 are preserved below for * **OpenAPI Specifications**: 7 OpenAPI YAML files for ServiceNow Incident Management and Knowledge Base APIs (both bearer token and basic auth versions). * **Agent Instructions**: Behavioral instructions optimized for ServiceNow operations (263 lines). * **Key Features**: Integration user creation, role assignment guidance, token management strategies, troubleshooting guide, and production deployment considerations. - * **Documentation Files**: `SERVICENOW_INTEGRATION.md` (760 lines), `SERVICENOW_OAUTH_SETUP.md` (480+ lines), `servicenow_agent_instructions.txt`, and 7 OpenAPI specs in `docs/how-to/agents/ServiceNow/`. + * **Documentation Files**: `SERVICENOW_INTEGRATION.md` (760 lines), `SERVICENOW_OAUTH_SETUP.md` (480+ lines), `servicenow_agent_instructions.txt`, and 7 OpenAPI specs in `docs/guides/servicenow/`. * (Ref: ServiceNow integration, OAuth 2.0, OpenAPI specifications, enterprise integrations) #### Bug Fixes diff --git a/docs/explanation/scenarios/agents/index.md b/docs/explanation/scenarios/agents/index.md index 815acfd80..3877e5619 100644 --- a/docs/explanation/scenarios/agents/index.md +++ b/docs/explanation/scenarios/agents/index.md @@ -1,9 +1,8 @@ -# explanation/scenarios/agents/index.md --- -layout: libdoc/page -title: Agent Scenarios -order: 131 -category: Scenarios +layout: page +title: "Agent Scenarios" +description: "Worked agent examples covering analysis, investigation, monitoring, and reporting tasks." +section: "Explanation" --- Agents \ No newline at end of file diff --git a/docs/explanation/scenarios/index.md b/docs/explanation/scenarios/index.md index 9308ade4a..b6f139c70 100644 --- a/docs/explanation/scenarios/index.md +++ b/docs/explanation/scenarios/index.md @@ -1,9 +1,8 @@ -# explanation/scenarios/index.md --- -layout: libdoc/page -title: Scenarios -order: 130 -category: Scenarios +layout: page +title: "Scenarios" +description: "Worked examples showing how Simple Chat is used for real agent and workspace scenarios." +section: "Explanation" --- # Scenarios diff --git a/docs/explanation/scenarios/workspaces/index.md b/docs/explanation/scenarios/workspaces/index.md index 31bd5db3c..b3213c2c3 100644 --- a/docs/explanation/scenarios/workspaces/index.md +++ b/docs/explanation/scenarios/workspaces/index.md @@ -1,9 +1,8 @@ -# explanation/scenarios/workspaces/index.md --- -layout: libdoc/page -title: Workspace Scenarios -order: 132 -category: Scenarios +layout: page +title: "Workspace Scenarios" +description: "Public workspace content sets for HR, IT, and service desk demonstrations." +section: "Explanation" --- Workspaces \ No newline at end of file diff --git a/docs/faqs.md b/docs/faqs.md deleted file mode 100644 index ddad64472..000000000 --- a/docs/faqs.md +++ /dev/null @@ -1,432 +0,0 @@ ---- -layout: showcase-page -title: "FAQ" -description: "Answers to the questions that come up most often when teams deploy, secure, and operate Simple Chat in Azure environments." -section: "Support" -accent: slate -eyebrow: "Troubleshooting" -hero_icons: - - bi-question-circle - - bi-shield-lock - - bi-search -hero_pills: - - Networking and firewall issues - - Identity and model setup - - Upload and retrieval troubleshooting -hero_links: - - label: "Getting Started" - url: /setup_instructions/ - style: primary - - label: "Review features" - url: /features/ - style: outline ---- - -Use this page as the fast triage layer: it focuses on the recurring questions that show up after a team has already deployed or started hardening the environment. - -
-
-
-
Quick jump
-

Start with the category that matches the failure mode

-

Each card jumps to the section where the most likely causes and checks are grouped together.

-
- Top issues -
- -
-
-
-
- - Network -
-

Firewall and private endpoint issues

-

Start here when uploads, search, or admin actions break only after the app is placed behind a firewall, WAF, or private network boundary.

-
-
- -
-
-
- - Identity -
-

Authentication and access

-

Use this section when users cannot sign in, role assignments do not behave as expected, or model enumeration fails because of tenant boundaries.

-
-
- -
-
-
- - Admin UI -
-

Branding, support, and docs

-

Use this section when users need to know where branding, home page text, health checks, Swagger, Support, and external links are configured.

-
-
- -
-
-
- - Ingestion -
-

Uploads and retrieval

-

Use this path when files fail during processing, indexes stay empty, or grounded answers are missing expected source material.

-
-
- -
-
-
- - Configuration -
-

Model and endpoint configuration

-

Use this section when GPT, embeddings, DALL-E, or APIM-mode settings are the part of the system that is behaving unexpectedly.

-
-
-
-
- -
- -
-
-
-
Network
-

Firewall and private endpoint questions

-

These issues usually surface after the app is moved behind network controls that only allow a subset of browser or outbound service traffic.

-
- Connectivity -
- -
- - - Q1 - We put Simple Chat behind a firewall or WAF and uploads, search, or admin updates stopped working. - The browser still needs to reach the backend API routes that power the app. - - - API traffic - Open answer - - -
-
-

What to verify

-
    -
  • Simple Chat serves a frontend in the browser and a backend API from the app service, so blocking `/api/*` traffic breaks core features even when the page shell still loads.
  • -
  • Allow browser-originated `GET`, `POST`, `PUT`, `PATCH`, and `DELETE` calls to your app service API routes.
  • -
  • Use the repository OpenAPI specification under `artifacts/open_api/openapi.yaml` to understand which endpoints the frontend depends on.
  • -
-
-
-
- -
- - - Q2 - Can I use Azure OpenAI and related services through private endpoints? - Yes, but the app service must be able to resolve and reach those private addresses. - - - Private networking - Open answer - - -
-
-

What to verify

-
    -
  • Integrate the app service with a VNet that can reach the private endpoints for Azure OpenAI, AI Search, Cosmos DB, Storage, and any other required service.
  • -
  • Make sure private DNS zones or custom DNS resolve those endpoints to the expected private IP addresses.
  • -
  • Validate outbound connectivity from the app service, not just from an admin workstation.
  • -
-
-
-
-
- -
- -
-
-
-
Identity
-

Authentication and access questions

-

When sign-in or model management fails, start by confirming the identity plane before you assume the app itself is broken.

-
- Entra ID -
- -
- - - Q3 - Users are getting authentication errors or cannot log in. - Most login failures come from app registration or assignment drift. - - - Access checks - Open answer - - -
-
-

What to verify

-
    -
  • The redirect URI for `/.auth/login/aad/callback` is configured correctly in the Entra app registration.
  • -
  • App Service Authentication is enabled, points at the correct app registration, and is set to require authentication.
  • -
  • Users or groups are assigned to the enterprise application when assignment is required.
  • -
  • Microsoft Graph permissions such as `User.Read`, `openid`, and `profile` are configured and have admin consent.
  • -
  • `TENANT_ID` and `CLIENT_ID` values in the app settings match the intended tenant and application.
  • -
-
-
-
- -
- - - Q4 - Fetch Models fails when the authentication app registration is in a different tenant than Azure OpenAI. - The data plane can still work even when cross-tenant management-plane model listing is blocked. - - - Cross-tenant - Open answer - - -
-
-

Workaround

-
    -
  1. In Admin Settings under GPT, enable Use APIM instead of direct to Azure OpenAI endpoint.
  2. -
  3. Enter the Azure OpenAI endpoint URL, API version, and deployment name in the APIM fields instead of a true APIM proxy.
  4. -
  5. Save the settings and fetch models again.
  6. -
-

That route shifts model listing into the APIM-mode flow and avoids the cross-tenant management-plane enumeration failure.

-
- Cross-tenant model support guidance screenshot. -
-
-
- -
- -
-
-
-
Admin UI
-

Branding, support, and documentation questions

-

These answers point admins to the General settings that control the parts of Simple Chat users notice first.

-
- User-facing settings -
- -
- - - Q5 - Where do I change the application title, logo, favicon, or home page text? - Branding and landing content are both in Admin Settings under General. - - - Branding - Open answer - - -
-
-

What to do

-
    -
  • Open Admin Settings > General > Branding to change the application title, logo visibility, light and dark logos, home page logo size, and favicon.
  • -
  • Open Admin Settings > General > Home Page Text to edit the landing page Markdown and alignment.
  • -
  • Use Configure Branding, Home Page, and Support Settings for the full checklist.
  • -
-
-
-
- -
- - - Q6 - How do I check whether the system is healthy? - Enable one of the external health check routes before wiring a monitor to it. - - - Health checks - Open answer - - -
-
-

What to verify

-
    -
  • Use Admin Settings > General > Health Check to enable /external/healthcheck or /external/healthcheckz.
  • -
  • Use /external/healthcheck when the monitoring path can reach a protected route.
  • -
  • Use /external/healthcheckz only when the monitor cannot authenticate and the network path is trusted.
  • -
  • Test from the monitoring network path, not only from an admin workstation.
  • -
-
-
-
- -
- - - Q7 - Where are the API docs? - Swagger is available at /swagger when the admin toggle is enabled. - - - Swagger - Open answer - - -
-
-

What to use

-
    -
  • Enable Admin Settings > General > API Documentation > Enable Swagger/OpenAPI Documentation (/swagger).
  • -
  • Open /swagger for the interactive browser.
  • -
  • Use /swagger.json or /swagger.yaml for tooling.
  • -
  • See the API Reference for endpoint details.
  • -
-
-
-
- -
- - - Q8 - How do I show a classification banner, Support menu, or external resource links? - These navigation and guidance features are configured from General settings. - - - Navigation - Open answer - - -
-
-

Where to go

-
    -
  • Classification Banner: set banner text, background color, and text color for the top-of-page sensitivity label.
  • -
  • Support: enable Support, rename the menu, configure Send Feedback, and choose which Latest Features users can see.
  • -
  • External Links: add links to policies, prompt guides, help desks, or other trusted resources.
  • -
  • System Settings: tune maximum file size, conversation history limit, and the default system prompt.
  • -
-
-
-
-
- - - -
-
-
-
Ingestion and retrieval
-

Upload and search questions

-

These problems usually come from permission gaps, failed indexing, or missing model configuration for embeddings and extraction.

-
- Grounding -
- -
- - - Q9 - File uploads are failing. - Start by checking the dependent services the pipeline writes to during ingestion. - - - Uploads - Open answer - - -
-
-

What to verify

-
    -
  • The app service has permission to reach AI Search, Document Intelligence, Storage, Speech, Video Indexer, and any other enabled service in the upload path.
  • -
  • Managed identity role assignments or configured keys are valid and match the services you enabled.
  • -
  • Application Insights and App Service logs show whether the failure happens during extraction, embedding, indexing, or storage.
  • -
  • File Processing Logs in Admin Settings can expose the exact stage that failed.
  • -
-
-
-
- -
- - - Q10 - RAG is not returning expected results or any results. - When search quality drops, verify indexing and embeddings before tuning prompts. - - - Search - Open answer - - -
-
-

What to verify

-
    -
  • Uploaded documents finished processing successfully in the workspace UI.
  • -
  • The relevant Azure AI Search index contains the documents you expect and its count increases after uploads.
  • -
  • The embedding deployment is configured correctly and reachable from the application.
  • -
  • The Search Documents toggle is enabled in the chat UI and the user question is phrased in a way that maps to indexed content.
  • -
-
-
-
-
- -
- -
-
-
-
Configuration
-

Model management questions

-

These checks matter when the application loads but the configured AI capabilities do not behave the way you expect.

-
- Admin settings -
- -
- - - Q11 - How do I update the GPT, embedding, or DALL-E models used by the application? - Model selection is handled through admin configuration rather than code deployment. - - - Model updates - Open answer - - -
-
-

What to do

-
    -
  • Open Admin Settings.
  • -
  • Go to the GPT, Embeddings, or Image Generation section that matches the model you want to change.
  • -
  • Fetch the available deployments from the configured Azure OpenAI endpoint and select the desired deployment name.
  • -
  • Save the settings. A code redeploy is not required when the endpoint stays the same.
  • -
-
-
-
-
diff --git a/docs/features.md b/docs/features.md index b5f66e810..8c2a2066d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,321 +1,84 @@ --- -layout: showcase-page +layout: page title: "Features" -description: "A structured view of the Simple Chat experience, from core workspace flows to optional feature packs and the Azure services behind them." -section: "Overview" -accent: orange -eyebrow: "Capability Map" -hero_icons: - - bi-stars - - bi-grid-3x3-gap - - bi-shield-check -hero_pills: - - Core workspace experience - - Optional admin-enabled packs - - Azure-native services -hero_links: - - label: "See latest release highlights" - url: /latest-release/ - style: primary - - label: "Review admin configuration" - url: /admin_configuration/ - style: outline +description: "A data-driven catalog of SimpleChat capabilities, grouped by operating area and tied to the settings that enable them." +section: "Features" +permalink: /features/ --- -Simple Chat starts with a grounded chat experience, then expands through optional modules for governance, media processing, feedback loops, advanced citations, and operational scale. - -
-
-
-
Core experience
-

What ships with the day-one product surface

-

These are the capabilities most teams use immediately after deployment.

-
- Always relevant -
- -
-
-
-
- - Conversation -
-

Chat with AI

-

Interact with Azure OpenAI deployments through a workspace-aware chat surface built for grounded answers, prompt reuse, and iterative conversations.

-
-
- -
-
-
- - Retrieval -
-

RAG with hybrid search

-

Combine vector and keyword retrieval over uploaded files so chat responses can pull directly from the indexed document set.

-
-
- -
-
-
- - Workspaces -
-

Personal and group document management

-

Upload, version, organize, and reuse files in personal workspaces or share them through group workspaces with role-aware access control.

-
-
- -
-
-
- - Session scope -
-

Ephemeral chat uploads

-

Attach single-conversation documents when you need temporary grounding without committing those files to persistent search indexes.

-
-
- -
-
-
- - Access -
-

Authentication and RBAC

-

Secure the app with Entra ID, managed identities, and app roles such as Admin, User, CreateGroup, SafetyAdmin, and FeedbackAdmin.

-
-
-
-
- -
-
-
-
Optional capability packs
-

Modules you can enable as the rollout matures

-

These features are designed to layer onto the base experience through admin settings and supporting Azure services.

-
- Admin controlled -
- -
-
-
-
- - Governance -
-

Content safety and policy controls

-

Review user prompts before they reach AI models, search, or image generation and give designated reviewers a place to inspect flagged activity.

-
-
- -
-
-
- - Generation -
-

Image generation and live web results

-

Add DALL-E powered image creation and Bing-powered web search when your users need current results or generated visuals inside the same app.

-
-
- -
-
-
- - Media -
-

Video and audio extraction

-

Transcribe media, capture timestamps, and turn recordings into searchable assets with citations that can point back to the right moment.

-
-
- -
-
-
- - Document context -
-

Classification, metadata, and enhanced citations

-

Add labels, AI-generated summaries, author/date inference, and source-linked previews so users can understand where an answer came from.

-
-
- -
-
-
- - Feedback + retention -
-

User feedback and conversation archiving

-

Collect structured sentiment on responses and, when needed, retain conversations in a dedicated archive for audit and compliance workflows.

-
-
- -
-
-
- - Scale + extensibility -
-

Redis cache, SQL agents, and processing logs

-

Scale session handling, inspect ingestion activity, and connect agent workflows to SQL data sources through configurable plugins.

-
-
- -
-
-
- - Actions -
-

Microsoft Graph agent actions

-

Expose delegated Microsoft Graph operations to agents, including profile, calendar, mail, directory, OneDrive, security alert, and configurable send-mail workflows.

-
-
- -
-
-
- - Branding -
-

Workspace branding controls

-

Let group and public workspace owners tune hero colors with preset or custom swatches so users can recognize shared workspace context at a glance.

-
-
-
-
- -
-
-
-
Platform services
-

Azure building blocks behind the product

-

Simple Chat is opinionated about which Azure services handle which part of the workload.

-
- Service map -
- -
-

Primary service roles

-
    -
  • Azure Cosmos DB stores conversations, metadata, settings, user and group records, and optional archive or feedback data.
  • -
  • Azure AI Search powers hybrid retrieval across indexed personal and group content.
  • -
  • Azure AI Document Intelligence extracts text and layout from uploaded files during ingestion.
  • -
  • Azure OpenAI handles chat completions, embeddings, optional image generation, and AI-driven metadata extraction.
  • -
  • Azure Storage supports enhanced citation content and related document assets.
  • -
  • Azure Cache for Redis is optional and becomes important when you need horizontal scale with shared sessions.
  • -
-
-
- -
-
-
-
Supported inputs
-

File types the platform is built to understand

-

Exact behavior depends on which optional extraction features you enable, but these are the main content categories the app is designed around.

-
- Coverage -
- -
-
-
-
- - Text -
-

Structured and plain text

-

`txt`, `md`, `html`, `json`, `xml`, `yaml`, `yml`, and `log` files fit the basic ingestion pipeline well.

-
-
- -
-
-
- - Documents -
-

Office and tabular documents

-

`pdf`, `doc`, `docm`, `docx`, `pptx`, `xlsx`, `xlsm`, `xls`, and `csv` cover the common enterprise file set.

-
-
- -
-
-
- - Images -
-

Image ingestion with OCR support

-

`jpg`, `jpeg`, `png`, `bmp`, `tiff`, `tif`, and `heif` can be processed through document analysis and OCR-aware workflows.

-
-
- -
-
-
- - Media -
-

Audio and video formats

-

`mp4`, `mov`, `avi`, `wmv`, `mkv`, `webm`, `mp3`, `wav`, `ogg`, `aac`, `flac`, and `m4a` become searchable when the related media services are enabled.

-
-
-
-
- -
-
-
-
Visual reference
-

Architecture and admin surface

-

The deployment architecture and admin settings page are usually the fastest way to explain how the feature set is organized.

-
- Screens -
- -
-
-
- -

Architecture overview

-

Use this diagram when you need to explain how App Service, Azure OpenAI, AI Search, Cosmos DB, and storage fit together.

-
-
- -
-
- -

Admin configuration surface

-

Most optional features are exposed through the admin settings experience, which keeps rollout decisions in one operational surface.

-
-
-
-
+SimpleChat capabilities are catalogued from the application surface inventory so every shipped feature toggle is represented once and only once. Use this page to find the capability, understand what it does, and jump to the admin setting that controls it. + +This catalog covers **38 capabilities** backed by **111 capability toggles**. + +## Chat and retrieval + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Chat file uploads](/features/chat-file-uploads/) | Lets users attach files directly to a chat so the conversation can use uploaded content as context. | End users | Enabled | [Workspaces](/admin/workspaces/) | +| [Collaborative conversations](/features/collaborative-conversations/) | Allows shared conversation participation so multiple users can work in the same chat context. | End users | Enabled | [Workspaces](/admin/workspaces/) | +| [Conversation productivity controls](/features/conversation-productivity/) | Controls chat navigation, appearance, drawers, notifications, and audio cues around conversation work. | End users | Mixed | [General](/admin/appearance/) | +| [Deep research and source review](/features/deep-research/) | Adds bounded source-page review and deeper web evidence inspection before web-grounded answers are used. | End users | Mixed | [Search and Extract](/admin/knowledge/) | +| [Enhanced citations](/features/enhanced-citations/) | Adds richer source previews and storage-backed citation rendering for documents used in answers. | End users | Disabled | [Citations](/admin/chat/) | +| [Grounded document search](/features/grounded-document-search/) | Controls Azure AI Search-backed retrieval, cached search results, and mixed-source evidence reuse in chat. | End users | Mixed | [Search and Extract](/admin/knowledge/) | +| [Source history summaries](/features/source-history-summaries/) | Summarizes older conversation context so search and long-running chats can keep useful history. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [URL access](/features/url-access/) | Lets chat and workflows fetch approved pasted URLs under admin-controlled limits and domain policy. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [Web search](/features/web-search/) | Adds Bing-backed web search through a configured Azure AI Foundry agent for current external information. | End users | Disabled | [Search and Extract](/admin/knowledge/) | + +## Documents and workspaces + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Document access index](/features/document-access-index/) | Maintains a dedicated document-access index for faster authorization-aware document reads and validation. | Operators | Mixed | [Scale](/admin/scale/) | +| [File sync](/features/file-sync/) | Lets enabled workspaces ingest content from configured storage sources instead of relying only on manual uploads. | End users | Mixed | [File Sync](/admin/knowledge/) | +| [Group workspaces](/features/group-workspaces/) | Enables shared group spaces, group creation, and approval-aware file sharing between groups. | End users | Mixed | [Workspaces](/admin/workspaces/) | +| [Personal workspaces](/features/personal-workspace/) | Gives each user a personal document workspace for uploads, tags, prompts, agents, actions, and workflows where enabled. | End users | Enabled | [Workspaces](/admin/workspaces/) | +| [Public workspaces](/features/public-workspaces/) | Publishes approved workspace content to a broader public workspace experience. | End users | Disabled | [Workspaces](/admin/workspaces/) | +| [Retention policies](/features/retention-policies/) | Applies automatic retention rules separately for personal, group, and public workspace content. | Admins | Disabled | [Backup, Migrate & Restore](/admin/backup-recovery/) | + +## Media and ingestion + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Audio and video ingestion](/features/media-file-ingestion/) | Allows audio and video files to be processed into searchable transcript-style content. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [Chunk size overrides](/features/chunk-size-overrides/) | Lets admins override default extraction chunk sizes by file type for indexing and retrieval. | Admins | Disabled | [Search and Extract](/admin/knowledge/) | +| [Cross-format document compare](/features/document-compare/) | Compares documents across supported formats, including staged one-to-many comparison behavior. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [Document extraction](/features/document-extraction/) | Controls Document Intelligence, enhanced extraction, formulas, and embedded Office image analysis during ingestion. | End users | Mixed | [Search and Extract](/admin/knowledge/) | +| [Document metadata and classification](/features/document-metadata-and-classification/) | Adds AI-assisted metadata extraction and category labels to workspace documents. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [Image generation](/features/image-generation/) | Adds AI image creation from chat using the configured image generation model route. | End users | Disabled | [AI Models](/admin/ai-models/) | +| [Multimodal vision](/features/multimodal-vision/) | Enables model-based visual analysis for images and visual document content. | End users | Disabled | [Search and Extract](/admin/knowledge/) | +| [Tabular analysis](/features/tabular-analysis/) | Runs structured CSV and spreadsheet analysis with durable planning, batching, checkpoints, and response controls. | End users | Mixed | [Agents](/admin/agents-actions/) | + +## Agents and actions + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Agents](/features/agents/) | Enables Semantic Kernel agents, agent discovery, templates, and multi-agent orchestration options. | End users | Mixed | [Agents](/admin/agents-actions/) | +| [Core actions](/features/core-actions/) | Controls built-in action plugins for HTTP access, math, text transformation, time, waits, fact memory, and embedding utilities. | End users | Mixed | [Agents](/admin/agents-actions/) | +| [MCP destination governance](/features/mcp-governance/) | Adds governance controls for Model Context Protocol destinations used by agent actions. | Admins | Disabled | [Governance](/admin/governance/) | + +## Governance and safety + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Content safety](/features/content-safety/) | Screens prompts and content through Azure AI Content Safety with optional API Management routing. | Admins | Disabled | [Safety](/admin/security/) | +| [Feedback and conversation archiving](/features/feedback-and-archiving/) | Collects user feedback, exposes support feedback, and can archive conversations for review or compliance workflows. | End users | Mixed | [Send Feedback](/admin/help/) | +| [Notices and terms of use](/features/notices-and-terms/) | Shows AI notices and terms-of-use acknowledgement prompts to users. | End users | Disabled | [General](/admin/appearance/) | +| [Support menu and latest features](/features/support-menu/) | Adds the support menu, latest-feature pages, and optional documentation links for in-app help. | End users | Mixed | [Latest Features New](/admin/help/) | + +## Integrations + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [Custom pages and external links](/features/custom-navigation/) | Lets admins add trusted in-app custom pages and curated external navigation links. | End users | Disabled | [Custom Pages](/admin/appearance/) | +| [Key Vault secret storage](/features/key-vault-secrets/) | Stores configured secrets in Azure Key Vault and can remind operators about secret expiration. | Operators | Disabled | [Security](/admin/security/) | + +## Platform and operations + +| Capability | What it does | Who uses it | Default | Enable it in | +| --- | --- | --- | --- | --- | +| [App maintenance and health checks](/features/app-maintenance-and-health/) | Runs startup and recurring maintenance jobs and exposes optional external health-check behavior. | Operators | Mixed | [Control Center](/admin/operations/) | +| [Front Door and session controls](/features/front-door-and-session-security/) | Supports Azure Front Door routing and optional idle-timeout behavior for browser sessions. | Operators | Disabled | [Security](/admin/security/) | +| [Logging and diagnostics](/features/logging-and-diagnostics/) | Controls diagnostic logging, Application Insights global logging, and user-visible file processing logs. | Operators | Mixed | [Logging](/admin/operations/) | +| [Model routing and endpoints](/features/model-routing/) | Configures how chat and embeddings reach approved Azure OpenAI or model endpoint routes. | Admins | Mixed | [AI Models](/admin/ai-models/) | +| [Scale and cache](/features/scale-and-cache/) | Enables Redis-backed scale-out support and cached conversation reads for faster hot paths. | Operators | Mixed | [Scale](/admin/scale/) | +| [Thoughts display](/features/thoughts-display/) | Shows configured processing-thought information in supported chat flows. | End users | Enabled | [General](/admin/appearance/) | diff --git a/docs/how-to/add_documents.md b/docs/guides/add-documents.md similarity index 90% rename from docs/how-to/add_documents.md rename to docs/guides/add-documents.md index 66cc618be..0c947da12 100644 --- a/docs/how-to/add_documents.md +++ b/docs/guides/add-documents.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Add Documents" -permalink: /how-to/add_documents/ +permalink: /guides/add-documents/ menubar: docs_menu accent: blue eyebrow: "How-To Guide" @@ -16,11 +16,14 @@ hero_pills: - Classification and citations supported hero_links: - label: "Document tutorial" - url: /tutorials/uploading_documents/ + url: /guides/upload-and-manage-documents/ style: primary - label: "Admin overview" url: /admin_configuration/ style: secondary +section: "Guides" +redirect_from: + - /how-to/add_documents/ --- Treat uploads as an ingestion workflow, not just a file transfer. The target workspace, enabled services, and metadata choices determine whether the file becomes grounded chat context, searchable workspace content, or an item waiting on a missing dependency. @@ -77,7 +80,7 @@ Treat uploads as an ingestion workflow, not just a file transfer. The target wor ## Related guides -- [Uploading and Managing Documents](../tutorials/uploading_documents.md) -- [Document Classification Tutorial](../tutorials/classifying_documents.md) -- [Application Workflows](../application_workflows.md) +- [Uploading and Managing Documents]({{ '/guides/upload-and-manage-documents/' | relative_url }}) +- [Document Classification Tutorial]({{ '/guides/classify-documents/' | relative_url }}) +- [Application Workflows]({{ '/reference/application-workflows/' | relative_url }}) - [Troubleshooting](../troubleshooting/troubleshooting.md) diff --git a/docs/guides/admin-deploy-simplechat.md b/docs/guides/admin-deploy-simplechat.md new file mode 100644 index 000000000..45fc06c4a --- /dev/null +++ b/docs/guides/admin-deploy-simplechat.md @@ -0,0 +1,91 @@ +--- +layout: page +title: "Deploy SimpleChat as an administrator" +description: "Choose a SimpleChat deployment path, prepare the required Azure resources, run the rollout, and verify first boot." +section: "Guides" +audience: admin +--- + +## What this covers + +This guide helps administrators choose a deployment path, line up the required Azure and identity prerequisites, run the deployment through the correct reference guide, and prove the new SimpleChat site is ready for first configuration. + +{% include media.html type="video" + title="Deploy SimpleChat as an administrator walkthrough" + poster="video-posters/guide-admin-deploy-simplechat.png" + capture="Recording planned. Walk the full journey end to end and explain the decisions an admin makes along the way." %} + +## Why it matters + +Deployment choices decide the runtime model you will operate later. The repo-supported deployers run SimpleChat as a container-based App Service, while the manual notes are for intentional native Python App Service deployments. Picking the wrong path can lead to the wrong startup command, upgrade command, or rollback expectation. + +## Before you start + +- Confirm you have Azure subscription rights to create the app, data, search, storage, monitoring, and AI resources required by the path you choose. +- Confirm who can create or coordinate the Entra application registration and app roles used for sign-in and role-gated features. +- Install the toolchain for your chosen path. The recommended Azure Developer CLI path requires Azure Developer CLI, Azure CLI, Git, and Python 3.12 because the repo deployment hooks run Python. +- Decide whether the deployment must use Azure Government, managed identity, enterprise private networking, or a native Python App Service model before you run the first deployer. + +## Step 1: Choose the deployment path + +Use the deployment references as the mechanical source of truth instead of copying command details into this journey. + +| Choose this path | Use it when | Read next | +| --- | --- | --- | +| Azure Developer CLI | You want the repo's recommended end-to-end flow for provisioning, configuration, and application deployment. | [Azure Developer CLI deployment]({{ '/reference/deploy/azd-cli_deploy/' | relative_url }}) | +| Azure CLI with PowerShell | You want a script-driven container App Service rollout with more direct sequencing and recovery control than AZD. | [Azure CLI with PowerShell deployment]({{ '/reference/deploy/azurecli_powershell_deploy/' | relative_url }}) | +| Bicep | You need to inspect or customize the infrastructure modules directly. | [Bicep deployment]({{ '/reference/deploy/bicep_deploy/' | relative_url }}) | +| Terraform | Your environment already manages infrastructure through Terraform and can own the image-publishing flow. | [Terraform deployment]({{ '/reference/deploy/terraform_deploy/' | relative_url }}) | +| Manual native Python | You intentionally operate native Python App Service instead of the repo container deployers. | [Manual deployment notes]({{ '/reference/deploy/manual_deploy/' | relative_url }}) | + +{% include media.html src="guides/admin-deploy-simplechat-path.png" + alt="Deployment decision table showing when to choose AZD, Azure CLI with PowerShell, Bicep, Terraform, or manual native Python." + title="Choose the deployment path" + capture="Capture the deployment reference decision map with the recommended order visible." %} + +## Step 2: Prepare the required Azure resources + +A first deployment needs the core runtime resources before optional capabilities matter: App Service, Azure OpenAI for chat, Azure OpenAI embeddings for retrieval, Azure AI Search, Cosmos DB for app data, Document Intelligence for ingestion, and monitoring. Optional resources such as Content Safety, Video Indexer, Speech, Storage for Enhanced Citations, Redis, and APIM should be provisioned only when the matching feature is in scope. + +For resource sizing and service-specific notes, use [Manual Setup]({{ '/setup_instructions_manual/' | relative_url }}). For Azure Government, managed identity, and private endpoint patterns, use [Special Setup Scenarios]({{ '/deploy/special-scenarios/' | relative_url }}). + +## Step 3: Run the deployment reference + +Follow the selected reference guide from start to finish. For the current AZD path, initialize the environment, answer subscription, region, and environment prompts, and let the deployment provision resources and deploy the app together. For script-driven or IaC paths, keep the deployment model aligned with the reference and avoid mixing native Python startup guidance into container deployments. + +{% include media.html src="guides/admin-deploy-simplechat-run.png" + alt="Terminal or deployment output showing the selected SimpleChat deployer completing successfully." + title="Run the deployer" + capture="Capture the final successful deployment output with resource names and secrets redacted." %} + +## Step 4: Keep startup behavior aligned with the runtime + +Container-based deployments through AZD, Azure CLI with PowerShell, Bicep, and Terraform use the container entrypoint to start Gunicorn. Do not add the native Python startup command to those App Services. Native Python deployments must deploy the `application/single_app` folder and use the startup command documented in [Manual deployment notes]({{ '/reference/deploy/manual_deploy/' | relative_url }}). + +## Step 5: Complete first-boot checks + +Open the app URL from the deployment output, sign in, and confirm the application reaches the authenticated experience. Then open Admin Settings and verify the first configuration tabs are available before inviting end users. + +## Verify it worked + +- The App Service is reachable at the expected URL. +- Sign-in completes through the intended Entra tenant and returns to SimpleChat. +- Admin Settings opens for an administrator. +- A basic chat model is ready or you know that the next step is [first configuration]({{ '/guides/admin-first-configuration/' | relative_url }}). +- Monitoring or deployment logs show the site started cleanly. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The site starts but native Python commands are being discussed for a container deployment | The runtime model was mixed during setup | Recheck the deployment reference and remove native Python startup assumptions from container App Service paths. | +| Authentication redirects fail after a routed-domain setup | Front Door or redirect URL configuration is incomplete | Review [Scale settings]({{ '/admin/scale/' | relative_url }}) and validate sign-in through the routed URL after saving. | +| The app loads but document search fails | Embedding, AI Search, or Document Intelligence configuration is incomplete | Finish [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) and [Search and Extract settings]({{ '/admin/knowledge/' | relative_url }}). | + +## Related + +- [Getting Started]({{ '/start/deployment-options/' | relative_url }}) +- [Deployment Reference]({{ '/reference/deploy/' | relative_url }}) +- [Manual Setup]({{ '/setup_instructions_manual/' | relative_url }}) +- [Special Setup Scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) +- [First configuration guide]({{ '/guides/admin-first-configuration/' | relative_url }}) diff --git a/docs/guides/admin-enable-a-capability.md b/docs/guides/admin-enable-a-capability.md new file mode 100644 index 000000000..c662b0e5b --- /dev/null +++ b/docs/guides/admin-enable-a-capability.md @@ -0,0 +1,90 @@ +--- +layout: page +title: "Enable a SimpleChat capability safely" +description: "Use a repeatable admin procedure to turn on optional SimpleChat capabilities without exposing broken controls to users." +section: "Guides" +audience: admin +--- + +## What this covers + +This guide gives administrators a repeatable rollout pattern for any optional capability controlled by Admin Settings. Use it for features such as Content Safety, Enhanced Citations, File Sync, URL Access, Deep Research, image generation, Redis, Key Vault-backed secrets, and other tab-specific toggles. + +{% include media.html type="video" + title="Enable a SimpleChat capability safely walkthrough" + poster="video-posters/guide-admin-enable-a-capability.png" + capture="Recording planned. Walk the full journey end to end and explain the decisions an admin makes along the way." %} + +## Why it matters + +A capability toggle is usually the last step, not the first one. Many SimpleChat switches expose UI or runtime paths that require Azure resources, managed identity roles, keys, domain policies, app roles, storage, quotas, or limits. Turning on the switch before the dependency is ready can give users a visible feature that fails when they try to use it. + +## Before you start + +- Identify the exact Admin Settings tab that owns the capability and read its page under [Administration]({{ '/admin/' | relative_url }}). +- Confirm whether the capability key appears in the docs inventory as one of the SimpleChat capability toggles. +- Know the user scope for the rollout: admin-only test, a pilot group, one workspace scope, or the whole tenant. +- Have a non-admin validation account that matches the intended user role and another account that should not have access when role gating is enabled. + +## Step 1: Confirm the backing service or policy exists + +Read the capability's tab page and list every dependency it names. Examples include Azure AI Content Safety for Content Safety, Storage for Enhanced Citations, Azure Cache for Redis for Redis-backed cache, Key Vault for agent and action secrets, Foundry agent details for Web Search, Speech service for audio and voice features, or Entra app roles for gated workspace actions. + +{% include media.html src="guides/admin-enable-a-capability-dependencies.png" + alt="Admin checklist showing the backing Azure resource, role, policy, quota, and limit required before enabling a capability." + title="Confirm capability dependencies" + capture="Capture a dependency checklist for one capability with resource names redacted and readiness marked before the toggle is enabled." %} + +## Step 2: Configure authentication before the toggle + +Choose the authentication mode shown on the tab: key, managed identity, APIM subscription key, connection string, or another supported option. For managed identity, assign the App Service identity to the target Azure resource before saving the setting. For app-role requirements, assign users or groups in the Enterprise App before enforcing the requirement. + +## Step 3: Set limits and allowlists first + +Configure limits, domains, scopes, thresholds, retention windows, source counts, concurrency, or guardrails before making the capability visible. This is especially important for URL Access, Deep Research, File Sync, tabular runs, Cosmos throughput automation, MCP destinations, and workspace downloads because those controls define how far the feature can reach. + +{% include media.html src="guides/admin-enable-a-capability-guardrails.png" + alt="Admin Settings page showing limits, roles, allowlists, and thresholds configured before enabling the user-facing switch." + title="Set guardrails before enabling" + capture="Capture the relevant Admin Settings tab with limits and policy fields populated before the final capability switch is turned on." %} + +## Step 4: Enable the capability for the smallest safe audience + +Turn on the capability after dependencies, authentication, and guardrails are ready. If the tab supports role requirements, group assignment, admin-only management, or workspace-specific scope controls, use those controls to start with the smallest group that can validate the feature. + +## Step 5: Validate with a non-admin account + +Sign in as a user who should have access and complete the smallest real workflow: send a blocked Content Safety test, open an enhanced citation, create a sync source, use URL Access on an allowed domain, generate an image, or create a test action. Then sign in as a user who should not have access and confirm the control is hidden or blocked according to the tab's policy. + +{% include media.html src="guides/admin-enable-a-capability-validate.png" + alt="Non-admin validation account using the newly enabled capability while an unassigned account is blocked." + title="Validate with normal users" + capture="Capture the capability working for an assigned non-admin account and redact any user content or secrets." %} + +## Step 6: Watch telemetry and prepare rollback + +After validation, check [Logging settings]({{ '/admin/operations/' | relative_url }}) and Application Insights for failures from the new path. Keep a rollback note with the setting names changed, the previous values, and any dependent resources or roles created. If the feature fails, disable the capability toggle first, then remove broader access or resource changes only after users are no longer hitting the path. + +## Verify it worked + +- The backing Azure resource, identity permission, key, APIM path, storage endpoint, or app role exists before the feature is enabled. +- The capability works for a non-admin account in the intended scope. +- Unassigned or out-of-scope users cannot use the feature when a role or assignment requirement is enabled. +- Telemetry does not show repeated failures from the newly enabled path. +- The rollback action is known and can be performed quickly. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Users see the control but the action fails | The toggle was enabled before endpoint, identity, key, or quota validation finished | Disable the toggle, finish dependency configuration, then test with a pilot user. | +| Assigned users are blocked | The Entra app role, workspace assignment, or allowed-list entry is missing | Add the assignment first, then re-test with the same account. | +| The feature works for admins but not normal users | Validation only used an admin account | Repeat validation with the intended non-admin role and check the tab's role requirement settings. | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [First configuration guide]({{ '/guides/admin-first-configuration/' | relative_url }}) +- [Operate SimpleChat day to day]({{ '/guides/admin-operate-simplechat/' | relative_url }}) +- [Safety settings]({{ '/admin/security/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/guides/admin-first-configuration.md b/docs/guides/admin-first-configuration.md new file mode 100644 index 000000000..012675283 --- /dev/null +++ b/docs/guides/admin-first-configuration.md @@ -0,0 +1,100 @@ +--- +layout: page +title: "Configure SimpleChat for the first time" +description: "Walk the Admin Settings tabs in the safest order after a successful SimpleChat deployment." +section: "Guides" +audience: admin +--- + +## What this covers + +This guide gives administrators the first-run order for Admin Settings after deployment. It starts with identity-facing and security decisions, then configures models, retrieval, workspaces, and optional capabilities that depend on those foundations. + +{% include media.html type="video" + title="Configure SimpleChat for the first time walkthrough" + poster="video-posters/guide-admin-first-configuration.png" + capture="Recording planned. Walk the full journey end to end and explain the decisions an admin makes along the way." %} + +## Why it matters + +Some Admin Settings tabs unlock experiences that depend on earlier tabs. Workspace search depends on embeddings and Azure AI Search. Agents depend on a working GPT model. Enhanced citations depend on storage. Role requirements depend on Entra app roles that already exist. Following the order below prevents users from seeing controls that cannot work yet. + +## Before you start + +- Finish deployment and verify an administrator can open Admin Settings. +- Have endpoint names, deployment names, authentication choices, and keys or managed identity assignments for the Azure resources that were deployed. +- Create required Entra app roles before enforcing role-gated features such as `CreateGroups`, `WorkflowUser`, `ChatFileUploadUser`, `SafetyViolationAdmin`, `FeedbackAdmin`, `UrlAccessUser`, or `DeepResearchUser`. +- Keep a non-admin test account available so you can verify what normal users see after each major change. + +## Step 1: Set tenant identity, notices, and visible app behavior + +Start with [General settings]({{ '/admin/appearance/' | relative_url }}). Publish the approved application title, logo, landing page copy, access-denied message, support destinations, health-check behavior, Terms of Use, AI notice, idle timeout, and upload/session limits. These are first because every user sees them before feature-specific settings matter. + +{% include media.html src="guides/admin-first-configuration-general.png" + alt="General settings page showing tenant branding, terms, support, health check, and session controls." + title="Configure general settings first" + capture="Capture the General tab after tenant-facing copy and support controls are populated, with any private URLs redacted." %} + +## Step 2: Configure secret handling before users create tools + +Use [Security settings]({{ '/admin/security/' | relative_url }}) if the deployment will store agent or action secrets in Azure Key Vault. Enable Key Vault-backed secrets only after the vault exists and the configured identity can read and write the required secrets. Decide reminder recipients before enabling expiration reminder tracking or requiring expiration dates. + +## Step 3: Configure chat and embedding models + +Open [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) and configure GPT routing before users test chat or agents. Configure embeddings before enabling document-heavy workspace use because retrieval depends on embeddings and Azure AI Search. Add image generation only after the image deployment, quota, and policy approval are ready. + +{% include media.html src="guides/admin-first-configuration-models.png" + alt="AI Models settings page with chat and embedding endpoint sections configured." + title="Configure model endpoints" + capture="Capture the AI Models tab showing chat and embedding sections complete, with keys and subscription details redacted." %} + +## Step 4: Configure search, extraction, and web evidence boundaries + +Use [Search and Extract settings]({{ '/admin/knowledge/' | relative_url }}) next. Configure Azure AI Search, Document Intelligence, chunking, and extraction modes before promoting workspace uploads. If enabling URL Access, Web Search, Deep Research, Speech, or Video Indexer, configure limits, domain policy, roles, and backing service details before turning on user-facing controls. + +## Step 5: Decide workspace scopes and data policies + +Move to [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) after model and retrieval prerequisites are ready. Enable only the personal, group, public, workflow, upload, download, retention, classification, and agreement capabilities the tenant has approved. Test role-gated behavior with assigned and unassigned users before wider rollout. + +{% include media.html src="guides/admin-first-configuration-workspaces.png" + alt="Workspaces settings page showing enabled scopes, role requirements, retention, and agreement choices." + title="Set workspace scope and retention" + capture="Capture the Workspaces tab with approved scopes and retention controls visible." %} + +## Step 6: Add citation storage when source previews are required + +Configure [Citations settings]({{ '/admin/chat/' | relative_url }}) when users need richer source previews or tabular source access. Provision and authorize storage before enabling Enhanced Citations. Set tabular preview and large-run confirmation thresholds according to available memory and cost tolerance. + +## Step 7: Enable safety review and feedback paths + +Use [Safety settings]({{ '/admin/security/' | relative_url }}) after core chat works. Content Safety needs an Azure AI Content Safety resource or APIM route and matching authentication. Feedback and safety review role requirements should be enabled only after the `SafetyViolationAdmin` and `FeedbackAdmin` assignments are ready. + +## Step 8: Roll out agents, actions, governance, and sync only after foundations pass + +Configure [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) after a GPT model works. Use [Governance settings]({{ '/admin/governance/' | relative_url }}) before opening broad user-created endpoints, agents, actions, or MCP destinations. Enable [File Sync settings]({{ '/admin/knowledge/' | relative_url }}) only after target workspace scopes exist and ingestion limits are set. + +## Step 9: Turn on operational controls + +Finish the first configuration pass with [Logging settings]({{ '/admin/operations/' | relative_url }}), [Scale settings]({{ '/admin/scale/' | relative_url }}), [Control Center settings]({{ '/admin/operations/' | relative_url }}), and [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}). Logging, backups, cache behavior, Front Door support, and throughput automation should be ready before the environment becomes business critical. + +## Verify it worked + +- A non-admin test user sees only approved navigation, workspace scopes, and optional controls. +- Basic chat works with the configured GPT model. +- A representative document can be uploaded, extracted, indexed, and used in grounded chat. +- Role-gated actions are visible to assigned users and hidden or blocked for unassigned users. +- Logging and backup settings are ready for day-two operations. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Workspace answers do not use uploaded documents | Embeddings, Azure AI Search, or extraction settings are incomplete | Revisit [AI Models settings]({{ '/admin/ai-models/' | relative_url }}) and [Search and Extract settings]({{ '/admin/knowledge/' | relative_url }}), then re-test ingestion. | +| Users cannot create groups or workflows after the toggle is enabled | The matching Entra app role requirement is on but assignments are missing | Assign the app role or disable the requirement until assignments are complete. | +| Agent controls appear but agents fail | Agents were enabled before a working GPT endpoint was available | Validate chat model routing, then return to [Agents settings]({{ '/admin/agents-actions/' | relative_url }}). | + +## Related + +- [Administration settings overview]({{ '/admin/' | relative_url }}) +- [Enable a capability safely]({{ '/guides/admin-enable-a-capability/' | relative_url }}) +- [Operate SimpleChat day to day]({{ '/guides/admin-operate-simplechat/' | relative_url }}) diff --git a/docs/guides/admin-operate-simplechat.md b/docs/guides/admin-operate-simplechat.md new file mode 100644 index 000000000..3e8f6009c --- /dev/null +++ b/docs/guides/admin-operate-simplechat.md @@ -0,0 +1,90 @@ +--- +layout: page +title: "Operate SimpleChat day to day" +description: "Monitor, scale, review safety and feedback, manage data, and troubleshoot a running SimpleChat deployment." +section: "Guides" +audience: admin +--- + +## What this covers + +This guide gives administrators a day-to-day operating loop for SimpleChat after deployment and first configuration: watch telemetry, scale proven bottlenecks, review feedback and safety events, manage retention and backups, and know where to look when something breaks. + +{% include media.html type="video" + title="Operate SimpleChat day to day walkthrough" + poster="video-posters/guide-admin-operate-simplechat.png" + capture="Recording planned. Walk the full journey end to end and explain the decisions an admin makes along the way." %} + +## Why it matters + +SimpleChat depends on several Azure services that fail or saturate differently. App Service pressure, Cosmos RU exhaustion, Azure AI Search latency, model quota, extraction failures, and governance blocks need different responses. A steady operating rhythm keeps admins from guessing during incidents. + +## Before you start + +- Confirm [Logging settings]({{ '/admin/operations/' | relative_url }}) are configured for Application Insights and that temporary debug logging has an auto-turnoff window when used. +- Confirm [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) have a valid storage target and a retention policy. +- Know which optional capabilities are enabled so you can check their backing Azure resources during incidents. +- Keep access to Azure Portal views for App Service, Cosmos DB, Azure AI Search, Azure OpenAI or Foundry, Document Intelligence, Storage, Redis, and Application Insights. + +## Step 1: Monitor the application before changing settings + +Use Application Insights and App Service logs to establish whether the issue is in the web tier, instrumentation, configuration, or a downstream dependency. The troubleshooting reference starts with failed requests and exceptions because those records identify the failing operation before you change Admin Settings. + +{% include media.html src="guides/admin-operate-simplechat-monitor.png" + alt="Application Insights view showing failed requests and exceptions for a SimpleChat operation." + title="Monitor requests and exceptions" + capture="Capture Application Insights requests and exceptions with operation identifiers visible and user content redacted." %} + +## Step 2: Scale the bottleneck you can prove + +Use [Application Scaling]({{ '/application_scaling/' | relative_url }}) and [Scale settings]({{ '/admin/scale/' | relative_url }}) together. Scale App Service up for memory or CPU pressure and out for concurrent traffic. Watch Cosmos RU consumption and HTTP 429 responses before increasing throughput. Use Azure AI Search replicas, partitions, or higher tiers when query latency, indexing throughput, or service limits justify it. Treat AI service quota and rate limits as separate from web-tier capacity. + +## Step 3: Review safety violations, feedback, and governed items + +Use [Safety settings]({{ '/admin/security/' | relative_url }}) to control Content Safety, feedback collection, review role requirements, and conversation archiving. Use [Governance settings]({{ '/admin/governance/' | relative_url }}) to review personal, group, and global endpoints, agents, actions, and MCP destinations. Keep review roles assigned before enforcing review-only access. + +{% include media.html src="guides/admin-operate-simplechat-review.png" + alt="Admin review surfaces for safety, feedback, and governed items." + title="Review operational queues" + capture="Capture safety, feedback, or governance review queues with any user content blurred or replaced by sample data." %} + +## Step 4: Manage retention, archiving, and backups + +Use [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) for default document and conversation retention by workspace type. Use [Safety settings]({{ '/admin/security/' | relative_url }}) when deletion should archive conversations instead of removing them immediately. Use [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) for scheduled backups, restore preflight, job history, migration, and emergency Cosmos JSON edits. + +## Step 5: Keep ingestion and source data under control + +Use [Search and Extract settings]({{ '/admin/knowledge/' | relative_url }}) to tune extraction, chunking, URL Access, Web Search, Deep Research, Speech, and Video Indexer. Use [File Sync settings]({{ '/admin/knowledge/' | relative_url }}) to limit source counts, schedule frequency, files per run, GB per run, concurrency, recursive sources, and allowed workspace scopes before sync jobs grow unexpectedly. + +## Step 6: Diagnose failures by layer + +Start with [Troubleshooting]({{ '/troubleshooting/' | relative_url }}). Query failed requests, pivot to exceptions with the same `operation_Id`, and only then decide whether the problem is authentication, model routing, retrieval, extraction, storage, cache, or a feature toggle. Use temporary debug logging for short windows and turn it off after reproducing the issue. + +{% include media.html src="guides/admin-operate-simplechat-diagnose.png" + alt="Troubleshooting flow showing requests, exceptions, Admin Settings, and Azure dependency checks." + title="Diagnose by layer" + capture="Capture the troubleshooting flow or a monitoring workbook that separates app, data, search, and AI service checks." %} + +## Verify it worked + +- Routine telemetry shows successful requests, expected latency, and no unexplained exception spike. +- Backup jobs complete on schedule and old backups follow the configured retention policy. +- Safety, feedback, governance, and file-processing queues are reviewed at the cadence your tenant requires. +- Scaling changes are tied to observed pressure such as CPU, memory, RU saturation, search latency, or AI service quota. +- Temporary debug logging is off outside active investigations. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| You cannot tell which backend call failed | The investigation started from settings instead of telemetry | Query Application Insights failed requests, capture `operation_Id`, then pivot to exceptions. | +| Horizontal scale causes inconsistent behavior | Shared cache or session assumptions were not ready | Review Redis-backed cache guidance in [Scale settings]({{ '/admin/scale/' | relative_url }}) before serious scale-out. | +| File processing history grows without cleanup | File-processing logs are enabled without a retention routine | Use [Logging settings]({{ '/admin/operations/' | relative_url }}) cleanup controls and set an operating cadence. | + +## Related + +- [Troubleshooting]({{ '/troubleshooting/' | relative_url }}) +- [Application Scaling]({{ '/application_scaling/' | relative_url }}) +- [Logging settings]({{ '/admin/operations/' | relative_url }}) +- [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) +- [Update SimpleChat]({{ '/guides/admin-update-simplechat/' | relative_url }}) diff --git a/docs/guides/admin-update-simplechat.md b/docs/guides/admin-update-simplechat.md new file mode 100644 index 000000000..27b6548d0 --- /dev/null +++ b/docs/guides/admin-update-simplechat.md @@ -0,0 +1,96 @@ +--- +layout: page +title: "Update an existing SimpleChat deployment" +description: "Plan, back up, upgrade, validate, and roll back a SimpleChat deployment without mixing runtime models." +section: "Guides" +audience: admin +--- + +## What this covers + +This guide helps administrators update an existing SimpleChat environment by identifying the current version and runtime model, taking backups, choosing the matching upgrade path, validating the release, and preparing a rollback route. + +{% include media.html type="video" + title="Update an existing SimpleChat deployment walkthrough" + poster="video-posters/guide-admin-update-simplechat.png" + capture="Recording planned. Walk the full journey end to end and explain the decisions an admin makes along the way." %} + +## Why it matters + +The correct upgrade path depends on how the current site is deployed. Native Python App Service upgrades need startup-command validation. Container-based deployments normally use `azd deploy` for code-only updates and reserve provisioning commands for infrastructure changes. A release plan that ignores that split can make rollback harder than the change itself. + +## Before you start + +- Identify whether the running site is native Python App Service or a container-based App Service deployed through AZD, Azure CLI with PowerShell, Bicep, or Terraform. +- Record the current SimpleChat version from `application/single_app/config.py` in the release you are running or from your deployed artifact metadata if your operations process tracks images separately. +- Confirm you can access App Service logs, Application Insights, Cosmos DB, Azure AI Search, and any storage used for Enhanced Citations or backups. +- Schedule the change window according to your tenant's availability and validation requirements. + +## Step 1: Check the current deployment model and version + +Start with [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}). The first decision is deployment model, not command choice. Confirm whether App Service is pulling a container image or running native Python code. Then record the current app version and current image tag or deployment package so rollback has a known target. + +{% include media.html src="guides/admin-update-simplechat-inventory.png" + alt="Upgrade inventory showing runtime model, current version, image or package identifier, and App Service details." + title="Inventory the current deployment" + capture="Capture the current deployment model and version evidence with subscription and secret values redacted." %} + +## Step 2: Back up the data surfaces first + +Use [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) before changing code or infrastructure. At minimum, preserve Cosmos DB data and AI Search indexes. Include source document blobs when Enhanced Citations are enabled and those blobs are required for restore or migration. Prefer a recent successful backup plus a clear restore policy over assuming the platform backup is enough. + +## Step 3: Choose the matching upgrade path + +Use the upgrade guide's split: + +| Current deployment | Normal upgrade path | Notes | +| --- | --- | --- | +| Native Python App Service | VS Code deploy, Azure CLI ZIP deploy, or deployment slots | Confirm the native Python Gunicorn startup command before closing the release. | +| Container-based App Service through AZD, Bicep, Terraform, or repo deployers | `azd deploy` for code-only releases | Use `azd provision` for infrastructure-only changes and `azd up` when app and infrastructure change together. | +| Azure CLI with PowerShell image rollout | `deployers/azurecli/upgrade-simplechat.ps1` | Use when your App Service already pulls from ACR and you want a PowerShell-first code-only rollout. | +| Image-tag promotion model | Move App Service back or forward between known image tags | Treat this as an advanced operations path that depends on your ACR and App Service process. | + +{% include media.html src="guides/admin-update-simplechat-path.png" + alt="Upgrade decision table separating native Python, container, PowerShell image rollout, and image-tag promotion paths." + title="Pick the upgrade path" + capture="Capture the Upgrade Paths decision guide with the deployment-model split visible." %} + +## Step 4: Run the update through the chosen path + +For container releases, use the smallest command that matches the change: application-only, infrastructure-only, or both. For native Python releases, deploy the `application/single_app` folder and verify dependency installation and startup behavior. For production, prefer deployment slots or image tags when your operating model supports staged validation and fast rollback. + +## Step 5: Validate after release + +Open the updated site, sign in, open Admin Settings, run basic chat, and test one document retrieval flow if workspaces are enabled. Check Application Insights or App Service logs for startup errors, dependency failures, authentication problems, downstream service failures, and unexpected throttling. + +{% include media.html src="guides/admin-update-simplechat-validate.png" + alt="Post-upgrade validation checklist showing sign-in, chat, document retrieval, and telemetry checks." + title="Validate the upgrade" + capture="Capture the post-upgrade validation checklist or monitoring view after a successful release." %} + +## Step 6: Roll back when validation fails + +Use the rollback method that matches the rollout. Swap back if you used deployment slots. Repoint App Service to the prior known-good image tag if you use image promotion. Redeploy the previous package for native Python when package rollback is your process. If data changed during the failed release, use [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) only after preflight confirms the target and collision policy. + +## Verify it worked + +- The site reports or corresponds to the intended SimpleChat version or image tag. +- App Service startup logs do not show dependency or Gunicorn failures. +- Authentication, Admin Settings, basic chat, and representative workspace retrieval work. +- Application Insights requests and exceptions do not show a new failure pattern. +- The rollback target remains available until the release is accepted. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A native Python site fails after deployment | Startup command or deployment folder is wrong | Use the native Python startup command and deploy `application/single_app` as documented in [Manual deployment notes]({{ '/reference/deploy/manual_deploy/' | relative_url }}). | +| A container release was reprovisioned unnecessarily | `azd up` was used for a code-only change | Prefer `azd deploy` for application-only updates and use provisioning commands only when infrastructure changed. | +| Post-upgrade document retrieval fails | Search, embedding, or extraction settings changed or a dependency is unhealthy | Check [AI Models settings]({{ '/admin/ai-models/' | relative_url }}), [Search and Extract settings]({{ '/admin/knowledge/' | relative_url }}), and related telemetry. | + +## Related + +- [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}) +- [Deployment Reference]({{ '/reference/deploy/' | relative_url }}) +- [Backup, Migrate & Restore settings]({{ '/admin/backup-recovery/' | relative_url }}) +- [Operate SimpleChat day to day]({{ '/guides/admin-operate-simplechat/' | relative_url }}) diff --git a/docs/how-to/azure_speech_managed_identity_manul_setup.md b/docs/guides/azure-speech-managed-identity-setup.md similarity index 98% rename from docs/how-to/azure_speech_managed_identity_manul_setup.md rename to docs/guides/azure-speech-managed-identity-setup.md index 25044e169..66a39f0c2 100644 --- a/docs/how-to/azure_speech_managed_identity_manul_setup.md +++ b/docs/guides/azure-speech-managed-identity-setup.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Azure Speech Managed Identity Setup" -permalink: /how-to/azure_speech_managed_identity_manul_setup/ +permalink: /guides/azure-speech-managed-identity-setup/ menubar: docs_menu accent: blue eyebrow: "How-To Guide" @@ -16,11 +16,14 @@ hero_pills: - Speech User role first hero_links: - label: "Managed identity guide" - url: /how-to/use_managed_identity/ + url: /guides/use-managed-identity/ style: primary - label: "Admin configuration" url: /reference/admin_configuration/ style: secondary +section: "Guides" +redirect_from: + - /how-to/azure_speech_managed_identity_manul_setup/ --- Speech is the place where managed identity setup becomes very specific. The most important rule is simple: the regional endpoint that works with keys is not enough for managed identity. You need the resource-specific custom-subdomain endpoint so Azure can evaluate RBAC against the correct Speech resource. diff --git a/docs/tutorials/classifying_documents.md b/docs/guides/classify-documents.md similarity index 93% rename from docs/tutorials/classifying_documents.md rename to docs/guides/classify-documents.md index c04ad10b4..c6bc0ec18 100644 --- a/docs/tutorials/classifying_documents.md +++ b/docs/guides/classify-documents.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Document Classification Tutorial" -permalink: /tutorials/classifying_documents/ +permalink: /guides/classify-documents/ menubar: docs_menu accent: emerald eyebrow: "Tutorial 4" @@ -16,7 +16,7 @@ hero_pills: - Better context for AI responses hero_links: - label: Tutorial index - url: /tutorials/ + url: /guides/ style: primary - label: Admin configuration url: /admin_configuration/ @@ -25,10 +25,13 @@ show_nav: true nav_links: prev: title: Uploading and Managing Documents - url: /tutorials/uploading_documents/ + url: /guides/upload-and-manage-documents/ next: title: How-to Guides - url: /how-to/ + url: /guides/ +section: "Guides" +redirect_from: + - /tutorials/classifying_documents/ --- Classification works when the categories reflect how your organization actually handles content. This tutorial focuses on making the labels operational, not decorative. @@ -335,11 +338,11 @@ Congratulations! You've successfully: ## Next Steps Now that you have document classification working: -- Create [specialized agents](first_agent) that work with specific document classifications -- Explore [advanced document management](../how-to/add_documents) techniques -- Set up [group workspaces](../how-to/group_management) with classification workflows -- Learn about [enhanced citations](../how-to/enhanced_citations) that show classification context +- Create [specialized agents]({{ '/guides/create-your-first-agent/' | relative_url }}) that work with specific document classifications +- Explore [advanced document management]({{ '/guides/add-documents/' | relative_url }}) techniques +- Set up [group workspaces]({{ '/guides/manage-group-workspaces/' | relative_url }}) with classification workflows +- Learn about [enhanced citations]({{ '/admin/chat/' | relative_url }}) that show classification context --- -*Tutorial series complete! Ready for [how-to guides](../how-to/) →* +*Tutorial series complete! Ready for [how-to guides]({{ '/guides/' | relative_url }}) →* diff --git a/docs/guides/collaborate-in-a-conversation.md b/docs/guides/collaborate-in-a-conversation.md new file mode 100644 index 000000000..fde9e8c33 --- /dev/null +++ b/docs/guides/collaborate-in-a-conversation.md @@ -0,0 +1,78 @@ +--- +layout: page +title: "Collaborate in a conversation" +description: "Share a chat with other people, mention participants, and approve generated files before they become available." +section: "Guides" +audience: user +--- + +## What this does + +Collaborative conversations let more than one person work in the same chat thread. Participants see the shared message history, can reply in context, and can use `@` mentions to direct attention to another participant or an AI target. + +{% include media.html type="video" + title="Collaborate in a conversation walkthrough" + poster="video-posters/guide-collaborate-in-a-conversation.png" + capture="Recording planned. Show sharing a conversation, mentioning a participant, and approving a shared file." %} + +## Why you would use this + +Use a shared conversation when the discussion, prompts, AI responses, and generated deliverables should stay in one place for a team. It is useful for handoffs, shared review, and decisions that need a visible trail. Keep private analysis in a normal one-person chat when other users should not see the messages or files. + +## Before you start + +- An admin must enable `enable_collaborative_conversations`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- You need an existing personal or group conversation to share, or you can start from a collaborative conversation you already manage. +- The person you add must be discoverable through recent collaborators or local user settings records in SimpleChat. +- In personal shared conversations, owners and admins can add or remove members. The owner can also change member roles and delete the shared conversation for everyone. +- In shared group conversations, group access still follows the group workspace role and status rules. + +## Steps + +1. Open the conversation you want to share. +2. Open the conversation menu and choose **Add participants**, or open **Details** and choose **Add participant** when the button is available. +3. Search for a person by name or email, select the right result, and confirm with **Add participant**. + +{% include media.html src="guides/collaborate-in-a-conversation-add-participant.png" + alt="Screenshot showing the Add participant dialog for a shared conversation." + title="Add a participant" + capture="Capture the Add participant dialog with a realistic collaborator search result. Redact names and email addresses." %} + +4. Ask the invited person to accept the invite before they post. Pending invitees can see invite controls, but they cannot participate until they accept. +5. Type a shared message in the composer. To mention someone, type `@`, use the arrow keys if needed, then press **Tab** or **Enter** to accept the highlighted participant. + +{% include media.html src="guides/collaborate-in-a-conversation-mention.png" + alt="Screenshot showing the shared conversation mention menu with a participant highlighted." + title="Mention a participant" + capture="Capture the mention menu in a shared conversation. Show the highlighted suggestion and redact personal details." %} + +6. If the selected `@` suggestion is a collaborator who is not already in the conversation, confirm the invite before sending the message. +7. When you want the AI to answer in a shared conversation, use an explicit AI target from the `@` menu instead of assuming every shared message calls the AI. +8. When a non-owner participant generates a downloadable file, wait for the approval card. The conversation owner approves files in personal shared conversations; a group **Owner**, **Admin**, or **Document Manager** approves files in group shared conversations. The approval gate applies to downloadable deliverables such as CSV, XLSX, DOCX, PDF, JSON, and XML; generated images and charts are not held for approval. + +{% include media.html src="guides/collaborate-in-a-conversation-file-approval.png" + alt="Screenshot showing a pending generated file approval card with Approve and Deny actions." + title="Approve a shared generated file" + capture="Capture the inline file approval card in a shared conversation. Show Approve and Deny, and redact file names if needed." %} + +9. Approve the file to make it downloadable, or deny it if it should not be released. Requesters cannot approve their own generated files. + +## Verify it worked + +The conversation appears with a shared or collaborative indicator, the participant list shows accepted or pending members, and new shared messages appear for each accepted participant. A completed `@` mention appears in the message text. A participant-generated file stays unavailable while approval is pending and becomes downloadable only after an authorized approver approves it. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **Add participants** is missing | Collaborative conversations are disabled, the conversation type is not supported, or your role cannot manage members | Ask an admin to confirm `enable_collaborative_conversations`, then check your role from conversation **Details**. | +| The invited person cannot post | Their invite is still pending | Have them accept the invite before replying. | +| **Tab** does not complete the `@` text | No suggestion is highlighted, the menu says no matches, or **Shift+Tab** was pressed | Keep typing until a match appears, use the arrow keys to highlight it, then press **Tab** or **Enter**. | +| A generated file cannot be downloaded | The file is waiting for approval, was denied, or expired before approval | Ask the conversation owner or eligible group approver to review the inline approval card or notification. | + +## Related + +- [Fork a conversation]({{ '/guides/fork-a-conversation/' | relative_url }}) +- [Upload documents in chat]({{ '/guides/upload-documents-in-chat/' | relative_url }}) +- [Chat controls reference]({{ '/reference/chat-controls/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/how-to/admin_ui_settings.md b/docs/guides/configure-branding-and-support-settings.md similarity index 98% rename from docs/how-to/admin_ui_settings.md rename to docs/guides/configure-branding-and-support-settings.md index a0d5080fc..75fe66c8b 100644 --- a/docs/how-to/admin_ui_settings.md +++ b/docs/guides/configure-branding-and-support-settings.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Configure Branding, Home Page, and Support Settings" -permalink: /how-to/admin_ui_settings/ +permalink: /guides/configure-branding-and-support-settings/ menubar: docs_menu accent: teal eyebrow: "Admin How-To" @@ -36,6 +36,9 @@ hero_links: - label: "API reference" url: /reference/api_reference/ style: secondary +section: "Guides" +redirect_from: + - /how-to/admin_ui_settings/ --- Use this guide when an admin needs to make the deployed application feel like the organization's product: name it, upload the logo, explain the home page, show data classification, expose help links, and decide which operational endpoints should be visible. diff --git a/docs/guides/create-a-file-sync.md b/docs/guides/create-a-file-sync.md new file mode 100644 index 000000000..286491214 --- /dev/null +++ b/docs/guides/create-a-file-sync.md @@ -0,0 +1,69 @@ +--- +layout: page +title: "Create a file sync" +description: "Connect an external file source so SimpleChat can import workspace documents." +section: "Guides" +audience: user +--- + +## What this does + +File Sync creates a source that imports files from an approved external location into a personal, group, or public workspace. This guide chooses a source type, connection, filters, tags, delete policy, and optional schedule. + +{% include media.html type="video" + title="Create a file sync walkthrough" + poster="video-posters/guide-create-a-file-sync.png" + capture="Recording planned. Show create a file sync end to end and explain why this task helps a user." %} + +## Why you would use this + +Use File Sync when important documents already live in a share or storage container and should stay current without repeated uploads. It replaces manual drag-and-drop for stable repositories; it is the wrong choice for temporary files, unapproved repositories, or sources too broad for the workspace. + +## Before you start + +- Admins must enable `enable_file_sync` and the target scope toggle: `enable_file_sync_personal`, `enable_file_sync_group`, or `enable_file_sync_public`; see [File Sync settings]({{ '/admin/knowledge/' | relative_url }}). +- The workspace scope must be enabled on [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- You need permission to manage sync sources and a readable credential or **Reusable identity**. + +## Steps + +1. Open the workspace that should receive synced documents. +2. Choose **Sync** from **Section** or the workspace tabs. +3. Select **Add Sync Source**. + +{% include media.html src="guides/create-a-file-sync-step-3.png" + alt="Screenshot showing create a file sync step 3." + title="Create a file sync step 3" + capture="Capture the create a file sync task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. On **1. Source Type**, choose **SMB Share**, **Azure Files**, **Azure Blob Storage**, or another available card. +5. Select **Configure Source**. +6. In **General**, enter **Source name** and connection fields such as **UNC path**, **File service URL**, **Share name**, **Container name**, or **Blob prefix**. +7. In **Identity and Authentication**, choose **Reusable identity** or source-local credentials. +8. In **Selection, Subfolders, and Filters**, choose paths, subfolders, patterns, and file types. +9. In **Tags**, choose fixed tags; in **Sync Schedule**, enable **Scheduled sync** only when automatic refresh is wanted. + +{% include media.html src="guides/create-a-file-sync-step-9.png" + alt="Screenshot showing create a file sync step 9." + title="Create a file sync step 9" + capture="Capture the create a file sync task at this step in SimpleChat with realistic sample data and redact secrets." %} + +10. Select **Test Connection**, then **Add Source** or **Save Source**. + +## Verify it worked + +The source appears in the Sync list. Imported files later appear on the workspace **Documents** tab with fixed or folder tags. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| No source types are visible | Admins have not made a source type visible | Ask an admin to review File Sync source type visibility. | +| **Include subfolders** is disabled | Recursive sources are disabled by admin policy | Sync a top-level path or ask whether recursion is allowed. | +| **Test Connection** fails | The credential, path, container, or share is wrong | Correct the fields and retest. | + +## Related + +- [Create and manage tags]({{ '/guides/create-and-manage-tags/' | relative_url }}) +- [Create a workflow]({{ '/guides/create-a-workflow/' | relative_url }}) +- [File Sync settings]({{ '/admin/knowledge/' | relative_url }}) diff --git a/docs/guides/create-a-workflow.md b/docs/guides/create-a-workflow.md new file mode 100644 index 000000000..17d7ff43d --- /dev/null +++ b/docs/guides/create-a-workflow.md @@ -0,0 +1,67 @@ +--- +layout: page +title: "Create a workflow" +description: "Save a repeatable multi-step task that can run manually or on a schedule." +section: "Guides" +audience: user +--- + +## What this does + +A workflow is an ordered set of instruction tasks that runs with a selected model or agent. This guide creates a workflow with runner, trigger, tasks, reliability choices, and review. + +{% include media.html type="video" + title="Create a workflow walkthrough" + poster="video-posters/guide-create-a-workflow.png" + capture="Recording planned. Show create a workflow end to end and explain why this task helps a user." %} + +## Why you would use this + +Use workflows for repeatable work where sequence matters: weekly document checks, multi-stage summaries, or group processes that should run the same way each time. It replaces copying a checklist into chat; it is not ideal for exploratory conversations that need a human decision after every answer. + +## Before you start + +- Personal workflows require `allow_user_workflows`; group workflows require `allow_group_workflows`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- Admins may require `require_member_of_workflow_user` before users can create workflows. +- If tasks use documents, upload them or configure File Sync first. + +## Steps + +1. Open **Personal Workspace** or a **Group Workspace**. +2. Choose **Workflows** from **Section** or the tab row. +3. Select **New Personal Workflow** or **New Group Workflow**. + +{% include media.html src="guides/create-a-workflow-step-3.png" + alt="Screenshot showing create a workflow step 3." + title="Create a workflow step 3" + capture="Capture the create a workflow task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. In **General**, enter a name, description, and default runner. +5. In **Trigger**, choose manual execution or a scheduled interval. +6. In **Tasks**, write the first task instructions and add more tasks with **Add Task**. +7. For each task, decide whether it inherits the runner or uses a specific **Direct Model** or **Agent**. +8. Optionally set a document action such as **Search**, **Analyze**, or **Compare**. + +{% include media.html src="guides/create-a-workflow-step-8.png" + alt="Screenshot showing create a workflow step 8." + title="Create a workflow step 8" + capture="Capture the create a workflow task at this step in SimpleChat with realistic sample data and redact secrets." %} + +9. In **Reliability**, choose retry and failure behavior, then review and save. + +## Verify it worked + +The workflow appears in the Workflows table with **Name**, **Runner**, **Trigger**, **Last Run**, and **Actions** columns. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The Workflows section is missing | Workflows are disabled for the scope | Ask an admin to enable personal or group workflows. | +| No agents are available as runners | No authorized agents exist for this workspace | Create an agent first or use a direct model runner. | + +## Related + +- [Trigger a workflow]({{ '/guides/trigger-a-workflow/' | relative_url }}) +- [Create an agent]({{ '/guides/create-an-agent/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/how-to/create_agents.md b/docs/guides/create-agents.md similarity index 91% rename from docs/how-to/create_agents.md rename to docs/guides/create-agents.md index 37a2c9ea9..393167a88 100644 --- a/docs/how-to/create_agents.md +++ b/docs/guides/create-agents.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Create Agents" -permalink: /how-to/create_agents/ +permalink: /guides/create-agents/ menubar: docs_menu accent: teal eyebrow: "How-To Guide" @@ -16,11 +16,14 @@ hero_pills: - Validate before publishing hero_links: - label: "First agent tutorial" - url: /tutorials/first_agent/ + url: /guides/create-your-first-agent/ style: primary - label: "Admin configuration" url: /admin_configuration/ style: secondary +section: "Guides" +redirect_from: + - /how-to/create_agents/ --- Create agents around a specific job to be done, not around a vague persona. The most reliable agents in Simple Chat have narrow instructions, deliberate tool access, and a test set that proves they behave the way you expect. @@ -83,7 +86,7 @@ Create agents around a specific job to be done, not around a vague persona. The ## Related guides -- [Create Your First Agent](../tutorials/first_agent.md) -- [Admin Configuration Reference](../reference/admin_configuration.md) +- [Create Your First Agent]({{ '/guides/create-your-first-agent/' | relative_url }}) +- [Admin Configuration Reference]({{ '/admin_configuration/' | relative_url }}) - [API Reference](../reference/api_reference.md) -- [ServiceNow Agent Examples](./agents/ServiceNow/) +- [ServiceNow Agent Examples]({{ '/guides/servicenow/' | relative_url }}) diff --git a/docs/guides/create-an-action.md b/docs/guides/create-an-action.md new file mode 100644 index 000000000..48bc037bb --- /dev/null +++ b/docs/guides/create-an-action.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Create an action" +description: "Create a reusable tool that an agent can call when chat alone is not enough." +section: "Guides" +audience: user +--- + +## What this does + +An action is a governed tool definition that lets an agent call an API, database, built-in utility, file source, or MCP server. This guide creates an action in a workspace and leaves it ready to attach to an agent. + +{% include media.html type="video" + title="Create an action walkthrough" + poster="video-posters/guide-create-an-action.png" + capture="Recording planned. Show create an action end to end and explain why this task helps a user." %} + +## Why you would use this + +Use an action when a model needs to do more than write an answer, such as query a read-only system or call an approved service. It replaces manual lookups and pasted context, but it is the wrong choice for unrestricted network access, unapproved tools, or secrets that should not be stored in SimpleChat. + +## Before you start + +- An admin must enable `enable_semantic_kernel` and allow actions with `allow_user_plugins` or `allow_group_plugins`; see [Agents settings]({{ '/admin/agents-actions/' | relative_url }}). +- Workspace actions require `per_user_semantic_kernel` when actions are personal or group scoped. +- Have the OpenAPI file, endpoint, credential, reusable identity, or MCP server details required by the action type. + +## Steps + +1. Open **Personal Workspace** or the target **Group Workspace**. +2. Choose **Your Actions** or the **Actions** tab. +3. Select **Add Action** or the workspace new action button. + +{% include media.html src="guides/create-an-action-step-3.png" + alt="Screenshot showing create an action step 3." + title="Create an action step 3" + capture="Capture the create an action task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. On **Select Action Type**, search for the type you need, such as **OpenAPI**, **MCP**, **Log Analytics**, or another enabled card. +5. Enter **Display Name**, review **Name**, and write a **Description** that tells agent authors when to use it. +6. Complete the type-specific configuration, including **OpenAPI Specification File**, **Base URL**, **Authentication Type**, **Reusable Identity**, or MCP discovery fields when shown. + +{% include media.html src="guides/create-an-action-step-6.png" + alt="Screenshot showing create an action step 6." + title="Create an action step 6" + capture="Capture the create an action task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Use **Test Connection** when available, then save the action. + +## Verify it worked + +The action appears in the Actions list or card view. When you create or edit an agent, it is available on the agent **Actions** step. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The Actions tab is missing | Actions are disabled for your workspace or account | Ask an admin to enable `enable_semantic_kernel` and the matching action scope. | +| **Test Connection** fails | The endpoint, identity, or credential cannot reach the target service | Fix the connection details and test again before saving. | + +## Related + +- [Create an agent]({{ '/guides/create-an-agent/' | relative_url }}) +- [Create an agent with actions]({{ '/guides/create-an-agent-with-actions/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/guides/create-an-agent-with-actions.md b/docs/guides/create-an-agent-with-actions.md new file mode 100644 index 000000000..021a3936e --- /dev/null +++ b/docs/guides/create-an-agent-with-actions.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Create an agent with actions" +description: "Attach approved actions to an agent so it can use tools while answering." +section: "Guides" +audience: user +--- + +## What this does + +This guide connects an existing action to an agent and narrows what the agent can do with it. The result is an assistant with both instructions and approved tool access. + +{% include media.html type="video" + title="Create an agent with actions walkthrough" + poster="video-posters/guide-create-an-agent-with-actions.png" + capture="Recording planned. Show create an agent with actions end to end and explain why this task helps a user." %} + +## Why you would use this + +Binding an action to an agent turns a broad tool into a safer task assistant. Use it for jobs like querying a known reporting source or calling an approved API; avoid it when the action exposes more access than the agent's job requires. + +## Before you start + +- `enable_semantic_kernel` must be on, and the workspace must allow agents and actions; see [Agents settings]({{ '/admin/agents-actions/' | relative_url }}). +- Create and test the action first. +- Decide which capabilities the agent may use before attaching the action. + +## Steps + +1. Open the workspace that owns the agent. +2. Open **Your Agents** or **Agents** and create or edit an agent. +3. Complete **Basic Information** and **Model & Connection**. + +{% include media.html src="guides/create-an-agent-with-actions-step-3.png" + alt="Screenshot showing create an agent with actions step 3." + title="Create an agent with actions step 3" + capture="Capture the create an agent with actions task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. On **Actions**, select the action you want the agent to use. +5. If capability controls appear, enable only the operations this agent needs. +6. In **Instructions**, explain when to use the action and when to answer without it. + +{% include media.html src="guides/create-an-agent-with-actions-step-6.png" + alt="Screenshot showing create an agent with actions step 6." + title="Create an agent with actions step 6" + capture="Capture the create an agent with actions task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Use `#` references from **Selected Actions & Knowledge** when helpful, then save the agent. + +## Verify it worked + +In chat, select the agent and ask for a task that requires the action. The response should follow the agent instructions and use the attached action only when appropriate. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The action is not listed | It is not in the same allowed scope or actions are disabled | Confirm the action exists in the workspace and governance allows it. | +| A Foundry agent cannot attach actions | Foundry agents cannot attach local SimpleChat actions | Use a **Local** agent for SimpleChat actions. | + +## Related + +- [Create an action]({{ '/guides/create-an-action/' | relative_url }}) +- [Create an agent]({{ '/guides/create-an-agent/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/guides/create-an-agent.md b/docs/guides/create-an-agent.md new file mode 100644 index 000000000..8215935e4 --- /dev/null +++ b/docs/guides/create-an-agent.md @@ -0,0 +1,67 @@ +--- +layout: page +title: "Create an agent" +description: "Build a reusable assistant with a clear role, model, knowledge, and instructions." +section: "Guides" +audience: user +--- + +## What this does + +An agent is a specialized assistant that keeps the same role, instructions, knowledge choices, and optional tools every time someone uses it. This guide creates a local workspace agent that can be selected from chat. + +{% include media.html type="video" + title="Create an agent walkthrough" + poster="video-posters/guide-create-an-agent.png" + capture="Recording planned. Show create an agent end to end and explain why this task helps a user." %} + +## Why you would use this + +Create an agent when a task benefits from consistent behavior, such as policy review, proposal help, research assistance, or workspace-specific expertise. It reduces prompt rewriting and makes approved instructions reusable; it is not needed for a one-time question or for bypassing document permissions. + +## Before you start + +- Admins must enable `enable_semantic_kernel`; workspace agents also require `per_user_semantic_kernel` and `allow_user_agents` or `allow_group_agents`; see [Agents settings]({{ '/admin/agents-actions/' | relative_url }}). +- At least one GPT model endpoint must be configured; see [AI Models]({{ '/admin/ai-models/' | relative_url }}). +- Upload and process workspace documents first if the agent should use assigned knowledge. + +## Steps + +1. Open **Personal Workspace** or the target **Group Workspace**. +2. Choose **Your Agents** or the **Agents** tab. +3. Select **New Agent**. + +{% include media.html src="guides/create-an-agent-step-3.png" + alt="Screenshot showing create an agent step 3." + title="Create an agent step 3" + capture="Capture the create an agent task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Choose **Local** in **Agent Type** unless an admin told you to use a Foundry option. +5. In **Basic Information**, enter **Display Name**, **Description**, optional **Tags**, and an icon. +6. In **Model & Connection**, select the model or connection for the workspace. +7. In **Assigned Knowledge**, enable **Restrict this agent to assigned workspace knowledge** only when the agent needs a defined document pool. +8. In **Instructions**, write **Agent Instructions** or use **Instruction Brief** and **Draft Instructions**. + +{% include media.html src="guides/create-an-agent-step-8.png" + alt="Screenshot showing create an agent step 8." + title="Create an agent step 8" + capture="Capture the create an agent task at this step in SimpleChat with realistic sample data and redact secrets." %} + +9. Review **Summary** and save the agent. + +## Verify it worked + +The agent appears in the Agents list. In chat, select **Agents**, open **Select an Agent**, and confirm your new agent is available. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **New Agent** is not shown | Personal or group agents are disabled by governance | Ask an admin to enable the relevant agent scope. | +| The agent cannot use expected documents | Assigned knowledge is empty or documents are not processed | Add source workspaces, tags, or specific documents and confirm **Active Documents**. | + +## Related + +- [Create an action]({{ '/guides/create-an-action/' | relative_url }}) +- [Create an agent with actions]({{ '/guides/create-an-agent-with-actions/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/guides/create-and-manage-tags.md b/docs/guides/create-and-manage-tags.md new file mode 100644 index 000000000..595474150 --- /dev/null +++ b/docs/guides/create-and-manage-tags.md @@ -0,0 +1,66 @@ +--- +layout: page +title: "Create and manage tags" +description: "Create colored workspace tags and apply them to documents." +section: "Guides" +audience: user +--- + +## What this does + +Tags are workspace labels that organize documents and create filtered source sets for chat. This guide creates a tag and applies it to documents. + +{% include media.html type="video" + title="Create and manage tags walkthrough" + poster="video-posters/guide-create-and-manage-tags.png" + capture="Recording planned. Show create and manage tags end to end and explain why this task helps a user." %} + +## Why you would use this + +Use tags when folders and file names are not enough: a document can belong to a project, phase, customer, or review set without being moved. Tags replace brittle naming conventions and long manual selections, but they are not access control. + +## Before you start + +- The workspace type must be enabled with `enable_user_workspace`, `enable_group_workspaces`, or `enable_public_workspaces`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- You need permission to manage documents in the workspace. +- Upload or sync at least one document if you want to assign the tag immediately. + +## Steps + +1. Open **Personal Workspace**, **Group Workspace**, or **Public Workspaces**. +2. Open the **Documents** tab. +3. Select **Manage Tags**. + +{% include media.html src="guides/create-and-manage-tags-step-3.png" + alt="Screenshot showing create and manage tags step 3." + title="Create and manage tags step 3" + capture="Capture the create and manage tags task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. In **Manage Workspace Tags**, enter a name in **Tag name (lowercase, alphanumeric)**. +5. Pick a **Color** and select **Add**. +6. To tag several documents, select **Multi-select** and choose documents. +7. Use **Tag Assignment** or **Tag Selected**. + +{% include media.html src="guides/create-and-manage-tags-step-7.png" + alt="Screenshot showing create and manage tags step 7." + title="Create and manage tags step 7" + capture="Capture the create and manage tags task at this step in SimpleChat with realistic sample data and redact secrets." %} + +8. Choose **Add Tags (append to existing)**, **Remove Tags (remove specific tags)**, or **Replace All Tags (overwrite existing)**, then select **Apply to Selected Documents**. + +## Verify it worked + +The tag appears in **Existing Tags** with a count after assignment. **Folders**, **Folders + Cards**, and **Filter by Tags** can use it. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The tag name changes after typing | Tag names are normalized to allowed characters | Use lowercase letters, numbers, hyphens, or underscores. | +| A tag count is zero | The tag exists but is not assigned | Use **Multi-select** and tag assignment to apply it. | + +## Related + +- [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) +- [Use tags on conversations]({{ '/guides/use-tags-on-conversations/' | relative_url }}) +- [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) diff --git a/docs/how-to/custom_pages.md b/docs/guides/create-custom-pages.md similarity index 76% rename from docs/how-to/custom_pages.md rename to docs/guides/create-custom-pages.md index 6372f85c2..949a3fb19 100644 --- a/docs/how-to/custom_pages.md +++ b/docs/guides/create-custom-pages.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Create Custom Pages" -permalink: /how-to/custom_pages/ +permalink: /guides/create-custom-pages/ menubar: docs_menu accent: teal eyebrow: "Developer How-To" @@ -16,18 +16,21 @@ hero_pills: - Python-backed Jinja and API pages hero_links: - label: "Canonical Markdown" - url: ../../application/single_app/docs/how-to/custom_pages.md + url: https://github.com/microsoft/simplechat/blob/main/application/single_app/docs/how-to/custom_pages.md style: primary - label: "Feature explanation" - url: /explanation/features/CUSTOM_PAGES/ + url: /admin/appearance/ style: secondary +section: "Guides" +redirect_from: + - /how-to/custom_pages/ --- # Create Custom Pages The canonical Custom Pages developer guide is maintained inside the application at: -[application/single_app/docs/how-to/custom_pages.md](../../application/single_app/docs/how-to/custom_pages.md) +[application/single_app/docs/how-to/custom_pages.md](https://github.com/microsoft/simplechat/blob/main/application/single_app/docs/how-to/custom_pages.md) Admin Settings > Custom Pages > Developer Guide renders that same Markdown in-app. Keeping the maintained copy inside `application/single_app` ensures the guide ships with non-container deployments, ACR-built containers, and any deployment process that sends only the app artifact. diff --git a/docs/tutorials/first_agent.md b/docs/guides/create-your-first-agent.md similarity index 92% rename from docs/tutorials/first_agent.md rename to docs/guides/create-your-first-agent.md index 6a2e7be6a..0be56f48d 100644 --- a/docs/tutorials/first_agent.md +++ b/docs/guides/create-your-first-agent.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Create Your First Agent" -permalink: /tutorials/first_agent/ +permalink: /guides/create-your-first-agent/ menubar: docs_menu accent: violet eyebrow: "Tutorial 2" @@ -16,7 +16,7 @@ hero_pills: - Test and refine behavior hero_links: - label: Tutorial index - url: /tutorials/ + url: /guides/ style: primary - label: Review admin configuration url: /admin_configuration/ @@ -25,10 +25,13 @@ show_nav: true nav_links: prev: title: Getting Started with Simple Chat - url: /tutorials/getting_started/ + url: /guides/getting-started/ next: title: Uploading and Managing Documents - url: /tutorials/uploading_documents/ + url: /guides/upload-and-manage-documents/ +section: "Guides" +redirect_from: + - /tutorials/first_agent/ --- Agents are most useful when they are specific. This tutorial shows how to define that specificity in instructions, data access, and testing rather than relying on a generic assistant prompt. @@ -236,10 +239,10 @@ Congratulations! You've successfully: ## Next Steps Now that you have your first agent: -- [Upload and organize more documents](uploading_documents) to expand agent knowledge -- [Classify documents](classifying_documents) to help agents find relevant information +- [Upload and organize more documents]({{ '/guides/upload-and-manage-documents/' | relative_url }}) to expand agent knowledge +- [Classify documents]({{ '/guides/classify-documents/' | relative_url }}) to help agents find relevant information - Create additional agents for different use cases -- Explore [advanced agent configuration](../how-to/create_agents) options +- Explore [advanced agent configuration]({{ '/guides/create-agents/' | relative_url }}) options ## Troubleshooting @@ -260,4 +263,4 @@ Now that you have your first agent: --- -*Next: [Uploading Documents](uploading_documents) →* +*Next: [Uploading Documents]({{ '/guides/upload-and-manage-documents/' | relative_url }}) →* diff --git a/docs/how-to/docker_customization.md b/docs/guides/docker-customization.md similarity index 98% rename from docs/how-to/docker_customization.md rename to docs/guides/docker-customization.md index 18f641c65..a569b2189 100644 --- a/docs/how-to/docker_customization.md +++ b/docs/guides/docker-customization.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Docker Customization" -permalink: /how-to/docker_customization/ +permalink: /guides/docker-customization/ menubar: docs_menu accent: slate eyebrow: "How-To Guide" @@ -19,8 +19,11 @@ hero_links: url: /reference/deploy/azd-cli_deploy/ style: primary - label: "Special setup scenarios" - url: /setup_instructions_special/ + url: /deploy/special-scenarios/ style: secondary +section: "Guides" +redirect_from: + - /how-to/docker_customization/ --- Use Docker customization when the default container image is correct for the application but not quite correct for the network or package-install environment you need to run inside. diff --git a/docs/how-to/enterprise_networking.md b/docs/guides/enterprise-networking.md similarity index 98% rename from docs/how-to/enterprise_networking.md rename to docs/guides/enterprise-networking.md index 72e8c52e9..4a0d6fddd 100644 --- a/docs/how-to/enterprise_networking.md +++ b/docs/guides/enterprise-networking.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "Enterprise Networking" -permalink: /how-to/enterprise_networking/ +permalink: /guides/enterprise-networking/ menubar: docs_menu accent: teal eyebrow: "How-To Guide" description: "Use private endpoints, VNets, and private DNS to move Simple Chat into an enterprise network boundary without losing deployment or service connectivity." hero_icons: ["bi-diagram-3", "bi-shield-lock", "bi-hdd-network"] hero_pills: ["Private endpoints across services", "DNS and route-all matter", "Deployment runner connectivity first"] -hero_links: [{ label: "Special setup scenarios", url: "/setup_instructions_special/", style: "primary" }, { label: "Managed identity guide", url: "/how-to/use_managed_identity/", style: "secondary" }] +hero_links: [{ label: "Special setup scenarios", url: "/deploy/special-scenarios/", style: "primary" }, { label: "Managed identity guide", url: "/guides/use-managed-identity/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/enterprise_networking/ --- Enterprise networking changes the deployment story as much as the runtime story. The main job is to keep the deployment runner, App Service, and Azure dependencies all able to reach one another while you progressively close off public access. diff --git a/docs/guides/export-a-conversation.md b/docs/guides/export-a-conversation.md new file mode 100644 index 000000000..296b20605 --- /dev/null +++ b/docs/guides/export-a-conversation.md @@ -0,0 +1,66 @@ +--- +layout: page +title: "Export a conversation" +description: "Download one or more conversations as JSON, Markdown, or PDF." +section: "Guides" +audience: user +--- + +## What this does + +The export wizard downloads selected conversations in a structured or readable format. This guide chooses conversations, format, packaging, optional AI-generated intro summary, and download. + +{% include media.html type="video" + title="Export a conversation walkthrough" + poster="video-posters/guide-export-a-conversation.png" + capture="Recording planned. Show export a conversation end to end and explain why this task helps a user." %} + +## Why you would use this + +Export conversations when you need an audit copy, handoff, or transcript for documentation. JSON is best for structured analysis, Markdown is easiest to edit, and PDF is best for print-ready sharing. Do not export content your organization does not allow you to remove from the app. + +## Before you start + +- You must be signed in and able to see the conversations you want to export. +- A chat model must be available if you want an AI-generated intro summary. +- If retention or archiving matters, review `enable_conversation_archiving` on [Safety settings]({{ '/admin/security/' | relative_url }}). + +## Steps + +1. Open **Chat**. +2. Select one or more conversations in the conversation list. +3. Select **Export selected conversations**. + +{% include media.html src="guides/export-a-conversation-step-3.png" + alt="Screenshot showing export a conversation step 3." + title="Export a conversation step 3" + capture="Capture the export a conversation task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. In **Export Conversations**, review the selected conversations and remove any unwanted item. +5. Choose **JSON**, **Markdown**, or **PDF**. +6. Choose **Single File** or **ZIP Archive**. +7. Optionally enable **Include AI-generated intro summary** and choose a **Summary model**. + +{% include media.html src="guides/export-a-conversation-step-7.png" + alt="Screenshot showing export a conversation step 7." + title="Export a conversation step 7" + capture="Capture the export a conversation task at this step in SimpleChat with realistic sample data and redact secrets." %} + +8. On **Ready to Export**, select **Download**. + +## Verify it worked + +A `.json`, `.md`, `.pdf`, or `.zip` file downloads. Open it and confirm it contains the expected titles and messages. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The wizard says no conversations are selected | No conversation is selected | Select at least one conversation first. | +| The downloaded format is unexpected | Format and packaging were chosen separately | Run the wizard again and verify both choices. | + +## Related + +- [Use tags on conversations]({{ '/guides/use-tags-on-conversations/' | relative_url }}) +- [Fork a conversation]({{ '/guides/fork-a-conversation/' | relative_url }}) +- [Safety settings]({{ '/admin/security/' | relative_url }}) diff --git a/docs/guides/fork-a-conversation.md b/docs/guides/fork-a-conversation.md new file mode 100644 index 000000000..8d80cea99 --- /dev/null +++ b/docs/guides/fork-a-conversation.md @@ -0,0 +1,63 @@ +--- +layout: page +title: "Fork a conversation" +description: "Branch an existing chat so you can explore a different direction without changing the original thread." +section: "Guides" +audience: user +--- + +## What this does + +Forking creates a new conversation from an existing point in a chat. The original thread remains available while the fork continues independently. + +{% include media.html type="video" + title="Fork a conversation walkthrough" + poster="video-posters/guide-fork-a-conversation.png" + capture="Recording planned. Show fork a conversation end to end and explain why this task helps a user." %} + +## Why you would use this + +Fork a conversation when one path should continue as-is and another needs a different assumption, audience, model, or source set. It replaces copying previous context into a new chat by hand; it is unnecessary for a simple follow-up that belongs in the same thread. + +## Before you start + +- Collaborative conversation behavior depends on `enable_collaborative_conversations`. +- You must be able to open the conversation you want to fork. +- If workspace grounding was used, you still need access to the related documents. + +## Steps + +1. Open **Chat** and select the conversation to branch. +2. Open the message actions menu at the point you want to branch from. +3. Select **Fork conversation**. + +{% include media.html src="guides/fork-a-conversation-step-3.png" + alt="Screenshot showing fork a conversation step 3." + title="Fork a conversation step 3" + capture="Capture the fork a conversation task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. In the confirmation modal, select **Fork conversation** again. +5. Continue in the new conversation with the alternate direction. + +{% include media.html src="guides/fork-a-conversation-step-5.png" + alt="Screenshot showing fork a conversation step 5." + title="Fork a conversation step 5" + capture="Capture the fork a conversation task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Rename the forked conversation if the title does not clearly describe the new path. + +## Verify it worked + +A new conversation opens or appears in the list. The original conversation remains unchanged. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **Fork conversation** is not available | That message or conversation type does not expose the action | Start a new chat manually or export the transcript if you need a copy. | +| The fork cannot use prior documents | Your document access changed | Re-select accessible documents with **Workspaces**. | + +## Related + +- [Export a conversation]({{ '/guides/export-a-conversation/' | relative_url }}) +- [Lock workspace scope in chat]({{ '/guides/lock-workspace-scope/' | relative_url }}) diff --git a/docs/guides/generate-images.md b/docs/guides/generate-images.md new file mode 100644 index 000000000..ef064c582 --- /dev/null +++ b/docs/guides/generate-images.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Generate images" +description: "Use the chat Image control to request AI-generated images." +section: "Guides" +audience: user +--- + +## What this does + +**Image** switches the chat composer into image-generation mode for the current prompt. While it is active, other source controls are disabled so the request stays focused on image generation. + +{% include media.html type="video" + title="Generate images walkthrough" + poster="video-posters/guide-generate-images.png" + capture="Recording planned. Show generate images end to end and explain why this task helps a user." %} + +## Why you would use this + +Use image generation for visual concepts, drafts, illustrations, and creative exploration where the output should be an image. It replaces leaving the app for a separate image tool, but it is wrong for grounded document analysis, web research, or prompts that require private source files. + +## Before you start + +- Admins must enable `enable_image_generation`; see [AI Models]({{ '/admin/ai-models/' | relative_url }}). +- Tenants using APIM may also configure `enable_image_gen_apim`. +- Prompts must comply with your organization's acceptable use policy. + +## Steps + +1. Open **Chat**. +2. Select **Image**. +3. Type a clear visual request with subject, style, setting, and constraints. + +{% include media.html src="guides/generate-images-step-3.png" + alt="Screenshot showing generate images step 3." + title="Generate images step 3" + capture="Capture the generate images task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Select **Send Message**. +5. Review the generated image output. + +{% include media.html src="guides/generate-images-step-5.png" + alt="Screenshot showing generate images step 5." + title="Generate images step 5" + capture="Capture the generate images task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Turn **Image** off before returning to normal chat, web search, URL review, file upload, or workspace grounding. + +## Verify it worked + +The conversation contains generated image output rather than a text-only answer. Other source controls return when **Image** is off. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **Image** is missing | Image generation is disabled | Ask an admin to enable `enable_image_generation`. | +| Source controls are disabled | Image mode intentionally disables them | Turn **Image** off. | + +## Related + +- [Use web search]({{ '/guides/use-web-search/' | relative_url }}) +- [Upload documents in chat]({{ '/guides/upload-documents-in-chat/' | relative_url }}) +- [AI Models]({{ '/admin/ai-models/' | relative_url }}) diff --git a/docs/tutorials/getting_started.md b/docs/guides/getting-started.md similarity index 86% rename from docs/tutorials/getting_started.md rename to docs/guides/getting-started.md index 2b6668e2d..92a7e1f19 100644 --- a/docs/tutorials/getting_started.md +++ b/docs/guides/getting-started.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Getting Started with Simple Chat" -permalink: /tutorials/getting_started/ +permalink: /guides/getting-started/ menubar: docs_menu accent: blue eyebrow: "Tutorial 1" @@ -16,16 +16,19 @@ hero_pills: - First grounded conversation hero_links: - label: Deployment reference - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: Tutorial index - url: /tutorials/ + url: /guides/ style: secondary show_nav: true nav_links: next: title: Create Your First Agent - url: /tutorials/first_agent/ + url: /guides/create-your-first-agent/ +section: "Guides" +redirect_from: + - /tutorials/getting_started/ --- This tutorial is the shortest useful path into the product. You will deploy Simple Chat, wire the essentials, add one document, and prove the retrieval loop works. @@ -154,11 +157,11 @@ Congratulations! You've successfully: Now that you're up and running, explore more features: -- [Create Your First Agent](first_agent) - Build a specialized AI assistant -- [Upload More Documents](uploading_documents) - Build a larger knowledge base -- [Document Classification](classifying_documents) - Organize your content +- [Create Your First Agent]({{ '/guides/create-your-first-agent/' | relative_url }}) - Build a specialized AI assistant +- [Upload More Documents]({{ '/guides/upload-and-manage-documents/' | relative_url }}) - Build a larger knowledge base +- [Document Classification]({{ '/guides/classify-documents/' | relative_url }}) - Organize your content -Or dive into specific tasks with our [how-to guides](../how-to/). +Or dive into specific tasks with our [how-to guides]({{ '/guides/' | relative_url }}). ## Troubleshooting @@ -176,8 +179,8 @@ Or dive into specific tasks with our [how-to guides](../how-to/). - Try more specific questions - Check your embeddings configuration -For more help, see our [FAQ](../reference/faqs) or [how-to guides](../how-to/). +For more help, see our [FAQ]({{ '/start/faqs/' | relative_url }}) or [how-to guides]({{ '/guides/' | relative_url }}). --- -*Next: [Create Your First Agent](first_agent) →* +*Next: [Create Your First Agent]({{ '/guides/create-your-first-agent/' | relative_url }}) →* diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 000000000..0fc543a70 --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,115 @@ +--- +layout: page +title: "Guides" +description: "Task-oriented guides for getting work done in SimpleChat." +section: "Guides" +audience: user +--- + +## Start with a task + +These guides focus on what you are trying to accomplish, why the task is useful, and what to verify when you are done. They now include the former tutorial and how-to content so every task guide lives under one URL space. + +Use this section when you want momentum first: each page is a practical path through one capability, with enough context to be useful and not so much detail that onboarding stalls. If you need conceptual background, switch to the explanation pages. If you need exact settings or route details, switch to the reference pages. + +## Getting started + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Getting started with Simple Chat]({{ '/guides/getting-started/' | relative_url }}) | Deploy Simple Chat, configure the basics, upload a document, and run the first grounded conversation. | New users and admins | +| [What is Simple Chat]({{ '/start/about/' | relative_url }}) | Understand the platform, its major capabilities, and how teams use it. | Everyone | +| [Choose a deployment path]({{ '/start/deployment-options/' | relative_url }}) | Compare deployment options before you start installing or operating SimpleChat. | Admins | +| [FAQ]({{ '/start/faqs/' | relative_url }}) | Get quick answers to common setup, usage, and administration questions. | Everyone | + +## Admin journeys + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Deploy SimpleChat as an administrator]({{ '/guides/admin-deploy-simplechat/' | relative_url }}) | Choose a deployment path, prepare Azure resources, run the rollout, and verify first boot. | Admins | +| [Configure SimpleChat for the first time]({{ '/guides/admin-first-configuration/' | relative_url }}) | Walk Admin Settings in dependency order after a successful deployment. | Admins | +| [Update an existing SimpleChat deployment]({{ '/guides/admin-update-simplechat/' | relative_url }}) | Back up, choose the right upgrade path, validate, and plan rollback. | Admins | +| [Operate SimpleChat day to day]({{ '/guides/admin-operate-simplechat/' | relative_url }}) | Monitor, scale, review safety and feedback, manage data, and troubleshoot. | Admins | +| [Enable a SimpleChat capability safely]({{ '/guides/admin-enable-a-capability/' | relative_url }}) | Turn on optional features with dependencies, role checks, validation, and rollback. | Admins | +| [Configure branding and support settings]({{ '/guides/configure-branding-and-support-settings/' | relative_url }}) | Change branding, home page text, health checks, API docs, classification banners, Support, external links, or global system settings. | Admins | + +## Chat + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Upload documents in chat]({{ '/guides/upload-documents-in-chat/' | relative_url }}) | Attach a local file directly to the current conversation for grounded chat. | Users | +| [Upload and manage documents]({{ '/guides/upload-and-manage-documents/' | relative_url }}) | Put the right files into the right workspace, recognize processing states, and keep retrieval clean as content grows. | Users | +| [Add documents]({{ '/guides/add-documents/' | relative_url }}) | Add documents to SimpleChat workspaces and validate that they are available for grounded answers. | Users | +| [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) | Filter workspace documents by tag before asking grounded questions. | Users | +| [Use tags on conversations]({{ '/guides/use-tags-on-conversations/' | relative_url }}) | Organize conversation work by tagging the documents and document sets that drive the chat. | Users | +| [Export a conversation]({{ '/guides/export-a-conversation/' | relative_url }}) | Download one or more conversations as JSON, Markdown, or PDF. | Users | +| [Fork a conversation]({{ '/guides/fork-a-conversation/' | relative_url }}) | Branch an existing chat so you can explore a different direction without changing the original thread. | Users | +| [Collaborate in a conversation]({{ '/guides/collaborate-in-a-conversation/' | relative_url }}) | Invite others into a shared chat, use mentions, and approve participant-generated files. | Users | +| [Lock workspace scope in chat]({{ '/guides/lock-workspace-scope/' | relative_url }}) | Keep a conversation tied to the same selected workspaces after grounded answers. | Users | +| [Use deep research]({{ '/guides/use-deep-research/' | relative_url }}) | Ask chat to perform deeper evidence review for URLs and sources in a message. | Users | +| [Use web search]({{ '/guides/use-web-search/' | relative_url }}) | Send the current chat message to Bing web search when fresh web information is needed. | Users | +| [Review pasted URLs]({{ '/guides/review-pasted-urls/' | relative_url }}) | Use URL Access to review links included in the current chat message. | Users | +| [Generate images]({{ '/guides/generate-images/' | relative_url }}) | Use the chat Image control to request AI-generated images. | Users | + +## Documents and workspaces + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Create and manage tags]({{ '/guides/create-and-manage-tags/' | relative_url }}) | Create colored workspace tags and apply them to documents. | Workspace users | +| [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) | Connect an external file source so SimpleChat can import workspace documents. | Workspace users | +| [Manage group workspaces]({{ '/guides/manage-group-workspaces/' | relative_url }}) | Use group workspaces for shared documents, prompts, agents, actions, and workflows. | Workspace users | +| [Use public workspaces]({{ '/guides/use-public-workspaces/' | relative_url }}) | Browse and manage shared public workspace content when your tenant enables it. | Workspace users | +| [Review approval requests]({{ '/guides/review-approval-requests/' | relative_url }}) | Find, approve, or deny requests that need reviewer action. | Workspace users | +| [Classify documents]({{ '/guides/classify-documents/' | relative_url }}) | Set up document classification and use it to organize uploads, filter content, and improve team workflows. | Workspace users | + +## Agents and actions + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Create an action]({{ '/guides/create-an-action/' | relative_url }}) | Create a reusable tool that an agent can call when chat alone is not enough. | Agent builders | +| [Create an agent]({{ '/guides/create-an-agent/' | relative_url }}) | Build a reusable assistant with a clear role, model, knowledge, and instructions. | Agent builders | +| [Create agents]({{ '/guides/create-agents/' | relative_url }}) | Create reusable agents for common work and configure their instructions, model, and access. | Agent builders | +| [Create your first agent]({{ '/guides/create-your-first-agent/' | relative_url }}) | Define an agent role, tune instructions, connect knowledge sources, and test behavior. | Agent builders | +| [Create an agent with actions]({{ '/guides/create-an-agent-with-actions/' | relative_url }}) | Attach approved actions to an agent so it can use tools while answering. | Agent builders | + +## ServiceNow agents + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [ServiceNow guides]({{ '/guides/servicenow/' | relative_url }}) | Start with the ServiceNow agent guide set and choose the right integration path. | Agent builders | +| [ServiceNow integration]({{ '/guides/servicenow/integration/' | relative_url }}) | Connect SimpleChat agents with ServiceNow capabilities. | Agent builders | +| [ServiceNow OAuth setup]({{ '/guides/servicenow/oauth-setup/' | relative_url }}) | Configure OAuth 2.0 for ServiceNow-backed agent actions. | Agent builders | +| [ServiceNow asset management setup]({{ '/guides/servicenow/asset-management-setup/' | relative_url }}) | Configure asset management workflows for ServiceNow integration. | Agent builders | +| [ServiceNow two-agent setup]({{ '/guides/servicenow/two-agent-setup/' | relative_url }}) | Split ServiceNow work across two coordinated agents. | Agent builders | + +## Automation + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Create a workflow]({{ '/guides/create-a-workflow/' | relative_url }}) | Save a repeatable multi-step task that can run manually or on a schedule. | Workflow owners | +| [Trigger a workflow]({{ '/guides/trigger-a-workflow/' | relative_url }}) | Run a workflow now, schedule future runs, and inspect run activity. | Workflow owners | +| [Application workflows]({{ '/reference/application-workflows/' | relative_url }}) | Review the workflow model, concepts, and reference details. | Workflow owners | + +## Deployment operations + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Enterprise networking]({{ '/guides/enterprise-networking/' | relative_url }}) | Plan and configure networking for enterprise deployments. | Operators | +| [Scale SimpleChat on Azure]({{ '/guides/scale-simplechat-on-azure/' | relative_url }}) | Coordinate App Service, Cosmos DB, Search, and AI service scaling decisions. | Operators | +| [Upgrade paths]({{ '/guides/upgrade-paths/' | relative_url }}) | Choose a safe update path and validate the deployment after upgrade. | Operators | +| [Docker customization]({{ '/guides/docker-customization/' | relative_url }}) | Customize the container image or runtime when the default image is not enough. | Operators | +| [Model endpoint identity]({{ '/guides/model-endpoint-identity-setup/' | relative_url }}) | Configure managed identity access for model endpoints. | Operators | +| [Use managed identity]({{ '/guides/use-managed-identity/' | relative_url }}) | Replace stored secrets with managed identity where supported. | Operators | +| [Azure Speech managed identity setup]({{ '/guides/azure-speech-managed-identity-setup/' | relative_url }}) | Configure Azure Speech to use managed identity. | Operators | +| [Microsoft Teams app configuration]({{ '/guides/microsoft-teams-app/' | relative_url }}) | Run SimpleChat as a Microsoft Teams personal tab with Teams SSO. | Operators | +| [Special setup scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) | Handle special deployment scenarios that do not fit the standard setup path. | Operators | + +## Account and experience + +| Guide | What you will do | Audience | +| --- | --- | --- | +| [Update profile preferences]({{ '/guides/update-profile-preferences/' | relative_url }}) | Adjust personal SimpleChat preferences such as appearance, tutorials, notifications, memory, and retention. | Users | +| [Manage notifications]({{ '/guides/manage-notifications/' | relative_url }}) | Review, filter, search, and mark SimpleChat notifications as read. | Users | +| [Send feedback]({{ '/guides/send-feedback/' | relative_url }}) | Prepare a bug report or feature request email for your SimpleChat administrators. | Users | +| [Create custom pages]({{ '/guides/create-custom-pages/' | relative_url }}) | Create custom pages for tailored app experiences. | Admins | +| [Solution demos]({{ '/guides/solution-demos/' | relative_url }}) | Explore demonstration scenarios and examples for SimpleChat solutions. | Everyone | +| [External applications]({{ '/reference/external-applications/' | relative_url }}) | Understand how external applications integrate with SimpleChat. | Developers | diff --git a/docs/guides/lock-workspace-scope.md b/docs/guides/lock-workspace-scope.md new file mode 100644 index 000000000..a96f71be9 --- /dev/null +++ b/docs/guides/lock-workspace-scope.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Lock workspace scope in chat" +description: "Keep a conversation tied to the same selected workspaces after grounded answers." +section: "Guides" +audience: user +--- + +## What this does + +Workspace scope lock keeps a conversation associated with the workspace scope used for grounded chat. The chat header shows **Scope locked** when locked scope metadata exists. + +{% include media.html type="video" + title="Lock workspace scope in chat walkthrough" + poster="video-posters/guide-lock-workspace-scope.png" + capture="Recording planned. Show lock workspace scope in chat end to end and explain why this task helps a user." %} + +## Why you would use this + +Use scope lock when a conversation should stay inside one team's documents or one workspace context. It prevents accidental drift into other accessible sources; unlock or start a new chat when you intentionally need a different source set. + +## Before you start + +- Workspace chat must be available through `enable_user_workspace`, `enable_group_workspaces`, or `enable_public_workspaces`. +- Admins can enforce the lock with `enforce_workspace_scope_lock`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- You need access to the workspace documents you plan to use. + +## Steps + +1. Open **Chat**. +2. Select **Workspaces** to open **Grounded Search**. +3. Choose a **Scope**, then optionally choose **Tags** and **Document**. + +{% include media.html src="guides/lock-workspace-scope-step-3.png" + alt="Screenshot showing lock workspace scope in chat step 3." + title="Lock workspace scope in chat step 3" + capture="Capture the lock workspace scope in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Send a grounded message. +5. Look for **Scope locked** in the chat header or beside **Scope**. +6. Select the lock indicator to manage it; if enforced, the modal explains that it cannot be unlocked. + +{% include media.html src="guides/lock-workspace-scope-step-6.png" + alt="Screenshot showing lock workspace scope in chat step 6." + title="Lock workspace scope in chat step 6" + capture="Capture the lock workspace scope in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Start a new conversation when you need a different locked source set. + +## Verify it worked + +The conversation continues to show the locked scope, and conflicting workspace selections are prevented or require unlocking first. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| You cannot unlock the scope | `enforce_workspace_scope_lock` is enabled | Start another conversation for a different source set. | +| Scope choices are disabled | They conflict with the locked conversation scope | Use the lock modal or create a new chat. | + +## Related + +- [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) +- [Fork a conversation]({{ '/guides/fork-a-conversation/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/guides/manage-group-workspaces.md b/docs/guides/manage-group-workspaces.md new file mode 100644 index 000000000..bd461608a --- /dev/null +++ b/docs/guides/manage-group-workspaces.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Manage group workspaces" +description: "Use group workspaces for shared documents, prompts, agents, actions, and workflows." +section: "Guides" +audience: user +--- + +## What this does + +Group workspaces are shared spaces for team documents and reusable AI assets. This guide helps you find or create a group, select it, and use the available workspace tabs. + +{% include media.html type="video" + title="Manage group workspaces walkthrough" + poster="video-posters/guide-manage-group-workspaces.png" + capture="Recording planned. Show manage group workspaces end to end and explain why this task helps a user." %} + +## Why you would use this + +Use a group workspace when a team needs common source documents, prompts, agents, and workflows instead of each person maintaining copies. It keeps shared material in one place and lets owners manage contribution; use personal workspaces for private drafts. + +## Before you start + +- Admins must enable `enable_group_workspaces`; creating groups also depends on `enable_group_creation` and may require `require_member_of_create_group`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- You need group membership or permission to create/find groups from Profile. +- Tabs such as **Sync**, **Workflows**, **Agents**, and **Actions** require their own admin toggles. + +## Steps + +1. Open **Profile** and choose **Groups** when you need to create or find a group. +2. Use **Create Group** when permitted, or **Find Group** to locate an existing group. +3. Open **Group Workspaces**. + +{% include media.html src="guides/manage-group-workspaces-step-3.png" + alt="Screenshot showing manage group workspaces step 3." + title="Manage group workspaces step 3" + capture="Capture the manage group workspaces task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Select the active group from the group selector. +5. Use **Documents** to upload, filter, tag, and chat with shared files. + +{% include media.html src="guides/manage-group-workspaces-step-5.png" + alt="Screenshot showing manage group workspaces step 5." + title="Manage group workspaces step 5" + capture="Capture the manage group workspaces task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Use **Prompts**, **Sync**, **Workflows**, **Agents**, or **Actions** when those tabs are enabled and your role allows them. + +## Verify it worked + +The active group name appears, documents load for that group, and group-scoped assets are visible only when the selected group and your role allow them. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Users cannot create groups | Group creation is disabled or the `CreateGroups` role is required | Ask an admin to enable group creation or assign the role. | +| The Sync tab is missing | Group File Sync is disabled | Ask an admin to enable `enable_file_sync` and `enable_file_sync_group`. | + +## Related + +- [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) +- [Create an agent with actions]({{ '/guides/create-an-agent-with-actions/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/guides/manage-notifications.md b/docs/guides/manage-notifications.md new file mode 100644 index 000000000..4f01da24e --- /dev/null +++ b/docs/guides/manage-notifications.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Manage notifications" +description: "Review, filter, search, and mark SimpleChat notifications as read." +section: "Guides" +audience: user +--- + +## What this does + +The Notifications page lists app notifications, filters read and unread items, searches the list, and opens notification details. You can mark everything read or follow a link when one is provided. + +{% include media.html type="video" + title="Manage notifications walkthrough" + poster="video-posters/guide-manage-notifications.png" + capture="Recording planned. Show manage notifications end to end and explain why this task helps a user." %} + +## Why you would use this + +Use notifications to catch workflow activity, shared conversation events, approvals, or other in-app updates without scanning every workspace. They complement browser pop-ups, but they are not a permanent audit log. + +## Before you start + +- You must be signed in. +- Desktop conversation notifications require `enable_desktop_notifications` and browser permission; see [Safety settings]({{ '/admin/security/' | relative_url }}). +- Your personal desktop preference is saved from Profile. + +## Steps + +1. Open **Notifications**. +2. Use **All**, **Unread**, or **Read** to filter. +3. Choose **10 per page**, **20 per page**, or **50 per page**. + +{% include media.html src="guides/manage-notifications-step-3.png" + alt="Screenshot showing manage notifications step 3." + title="Manage notifications step 3" + capture="Capture the manage notifications task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Type in **Search notifications...** to narrow the list. +5. Select a notification to open the **Notification** modal. +6. Use **Go to Link** when it appears. + +{% include media.html src="guides/manage-notifications-step-6.png" + alt="Screenshot showing manage notifications step 6." + title="Manage notifications step 6" + capture="Capture the manage notifications task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Select **Mark All Read** after reviewing the queue, or refresh the list. + +## Verify it worked + +Unread notifications lose unread styling after being marked read. Filters and search update the list without changing unrelated notifications. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Desktop pop-ups do not show | Browser permission or profile preference is off | Enable browser notifications and save the profile setting. | +| Older items are hard to find | Search or status filters are narrowing the list | Clear search and choose **All**. | + +## Related + +- [Update profile preferences]({{ '/guides/update-profile-preferences/' | relative_url }}) +- [Trigger a workflow]({{ '/guides/trigger-a-workflow/' | relative_url }}) +- [Safety settings]({{ '/admin/security/' | relative_url }}) diff --git a/docs/how-to/teams_app.md b/docs/guides/microsoft-teams-app.md similarity index 94% rename from docs/how-to/teams_app.md rename to docs/guides/microsoft-teams-app.md index 453bfef6b..26ec2fea7 100644 --- a/docs/how-to/teams_app.md +++ b/docs/guides/microsoft-teams-app.md @@ -1,3 +1,13 @@ +--- +layout: page +title: "Microsoft Teams App Configuration" +permalink: /guides/microsoft-teams-app/ +section: "Guides" +redirect_from: + - /how-to/teams_app/ + - /how-to/teams_app.html +--- + # Microsoft Teams App Configuration This guide explains how to run SimpleChat as a Microsoft Teams personal tab with Teams Single Sign-On (SSO). @@ -100,7 +110,7 @@ CUSTOM_TEAMS_ORIGINS=["https://teams.example.cloud", "https://*.teams.example.cl ## Teams App Manifest -Use [application/teams_app/manifest.template.json](../../application/teams_app/manifest.template.json) as the starting point. +Use [application/teams_app/manifest.template.json](https://github.com/microsoft/simplechat/blob/main/application/teams_app/manifest.template.json) as the starting point. Replace these placeholders: diff --git a/docs/how-to/model_endpoint_identity_setup.md b/docs/guides/model-endpoint-identity-setup.md similarity index 98% rename from docs/how-to/model_endpoint_identity_setup.md rename to docs/guides/model-endpoint-identity-setup.md index 241b2ca2b..811c306f8 100644 --- a/docs/how-to/model_endpoint_identity_setup.md +++ b/docs/guides/model-endpoint-identity-setup.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Configure Model Endpoint Identity" -permalink: /how-to/model_endpoint_identity_setup/ +permalink: /guides/model-endpoint-identity-setup/ menubar: docs_menu accent: teal eyebrow: "Admin How-To" @@ -29,8 +29,11 @@ hero_links: url: /admin_configuration/ style: primary - label: "Managed identity guide" - url: /how-to/use_managed_identity/ + url: /guides/use-managed-identity/ style: secondary +section: "Guides" +redirect_from: + - /how-to/model_endpoint_identity_setup/ --- Use this guide when admins need to configure the shared **Model Endpoint** modal for Azure OpenAI, Foundry (classic), or New Foundry without depending on legacy single-endpoint settings. @@ -246,7 +249,5 @@ API keys are not a replacement for Foundry RBAC when users need New Foundry agen ## Related Documentation -- [Use Managed Identity]({{ '/how-to/use_managed_identity/' | relative_url }}) -- [Admin configuration overview]({{ '/admin_configuration/' | relative_url }}) -- [Workspace Multi Endpoints]({{ '/explanation/features/v0.241.001/WORKSPACE_MULTI_ENDPOINTS/' | relative_url }}) -- [Dual Foundry Agent Support]({{ '/explanation/features/v0.241.001/DUAL_FOUNDRY_AGENT_SUPPORT/' | relative_url }}) \ No newline at end of file +- [Use Managed Identity]({{ '/guides/use-managed-identity/' | relative_url }}) +- [Admin configuration overview]({{ '/admin_configuration/' | relative_url }}) \ No newline at end of file diff --git a/docs/guides/review-approval-requests.md b/docs/guides/review-approval-requests.md new file mode 100644 index 000000000..98398ddf9 --- /dev/null +++ b/docs/guides/review-approval-requests.md @@ -0,0 +1,66 @@ +--- +layout: page +title: "Review approval requests" +description: "Find, approve, or deny requests that need reviewer action." +section: "Guides" +audience: user +--- + +## What this does + +The **Approval Requests** page lists pending and historical requests such as ownership changes, document deletion, group deletion, user actions, and agent template submissions. Reviewers inspect details, comment, and approve or deny eligible requests. + +{% include media.html type="video" + title="Review approval requests walkthrough" + poster="video-posters/guide-review-approval-requests.png" + capture="Recording planned. Show review approval requests end to end and explain why this task helps a user." %} + +## Why you would use this + +Approvals create a checkpoint before sensitive actions execute. Use this page when a request can affect another user, remove shared content, or publish a template. It is not a general task inbox; only approval-backed actions appear here. + +## Before you start + +- You must have reviewer access for the request type. +- Agent template approvals depend on `enable_agent_template_gallery`; see [Agents settings]({{ '/admin/agents-actions/' | relative_url }}). +- Safety and feedback review roles may be governed from [Safety settings]({{ '/admin/security/' | relative_url }}). + +## Steps + +1. Open **Approval Requests**. +2. Use **Search approvals...** to find a request. +3. Set **Pending Only** or **All Statuses**. + +{% include media.html src="guides/review-approval-requests-step-3.png" + alt="Screenshot showing review approval requests step 3." + title="Review approval requests step 3" + capture="Capture the review approval requests task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Use **All Types** to filter by request type. +5. Open the request action from **Actions**. +6. Review **Request Details**, including **Request Type**, **Target**, **Requested By**, **Date**, and **Reason**. +7. Add a comment and select **Approve & Execute** or **Deny Request**. + +{% include media.html src="guides/review-approval-requests-step-7.png" + alt="Screenshot showing review approval requests step 7." + title="Review approval requests step 7" + capture="Capture the review approval requests task at this step in SimpleChat with realistic sample data and redact secrets." %} + +8. For templates, use **Agent Template Approvals** and its status filters. + +## Verify it worked + +The request status changes in the table. Approved executable requests complete the requested action, while denied requests remain recorded with the decision. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| You cannot approve a request | You are not an eligible reviewer | Ask an eligible reviewer or admin to handle it. | +| Completed requests are hidden | The status filter is **Pending Only** | Change it to **All Statuses**. | + +## Related + +- [Create an agent]({{ '/guides/create-an-agent/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) +- [Safety settings]({{ '/admin/security/' | relative_url }}) diff --git a/docs/guides/review-pasted-urls.md b/docs/guides/review-pasted-urls.md new file mode 100644 index 000000000..f822cc2ee --- /dev/null +++ b/docs/guides/review-pasted-urls.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Review pasted URLs" +description: "Use URL Access to review links included in the current chat message." +section: "Guides" +audience: user +--- + +## What this does + +**URLs** turns on URL Access for the current message so pasted links are reviewed instead of treated as plain text. The control appears when URL Access is available for the prompt. + +{% include media.html type="video" + title="Review pasted URLs walkthrough" + poster="video-posters/guide-review-pasted-urls.png" + capture="Recording planned. Show review pasted urls end to end and explain why this task helps a user." %} + +## Why you would use this + +Use URL Access when you already know the exact pages the model should review. It is more precise than broad web search because you provide the URLs; use web search or Deep Research when discovery or deeper review is the real task. + +## Before you start + +- Admins must enable `enable_url_access`; see [Search and Extract]({{ '/admin/knowledge/' | relative_url }}). +- The prompt must include valid `http://` or `https://` URLs. +- Chat warns when the URL count exceeds the configured limit. + +## Steps + +1. Open **Chat**. +2. Paste one or more URLs into **Type your message...** with your question. +3. Select **URLs**; its tooltip is **Review pasted URLs**. + +{% include media.html src="guides/review-pasted-urls-step-3.png" + alt="Screenshot showing review pasted urls step 3." + title="Review pasted URLs step 3" + capture="Capture the review pasted urls task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Select **Send Message**. +5. Review the answer and URL review progress. + +{% include media.html src="guides/review-pasted-urls-step-5.png" + alt="Screenshot showing review pasted urls step 5." + title="Review pasted URLs step 5" + capture="Capture the review pasted urls task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Use **Research** instead when the source review needs deeper investigation. + +## Verify it worked + +The response uses information from the pasted URLs, or SimpleChat warns when too many URLs were included. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **URLs** is hidden | URL Access is disabled or no URL is detected | Paste a valid URL or ask an admin to enable it. | +| A URL limit warning appears | Too many URLs are in one prompt | Split the work across messages. | + +## Related + +- [Use deep research]({{ '/guides/use-deep-research/' | relative_url }}) +- [Use web search]({{ '/guides/use-web-search/' | relative_url }}) +- [Search and Extract]({{ '/admin/knowledge/' | relative_url }}) diff --git a/docs/how-to/scaling_on_azure.md b/docs/guides/scale-simplechat-on-azure.md similarity index 71% rename from docs/how-to/scaling_on_azure.md rename to docs/guides/scale-simplechat-on-azure.md index c57ab73f7..85e12d01e 100644 --- a/docs/how-to/scaling_on_azure.md +++ b/docs/guides/scale-simplechat-on-azure.md @@ -1,408 +1,819 @@ --- layout: showcase-page title: "Scale Simple Chat on Azure" -permalink: /how-to/scaling_on_azure/ +permalink: /guides/scale-simplechat-on-azure/ menubar: docs_menu accent: orange eyebrow: "How-To Guide" description: "Use this guide when a working deployment needs more throughput, higher availability, or cleaner scaling rules across App Service, Cosmos DB, Search, and AI services." hero_icons: ["bi-graph-up-arrow", "bi-server", "bi-speedometer2"] hero_pills: ["App Service and data scale", "Search and AI throughput", "Monitor before you overprovision"] -hero_links: [{ label: "Scaling overview", url: "/application_scaling/", style: "primary" }, { label: "Admin overview", url: "/admin_configuration/", style: "secondary" }] +hero_links: [{ label: "Admin overview", url: "/admin_configuration/", style: "primary" }] +section: "Guides" +redirect_from: + - /application_scaling/ + - /how-to/scaling_on_azure/ --- -Scaling Simple Chat is about coordinating several Azure services, not just adding more web instances. Use this guide when you already have a functioning deployment and need to expand capacity without breaking session behavior, retrieval performance, or downstream service quotas. - -
-
-
-

Scale App Service deliberately

-

Vertical and horizontal scaling solve different problems, and scale-out requires session strategy to be correct before you add instances.

-
-
-
-

Track Cosmos RU pressure

-

Most data-tier trouble shows up as throttling, query latency, or hot containers before it shows up as a full outage.

-
-
-
-

Protect retrieval quality

-

Search replicas, partitions, and index performance all influence how grounded chat behaves at higher load.

-
-
-
-

Mind downstream AI quotas

-

Azure OpenAI and other AI services have their own TPM, RPM, and service limits, so app scaling alone does not solve throughput bottlenecks.

-
-
- -
-

Redis comes before App Service scale-out

-

If you plan to run more than one web instance, configure shared session storage first. Without it, scale-out can create authentication and session consistency problems that look like random app instability.

-
+Scaling Simple Chat is about coordinating several Azure services, not just adding more web instances. This page is the canonical scaling guide and replaces the former application scaling overview. Use this guide when you already have a functioning deployment and need to expand capacity without breaking session behavior, retrieval performance, or downstream service quotas. + + + + +## Component overview + +### Azure App Service + +Scale up when single requests need more memory or CPU. Scale out when concurrent users, long chats, and upload traffic rise. + +### Azure Cosmos DB + +Watch RU consumption and move to container-level autoscale for the busiest containers so chat and document traffic do not fight each other. + +### Azure AI Search + +Replicas lift query throughput, partitions lift storage and indexing throughput, and higher tiers raise the service ceiling when S1 runs out. + +### Azure AI and Cognitive Services + +OpenAI, Document Intelligence, Speech, Content Safety, and Video Indexer are usually quota-driven. Route them through APIM if you need retries, load balancing, or consistent policy enforcement. + +## Redis comes before App Service scale-out + +If you plan to run more than one web instance, configure shared session storage first. Without it, scale-out can create authentication and session consistency problems that look like random app instability. + + ## When to Scale + + Monitor these indicators to know when scaling is needed: + + ### Performance Metrics to Watch + - **CPU utilization** consistently above 70% + - **Memory usage** consistently above 80% + - **Request latency** increasing beyond acceptable levels + - **Queue lengths** growing consistently + - **RU consumption** approaching limits in Cosmos DB + - **Rate limit responses** (HTTP 429 errors) from Azure services + + Use **Azure Monitor** and **Application Insights** to track these metrics. + + + + +## Scaling principle + + + +Scale the bottleneck you can prove. Track CPU and memory, request latency, queue depth, Cosmos DB RU consumption, Azure AI Search query latency, indexing backlog, and rate-limit responses before changing service sizes. + + + ## Scaling Azure App Service + + The App Service hosts your Python backend application. + + ### Scale Up (Vertical Scaling) + + **When to scale up:** + - Individual requests are resource-intensive + - Single instances need more power + - Complex document processing operations + + **How to scale up:** + 1. Go to **Azure Portal** → your App Service + 2. Navigate to **Scale up (App Service plan)** + 3. Select a higher pricing tier: + - From P0v3 → P1v3, P2v3, P3v3 + - Consider Premium v3 tiers for better performance + 4. Click **Apply** + + **Immediate benefits:** + - More CPU cores per instance + - Increased RAM per instance + - Better disk I/O performance + + ### Scale Out (Horizontal Scaling) + + **When to scale out:** + - High number of concurrent users + - Need improved availability + - Load balancing across instances + + **Prerequisites:** -⚠️ **IMPORTANT:** You must configure **Azure Cache for Redis** before scaling out. Without Redis, user sessions will be tied to specific instances, causing authentication issues. + +**Important:** You must configure **Azure Cache for Redis** before scaling out. Without Redis, user sessions will be tied to specific instances, causing authentication issues. + + **Setup Redis first:** + 1. Deploy **Azure Cache for Redis** instance + 2. Configure Simple Chat to use Redis for session storage + 3. Update application configuration + 4. Test with multiple instances + + **How to scale out:** + 1. Go to **Azure Portal** → your App Service + 2. Navigate to **Scale out (App Service plan)** + 3. Increase **Instance count** manually, or + 4. Configure **Autoscale rules**: + - Based on CPU percentage (scale out at 70%, scale in at 30%) + - Based on memory usage + - Based on request queue length + + **Autoscale example configuration:** + ``` + Scale Out Rule: + - Metric: CPU Percentage + - Operator: Greater than + - Threshold: 70% + - Duration: 5 minutes + - Action: Increase instance count by 1 + + Scale In Rule: + - Metric: CPU Percentage + - Operator: Less than + - Threshold: 30% + - Duration: 10 minutes + - Action: Decrease instance count by 1 + + Minimum instances: 2 + Maximum instances: 10 + ``` + + ## Scaling Azure Cosmos DB + + Cosmos DB stores conversations, metadata, and settings. + + ### Understanding Request Units (RU/s) + + Request Units measure the cost of database operations: + - **Read operations**: Lower RU cost + - **Write operations**: Higher RU cost + - **Complex queries**: Variable RU cost based on complexity + + ### Autoscale Throughput (Recommended) + + **Benefits:** + - Automatic scaling between 10% and 100% of max RU/s + - Pay only for what you use + - Handles traffic spikes automatically + + **How to configure:** + 1. Go to **Azure Portal** → your Cosmos DB account + 2. Navigate to **Data Explorer** + 3. For each container, select **Scale & Settings** + 4. Choose **Autoscale** throughput + 5. Set **Maximum RU/s** + + ### Recommended Container Scaling + + +![Scale - Cosmos DB Container Throughput Settings]({{ '/images/scale-cosmos.png' | relative_url }}) + + + + + **High-traffic containers:** + ``` + messages container: + - Max RU/s: 4,000 (scales 400-4,000) + - Reason: High read/write volume from chat + + documents container: + - Max RU/s: 4,000 (scales 400-4,000) + - Reason: Document metadata operations + + group_documents container: + - Max RU/s: 4,000 (scales 400-4,000) + - Reason: Group document sharing + ``` + + **Lower-traffic containers:** + ``` + settings container: + - Max RU/s: 1,000 (scales 100-1,000) + - Reason: Infrequent configuration changes + + feedback container: + - Max RU/s: 1,000 (scales 100-1,000) + - Reason: Periodic feedback submission + + archived_conversations container: + - Max RU/s: 1,000 (scales 100-1,000) + - Reason: Archive operations are less frequent + ``` + + ### Monitor and Adjust + + **Key metrics to monitor:** + - **Request Unit consumption** per container + - **Throttling events** (HTTP 429 errors) + - **Query performance** and latency + + **How to monitor:** + 1. Use **Azure Monitor** metrics for Cosmos DB + 2. Set up **alerts** for high RU consumption + 3. Review **query performance** in Data Explorer + 4. Check **throttling metrics** regularly + + **When to increase RU/s:** + - Consistent throttling (429 errors) + - Query latency increasing + - Application performance degrading + + ## Scaling Azure AI Search + + AI Search powers document retrieval and hybrid search. + + ### Search Service Tiers + + **Basic tier:** + - Limited storage and query volume + - Good for development and small deployments + + **Standard tiers (S1, S2, S3):** + - Increased storage capacity + - Higher query per second (QPS) limits + - Better performance for production workloads + + **Storage Optimized tiers (L1, L2):** + - Optimized for large document collections + - Lower cost per GB of storage + + ### When to Scale AI Search + + **Scale up when you experience:** + - Slow search response times + - Query throttling + - Running out of storage space + - Need higher availability (replicas) + + ### How to Scale AI Search + + **Increase search units:** + 1. Go to **Azure Portal** → your Search Service + 2. Navigate to **Scale** + 3. Adjust **Search units** (combination of replicas and partitions) + 4. **Replicas**: Improve query performance and availability + 5. **Partitions**: Increase storage and indexing capacity + + **Scaling examples:** + ``` + Small deployment: + - 1 replica, 1 partition (1 search unit) + - Up to 15 million documents + - Basic query performance + + Medium deployment: + - 2 replicas, 2 partitions (4 search units) + - Up to 30 million documents + - Better performance and availability + + Large deployment: + - 3 replicas, 3 partitions (9 search units) + - Up to 45 million documents + - High performance and high availability + ``` + + ### Monitor AI Search Performance + + **Key metrics:** + - **Query latency**: Time to execute searches + - **Query per second (QPS)**: Current query load + - **Storage utilization**: How much space is used + - **Indexing performance**: Document processing speed + + **Optimization tips:** + - Use **semantic search** for better relevance + - Implement **query caching** where appropriate + - Consider **index optimization** for large datasets + - Monitor **suggester performance** if using autocomplete + + ## Scaling Azure OpenAI Services + + OpenAI services power the chat models and embeddings. + + ### Understand Rate Limits + + Each deployment has rate limits measured in: + - **Tokens per minute (TPM)** + - **Requests per minute (RPM)** + + ### Scale OpenAI Deployments + + **Increase quota:** + 1. Go to **Azure OpenAI Studio** + 2. Navigate to **Deployments** + 3. Select your deployment + 4. **Increase TPM/RPM limits** if quota allows + 5. Request **quota increases** if needed + + **Multiple deployments:** + - Deploy same model to multiple regions + - Implement **load balancing** between deployments + - Use **API Management** for advanced routing + + **Model selection for scale:** + - **GPT-4**: Higher quality, lower throughput + - **GPT-3.5 Turbo**: Lower cost, higher throughput + - **Consider newer models** as they become available + + + + +### APIM as the scaling control plane + + + +Azure API Management is the recommended control plane when SimpleChat needs a stable gateway in front of AI services. + + + +- It can spread traffic across multiple Azure OpenAI deployments or regions. + +- It can rate-limit callers before they hit backend service quotas. + +- It can retry transient failures such as HTTP 429 responses. + +- It gives operations teams one place to apply authentication, policy, and logging rules. + + + +SimpleChat supports APIM-backed configuration for GPT, embeddings, image generation, Content Safety, Document Intelligence, and Azure AI Search through Admin Settings. + + + +For a strong Azure OpenAI pattern, review the [AzureOpenAI-with-APIM repository](https://github.com/microsoft/AzureOpenAI-with-APIM). + + + ## Scaling Other Components + + ### Azure Cache for Redis + + **When to scale Redis:** + - High memory utilization (>80%) + - Increased connection counts + - Performance degradation + + **How to scale:** + 1. **Scale up**: Move to higher pricing tier (C1 → C2 → C3, etc.) + 2. **Scale out**: Use Redis clustering (Premium tiers) + 3. **Monitor**: Memory usage, CPU, network throughput + + ### Azure Storage Account (Enhanced Citations) + + **When to scale:** + - High transaction volumes + - Storage capacity approaching limits + - Performance degradation + + **How to scale:** + 1. **Performance tiers**: Standard → Premium storage + 2. **Replication**: LRS → ZRS → GRS for availability + 3. **Multiple accounts**: Distribute load across accounts + + ### Document Intelligence and Other AI Services + + **Scaling approach:** + - **Request rate limits**: Similar to OpenAI services + - **Multiple regions**: Deploy across regions for availability + - **Service tiers**: Standard tiers offer higher limits + + ## Best Practices for Scaling + + ### Planning -- ✅ **Baseline performance**: Measure current metrics before scaling -- ✅ **Load testing**: Test scaling decisions in non-production first -- ✅ **Gradual scaling**: Make incremental changes -- ✅ **Monitor impact**: Watch metrics after each scaling change + +- **Baseline performance**: Measure current metrics before scaling + +- **Load testing**: Test scaling decisions in non-production first + +- **Gradual scaling**: Make incremental changes + +- **Monitor impact**: Watch metrics after each scaling change + + ### Cost Optimization -- ✅ **Autoscale where possible**: Use autoscale for variable workloads -- ✅ **Right-sizing**: Don't over-provision resources -- ✅ **Reserved instances**: Use reservations for predictable workloads -- ✅ **Resource cleanup**: Remove unused resources + +- **Autoscale where possible**: Use autoscale for variable workloads + +- **Right-sizing**: Don't over-provision resources + +- **Reserved instances**: Use reservations for predictable workloads + +- **Resource cleanup**: Remove unused resources + + ### Monitoring and Alerting -- ✅ **Comprehensive monitoring**: Monitor all components -- ✅ **Proactive alerts**: Set alerts before hitting limits -- ✅ **Regular reviews**: Review performance and scaling regularly -- ✅ **Scaling runbooks**: Document scaling procedures + +- **Comprehensive monitoring**: Monitor all components + +- **Proactive alerts**: Set alerts before hitting limits + +- **Regular reviews**: Review performance and scaling regularly + +- **Scaling runbooks**: Document scaling procedures + + ### High Availability -- ✅ **Multi-instance App Service**: At least 2 instances -- ✅ **Redis clustering**: Use Premium Redis for HA -- ✅ **Multiple search replicas**: Ensure search availability -- ✅ **Cross-region**: Consider multi-region for critical workloads + +- **Multi-instance App Service**: At least 2 instances + +- **Redis clustering**: Use Premium Redis for HA + +- **Multiple search replicas**: Ensure search availability + +- **Cross-region**: Consider multi-region for critical workloads + + ## Troubleshooting Scaling Issues + + ### App Service Scaling Problems + **Session issues after scale out:** + - Verify Redis configuration + - Check session storage settings + - Test load balancer configuration + + **Performance not improving:** + - Check if bottleneck is elsewhere (database, AI services) + - Verify application is utilizing multiple cores + - Review application performance profiles + + ### Database Scaling Issues + **Continued throttling after RU increase:** + - Check for hot partitions + - Review query patterns and indexing + - Consider data modeling changes + + **High costs:** + - Review RU consumption patterns + - Optimize queries to reduce RU usage + - Consider data archival strategies + + ### Search Performance Issues + **Slow queries after scaling:** + - Review index design and field configuration + - Check query complexity and filters + - Consider semantic search optimizations + + ## Scaling Checklist + + **Before scaling:** + - [ ] Identify performance bottlenecks + - [ ] Review current resource utilization + - [ ] Plan scaling approach + - [ ] Prepare monitoring and rollback plans + + **During scaling:** + - [ ] Enable Redis before App Service scale out + - [ ] Configure autoscale rules appropriately + - [ ] Set up proper monitoring and alerting + - [ ] Test functionality after each change + + **After scaling:** + - [ ] Monitor performance improvements + - [ ] Verify cost impacts are acceptable + - [ ] Document changes and decisions + - [ ] Plan next scaling steps if needed + + This guide provides the foundation for scaling Simple Chat effectively. Remember that scaling is an iterative process - monitor, adjust, and optimize continuously as your usage grows. diff --git a/docs/guides/send-feedback.md b/docs/guides/send-feedback.md new file mode 100644 index 000000000..5f098013d --- /dev/null +++ b/docs/guides/send-feedback.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Send feedback" +description: "Prepare a bug report or feature request email for your SimpleChat administrators." +section: "Guides" +audience: user +--- + +## What this does + +The **Send Feedback** page prepares a text-only email draft in your local mail client. You choose a bug report or feature request and review the draft before sending. + +{% include media.html type="video" + title="Send feedback walkthrough" + poster="video-posters/guide-send-feedback.png" + capture="Recording planned. Show send feedback end to end and explain why this task helps a user." %} + +## Why you would use this + +Use the feedback form when admins need enough context to reproduce a problem or evaluate an improvement. It replaces informal messages that omit expected outcome, impact, or steps to reproduce. It is not an in-app support ticket system; the final message is sent from your mail client. + +## Before you start + +- Admins must enable `enable_support_menu` and `enable_support_send_feedback`; see [General settings]({{ '/admin/appearance/' | relative_url }}). +- Your device needs a local mail client or mail handler. +- Collect reproduction steps, screenshots, business impact, or desired outcome before filling the form. + +## Steps + +1. Open the **Support** menu. +2. Select **Send Feedback**. +3. For a bug, use **Bug Report** and fill **Name**, **Email**, **Organization**, and **Bug Details**. + +{% include media.html src="guides/send-feedback-step-3.png" + alt="Screenshot showing send feedback step 3." + title="Send feedback step 3" + capture="Capture the send feedback task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Include what happened, what you expected, and how to reproduce it. +5. Select **Open Bug Report Draft**. +6. For an improvement, use **Feature Request**, fill the same contact fields, and describe the problem and desired outcome. + +{% include media.html src="guides/send-feedback-step-6.png" + alt="Screenshot showing send feedback step 6." + title="Send feedback step 6" + capture="Capture the send feedback task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Review the email draft in your mail client and send it when ready. + +## Verify it worked + +Your mail client opens a draft addressed according to tenant feedback configuration, and the draft includes the details you entered. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Send Feedback is missing | The support menu or destination is disabled | Ask an admin to enable `enable_support_menu` and `enable_support_send_feedback`. | +| No email draft opens | No local mail handler is configured | Set a default mail app or copy the content into email manually. | + +## Related + +- [Update profile preferences]({{ '/guides/update-profile-preferences/' | relative_url }}) +- [General settings]({{ '/admin/appearance/' | relative_url }}) +- [Send Feedback settings]({{ '/admin/help/' | relative_url }}) diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt b/docs/guides/servicenow/agent_instructions/servicenow_agent_instructions.txt similarity index 100% rename from docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt rename to docs/guides/servicenow/agent_instructions/servicenow_agent_instructions.txt diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_asset_management_agent_instructions.txt b/docs/guides/servicenow/agent_instructions/servicenow_asset_management_agent_instructions.txt similarity index 100% rename from docs/how-to/agents/ServiceNow/agent_instructions/servicenow_asset_management_agent_instructions.txt rename to docs/guides/servicenow/agent_instructions/servicenow_asset_management_agent_instructions.txt diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_kb_management_agent_instructions.txt b/docs/guides/servicenow/agent_instructions/servicenow_kb_management_agent_instructions.txt similarity index 100% rename from docs/how-to/agents/ServiceNow/agent_instructions/servicenow_kb_management_agent_instructions.txt rename to docs/guides/servicenow/agent_instructions/servicenow_kb_management_agent_instructions.txt diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md b/docs/guides/servicenow/asset-management-setup.md similarity index 92% rename from docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md rename to docs/guides/servicenow/asset-management-setup.md index 713f3837d..a53fa5094 100644 --- a/docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md +++ b/docs/guides/servicenow/asset-management-setup.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "ServiceNow Asset Management Setup" -permalink: /how-to/agents/ServiceNow/servicenow_asset_management_setup/ +permalink: /guides/servicenow/asset-management-setup/ menubar: docs_menu accent: orange eyebrow: "How-To Guide" description: "Set up a ServiceNow asset-management agent with separate actions for querying, creating, updating, and deleting records in the `alm_asset` table." hero_icons: ["bi-pc-display-horizontal", "bi-database-gear", "bi-arrow-repeat"] hero_pills: ["Four-action asset workflow", "Direct `alm_asset` table operations", "Query-first pattern for update and delete"] -hero_links: [{ label: "ServiceNow guides", url: "/how-to/agents/ServiceNow/", style: "primary" }, { label: "OAuth setup", url: "/how-to/agents/ServiceNow/servicenow_oauth_setup/", style: "secondary" }] +hero_links: [{ label: "ServiceNow guides", url: "/guides/servicenow/", style: "primary" }, { label: "OAuth setup", url: "/guides/servicenow/oauth-setup/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/agents/ServiceNow/servicenow_asset_management_setup/ --- This guide is for teams that want a dedicated asset-management agent instead of overloading a general ServiceNow support agent. It keeps query, create, update, and delete actions separate so each operation is easier to govern and debug. @@ -125,7 +128,7 @@ Create a dedicated ServiceNow service account for asset management operations. ### Generate OAuth Bearer Token -> **📘 For complete OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** +> **📘 For complete OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md]({{ '/guides/servicenow/oauth-setup/' | relative_url }})** > > The OAuth setup guide provides: > - OAuth application configuration in ServiceNow @@ -157,7 +160,7 @@ When creating each action (query, create, update, delete), use the same authenti **Description:** `Query assets and retrieve asset details from ServiceNow` **Type:** `openapi` -**OpenAPI Specification:** See [servicenow_query_assets_openapi.json](servicenow_query_assets_openapi.json) +**OpenAPI Specification:** See [servicenow_query_assets_openapi.json]({{ '/guides/servicenow/open_api_specs/servicenow_query_assets_openapi.json' | relative_url }}) **Key Operations:** - `queryAssets` - Search and filter assets with query parameters @@ -178,7 +181,7 @@ When creating each action (query, create, update, delete), use the same authenti **Description:** `Create new assets in ServiceNow` **Type:** `openapi` -**OpenAPI Specification:** See [servicenow_create_asset_openapi.json](servicenow_create_asset_openapi.json) +**OpenAPI Specification:** See [servicenow_create_asset_openapi.json]({{ '/guides/servicenow/open_api_specs/servicenow_create_asset_openapi.json' | relative_url }}) **Key Operation:** - `createAsset` - Create new asset with required fields (asset_tag, display_name) @@ -200,7 +203,7 @@ When creating each action (query, create, update, delete), use the same authenti **Description:** `Update existing assets in ServiceNow` **Type:** `openapi` -**OpenAPI Specification:** See [servicenow_update_asset_openapi.json](servicenow_update_asset_openapi.json) +**OpenAPI Specification:** See [servicenow_update_asset_openapi.json]({{ '/guides/servicenow/open_api_specs/servicenow_update_asset_openapi.json' | relative_url }}) **Key Operation:** - `updateAsset` - Update asset fields using PATCH method @@ -220,7 +223,7 @@ When creating each action (query, create, update, delete), use the same authenti **Description:** `Delete assets from ServiceNow` **Type:** `openapi` -**OpenAPI Specification:** See [servicenow_delete_asset_openapi.json](servicenow_delete_asset_openapi.json) +**OpenAPI Specification:** See [servicenow_delete_asset_openapi.json]({{ '/guides/servicenow/open_api_specs/servicenow_delete_asset_openapi.json' | relative_url }}) **Key Operation:** - `deleteAsset` - Permanently delete an asset from ServiceNow @@ -250,7 +253,7 @@ When creating each action (query, create, update, delete), use the same authenti ### Agent Instructions File -Upload the agent instructions file: [servicenow_asset_management_agent_instructions.txt](servicenow_asset_management_agent_instructions.txt) +Upload the agent instructions file: [servicenow_asset_management_agent_instructions.txt]({{ '/guides/servicenow/agent_instructions/servicenow_asset_management_agent_instructions.txt' | relative_url }}) **Key Instruction Highlights:** - No-narration execution pattern (execute silently, show results only) diff --git a/docs/how-to/agents/ServiceNow/index.md b/docs/guides/servicenow/index.md similarity index 83% rename from docs/how-to/agents/ServiceNow/index.md rename to docs/guides/servicenow/index.md index cc85c6765..830d3bc80 100644 --- a/docs/how-to/agents/ServiceNow/index.md +++ b/docs/guides/servicenow/index.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "ServiceNow Agent Guides" -permalink: /how-to/agents/ServiceNow/ +permalink: /guides/servicenow/ menubar: docs_menu accent: slate eyebrow: "How-To Guide" description: "Use these guides to connect Simple Chat agents and actions to ServiceNow for incident handling, knowledge workflows, OAuth authentication, and asset management." hero_icons: ["bi-plug", "bi-life-preserver", "bi-database-gear"] hero_pills: ["Incident and KB integrations", "OAuth 2.0 authentication", "Single-agent and multi-agent patterns"] -hero_links: [{ label: "Create agents", url: "/how-to/create_agents/", style: "primary" }, { label: "How-to index", url: "/how-to/", style: "secondary" }] +hero_links: [{ label: "Create agents", url: "/guides/create-agents/", style: "primary" }, { label: "How-to index", url: "/guides/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/agents/ServiceNow/ --- These guides are best read as a family. Start with the single-agent integration if you want the fastest path, move into OAuth when you need production authentication, then use the advanced guides only when your permissions or workflows demand more separation. @@ -18,25 +21,25 @@ These guides are best read as a family. Start with the single-agent integration

Single-agent ServiceNow integration

Use the standard support-agent guide for incident management and read-only KB search through one agent and two actions.

-

Open integration guide

+

Open integration guide

OAuth 2.0 setup

Use the OAuth guide when you need bearer-token authentication instead of Basic Auth for ServiceNow actions in production-style environments.

-

Open OAuth guide

+

Open OAuth guide

Two-agent KB management

Use the advanced split-agent design when incident handling and KB publishing need separate roles, tokens, and approval boundaries.

-

Open two-agent guide

+

Open two-agent guide

Asset management agent

Use the asset-management guide when you want a dedicated agent with separate query, create, update, and delete actions over the `alm_asset` table.

-

Open asset guide

+

Open asset guide

diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md b/docs/guides/servicenow/integration.md similarity index 97% rename from docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md rename to docs/guides/servicenow/integration.md index a4376a667..843a34186 100644 --- a/docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md +++ b/docs/guides/servicenow/integration.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "ServiceNow Integration" -permalink: /how-to/agents/ServiceNow/servicenow_integration/ +permalink: /guides/servicenow/integration/ menubar: docs_menu accent: blue eyebrow: "How-To Guide" description: "Configure the standard single-agent ServiceNow integration for incident management and read-only knowledge-base search in Simple Chat." hero_icons: ["bi-life-preserver", "bi-plug", "bi-search"] hero_pills: ["Single-agent support pattern", "Incident management + KB search", "Global or group-scoped rollout"] -hero_links: [{ label: "ServiceNow guides", url: "/how-to/agents/ServiceNow/", style: "primary" }, { label: "OAuth setup", url: "/how-to/agents/ServiceNow/servicenow_oauth_setup/", style: "secondary" }] +hero_links: [{ label: "ServiceNow guides", url: "/guides/servicenow/", style: "primary" }, { label: "OAuth setup", url: "/guides/servicenow/oauth-setup/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/agents/ServiceNow/servicenow_integration/ --- This is the default ServiceNow pattern for most teams. It gives you one support-focused agent backed by modular OpenAPI actions so users can manage incidents and search KB content without splitting responsibilities across multiple agents. @@ -54,8 +57,7 @@ This guide documents the **single-agent integration** between Simple Chat and Se - ✅ Single ServiceNow agent for standard support operations **For advanced KB management (create/publish articles), see:** -- 📘 [TWO_AGENT_SETUP.md](TWO_AGENT_SETUP.md) - Recommended approach with separate KB Management agent -- 📘 [KB_MULTI_ACTION_SETUP.md](KB_MULTI_ACTION_SETUP.md) - Alternative multi-action approach +- 📘 [TWO_AGENT_SETUP.md]({{ '/guides/servicenow/two-agent-setup/' | relative_url }}) - Recommended approach with separate KB Management agent > **⚠️ Important - Work in Progress:** > This integration is under active development. **Check back regularly for updates** to the OpenAPI specifications and agent instructions. Unit testing of prompts is still in progress, so further changes to the spec files and agent instruction file are expected. @@ -208,7 +210,7 @@ The integration uses two OpenAPI specification files that define all ServiceNow ### OpenAPI Specification Files -**Locations:** `docs/how-to/agents/ServiceNow/open_api_specs/` +**Locations:** `docs/guides/servicenow/open_api_specs/` **Available Authentication Options:** @@ -343,7 +345,7 @@ Scope: Global or Group ``` > **📄 Agent Instructions File:** -> - **Location:** `docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt` +> - **Location:** `docs/guides/servicenow/agent_instructions/servicenow_agent_instructions.txt` > - **Purpose:** Comprehensive behavioral instructions for the ServiceNow support agent > - **Usage:** Copy the entire content from this file into the "Instructions" field when creating the agent > diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md b/docs/guides/servicenow/oauth-setup.md similarity index 97% rename from docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md rename to docs/guides/servicenow/oauth-setup.md index e19e3a0e1..37c10c8e0 100644 --- a/docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md +++ b/docs/guides/servicenow/oauth-setup.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "ServiceNow OAuth 2.0 Setup" -permalink: /how-to/agents/ServiceNow/servicenow_oauth_setup/ +permalink: /guides/servicenow/oauth-setup/ menubar: docs_menu accent: violet eyebrow: "How-To Guide" description: "Configure OAuth 2.0 bearer-token authentication for ServiceNow actions in Simple Chat using ServiceNow's current inbound integration flow." hero_icons: ["bi-key", "bi-shield-lock", "bi-arrow-repeat"] hero_pills: ["Bearer token auth", "Integration user + OAuth app credentials", "Production-oriented alternative to Basic Auth"] -hero_links: [{ label: "ServiceNow integration", url: "/how-to/agents/ServiceNow/servicenow_integration/", style: "primary" }, { label: "Two-agent setup", url: "/how-to/agents/ServiceNow/two_agent_setup/", style: "secondary" }] +hero_links: [{ label: "ServiceNow integration", url: "/guides/servicenow/integration/", style: "primary" }, { label: "Two-agent setup", url: "/guides/servicenow/two-agent-setup/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/agents/ServiceNow/servicenow_oauth_setup/ --- This guide matters when Basic Auth is not acceptable and you want a cleaner production authentication story for ServiceNow actions. The important idea is that OAuth setup here still depends on a dedicated ServiceNow user because the token executes with that user's permissions. @@ -559,5 +562,5 @@ Refresh Token Lifespan: 604800 (7 days) ## Related Documentation - [ServiceNow OAuth Documentation](https://docs.servicenow.com/bundle/xanadu-platform-security/page/administer/security/concept/c_OAuthApplications.html) -- [Simple Chat OpenAPI Basic Auth Fix](./explanation/fixes/OPENAPI_BASIC_AUTH_FIX.md) -- [ServiceNow Integration Guide](./SERVICENOW_INTEGRATION.md) +- [Simple Chat OpenAPI Basic Auth Fix](https://github.com/microsoft/simplechat/blob/main/docs/explanation/fixes/OPENAPI_BASIC_AUTH_FIX.md) +- [ServiceNow Integration Guide]({{ '/guides/servicenow/integration/' | relative_url }}) diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_create_spec.yaml b/docs/guides/servicenow/open_api_specs/sample_now_knowledge_create_spec.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_create_spec.yaml rename to docs/guides/servicenow/open_api_specs/sample_now_knowledge_create_spec.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_publish_spec.yaml b/docs/guides/servicenow/open_api_specs/sample_now_knowledge_publish_spec.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_publish_spec.yaml rename to docs/guides/servicenow/open_api_specs/sample_now_knowledge_publish_spec.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec.yaml b/docs/guides/servicenow/open_api_specs/sample_now_knowledge_search_spec.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec.yaml rename to docs/guides/servicenow/open_api_specs/sample_now_knowledge_search_spec.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml b/docs/guides/servicenow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml rename to docs/guides/servicenow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api.yaml b/docs/guides/servicenow/open_api_specs/sample_servicenow_incident_api.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api.yaml rename to docs/guides/servicenow/open_api_specs/sample_servicenow_incident_api.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml b/docs/guides/servicenow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml rename to docs/guides/servicenow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_create_asset_openapi.json b/docs/guides/servicenow/open_api_specs/servicenow_create_asset_openapi.json similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/servicenow_create_asset_openapi.json rename to docs/guides/servicenow/open_api_specs/servicenow_create_asset_openapi.json diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_delete_asset_openapi.json b/docs/guides/servicenow/open_api_specs/servicenow_delete_asset_openapi.json similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/servicenow_delete_asset_openapi.json rename to docs/guides/servicenow/open_api_specs/servicenow_delete_asset_openapi.json diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_incident_api - basic auth sample.yaml b/docs/guides/servicenow/open_api_specs/servicenow_incident_api - basic auth sample.yaml similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/servicenow_incident_api - basic auth sample.yaml rename to docs/guides/servicenow/open_api_specs/servicenow_incident_api - basic auth sample.yaml diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_query_assets_openapi.json b/docs/guides/servicenow/open_api_specs/servicenow_query_assets_openapi.json similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/servicenow_query_assets_openapi.json rename to docs/guides/servicenow/open_api_specs/servicenow_query_assets_openapi.json diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_update_asset_openapi.json b/docs/guides/servicenow/open_api_specs/servicenow_update_asset_openapi.json similarity index 100% rename from docs/how-to/agents/ServiceNow/open_api_specs/servicenow_update_asset_openapi.json rename to docs/guides/servicenow/open_api_specs/servicenow_update_asset_openapi.json diff --git a/docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md b/docs/guides/servicenow/two-agent-setup.md similarity index 97% rename from docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md rename to docs/guides/servicenow/two-agent-setup.md index ece56e4d1..50695e63c 100644 --- a/docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md +++ b/docs/guides/servicenow/two-agent-setup.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "ServiceNow Two-Agent Setup" -permalink: /how-to/agents/ServiceNow/two_agent_setup/ +permalink: /guides/servicenow/two-agent-setup/ menubar: docs_menu accent: teal eyebrow: "How-To Guide" description: "Use a split-agent ServiceNow design when incident handling and knowledge-base management need different roles, users, and approval boundaries." hero_icons: ["bi-people", "bi-journal-richtext", "bi-shield-lock"] hero_pills: ["Separate support and KB permissions", "Distinct integration users and tokens", "Approval-aware KB management"] -hero_links: [{ label: "ServiceNow guides", url: "/how-to/agents/ServiceNow/", style: "primary" }, { label: "OAuth setup", url: "/how-to/agents/ServiceNow/servicenow_oauth_setup/", style: "secondary" }] +hero_links: [{ label: "ServiceNow guides", url: "/guides/servicenow/", style: "primary" }, { label: "OAuth setup", url: "/guides/servicenow/oauth-setup/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/agents/ServiceNow/two_agent_setup/ --- Use this pattern only when separation really helps. The value is not having more agents. The value is keeping support operations and KB publishing under different permission boundaries so governance and audit trails stay cleaner. @@ -55,7 +58,7 @@ This guide describes the **advanced two-agent architecture** for organizations t - ✅ Workflow-based KB article approval (draft → review → published) **For simpler single-agent setup (incidents + KB search only), see:** -- 📘 [SERVICENOW_INTEGRATION.md](SERVICENOW_INTEGRATION.md) - Recommended for most users +- 📘 [SERVICENOW_INTEGRATION.md]({{ '/guides/servicenow/integration/' | relative_url }}) - Recommended for most users --- @@ -127,7 +130,7 @@ Primary agent for incident management and KB article searches. ### Agent Instructions - File: `servicenow_agent_instructions.txt` -- Location: `docs/how-to/agents/ServiceNow/` +- Location: `docs/guides/servicenow/` ### Available To - All users (standard support staff) @@ -201,7 +204,7 @@ Specialized agent for knowledge base article management. ### Agent Instructions - File: `servicenow_kb_management_agent_instructions.txt` -- Location: `docs/how-to/agents/ServiceNow/` +- Location: `docs/guides/servicenow/` ### Available To - Knowledge managers only @@ -262,7 +265,7 @@ Specialized agent for knowledge base article management. ### Step 2: Generate OAuth Tokens -> **📘 For detailed OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** +> **📘 For detailed OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md]({{ '/guides/servicenow/oauth-setup/' | relative_url }})** > > The OAuth setup guide provides: > - Complete OAuth application configuration in ServiceNow @@ -588,7 +591,7 @@ Your ServiceNow instance requires approval workflow: **Draft → Review → Publ Bearer tokens expire after a period (typically 1 hour). -> **📘 For token refresh procedures, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** +> **📘 For token refresh procedures, see: [SERVICENOW_OAUTH_SETUP.md]({{ '/guides/servicenow/oauth-setup/' | relative_url }})** After obtaining a new token, update it in the agent action configuration in Simple Chat. @@ -616,7 +619,6 @@ If ServiceNow roles change: - `sample_now_knowledge_publish_spec.yaml` - KB publish/update operations ### Documentation -- `KB_MULTI_ACTION_SETUP.md` - Original multi-action approach (reference) - `TWO_AGENT_SETUP.md` - This file (recommended approach) --- diff --git a/docs/demos.md b/docs/guides/solution-demos.md similarity index 91% rename from docs/demos.md rename to docs/guides/solution-demos.md index bdc819ac7..1fc6fb495 100644 --- a/docs/demos.md +++ b/docs/guides/solution-demos.md @@ -1,7 +1,8 @@ --- layout: showcase-page title: "Solution Demonstrations" -permalink: /demos/ +section: "Guides" +permalink: /guides/solution-demos/ menubar: docs_menu accent: orange eyebrow: "See The Product Move" @@ -16,12 +17,15 @@ hero_pills: - Observe the user flow quickly hero_links: - label: Open Getting Started - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: Review workflows - url: /application_workflows/ + url: /reference/application-workflows/ style: secondary +redirect_from: + - /demos/ --- + These demos are useful when you need a quick visual of the product before walking through setup or user training.
@@ -44,4 +48,4 @@ These demos are useful when you need a quick visual of the product before walkin

Where to go next

If these flows match what your team needs, move into the getting started guide for deployment and then into the tutorials for end-user onboarding.

-
\ No newline at end of file + diff --git a/docs/guides/trigger-a-workflow.md b/docs/guides/trigger-a-workflow.md new file mode 100644 index 000000000..4641af1a5 --- /dev/null +++ b/docs/guides/trigger-a-workflow.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Trigger a workflow" +description: "Run a workflow now, schedule future runs, and inspect run activity." +section: "Guides" +audience: user +--- + +## What this does + +This guide starts a saved workflow, explains scheduling, and points you to the activity view after a run begins. It applies to personal and group workflows. + +{% include media.html type="video" + title="Trigger a workflow walkthrough" + poster="video-posters/guide-trigger-a-workflow.png" + capture="Recording planned. Show trigger a workflow end to end and explain why this task helps a user." %} + +## Why you would use this + +A workflow only helps when it runs at the right moment and leaves evidence you can inspect. Manual runs are best for tests and controlled work; schedules fit routine checks that can run unattended. Do not schedule workflows that require approval before each step. + +## Before you start + +- Create the workflow first. +- The relevant toggle must be enabled: `allow_user_workflows` or `allow_group_workflows`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- If the workflow uses File Sync, the selected source must be enabled and reachable. + +## Steps + +1. Open the workspace that owns the workflow. +2. Choose **Workflows**. +3. Search with **Search workflows by name, runner, or task prompt...** if needed. + +{% include media.html src="guides/trigger-a-workflow-step-3.png" + alt="Screenshot showing trigger a workflow step 3." + title="Trigger a workflow step 3" + capture="Capture the trigger a workflow task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Use the workflow row **Actions** to start a manual run, or edit the workflow to adjust **Trigger**. +5. For scheduled operation, choose an interval trigger and leave the workflow enabled. +6. After a run starts, open **Open workflow activity view** from the chat header when available. + +{% include media.html src="guides/trigger-a-workflow-step-6.png" + alt="Screenshot showing trigger a workflow step 6." + title="Trigger a workflow step 6" + capture="Capture the trigger a workflow task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Review status, task output, failures, and completion alerts in activity or history. + +## Verify it worked + +The workflow's **Last Run** updates, and the activity view or history shows the run status and task output. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A scheduled workflow does not run | It is disabled or still configured for manual trigger | Edit the trigger and confirm the workflow is enabled. | +| A run fails immediately | A runner, action, document, or File Sync source is unavailable | Open run details, fix the dependency, and run again. | + +## Related + +- [Create a workflow]({{ '/guides/create-a-workflow/' | relative_url }}) +- [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) +- [Manage notifications]({{ '/guides/manage-notifications/' | relative_url }}) diff --git a/docs/guides/update-profile-preferences.md b/docs/guides/update-profile-preferences.md new file mode 100644 index 000000000..2c32b653d --- /dev/null +++ b/docs/guides/update-profile-preferences.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Update profile preferences" +description: "Adjust personal SimpleChat preferences such as appearance, tutorials, notifications, memory, and retention." +section: "Guides" +audience: user +--- + +## What this does + +The Profile page combines account information, usage statistics, and personal preferences. This guide opens settings and saves changes that affect only your SimpleChat experience. + +{% include media.html type="video" + title="Update profile preferences walkthrough" + poster="video-posters/guide-update-profile-preferences.png" + capture="Recording planned. Show update profile preferences end to end and explain why this task helps a user." %} + +## Why you would use this + +Profile preferences are for personal comfort and control: font size, navigation behavior, tutorial buttons, desktop notifications, fact memory, retention, microphone permission, and text-to-speech. They replace admin requests for account-only changes; they do not change tenant-wide settings. + +## Before you start + +- You must be signed in. +- Some cards appear only when admins enable features such as `enable_desktop_notifications`, `enable_fact_memory_plugin`, or retention policy toggles. +- Browser permission may be required for microphone or desktop notifications. + +## Steps + +1. Open **Profile**. +2. Choose **Settings**. +3. In **Appearance Preferences**, pick **Font size** and select **Save Font Size**. + +{% include media.html src="guides/update-profile-preferences-step-3.png" + alt="Screenshot showing update profile preferences step 3." + title="Update profile preferences step 3" + capture="Capture the update profile preferences task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. In **Navigation Preferences**, choose the sidebar hide button style and select **Save Navigation Preferences**. +5. In **Tutorial Preferences**, choose whether tutorial buttons appear and select **Save Tutorial Preferences**. + +{% include media.html src="guides/update-profile-preferences-step-5.png" + alt="Screenshot showing update profile preferences step 5." + title="Update profile preferences step 5" + capture="Capture the update profile preferences task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. If shown, configure **Desktop Conversation Notifications**, **Fact Memory**, retention settings, microphone permission, or text-to-speech settings. + +## Verify it worked + +Each card shows a status message after saving. Reload the app and confirm the preference still applies. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A preference card is missing | The admin disabled that feature | Ask whether the related capability can be enabled. | +| Browser notifications do not appear | Browser permission or profile preference is off | Allow notifications and save the preference again. | + +## Related + +- [Manage notifications]({{ '/guides/manage-notifications/' | relative_url }}) +- [Send feedback]({{ '/guides/send-feedback/' | relative_url }}) +- [Safety settings]({{ '/admin/security/' | relative_url }}) diff --git a/docs/how-to/upgrade_paths.md b/docs/guides/upgrade-paths.md similarity index 97% rename from docs/how-to/upgrade_paths.md rename to docs/guides/upgrade-paths.md index f31fcb4a8..fb6fa5d13 100644 --- a/docs/how-to/upgrade_paths.md +++ b/docs/guides/upgrade-paths.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Upgrade Paths" -permalink: /how-to/upgrade_paths/ +permalink: /guides/upgrade-paths/ menubar: docs_menu accent: emerald eyebrow: "How-To Guide" @@ -21,6 +21,9 @@ hero_links: - label: "Manual deployment notes" url: /reference/deploy/manual_deploy/ style: secondary +section: "Guides" +redirect_from: + - /how-to/upgrade_paths/ --- The first upgrade decision is not which command to run. It is whether your existing site is running as native Python App Service or as one of the repo's container-based deployment models. That single distinction drives startup handling, rollout mechanics, and rollback choices. @@ -169,7 +172,7 @@ For the Azure CLI deployer specifically, the repo now includes `deployers/azurec The repo already contains an image publish workflow: -- [.github/workflows/docker_image_publish.yml](../../.github/workflows/docker_image_publish.yml) +- [.github/workflows/docker_image_publish.yml](https://github.com/microsoft/simplechat/blob/main/.github/workflows/docker_image_publish.yml) That workflow publishes: diff --git a/docs/tutorials/uploading_documents.md b/docs/guides/upload-and-manage-documents.md similarity index 93% rename from docs/tutorials/uploading_documents.md rename to docs/guides/upload-and-manage-documents.md index 82aca44bc..8c6f3a1ab 100644 --- a/docs/tutorials/uploading_documents.md +++ b/docs/guides/upload-and-manage-documents.md @@ -1,7 +1,7 @@ --- layout: showcase-page title: "Uploading and Managing Documents" -permalink: /tutorials/uploading_documents/ +permalink: /guides/upload-and-manage-documents/ menubar: docs_menu accent: orange eyebrow: "Tutorial 3" @@ -16,7 +16,7 @@ hero_pills: - Retrieval quality starts here hero_links: - label: Tutorial index - url: /tutorials/ + url: /guides/ style: primary - label: Feature overview url: /features/ @@ -25,11 +25,15 @@ show_nav: true nav_links: prev: title: Create Your First Agent - url: /tutorials/first_agent/ + url: /guides/create-your-first-agent/ next: title: Document Classification Tutorial - url: /tutorials/classifying_documents/ + url: /guides/classify-documents/ +section: "Guides" +redirect_from: + - /tutorials/uploading_documents/ --- + Retrieval quality starts with disciplined uploads. This tutorial explains where documents belong, how processing works, and how to keep the knowledge base understandable as it expands.
@@ -288,11 +292,11 @@ Congratulations! You've successfully: ## Next Steps Now that you're comfortable with document management: -- [Classify your documents](classifying_documents) for better organization -- [Create agents](first_agent) that work with specific document sets -- Explore [advanced document management](../how-to/add_documents) techniques -- Learn about [enhanced citations](../how-to/enhanced_citations) for better document linking +- [Classify your documents]({{ '/guides/classify-documents/' | relative_url }}) for better organization +- [Create agents]({{ '/guides/create-your-first-agent/' | relative_url }}) that work with specific document sets +- Explore [advanced document management]({{ '/guides/add-documents/' | relative_url }}) techniques +- Learn about [enhanced citations]({{ '/admin/chat/' | relative_url }}) for better document linking --- -*Next: [Classifying Documents](classifying_documents) →* +*Next: [Classifying Documents]({{ '/guides/classify-documents/' | relative_url }}) →* diff --git a/docs/guides/upload-documents-in-chat.md b/docs/guides/upload-documents-in-chat.md new file mode 100644 index 000000000..7de5ba9f4 --- /dev/null +++ b/docs/guides/upload-documents-in-chat.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Upload documents in chat" +description: "Attach a local file directly to the current conversation for grounded chat." +section: "Guides" +audience: user +--- + +## What this does + +Chat file upload adds a local file to the current conversation without first visiting a workspace document page. The upload starts automatically after you choose a file. + +{% include media.html type="video" + title="Upload documents in chat walkthrough" + poster="video-posters/guide-upload-documents-in-chat.png" + capture="Recording planned. Show upload documents in chat end to end and explain why this task helps a user." %} + +## Why you would use this + +Use chat upload when you need to ask about a file in the moment: a PDF someone sent you, a spreadsheet you are checking, or a document that is not worth adding to a shared workspace yet. Use a workspace or File Sync source instead for material a team should reuse. + +## Before you start + +- Admins must enable `enable_chat_file_uploads`; they may also require `require_member_of_chat_file_upload_user`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- The file type must be in the accept list shown by the **File** button tooltip. +- The file must fit within `max_file_size_mb` on [General settings]({{ '/admin/appearance/' | relative_url }}). + +## Steps + +1. Open **Chat** and choose a conversation. +2. Select **File** in the chat toolbar. +3. Choose a supported local file; the button changes to the file name while upload starts. + +{% include media.html src="guides/upload-documents-in-chat-step-3.png" + alt="Screenshot showing upload documents in chat step 3." + title="Upload documents in chat step 3" + capture="Capture the upload documents in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Wait for upload progress to finish before asking detailed questions. +5. Type your question in **Type your message...** and select **Send Message**. + +{% include media.html src="guides/upload-documents-in-chat-step-5.png" + alt="Screenshot showing upload documents in chat step 5." + title="Upload documents in chat step 5" + capture="Capture the upload documents in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Use the cancel icon on the **File** button if you selected the wrong file. + +## Verify it worked + +The response can reference the uploaded file when relevant. If enabled, **Open used documents** can show documents used by the conversation. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The **File** button is missing | Chat uploads are disabled or role-gated | Ask an admin to enable chat uploads and assign the role if required. | +| Image mode disables file controls | Image generation intentionally turns off source controls | Turn off **Image** before uploading. | + +## Related + +- [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) +- [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) +- [General settings]({{ '/admin/appearance/' | relative_url }}) diff --git a/docs/guides/use-deep-research.md b/docs/guides/use-deep-research.md new file mode 100644 index 000000000..7c159f758 --- /dev/null +++ b/docs/guides/use-deep-research.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Use deep research" +description: "Ask chat to perform deeper evidence review for URLs and sources in a message." +section: "Guides" +audience: user +--- + +## What this does + +**Deep Research** is a chat control that reviews sources more deeply for the current message. It is separate from normal workspace search and direct URL Access. + +{% include media.html type="video" + title="Use deep research walkthrough" + poster="video-posters/guide-use-deep-research.png" + capture="Recording planned. Show use deep research end to end and explain why this task helps a user." %} + +## Why you would use this + +Use Deep Research when the answer depends on evidence review rather than a quick model response, especially when pasted URLs need a deeper read. It is wrong for short drafting tasks, private content that should not leave approved paths, or work that only needs local workspace documents. + +## Before you start + +- Admins must enable `enable_source_review`; deeper behavior may depend on `enable_deep_source_review`; see [Search and Extract]({{ '/admin/knowledge/' | relative_url }}). +- Direct URLs in the prompt are subject to configured URL limits. +- Do not paste sensitive URLs unless your organization permits that review path. + +## Steps + +1. Open **Chat**. +2. Type the question that needs source review. +3. Include exact URLs when specific web pages should be reviewed. + +{% include media.html src="guides/use-deep-research-step-3.png" + alt="Screenshot showing use deep research step 3." + title="Use deep research step 3" + capture="Capture the use deep research task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Select **Research**; its tooltip is **Deep Research**. +5. Select **Send Message**. + +{% include media.html src="guides/use-deep-research-step-5.png" + alt="Screenshot showing use deep research step 5." + title="Use deep research step 5" + capture="Capture the use deep research task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Review progress and the final answer; conversation details can show **Deep Research Enabled** and **Deep Research Used**. + +## Verify it worked + +The response reflects reviewed evidence rather than only general model knowledge, and metadata can show that Deep Research was enabled and used. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **Research** is hidden | Source review is off or unavailable for the prompt | Add a valid URL or ask an admin to enable source review. | +| A URL count warning appears | The prompt exceeds the per-turn URL limit | Split URLs across multiple turns. | + +## Related + +- [Review pasted URLs]({{ '/guides/review-pasted-urls/' | relative_url }}) +- [Use web search]({{ '/guides/use-web-search/' | relative_url }}) +- [Search and Extract]({{ '/admin/knowledge/' | relative_url }}) diff --git a/docs/how-to/use_managed_identity.md b/docs/guides/use-managed-identity.md similarity index 98% rename from docs/how-to/use_managed_identity.md rename to docs/guides/use-managed-identity.md index 9f1cf5c5d..b5fc54862 100644 --- a/docs/how-to/use_managed_identity.md +++ b/docs/guides/use-managed-identity.md @@ -1,14 +1,17 @@ --- layout: showcase-page title: "Use Managed Identity" -permalink: /how-to/use_managed_identity/ +permalink: /guides/use-managed-identity/ menubar: docs_menu accent: teal eyebrow: "How-To Guide" description: "Use Azure Managed Identity to move Simple Chat away from stored API keys and toward resource-to-resource authentication with RBAC." hero_icons: ["bi-shield-lock", "bi-key", "bi-person-badge"] hero_pills: ["RBAC over stored secrets", "Service-by-service role assignment", "Validate then remove keys"] -hero_links: [{ label: "Admin configuration", url: "/reference/admin_configuration/", style: "primary" }, { label: "Speech MI manual setup", url: "/how-to/azure_speech_managed_identity_manul_setup/", style: "secondary" }] +hero_links: [{ label: "Admin configuration", url: "/reference/admin_configuration/", style: "primary" }, { label: "Speech MI manual setup", url: "/guides/azure-speech-managed-identity-setup/", style: "secondary" }] +section: "Guides" +redirect_from: + - /how-to/use_managed_identity/ --- Managed identity works best when you treat it as an incremental migration. Enable the identity, assign the right RBAC roles, switch each service configuration over deliberately, and only then remove the old keys. diff --git a/docs/guides/use-public-workspaces.md b/docs/guides/use-public-workspaces.md new file mode 100644 index 000000000..7db2f7621 --- /dev/null +++ b/docs/guides/use-public-workspaces.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Use public workspaces" +description: "Browse and manage shared public workspace content when your tenant enables it." +section: "Guides" +audience: user +--- + +## What this does + +Public workspaces make selected documents and prompts available through a shared workspace surface. This guide selects a public workspace, browses documents, and uses filters, tags, and chat actions where your role permits. + +{% include media.html type="video" + title="Use public workspaces walkthrough" + poster="video-posters/guide-use-public-workspaces.png" + capture="Recording planned. Show use public workspaces end to end and explain why this task helps a user." %} + +## Why you would use this + +Use public workspaces for curated materials intended for a broad audience, such as reference libraries, policies, onboarding packs, or published knowledge collections. They reduce duplicate uploads; use group workspaces instead when membership or contribution must be tighter. + +## Before you start + +- Admins must enable `enable_public_workspaces`; creation may require `require_member_of_create_public_workspace`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- Your role determines whether you can upload, manage prompts, tag documents, or only browse. +- Downloads and File Sync for public workspaces have separate admin controls. + +## Steps + +1. Open **Public Workspaces**. +2. Use **Select a workspace...** to choose the public workspace. +3. Review the role indicator. + +{% include media.html src="guides/use-public-workspaces-step-3.png" + alt="Screenshot showing use public workspaces step 3." + title="Use public workspaces step 3" + capture="Capture the use public workspaces task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. On **Documents**, use **Show Search/Filters** to search by file name, title, classification, author, keywords, abstract, or tags. +5. Switch between **List**, **Cards**, **Folders**, and **Folders + Cards** views as needed. + +{% include media.html src="guides/use-public-workspaces-step-5.png" + alt="Screenshot showing use public workspaces step 5." + title="Use public workspaces step 5" + capture="Capture the use public workspaces task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. If permitted, upload files, select documents, use **Chat with Selected**, or manage prompts from **Prompts**. + +## Verify it worked + +The selected public workspace shows owner, description, role, documents, and prompts. A chat started with selected documents uses public workspace files as context. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| You cannot upload or manage prompts | Your role does not allow management | Ask the public workspace owner to update your role. | +| Public workspace navigation is missing | Public workspaces are disabled | Ask an admin to enable `enable_public_workspaces`. | + +## Related + +- [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) +- [Create a file sync]({{ '/guides/create-a-file-sync/' | relative_url }}) +- [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}) diff --git a/docs/guides/use-tags-in-chat.md b/docs/guides/use-tags-in-chat.md new file mode 100644 index 000000000..c2fb4fe90 --- /dev/null +++ b/docs/guides/use-tags-in-chat.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Use tags in chat" +description: "Filter workspace documents by tag before asking grounded questions." +section: "Guides" +audience: user +--- + +## What this does + +Tags in chat narrow the document pool used by **Grounded Search**. This guide chooses scope, tags, optional documents, and a document action before sending a question. + +{% include media.html type="video" + title="Use tags in chat walkthrough" + poster="video-posters/guide-use-tags-in-chat.png" + capture="Recording planned. Show use tags in chat end to end and explain why this task helps a user." %} + +## Why you would use this + +Use tag filters when a workspace contains many documents but your question belongs to one project, case, client, or review batch. Tags reduce irrelevant citations and avoid selecting files one by one; skip them when you need every accessible document. + +## Before you start + +- Workspaces must be enabled with `enable_user_workspace`, `enable_group_workspaces`, or `enable_public_workspaces`; see [Workspaces settings]({{ '/admin/workspaces/' | relative_url }}). +- Documents must be processed and tagged. +- You need access to the workspace that contains the tagged documents. + +## Steps + +1. Open **Chat**. +2. Select **Workspaces** to open **Grounded Search**. +3. Under **Scope**, choose a workspace or leave **All**. + +{% include media.html src="guides/use-tags-in-chat-step-3.png" + alt="Screenshot showing use tags in chat step 3." + title="Use tags in chat step 3" + capture="Capture the use tags in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Under **Tags**, select one or more tags. +5. Leave **Document** at **All Documents** or choose specific documents to narrow further. +6. Choose **Search**, **Analyze**, or **Compare** in **Action**. + +{% include media.html src="guides/use-tags-in-chat-step-6.png" + alt="Screenshot showing use tags in chat step 6." + title="Use tags in chat step 6" + capture="Capture the use tags in chat task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Type your question and select **Send Message**. + +## Verify it worked + +The answer should cite or discuss documents from the selected tag set. The conversation may show **Scope locked** after a grounded response. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The **Tags** dropdown stays disabled | No tags loaded for the selected scope | Choose the correct scope and confirm the workspace has tags. | +| Expected files are missing | The documents are untagged or unprocessed | Check the workspace Documents tab and **Filter by Tags**. | + +## Related + +- [Create and manage tags]({{ '/guides/create-and-manage-tags/' | relative_url }}) +- [Lock workspace scope in chat]({{ '/guides/lock-workspace-scope/' | relative_url }}) +- [Agents settings]({{ '/admin/agents-actions/' | relative_url }}) diff --git a/docs/guides/use-tags-on-conversations.md b/docs/guides/use-tags-on-conversations.md new file mode 100644 index 000000000..0aa112254 --- /dev/null +++ b/docs/guides/use-tags-on-conversations.md @@ -0,0 +1,65 @@ +--- +layout: page +title: "Use tags on conversations" +description: "Organize conversation work by tagging the documents and document sets that drive the chat." +section: "Guides" +audience: user +--- + +## What this does + +SimpleChat tags are document organization, and those tags carry into chat when you select tagged documents for grounded work. This guide uses tagged document sets to keep conversations understandable. + +{% include media.html type="video" + title="Use tags on conversations walkthrough" + poster="video-posters/guide-use-tags-on-conversations.png" + capture="Recording planned. Show use tags on conversations end to end and explain why this task helps a user." %} + +## Why you would use this + +Conversation lists get hard to scan when many chats have similar questions. Tagging the underlying documents gives you a reliable way to return to the same source set, start a fresh thread, or explain which files shaped an answer. This is not direct conversation labeling. + +## Before you start + +- The relevant workspace must be enabled with `enable_user_workspace`, `enable_group_workspaces`, or `enable_public_workspaces`. +- Tags must already exist on the documents you plan to use. +- You need chat and document access for the workspace. + +## Steps + +1. Open the workspace **Documents** tab. +2. Use **Filter by Tags** or **Folders** to confirm the source set. +3. Select **Multi-select** and choose the tagged documents. + +{% include media.html src="guides/use-tags-on-conversations-step-3.png" + alt="Screenshot showing use tags on conversations step 3." + title="Use tags on conversations step 3" + capture="Capture the use tags on conversations task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Select **Chat with Selected**. +5. Ask a focused question about the selected document set. +6. Rename the conversation so the task is clear. + +{% include media.html src="guides/use-tags-on-conversations-step-6.png" + alt="Screenshot showing use tags on conversations step 6." + title="Use tags on conversations step 6" + capture="Capture the use tags on conversations task at this step in SimpleChat with realistic sample data and redact secrets." %} + +7. Pin important threads and use export for handoff or archiving. + +## Verify it worked + +The conversation is grounded in the selected tagged documents, and you can return to the same tag later to start another conversation from that source set. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| You cannot find the same source set later | Documents were not tagged consistently | Apply one shared tag to the set. | +| The chat includes too many sources | The tag is too broad or all documents were selected | Create a narrower tag or select specific documents. | + +## Related + +- [Create and manage tags]({{ '/guides/create-and-manage-tags/' | relative_url }}) +- [Use tags in chat]({{ '/guides/use-tags-in-chat/' | relative_url }}) +- [Export a conversation]({{ '/guides/export-a-conversation/' | relative_url }}) diff --git a/docs/guides/use-web-search.md b/docs/guides/use-web-search.md new file mode 100644 index 000000000..29cda26d8 --- /dev/null +++ b/docs/guides/use-web-search.md @@ -0,0 +1,64 @@ +--- +layout: page +title: "Use web search" +description: "Send the current chat message to Bing web search when fresh web information is needed." +section: "Guides" +audience: user +--- + +## What this does + +**Web** turns on Bing web search for the current chat message. When the user notice is enabled, SimpleChat explains that only the current message is sent for web search. + +{% include media.html type="video" + title="Use web search walkthrough" + poster="video-posters/guide-use-web-search.png" + capture="Recording planned. Show use web search end to end and explain why this task helps a user." %} + +## Why you would use this + +Use web search when an answer depends on current public information rather than workspace documents or model memory. It replaces searching the web yourself and pasting snippets; it is wrong for confidential prompts or answers that must come only from approved internal files. + +## Before you start + +- Admins must enable `enable_web_search` and complete consent/configuration; see [Search and Extract]({{ '/admin/knowledge/' | relative_url }}). +- `enable_web_search_user_notice` may show a notice while web search is active. +- Do not include sensitive content in a web-search prompt. + +## Steps + +1. Open **Chat**. +2. Write a question that needs current public information. +3. Select **Web**. + +{% include media.html src="guides/use-web-search-step-3.png" + alt="Screenshot showing use web search step 3." + title="Use web search step 3" + capture="Capture the use web search task at this step in SimpleChat with realistic sample data and redact secrets." %} + +4. Read and dismiss the web search notice if it appears. +5. Select **Send Message**. + +{% include media.html src="guides/use-web-search-step-5.png" + alt="Screenshot showing use web search step 5." + title="Use web search step 5" + capture="Capture the use web search task at this step in SimpleChat with realistic sample data and redact secrets." %} + +6. Turn **Web** off for later messages that should not use public search. + +## Verify it worked + +The answer should use web search when relevant. The notice appears only while web search is active and the tenant settings allow it. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| **Web** is missing | Web search is disabled or consent is not accepted | Ask an admin to review `enable_web_search`. | +| Web turns off in **Image** mode | Image generation disables source controls | Turn off **Image** first. | + +## Related + +- [Review pasted URLs]({{ '/guides/review-pasted-urls/' | relative_url }}) +- [Use deep research]({{ '/guides/use-deep-research/' | relative_url }}) +- [Search and Extract]({{ '/admin/knowledge/' | relative_url }}) diff --git a/docs/how-to/index.md b/docs/how-to/index.md index 9035aa195..3010d82fa 100644 --- a/docs/how-to/index.md +++ b/docs/how-to/index.md @@ -1,46 +1,9 @@ --- -layout: showcase-page -title: "How-To Guides" +layout: page +title: "How-to guides moved to Guides" +description: "Redirects the former How-to section to the unified Guides index." permalink: /how-to/ -menubar: docs_menu -accent: teal -eyebrow: "Task-Oriented Guidance" -description: "Use the how-to section for practical workflows after the platform is already understood: uploads, agents, identity, scaling, upgrades, networking, and external integrations." -hero_icons: ["bi-tools", "bi-signpost-split", "bi-check2-square"] -hero_pills: ["Repeatable operator tasks", "Platform and integration workflows", "Shorter than full reference pages"] -hero_links: [{ label: "Tutorials", url: "/tutorials/", style: "primary" }, { label: "Reference section", url: "/reference/", style: "secondary" }] +redirect_to: /guides/ --- -How-to guides assume you already know what area of the product you are working in. They focus on practical execution: the steps, dependencies, and validation checks needed to complete a specific task. - -
- - - -
-
-

Deployment operations

-

Use these guides for enterprise networking, scaling, upgrades, and Docker customization once the deployment starts moving toward production operations.

-

Enterprise networking · Scaling · Upgrade paths

-
-
- -
-

Choose the shortest path that solves the task

-

If you need conceptual background, switch to the explanation pages. If you need exact settings or route details, switch to the reference pages. Stay in the how-to section when the task itself is already clear and you want the execution sequence.

-
\ No newline at end of file +This section has moved to [Guides]({{ '/guides/' | relative_url }}). diff --git a/docs/images/latest-release/release_250_ai_access.png b/docs/images/latest-release/release_250_ai_access.png new file mode 100644 index 000000000..1bbac7d34 Binary files /dev/null and b/docs/images/latest-release/release_250_ai_access.png differ diff --git a/docs/images/latest-release/release_250_assigned_knowledge.png b/docs/images/latest-release/release_250_assigned_knowledge.png new file mode 100644 index 000000000..defb0a466 Binary files /dev/null and b/docs/images/latest-release/release_250_assigned_knowledge.png differ diff --git a/docs/images/latest-release/release_250_chat_uploads.png b/docs/images/latest-release/release_250_chat_uploads.png new file mode 100644 index 000000000..c49f3991e Binary files /dev/null and b/docs/images/latest-release/release_250_chat_uploads.png differ diff --git a/docs/images/latest-release/release_250_conversation_feed.png b/docs/images/latest-release/release_250_conversation_feed.png new file mode 100644 index 000000000..a55ffd96a Binary files /dev/null and b/docs/images/latest-release/release_250_conversation_feed.png differ diff --git a/docs/images/latest-release/release_250_custom_pages.png b/docs/images/latest-release/release_250_custom_pages.png new file mode 100644 index 000000000..b92210bac Binary files /dev/null and b/docs/images/latest-release/release_250_custom_pages.png differ diff --git a/docs/images/latest-release/release_250_databricks_action.png b/docs/images/latest-release/release_250_databricks_action.png new file mode 100644 index 000000000..fa32c1e68 Binary files /dev/null and b/docs/images/latest-release/release_250_databricks_action.png differ diff --git a/docs/images/latest-release/release_250_deep_research.png b/docs/images/latest-release/release_250_deep_research.png new file mode 100644 index 000000000..1f6b7316d Binary files /dev/null and b/docs/images/latest-release/release_250_deep_research.png differ diff --git a/docs/images/latest-release/release_250_document_intelligence.png b/docs/images/latest-release/release_250_document_intelligence.png new file mode 100644 index 000000000..8bf13920c Binary files /dev/null and b/docs/images/latest-release/release_250_document_intelligence.png differ diff --git a/docs/images/latest-release/release_250_file_sync.png b/docs/images/latest-release/release_250_file_sync.png new file mode 100644 index 000000000..f6c7fb5f6 Binary files /dev/null and b/docs/images/latest-release/release_250_file_sync.png differ diff --git a/docs/images/latest-release/release_250_follow_up_actions.png b/docs/images/latest-release/release_250_follow_up_actions.png new file mode 100644 index 000000000..e95f97ee5 Binary files /dev/null and b/docs/images/latest-release/release_250_follow_up_actions.png differ diff --git a/docs/images/latest-release/release_250_generated_documents.png b/docs/images/latest-release/release_250_generated_documents.png new file mode 100644 index 000000000..307e781e3 Binary files /dev/null and b/docs/images/latest-release/release_250_generated_documents.png differ diff --git a/docs/images/latest-release/release_250_group_file_sharing.png b/docs/images/latest-release/release_250_group_file_sharing.png new file mode 100644 index 000000000..aed55af25 Binary files /dev/null and b/docs/images/latest-release/release_250_group_file_sharing.png differ diff --git a/docs/images/latest-release/release_250_layered_masking.png b/docs/images/latest-release/release_250_layered_masking.png new file mode 100644 index 000000000..b2f99ed8d Binary files /dev/null and b/docs/images/latest-release/release_250_layered_masking.png differ diff --git a/docs/images/latest-release/release_250_m365_actions.png b/docs/images/latest-release/release_250_m365_actions.png new file mode 100644 index 000000000..0b3705d96 Binary files /dev/null and b/docs/images/latest-release/release_250_m365_actions.png differ diff --git a/docs/images/latest-release/release_250_model_agent_avatars.png b/docs/images/latest-release/release_250_model_agent_avatars.png new file mode 100644 index 000000000..7b268c413 Binary files /dev/null and b/docs/images/latest-release/release_250_model_agent_avatars.png differ diff --git a/docs/images/latest-release/release_250_profile_stats.png b/docs/images/latest-release/release_250_profile_stats.png new file mode 100644 index 000000000..1b3c6b062 Binary files /dev/null and b/docs/images/latest-release/release_250_profile_stats.png differ diff --git a/docs/images/latest-release/release_250_source_continuity.png b/docs/images/latest-release/release_250_source_continuity.png new file mode 100644 index 000000000..e03fc7fa8 Binary files /dev/null and b/docs/images/latest-release/release_250_source_continuity.png differ diff --git a/docs/images/latest-release/release_250_tableau_action.png b/docs/images/latest-release/release_250_tableau_action.png new file mode 100644 index 000000000..8fb9199d9 Binary files /dev/null and b/docs/images/latest-release/release_250_tableau_action.png differ diff --git a/docs/images/latest-release/release_250_tabular_analysis.png b/docs/images/latest-release/release_250_tabular_analysis.png new file mode 100644 index 000000000..39029b187 Binary files /dev/null and b/docs/images/latest-release/release_250_tabular_analysis.png differ diff --git a/docs/images/latest-release/release_250_url_access.png b/docs/images/latest-release/release_250_url_access.png new file mode 100644 index 000000000..d61cb05c5 Binary files /dev/null and b/docs/images/latest-release/release_250_url_access.png differ diff --git a/docs/images/latest-release/release_250_visio_msg_ingestion.png b/docs/images/latest-release/release_250_visio_msg_ingestion.png new file mode 100644 index 000000000..fd21923f6 Binary files /dev/null and b/docs/images/latest-release/release_250_visio_msg_ingestion.png differ diff --git a/docs/images/latest-release/release_250_voice_assisted_inputs.png b/docs/images/latest-release/release_250_voice_assisted_inputs.png new file mode 100644 index 000000000..fe45091f7 Binary files /dev/null and b/docs/images/latest-release/release_250_voice_assisted_inputs.png differ diff --git a/docs/images/latest-release/release_250_workflows.png b/docs/images/latest-release/release_250_workflows.png new file mode 100644 index 000000000..3bc582055 Binary files /dev/null and b/docs/images/latest-release/release_250_workflows.png differ diff --git a/docs/images/latest-release/release_250_workspace_views.png b/docs/images/latest-release/release_250_workspace_views.png new file mode 100644 index 000000000..4c2e90aff Binary files /dev/null and b/docs/images/latest-release/release_250_workspace_views.png differ diff --git a/docs/images/latest-release/release_260_action_test_connection_1.png b/docs/images/latest-release/release_260_action_test_connection_1.png new file mode 100644 index 000000000..07329b0d1 Binary files /dev/null and b/docs/images/latest-release/release_260_action_test_connection_1.png differ diff --git a/docs/images/latest-release/release_260_action_test_connection_2.png b/docs/images/latest-release/release_260_action_test_connection_2.png new file mode 100644 index 000000000..355675d15 Binary files /dev/null and b/docs/images/latest-release/release_260_action_test_connection_2.png differ diff --git a/docs/images/latest-release/release_260_action_test_connection_3.png b/docs/images/latest-release/release_260_action_test_connection_3.png new file mode 100644 index 000000000..2b4890738 Binary files /dev/null and b/docs/images/latest-release/release_260_action_test_connection_3.png differ diff --git a/docs/images/latest-release/release_260_agent_instruction_references_1.png b/docs/images/latest-release/release_260_agent_instruction_references_1.png new file mode 100644 index 000000000..3fa10e330 Binary files /dev/null and b/docs/images/latest-release/release_260_agent_instruction_references_1.png differ diff --git a/docs/images/latest-release/release_260_agent_instruction_references_2.png b/docs/images/latest-release/release_260_agent_instruction_references_2.png new file mode 100644 index 000000000..5136e97d3 Binary files /dev/null and b/docs/images/latest-release/release_260_agent_instruction_references_2.png differ diff --git a/docs/images/latest-release/release_260_agent_instruction_references_3.png b/docs/images/latest-release/release_260_agent_instruction_references_3.png new file mode 100644 index 000000000..6eba1b586 Binary files /dev/null and b/docs/images/latest-release/release_260_agent_instruction_references_3.png differ diff --git a/docs/images/latest-release/release_260_audio_file_support_1.png b/docs/images/latest-release/release_260_audio_file_support_1.png new file mode 100644 index 000000000..59399a089 Binary files /dev/null and b/docs/images/latest-release/release_260_audio_file_support_1.png differ diff --git a/docs/images/latest-release/release_260_audio_file_support_2.png b/docs/images/latest-release/release_260_audio_file_support_2.png new file mode 100644 index 000000000..ae4d72911 Binary files /dev/null and b/docs/images/latest-release/release_260_audio_file_support_2.png differ diff --git a/docs/images/latest-release/release_260_audio_file_support_3.png b/docs/images/latest-release/release_260_audio_file_support_3.png new file mode 100644 index 000000000..7346dc050 Binary files /dev/null and b/docs/images/latest-release/release_260_audio_file_support_3.png differ diff --git a/docs/images/latest-release/release_260_azure_blob_file_sync_1.png b/docs/images/latest-release/release_260_azure_blob_file_sync_1.png new file mode 100644 index 000000000..4b0c05ae5 Binary files /dev/null and b/docs/images/latest-release/release_260_azure_blob_file_sync_1.png differ diff --git a/docs/images/latest-release/release_260_azure_blob_file_sync_2.png b/docs/images/latest-release/release_260_azure_blob_file_sync_2.png new file mode 100644 index 000000000..a4736bdb3 Binary files /dev/null and b/docs/images/latest-release/release_260_azure_blob_file_sync_2.png differ diff --git a/docs/images/latest-release/release_260_azure_blob_file_sync_3.png b/docs/images/latest-release/release_260_azure_blob_file_sync_3.png new file mode 100644 index 000000000..0598a0ae8 Binary files /dev/null and b/docs/images/latest-release/release_260_azure_blob_file_sync_3.png differ diff --git a/docs/images/latest-release/release_260_chat_ai_notice_1.png b/docs/images/latest-release/release_260_chat_ai_notice_1.png new file mode 100644 index 000000000..8bd457a05 Binary files /dev/null and b/docs/images/latest-release/release_260_chat_ai_notice_1.png differ diff --git a/docs/images/latest-release/release_260_chat_ai_notice_2.png b/docs/images/latest-release/release_260_chat_ai_notice_2.png new file mode 100644 index 000000000..076ca9e00 Binary files /dev/null and b/docs/images/latest-release/release_260_chat_ai_notice_2.png differ diff --git a/docs/images/latest-release/release_260_chat_ai_notice_3.png b/docs/images/latest-release/release_260_chat_ai_notice_3.png new file mode 100644 index 000000000..68400d6df Binary files /dev/null and b/docs/images/latest-release/release_260_chat_ai_notice_3.png differ diff --git a/docs/images/latest-release/release_260_chat_scroll_508_1.png b/docs/images/latest-release/release_260_chat_scroll_508_1.png new file mode 100644 index 000000000..eee4d555f Binary files /dev/null and b/docs/images/latest-release/release_260_chat_scroll_508_1.png differ diff --git a/docs/images/latest-release/release_260_chat_scroll_508_2.png b/docs/images/latest-release/release_260_chat_scroll_508_2.png new file mode 100644 index 000000000..83ebdf785 Binary files /dev/null and b/docs/images/latest-release/release_260_chat_scroll_508_2.png differ diff --git a/docs/images/latest-release/release_260_chat_scroll_508_3.png b/docs/images/latest-release/release_260_chat_scroll_508_3.png new file mode 100644 index 000000000..dc41151f9 Binary files /dev/null and b/docs/images/latest-release/release_260_chat_scroll_508_3.png differ diff --git a/docs/images/latest-release/release_260_completion_notifications_1.png b/docs/images/latest-release/release_260_completion_notifications_1.png new file mode 100644 index 000000000..17b25e46d Binary files /dev/null and b/docs/images/latest-release/release_260_completion_notifications_1.png differ diff --git a/docs/images/latest-release/release_260_completion_notifications_2.png b/docs/images/latest-release/release_260_completion_notifications_2.png new file mode 100644 index 000000000..8b1b3ce5f Binary files /dev/null and b/docs/images/latest-release/release_260_completion_notifications_2.png differ diff --git a/docs/images/latest-release/release_260_completion_notifications_3.png b/docs/images/latest-release/release_260_completion_notifications_3.png new file mode 100644 index 000000000..bb57e5ce0 Binary files /dev/null and b/docs/images/latest-release/release_260_completion_notifications_3.png differ diff --git a/docs/images/latest-release/release_260_conversation_contents_drawer_1.png b/docs/images/latest-release/release_260_conversation_contents_drawer_1.png new file mode 100644 index 000000000..b8bebe03e Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_contents_drawer_1.png differ diff --git a/docs/images/latest-release/release_260_conversation_contents_drawer_2.png b/docs/images/latest-release/release_260_conversation_contents_drawer_2.png new file mode 100644 index 000000000..38e5c441f Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_contents_drawer_2.png differ diff --git a/docs/images/latest-release/release_260_conversation_contents_drawer_3.png b/docs/images/latest-release/release_260_conversation_contents_drawer_3.png new file mode 100644 index 000000000..809696c71 Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_contents_drawer_3.png differ diff --git a/docs/images/latest-release/release_260_conversation_context_grounding_1.png b/docs/images/latest-release/release_260_conversation_context_grounding_1.png new file mode 100644 index 000000000..8fd41c5ed Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_context_grounding_1.png differ diff --git a/docs/images/latest-release/release_260_conversation_context_grounding_2.png b/docs/images/latest-release/release_260_conversation_context_grounding_2.png new file mode 100644 index 000000000..9b99ddb7e Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_context_grounding_2.png differ diff --git a/docs/images/latest-release/release_260_conversation_context_grounding_3.png b/docs/images/latest-release/release_260_conversation_context_grounding_3.png new file mode 100644 index 000000000..40c7bbaf9 Binary files /dev/null and b/docs/images/latest-release/release_260_conversation_context_grounding_3.png differ diff --git a/docs/images/latest-release/release_260_enhanced_extraction_1.png b/docs/images/latest-release/release_260_enhanced_extraction_1.png new file mode 100644 index 000000000..666f0c528 Binary files /dev/null and b/docs/images/latest-release/release_260_enhanced_extraction_1.png differ diff --git a/docs/images/latest-release/release_260_enhanced_extraction_2.png b/docs/images/latest-release/release_260_enhanced_extraction_2.png new file mode 100644 index 000000000..a75c12221 Binary files /dev/null and b/docs/images/latest-release/release_260_enhanced_extraction_2.png differ diff --git a/docs/images/latest-release/release_260_enhanced_extraction_3.png b/docs/images/latest-release/release_260_enhanced_extraction_3.png new file mode 100644 index 000000000..b8053ad0d Binary files /dev/null and b/docs/images/latest-release/release_260_enhanced_extraction_3.png differ diff --git a/docs/images/latest-release/release_260_font_size_zoom_1.png b/docs/images/latest-release/release_260_font_size_zoom_1.png new file mode 100644 index 000000000..e7cf0a2db Binary files /dev/null and b/docs/images/latest-release/release_260_font_size_zoom_1.png differ diff --git a/docs/images/latest-release/release_260_font_size_zoom_2.png b/docs/images/latest-release/release_260_font_size_zoom_2.png new file mode 100644 index 000000000..802c01234 Binary files /dev/null and b/docs/images/latest-release/release_260_font_size_zoom_2.png differ diff --git a/docs/images/latest-release/release_260_font_size_zoom_3.png b/docs/images/latest-release/release_260_font_size_zoom_3.png new file mode 100644 index 000000000..ad606ce7e Binary files /dev/null and b/docs/images/latest-release/release_260_font_size_zoom_3.png differ diff --git a/docs/images/latest-release/release_260_mcp_platform_1.png b/docs/images/latest-release/release_260_mcp_platform_1.png new file mode 100644 index 000000000..50f357c30 Binary files /dev/null and b/docs/images/latest-release/release_260_mcp_platform_1.png differ diff --git a/docs/images/latest-release/release_260_mcp_platform_2.png b/docs/images/latest-release/release_260_mcp_platform_2.png new file mode 100644 index 000000000..39973c61c Binary files /dev/null and b/docs/images/latest-release/release_260_mcp_platform_2.png differ diff --git a/docs/images/latest-release/release_260_mcp_platform_3.png b/docs/images/latest-release/release_260_mcp_platform_3.png new file mode 100644 index 000000000..3b2350b55 Binary files /dev/null and b/docs/images/latest-release/release_260_mcp_platform_3.png differ diff --git a/docs/images/latest-release/release_260_message_audio_export_1.png b/docs/images/latest-release/release_260_message_audio_export_1.png new file mode 100644 index 000000000..1c56af3c4 Binary files /dev/null and b/docs/images/latest-release/release_260_message_audio_export_1.png differ diff --git a/docs/images/latest-release/release_260_message_audio_export_2.png b/docs/images/latest-release/release_260_message_audio_export_2.png new file mode 100644 index 000000000..92c382ee1 Binary files /dev/null and b/docs/images/latest-release/release_260_message_audio_export_2.png differ diff --git a/docs/images/latest-release/release_260_message_audio_export_3.png b/docs/images/latest-release/release_260_message_audio_export_3.png new file mode 100644 index 000000000..dcdc8f989 Binary files /dev/null and b/docs/images/latest-release/release_260_message_audio_export_3.png differ diff --git a/docs/images/latest-release/release_260_office_embedded_images_1.png b/docs/images/latest-release/release_260_office_embedded_images_1.png new file mode 100644 index 000000000..995c2a420 Binary files /dev/null and b/docs/images/latest-release/release_260_office_embedded_images_1.png differ diff --git a/docs/images/latest-release/release_260_office_embedded_images_2.png b/docs/images/latest-release/release_260_office_embedded_images_2.png new file mode 100644 index 000000000..8da6ebe85 Binary files /dev/null and b/docs/images/latest-release/release_260_office_embedded_images_2.png differ diff --git a/docs/images/latest-release/release_260_office_embedded_images_3.png b/docs/images/latest-release/release_260_office_embedded_images_3.png new file mode 100644 index 000000000..d5f045ccc Binary files /dev/null and b/docs/images/latest-release/release_260_office_embedded_images_3.png differ diff --git a/docs/images/latest-release/release_260_public_workspace_display_name_1.png b/docs/images/latest-release/release_260_public_workspace_display_name_1.png new file mode 100644 index 000000000..6cf13a73e Binary files /dev/null and b/docs/images/latest-release/release_260_public_workspace_display_name_1.png differ diff --git a/docs/images/latest-release/release_260_public_workspace_display_name_2.png b/docs/images/latest-release/release_260_public_workspace_display_name_2.png new file mode 100644 index 000000000..91e7c10ef Binary files /dev/null and b/docs/images/latest-release/release_260_public_workspace_display_name_2.png differ diff --git a/docs/images/latest-release/release_260_public_workspace_display_name_3.png b/docs/images/latest-release/release_260_public_workspace_display_name_3.png new file mode 100644 index 000000000..70b863f81 Binary files /dev/null and b/docs/images/latest-release/release_260_public_workspace_display_name_3.png differ diff --git a/docs/images/latest-release/release_260_rocksdb_action_1.png b/docs/images/latest-release/release_260_rocksdb_action_1.png new file mode 100644 index 000000000..0c1e431e4 Binary files /dev/null and b/docs/images/latest-release/release_260_rocksdb_action_1.png differ diff --git a/docs/images/latest-release/release_260_rocksdb_action_2.png b/docs/images/latest-release/release_260_rocksdb_action_2.png new file mode 100644 index 000000000..3eb7f4c59 Binary files /dev/null and b/docs/images/latest-release/release_260_rocksdb_action_2.png differ diff --git a/docs/images/latest-release/release_260_rocksdb_action_3.png b/docs/images/latest-release/release_260_rocksdb_action_3.png new file mode 100644 index 000000000..3a0eb2524 Binary files /dev/null and b/docs/images/latest-release/release_260_rocksdb_action_3.png differ diff --git a/docs/images/latest-release/release_260_terms_of_use_1.png b/docs/images/latest-release/release_260_terms_of_use_1.png new file mode 100644 index 000000000..4cfb1c7e2 Binary files /dev/null and b/docs/images/latest-release/release_260_terms_of_use_1.png differ diff --git a/docs/images/latest-release/release_260_terms_of_use_2.png b/docs/images/latest-release/release_260_terms_of_use_2.png new file mode 100644 index 000000000..2167dac08 Binary files /dev/null and b/docs/images/latest-release/release_260_terms_of_use_2.png differ diff --git a/docs/images/latest-release/release_260_terms_of_use_3.png b/docs/images/latest-release/release_260_terms_of_use_3.png new file mode 100644 index 000000000..7a63cea0d Binary files /dev/null and b/docs/images/latest-release/release_260_terms_of_use_3.png differ diff --git a/docs/images/latest-release/release_260_used_documents_fork_1.png b/docs/images/latest-release/release_260_used_documents_fork_1.png new file mode 100644 index 000000000..3461a89e2 Binary files /dev/null and b/docs/images/latest-release/release_260_used_documents_fork_1.png differ diff --git a/docs/images/latest-release/release_260_used_documents_fork_2.png b/docs/images/latest-release/release_260_used_documents_fork_2.png new file mode 100644 index 000000000..567ace5ad Binary files /dev/null and b/docs/images/latest-release/release_260_used_documents_fork_2.png differ diff --git a/docs/images/latest-release/release_260_used_documents_fork_3.png b/docs/images/latest-release/release_260_used_documents_fork_3.png new file mode 100644 index 000000000..519fa2619 Binary files /dev/null and b/docs/images/latest-release/release_260_used_documents_fork_3.png differ diff --git a/docs/images/latest-release/release_260_workflow_task_sequences_1.png b/docs/images/latest-release/release_260_workflow_task_sequences_1.png new file mode 100644 index 000000000..292df8c50 Binary files /dev/null and b/docs/images/latest-release/release_260_workflow_task_sequences_1.png differ diff --git a/docs/images/latest-release/release_260_workflow_task_sequences_2.png b/docs/images/latest-release/release_260_workflow_task_sequences_2.png new file mode 100644 index 000000000..02fc5cf33 Binary files /dev/null and b/docs/images/latest-release/release_260_workflow_task_sequences_2.png differ diff --git a/docs/images/latest-release/release_260_workflow_task_sequences_3.png b/docs/images/latest-release/release_260_workflow_task_sequences_3.png new file mode 100644 index 000000000..9d3d84775 Binary files /dev/null and b/docs/images/latest-release/release_260_workflow_task_sequences_3.png differ diff --git a/docs/images/latest-release/release_260_yamcs_action_1.png b/docs/images/latest-release/release_260_yamcs_action_1.png new file mode 100644 index 000000000..994045959 Binary files /dev/null and b/docs/images/latest-release/release_260_yamcs_action_1.png differ diff --git a/docs/images/latest-release/release_260_yamcs_action_2.png b/docs/images/latest-release/release_260_yamcs_action_2.png new file mode 100644 index 000000000..74aa83eba Binary files /dev/null and b/docs/images/latest-release/release_260_yamcs_action_2.png differ diff --git a/docs/images/latest-release/release_260_yamcs_action_3.png b/docs/images/latest-release/release_260_yamcs_action_3.png new file mode 100644 index 000000000..f7abecf2c Binary files /dev/null and b/docs/images/latest-release/release_260_yamcs_action_3.png differ diff --git a/docs/index.md b/docs/index.md index 396c2b254..e39893256 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,219 +1,113 @@ --- -layout: showcase-page +layout: page title: "Simple Chat Documentation" -description: "Azure-native documentation for deploying, operating, and extending Simple Chat with the same polished visual language as the latest-release experience." -section: "Overview" -accent: blue -eyebrow: "Docs Overview" -home_search: true -hero_icons: - - bi-house-fill - - bi-stars - - bi-rocket-takeoff -hero_pills: - - Azure OpenAI - - Retrieval-Augmented Generation - - Enterprise controls -hero_links: - - label: "Start the guide" - url: /tutorials/ - style: primary - - label: "Open API reference" - url: /reference/api_reference/ - style: outline +description: "Deploy, operate, and extend Simple Chat: an Azure-native chat platform with retrieval, workspaces, agents, and enterprise controls." +section: "Start" +hide_toc: false --- -Simple Chat gives teams an Azure-native way to deploy, ground, govern, and extend AI experiences without stitching together a separate chat app, search layer, and admin plane. - -
-
-
-
Start here
-

The shortest paths through the docs

-

Use these entry points when you want guidance, API details, examples, and release history without hunting through the entire docs tree.

-
- Top routes -
- -
-
-
-
- - Deployment -
-

Getting Started

-

Pick the right deployment path, line up prerequisites, and follow the repo's recommended order of operations.

- -
-
- -
-
-
- - Guide -
-

Tutorials

-

Follow guided product paths for first deployment, document upload, agents, and classification.

- -
-
- -
-
-
- - Capabilities -
-

Features

-

See the core workspace experience, optional feature packs, platform services, and supported file types in one place.

- -
-
- -
-
-
- - Reference -
-

API Reference

-

Find live Swagger endpoints, OpenAPI helper routes, and the right source of truth for route review.

- -
-
- -
-
-
- - Admin UI -
-

Branding and Support Settings

-

Change logo, title, home page text, health checks, Swagger, classification banner, Support, external links, and system settings.

- -
-
- -
-
-
- - Examples -
-

Scenario Library

-

Browse workspace and agent scenarios that show how Simple Chat can be shaped for practical team workflows.

- -
-
- -
-
-
- - Changelog -
-

Release Notes

-

Track feature additions, UI improvements, and fixes by version when you need implementation history.

- -
-
-
-
- -
-
-
-
Why teams use it
-

One application, multiple working modes

-

Simple Chat is opinionated about the hard parts: identity, grounding, document processing, and admin controls are already wired together.

-
- Azure-native -
- -
-
-
-
- - Chat + grounding -
-

Context-aware AI conversations

-

Use Azure OpenAI with hybrid retrieval over personal, group, and public workspace content so responses stay tied to your own data.

-
-
- -
-
-
- - Documents -
-

Document pipelines that stay searchable

-

Ingest PDFs, Office files, images, audio, and video through Azure AI services, then retrieve them with citations and optional metadata enrichment.

-
-
- -
-
-
- - Governance -
-

Controls for enterprise rollouts

-

Layer on Entra ID roles, content safety, feedback review, conversation archiving, and operational logging without rebuilding the app.

-
-
-
-
- -
-
-
-
Architecture
-

What sits behind the experience

-

The platform runs on Azure App Service and composes search, storage, document processing, and conversation state into a single application surface.

-
- Reference view -
- -
-

Platform at a glance

-

Core application state lives in Azure Cosmos DB, document retrieval runs through Azure AI Search, ingestion is handled by Azure AI Document Intelligence and related media services, and authentication uses Entra ID. That combination makes it practical to run Simple Chat as a governed internal tool instead of a demo-only sample.

-
- -
- Architecture diagram showing Simple Chat running on Azure App Service with Azure OpenAI, AI Search, Cosmos DB, and storage services. -
-
- -
-
-
-
Contribute
-

Working in the repo

-

The docs, app, and deployers are all maintained together, so the contributor guide is the fastest way to align with the expected workflow.

-
- Collaboration -
- -
-

Want to make changes?

-

Use the Contributing guide for the fork-based workflow, target branch expectations, and local development references before you start editing code or docs.

-
-
+Simple Chat gives teams an Azure-native way to deploy, ground, govern, and +extend AI experiences without stitching together a separate chat app, search +layer, and admin plane. + +New here? Start with [what Simple Chat is]({{ '/start/about/' | relative_url }}), then +pick a [deployment path]({{ '/start/deployment-options/' | relative_url }}). + +## Find what you need + +| I want to... | Go to | +| --- | --- | +| Understand what Simple Chat is | [About Simple Chat]({{ '/start/about/' | relative_url }}) | +| Deploy it for the first time | [Choose a deployment path]({{ '/start/deployment-options/' | relative_url }}) | +| Learn a task step by step | [Guides]({{ '/guides/' | relative_url }}) | +| Configure a setting | [Admin settings]({{ '/admin/' | relative_url }}) | +| See what the product can do | [Feature map]({{ '/features/' | relative_url }}) | +| Look up an API | [API reference]({{ '/reference/api_reference/' | relative_url }}) | +| See what changed | [Release notes]({{ '/explanation/release_notes/' | relative_url }}) | +| Find something specific | [Search the docs]({{ '/search/' | relative_url }}) | + +## Documentation sections + +### Start + +Orientation and first deployment. Covers what Simple Chat is, the deployment +options and their tradeoffs, the architecture, and the +[FAQ]({{ '/start/faqs/' | relative_url }}). + +### Guides + +Task-oriented, step-by-step instructions for users and admins. Each guide +explains what the task does and why you would do it before listing the steps. +See [tutorials]({{ '/guides/' | relative_url }}) and +[how-to guides]({{ '/guides/' | relative_url }}). + +### Features + +What the product can do, including the +[feature map]({{ '/features/' | relative_url }}), +[latest release highlights]({{ '/latest-release/' | relative_url }}), and +[worked scenarios]({{ '/explanation/scenarios/' | relative_url }}). + +### Administration + +One page per [admin settings tab]({{ '/admin/' | relative_url }}), covering what +each tab controls, why it matters, every setting and its default, prerequisites, +and the common tasks admins perform there. + +### Deploy and operate + +[Deployment paths]({{ '/reference/deploy/' | relative_url }}) for Azure Developer +CLI, Bicep, Terraform, Azure CLI, and manual setup, plus +[networking]({{ '/guides/enterprise-networking/' | relative_url }}), +[scaling]({{ '/application_scaling/' | relative_url }}), +[upgrades]({{ '/guides/upgrade-paths/' | relative_url }}), and +[troubleshooting]({{ '/troubleshooting/' | relative_url }}). + +### Reference + +[API reference]({{ '/reference/api_reference/' | relative_url }}), configuration +reference, [application workflows]({{ '/reference/application-workflows/' | relative_url }}), +and [release notes]({{ '/explanation/release_notes/' | relative_url }}). + +## What Simple Chat does + +### Context-aware AI conversations + +Uses Azure OpenAI with hybrid retrieval over personal, group, and public +workspace content, so responses stay tied to your own data rather than to the +model's general knowledge. + +### Document pipelines that stay searchable + +Ingests PDFs, Office files, images, audio, and video through Azure AI services, +then retrieves them with citations and optional metadata enrichment. + +### Controls for enterprise rollouts + +Adds Entra ID roles, content safety, feedback review, conversation archiving, +and operational logging without rebuilding the application. + +## Platform at a glance + +Core application state lives in Azure Cosmos DB. Document retrieval runs through +Azure AI Search. Ingestion is handled by Azure AI Document Intelligence and +related media services. Authentication uses Entra ID. + +That combination makes it practical to run Simple Chat as a governed internal +tool rather than a demo-only sample. + +{% include media.html src="architecture.png" + alt="Architecture diagram showing Simple Chat running on Azure App Service with Azure OpenAI, Azure AI Search, Cosmos DB, and storage services." + title="Simple Chat architecture" + caption="Simple Chat on Azure App Service, composing search, storage, document processing, and conversation state into one application." %} + +## Contributing + +The docs, application, and deployers are maintained together. Read the +[contributing guide]({{ '/contributing/' | relative_url }}) for the fork-based +workflow, target branch expectations, and local development setup before editing +code or documentation. + +Documentation contributors should also check the +[media status page]({{ '/contributing/media-status/' | relative_url }}), which +lists every screenshot and video slot that still needs to be captured. diff --git a/docs/latest-release/index.md b/docs/latest-release/index.md index 1c0b8e056..baa9ea873 100644 --- a/docs/latest-release/index.md +++ b/docs/latest-release/index.md @@ -5,7 +5,7 @@ description: "Current feature guides with previous release highlights kept for r section: "Latest Release" --- -This page mirrors the curated in-app Latest Features experience for SimpleChat v0.250.001, keeps the v0.241.001-v0.241.007 feature set in Previous Release, and preserves v0.239.001 highlights in the archive below it. +This page mirrors the curated in-app Latest Features experience for SimpleChat v0.260.001, keeps the v0.250.001 feature set in Previous Release, and preserves v0.239.001-v0.241.007 highlights in the archive below it. ## Admin Setup and User View @@ -13,6 +13,6 @@ Admins control whether Latest Features are visible from the Support menu and can Annotated Admin Settings screenshot showing Support menu and user-facing Latest Features controls. -Users see the curated set from **Support > Latest Features**. The v0.250.001 feature guides below use placeholder screenshot filenames that can be replaced with final captures, while previous release guides keep the existing annotated screenshots where available. +Users see the curated set from **Support > Latest Features**. The v0.260.001 feature guides below use copied app-gallery screenshots, including branded pending placeholders where final captures are still needed, while v0.250.001 and archive guides remain available below. Annotated user-facing Latest Features page showing current release cards. diff --git a/docs/latest-release/release-260-action-test-connection.md b/docs/latest-release/release-260-action-test-connection.md new file mode 100644 index 000000000..f7d81558e --- /dev/null +++ b/docs/latest-release/release-260-action-test-connection.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Test Connection Before You Save an Action" +description: "Test Connection is now available across twelve action types, verifying credentials and reachability without making you re-enter stored secrets." +section: "Latest Release" +--- + +Current release version for Test Connection Before You Save an Action: **0.260.001** + +OpenAPI, Azure Maps, Blob Storage, Databricks, Log Analytics, MCP, Snowflake, Tableau, RocksDB, Yamcs, SQL, and Cosmos DB actions all support Test Connection. The test resolves secrets stored in Key Vault on the server side, so you never retype a credential to check it. A successful test reports useful detail about what it reached, and a failure names the specific cause rather than a generic error. + +## User Side + +Test Connection is now available across twelve action types, verifying credentials and reachability without making you re-enter stored secrets. + +## Admin Side + +Admins decide whether Test Connection Before You Save an Action is available in your environment. If you cannot find Open Personal Workspace and Open Agents, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Test Connection Before You Save an Action. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_action_test_connection_1.png` +- `/images/latest-release/release_260_action_test_connection_2.png` +- `/images/latest-release/release_260_action_test_connection_3.png` + +## Why It Matters + +This matters because a broken action used to surface as a confusing failure mid-conversation, and now you find out at setup time with a message that tells you what to fix. + +## How to Try It + +1. Open Personal Workspace and go to the Actions section. +2. Create a new action or open an existing one that is not behaving as expected. +3. Fill in the connection details for the action type you are configuring. +4. Click Test Connection and wait for the result rather than saving immediately. +5. On success, read the returned detail to confirm you reached the intended system and scope. +6. On failure, read the named cause and correct just that field, then test again. +7. Save the action once the test passes, then attach it to an agent. + +## Notes + +- The Test Connection Before You Save an Action guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_action_test_connection_1.png`, `release_260_action_test_connection_2.png`, `release_260_action_test_connection_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-agent-instruction-references.md b/docs/latest-release/release-260-agent-instruction-references.md new file mode 100644 index 000000000..fd2a480a7 --- /dev/null +++ b/docs/latest-release/release-260-agent-instruction-references.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Reference Actions and Knowledge Directly in Agent Instructions" +description: "Agent instructions can now name the exact actions and documents an agent holds using autocompleted hash-action and hash-knowledge tokens, and the agent builder reorders its steps so instructions come last." +section: "Latest Release" +--- + +Current release version for Reference Actions and Knowledge Directly in Agent Instructions: **0.260.001** + +While writing agent instructions you can type a hash character to open an autocomplete listing the actions, capabilities, and documents that agent actually has, then insert a precise reference instead of describing the tool in prose. The agent modal now runs Actions, then Knowledge, then Instructions, so you choose capabilities before you write about them, and a collapsible summary panel shows your selections while you write. + +## User Side + +Agent instructions can now name the exact actions and documents an agent holds using autocompleted hash-action and hash-knowledge tokens, and the agent builder reorders its steps so instructions come last. + +## Admin Side + +Admins decide whether Reference Actions and Knowledge Directly in Agent Instructions is available in your environment. If you cannot find Open Agents and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Reference Actions and Knowledge Directly in Agent Instructions. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_agent_instruction_references_1.png` +- `/images/latest-release/release_260_agent_instruction_references_2.png` +- `/images/latest-release/release_260_agent_instruction_references_3.png` + +## Why It Matters + +This matters because vague instructions are the most common reason an agent ignores a tool you gave it, and naming the capability directly removes that guesswork. + +## How to Try It + +1. Open Agents and create a new agent or edit an existing one. +2. Work through the Actions step first and attach the actions this agent should be able to call. +3. Move to the Knowledge step and select the workspaces or documents it should ground on. +4. Continue to the Instructions step and expand the summary panel to review what you selected. +5. Start typing a hash character in the instruction editor to open the reference autocomplete. +6. Insert an action or knowledge reference so the instruction names the capability exactly. +7. Use Draft Instructions if you want a starting point, then save and test the agent in Chat. + +## Notes + +- The Reference Actions and Knowledge Directly in Agent Instructions guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_agent_instruction_references_1.png`, `release_260_agent_instruction_references_2.png`, `release_260_agent_instruction_references_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-audio-file-support.md b/docs/latest-release/release-260-audio-file-support.md new file mode 100644 index 000000000..192b48748 --- /dev/null +++ b/docs/latest-release/release-260-audio-file-support.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Upload Almost Any Audio File" +description: "Audio uploads now cover a much wider set of formats including AAC, FLAC, M4A, OGG, Opus, WAV, WebM audio, and WMA." +section: "Latest Release" +--- + +Current release version for Upload Almost Any Audio File: **0.260.001** + +Media handling is now bundled directly into SimpleChat container builds, which greatly expands the audio formats it can recognize and transcribe. Transcription degrades gracefully when the media tooling is unavailable rather than failing outright, and iPhone M4A voice memo uploads work correctly. + +## User Side + +Audio uploads now cover a much wider set of formats including AAC, FLAC, M4A, OGG, Opus, WAV, WebM audio, and WMA. + +## Admin Side + +Admins decide whether Upload Almost Any Audio File is available in your environment. If you cannot find Open Personal Workspace and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Upload Almost Any Audio File. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_audio_file_support_1.png` +- `/images/latest-release/release_260_audio_file_support_2.png` +- `/images/latest-release/release_260_audio_file_support_3.png` + +## Why It Matters + +This matters because meeting recordings and voice memos arrive in whatever format the recording device produced, and converting them by hand first was a real obstacle. + +## How to Try It + +1. Open Personal Workspace and go to the Documents section. +2. Upload an audio file in whatever format you have, such as an iPhone M4A voice memo or a WAV recording. +3. Wait for the file to finish processing and produce a transcript. +4. Open the document details and read the transcript that was generated. +5. Open Chat and ground a conversation on that audio file. +6. Ask for a summary, decisions, or action items from the recording. +7. Check the returned citation to confirm it points back at the audio document. + +## Notes + +- The Upload Almost Any Audio File guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_audio_file_support_1.png`, `release_260_audio_file_support_2.png`, `release_260_audio_file_support_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-azure-blob-file-sync.md b/docs/latest-release/release-260-azure-blob-file-sync.md new file mode 100644 index 000000000..5f09de0a9 --- /dev/null +++ b/docs/latest-release/release-260-azure-blob-file-sync.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Sync Documents From Azure Blob Storage" +description: "Azure Blob Storage containers can now be used as File Sync sources for personal, group, and public workspaces, with folder browsing and change detection." +section: "Latest Release" +--- + +Current release version for Sync Documents From Azure Blob Storage: **0.260.001** + +Blob Storage joins the existing File Sync connectors. Sources support managed identity, Key Vault backed service principals, connection strings, and SAS tokens, and you can browse virtual folders rather than typing paths blind. Change detection uses ETags so only changed blobs are reprocessed, and prefix and filter controls keep a sync narrow. + +## User Side + +Azure Blob Storage containers can now be used as File Sync sources for personal, group, and public workspaces, with folder browsing and change detection. + +## Admin Side + +Admins decide whether Sync Documents From Azure Blob Storage is available in your environment. If you cannot find Open Workspace Sync and Open Group Workspaces, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Sync Documents From Azure Blob Storage. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_azure_blob_file_sync_1.png` +- `/images/latest-release/release_260_azure_blob_file_sync_2.png` +- `/images/latest-release/release_260_azure_blob_file_sync_3.png` + +## Why It Matters + +This matters because a lot of organizational content already lives in blob containers, and syncing it keeps workspace documents current without manual re-uploads. + +## How to Try It + +1. Open Personal Workspace and go to the Sync section. +2. Add a new sync source and choose Azure Blob Storage. +3. Select the authentication method your container uses, such as managed identity or a SAS token. +4. Browse the virtual folders in the container and pick the prefix you want to sync. +5. Apply filters so only the file types you care about are pulled in. +6. Run the sync and watch the status, counts, and history for that source. +7. Open the Documents section and confirm the synced files appear with their sync badges. + +## Notes + +- The Sync Documents From Azure Blob Storage guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_azure_blob_file_sync_1.png`, `release_260_azure_blob_file_sync_2.png`, `release_260_azure_blob_file_sync_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-chat-ai-notice.md b/docs/latest-release/release-260-chat-ai-notice.md new file mode 100644 index 000000000..23544973a --- /dev/null +++ b/docs/latest-release/release-260-chat-ai-notice.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "AI Usage Guidance in Chat" +description: "Your organization can display its own AI guidance directly under the chat composer, with control over how often it reappears." +section: "Latest Release" +--- + +Current release version for AI Usage Guidance in Chat: **0.260.001** + +Admins write the notice in Markdown and choose how it behaves: always visible, dismissible for the session, dismissible for the day, or dismissible until the wording changes. If your organization updates the text, the notice comes back automatically so you see the current guidance rather than a stale version you dismissed months ago. + +## User Side + +Your organization can display its own AI guidance directly under the chat composer, with control over how often it reappears. + +## Admin Side + +Admins decide whether AI Usage Guidance in Chat is available in your environment. If you cannot find Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for AI Usage Guidance in Chat. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_chat_ai_notice_1.png` +- `/images/latest-release/release_260_chat_ai_notice_2.png` +- `/images/latest-release/release_260_chat_ai_notice_3.png` + +## Why It Matters + +This matters because AI usage rules differ by organization, and the reminder is most useful sitting right where you type rather than buried in a policy document. + +## How to Try It + +1. Open Chat and look directly beneath the message composer. +2. Read the AI usage notice if your organization has configured one. +3. Follow any links in the notice for your local policy details. +4. Dismiss the notice if your admins allowed dismissal and you have read it. +5. Expect it to return based on the schedule your admins chose, such as each session or each day. +6. Watch for it to reappear automatically whenever your organization updates the wording. +7. Contact your admin if the guidance looks out of date for your team. + +## Notes + +- The AI Usage Guidance in Chat guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_chat_ai_notice_1.png`, `release_260_chat_ai_notice_2.png`, `release_260_chat_ai_notice_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-chat-scroll-508.md b/docs/latest-release/release-260-chat-scroll-508.md new file mode 100644 index 000000000..a3be31e3c --- /dev/null +++ b/docs/latest-release/release-260-chat-scroll-508.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Chat Stops Yanking You to the Bottom" +description: "The conversation no longer jumps to the bottom while you are reading further up, and a floating control takes you to the newest content when you want it." +section: "Latest Release" +--- + +Current release version for Chat Stops Yanking You to the Bottom: **0.260.001** + +Previously, new streaming content pulled the viewport down even when you had deliberately scrolled up to read something. Now the view stays where you put it, and a floating scroll-to-latest button appears when there is newer content below your current position. The change also improves the experience for keyboard users and screen reader testing. + +## User Side + +The conversation no longer jumps to the bottom while you are reading further up, and a floating control takes you to the newest content when you want it. + +## Admin Side + +Admins decide whether Chat Stops Yanking You to the Bottom is available in your environment. If you cannot find Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Chat Stops Yanking You to the Bottom. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_chat_scroll_508_1.png` +- `/images/latest-release/release_260_chat_scroll_508_2.png` +- `/images/latest-release/release_260_chat_scroll_508_3.png` + +## Why It Matters + +This matters because losing your place mid-paragraph while an answer is still streaming makes long responses genuinely hard to read. + +## How to Try It + +1. Open Chat and ask a question that produces a long streaming answer. +2. While it is still generating, scroll up to re-read an earlier part of the response. +3. Confirm the view stays where you put it instead of snapping back down. +4. Look for the floating scroll-to-latest control that appears near the bottom. +5. Select it when you are ready to return to the newest content. +6. Try the same flow using only the keyboard to move through the conversation. +7. Confirm focus order stays sensible as new content arrives. + +## Notes + +- The Chat Stops Yanking You to the Bottom guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_chat_scroll_508_1.png`, `release_260_chat_scroll_508_2.png`, `release_260_chat_scroll_508_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-completion-notifications.md b/docs/latest-release/release-260-completion-notifications.md new file mode 100644 index 000000000..a058da0bf --- /dev/null +++ b/docs/latest-release/release-260-completion-notifications.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Know When a Long Answer Finishes" +description: "You can opt in to a sound when a response completes, a desktop notification when the answer lands in a tab you are not looking at, or both." +section: "Latest Release" +--- + +Current release version for Know When a Long Answer Finishes: **0.260.001** + +Two independent opt-in preferences live on your profile. Completion audio cues offer ten bundled sounds with volume control and a preview button. Desktop notifications use your browser notification permission and appear when the response finishes in a hidden or unfocused tab, showing only the app and conversation title rather than the response content, and clicking one focuses the existing tab instead of opening a new one. + +## User Side + +You can opt in to a sound when a response completes, a desktop notification when the answer lands in a tab you are not looking at, or both. + +## Admin Side + +Admins decide whether Know When a Long Answer Finishes is available in your environment. If you cannot find Open Profile and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Know When a Long Answer Finishes. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_completion_notifications_1.png` +- `/images/latest-release/release_260_completion_notifications_2.png` +- `/images/latest-release/release_260_completion_notifications_3.png` + +## Why It Matters + +This matters because long research answers are worth stepping away from, and there was previously no way to know the response had arrived. + +## How to Try It + +1. Open your Profile page and find the notification and audio preferences. +2. Turn on completion audio cues if you want an audible signal. +3. Pick one of the ten available sounds and set the volume, using preview to hear it. +4. Turn on desktop notifications separately if you want alerts for background tabs. +5. Allow the browser notification permission prompt when it appears. +6. Open Chat, ask a question that takes a while, and switch to another tab. +7. Confirm you get the notification and that clicking it returns you to the existing tab. + +## Notes + +- The Know When a Long Answer Finishes guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_completion_notifications_1.png`, `release_260_completion_notifications_2.png`, `release_260_completion_notifications_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-conversation-contents-drawer.md b/docs/latest-release/release-260-conversation-contents-drawer.md new file mode 100644 index 000000000..cb5bd609f --- /dev/null +++ b/docs/latest-release/release-260-conversation-contents-drawer.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Jump Back to Any Earlier Prompt" +description: "A contents drawer indexes the questions you asked in a conversation so you can jump straight back to any of them instead of scrolling." +section: "Latest Release" +--- + +Current release version for Jump Back to Any Earlier Prompt: **0.260.001** + +The drawer lists your saved prompts as navigable entries and tracks where you currently are in the conversation. It is on by default when your admins enable it, works with keyboard navigation, and adapts to an off-canvas panel on smaller screens. You can hide it for your own account from your profile if you prefer a wider chat area. + +## User Side + +A contents drawer indexes the questions you asked in a conversation so you can jump straight back to any of them instead of scrolling. + +## Admin Side + +Admins decide whether Jump Back to Any Earlier Prompt is available in your environment. If you cannot find Open Chat and Open Profile, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Jump Back to Any Earlier Prompt. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_conversation_contents_drawer_1.png` +- `/images/latest-release/release_260_conversation_contents_drawer_2.png` +- `/images/latest-release/release_260_conversation_contents_drawer_3.png` + +## Why It Matters + +This matters because a long working session becomes hard to navigate, and the thing you want to return to is almost always a question you already asked. + +## How to Try It + +1. Open Chat and continue a conversation that already has many messages. +2. Find the conversation contents drawer beside the message area. +3. Scan the list of your earlier prompts to locate the point you want. +4. Select an entry to jump directly to that place in the conversation. +5. Notice the drawer tracks your current position as you scroll. +6. On a smaller screen, open the drawer as an off-canvas panel instead. +7. Open your Profile and hide the drawer for your account if you would rather have the space. + +## Notes + +- The Jump Back to Any Earlier Prompt guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_conversation_contents_drawer_1.png`, `release_260_conversation_contents_drawer_2.png`, `release_260_conversation_contents_drawer_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-conversation-context-grounding.md b/docs/latest-release/release-260-conversation-context-grounding.md new file mode 100644 index 000000000..59bd776f3 --- /dev/null +++ b/docs/latest-release/release-260-conversation-context-grounding.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "See Exactly What Shaped Each Answer" +description: "Every response now carries a Conversation Context citation showing the model, app version, workspace scope, selected documents, agent, and capabilities that were active when it was written." +section: "Latest Release" +--- + +Current release version for See Exactly What Shaped Each Answer: **0.260.001** + +The context snapshot is both given to the model as hidden grounding and shown to you as a citation on the response. It covers streaming answers, retries, fallbacks, collaboration conversations, and document actions, so the record is consistent no matter which path produced the answer. + +## User Side + +Every response now carries a Conversation Context citation showing the model, app version, workspace scope, selected documents, agent, and capabilities that were active when it was written. + +## Admin Side + +Admins decide whether See Exactly What Shaped Each Answer is available in your environment. If you cannot find Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for See Exactly What Shaped Each Answer. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_conversation_context_grounding_1.png` +- `/images/latest-release/release_260_conversation_context_grounding_2.png` +- `/images/latest-release/release_260_conversation_context_grounding_3.png` + +## Why It Matters + +This matters because when an answer surprises you, the first question is usually which model and which documents were actually in play, and now that is one click away. + +## How to Try It + +1. Open Chat and send any question. +2. When the response arrives, open the citations area on that message. +3. Select the Conversation Context citation. +4. Review the model name and SimpleChat version that produced the answer. +5. Check the workspace scope and the specific documents that were selected. +6. Confirm which agent and which capabilities were active for that turn. +7. Change your model or document selection, ask again, and compare the two context snapshots. + +## Notes + +- The See Exactly What Shaped Each Answer guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_conversation_context_grounding_1.png`, `release_260_conversation_context_grounding_2.png`, `release_260_conversation_context_grounding_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-enhanced-extraction.md b/docs/latest-release/release-260-enhanced-extraction.md new file mode 100644 index 000000000..24fb1fbdc --- /dev/null +++ b/docs/latest-release/release-260-enhanced-extraction.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Sharper Document Extraction with Figure Descriptions" +description: "Enhanced extraction now reads charts, diagrams, and figures inside your documents and writes searchable descriptions of them, so answers can draw on pictures instead of skipping past them." +section: "Latest Release" +--- + +Current release version for Sharper Document Extraction with Figure Descriptions: **0.260.001** + +When your admins turn on Enhanced extraction, SimpleChat uses Azure AI Content Understanding to describe figures, charts, and diagrams as it processes a file. Those descriptions become searchable text, so a question about a chart can be answered from the chart itself. Workspace document rows show a badge naming which extraction engine actually ran, and why it fell back if a different one was used. + +## User Side + +Enhanced extraction now reads charts, diagrams, and figures inside your documents and writes searchable descriptions of them, so answers can draw on pictures instead of skipping past them. + +## Admin Side + +Admins decide whether Sharper Document Extraction with Figure Descriptions is available in your environment. If you cannot find Open Personal Workspace and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Sharper Document Extraction with Figure Descriptions. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_enhanced_extraction_1.png` +- `/images/latest-release/release_260_enhanced_extraction_2.png` +- `/images/latest-release/release_260_enhanced_extraction_3.png` + +## Why It Matters + +This matters because a large share of the meaning in reports, decks, and scanned documents lives in pictures, and until now that content was effectively invisible to search. + +## How to Try It + +1. Open Personal Workspace and upload a document that contains charts, diagrams, or scanned figures. +2. Wait for processing to finish, then expand the document row to see the extraction badge. +3. Hover the badge to see which engine ran, and the fallback reason if a different engine was used. +4. Open the document details to read the generated figure descriptions alongside the extracted text. +5. Go to Chat, ground on that document, and ask a question that can only be answered from a figure or chart. +6. If an older document was uploaded before this change, use Change Extraction to reprocess it with the newer engine. +7. If you do not see the option, ask your admin whether Enhanced extraction is enabled for your environment. + +## Notes + +- The Sharper Document Extraction with Figure Descriptions guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_enhanced_extraction_1.png`, `release_260_enhanced_extraction_2.png`, `release_260_enhanced_extraction_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-font-size-zoom.md b/docs/latest-release/release-260-font-size-zoom.md new file mode 100644 index 000000000..bd307837a --- /dev/null +++ b/docs/latest-release/release-260-font-size-zoom.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Choose Your Text Size" +description: "Five font size choices ranging from 75 percent to 200 percent are available on your profile, and the interface stays usable at 200 percent browser zoom." +section: "Latest Release" +--- + +Current release version for Choose Your Text Size: **0.260.001** + +Font size is a saved per-user preference, so it follows you across sessions rather than resetting. Alongside it, chat, top navigation, classification banners, and the sidebar were reworked to remain fully usable when the browser itself is zoomed to 200 percent. + +## User Side + +Five font size choices ranging from 75 percent to 200 percent are available on your profile, and the interface stays usable at 200 percent browser zoom. + +## Admin Side + +Admins decide whether Choose Your Text Size is available in your environment. If you cannot find Open Profile and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Choose Your Text Size. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_font_size_zoom_1.png` +- `/images/latest-release/release_260_font_size_zoom_2.png` +- `/images/latest-release/release_260_font_size_zoom_3.png` + +## Why It Matters + +This matters because readable text is an accessibility requirement, not a preference, and layouts that break under zoom effectively lock people out. + +## How to Try It + +1. Open your Profile page and find the font size preference. +2. Choose from the five available sizes ranging from extra small to extra large. +3. Save the preference and return to Chat to see it applied. +4. Confirm the size persists after you sign out and back in. +5. Separately, set your browser zoom to 200 percent to check the layout. +6. Confirm the chat area, top navigation, and sidebar all remain usable at that zoom level. +7. Adjust between the font preference and browser zoom to find the combination that reads best. + +## Notes + +- The Choose Your Text Size guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_font_size_zoom_1.png`, `release_260_font_size_zoom_2.png`, `release_260_font_size_zoom_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-mcp-platform.md b/docs/latest-release/release-260-mcp-platform.md new file mode 100644 index 000000000..9e2079bf9 --- /dev/null +++ b/docs/latest-release/release-260-mcp-platform.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Model Context Protocol Connections" +description: "SimpleChat can now act as a governed MCP server for approved external clients, and outbound MCP actions gained presets, preconfigured catalogs, and a Test Connection button." +section: "Latest Release" +--- + +Current release version for Model Context Protocol Connections: **0.260.001** + +Model Context Protocol connections work in both directions. Outbound, your agents reach MCP servers using presets and admin-curated catalogs rather than hand-typed configuration, and you can verify a connection before saving it. Inbound, approved MCP clients can reach SimpleChat conversations, documents, prompts, tags, and workflow tools under admin governance. + +## User Side + +SimpleChat can now act as a governed MCP server for approved external clients, and outbound MCP actions gained presets, preconfigured catalogs, and a Test Connection button. + +## Admin Side + +Admins decide whether Model Context Protocol Connections is available in your environment. If you cannot find Open Personal Workspace and Open Agents, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Model Context Protocol Connections. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_mcp_platform_1.png` +- `/images/latest-release/release_260_mcp_platform_2.png` +- `/images/latest-release/release_260_mcp_platform_3.png` + +## Why It Matters + +This matters because it lets SimpleChat participate in the wider tool ecosystem your organization already uses, without every team hand-rolling its own integration. + +## How to Try It + +1. Open Personal Workspace and go to the Actions section, or open Agents if you are wiring an agent directly. +2. Create a new action and choose the MCP action type. +3. Pick a preset or an admin-preconfigured server entry instead of typing the connection by hand. +4. Fill in any remaining destination and authentication details the preset does not cover. +5. Click Test Connection and confirm the server responds before saving. +6. Attach the saved action to an agent so it can call those MCP tools during a conversation. +7. Open Chat, select that agent, and ask something that requires the connected MCP tool. + +## Notes + +- The Model Context Protocol Connections guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_mcp_platform_1.png`, `release_260_mcp_platform_2.png`, `release_260_mcp_platform_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-message-audio-export.md b/docs/latest-release/release-260-message-audio-export.md new file mode 100644 index 000000000..6a38ecd3a --- /dev/null +++ b/docs/latest-release/release-260-message-audio-export.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Download a Response as Audio" +description: "Any completed chat message can be exported as an MP3 using the configured speech voice and speed." +section: "Latest Release" +--- + +Current release version for Download a Response as Audio: **0.260.001** + +The export uses the active Azure Speech voice and speed settings for your environment and produces a normal MP3 download. The audio is generated on demand and is not stored in SimpleChat, so nothing is retained after the download completes. + +## User Side + +Any completed chat message can be exported as an MP3 using the configured speech voice and speed. + +## Admin Side + +Admins decide whether Download a Response as Audio is available in your environment. If you cannot find Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Download a Response as Audio. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_message_audio_export_1.png` +- `/images/latest-release/release_260_message_audio_export_2.png` +- `/images/latest-release/release_260_message_audio_export_3.png` + +## Why It Matters + +This matters because a long answer is sometimes easier to absorb on a commute than on a screen. + +## How to Try It + +1. Open Chat and find a completed assistant response you want to listen to. +2. Open the message actions for that response. +3. Choose the option to export the message as audio. +4. Wait for the MP3 to be generated from the configured speech voice. +5. Save the downloaded file when your browser prompts you. +6. Play it back in any normal audio player. +7. Repeat on another message and note that nothing is stored in SimpleChat between exports. + +## Notes + +- The Download a Response as Audio guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_message_audio_export_1.png`, `release_260_message_audio_export_2.png`, `release_260_message_audio_export_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-office-embedded-images.md b/docs/latest-release/release-260-office-embedded-images.md new file mode 100644 index 000000000..f72419302 --- /dev/null +++ b/docs/latest-release/release-260-office-embedded-images.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Pictures Inside Word and PowerPoint Are Now Searchable" +description: "Images embedded in Word and PowerPoint files, including SmartArt, Visio drawings, and chart graphics, are now analyzed and cited with the correct page or slide." +section: "Latest Release" +--- + +Current release version for Pictures Inside Word and PowerPoint Are Now Searchable: **0.260.001** + +SimpleChat now pulls images out of DOCX, PPTX, and the legacy DOC and PPT formats, including EMF and WMF metafile diagrams that older Office documents use. Each image is indexed as its own citable chunk with proper page or slide attribution, duplicate images are collapsed, and figures stay in the same chunk as the text around them instead of being dumped at the end of the document. + +## User Side + +Images embedded in Word and PowerPoint files, including SmartArt, Visio drawings, and chart graphics, are now analyzed and cited with the correct page or slide. + +## Admin Side + +Admins decide whether Pictures Inside Word and PowerPoint Are Now Searchable is available in your environment. If you cannot find Open Personal Workspace and Open Chat, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Pictures Inside Word and PowerPoint Are Now Searchable. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_office_embedded_images_1.png` +- `/images/latest-release/release_260_office_embedded_images_2.png` +- `/images/latest-release/release_260_office_embedded_images_3.png` + +## Why It Matters + +This matters because architecture diagrams, org charts, and process flows are often the whole point of a deck, and citations now point at the slide those visuals actually live on. + +## How to Try It + +1. Upload a Word document or PowerPoint deck that contains diagrams, SmartArt, or embedded charts. +2. Let processing finish, then open the document details to see the extracted image chunks. +3. Confirm each image chunk reports the page or slide number it came from. +4. Open Chat and ground a conversation on that document. +5. Ask about something that only appears in a diagram, such as a process step or a box in an org chart. +6. Open the citation on the answer and confirm it points at the correct slide or page. +7. For documents uploaded before this release, use Change Extraction or re-upload so figures land in the right chunk. + +## Notes + +- The Pictures Inside Word and PowerPoint Are Now Searchable guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_office_embedded_images_1.png`, `release_260_office_embedded_images_2.png`, `release_260_office_embedded_images_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-public-workspace-display-name.md b/docs/latest-release/release-260-public-workspace-display-name.md new file mode 100644 index 000000000..42a812795 --- /dev/null +++ b/docs/latest-release/release-260-public-workspace-display-name.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Public Workspace Can Carry Your Own Name" +description: "Your organization can rename the Public Workspace to something meaningful such as Domain Knowledge, and the new name appears everywhere in the interface." +section: "Latest Release" +--- + +Current release version for Public Workspace Can Carry Your Own Name: **0.260.001** + +When admins set a display name, it replaces the generic label across navigation, your profile, chat scope selection, and directory pages. Only the label changes; the underlying workspace, its documents, and every link continue to work exactly as before. + +## User Side + +Your organization can rename the Public Workspace to something meaningful such as Domain Knowledge, and the new name appears everywhere in the interface. + +## Admin Side + +Admins decide whether Public Workspace Can Carry Your Own Name is available in your environment. If you cannot find Open Chat and Open Profile, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Public Workspace Can Carry Your Own Name. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_public_workspace_display_name_1.png` +- `/images/latest-release/release_260_public_workspace_display_name_2.png` +- `/images/latest-release/release_260_public_workspace_display_name_3.png` + +## Why It Matters + +This matters because shared knowledge collections usually already have a name inside your organization, and matching it removes a translation step for everyone. + +## How to Try It + +1. Open Chat and look at the workspace scope selector. +2. Note the name your organization chose in place of the default Public Workspace label. +3. Open your Profile and confirm the same name appears there. +4. Check the navigation and directory pages for the same consistent label. +5. Select that workspace as your chat scope and ask a question against it. +6. Confirm documents and citations behave exactly as they did before the rename. +7. Ask your admin if the label does not match what your team calls this collection. + +## Notes + +- The Public Workspace Can Carry Your Own Name guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_public_workspace_display_name_1.png`, `release_260_public_workspace_display_name_2.png`, `release_260_public_workspace_display_name_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-rocksdb-action.md b/docs/latest-release/release-260-rocksdb-action.md new file mode 100644 index 000000000..5dae8ad94 --- /dev/null +++ b/docs/latest-release/release-260-rocksdb-action.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "RocksDB Key-Value Store Action" +description: "A new RocksDB action type lets agents read from an ordered key-value store through a conforming HTTP and JSON service, with get, scan, and stats tools." +section: "Latest Release" +--- + +Current release version for RocksDB Key-Value Store Action: **0.260.001** + +The RocksDB action targets an HTTP and JSON service in front of a RocksDB store. It exposes get, scan, and stats tools for reads, plus guarded write operations, and supports no-auth, bearer token, and API key authentication. A dedicated configuration card and a Test Connection button let you confirm the endpoint before saving. + +## User Side + +A new RocksDB action type lets agents read from an ordered key-value store through a conforming HTTP and JSON service, with get, scan, and stats tools. + +## Admin Side + +Admins decide whether RocksDB Key-Value Store Action is available in your environment. If you cannot find Open Personal Workspace and Open Agents, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for RocksDB Key-Value Store Action. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_rocksdb_action_1.png` +- `/images/latest-release/release_260_rocksdb_action_2.png` +- `/images/latest-release/release_260_rocksdb_action_3.png` + +## Why It Matters + +This matters because ordered key-value data is common in telemetry and logging systems, and agents can now query it directly instead of asking a person to run a lookup. + +## How to Try It + +1. Open Personal Workspace and go to the Actions section. +2. Create a new action and choose the RocksDB action type. +3. Enter the base address of the HTTP and JSON service that fronts your RocksDB store. +4. Choose no-auth, bearer token, or API key authentication to match that service. +5. Click Test Connection and confirm the endpoint responds before saving. +6. Attach the saved action to an agent and confirm the get, scan, and stats tools appear. +7. Open Chat, select that agent, and ask for a specific key or a range scan over a key prefix. + +## Notes + +- The RocksDB Key-Value Store Action guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_rocksdb_action_1.png`, `release_260_rocksdb_action_2.png`, `release_260_rocksdb_action_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-terms-of-use.md b/docs/latest-release/release-260-terms-of-use.md new file mode 100644 index 000000000..45febc267 --- /dev/null +++ b/docs/latest-release/release-260-terms-of-use.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Terms of Use Acceptance" +description: "Your organization can require you to accept a terms of use or rules of behavior notice before using SimpleChat, with the reminder repeating on a schedule your admins choose." +section: "Latest Release" +--- + +Current release version for Terms of Use Acceptance: **0.260.001** + +When enabled, an acceptance screen appears before you reach the app. Admins choose whether it returns every session, once per day, or only when the text changes. Your accept and decline choices are recorded in the activity log, and the gate is enforced on the server so it cannot be skipped by navigating directly to a page. + +## User Side + +Your organization can require you to accept a terms of use or rules of behavior notice before using SimpleChat, with the reminder repeating on a schedule your admins choose. + +## Admin Side + +Admins configure the tenant policy behind Terms of Use Acceptance. If the experience does not appear when expected, ask your SimpleChat admin whether the organization has enabled this release item for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Terms of Use Acceptance. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_terms_of_use_1.png` +- `/images/latest-release/release_260_terms_of_use_2.png` +- `/images/latest-release/release_260_terms_of_use_3.png` + +## Why It Matters + +This matters because many organizations must record that users acknowledged acceptable-use rules before working with an AI assistant. + +## How to Try It + +1. Sign in to SimpleChat and read the terms of use notice if your organization has enabled one. +2. Scroll through the full text before responding, since the content is set by your organization. +3. Choose Accept to continue into the app. +4. Expect the notice to reappear according to the schedule your admins configured. +5. If the wording changes, expect to be asked again even if you accepted the earlier version. +6. Choose Decline if you do not agree, which will end your session rather than continuing. +7. Contact your admin if you believe the notice is appearing more often than intended. + +## Notes + +- The Terms of Use Acceptance guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_terms_of_use_1.png`, `release_260_terms_of_use_2.png`, `release_260_terms_of_use_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-used-documents-fork.md b/docs/latest-release/release-260-used-documents-fork.md new file mode 100644 index 000000000..041970705 --- /dev/null +++ b/docs/latest-release/release-260-used-documents-fork.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Used Documents View and Conversation Forking" +description: "A Used Documents mode shows only the documents actually cited in a conversation, and you can fork a conversation from any response to explore a different direction." +section: "Latest Release" +--- + +Current release version for Used Documents View and Conversation Forking: **0.260.001** + +The chat side pane gained a Used Documents mode that lists the documents a conversation has genuinely drawn on, without opening the full details modal, and it opens automatically the first time cited documents appear. Separately, forking from an assistant response creates an independent copy of the conversation through that message, so the original stays intact. + +## User Side + +A Used Documents mode shows only the documents actually cited in a conversation, and you can fork a conversation from any response to explore a different direction. + +## Admin Side + +Admins decide whether Used Documents View and Conversation Forking is available in your environment. If you cannot find Open Chat and Open Conversations, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Used Documents View and Conversation Forking. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_used_documents_fork_1.png` +- `/images/latest-release/release_260_used_documents_fork_2.png` +- `/images/latest-release/release_260_used_documents_fork_3.png` + +## Why It Matters + +This matters because long conversations accumulate a lot of context, and both knowing what was actually used and being able to branch without losing the thread are hard problems otherwise. + +## How to Try It + +1. Open Chat and start a conversation grounded on several workspace documents. +2. Ask a few questions so the assistant cites real sources. +3. Watch the side pane open to Used Documents the first time a citation appears. +4. Switch the side pane to Used Documents manually at any time to see the current list. +5. Confirm the list shows only documents that were genuinely cited, not everything in scope. +6. Find an assistant response where you want to try a different direction and choose to fork from it. +7. Work in the forked copy and confirm the original conversation is unchanged. + +## Notes + +- The Used Documents View and Conversation Forking guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_used_documents_fork_1.png`, `release_260_used_documents_fork_2.png`, `release_260_used_documents_fork_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-workflow-task-sequences.md b/docs/latest-release/release-260-workflow-task-sequences.md new file mode 100644 index 000000000..0346cd0c8 --- /dev/null +++ b/docs/latest-release/release-260-workflow-task-sequences.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Multi-Step Workflows With Alert Rules" +description: "Workflows can now run an ordered sequence of tasks, each with its own model, agent, and documents, and can notify you through configurable alert rules instead of a single priority setting." +section: "Latest Release" +--- + +Current release version for Multi-Step Workflows With Alert Rules: **0.260.001** + +The workflow builder is now a stepped experience covering General, Trigger, Tasks, Reliability, and Review. Each task chooses its own model or agent, sets its own document action and targets, and passes context forward to the next task. Alerts moved to a rules engine supporting run status, text matches, regular expressions, File Sync results, and AI-judged conditions across five severity levels. Runs can also be cancelled while in flight. + +## User Side + +Workflows can now run an ordered sequence of tasks, each with its own model, agent, and documents, and can notify you through configurable alert rules instead of a single priority setting. + +## Admin Side + +Admins decide whether Multi-Step Workflows With Alert Rules is available in your environment. If you cannot find Open Personal Workspace, Open Group Workspaces, and Open Workflow Activity, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Multi-Step Workflows With Alert Rules. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_workflow_task_sequences_1.png` +- `/images/latest-release/release_260_workflow_task_sequences_2.png` +- `/images/latest-release/release_260_workflow_task_sequences_3.png` + +## Why It Matters + +This matters because real work is rarely one prompt, and chaining steps with targeted notifications turns a workflow into something you can trust to run unattended. + +## How to Try It + +1. Open Personal Workspace and go to the Workflows section, or open Group Workspaces for a shared workflow. +2. Create a workflow and step through General and Trigger to name it and choose when it runs. +3. In the Tasks step, add your first instruction task and pick the model or agent that should run it. +4. Set that task document action and choose the specific documents it should operate on. +5. Add a second task and reference what the first task produced so the steps build on each other. +6. In the Reliability step, set retry and failure handling for tasks that call external systems. +7. In the Review step, add alert rules such as notify on failure or notify when the output matches a phrase, then save and run it. + +## Notes + +- The Multi-Step Workflows With Alert Rules guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_workflow_task_sequences_1.png`, `release_260_workflow_task_sequences_2.png`, `release_260_workflow_task_sequences_3.png` from the app Latest Features catalog. diff --git a/docs/latest-release/release-260-yamcs-action.md b/docs/latest-release/release-260-yamcs-action.md new file mode 100644 index 000000000..e66d74dfb --- /dev/null +++ b/docs/latest-release/release-260-yamcs-action.md @@ -0,0 +1,45 @@ +--- +layout: latest-release-feature +title: "Yamcs Mission Control Integration" +description: "A new Yamcs action type connects agents to Yamcs mission control servers with eleven read-only tools covering telemetry, parameters, events, packets, alarms, and archive queries." +section: "Latest Release" +--- + +Current release version for Yamcs Mission Control Integration: **0.260.001** + +The Yamcs action is strictly read-only by design, so an agent can investigate mission data but cannot command a spacecraft. Archive SQL access is opt-in and enforced as SELECT-only. Several authentication methods are supported, and a dedicated configuration panel plus a Test Connection button make setup verifiable before you rely on it. + +## User Side + +A new Yamcs action type connects agents to Yamcs mission control servers with eleven read-only tools covering telemetry, parameters, events, packets, alarms, and archive queries. + +## Admin Side + +Admins decide whether Yamcs Mission Control Integration is available in your environment. If you cannot find Open Personal Workspace and Open Agents, ask whether the related settings, governance policy, or workspace access has been enabled for your account. + +## Screenshot Placeholder + +The v0.260.001 app catalog currently provides branded placeholder captures for Yamcs Mission Control Integration. Replace these copied documentation images when final screenshots are ready: + +- `/images/latest-release/release_260_yamcs_action_1.png` +- `/images/latest-release/release_260_yamcs_action_2.png` +- `/images/latest-release/release_260_yamcs_action_3.png` + +## Why It Matters + +This matters because mission operators can ask plain-language questions about telemetry and alarms instead of hand-writing queries against the archive. + +## How to Try It + +1. Open Personal Workspace and go to the Actions section. +2. Create a new action and choose the Yamcs action type. +3. Enter your Yamcs server address and pick the authentication method your instance uses. +4. Leave archive SQL disabled unless you specifically need archive queries, then enable it deliberately. +5. Click Test Connection to confirm SimpleChat can reach the server and read an instance. +6. Attach the saved action to an agent, then open Agents to confirm the eleven Yamcs tools are listed. +7. Open Chat, select that agent, and ask about recent telemetry, parameters, events, or active alarms. + +## Notes + +- The Yamcs Mission Control Integration guide belongs to the SimpleChat 0.260.001 latest-feature set. +- The gallery for this page uses `release_260_yamcs_action_1.png`, `release_260_yamcs_action_2.png`, `release_260_yamcs_action_3.png` from the app Latest Features catalog. diff --git a/docs/reference/actions/azure-maps-openlayers.md b/docs/reference/actions/azure-maps-openlayers.md new file mode 100644 index 000000000..4beb5b684 --- /dev/null +++ b/docs/reference/actions/azure-maps-openlayers.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Azure Maps OpenLayers" +description: "Reference for the Azure Maps OpenLayers SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-azure-maps-openlayers-configuration.png" alt="Azure Maps OpenLayers action setup or assignment UI." title="Azure Maps OpenLayers action" capture="Capture the Azure Maps OpenLayers action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Creates inline OpenLayers map payloads and proxies Azure Maps raster tiles through SimpleChat. + +## Why and when to use it + +Use it when an agent needs to turn known locations, areas, or paths into a map inside chat. Do not use it for general GIS editing or broad geocoding. + +## Before you start + +- Azure Maps subscription key; key auth; agents enabled with `enable_semantic_kernel`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Use common setup, then provide **Subscription Key** and test the Azure Maps connection. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/blob-storage.md b/docs/reference/actions/blob-storage.md new file mode 100644 index 000000000..bc515aa9a --- /dev/null +++ b/docs/reference/actions/blob-storage.md @@ -0,0 +1,45 @@ +--- +layout: page +title: "Blob Storage" +description: "Reference for the Blob Storage SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-blob-storage-configuration.png" alt="Blob Storage action setup or assignment UI." title="Blob Storage action" capture="Capture the Blob Storage action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Lists, reads, and uploads supported files in one configured Azure Blob container, optionally narrowed to a prefix. + +## Why and when to use it + +Use it when an agent should work with a controlled container path without broad storage access. Use Workspace upload/search for normal SimpleChat documents. + +## Before you start + +- One authentication method for the target container: a storage connection string, managed identity access to the storage account, or an account key. +- For managed identity, the SimpleChat application identity must already have Azure Blob Storage data-plane access to the account or container. Use a read role for list/read-only actions and a write-capable role when uploads are enabled. +- Agents enabled with `enable_semantic_kernel`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Choose **Authentication Type**: + +- **Connection String** stores the full Azure Storage connection string through the normal action secret flow. Choose it when the action needs a self-contained credential and managed identity is not available. +- **Managed Identity** uses the application's Azure identity with the **Blob Service Endpoint**. Choose it when you want to avoid stored secrets and can grant the app identity the required Storage Blob data role ahead of time. +- **Account Key** uses the **Blob Service Endpoint** plus a primary or secondary storage account key. Choose it when a full connection string is not desired but key-based access is still required. + +Then set **Container Name**, optional **Blob Prefix**, default capabilities, and supported read/upload file types. The endpoint must be an Azure Blob service hostname such as `https://account.blob.core.windows.net`; SimpleChat validates the endpoint before saving and again before building the action client. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/chart.md b/docs/reference/actions/chart.md new file mode 100644 index 000000000..81eaf3ef0 --- /dev/null +++ b/docs/reference/actions/chart.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Chart" +description: "Reference for the Chart SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-chart-configuration.png" alt="Chart action setup or assignment UI." title="Chart action" capture="Capture the Chart action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Builds validated inline chart payloads for line, bar, pie, scatter, area, bubble, radar, and stacked chart variants. + +## Why and when to use it + +Use it when a visual answer is easier to understand than prose. Pair it with a data action for computed values. + +## Before you start + +- No external credentials; rendered with SimpleChat internal Chart.js assets. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Choose the default chart types exposed to agents. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/cosmos-query.md b/docs/reference/actions/cosmos-query.md new file mode 100644 index 000000000..f1ada56e5 --- /dev/null +++ b/docs/reference/actions/cosmos-query.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Cosmos Query" +description: "Reference for the Cosmos Query SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-cosmos-query-configuration.png" alt="Cosmos Query action setup or assignment UI." title="Cosmos Query action" capture="Capture the Cosmos Query action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Runs read-only Azure Cosmos DB for NoSQL queries and validates Cosmos SQL-style queries. + +## Why and when to use it + +Use it for governed lookup over one Cosmos container. Use SQL Query for relational databases. + +## Before you start + +- Cosmos endpoint, database, container, partition key path, and managed identity or account key. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Set account/container fields, auth method, optional field hints, max items, timeout, and test the connection. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/databricks-table.md b/docs/reference/actions/databricks-table.md new file mode 100644 index 000000000..1998a4722 --- /dev/null +++ b/docs/reference/actions/databricks-table.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Databricks Table" +description: "Reference for the Databricks Table SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-databricks-table-configuration.png" alt="Databricks Table action setup or assignment UI." title="Databricks Table action" capture="Capture the Databricks Table action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Compatibility wrapper for legacy `databricks_table` manifests. + +## Why and when to use it + +Use only to keep older manifests working. For new work, use Databricks. + +## Before you start + +- Existing legacy manifest with key auth; the create-action UI hides `databricks_table`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +No new-action panel; migrate to [Databricks]({{ '/reference/actions/databricks/' | relative_url }}) for new actions. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/databricks.md b/docs/reference/actions/databricks.md new file mode 100644 index 000000000..7f54c5d2a --- /dev/null +++ b/docs/reference/actions/databricks.md @@ -0,0 +1,59 @@ +--- +layout: page +title: "Databricks" +description: "Full guide for the Databricks SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-databricks-configuration.png" alt="Databricks action configuration UI." title="Databricks configuration" capture="Capture Workspace and Warehouse, Authentication, Execution Limits, and Test Connection. Redact tokens and IDs if needed." %} + +## What this action does + +Databricks uses the Databricks SQL Statement Execution API for Azure Commercial workspaces. It can execute SQL, list catalogs, list schemas, list tables, and describe a table through a configured SQL Warehouse. + +## Why and when to use it + +Use Databricks when governed analytics data lives in Databricks and users should query it through approved agents instead of direct warehouse tools. It is read-oriented and configured with row/time limits. Do not use it for Snowflake, generic SQL databases, or spreadsheet uploads; use Snowflake, SQL Query, or Tabular Processing instead. + +## Before you start + +- Azure Commercial Databricks **Workspace URL**, not the SQL statement API path. +- **SQL Warehouse ID**. +- Optional default **Catalog** and **Schema**. +- Personal access token, bearer token, service principal, managed identity, or compatible reusable identity. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **Databricks**. +2. Set **Workspace URL**. +3. Confirm **Cloud** is **Azure Commercial**. +4. Enter **SQL Warehouse ID** and optional **Default Catalog** and **Default Schema**. +5. Choose **Authentication Method**: **Personal Access Token**, bearer token, **Service Principal**, or **Managed Identity**. +6. Fill **Token** or **Client ID**, **Client Secret**, and **Tenant ID** when those auth modes are selected, or choose **Reusable Identity**. +7. Set **Max Rows**, **Timeout (seconds)**, and **Wait Timeout (seconds)**. +8. Use **Test Connection**. + +## Example prompts + +- "Show the top product categories by revenue in the last completed quarter." +- "List tables in the finance schema and describe the invoice table." +- "Run a read-only query that groups daily active users by week for the past 90 days." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Connection test cannot reach Databricks | Workspace URL is not the base workspace URL or warehouse ID is wrong. | Use the `https://adb-...azuredatabricks.net` workspace URL and SQL Warehouse ID. | +| Authentication fails | Token, service principal, or managed identity lacks workspace/warehouse access. | Grant Databricks SQL permissions and retest. | +| Long queries fail | Timeout or wait timeout is too low. | Tune the SQL or raise **Timeout** and **Wait Timeout** within limits. | + +## Related + +- [SQL Query](../sql-query/) +- [Snowflake](../snowflake/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) + diff --git a/docs/reference/actions/document-search.md b/docs/reference/actions/document-search.md new file mode 100644 index 000000000..4695211ed --- /dev/null +++ b/docs/reference/actions/document-search.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Document Search" +description: "Reference for the Document Search SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-document-search-configuration.png" alt="Document Search action setup or assignment UI." title="Document Search action" capture="Capture the Document Search action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Searches accessible SimpleChat documents, retrieves chunks, and summarizes documents using current user access. + +## Why and when to use it + +Use it when an agent should reason over workspace documents as a tool. Use the normal grounded-search panel for one-off user searches. + +## Before you start + +- Accessible personal, group, or public workspace content; no external credentials. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Configure default scope, result limit, preferred windowing, and summary defaults. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/embedding-model.md b/docs/reference/actions/embedding-model.md new file mode 100644 index 000000000..0a8ed3ee9 --- /dev/null +++ b/docs/reference/actions/embedding-model.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Embedding Model" +description: "Reference for the Embedding Model SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-embedding-model-configuration.png" alt="Embedding Model action setup or assignment UI." title="Embedding Model action" capture="Capture the Embedding Model action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Creates embeddings for supplied text through a configured embedding endpoint. + +## Why and when to use it + +Use it for workflows that need vector representations, not for conversational answers. + +## Before you start + +- Embedding endpoint and key; global toggle `enable_default_embedding_model_plugin`; UI hides `embedding_model`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Configure existing/default embedding action settings through Agents controls. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/fact-memory.md b/docs/reference/actions/fact-memory.md new file mode 100644 index 000000000..1061275c6 --- /dev/null +++ b/docs/reference/actions/fact-memory.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Fact Memory" +description: "Reference for the Fact Memory SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-fact-memory-configuration.png" alt="Fact Memory action setup or assignment UI." title="Fact Memory action" capture="Capture the Fact Memory action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Stores, updates, deletes, and retrieves persistent facts for agent context. + +## Why and when to use it + +Use it for durable preferences or background facts. Do not store secrets or regulated data as memory facts. + +## Before you start + +- Built-in storage; controlled by `enable_fact_memory_plugin` and user memory preferences. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Assign/enable the built-in memory action; no external service fields are required. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/index.md b/docs/reference/actions/index.md new file mode 100644 index 000000000..461507042 --- /dev/null +++ b/docs/reference/actions/index.md @@ -0,0 +1,73 @@ +--- +layout: page +title: "Actions reference" +description: "Reference for SimpleChat action types and when to use each one." +section: "Reference" +audience: user +--- + +## What actions are + +Actions are approved tools that agents can call. They may query data, search documents, call APIs, render charts, or work with Microsoft 365 depending on how admins and workspace owners configure them. + +## Common action setup steps + +1. Select an action type from **Select Type**. +2. Fill **Display Name**, review generated **Name**, and add a useful **Description**. +3. Complete action-specific **Configuration** fields described on each action page. +4. In **Advanced**, optionally add **Metadata (JSON)**, **Additional Fields (JSON)**, or Key Vault expiration reminder metadata when secret tracking is enabled. +5. Review **Action Summary**, use **Test Connection** where provided, then save the action. + +If an action type does not appear, check Agents settings, workspace action permissions, and Governance policy. + +## Data and analytics + +| Action | What it does | Typical use | Depth | +| --- | --- | --- | --- | +| [Cosmos Query](./cosmos-query/) | Runs read-only Azure Cosmos DB for NoSQL queries and validates Cosmos SQL-style queries. | Use it for governed lookup over one Cosmos container. Use SQL Query for relational databases. | overview | +| [Databricks Table](./databricks-table/) | Compatibility wrapper for legacy `databricks_table` manifests. | Use only to keep older manifests working. For new work, use Databricks. | overview | +| [Document Search](./document-search/) | Searches accessible SimpleChat documents, retrieves chunks, and summarizes documents using current user access. | Use it when an agent should reason over workspace documents as a tool. Use the normal grounded-search panel for one-off user searches. | overview | +| [Log Analytics](./log-analytics/) | Discovers Azure Log Analytics table schemas and runs read-only KQL queries against one workspace. | Use it for operational telemetry and App Insights-style investigation. Use SQL/Snowflake/Databricks for business data. | overview | +| [SQL Query](./sql-query/) | Executes SQL queries and scalar queries against configured SQL Server, Azure SQL, PostgreSQL, MySQL, or SQLite databases. | Exact relational data questions with joins, filters, counts, or current rows. | full guide | +| [SQL Schema](./sql-schema/) | Discovers tables, table schemas, and relationships for configured SQL databases. | Schema discovery before an agent writes SQL. | full guide | +| [Databricks]({{ '/reference/actions/databricks/' | relative_url }}) | Runs read-only SQL and discovers catalogs, schemas, tables, and table details through Databricks SQL Statement Execution. | Governed analytics queries in Azure Commercial Databricks. | full guide | +| [Snowflake](./snowflake/) | Runs read-only Snowflake SQL and discovers databases, schemas, tables, and table details. | Governed analytics queries in Snowflake. | full guide | +| [Tableau](./tableau/) | Discovers Tableau projects, workbooks, views, datasources, and workbook details. | Finding Tableau assets and metadata. | full guide | +| [Tabular Processing](./tabular-processing/) | Analyzes CSV and Excel files from workspaces and chat uploads with deterministic tabular operations. | Exact spreadsheet and CSV questions. | full guide | + +## Microsoft 365 and identity + +| Action | What it does | Typical use | Depth | +| --- | --- | --- | --- | +| [Microsoft Graph](./msgraph/) | Uses delegated Microsoft Graph permissions for profile, mail, calendar, directory, OneDrive, and security-alert operations. | User-context Microsoft 365 workflows. | full guide | + +## Protocols and integration + +| Action | What it does | Typical use | Depth | +| --- | --- | --- | --- | +| [Azure Maps OpenLayers](./azure-maps-openlayers/) | Creates inline OpenLayers map payloads and proxies Azure Maps raster tiles through SimpleChat. | Use it when an agent needs to turn known locations, areas, or paths into a map inside chat. Do not use it for general GIS editing or broad geocoding. | overview | +| [Yamcs](./yamcs/) | Retrieves read-only Yamcs telemetry, mission database, archive, event, packet, alarm, and link information. | Use it for mission-control visibility. Do not use it for commanding; the plugin source intentionally does not support commands or writes. | overview | +| [MCP](./mcp/) | Connects to a Model Context Protocol server and exposes selected server tools to agents. | Standard tool-server integrations. | full guide | +| [OpenAPI](./openapi/) | Turns an uploaded OpenAPI specification into callable API operations. | Documented REST API integrations. | full guide | +| [Smart HTTP](./smart-http/) | Fetches web and PDF content with content-size management, text extraction, and PDF processing. | Reading URLs, web pages, and PDFs. | full guide | + +## Storage and messaging + +| Action | What it does | Typical use | Depth | +| --- | --- | --- | --- | +| [Blob Storage](./blob-storage/) | Lists, reads, and uploads supported files in one configured Azure Blob container, optionally narrowed to a prefix. | Use it when an agent should work with a controlled container path without broad storage access. Use Workspace upload/search for normal SimpleChat documents. | overview | +| [Queue Storage](./queue-storage/) | Sends messages to an Azure Queue Storage queue. | Use it when an agent should enqueue work for another system. Do not use it for long-form storage. | overview | +| [RocksDB](./rocksdb/) | Reads and optionally writes keys through a RocksDB HTTP/JSON service with prefix/range scans. | Use it for low-level key-value lookup behind an approved service. Keep read-only unless writes are reviewed. | overview | + +## Utilities + +| Action | What it does | Typical use | Depth | +| --- | --- | --- | --- | +| [Chart](./chart/) | Builds validated inline chart payloads for line, bar, pie, scatter, area, bubble, radar, and stacked chart variants. | Use it when a visual answer is easier to understand than prose. Pair it with a data action for computed values. | overview | +| [Embedding Model](./embedding-model/) | Creates embeddings for supplied text through a configured embedding endpoint. | Use it for workflows that need vector representations, not for conversational answers. | overview | +| [Fact Memory](./fact-memory/) | Stores, updates, deletes, and retrieves persistent facts for agent context. | Use it for durable preferences or background facts. Do not store secrets or regulated data as memory facts. | overview | +| [Math](./math/) | Performs deterministic calculations such as multiply, divide, power, square root, and modulus. | Use it when an agent should calculate instead of approximating arithmetic. Use Tabular or SQL for dataset aggregations. | overview | +| [SimpleChat](./simplechat/) | Lets agents create groups, conversations, workflows, alerts, messages, and generated documents using SimpleChat APIs. | Use it for in-app automation. Do not enable capabilities beyond what the agent should do for users. | overview | +| [Text](./text/) | Provides Semantic Kernel text functions with invocation logging. | Use it for simple text transformations inside agent workflows; plain prompting is enough for many cases. | overview | +| [Time](./time/) | Provides time-related functions with invocation logging. | Use it when an agent needs current time or time calculations as a tool. | overview | +| [Wait](./wait/) | Provides wait/sleep behavior with invocation logging. | Use it only in workflows or tool chains that intentionally pause between operations. | overview | diff --git a/docs/reference/actions/log-analytics.md b/docs/reference/actions/log-analytics.md new file mode 100644 index 000000000..b7decd5b6 --- /dev/null +++ b/docs/reference/actions/log-analytics.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Log Analytics" +description: "Reference for the Log Analytics SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-log-analytics-configuration.png" alt="Log Analytics action setup or assignment UI." title="Log Analytics action" capture="Capture the Log Analytics action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Discovers Azure Log Analytics table schemas and runs read-only KQL queries against one workspace. + +## Why and when to use it + +Use it for operational telemetry and App Insights-style investigation. Use SQL/Snowflake/Databricks for business data. + +## Before you start + +- Workspace ID plus identity, user, service principal, or key access. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Set Workspace ID, Cloud, optional endpoints, Authentication Method, and test the connection. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/math.md b/docs/reference/actions/math.md new file mode 100644 index 000000000..d37e8d3bc --- /dev/null +++ b/docs/reference/actions/math.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Math" +description: "Reference for the Math SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-math-configuration.png" alt="Math action setup or assignment UI." title="Math action" capture="Capture the Math action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Performs deterministic calculations such as multiply, divide, power, square root, and modulus. + +## Why and when to use it + +Use it when an agent should calculate instead of approximating arithmetic. Use Tabular or SQL for dataset aggregations. + +## Before you start + +- No credentials; controlled by `enable_math_plugin`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +No service-specific configuration beyond enabling/assigning the action. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/mcp.md b/docs/reference/actions/mcp.md new file mode 100644 index 000000000..8687bfd29 --- /dev/null +++ b/docs/reference/actions/mcp.md @@ -0,0 +1,59 @@ +--- +layout: page +title: "MCP" +description: "Full guide for the MCP SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-mcp-configuration.png" alt="MCP action configuration UI." title="MCP configuration" capture="Capture MCP Server, Authentication, Tool Exposure, Timeouts and Retries, Discover Tools, and Test Connection. Redact secrets." %} + +## What this action does + +MCP connects SimpleChat to a Model Context Protocol server and exposes selected server tools to agents. Supported transports include streamable HTTP, server-sent events, WebSocket, and stdio. The action can discover tool metadata and call configured tools by name. + +## Why and when to use it + +Use MCP when a provider already exposes an MCP server and agents should use those tools through a standard protocol. Do not use MCP for a single REST API with a stable OpenAPI spec; OpenAPI is simpler. Do not expose every discovered tool by default if the server has broad or sensitive capabilities. + +## Before you start + +- An MCP server endpoint or stdio command reachable from the SimpleChat environment. +- Transport choice: **Streamable HTTP**, **Server-Sent Events**, **WebSocket**, or **Stdio**. +- Auth details when required: bearer token, API key header, basic auth, reusable identity, or custom headers. +- Tool exposure policy: load tools, optional prompts, allowed tool names, and large-result handling. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **MCP**. +2. Optionally select **Preconfigured MCP Server** and **Server Preset**. +3. Choose **Transport** and fill **Endpoint**. For stdio, fill **Command**, **Arguments**, and **Environment (JSON)**. +4. Choose **Authentication Method** and fill the shown auth fields or **Custom Headers (JSON)**. +5. Set **Load tools**, **Load prompts**, **Validate tool arguments**, and **Large Result Policy**. +6. Fill **Allowed Tool Names** to expose only approved tools, or leave blank to expose all discovered tools. +7. Use **Discover Tools** to populate **Discovered Tool Metadata (JSON)**. +8. Set **Request Timeout**, **Connect Timeout**, **SSE Read Timeout**, **Retry Count**, and **Retry Backoff**, then use **Test Connection**. + +## Example prompts + +- "Use the GitHub MCP tool to summarize open issues labeled customer-impact." +- "Call the approved search tool from this MCP server and compare the top results." +- "List the tools exposed by this MCP action before deciding which one to call." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Discovery finds too many tools | **Allowed Tool Names** is blank. | Add one approved tool name per line. | +| Tool calls fail on arguments | Arguments do not match the discovered schema. | Enable **Validate tool arguments** and rediscover tools. | +| SSE connections time out | Read timeout is too low or a proxy interrupts the stream. | Increase **SSE Read Timeout** and verify the network path. | + +## Related + +- [OpenAPI](../openapi/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) + diff --git a/docs/reference/actions/msgraph.md b/docs/reference/actions/msgraph.md new file mode 100644 index 000000000..936a84ef3 --- /dev/null +++ b/docs/reference/actions/msgraph.md @@ -0,0 +1,56 @@ +--- +layout: page +title: "Microsoft Graph" +description: "Full guide for the Microsoft Graph SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-msgraph-configuration.png" alt="Microsoft Graph action configuration UI." title="Microsoft Graph configuration" capture="Capture Default Microsoft Graph Capabilities and mail/calendar delivery settings. Redact user identifiers." %} + +## What this action does + +Microsoft Graph uses the signed-in user's delegated permissions and the standard Graph endpoint. Its capabilities include profile, timezone, calendar events, calendar invites, mail read/update/send, directory search, user lookup, OneDrive listing, and security alerts available to the user. + +## Why and when to use it + +Use Microsoft Graph when an agent should help with Microsoft 365 work in the user's own context: drafting mail, checking calendar context, finding people, or listing OneDrive items. Do not use it for app-wide mailbox automation unless delegated-user behavior is intended and consented. Keep mail/calendar sending modes conservative when users must review drafts before delivery. + +## Before you start + +- Microsoft Graph permissions and consent appropriate for enabled capabilities. +- Signed-in users who are allowed to use the action. +- A decision about risky capabilities, especially **Send mail** and **Create calendar invites**. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **Microsoft Graph**. +2. Review **Default Microsoft Graph Capabilities** and enable only needed operations. +3. For mail, choose manual draft, delayed draft, or auto-send where allowed. +4. Set mail delay seconds when using delayed delivery. +5. For calendar invites, choose the invite delivery behavior and delay seconds when applicable. +6. Save and assign the action only to agents that need Microsoft 365 access. + +## Example prompts + +- "Draft a reply to the latest unread message from Contoso and leave it for my review." +- "Find free time tomorrow afternoon and create a Teams meeting invite for these attendees." +- "Search the directory for Alex Chen and show the likely match with email address." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Graph calls fail for one user | Delegated permission, consent, or mailbox access is missing for that user. | Confirm Graph scopes and have the user re-consent if required. | +| Mail sends when drafts were expected | The action is configured for auto-send. | Change mail delivery mode to manual draft or delayed draft. | +| Calendar invite lacks group members | The prompt or permissions did not include resolvable group context. | Enable invite capability and include group/workspace context. | + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) + diff --git a/docs/reference/actions/openapi.md b/docs/reference/actions/openapi.md new file mode 100644 index 000000000..4f5090ecb --- /dev/null +++ b/docs/reference/actions/openapi.md @@ -0,0 +1,58 @@ +--- +layout: page +title: "OpenAPI" +description: "Full guide for the OpenAPI SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-openapi-configuration.png" alt="OpenAPI action configuration UI." title="OpenAPI configuration" capture="Capture OpenAPI Configuration, Authentication Configuration, API Information, and Test Connection. Redact secrets." %} + +## What this action does + +OpenAPI turns a YAML or JSON OpenAPI specification into callable operations. The action can list available APIs, inspect operation details, and call operations by `operationId` with configured authentication. + +## Why and when to use it + +Use OpenAPI when an HTTP API has a maintained OpenAPI spec and users need agents to call multiple documented operations. Do not use it for arbitrary web pages or PDFs; use Smart HTTP. Do not use it for MCP servers; use MCP. Tool-call quality depends on clear operation IDs and parameter schemas. + +## Before you start + +- An OpenAPI specification file in YAML or JSON, max 5 MB according to the modal help. +- API **Base URL**, auto-populated from the spec when available but editable. +- Authentication details: no auth, API key, bearer token, basic auth, OAuth2 access token, or compatible reusable identity. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **OpenAPI**. +2. Upload **OpenAPI Specification File**. +3. Review or edit **Base URL**. +4. Read the **API Information** panel after parsing. +5. Choose **Authentication Type**: **No Authentication**, **API Key**, bearer token, **Basic Auth**, or **OAuth2**. +6. For API key auth, choose **Location**, fill **Key Name**, and fill **API Key**. +7. For bearer, basic, or OAuth2 auth, fill the shown token, username/password, or access token fields. +8. Use **Test Connection**. + +## Example prompts + +- "List the available API operation IDs for this action." +- "Call the customer status endpoint for account 12345 and summarize the response." +- "Get operation details for createIncident before using it." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Operation names are hard for the agent to choose | The spec has missing or ambiguous operation IDs. | Improve operation IDs and summaries, then re-upload. | +| Authentication fails | Wrong auth type, header name, token, or query parameter is configured. | Match modal auth fields to the API security scheme. | +| Spec upload fails | File is invalid JSON/YAML or too large. | Validate the spec and keep it under the documented upload limit. | + +## Related + +- [MCP](../mcp/) +- [Smart HTTP](../smart-http/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) + diff --git a/docs/reference/actions/queue-storage.md b/docs/reference/actions/queue-storage.md new file mode 100644 index 000000000..089658607 --- /dev/null +++ b/docs/reference/actions/queue-storage.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Queue Storage" +description: "Reference for the Queue Storage SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-queue-storage-configuration.png" alt="Queue Storage action setup or assignment UI." title="Queue Storage action" capture="Capture the Queue Storage action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Sends messages to an Azure Queue Storage queue. + +## Why and when to use it + +Use it when an agent should enqueue work for another system. Do not use it for long-form storage. + +## Before you start + +- Queue endpoint and identity or key auth; the create-action UI hides `queue_storage`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Use legacy/existing queue manifest fields where exposed. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/rocksdb.md b/docs/reference/actions/rocksdb.md new file mode 100644 index 000000000..45e14943e --- /dev/null +++ b/docs/reference/actions/rocksdb.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "RocksDB" +description: "Reference for the RocksDB SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-rocksdb-configuration.png" alt="RocksDB action setup or assignment UI." title="RocksDB action" capture="Capture the RocksDB action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Reads and optionally writes keys through a RocksDB HTTP/JSON service with prefix/range scans. + +## Why and when to use it + +Use it for low-level key-value lookup behind an approved service. Keep read-only unless writes are reviewed. + +## Before you start + +- RocksDB service URL and optional bearer/API key auth. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Set Service Base URL, Authentication Scheme, Column Family, Read-Only, encodings, prefix hints, and limits. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/simplechat.md b/docs/reference/actions/simplechat.md new file mode 100644 index 000000000..dc569d80e --- /dev/null +++ b/docs/reference/actions/simplechat.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "SimpleChat" +description: "Reference for the SimpleChat SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-simplechat-configuration.png" alt="SimpleChat action setup or assignment UI." title="SimpleChat action" capture="Capture the SimpleChat action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Lets agents create groups, conversations, workflows, alerts, messages, and generated documents using SimpleChat APIs. + +## Why and when to use it + +Use it for in-app automation. Do not enable capabilities beyond what the agent should do for users. + +## Before you start + +- Uses signed-in user permissions; no external credentials. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Choose the default SimpleChat capabilities exposed to agents. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/smart-http.md b/docs/reference/actions/smart-http.md new file mode 100644 index 000000000..bdd00efa2 --- /dev/null +++ b/docs/reference/actions/smart-http.md @@ -0,0 +1,54 @@ +--- +layout: page +title: "Smart HTTP" +description: "Full guide for the Smart HTTP SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-smart-http-configuration.png" alt="Smart HTTP action assignment or configuration UI." title="Smart HTTP configuration" capture="Capture the HTTP Action setting or Smart HTTP action assignment UI. Redact URLs if needed." %} + +## What this action does + +Smart HTTP fetches web content with size management and text extraction. It supports HTML, JSON, and PDF URLs. PDFs can be processed through Document Intelligence, and large documents can be summarized into model-sized content with transparency about original size, chunking, and reduction. + +## Why and when to use it + +Use Smart HTTP when the user gives a URL and the agent needs page or PDF content, especially when content may be large. Do not use it for APIs with structured operations; OpenAPI is safer. Do not use it for broad research over many sources; use Deep Research or web search controls when enabled. Be careful with private or sensitive URLs because the action fetches external content. + +## Before you start + +- Network access from the app to target URLs. +- Document Intelligence configuration if PDF extraction is expected. +- The global HTTP action controlled by [`enable_http_plugin`]({{ '/admin/agents-actions/' | relative_url }}). +- No Smart HTTP-specific panel was confirmed in `_plugin_modal.html`; behavior is implemented in `smart_http_plugin.py`. + +## Configure the action + +1. Enable **HTTP Action** on the Agents admin page when using the built-in Smart HTTP action. +2. Assign the action only to agents that should fetch URLs. +3. If your environment exposes Smart HTTP as a configurable action, use standard **Display Name**, **Name**, **Description**, and **Advanced** fields; the modal source does not show a dedicated Smart HTTP panel. +4. Document allowed-domain and data-handling rules in the agent instructions. + +## Example prompts + +- "Read this PDF URL and summarize the renewal obligations with source notes." +- "Fetch this public changelog page and list breaking changes since last month." +- "Compare these two documentation URLs and identify changed requirements." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| A large page is summarized instead of returned verbatim | Smart HTTP intentionally reduces oversized content to avoid token-limit failures. | Ask for targeted sections or provide a smaller source URL. | +| PDF extraction is poor or unavailable | Document Intelligence is not configured or the PDF is difficult. | Enable/configure Document Intelligence or upload the PDF as a workspace document. | +| A site returns an HTTP error | The URL requires authentication, blocks scraping, or is unavailable. | Use an authenticated API/action or provide accessible content. | + +## Related + +- [OpenAPI](../openapi/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Search and Extract administration]({{ '/admin/knowledge/' | relative_url }}) + diff --git a/docs/reference/actions/snowflake.md b/docs/reference/actions/snowflake.md new file mode 100644 index 000000000..d57cb29d5 --- /dev/null +++ b/docs/reference/actions/snowflake.md @@ -0,0 +1,57 @@ +--- +layout: page +title: "Snowflake" +description: "Full guide for the Snowflake SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-snowflake-configuration.png" alt="Snowflake action configuration UI." title="Snowflake configuration" capture="Capture Account and Warehouse, Authentication, Execution Limits, and Test Connection. Redact secrets." %} + +## What this action does + +Snowflake connects to one Snowflake account and warehouse for read-only data retrieval. It can run SQL and discover databases, schemas, tables, and table details. + +## Why and when to use it + +Use Snowflake when analytics data is stored in Snowflake and users need governed natural-language access through an agent. Do not use it for Databricks SQL Warehouses or traditional SQL Server/PostgreSQL/MySQL targets; use those dedicated actions. + +## Before you start + +- Snowflake **Account Identifier** without the `snowflakecomputing.com` suffix. +- **Warehouse** and optional default database, schema, role, and user. +- Password, key-pair private key, OAuth token, or compatible reusable identity. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **Snowflake**. +2. Fill **Account Identifier** and **Warehouse**. +3. Optionally set **Default Database**, **Default Schema**, **Role**, and **Snowflake User**. +4. Choose **Authentication Method**: **Password**, **Key Pair**, or **OAuth Token**. +5. Fill **Password**, **Private Key** and optional **Private Key Passphrase**, or **OAuth Token**. +6. Set **Max Rows**, **Query Timeout (seconds)**, and **Login Timeout (seconds)**. +7. Use **Test Connection**. + +## Example prompts + +- "Which regions had the highest subscription growth last month?" +- "List schemas in the ANALYTICS database and describe CUSTOMER_HEALTH." +- "Query the top 50 accounts with declining usage over the last 30 days." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Login fails | Account identifier, user, role, or credential is wrong. | Verify account identifier format and selected auth method. | +| Queries run in the wrong namespace | Default database/schema are blank or incorrect. | Set **Default Database** and **Default Schema**, or use fully qualified names. | +| Warehouse errors occur | The configured role cannot use the warehouse. | Grant warehouse usage to the role used by the action. | + +## Related + +- [Databricks](../databricks/) +- [SQL Query](../sql-query/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) + diff --git a/docs/reference/actions/sql-query.md b/docs/reference/actions/sql-query.md new file mode 100644 index 000000000..8877388c4 --- /dev/null +++ b/docs/reference/actions/sql-query.md @@ -0,0 +1,58 @@ +--- +layout: page +title: "SQL Query" +description: "Full guide for the SQL Query SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-sql-query-configuration.png" alt="SQL Query action configuration UI." title="SQL Query configuration" capture="Capture the SQL Database Configuration panel with Query Plugin selected. Redact connection strings and credentials." %} + +## What this action does + +SQL Query executes SQL against configured SQL Server, Azure SQL, PostgreSQL, MySQL, or SQLite databases. It can validate a query, run row-returning queries, run scalar queries, and answer database questions with supplied SQL. **Max Rows** and **Timeout (seconds)** limit result size and execution time. + +## Why and when to use it + +Use SQL Query when the answer depends on exact relational data: joins, filters, counts, current rows, or scalar values. Pair it with SQL Schema when the agent needs table and column discovery before writing SQL. Do not use it for Snowflake or Databricks warehouses; those have dedicated actions. Do not use it for spreadsheets; use Tabular Processing. Keep **Read Only Mode** set to **Yes (Recommended)** unless writes have been separately reviewed. + +## Before you start + +- A reachable SQL Server, Azure SQL, PostgreSQL, MySQL, or SQLite database. +- A connection string or individual **Server**, **Database**, optional **Port**, and SQL Server **ODBC Driver** values. +- Credentials through **Username & Password**, **Integrated Authentication**, **Managed Identity (Azure)**, **Service Principal (Azure)**, **Connection String Only**, or a compatible **Reusable Identity**. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **SQL Query**. If the combined SQL panel opens, select **Plugin Type** > **Query Plugin**. +2. Choose **Database Type**: **SQL Server**, **Azure SQL**, **PostgreSQL**, **MySQL**, or **SQLite**. +3. Choose **Connection Method**: **Connection String** or **Individual Parameters**. +4. Fill **Connection String**, or fill **Server**, **Database**, optional **Port**, and **ODBC Driver** when shown. +5. Choose **Authentication Type** and fill the shown credential fields, or choose **Reusable Identity** when available. +6. Under **Query Plugin Settings**, leave **Read Only Mode** at **Yes (Recommended)** unless writes are approved. +7. Set **Max Rows** and **Timeout (seconds)**. +8. Use **Test Connection**, then save. + +## Example prompts + +- "How many open support tickets were created last week by product area?" +- "List the top 20 customers by renewal risk score and include account owner." +- "Validate this query before running it: SELECT COUNT(*) FROM dbo.Events." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Connection test fails | Connection string, server, database, driver, or credentials do not match the target. | Test the same credentials outside SimpleChat, then correct the modal fields. | +| The agent writes invalid SQL | It does not know the schema. | Pair the agent with SQL Schema or provide table and column names in instructions. | +| Results are truncated | **Max Rows** is lower than the result size. | Ask for a grouped or filtered result, or raise **Max Rows** within governance limits. | + +## Related + +- [SQL Schema](../sql-schema/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) + diff --git a/docs/reference/actions/sql-schema.md b/docs/reference/actions/sql-schema.md new file mode 100644 index 000000000..7620a3811 --- /dev/null +++ b/docs/reference/actions/sql-schema.md @@ -0,0 +1,57 @@ +--- +layout: page +title: "SQL Schema" +description: "Full guide for the SQL Schema SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-sql-schema-configuration.png" alt="SQL Schema action configuration UI." title="SQL Schema configuration" capture="Capture the SQL Database Configuration panel with Schema Plugin selected. Redact connection strings and credentials." %} + +## What this action does + +SQL Schema inspects a configured relational database and returns database schema, table lists, table details, and foreign-key relationships. It is the discovery companion for SQL Query. + +## Why and when to use it + +Use SQL Schema when an agent must understand table names, column names, types, and relationships before creating SQL. It is safer than pasting schema into prompts and reduces invalid queries. Do not use it to retrieve business rows; use SQL Query for data retrieval. The create-action UI hides the separate `sql_schema` type, but the combined SQL configuration panel includes **Schema Plugin**. + +## Before you start + +- A supported SQL database reachable by the app. +- Credentials with permission to inspect metadata for the relevant schema. +- A decision about **Include System Tables**; the recommended default is **No**. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose the SQL action flow and select **Plugin Type** > **Schema Plugin**. +2. Choose **Database Type** and **Connection Method**. +3. Fill **Connection String** or individual **Server**, **Database**, **Port**, and **ODBC Driver** fields. +4. Choose **Authentication Type** and provide credentials or a **Reusable Identity**. +5. Under **Schema Plugin Settings**, keep **Include System Tables** set to **No (Recommended)** unless system metadata is required. +6. Use **Table Filter** to narrow discovery, for example `user_*` or `*_log`. +7. Use **Test Connection** before saving. + +## Example prompts + +- "What tables are available for order history, and how are they related?" +- "Describe the schema for dbo.Customer and dbo.Invoice." +- "Find likely join paths between tickets and accounts before writing a query." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Schema output includes too much noise | System tables or broad schemas are included. | Turn off **Include System Tables** and add a **Table Filter**. | +| The agent cannot see relationships | Metadata permissions are missing or relationships are not defined as foreign keys. | Grant metadata visibility or document the relationship in agent instructions. | +| The action type is not visible as a separate card | `sql_schema` is hidden from the create-action UI. | Use the combined SQL configuration panel and choose **Schema Plugin**. | + +## Related + +- [SQL Query](../sql-query/) +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) + diff --git a/docs/reference/actions/tableau.md b/docs/reference/actions/tableau.md new file mode 100644 index 000000000..5d5cee0c2 --- /dev/null +++ b/docs/reference/actions/tableau.md @@ -0,0 +1,57 @@ +--- +layout: page +title: "Tableau" +description: "Full guide for the Tableau SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-tableau-configuration.png" alt="Tableau action configuration UI." title="Tableau configuration" capture="Capture Server and Site, Authentication, Discovery Limits, and Test Connection. Redact credentials." %} + +## What this action does + +Tableau is a read-only discovery action built on Tableau Server Client. It searches Tableau content and lists projects, workbooks, views, datasources, and workbook details. + +## Why and when to use it + +Use Tableau when users need to find or reason about Tableau assets without switching contexts. It is for metadata and content discovery, not rendering Tableau dashboards or editing assets. For underlying data questions, use the source action such as Snowflake, Databricks, or SQL. + +## Before you start + +- Tableau Server or Tableau Cloud base **Server URL**. +- Optional **Site Content URL**; leave blank for the default Tableau Server site. +- Personal Access Token name/secret or username/password. +- Agents/actions enabled with [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}). + +## Configure the action + +1. Choose **Tableau**. +2. Set **Server URL** without the site path. +3. Set **Site Content URL** if the site is not the default. +4. Choose **Authentication Method**: **Personal Access Token** or **Username and Password**. +5. Fill **PAT Name** and **PAT Secret**, or **Username** and **Password**. +6. Set **Page Size**, **Max Results**, **Timeout (seconds)**, and **Use Tableau server version negotiation**. +7. Use **Test Connection**. + +## Example prompts + +- "Find workbooks related to quarterly pipeline and list their views." +- "Which datasources are used by this workbook ID?" +- "Search Tableau projects for finance reporting assets." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| No content is returned | The Tableau credential can sign in but lacks content access. | Grant Tableau permissions or use a PAT for a user with intended access. | +| Site sign-in fails | Site Content URL includes the full path or is wrong. | Use only the content URL segment, or leave blank for the default site. | +| Version errors appear | Server version negotiation does not match the environment. | Toggle **Use Tableau server version negotiation** and retest. | + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Databricks](../databricks/) +- [Snowflake](../snowflake/) + diff --git a/docs/reference/actions/tabular-processing.md b/docs/reference/actions/tabular-processing.md new file mode 100644 index 000000000..5d8d2cec5 --- /dev/null +++ b/docs/reference/actions/tabular-processing.md @@ -0,0 +1,55 @@ +--- +layout: page +title: "Tabular Processing" +description: "Full guide for the Tabular Processing SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-tabular-processing-configuration.png" alt="Tabular Processing availability and related document settings." title="Tabular Processing configuration" capture="Capture the relevant citation/document processing settings or a chat with an uploaded CSV/XLSX ready for tabular analysis." %} + +## What this action does + +Tabular Processing analyzes CSV, XLSX, XLS, and XLSM files stored in workspace or chat-upload blob containers. It can list files, describe schemas and previews, look up values, return distinct values, count rows, aggregate columns, filter/search rows, run pandas query expressions, relate values across sheets, and group by columns or datetime components. + +## Why and when to use it + +Use Tabular Processing when users ask exact questions about spreadsheets or CSV files: counts, filters, group-bys, lookups, joins between sheets, or chart-ready aggregates. Do not use it for relational databases; use SQL Query, Databricks, or Snowflake. Do not use normal document search for questions that require row-exact computation; search is better for narrative text. + +## Before you start + +- CSV, XLSX, XLS, or XLSM files uploaded to personal, group, public, or chat document storage. +- Workspace/document access for the current user. +- Sufficient processing limits for the file size; Citation settings include tabular preview controls. +- `enable_tabular_processing_plugin` is derived in `functions_settings.py`; admin UI notes say the action is automatically enabled whenever Enhanced Citations is enabled. + +## Configure the action + +1. No dedicated Tabular Processing action panel was confirmed in `_plugin_modal.html`. +2. Enable the document processing/citation features that make tabular files available for detailed analysis. +3. Upload the CSV or workbook through Chat or Workspace so it is stored in supported containers. +4. Ask the agent to describe the file first when sheet names or columns are unclear. +5. For large outputs, ask for aggregates, filters, top-N results, or generated exports instead of all rows. + +## Example prompts + +- "In sales.xlsx, group revenue by region and quarter and show the top five regions." +- "Find rows where RenewalDate is in the next 30 days and Status is not Closed." +- "Compare the Account sheet to the Tickets sheet and count open tickets by account owner." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| The agent gives a narrative answer instead of computed rows | The prompt did not make the spreadsheet target clear. | Name the file and ask for counts, filters, or aggregates explicitly. | +| A workbook sheet is misread | The default sheet is not the intended sheet. | Include the sheet name in the prompt. | +| Output is too large | The requested rows exceed safe response limits. | Ask for top N, filters, grouped results, or an export artifact. | + +## Related + +- [SQL Query](../sql-query/) +- [Chart](../chart/) +- [Citation settings]({{ '/admin/chat/' | relative_url }}) + diff --git a/docs/reference/actions/text.md b/docs/reference/actions/text.md new file mode 100644 index 000000000..70501ba71 --- /dev/null +++ b/docs/reference/actions/text.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Text" +description: "Reference for the Text SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-text-configuration.png" alt="Text action setup or assignment UI." title="Text action" capture="Capture the Text action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Provides Semantic Kernel text functions with invocation logging. + +## Why and when to use it + +Use it for simple text transformations inside agent workflows; plain prompting is enough for many cases. + +## Before you start + +- No credentials; controlled by `enable_text_plugin`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +No service-specific configuration beyond enabling/assigning the action. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/time.md b/docs/reference/actions/time.md new file mode 100644 index 000000000..4d01d4f74 --- /dev/null +++ b/docs/reference/actions/time.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Time" +description: "Reference for the Time SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-time-configuration.png" alt="Time action setup or assignment UI." title="Time action" capture="Capture the Time action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Provides time-related functions with invocation logging. + +## Why and when to use it + +Use it when an agent needs current time or time calculations as a tool. + +## Before you start + +- No credentials; controlled by `enable_time_plugin`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +No service-specific configuration beyond enabling/assigning the action. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/wait.md b/docs/reference/actions/wait.md new file mode 100644 index 000000000..6ace62967 --- /dev/null +++ b/docs/reference/actions/wait.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Wait" +description: "Reference for the Wait SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-wait-configuration.png" alt="Wait action setup or assignment UI." title="Wait action" capture="Capture the Wait action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Provides wait/sleep behavior with invocation logging. + +## Why and when to use it + +Use it only in workflows or tool chains that intentionally pause between operations. + +## Before you start + +- No credentials; controlled by `enable_wait_plugin`. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +No service-specific configuration beyond enabling/assigning the action. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/actions/yamcs.md b/docs/reference/actions/yamcs.md new file mode 100644 index 000000000..f87d02058 --- /dev/null +++ b/docs/reference/actions/yamcs.md @@ -0,0 +1,37 @@ +--- +layout: page +title: "Yamcs" +description: "Reference for the Yamcs SimpleChat action." +section: "Reference" +audience: user +--- + + + +{% include media.html src="reference/actions-yamcs-configuration.png" alt="Yamcs action setup or assignment UI." title="Yamcs action" capture="Capture the Yamcs action setup or assignment UI with relevant fields visible. Redact secrets and user identifiers." %} + +## What this action does + +Retrieves read-only Yamcs telemetry, mission database, archive, event, packet, alarm, and link information. + +## Why and when to use it + +Use it for mission-control visibility. Do not use it for commanding; the plugin source intentionally does not support commands or writes. + +## Before you start + +- Yamcs server URL, instance, processor, auth method, and retrieval limits. +- Users also need access to the action through workspace or governance policy where applicable. + +## Configuration overview + +Set Server URL, Instance, Processor, Authentication Method, credentials, Max Rows, Timeout, Verify TLS, and optional read-only archive SQL. + +Shared wizard steps: [Common action setup steps](../#common-action-setup-steps). + +## Related + +- [Actions reference index]({{ '/reference/actions/' | relative_url }}) +- [Agents administration]({{ '/admin/agents-actions/' | relative_url }}) +- [Workspace identities]({{ '/admin/workspaces/' | relative_url }}) +- [Governance]({{ '/admin/governance/' | relative_url }}) diff --git a/docs/reference/admin_configuration.md b/docs/reference/admin_configuration.md deleted file mode 100644 index 4e0dada47..000000000 --- a/docs/reference/admin_configuration.md +++ /dev/null @@ -1,552 +0,0 @@ ---- -layout: showcase-page -title: "Admin Configuration Reference" -permalink: /reference/admin_configuration/ -menubar: docs_menu -accent: teal -eyebrow: "Reference" -description: "Use this operator guide to map each Admin Settings section to the Azure services, workspace policies, and runtime behaviors it controls." -hero_icons: ["bi-sliders", "bi-shield-check", "bi-diagram-3"] -hero_pills: ["Dependency-aware settings", "Workspace and safety controls", "Agents, actions, and citations"] -hero_links: [{ label: "Admin overview", url: "/admin_configuration/", style: "primary" }, { label: "Managed identity setup", url: "/how-to/use_managed_identity/", style: "secondary" }] ---- - -This page is the detailed companion to the main admin guide. Use it when you need to understand what a setting actually governs, which Azure dependency it relies on, and what downstream behavior changes when you turn it on. - -
-
-
-

Branding and model routing

-

Start here when you are setting the application title, logo, GPT routing, embeddings, and optional image generation behavior.

-
-
-
-

Workspaces and ingestion

-

These sections control personal, group, and public workspace availability, multimedia processing, metadata extraction, and classification behavior.

-
-
-
-

Citations and retrieval

-

Search, extraction, enhanced citations, and storage-backed preview features all sit behind separate settings with their own service requirements.

-
-
-
-

Safety and automation

-

Content safety, feedback, archiving, agents, and custom action integrations should be enabled deliberately because they affect governance as much as user experience.

-
-
- -
-

Read the settings in dependency order

-

Start with identity, branding, and model connectivity. Then enable workspaces, search, and extraction. After that, layer on citations, safety, and agents. That order mirrors how the application actually lights up features and makes troubleshooting much easier when a dependency is missing.

-
- -## Accessing Admin Settings - -Once deployed and running, users with the **Admin** role can access the Admin Settings page through the application interface. - -![Admin Settings Page](../images/admin_settings_page.png) - -## Configuration Sections - -### 1. General Settings - -Controls basic application appearance and branding. - -**Application Title** -- Set custom title displayed in browser and interface -- Default: "Simple Chat" -- Format: Plain text string - -**Custom Logo Upload** -- Upload organization logo to replace default branding -- Supported formats: PNG, JPG, GIF -- Recommended size: 200x50 pixels -- Maximum file size: 1MB - -**Landing Page Markdown Text** -- Customize welcome message on landing page -- Supports full Markdown formatting -- Can include links, images, and styling -- Useful for announcements or instructions - -### 2. Chat Model - -Configure Azure OpenAI endpoints for chat models. - -**Endpoint Configuration** -- **Direct Endpoint**: Connect directly to Azure OpenAI resource -- **APIM Endpoint**: Connect through Azure API Management - -**Authentication Methods** -- **API Key**: Traditional key-based authentication -- **Managed Identity**: Azure Managed Identity (recommended) - -**Connection Testing** -- **Test Connection**: Verify endpoint accessibility and authentication -- **Connection Status**: Real-time connection validation - -**Model Deployment Selection** -- **Active Deployment(s)**: Choose which GPT model deployments are available -- **Multi-model Support**: Allow users to select from multiple models -- **Default Model**: Set default model for new conversations - -**Multi-Model Selection Setup** -1. Configure multiple model deployments in Azure OpenAI -2. Add each deployment endpoint in Admin Settings -3. Test connections for all deployments -4. Enable multi-model selection for users -5. Set appropriate default model - -### 3. Embeddings Configuration - -Configure Azure OpenAI endpoints for embedding models used in RAG. - -**Endpoint Options** -- **Direct Endpoint**: Direct connection to Azure OpenAI -- **APIM Endpoint**: API Management routing - -**Authentication** -- **API Key**: Key-based authentication -- **Managed Identity**: Recommended for production - -**Deployment Selection** -- **Active Deployment**: Choose embedding model deployment -- **Connection Testing**: Verify embedding service availability - -**Supported Embedding Models** -- text-embedding-ada-002 -- text-embedding-3-small -- text-embedding-3-large - -### 4. Image Generation (Optional) - -Configure DALL-E image generation capabilities. - -**Feature Control** -- **Enable/Disable**: Toggle image generation feature -- **User Access**: Control which users can generate images - -**Azure OpenAI DALL-E Configuration** -- **Endpoint**: DALL-E deployment endpoint -- **Authentication**: Key or Managed Identity -- **Model Selection**: Choose DALL-E model version -- **Connection Testing**: Verify DALL-E accessibility - -### 5. Workspaces Configuration - -Control document workspace features and capabilities. - -#### Your Workspace (Personal Documents) -- **Enable/Disable**: Control personal document uploads -- **Storage Limit**: Set per-user storage limits (if applicable) -- **File Type Restrictions**: Limit allowed file types - -#### My Groups (Group Workspaces) -- **Enable/Disable**: Control group workspace functionality -- **Group Creation**: Require `CreateGroups` RBAC role for new group creation -- **Member Management**: Control who can add/remove group members -- **Document Permissions**: Set group document access policies - -#### Multimedia Support -**Video Processing (Azure Video Indexer)** -- **Enable/Disable**: Toggle video file support -- **Cloud / Endpoint Mode**: Choose Azure Public, Azure Government, or Custom Endpoint -- **Resource Group**: Azure resource group containing the Video Indexer resource -- **Subscription ID**: Azure subscription GUID for the Video Indexer resource -- **Account Name**: Video Indexer resource name -- **Account ID**: Video Indexer account identifier -- **Location**: Geographic location of Video Indexer account -- **API Endpoint**: Derived from the selected cloud mode unless Custom Endpoint is selected -- **Authentication Model**: App Service system-assigned managed identity with `Contributor` on the Video Indexer resource -- **Timeout Settings**: Processing timeout limits - -**Audio Processing (Azure Speech Service)** -- **Shared Service**: The same Speech configuration is used for audio uploads, speech-to-text input, and text-to-speech -- **Endpoint**: Speech service endpoint URL -- **Region**: Azure region for Speech service -- **Locale**: Default transcription locale -- **Authentication Type**: Key or Managed Identity -- **Speech Resource ID**: Required for managed-identity text-to-speech - -#### Metadata Extraction -- **Enable/Disable**: Toggle AI-powered metadata extraction -- **GPT Model Selection**: Choose model for metadata generation -- **Extraction Types**: Keywords, summaries, author/date inference -- **Manual Override**: Allow users to edit extracted metadata - -#### Document Classification -- **Enable/Disable**: Toggle document classification features -- **Classification Labels**: Define category names and descriptions -- **Color Coding**: Assign colors for visual organization -- **Required Classification**: Make classification mandatory for uploads -- **Default Categories**: Set default classification options - -**Sample Classification Configuration:** -``` -Financial Reports (Blue) -HR Policies (Purple) -Technical Documentation (Green) -Marketing Materials (Orange) -Legal Documents (Red) -Meeting Notes (Yellow) -``` - -### 6. Citations Configuration - -Control how document citations are displayed and linked. - -#### Standard Citations -- **Always Enabled**: Basic text-based citation references -- **Format**: "Document: filename, Chunk: number" -- **Clickable Links**: Link to document source when available - -#### Enhanced Citations -- **Enable/Disable**: Toggle rich citation features -- **Azure Storage Connection**: Storage account for processed files -- **Authentication**: Connection string or Managed Identity -- **File Organization**: User and document-scoped folder structure -- **Preview Integration**: Direct links to document pages/timestamps - -**Enhanced Citations Setup:** -1. Configure Azure Storage Account connection -2. Choose authentication method (Managed Identity recommended) -3. Test storage connectivity -4. Enable enhanced citations feature -5. Verify citation links in chat interface - -#### Mixed-Source Rollout Controls - -Mixed-source document orchestration uses independently reversible settings. Keep every stage off until the preceding stage has acceptable omission, error, and latency metrics: - -| Stage | Setting | Default | -|---|---|---| -| Manifest diagnostics | `enable_mixed_source_manifest` | Off | -| Chat and workflow Search | `enable_mixed_source_chat_search` | Off | -| Combined Analyze | `enable_mixed_source_analyze` | Off | -| Cross-format Compare | `enable_cross_format_compare` | Off | -| Reauthorized continuity | `enable_mixed_source_conversation_continuity` | Off | -| Relevance table candidates | `enable_mixed_source_relevance_candidates` | Off | -| Bounded Analyze All | `enable_mixed_source_analyze_all` | Off | -| One-to-many mixed Compare | `enable_cross_format_compare_one_to_many` | Off | -| Aggregate development telemetry | `enable_mixed_source_development_telemetry` | Off | - -Analyze All requires a ready document access index and uses the configured workflow Analyze document limit. A catalog above that limit is rejected rather than truncated. Development telemetry contains allowlisted aggregate counts and timings only; source identifiers, filenames, prompts, evidence, paths, locators, credentials, and raw settings are prohibited. - -#### Tabular Analyze/Search Parity Controls - -Tabular Analyze/Search parity ships **active by default** as of `0.250.186`. The durable preflight path is what stops Chat/Search/Analyze from answering exhaustive row-by-row tabular requests with a truncated, bounded foreground answer, so there is no admin UI toggle for it. These remain backend-only controls; use them only for canary/rollback during an incident: - -| Stage | Setting | Default | -|---|---|---| -| Shared planner mode | `tabular_request_planner_mode` | `active` | -| Search shared preflight | `enable_tabular_search_shared_preflight` | On | -| Pure tabular Analyze durable preflight | `enable_tabular_analyze_durable_preflight` | On | -| Mixed deferred-composition planning | `enable_tabular_mixed_deferred_composition_planning` | Off | -| Multi-file execution-unit planning | `enable_tabular_multifile_execution_unit_planning` | Off | - -Emergency rollback (no admin UI, no settings edit required): set the App Service/environment variable `SIMPLECHAT_DISABLE_TABULAR_PARITY_DURABLE_PREFLIGHT=true` to force `tabular_request_planner_mode` back to `off` and both shared-preflight flags back to `False` for every request, regardless of stored settings. Remove or unset the variable to restore the active default. -| Parity rollout percentage | `tabular_analyze_parity_rollout_percent` | `100` | -| Legacy post-tool fallback mode | `tabular_legacy_post_tool_fallback_mode` | `enabled` | - -The shared preflight telemetry tag is `[TABULAR_SHARED_PREFLIGHT]`. Use allowlisted dimensions such as planner mode, execution contract, execution state, reason code, rollout assignment, rollout percentage, and fallback mode. Do not log prompt text, file names, source locators, generated answers, raw errors, credentials, or raw settings. - -The two planning controls expose metadata only. Multi-file planning does not queue grouped durable runs, and deferred-composition planning does not resume collective synthesis after individual runs finish. - -### 7. Safety Configuration - -Configure content moderation and user feedback systems. - -#### Content Safety (Azure AI Content Safety) -- **Enable/Disable**: Toggle content moderation -- **Endpoint Configuration**: Direct or APIM endpoint -- **Authentication**: Key or Managed Identity authentication -- **Connection Testing**: Verify Content Safety service access - -**Content Filtering Categories:** -- **Hate Speech**: Detect and filter hate content -- **Sexual Content**: Moderate sexual material -- **Violence**: Filter violent content -- **Self-Harm**: Detect self-harm content -- **Severity Thresholds**: Set filtering sensitivity levels - -**Custom Blocklists:** -- **Custom Terms**: Add organization-specific blocked terms -- **Regex Patterns**: Use pattern matching for complex filtering -- **Allowlists**: Define permitted terms that override filters - -#### User Feedback System -- **Enable/Disable**: Toggle user rating and feedback collection -- **Rating Types**: Thumbs up/down, star ratings, custom scales -- **Comment Collection**: Allow detailed feedback comments -- **Admin Dashboard**: Interface for reviewing user feedback - -#### RBAC Access Control -**Safety Violation Admin (`SafetyViolationAdmin` role)** -- **Requirement**: Require specific role for safety violation access -- **Violation Dashboard**: Interface for reviewing flagged content -- **User Management**: Actions for users with safety violations - -**Feedback Admin (`FeedbackAdmin` role)** -- **Requirement**: Require specific role for feedback access -- **Feedback Dashboard**: Interface for reviewing user feedback -- **Response Management**: Tools for responding to user feedback - -#### Conversation Archiving -- **Enable/Disable**: Toggle conversation archiving for compliance -- **Archive Location**: Cosmos DB container for archived conversations -- **Retention Policy**: How long to retain archived conversations -- **Access Control**: Who can access archived conversations -- **Export Options**: Tools for exporting archived data - -### 8. Search & Extract Configuration - -Configure document processing and search capabilities. - -#### Azure AI Search -- **Endpoint**: AI Search service endpoint URL -- **Authentication**: Key or Managed Identity -- **Connection Testing**: Verify search service connectivity -- **Index Management**: Configuration for search indexes -- **Query Configuration**: Search behavior and relevance tuning - -**Search Performance Options:** -- **Semantic Search**: Enable semantic ranking for better relevance -- **Hybrid Search**: Combine vector and keyword search -- **Query Expansion**: Expand queries for better recall -- **Result Ranking**: Configure search result ordering - -#### Document Intelligence -- **Endpoint**: Document Intelligence service endpoint -- **Authentication**: Key or Managed Identity -- **Connection Testing**: Verify document processing service -- **Processing Options**: Configure document analysis features - -**Supported Document Intelligence Features:** -- **OCR**: Optical character recognition for images/PDFs -- **Layout Analysis**: Extract document structure and formatting -- **Table Extraction**: Identify and extract table data -- **Form Recognition**: Process structured forms and documents - -### 9. Other Settings - -Additional configuration options for application behavior. - -#### File Management -**Maximum File Size** -- **Setting**: Maximum upload size in MB -- **Default**: 50MB -- **Range**: 1MB - 500MB (depending on App Service plan) -- **Impact**: Affects upload performance and storage costs - -**Supported File Types** -- **Text Files**: .txt, .md, .html, .json -- **Documents**: .pdf, .docx, .pptx, .xlsx, .xlsm, .xls, .csv -- **Images**: .jpg, .jpeg, .png, .bmp, .tiff, .tif, .heif -- **Video**: .mp4, .mov, .avi, .wmv, .mkv, .webm (requires Video Indexer) -- **Audio**: .mp3, .wav, .ogg, .aac, .flac, .m4a (requires Speech Service) - -#### Chat Behavior -**Conversation History Limit** -- **Setting**: Maximum number of past conversations displayed -- **Default**: 50 conversations -- **Range**: 10 - 1000 conversations -- **Impact**: Affects UI performance and user experience - -**Default System Prompt** -- **Configuration**: Base instructions for AI model behavior -- **Customization**: Tailor AI personality and response style -- **Best Practices**: Clear, specific instructions work best -- **Template Variables**: Support for dynamic prompt elements - -**Example System Prompt:** -``` -You are a helpful AI assistant for [Organization Name]. -You provide accurate, professional responses based on the -documents and context provided. Always cite your sources -and indicate when you're uncertain about information. - -Guidelines: -- Be concise but thorough -- Use professional tone -- Cite document sources -- Ask for clarification when needed -- Follow company policies and guidelines -``` - -#### Logging and Monitoring -**File Processing Logs** -- **Enable/Disable**: Toggle verbose logging for document ingestion -- **Log Level**: Control detail level of processing logs -- **Retention**: How long to keep processing logs -- **Access**: Who can view processing logs - -**Application Insights Integration** -- **Performance Monitoring**: Track application performance metrics -- **Error Tracking**: Monitor and alert on application errors -- **User Analytics**: Track user behavior and feature usage -- **Custom Events**: Log business-specific events and metrics - -## Configuration Best Practices - -### Security -- ✅ Use Managed Identity authentication whenever possible -- ✅ Regularly rotate API keys if used -- ✅ Enable Content Safety for production deployments -- ✅ Implement proper RBAC role assignments -- ✅ Monitor access to admin settings -- ✅ Use managed identity, not API keys, for Azure Video Indexer in the current Simple Chat setup -- ✅ Use the Speech custom-domain endpoint for managed identity and add the Speech Resource ID when enabling managed-identity voice responses - -### Performance -- ✅ Test all service connections after configuration -- ✅ Configure appropriate timeouts for external services -- ✅ Monitor service quotas and rate limits -- ✅ Use autoscaling for variable workloads -- ✅ Implement caching where appropriate - -### Operational -- ✅ Document configuration decisions and changes -- ✅ Create configuration backup/restore procedures -- ✅ Test disaster recovery scenarios -- ✅ Monitor service health and dependencies -- ✅ Plan for service maintenance windows - -### Cost Management -- ✅ Monitor usage and costs across all services -- ✅ Set appropriate service tiers for usage patterns -- ✅ Use consumption-based pricing where available -- ✅ Implement cost alerts and budgets -- ✅ Regular cost optimization reviews - -## Troubleshooting Configuration Issues - -### Service Connection Failures - -**Azure OpenAI Connection Issues:** -``` -Common Problems: -- Incorrect endpoint URL format -- Invalid API key or expired key -- Managed Identity not properly configured -- Model deployment not found -- Rate limiting or quota exceeded - -Solutions: -- Verify endpoint URL includes proper prefix/suffix -- Generate new API key in Azure Portal -- Check Managed Identity role assignments -- Confirm model deployment name and availability -- Monitor quota usage in Azure OpenAI Studio -``` - -**Cosmos DB Connection Issues:** -``` -Common Problems: -- Firewall blocking App Service -- Invalid connection string format -- Managed Identity permissions missing -- Network connectivity issues - -Solutions: -- Add App Service IP to Cosmos DB firewall -- Verify connection string includes AccountEndpoint -- Assign Cosmos DB Data Contributor role -- Check Private Endpoint configuration -``` - -### Feature Configuration Problems - -**Document Processing Issues:** -``` -Common Problems: -- File size exceeding limits -- Unsupported file types -- Document Intelligence quota exceeded -- Storage connection issues - -Solutions: -- Increase file size limits or compress files -- Check supported file type list -- Monitor Document Intelligence usage -- Verify storage account connectivity -``` - -**Search and RAG Issues:** -``` -Common Problems: -- Documents not appearing in search results -- Poor search relevance -- Slow query performance -- Index synchronization issues - -Solutions: -- Verify document processing completed successfully -- Tune search query parameters -- Check AI Search service performance tier -- Monitor indexing status and errors -``` - -## API Configuration Reference - -For programmatic configuration management, Simple Chat exposes REST APIs for admin settings: - -### Configuration API Endpoints - -**Get Configuration:** -```http -GET /api/admin/config -Authorization: Bearer {token} -``` - -**Update Configuration:** -```http -PUT /api/admin/config -Content-Type: application/json -Authorization: Bearer {token} - -{ - "general": {...}, - "gpt": {...}, - "embeddings": {...} -} -``` - -**Test Service Connection:** -```http -POST /api/admin/test-connection -Content-Type: application/json -Authorization: Bearer {token} - -{ - "service": "openai", - "config": {...} -} -``` - -### Configuration Schema - -The configuration follows a structured schema with validation: - -```json -{ - "general": { - "appTitle": "string", - "customLogo": "base64-string", - "landingPageText": "markdown-string" - }, - "gpt": { - "endpoint": "url", - "authType": "key|managed-identity", - "apiKey": "string", - "deployments": ["string-array"] - }, - "embeddings": { - "endpoint": "url", - "authType": "key|managed-identity", - "deployment": "string" - } -} -``` - -This reference provides comprehensive details for configuring Simple Chat to meet your organization's specific needs and requirements. diff --git a/docs/reference/api_reference.md b/docs/reference/api_reference.md index c338d92b2..1115bba95 100644 --- a/docs/reference/api_reference.md +++ b/docs/reference/api_reference.md @@ -63,7 +63,7 @@ The live API documentation and external health routes are controlled from **Admi | Protected health check | **Enable /external/healthcheck** | /external/healthcheck | | Unauthenticated health check | **Enable /external/healthcheckz** | /external/healthcheckz | -For the full operator workflow, see [Configure Branding, Home Page, and Support Settings]({{ '/how-to/admin_ui_settings/' | relative_url }}). +For the full operator workflow, see [Configure Branding, Home Page, and Support Settings]({{ '/guides/configure-branding-and-support-settings/' | relative_url }}). ## Documentation endpoints diff --git a/docs/application_workflows.md b/docs/reference/application-workflows.md similarity index 94% rename from docs/application_workflows.md rename to docs/reference/application-workflows.md index c794b551f..903455c38 100644 --- a/docs/application_workflows.md +++ b/docs/reference/application-workflows.md @@ -1,7 +1,8 @@ --- layout: showcase-page title: "Application Workflows" -permalink: /application_workflows/ +section: "Reference" +permalink: /reference/application-workflows/ menubar: docs_menu accent: violet eyebrow: "How Requests Move" @@ -21,7 +22,10 @@ hero_links: - label: Open troubleshooting url: /troubleshooting/ style: secondary +redirect_from: + - /application_workflows/ --- + These flows are the fastest way to understand where Simple Chat applies safety checks, where Azure services participate, and where document-grounded answers actually come from.
@@ -48,7 +52,7 @@ These flows are the fastest way to understand where Simple Chat applies safety c ## Content Safety Workflow -![Workflow - Content Safety](./images/workflow-content_safety.png) +![Workflow - Content Safety]({{ '/images/workflow-content_safety.png' | relative_url }}) 1. A user sends a message from the chat interface. 2. If Content Safety is enabled, the message is evaluated before it reaches the target backend service. @@ -68,7 +72,7 @@ The default flow protects the inbound user message. Downstream services may stil This workflow covers what happens when users upload content into personal or group workspaces for Retrieval-Augmented Generation. -![Add your data - Workflow](./images/workflow-add_your_data.png) +![Add your data - Workflow]({{ '/images/workflow-add_your_data.png' | relative_url }}) 1. Users upload one or more supported files through the application UI. 2. The backend determines file type and chooses the correct extraction path. diff --git a/docs/reference/chat-controls.md b/docs/reference/chat-controls.md new file mode 100644 index 000000000..9f1cf5fbc --- /dev/null +++ b/docs/reference/chat-controls.md @@ -0,0 +1,121 @@ +--- +layout: page +title: "Chat interface controls" +description: "Reference for every documented control in the SimpleChat chat interface." +section: "Reference" +audience: user +--- + +## How to use this reference + +Use this page when you can see a control in Chat but are not sure what it does or when to use it. The generated inventory lists 47 real controls plus 3 child sub-elements. The child elements `document-comparison-edit-btn-label`, `fork-conversation-button-label`, and `fork-conversation-button-spinner` are mentioned with their parent controls instead of receiving separate rows. + +## Conversation list + +{% include media.html src="reference/chat-controls-conversation-list.png" alt="Conversation list showing selection actions and the new-chat button." title="Conversation list" capture="Capture the conversation list with selection actions and the new-chat button visible. Redact conversation titles and user names." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `pin-selected-btn` | Pins the selected conversations so they stay prominent in the conversation list. | Use it when a project, incident, or recurring workflow needs to stay easy to find. | Always available | +| `hide-selected-btn` | Hides selected conversations from the normal feed without deleting them. | Use it to reduce noise while keeping conversations recoverable from hidden/history views. | Always available | +| `delete-selected-btn` | Starts deletion for selected conversations. If archiving is enabled, deletion can archive instead of immediately removing records. | Use it when old conversations should no longer appear in your active workspace. | Always available | +| `export-selected-btn` | Opens the export flow for selected conversations. | Use it when you need an offline copy for handoff, records, review, or migration. | Always available | +| `new-conversation-btn` | Creates a fresh chat thread and resets the active conversation context. | Use it when a new task should not inherit previous context, documents, or agent state. | Always available | + +## Conversation header and status + +{% include media.html src="reference/chat-controls-conversation-header.png" alt="Conversation header with title actions, scope lock, workflow activity, contents, and document buttons visible." title="Conversation header" capture="Capture the conversation header with title actions, scope lock, workflow activity, contents, and document buttons visible. Redact conversation title." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `workflow-activity-btn` | Opens the workflow activity view associated with the current conversation when a workflow run is linked. | Use it to inspect workflow progress, failures, and generated activity without leaving the chat context. | Always available | +| `conversation-info-btn` | Opens conversation details for the active chat. | Use it when you need ownership, timestamps, participants, or other conversation-level facts. | Always available | +| `conversation-contents-toggle` | Opens the conversation contents drawer for navigating messages and artifacts. | Use it in long chats when jumping to a section is faster than scrolling. | Always available | +| `conversation-documents-toggle` | Opens the used-documents drawer for files and sources referenced by the conversation. | Use it to audit what grounded an answer or to get back to a cited source. | Always available | +| `header-scope-lock-btn` | Shows that the conversation search/document scope is locked and opens the scope-lock modal. | Use it to confirm why a chat is constrained before asking broader questions. | Always available | +| `confirm-scope-lock-toggle-btn` | Confirms a scope-lock change from the scope-lock modal. | Use it when you intentionally want to lock or unlock the conversation scope after reviewing the warning. | Always available | + +## Chat tools and composer + +{% include media.html src="reference/chat-controls-composer-tools.png" alt="Message composer with quick tools, upload controls, URL review, web search, and send button visible." title="Chat tools and composer" capture="Capture the message composer with quick tools, upload controls, URL review, web search, and send button visible." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `image-generate-btn` | Adds image-generation intent to the chat flow so the assistant can create images from the prompt. | Use it for visual concepts, mockups, illustrations, or generated imagery rather than text-only answers. | [`enable_image_generation`]({{ '/admin/ai-models/' | relative_url }}) | +| `search-documents-btn` | Opens the grounded search panel for searching workspace documents from chat. | Use it when the answer should come from personal or group workspace content instead of general model knowledge. | [`enable_group_workspaces`]({{ '/admin/workspaces/' | relative_url }})
[`enable_user_workspace`]({{ '/admin/workspaces/' | relative_url }}) | +| `choose-file-btn` | Lets you select a supported local file and starts chat upload processing for the conversation. | Use it when the file is the immediate subject of the conversation and you do not want to visit Workspace first. | [`enable_chat_file_uploads`]({{ '/admin/workspaces/' | relative_url }}) | +| `upload-btn` | Adds the selected upload to the chat after file selection. | Use it to confirm an upload before asking questions about that file. | [`enable_web_search`]({{ '/admin/knowledge/' | relative_url }}) | +| `search-web-btn` | Allows the chat to search the web using the configured Bing/Foundry web-search path. | Use it for current events, public facts, or external pages that are not in your workspaces. | [`enable_web_search`]({{ '/admin/knowledge/' | relative_url }}) | +| `url-access-btn` | Opens review for URLs pasted into the message so they can be inspected by the chat flow. | Use it when pasted links should be fetched or reasoned over instead of treated as plain text. | [`enable_url_access`]({{ '/admin/knowledge/' | relative_url }}) | +| `source-review-btn` | Starts Deep Research so SimpleChat can inspect search results and linked source pages within configured crawl limits. | Use it for research tasks where source review and evidence collection matter more than a quick answer. | [`enable_source_review`]({{ '/admin/knowledge/' | relative_url }}) | +| `send-btn` | Sends the current composer message to the selected model or agent. | Use it once the prompt, files, scope, and optional tools are ready. | Always available | +| `scroll-to-bottom-btn` | Jumps the message pane to the newest message when you are scrolled upward. | Use it to return to an in-progress response or the latest turn. | Always available | +| `chat-mobile-tools-toggle` | Opens the mobile tools panel containing quick actions, voice controls, and selectors. | Use it on smaller screens when desktop toolbar controls move into the offcanvas panel. | Always available | +| `chat-tutorial-btn` | Launches the guided chat walkthrough. | Use it when onboarding users or when you want a reminder of the main chat workflow. | Always available | + +## Prompt, model, agent, and reasoning selectors + +{% include media.html src="reference/chat-controls-selectors.png" alt="Toolbar selectors showing saved prompts, model picker, agent picker, reasoning, and voice-response toggle." title="Prompt, model, agent, and reasoning selectors" capture="Capture toolbar selectors showing saved prompts, model picker, agent picker, reasoning, and voice-response toggle." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `search-prompts-btn` | Shows the saved-prompt picker button for finding prompts available from enabled workspaces and agents. | Use it when you want a reusable prompt instead of typing instructions from scratch. | [`enable_group_workspaces`]({{ '/admin/workspaces/' | relative_url }})
[`enable_public_workspaces`]({{ '/admin/workspaces/' | relative_url }})
[`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }})
[`enable_user_workspace`]({{ '/admin/workspaces/' | relative_url }}) | +| `prompt-dropdown-button` | Opens the searchable saved-prompt dropdown. | Use it to insert a prepared prompt for a common workflow, team standard, or public workspace task. | [`enable_group_workspaces`]({{ '/admin/workspaces/' | relative_url }})
[`enable_public_workspaces`]({{ '/admin/workspaces/' | relative_url }})
[`enable_user_workspace`]({{ '/admin/workspaces/' | relative_url }}) | +| `model-dropdown-button` | Opens the searchable model picker for the active chat. | Use it when you need a different approved model for cost, quality, modality, or policy reasons. | Always available | +| `enable-agents-btn` | Switches the chat toolbar toward agent selection when agents are enabled. | Use it when the task needs configured tools, instructions, or actions rather than a plain model response. | [`enable_semantic_kernel`]({{ '/admin/agents-actions/' | relative_url }}) | +| `agent-dropdown-button` | Opens the searchable agent picker. | Use it to choose a specialized agent with approved instructions, actions, documents, or governance scope. | Always available | +| `reasoning-toggle-btn` | Opens reasoning-effort controls for models that support configurable reasoning. | Use it when a hard planning or analysis task needs more deliberate reasoning, or a simple task should be cheaper/faster. | Always available | +| `tts-autoplay-toggle-btn` | Toggles automatic spoken playback for AI responses. | Use it for hands-free review, accessibility, or listening while working in another window. | [`enable_text_to_speech`]({{ '/admin/knowledge/' | relative_url }}) | + +## Grounded search and document scope + +{% include media.html src="reference/chat-controls-grounded-search.png" alt="Grounded Search panel with action, scope, document, tags, filters, and comparison controls visible." title="Grounded search and document scope" capture="Capture the Grounded Search panel with action, scope, document, tags, filters, and comparison controls visible." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `scope-dropdown-button` | Chooses the workspace scope used by grounded search, such as all accessible content, personal, group, or public workspaces. | Use it when the same question should be limited to a team workspace, public workspace, or personal files. | Always available | +| `document-dropdown-button` | Chooses the specific documents used by grounded search, analysis, or comparison. | Use it when you know which file or small document set should ground the answer. | Always available | +| `tags-dropdown-button` | Filters grounded search by document tags. | Use it to narrow broad workspaces to a topic, project, lifecycle stage, or classification. | Always available | +| `clearFiltersBtn` | Clears selected grounded-search filters. | Use it when a search is too narrow or you want to return to the full chosen scope. | Always available | +| `document-comparison-edit-btn` | Reopens comparison setup for source/target document selections. The child label `document-comparison-edit-btn-label` supplies the visible text. | Use it when the wrong source or target document was selected for Compare. | Always available | + +## Search within a conversation + +{% include media.html src="reference/chat-controls-conversation-search.png" alt="Search-in-conversation modal with Search, Previous, Next, Clear Filters, and Clear History controls." title="Search within a conversation" capture="Capture the search-in-conversation modal with Search, Previous, Next, Clear Filters, and Clear History controls visible." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `performSearchBtn` | Runs the current search query against conversation content. | Use it to find a prior answer, pasted detail, citation, or decision inside a long conversation. | Always available | +| `searchPrevBtn` | Moves to the previous match in the conversation search results. | Use it to step backward through matches without closing the search panel. | Always available | +| `searchNextBtn` | Moves to the next match in the conversation search results. | Use it to scan every occurrence of a term or phrase in order. | Always available | +| `clearHistoryBtn` | Clears stored conversation search history. | Use it when old searches are no longer useful or should not appear as suggestions. | Always available | + +## Export, fork, and delete dialogs + +{% include media.html src="reference/chat-controls-export-fork-delete.png" alt="Conversation export, fork, and delete confirmation dialogs with navigation and confirmation buttons visible." title="Export, fork, and delete dialogs" capture="Capture conversation export, fork, and delete confirmation dialogs with navigation and confirmation buttons visible. Redact content." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `export-prev-btn` | Moves back to the previous step in the conversation export flow. | Use it to revise export choices before creating the output. | Always available | +| `export-next-btn` | Moves forward to the next step in the conversation export flow. | Use it to continue after choosing conversations and export options. | Always available | +| `confirm-fork-conversation-btn` | Confirms forking the conversation; `fork-conversation-button-label` supplies text and `fork-conversation-button-spinner` appears while the fork runs. | Use it when you want a separate branch of the same chat context for a different direction or experiment. | Always available | +| `confirm-delete-conversation-btn` | Confirms deletion for the active conversation. | Use it after reviewing the delete dialog and deciding the conversation should be removed or archived. | Always available | + +## Collaboration and replies + +{% include media.html src="reference/chat-controls-collaboration.png" alt="Collaboration dialog showing participant confirmation and reply cancellation controls." title="Collaboration and replies" capture="Capture the collaboration dialog showing participant confirmation and reply cancellation controls. Redact names and email addresses." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `collaboration-confirm-add-btn` | Confirms adding a participant to a collaborative conversation. | Use it when the selected person should join the shared conversation. | Always available | +| `collaboration-reply-cancel-btn` | Cancels the current reply-to-message state. | Use it when you started replying to a specific message but want the next message to be a normal conversation turn. | Always available | + +## Voice input + +{% include media.html src="reference/chat-controls-voice.png" alt="Composer voice input recording state with microphone, send recording, and cancel recording controls visible." title="Voice input" capture="Capture the composer voice input recording state with microphone, send recording, and cancel recording controls visible." %} + +| Control | What it does | Why you would use it | Enabled by | +| --- | --- | --- | --- | +| `speech-input-btn` | Starts voice input capture for speech-to-text in chat. | Use it to dictate longer prompts, work hands-free, or reduce typing. | [`enable_speech_to_text_input`]({{ '/admin/knowledge/' | relative_url }}) | +| `send-recording-btn` | Submits the captured recording for transcription and chat input. | Use it after recording a prompt you want SimpleChat to process. | Always available | +| `cancel-recording-btn` | Stops and discards the current voice recording. | Use it when you misspoke, captured background noise, or no longer want to send the dictated prompt. | Always available | diff --git a/docs/reference/deploy/azd-cli_deploy.md b/docs/reference/deploy/azd-cli_deploy.md index c7de9157a..28a488da7 100644 --- a/docs/reference/deploy/azd-cli_deploy.md +++ b/docs/reference/deploy/azd-cli_deploy.md @@ -16,10 +16,10 @@ hero_pills: - Container-based runtime hero_links: - label: "Getting Started" - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: "Upgrade paths" - url: /how-to/upgrade_paths/ + url: /guides/upgrade-paths/ style: secondary nav_links: prev: diff --git a/docs/reference/deploy/azurecli_powershell_deploy.md b/docs/reference/deploy/azurecli_powershell_deploy.md index 44ddb4eaf..ee48e655b 100644 --- a/docs/reference/deploy/azurecli_powershell_deploy.md +++ b/docs/reference/deploy/azurecli_powershell_deploy.md @@ -19,7 +19,7 @@ hero_links: url: /reference/deploy/ style: primary - label: "Upgrade paths" - url: /how-to/upgrade_paths/ + url: /guides/upgrade-paths/ style: secondary nav_links: prev: @@ -103,6 +103,6 @@ cd deployers/azurecli ## References -- [Setup Instructions](../../setup_instructions.md) -- [Upgrade Paths](../../how-to/upgrade_paths.md) +- [Setup Instructions]({{ '/start/deployment-options/' | relative_url }}) +- [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}) - [Azure CLI deployer README](https://github.com/microsoft/simplechat/blob/main/deployers/azurecli/README.md) \ No newline at end of file diff --git a/docs/reference/deploy/bicep_deploy.md b/docs/reference/deploy/bicep_deploy.md index dd7ea60ab..33c36ae07 100644 --- a/docs/reference/deploy/bicep_deploy.md +++ b/docs/reference/deploy/bicep_deploy.md @@ -19,7 +19,7 @@ hero_links: url: /reference/deploy/azd-cli_deploy/ style: primary - label: "Enterprise networking" - url: /how-to/enterprise_networking/ + url: /guides/enterprise-networking/ style: secondary nav_links: prev: @@ -75,7 +75,7 @@ The Bicep templates are the infrastructure backbone behind the repo's recommende ## References -- [Setup Instructions](../../setup_instructions.md) -- [Special Deployment Scenarios](../../setup_instructions_special.md) -- [Enterprise Networking](../../how-to/enterprise_networking.md) +- [Setup Instructions]({{ '/start/deployment-options/' | relative_url }}) +- [Special Deployment Scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) +- [Enterprise Networking]({{ '/guides/enterprise-networking/' | relative_url }}) - [Bicep deployer README](https://github.com/microsoft/simplechat/blob/main/deployers/bicep/README.md) diff --git a/docs/reference/deploy/index.md b/docs/reference/deploy/index.md index fd6b18cfa..9e96cf6b4 100644 --- a/docs/reference/deploy/index.md +++ b/docs/reference/deploy/index.md @@ -16,10 +16,10 @@ hero_pills: - Native Python notes included hero_links: - label: "Getting Started" - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: "Upgrade paths" - url: /how-to/upgrade_paths/ + url: /guides/upgrade-paths/ style: secondary --- @@ -67,6 +67,6 @@ This section is the deployment decision map. Use it after you already understand ## Related guides -- [Setup Instructions](../../setup_instructions.md) -- [Upgrade Paths](../../how-to/upgrade_paths.md) -- [Special Deployment Scenarios](../../setup_instructions_special.md) \ No newline at end of file +- [Setup Instructions]({{ '/start/deployment-options/' | relative_url }}) +- [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}) +- [Special Deployment Scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) \ No newline at end of file diff --git a/docs/reference/deploy/manual_deploy.md b/docs/reference/deploy/manual_deploy.md index c8285c77a..6d0bd908e 100644 --- a/docs/reference/deploy/manual_deploy.md +++ b/docs/reference/deploy/manual_deploy.md @@ -16,7 +16,7 @@ hero_pills: - Upgrade validation matters hero_links: - label: "Upgrade paths" - url: /how-to/upgrade_paths/ + url: /guides/upgrade-paths/ style: primary - label: "Deployment reference" url: /reference/deploy/ diff --git a/docs/reference/deploy/terraform_deploy.md b/docs/reference/deploy/terraform_deploy.md index 026bc305f..4f188dc0d 100644 --- a/docs/reference/deploy/terraform_deploy.md +++ b/docs/reference/deploy/terraform_deploy.md @@ -19,7 +19,7 @@ hero_links: url: /reference/deploy/ style: primary - label: "Upgrade paths" - url: /how-to/upgrade_paths/ + url: /guides/upgrade-paths/ style: secondary nav_links: prev: @@ -87,6 +87,6 @@ python -m gunicorn -c gunicorn.conf.py app:app ## References -- [Setup Instructions](../../setup_instructions.md) -- [Upgrade Paths](../../how-to/upgrade_paths.md) +- [Setup Instructions]({{ '/start/deployment-options/' | relative_url }}) +- [Upgrade Paths]({{ '/guides/upgrade-paths/' | relative_url }}) - [Terraform deployer README](https://github.com/microsoft/simplechat/blob/main/deployers/terraform/ReadMe.md) diff --git a/docs/external_apps_overview.md b/docs/reference/external-applications.md similarity index 94% rename from docs/external_apps_overview.md rename to docs/reference/external-applications.md index 19c784d37..d8ffa90b6 100644 --- a/docs/external_apps_overview.md +++ b/docs/reference/external-applications.md @@ -1,7 +1,8 @@ --- layout: showcase-page title: "External Applications Overview" -permalink: /external_apps_overview/ +section: "Reference" +permalink: /reference/external-applications/ menubar: docs_menu accent: emerald eyebrow: "Supporting Utilities" @@ -21,6 +22,8 @@ hero_links: - label: Admin configuration url: /admin_configuration/ style: secondary +redirect_from: + - /external_apps_overview/ --- These utilities sit beside the main application. They are intended for administrators and support teams that need faster environment preparation or large-scale document loading. diff --git a/docs/reference/features.md b/docs/reference/features.md index e94a406a2..94860aeb8 100644 --- a/docs/reference/features.md +++ b/docs/reference/features.md @@ -1,57 +1,32 @@ --- -layout: showcase-page +layout: page title: "Features Reference" permalink: /reference/features/ menubar: docs_menu -accent: blue -eyebrow: "Reference" description: "Use this page as an operator-focused map of Simple Chat capabilities, service dependencies, and the docs that go deeper on each area." -hero_icons: - - bi-grid-1x2 - - bi-chat-square-text - - bi-gear -hero_pills: - - Chat and retrieval - - Workspaces and processing - - Safety and automation -hero_links: - - label: "Feature overview" - url: /features/ - style: primary - - label: "Application workflows" - url: /application_workflows/ - style: secondary --- The main features page is the broad product tour. This reference page is the faster operator map for people who need to connect a capability to the Azure services, admin settings, and user workflows behind it. -
-
-
-

Conversation surface

-

Core chat includes model selection, grounded responses, citations, exports, history, and optional multimedia or image-generation extensions.

-
-
-
-

Workspace ingestion

-

Personal, group, and public workspaces add uploads, extraction, chunking, indexing, metadata, and optional classification across shared document sets.

-
-
-
-

Governance and safety

-

Admins can control content safety, archiving, feedback, access roles, API documentation visibility, and advanced feature exposure from one settings surface.

-
-
-
-

Agents and integrations

-

Optional Semantic Kernel agents, actions, OpenAPI plugins, SQL workflows, and external application helpers let teams move beyond plain chat into automation.

-
-
- -
-

Use the narrative page and the reference page differently

-

Start with Features when you want the user-facing tour. Switch to this page when you are mapping a requirement to configuration, dependencies, or adjacent docs.

-
+## Conversation surface + +Core chat includes model selection, grounded responses, citations, exports, history, and optional multimedia or image-generation extensions. + +## Workspace ingestion + +Personal, group, and public workspaces add uploads, extraction, chunking, indexing, metadata, and optional classification across shared document sets. + +## Governance and safety + +Admins can control content safety, archiving, feedback, access roles, API documentation visibility, and advanced feature exposure from one settings surface. + +## Agents and integrations + +Optional Semantic Kernel agents, actions, OpenAPI plugins, SQL workflows, and external application helpers let teams move beyond plain chat into automation. + +## Use the narrative page and the reference page differently + +Start with [Features]({{ '/features/' | relative_url }}) when you want the user-facing tour. Switch to this page when you are mapping a requirement to configuration, dependencies, or adjacent docs. ## Feature map by operating area @@ -65,29 +40,26 @@ The main features page is the broad product tour. This reference page is the fas ## Where to go deeper -
-
-
-

Admin settings

-

Use the admin reference when you need to turn a capability on and verify which toggles or service tests control it.

-

Open admin reference

-
-
-
-

Workflows

-

Use the workflow guide when you want to understand how uploads, safety review, and retrieval-backed chat behave behind the UI.

-

Review workflows

-
-
-
-

Latest release changes

-

Use the latest-release section when you need the most recent UI and capability changes rather than the long-lived platform map.

-

Browse latest release

-
-
-
-

Tutorials and how-to guides

-

Use tutorials for onboarding and the how-to guides for repeatable operational workflows once the platform is already running.

-

Open tutorials

-
-
+### Admin settings + +Use the admin reference when you need to turn a capability on and verify which toggles or service tests control it. + +[Open admin reference]({{ '/reference/admin_configuration/' | relative_url }}) + +### Workflows + +Use the workflow guide when you want to understand how uploads, safety review, and retrieval-backed chat behave behind the UI. + +[Review workflows]({{ '/reference/application-workflows/' | relative_url }}) + +### Latest release changes + +Use the latest-release section when you need the most recent UI and capability changes rather than the long-lived platform map. + +[Browse latest release]({{ '/latest-release/' | relative_url }}) + +### Tutorials and how-to guides + +Use tutorials for onboarding and the how-to guides for repeatable operational workflows once the platform is already running. + +[Open tutorials]({{ '/guides/' | relative_url }}) diff --git a/docs/search-index.json b/docs/search-index.json new file mode 100644 index 000000000..218db594b --- /dev/null +++ b/docs/search-index.json @@ -0,0 +1,57 @@ +--- +layout: null +permalink: /search-index.json +--- +{%- comment -%} +Search index for the documentation site. + +The previous index (search.json) contained title, description and keywords only. +Because 88% of entries had no description and no page body was ever indexed, +search was effectively a substring match against page titles. This version +indexes real page content so queries match what readers actually read. + +Excluded from the index: + - Engineering notes under explanation/features and explanation/fixes. Those + are excluded from the build entirely (they were 84% of the old index). + - The 404 page and the media status worklist, which are not reader content. + - Body text of generated release-notes pages, which are large changelogs; their + titles and descriptions are still indexed so version lookups work. +{%- endcomment -%} +{%- assign indexable = site.pages | where_exp: "item", "item.title" -%} +[ +{%- assign first = true -%} +{%- for item in indexable -%} + {%- assign skip = false -%} + {%- if item.url contains '/404' -%}{%- assign skip = true -%}{%- endif -%} + {%- if item.url contains '/contributing/media-status' -%}{%- assign skip = true -%}{%- endif -%} + {%- if item.noindex -%}{%- assign skip = true -%}{%- endif -%} + + {%- unless skip -%} + {%- comment -%} Large changelog pages are indexed by metadata only. {%- endcomment -%} + {%- assign body = '' -%} + {%- unless item.url contains '/explanation/release-notes/' or item.url contains '/explanation/release_notes' -%} + {%- assign body = item.content | strip_html | normalize_whitespace | truncate: 3000, '' -%} + {%- endunless -%} + + {%- comment -%} Guarantee every entry has a description; fall back to the opening prose. {%- endcomment -%} + {%- assign desc = item.description -%} + {%- unless desc -%} + {%- assign desc = item.content | strip_html | normalize_whitespace | truncate: 180 -%} + {%- endunless -%} + + {%- assign section = item.section | default: 'Docs' -%} + {%- assign audience = item.audience | default: '' -%} + + {%- if first == false -%},{%- endif -%} + {%- assign first = false -%} + { + "title": {{ item.title | jsonify }}, + "url": {{ item.url | relative_url | jsonify }}, + "section": {{ section | jsonify }}, + "audience": {{ audience | jsonify }}, + "description": {{ desc | jsonify }}, + "body": {{ body | jsonify }} + } + {%- endunless -%} +{%- endfor -%} +] diff --git a/docs/search.json b/docs/search.json deleted file mode 100644 index d2cc1da07..000000000 --- a/docs/search.json +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: null -permalink: /search.json ---- -[ - {% assign searchable_pages = site.pages | where_exp: "item", "item.title" | sort: "title" %} - {% for item in searchable_pages %} - {% capture item_keywords %}{% if item.keywords %}{% for keyword in item.keywords %}{{ keyword }} {% endfor %}{% endif %}{% endcapture %} - { - "title": {{ item.title | jsonify }}, - "description": {{ item.description | default: '' | strip_html | strip_newlines | truncate: 180 | jsonify }}, - "keywords": {{ item_keywords | normalize_whitespace | jsonify }}, - "section": {{ item.section | default: item.collection | default: 'Docs' | jsonify }}, - "url": {{ item.url | relative_url | jsonify }} - }{% unless forloop.last %},{% endunless %} - {% endfor %} -] \ No newline at end of file diff --git a/docs/search.md b/docs/search.md new file mode 100644 index 000000000..4e27d5db3 --- /dev/null +++ b/docs/search.md @@ -0,0 +1,34 @@ +--- +layout: page +title: "Search" +description: "Search the Simple Chat documentation." +section: "Search" +permalink: /search/ +hide_toc: true +noindex: true +--- + +
+ + +
+ +

+ +
+ + +
diff --git a/docs/setup_instructions.md b/docs/setup_instructions.md deleted file mode 100644 index c115c5911..000000000 --- a/docs/setup_instructions.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -layout: showcase-page -title: "Getting Started" -description: "Choose the right deployment path, line up the prerequisites, and follow the repo's recommended rollout order for Simple Chat." -section: "Tutorials" -accent: emerald -eyebrow: "Deployment Guide" -hero_icons: - - bi-rocket-takeoff - - bi-cloud-arrow-up - - bi-sliders2 -hero_pills: - - "Recommended path: azd up" - - Container-first deployers - - Native Python fallback available -hero_links: - - label: "Open AZD deployment guide" - url: /reference/deploy/azd-cli_deploy/ - style: primary - - label: "Review upgrade paths" - url: /how-to/upgrade_paths/ - style: outline -nav_links: - next: - title: "Manual Setup" - url: /setup_instructions_manual/ ---- - -If you want the most current, least ambiguous deployment path, start with Azure Developer CLI and run `azd up`. The rest of the deployment options exist to match different operating models, not because they are all equally preferred. - -
-
-
-
Choose your path
-

Deployment options in recommended order

-

All of these paths are supported, but they differ in how much automation, flexibility, and operational context they give you.

-
- Most used first -
- -
-
-
-
- - Recommended -
-

Azure Developer CLI

-

Use this when you want the smoothest repo-supported experience. It provisions infrastructure, packages the app, and deploys it through the same workflow.

-
-
Why it matters
-

This is the path reflected across the main README and the current deployment documentation.

-
- -
-
- -
-
-
- - Scripted -
-

Azure CLI with PowerShell

-

Use this when you want more direct control over sequencing, recovery steps, and script-driven operations without moving to a fully manual deployment.

- -
-
- -
-
-
- - IaC -
-

Bicep

-

Use Bicep when you want to inspect or customize the infrastructure modules directly. It is the same IaC layer that the AZD path builds on.

- -
-
- -
-
-
- - Alternative -
-

Terraform

-

Use Terraform when it is already the standard in your environment and you are comfortable handling image publishing as part of the rollout.

- -
-
-
-
- -
-
-
-
Recommended flow
-

What a clean first deployment looks like

-

These four steps keep you aligned with the repo's expectations and reduce the chance of backtracking later.

-
- Step by step -
- -
-
-
-
- - Step 1 -
-

Prepare access

-

Confirm Azure subscription permissions, app registration creation rights, and access to container build resources before you begin.

-
-
- -
-
-
- - Step 2 -
-

Choose the deployer

-

Default to AZD unless your environment already depends on a different provisioning workflow or you need native Python deployment details.

-
-
- -
-
-
- - Step 3 -
-

Deploy infrastructure and app

-

Run the chosen workflow end to end so the app service, identity, storage, search, and runtime expectations stay in sync.

-
-
- -
-
-
- - Step 4 -
-

Plan the upgrade path

-

Once the first environment is live, switch to the dedicated upgrade guidance for updates instead of replaying the initial setup flow.

-
-
-
-
- -
-
-
-
Prerequisites
-

What to line up before you run anything

-

Most failed first deployments come from missing access, not from the deployer itself.

-
- Read once -
- -
-
-
-
- - Access -
-

Azure and identity permissions

-

You need subscription-level deployment rights plus the ability to create or coordinate an Entra application registration for the app.

-
-
- -
-
-
- - Tooling -
-

Install the local toolchain

-

At minimum, line up Azure CLI, Azure Developer CLI, Python 3.12, PowerShell 7, and Visual Studio Code before starting the primary flow.

-
-
- -
-
-
- - Platform model -
-

Know whether you need exceptions

-

If you need sovereign cloud support, private networking, managed identity-specific configuration, or native Python hosting, review the related docs before deploying.

-
-
-
-
- -
-

Python is part of the AZD toolchain

-

The repo's AZD workflow runs Python during the preprovision and postprovision hooks defined in deployers/azure.yaml. Install Python 3.12 and confirm python works on Windows or python3 works on Linux/macOS before running azd up.

-
- -
-
-
-
Next steps
-

Use the follow-on guides when the default path is not enough

-

These documents sit alongside the main setup flow instead of replacing it.

-
- Related guides -
- -
-
-
-
- - Detailed setup -
-

Manual deployment

-

Use this for native Python App Service deployments or when you need the lower-level configuration path spelled out.

-
-
- -
-
-
- - Scenarios -
-

Special deployment scenarios

-

Review guidance for Azure Government, managed identities, enterprise networking, and other non-default rollout patterns.

-
-
- -
-
-
- - Operations -
-

Upgrade existing deployments

-

Once you are live, use the upgrade guide to decide between code-only, image-only, and infrastructure-aware updates.

-
-
-
-
\ No newline at end of file diff --git a/docs/setup_instructions_manual.md b/docs/setup_instructions_manual.md index f9c2d7f03..91fccae7c 100644 --- a/docs/setup_instructions_manual.md +++ b/docs/setup_instructions_manual.md @@ -1,674 +1,45 @@ --- -layout: showcase-page -title: "Manual Setup" -permalink: /setup_instructions_manual/ -menubar: docs_menu -accent: slate -eyebrow: "Deployment Reference" -description: "Use the manual path when you need direct control over Azure resources, identity wiring, environment variables, and deployment mechanics." -hero_icons: - - bi-terminal - - bi-cloud-check - - bi-tools -hero_pills: - - Direct Azure provisioning - - Explicit identity setup - - Best for controlled enterprise rollouts -hero_links: - - label: Back to getting started - url: /setup_instructions/ - style: primary - - label: Special setup scenarios - url: /setup_instructions_special/ - style: secondary -show_nav: true -nav_links: - prev: - title: Getting Started - url: /setup_instructions/ - next: - title: Special Setup Scenarios - url: /setup_instructions_special/ ---- -This guide is for teams that need to provision every dependency themselves, integrate with existing standards, or understand the full deployment surface before they automate it. - -
-
-
-

Provision Azure resources

-

Stand up App Service, Azure OpenAI, Search, Cosmos DB, Document Intelligence, and the optional services needed for your chosen feature set.

-
-
-
-

Configure identity and auth

-

Set up the Entra app registration, App Service authentication, Graph permissions, app roles, and any managed identity access the runtime requires.

-
-
-
-

Deploy application code

-

Clone the repo, populate settings, initialize search indexes, and deploy through VS Code or Azure CLI depending on your workflow.

-
-
-
-

Run and upgrade safely

-

Use deployment slots or another controlled promotion flow when you move from dev/test installs into production operations.

-
-
- -
-

How to use this page

-

Read it top to bottom the first time. After that, treat it as a reference for the specific step you are automating or verifying.

-
- -## Provision Azure Resources - -Deploy the necessary Azure services. For a quick estimate of monthly costs based on recommended baseline SKUs for a Demo/Proof-of-Concept (POC)/Minimum Viable Product (MVP) solution, refer to the [Azure Pricing Calculator Link](https://azure.com/e/86504dd2857343ae80bda654ae4cc2f4). The services and SKUs below are reflected in that estimate. - -> [!IMPORTANT] -> The following recommended SKUs are intended for **Development, Demo, POC, or MVP purposes only**. You **must** scale these services appropriately based on expected user load, data volume, and performance requirements when moving to a Production environment. Factors like concurrent users, document ingestion rate, and query complexity will influence the required tiers and instance counts. - -| Service Type | Recommended Minimum SKU (for Dev/Demo/POC/MVP) | Description / Notes | -| :--------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| **App Service (Frontend)** | Premium V3 P0v3 (1 Core, 4 GB RAM, 250 GB Storage), Linux | Hosts the Python Flask web application. Consider scaling up (P1v3+) or out (multiple instances) for production. | -| **Azure OpenAI (GPT)** | Standard S0, `gpt-4o` deployment | Powers core chat functionality and optional Metadata Extraction. Choose model based on cost/performance needs. Pay-as-you-go pricing. | -| **Azure OpenAI (Embedding)** | Standard S0, `text-embedding-3-small` deployment | Required for RAG (Your Workspace, My Groups). Generates vector embeddings. Pay-as-you-go. | -| **Azure OpenAI (Image Gen)** | Standard S0, `dall-e-3` deployment (Optional) | Required only if Image Generation feature is enabled. Pay-as-you-go per image. | -| **Azure AI Search** | Standard S1 (consider S2/S3 for larger scale/HA) | Stores and indexes document chunks for RAG. Includes Semantic Ranker capacity. Scale units/replicas/partitions for performance/HA. | -| **Content Safety** | Standard S0 (Optional) | Required only if Content Safety feature is enabled. Pay-as-you-go per 1k text records / 1k images. | -| **Document Intelligence** | Standard S0 | Used for text/layout extraction from various file types during ingestion. Pay-as-you-go per page processed. | -| **Cosmos DB (NoSQL)** | Autoscale provisioned throughput (Start ~1000 RU/s), Single-Region Write | Stores metadata, conversations, settings. Autoscale helps manage costs, but monitor RU consumption and adjust max RU for production loads. | -| **Video Indexer** | Standard Tier (Optional) | Required only if Video Extraction feature is enabled. Pay-as-you-go per input content minute (All Insights). | -| **Speech Service** | Standard S0 (Optional) | Required only if Audio Extraction feature is enabled. Pay-as-you-go per audio hour (Standard fast transcription). | -| **Storage Account** | General Purpose V2, LRS, Hot Tier (Optional) | Required if Enhanced Citations feature is enabled. Stores processed files. Hierarchical Namespace (ADLS Gen2) recommended. - OR - Required if you want to use Azure Storage for temporaty file storage which is recommend for scalability and better performance | -| **Azure Cache for Redis** | Standard Tier, C0 cache size (Optional) | Required only if you need the performance, scalability, and distributed session support provided by Redis Cache. | - -> **Note**: Pricing is subject to change and varies significantly based on usage, region, specific configurations (e.g., network security, backup policies), and selected tiers. Always use the official Azure Pricing Calculator and monitor your Azure costs closely. - -**Deployment Steps:** - -1. **Create or Select a Resource Group**: - * Group all related resources within a single Azure Resource Group (e.g., `rg-simple-chat-prod`, `rg-simple-chat-dev`). - * Deploy resources in the same Azure region where possible to minimize latency, unless specific service availability or compliance dictates otherwise (e.g., Azure OpenAI model availability). -2. **Deploy App Service**: - * Create an Azure App Service instance. - * **Publish**: Code - * **Runtime stack**: Python 3.12 - * **Operating System**: Linux - * **Region**: Choose your desired region. - * **App Service Plan**: Create a new Linux plan using the **Premium V3 (P0v3)** tier (or higher for production). Zone redundancy typically **Disabled** for baseline, enable for HA if needed. - * Review Networking (Public access defaults), Deployment, Monitoring settings. Modify based on organizational security/operational requirements. - * Note the default **App Name** and **URL** (e.g., `https://my-simplechat-app.azurewebsites.net`). This URL will be needed for AAD App Registration redirects. -3. **Deploy Azure OpenAI Service(s)**: - * You can deploy a single Azure OpenAI resource hosting all models or separate resources (e.g., one for GPT, one for Embeddings) based on regional availability or management preference. - * Create an **Azure OpenAI** resource. Select **Standard S0** pricing tier. - * **Deploy Models**: Within the Azure OpenAI Studio for your resource(s), deploy the required models with custom deployment names: - * **GPT Model**: e.g., `gpt-4o` (Required for chat, optional for metadata). Note the **Deployment Name**. - * **Embedding Model**: e.g., `text-embedding-3-small` (Required for Workspaces/RAG). Note the **Deployment Name**. - * **Image Generation Model**: e.g., `dall-e-3` (Required for optional Image Generation). Note the **Deployment Name**. - * Review Networking settings (default public access, modify as needed). - * If using **Managed Identity** authentication later, you will need to grant the App Service's Managed Identity the `Cognitive Services OpenAI User` role on this resource(s). -4. **Deploy Azure AI Search**: - * Create an **Azure AI Search** service. - * Select the **Standard S1** tier (or higher based on scale/HA needs). Consider replicas/partitions for production. - * Review Networking settings. - * You will initialize indexes later ([Initializing Indexes](#initializing-indexes-in-azure-ai-search)). - * If using **Managed Identity**, grant the App Service's Managed Identity the `Search Index Data Contributor` role on this resource. -5. **Deploy Azure Cosmos DB**: - * Create an **Azure Cosmos DB** account. - * Select the **Azure Cosmos DB for NoSQL** API. - * **Capacity mode**: Provisioned throughput. Choose **Autoscale**. - * Set **Max throughput** at the database level initially (e.g., start with 1000 RU/s, monitor and adjust). - - Note: Autoscale automatically adjusts the provisioned Request Units (RU/s) between 10% and 100% of this maximum value based on usage (e.g., 1000 max RU/s scales between 100 - 1000 RU/s). - - **Container-Level Scaling (Recommended Post-Setup)**: While you set an initial database-level throughput, it's highly recommended to configure Autoscale throughput **per container** after the application creates them (or manually create them with these settings). For optimal performance and cost-efficiency, consider setting the *maximum* Autoscale throughput for key containers as follows: - - messages container: **4000 RU/s** (will scale between 400 - 4000 RU/s) - - documents container: **4000 RU/s** (will scale between 400 - 4000 RU/s) - - group_documents container: **4000 RU/s** (will scale between 400 - 4000 RU/s) - - Other containers (like settings, feedback, archived_conversations) often have lower usage and can typically start with a lower maximum (e.g., 1000 RU/s, scaling 100-1000 RU/s), but monitor their consumption. - * **Apply Free Tier Discount**: **DO NOT APPLY** (Free tier throughput is insufficient). - * **Limit total account throughput**: **Uncheck** (DISABLE). - * Review Networking, Backup Policy, Encryption settings. - * If using **Managed Identity**, grant the App Service's Managed Identity the `Cosmos DB Built-in Data Contributor` role (or create custom roles for least privilege). *Note: Managed Identity support for Cosmos DB data plane might require specific configurations.* Key-based auth is simpler initially. -6. **Deploy Azure AI Document Intelligence**: - * Create an **Azure AI Document Intelligence** (formerly Form Recognizer) resource. - * Select the **Standard S0** pricing tier. - * Review Networking settings. - * If using **Managed Identity**, grant the App Service's Managed Identity the `Cognitive Services User` role on this resource. -7. **Deploy Azure AI Content Safety (Optional)**: - * If using the Content Safety feature, create an **Azure AI Content Safety** resource. - * Select the **Standard S0** pricing tier. - * Review Networking settings. - * If using **Managed Identity**, grant the App Service's Managed Identity the `Cognitive Services Contributor` role on this resource. -8. **Deploy Azure Video Indexer (Optional)**: - * If using the Video Extraction feature, create an **Azure Video Indexer** resource in the Azure Portal. - * You'll need to associate it with an Azure Media Services account (can be created during VI setup) and a Storage Account (used for temporary processing, can be new or existing). - * **Enable System-assigned Managed Identity** on your App Service if not already enabled (Identity > System assigned > Status: On). - * **Grant the App Service's Managed Identity the `Contributor` role** on the Video Indexer resource: - - Navigate to your Video Indexer resource > Access control (IAM) - - Add role assignment > Select `Contributor` role - - Assign access to "Managed Identity" - - Select your App Service's managed identity - * Note the **Account ID**, **Account Name**, **Resource Group**, **Subscription ID**, and **Location** (e.g., eastus). These will be configured in Admin Settings. - * See [Azure Video Indexer documentation](https://learn.microsoft.com/azure/azure-video-indexer/connect-to-azure) for detailed setup instructions. -9. **Deploy Azure Speech Service (Optional)**: - * If using the Audio Extraction feature, create an **Azure AI Speech** resource. - * Select the **Standard S0** pricing tier. - * Review Networking and Identity settings. - * Note the **Endpoint**, **Region/Location**, and one of the **Keys**. These will be configured in Admin Settings. -11. **Deploy Storage Account (Optional)**: - * If using the Enhanced Citations feature, create an **Azure Storage Account**. - * **Performance**: Standard. - * **Redundancy**: LRS (or higher based on requirements). - * **Account Kind**: StorageV2 (general purpose v2). - * **Enable hierarchical namespace** (Azure Data Lake Storage Gen2) is recommended for better organization if storing large volumes. - * Review Networking, Data protection, Encryption settings. - * Note the **Connection String** (under Access Keys or SAS token). This will be configured in Admin Settings. If using Managed Identity, grant the App Service's Managed Identity the `Storage Blob Data Contributor` role. - * After deployment, note the **Connection String** (under Access Keys or SAS token). This will be configured in Admin Settings. If using Managed Identity, grant the App Service's Managed Identity the `Storage Blob Data Contributor` role. - * Navigate to **Data Storage** > **Containers** > **+ Container**. Add two new containers - `user-documents` and `group-documents -10. **Deploy Azure Cache for Redis (Optional)**: - * Create an **Azure Cache for Redis** service. - * **Name**: Choose a unique name for your Redis instance (e.g., `simplechat-redis`). - * **Region**: Select the same region as your App Service for lowest latency. - * **Cache SKU**: Standard. - * **Cache Size**: C0 (or higher based on requirements). - * **Networking**: Set to **Public** for initial setup (can be made private later for enhanced security). - * **Advanced**: - - Enable **Access Keys Authentication** (required for key-based access). - - All other advanced settings can remain at their defaults unless you have specific requirements. - * After Redis is created, note the **Host Name** and **Access Keys** (if using key authentication). - * If using managed identities, enable Entra Authentication and select the App Service managed identity. - * The Redis service can take 15-30 minutes to fully deploy. -13. **Use Azure Storage for temporary data data (Optional)**: - * Create an **Azure Storage Account** if you previously created Enhanced Citations you can use it. Otherwise look at step 11 for recommendations on settings. - * **Enable Storage Account Key Access** - - Goto the storage account - - Click on Configuration (in the Settings section) - - Click on Enable Key Access and click Save - * **Create a FileShare**: - - Cick on File Shares in the Data Storage section - - Click Add File Share - - Give it a name (write it down for use later) - - Click Next: Backup - - Turn off enable backup, unless you are using this share for other files - - Click Review and Create, then Create - * **Create the Share in your App Service**: - - Return to your App Service - - Click on Configuration (the in Settings Section) - - Click on Path Mappings - - Click on Add New Azure Storage Mount - - Give it a name - - Use Basic for Configuration Options - - Select your storage account - - Select Azure Files for Storage Type - - Select SMB for Protocal - - Select the FileShare your created for the Storage Container - - Set the mount path **/sc-temp-files** - Important to use this name - - Click OK and then click Save - -## Application-Specific Configuration Steps - -> Return to top - -With the Azure resources provisioned, proceed with configuring the application itself. Perform these steps in order. - -### Setting Up Authentication (Azure AD / Entra ID) - -The application uses Azure Active Directory (Entra ID) for user authentication and role management. - -1. **Register an Application in Azure AD**: - * Navigate to **Azure Active Directory** > **App registrations** > **+ New registration**. - * Give it a name (e.g., `SimpleChatApp-Prod`). - * Select **Accounts in this organizational directory only** (or adjust if multi-tenant access is needed). - * Set the **Redirect URI**: - * Select **Web** platform. - * Enter the URI: `https://.azurewebsites.net/.auth/login/aad/callback` (Replace `` with your actual App Service name). - * Click **Register**. - * Note the **Application (client) ID** and **Directory (tenant) ID**. These are needed for the `.env` file (`CLIENT_ID`, `TENANT_ID`). - * Next, click the **Authentication** link in the Manage section - * In the Web **Redirect URIs** section of the page click **Add URI** - * Enter the URI: `https://.azurewebsites.net/getAToken` (Replace `` with your actual App Service name). - * Next in the **Front-channel logout URL** section of the page - * Enter the URI: `https://.azurewebsites.net/logout` (Replace `` with your actual App Service name). - * Now look at the **Implicit grant and hybrid flows** section - * Make sure the checked for ***ID tokens (used for implicit and hybrid flows)** is checked - * Click the **Save** button - ![App Registration Settings](./images/app_reg_settings.png) *(Note: Image shows general area, details might differ slightly)* - * Click on the **Certificates and secrets link** in the manage section - * Click on **Client Secrets** - * Verify that there is a Secret Named **MICROSOFT_PROVIDER_AUTHENTICATION_SECRET** - * If there is not, click on **New Client Secret** to create a new secret using **MICROSOFT_PROVIDER_AUTHENTICATION_SECRET** as the name. - * Make sure you copy the **Value** before you leave this page. - ![App Registration Settings](./images/app_reg_secrets.png) *(Note: Image shows general area, details might differ slightly)* - -2. **Configure App Service Authentication**: - * Go to your **App Service** in the Azure portal. - * Navigate to **Settings** > **Authentication**. - * Click **Add identity provider**. - * **Identity provider**: Microsoft - * **App registration type**: Pick an existing app registration in this directory. - * Select the **App registration** you just created. - * **Restrict access**: Require authentication. - * **Unauthenticated requests**: HTTP 302 Found redirect: recommended for web apps. - * Click **Add**. This configures the built-in App Service Authentication (Easy Auth). - * ⚠️ **Important** ⚠️: After adding the provider, go back into the **Authentication** settings for the App Service, click **Edit** on the Microsoft provider. - * Ensure the **Issuer URL** is correct (usually `https://login.microsoftonline.com//v2.0` or `https://sts.windows.net//v2.0`). - * Note the **Client Secret Setting Name** value shown here. This secret (`MICROSOFT_PROVIDER_AUTHENTICATION_SECRET`) is often automatically added to App Service Application Settings. If the name is not there (or a different name is there) click on **Click to edit secret value** - * Click on **Add**, for the name use `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET` for the value enter the Key that you copied in the previous step - * Click **Apply**, the click **Apply** again - * Return to the **Edit identity provider** page and now select `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET` for the Client Secret setting name. (It may take a minute for that name to appear) - - ![App Registration - Authentication Configuration in App Service](./images/app_reg_edit_identity.png) *(Note: Image shows general area, details might differ slightly)* - -3. **Configure API Permissions**: - * Go back to your **App Registration** in Azure AD. - * Navigate to **API permissions**. - * Click **+ Add a permission**. - * Select **Microsoft Graph**. - * Select **Delegated permissions**. - * Add the following permissions: - * `email` - * `offline_access` - * `openid` - * `profile` - * `User.Read` (Allows sign-in and reading the user's profile) - * `User.ReadBasic.All` (Allows reading basic profiles of all users - often needed for people pickers if not using `People.Read.All`) - * **(Conditional)** `People.Read.All`: **Required if** you enable the **My Groups** feature, as it's used to search for users within your tenant to add to groups. Add this permission if needed. - * **(Conditional)** `Group.Read.All`: **Required if** you enable the **My Groups** feature or need to read group memberships and group details for group workspaces. This permission allows the app to list groups and read group properties and memberships in your organization. Add this permission if group-based collaboration or group document access is needed. - * After adding permissions, click **Grant admin consent for [Your Tenant Name]**. This is crucial, especially for `*.All` permissions. - - ![App Registration - API Permissions](./images/app_reg-api_permissions.png) - -4. **Configure App Roles**: - * In your **App Registration**, navigate to **App roles**. - * Click **+ Create app role**. - * Create roles based on the following table. Repeat for each role: - - | Display Name | Allowed member types | Value | Description | Do you want to enable this app role? | - | :------------------------- | :------------------- | :--------------------- | :----------------------------------------------- | :----------------------------------- | - | **Admins** | Users/Groups | `Admin` | Allows access to Admin Settings page. | Yes | - | **Users** | Users/Groups | `User` | Standard user access to chat features. | Yes | - | **Create Group** | Users/Groups | `CreateGroups` | Allows user to create new groups (if enabled). | Yes | - | **Chat File Upload User** | Users/Groups | `ChatFileUploadUser` | Allows chat file uploads when role enforcement is enabled. | Yes | - | **Workflow User** | Users/Groups | `WorkflowUser` | Allows personal workflow access when role enforcement is enabled. | Yes | - | **Safety Violation Admin** | Users/Groups | `SafetyViolationAdmin` | Allows access to view content safety violations. | Yes | - | **Feedback Admin** | Users/Groups | `FeedbackAdmin` | Allows access to view user feedback admin page. | Yes | - - ![App Registration - App Roles](./images/app_reg-app_roles.png) - -5. **Assign Users/Groups to Roles via Enterprise Application**: - * App Roles are *assigned* through the **Enterprise Application** associated with your App Registration. - * Navigate to **Azure Active Directory** > **Enterprise applications**. - * Find the application with the same name as your App Registration (or search by Application ID). - * Select your Enterprise Application. - * Go to **Users and groups**. - * Click **+ Add user/group**. - * Select the users or security groups you want to grant access. - * Under **Select a role**, choose the appropriate App Role (`Admins`, `Users`, etc.) you defined. - * Click **Assign**. Only assigned users/groups will be able to log in (if "Assignment required?" is enabled on the Enterprise App, which is recommended). - -### Grant App Registration Access to Azure OpenAI (for Model Fetching) - -The application needs permission to list the available models deployed in your Azure OpenAI resource(s). This uses the *App Registration's Service Principal*. - -1. Go to each **Azure OpenAI** service resource in the Azure portal. -2. Select **Access control (IAM)**. -3. Click **+ Add** > **Add role assignment**. -4. Search for and select the role **Cognitive Services OpenAI User**. Click Next. -5. **Assign access to**: Select **User, group, or service principal**. -6. **Members**: Click **+ Select members**. -7. Search for the **name of your App Registration** (e.g., `SimpleChatApp-Prod`). Select it. -8. Click **Select**, then **Next**. -9. Click **Review + assign**. -10. Repeat for *all* Azure OpenAI resources used by the application (GPT, Embedding, Image Gen if separate). - -![Add role assignment - Job function role selected](./images/add_role_assignment-job_function.png) -![Add role assignment - Selecting the Service Principal (App Registration)](./images/add_role_assignment-select_member-service_principal.png) - -### Clone the Repository - -Get the application code onto your local machine. - -1. Open a terminal or command prompt. -2. Use Git to clone the repository: - ```bash - git clone - cd - ``` - (Replace `` and `` accordingly). - *Alternatively*, use GitHub Desktop or download the ZIP and extract it. - -![Clone the repo options in GitHub UI](./images/clone_the_repo.png) - -### Configure Environment Variables (`.env` File) - -Core configuration values are managed via environment variables, typically set in the Azure App Service Application Settings. A `.env` file is used locally and can be uploaded to populate these settings. - -1. **Create `.env` from Example**: - * Find the `example.env` file in the cloned repository. - * Rename or copy it to `.env`. -2. **Edit `.env`**: - * Open the `.env` file in a text editor (like VS Code). - * Fill in the placeholder values with your actual service details: - - ```dotenv - # Azure Cosmos DB - # Use connection string OR endpoint/key OR managed identity - # e.g., https://mycosmosdb.documents.azure.com:443/ - AZURE_COSMOS_ENDPOINT="" - AZURE_COSMOS_KEY="" - # Options: "key", "connection_string", "managed_identity" - AZURE_COSMOS_AUTHENTICATION_TYPE="key" - - # Azure AD Authentication (Required) - CLIENT_ID="" - TENANT_ID="" - # SECRET_KEY should be a long, random, secret string (e.g., 32+ chars) used for Flask session signing. Generate one securely. - SECRET_KEY="Generate-A-Strong-Random-Secret-Key-Here!" - # AZURE_ENVIRONMENT: Set based on your cloud environment - # Options: "public", "usgovernment", "custom" - AZURE_ENVIRONMENT="public" - ``` - -3. **Upload Settings to Azure App Service (Recommended using VS Code)**: - - * Ensure the `.env` file is saved and **closed**. - * In VS Code, with the Azure App Service extension installed and signed in: - * **Option 1 (Command Palette)**: Press `Ctrl+Shift+P` (or `Cmd+Shift+P`), type `Azure App Service: Upload Local Settings`, select your subscription and App Service instance, then choose the `.env` file. - * **Option 2 (File Explorer)**: Right-click the `.env` file in the VS Code explorer, select `Azure App Service: Upload Local Settings`, and follow the prompts. - * This action reads your `.env` file and sets the corresponding **Application Settings** in the Azure App Service configuration blade. - - ![Upload local settings - Option 1 (Command Palette)](./images/upload_local_settings_1.png) - ![Upload local settings - Option 2 (Right-click)](./images/upload_local_settings_2.png) - -4. **(Optional) Download Settings from Azure App Service**: - * To verify or synchronize settings from Azure back to a local `.env` file: - * Press `Ctrl+Shift+P`, type `Azure App Service: Download Remote Settings`, select your App Service, and choose where to save the file (e.g., overwrite your local `.env`). This is useful to capture settings automatically added by Azure (like `APPLICATIONINSIGHTS_CONNECTION_STRING` or `WEBSITE_AUTH_AAD_ALLOWED_TENANTS`). - - ![Download remote settings command](./images/download_remote_settings.png) - -5. **First-Time Configuration Wizard**: - * When you first access the admin settings page, a configuration wizard will guide you through the required and optional settings. - * The wizard will help you configure: - * **Application basics**: Title and logo customization - * **GPT API settings**: Configure Azure OpenAI endpoints and models - * **Workspace settings**: Enable personal and group workspaces - * **Additional services**: Configure embedding, AI Search, Document Intelligence, and other required services - * **Optional features**: Content safety, user feedback, conversation archiving, and other optional features - * Required settings are clearly marked, ensuring that you configure all necessary components for your deployment scenario. - -### Local VS Code Developer Environment - -If you want to run Simple Chat locally in VS Code before deploying to Azure App Service, use a repo-local `.venv` created with Python 3.12. - -From the repo root on Windows: - -```powershell -py -3.12 -m venv .venv -.\.venv\Scripts\Activate.ps1 -pip install --upgrade pip -pip install -r application/single_app/requirements.txt -``` - -Then in VS Code run `Python: Select Interpreter` and choose the Python 3.12 interpreter inside `.venv`. - -For the full local development workflow, including `FLASK_DEBUG` guidance and when to use Docker or WSL2 for Gunicorn validation, see [Running Simple Chat Locally](./explanation/running_simplechat_locally.md). - -### Alternate Method: Update App Settings via JSON (Advanced) - -You can directly edit Application Settings in the Azure portal using the "Advanced edit" feature, pasting a JSON array. This is useful for bulk updates but requires care not to overwrite essential settings added by Azure. - -1. Navigate to your **App Service** > **Settings** > **Configuration** > **Application settings**. -2. **Backup Existing Values**: Before pasting, **copy** the current values for critical settings like `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET`, `APPLICATIONINSIGHTS_CONNECTION_STRING`, and `WEBSITE_AUTH_AAD_ALLOWED_TENANTS`. -3. **Prepare JSON**: Create a JSON array similar to the example below, inserting your specific values and the backed-up Azure-managed values. -4. Click **Advanced edit**. -5. **Carefully replace** the existing JSON content with your prepared JSON. -6. Click **OK**, then **Save**. -**Example JSON Structure:** +layout: page -```json -[ - // --- Azure Managed / Essential Settings --- - { "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", "value": "", "slotSetting": false }, - { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", "value": "", "slotSetting": false }, // Often same key as connection string contains - { "name": "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET", "value": "", "slotSetting": true }, // CRITICAL - Get from portal if unsure - { "name": "WEBSITE_AUTH_AAD_ALLOWED_TENANTS", "value": "", "slotSetting": false }, // Usually set by Auth config - { "name": "WEBSITE_AUTH_ENABLED", "value": "True", "slotSetting": true }, // Should be set by Auth config - { "name": "WEBSITE_AUTH_DEFAULT_PROVIDER", "value": "AzureActiveDirectory", "slotSetting": true }, // Should be set by Auth config - - // --- Your Application Settings (from .env) --- - { "name": "AZURE_COSMOS_ENDPOINT", "value": "", "slotSetting": false }, - { "name": "AZURE_COSMOS_KEY", "value": "", "slotSetting": false }, - { "name": "AZURE_COSMOS_DATABASE", "value": "SimpleChat", "slotSetting": false }, - { "name": "AZURE_COSMOS_AUTHENTICATION_TYPE", "value": "key", "slotSetting": false }, // or "managed_identity" - { "name": "CLIENT_ID", "value": "", "slotSetting": false }, - { "name": "TENANT_ID", "value": "", "slotSetting": false }, - { "name": "SECRET_KEY", "value": "", "slotSetting": false }, - { "name": "AZURE_ENVIRONMENT", "value": "public", "slotSetting": false }, // or "usgovernment", or "custom" - - // --- Build & Runtime Settings --- - { "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "true", "slotSetting": false }, // Ensures requirements.txt is processed - { "name": "WEBSITE_HTTPLOGGING_RETENTION_DAYS", "value": "7", "slotSetting": false }, - - // --- Optional App Insights Advanced Settings (Defaults usually fine) --- - { "name": "ApplicationInsightsAgent_EXTENSION_VERSION", "value": "~3", "slotSetting": false }, - { "name": "APPLICATIONINSIGHTSAGENT_EXTENSION_ENABLED", "value": "true", "slotSetting": false }, - { "name": "XDT_MicrosoftApplicationInsights_Mode", "value": "default", "slotSetting": false }, - { "name": "APPINSIGHTS_PROFILERFEATURE_VERSION", "value": "1.0.0", "slotSetting": false }, - { "name": "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", "value": "1.0.0", "slotSetting": false }, - { "name": "SnapshotDebugger_EXTENSION_VERSION", "value": "disabled", "slotSetting": false }, - { "name": "InstrumentationEngine_EXTENSION_VERSION", "value": "disabled", "slotSetting": false }, - { "name": "XDT_MicrosoftApplicationInsights_BaseExtensions", "value": "disabled", "slotSetting": false }, - { "name": "XDT_MicrosoftApplicationInsights_PreemptSdk", "value": "disabled", "slotSetting": false } -] -``` - -> [!WARNING] -> -> Editing Application Settings via JSON is powerful but risky. Incorrectly modifying or omitting settings managed by Azure (especially Authentication or App Insights integration) can break functionality. Proceed with caution and always back up existing values. Using the .env upload method is generally safer. - -![alt text](./images/advanced_edit_env.png) - -### Initializing Indexes in Azure AI Search - -The application requires two Azure AI Search indexes: one for personal user documents and one for shared group documents. The schemas are defined in JSON files within the repository. - -1. **Locate Index Schema Files**: - - - In your cloned repository, find the artifacts/ai_search_index/ directory. - - It contains ai_search-index-user.json and ai_search-index-group.json. - - ``` - 📁 SimpleChat - └── 📁 artifacts - └── 📁 ai_search_index - ├── ai_search-index-group.json - └── ai_search-index-user.json - ``` - -2. **Access Azure AI Search in Azure Portal**: - - - Navigate to your **Azure AI Search** service resource. - - Under **Search management**, select **Indexes**. - -3. **Create Indexes from JSON**: - - - Click **+ Add index**. - - Change the creation method from Enter index name to **Import from JSON**. - - **User Index**: - - Open ai_search-index-user.json locally, copy its entire content. - - Paste the JSON into the **Index definition (JSON)** editor in the portal. - - The Index Name should automatically populate as simplechat-user-index. - - Click **Save**. - - **Group Index**: - - Click **+ Add index** again and choose **Import from JSON**. - - Open ai_search-index-group.json locally, copy its content. - - Paste the JSON into the editor. - - The Index Name should populate as simplechat-group-index. - - Click **Save**. - -4. **Verify Indexes**: - - - You should now see simplechat-user-index and simplechat-group-index listed under **Indexes**. - -> [!NOTE] -> -> **Automatic Schema Update Feature**: If you happen to miss this step or deploy an updated version of the application with new required index fields, the application includes a mechanism to help. When an Admin user navigates to the **Admin > App Settings** page, the application backend checks the schemas of the existing simplechat-user-index and simplechat-group-index against the expected schema. If missing fields are detected, notification buttons will appear at the top of the Admin Settings page: "**Add missing user fields**" and "**Add missing group fields**". Clicking these buttons will automatically add the missing fields to your Azure AI Search indexes without data loss. While this feature provides resilience, it's still recommended to create the indexes correctly using the JSON definitions initially. - -![alt text](./images/ai_search-missing_index_fields.png) - -## Installing and Deploying the Application Code - -Deploy the application code from your local repository to the Azure App Service. - -### Deploying via VS Code (Recommended for Simplicity) - -1. **Ensure Azure Extensions are Installed**: You need the **Azure Tools Extension Pack** and the **Azure App Service** extension in VS Code. -2. **Sign In to Azure**: Use the Azure extension to sign in to your Azure account. -3. **Deploy**: - - In the VS Code Activity Bar, click the Azure icon. - - Expand **App Service**, find your subscription and the App Service instance you created. - - **Right-click** on the App Service name. - - Select **Deploy to Web App...**. - - Browse and select the `application/single_app` folder from the repository. - - VS Code will prompt to confirm the deployment, potentially warning about overwriting existing content. Click **Deploy**. - - Make sure your requirements.txt file is up-to-date before deploying. The deployment process (SCM_DO_BUILD_DURING_DEPLOYMENT=true) will use this file to install dependencies on the App Service. - - Monitor the deployment progress in the VS Code Output window. - -### Deploying via Azure CLI (Zip Deploy) - -This method involves creating a zip file of the application code and uploading it using the Azure CLI. Refer to the official documentation for detailed steps: [Quickstart: Deploy a Python web app to Azure App Service](https://www.google.com/url?sa=E&q=https://learn.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Czip-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli). - -**Key Steps:** - -1. **Create the ZIP file**: - - - Navigate into `application/single_app` in your terminal. - - Create a zip file containing **only** the necessary application files and folders. **Crucially, zip the contents, not the parent folder itself.** - - **Include**: - - static/ folder - - templates/ folder - - requirements.txt file - - All Python files (*.py) at the root level (e.g., app.py, utils.py, etc.). - - Any other necessary support files or directories at the root level. - - **Exclude**: - - .git/ folder and .gitignore - - .vscode/ folder - - __pycache__/ directories - - .env, example.env (environment variables are set in App Settings) - - .deployment, Dockerfile, .dockerignore (unless specifically using Docker deployment) - - README.md, LICENSE, .DS_Store, etc. - - Any local virtual environment folders (e.g., .venv, env). - - ![alt text](./images/files_to_zip.png) - - ![alt text](./images/zip_the_files.png) - - **Ensure SCM_DO_BUILD_DURING_DEPLOYMENT is Set**: Verify this application setting is true in your App Service configuration to ensure dependencies are installed from requirements.txt during deployment. - -2. **Deploy using Azure CLI**: - - ``` - az login # Sign in if you haven't already - az account set --subscription "" - - az webapp deploy --resource-group --name --src-path ../deployment.zip --type zip - ``` - -## Running the Application - -1. Navigate to your **App Service** in the Azure Portal. -2. On the **Overview** blade, find the **Default domain** URL (e.g., https://my-simplechat-app.azurewebsites.net). -3. Click the URL to open the application in your browser. -4. You should be redirected to the Microsoft login page to authenticate via Azure AD. Log in with a user account that has been assigned a role in the Enterprise Application. - -![alt text](./images/visit_app.png) - -## Upgrading the Application - -> Return to top - -This section covers **native Python Azure App Service** upgrades for the manual deployment path. - -Before upgrading a native Python deployment, confirm that the App Service Stack Settings Startup command is set correctly and is not blank. - -Deploy and run the `application/single_app` folder in App Service. - -Use this Startup command: - -```bash -python -m gunicorn -c gunicorn.conf.py app:app -``` - -For a shorter decision guide that also covers container-based upgrades, see [Upgrade Paths](./how-to/upgrade_paths.md). - -Keeping your Simple Chat application up-to-date involves deploying the newer version of the code. Using **Deployment Slots** is the recommended approach for production environments to ensure zero downtime and provide easy rollback capabilities. - -![alt text](./images/admin_settings-upgrade_available_notification.png) - -### Using Deployment Slots (Recommended for Production/Staging) +title: "Manual Setup" -1. **Create a Deployment Slot**: +description: "Manual deployment hub for teams that need direct control over Azure resources, identity wiring, environment variables, and deployment mechanics." - - In your App Service, go to **Deployment** > **Deployment slots**. - - Click **+ Add Slot**. Give it a name (e.g., staging). - - Choose to **clone settings** from the production slot initially. - - This creates a fully functional, independent instance of your app connected to the same App Service Plan. +section: "Deploy & Operate" -2. **Deploy New Version to Staging Slot**: +permalink: /setup_instructions_manual/ - - Deploy the updated application code (using VS Code deployment or az webapp deploy) specifically targeting the **staging slot**. +menubar: docs_menu - - **VS Code**: When deploying, VS Code will prompt you to select the target slot (production or staging). Choose staging. +--- - - **Azure CLI**: Add the --slot staging parameter to your az webapp deploy command: - ``` - az webapp deploy --resource-group --name --src-path --type zip --slot staging - ``` -3. **Test the Staging Slot**: +This manual setup hub keeps the original deployment reference URL and points to focused pages in the order used by the original guide. - - The staging slot has its own unique URL (e.g., https://my-simplechat-app-staging.azurewebsites.net). Access this URL directly. - - Thoroughly test all application functionality, including new features and critical paths, in the staging environment. This slot typically uses the same backend resources (Cosmos DB, AI Search, etc.) as production unless configured otherwise (e.g., using slot-specific Application Settings). -4. **Swap Staging to Production**: - - Once confident the new version in staging is stable, go back to **Deployment slots** in the Azure portal. +## Manual setup sections - - Click the **Swap** button. - - Configure the swap: - - **Source**: staging - - **Target**: production +1. [Provision Azure Resources]({{ '/deploy/manual/provision-azure-resources/' | relative_url }}) - - Azure performs a "warm-up" of the staging slot instance before redirecting production traffic to it. The previous production code is simultaneously moved to the staging slot. This swap happens near-instantly from a user perspective. +2. [Application-Specific Configuration Steps]({{ '/deploy/manual/application-specific-configuration/' | relative_url }}) - - **Azure CLI Swap Command**: +3. [Installing and Deploying the Application Code]({{ '/deploy/manual/installing-and-deploying-application-code/' | relative_url }}) - ``` - az webapp deployment slot swap --resource-group --name --slot staging --target-slot production - ``` +4. [Running the Application]({{ '/deploy/manual/running-the-application/' | relative_url }}) -5. **Monitor and Rollback (If Necessary)**: +5. [Upgrading the Application]({{ '/deploy/manual/upgrading-the-application/' | relative_url }}) - - Monitor the application closely after the swap using Application Insights and user feedback. - - If critical issues arise, you can perform another **Swap** operation, this time swapping production (which now contains the problematic code) back with staging (which now contains the previous stable code). This provides an immediate rollback. -### Using Direct Deployment to Production (Simpler, for Dev/Test or Low Impact Changes) -You can deploy directly to the production slot using the same VS Code or Azure CLI methods described in the initial deployment section, simply omitting the --slot parameter or choosing the production slot in VS Code. +## Related setup pages -> [!WARNING] -> -> Deploying directly to production overwrites the live code. This will cause a brief application restart and offers no immediate rollback capability (you would need to redeploy the previous version). This method is generally **not recommended** for production environments or significant updates due to the downtime and risk involved. -### Automate via CI/CD -For mature development practices, set up a Continuous Integration/Continuous Deployment (CI/CD) pipeline using tools like GitHub Actions or Azure DevOps Pipelines. A typical pipeline would: +- [Choose a deployment path]({{ '/start/deployment-options/' | relative_url }}) -1. Trigger on code commits/merges to specific branches (e.g., main, release/*). -2. Build the application artifact (e.g., create the zip file). -3. Deploy the artifact to the staging slot. -4. (Optional) Run automated tests against the staging slot. -5. Require manual approval (or automatically trigger based on test results) to perform the swap operation to production. +- [Special setup scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) diff --git a/docs/about.md b/docs/start/about.md similarity index 94% rename from docs/about.md rename to docs/start/about.md index d432ece8b..f9c9f0e95 100644 --- a/docs/about.md +++ b/docs/start/about.md @@ -1,7 +1,8 @@ --- layout: showcase-page title: "About Simple Chat" -permalink: /about/ +section: "Start" +permalink: /start/about/ menubar: docs_menu accent: teal eyebrow: "Platform Overview" @@ -16,11 +17,13 @@ hero_pills: - Optional agents and automation hero_links: - label: Start with setup - url: /setup_instructions/ + url: /start/deployment-options/ style: primary - label: Explore features url: /features/ style: secondary +redirect_from: + - /about/ --- Simple Chat is an enterprise-ready Flask application for teams that want conversational AI grounded in their own data, with admin controls that stay practical instead of sprawling. @@ -55,7 +58,7 @@ Simple Chat is an enterprise-ready Flask application for teams that want convers

Deploy

Use the guided deployment path if you want the fastest route into a working environment.

-

Open Getting Started

+

Open Getting Started

@@ -73,7 +76,7 @@ Simple Chat is an enterprise-ready Flask application for teams that want convers

Teach the team

The tutorial track is written for people who need to go from first login to useful document-grounded chat without reading every reference page first.

-

Open tutorials

+

Open tutorials

diff --git a/docs/start/deployment-options.md b/docs/start/deployment-options.md new file mode 100644 index 000000000..c60858834 --- /dev/null +++ b/docs/start/deployment-options.md @@ -0,0 +1,59 @@ +--- +layout: page +title: "Getting Started" +description: "Choose the right deployment path, line up the prerequisites, and follow the repo's recommended rollout order for Simple Chat." +section: "Start" +nav_links: + next: + title: "Manual Setup" + url: /setup_instructions_manual/ +permalink: /start/deployment-options/ +redirect_from: + - /setup_instructions/ +--- + +If you want the most current, least ambiguous deployment path, start with Azure Developer CLI and run `azd up`. The rest of the deployment options exist to match different operating models, not because they are all equally preferred. + +## Deployment options in recommended order + +All of these paths are supported, but they differ in how much automation, flexibility, and operational context they give you. Start with the most-used path first unless your environment already has a stronger reason to choose another deployer. + +| Recommended order | Deployment option | Use when | Why it matters | Guide | +| --- | --- | --- | --- | --- | +| 1 | Azure Developer CLI | You want the smoothest repo-supported experience. It provisions infrastructure, packages the app, and deploys it through the same workflow. | This is the path reflected across the main README and the current deployment documentation. | [Read AZD guide]({{ '/reference/deploy/azd-cli_deploy/' | relative_url }}) | +| 2 | Azure CLI with PowerShell | You want more direct control over sequencing, recovery steps, and script-driven operations without moving to a fully manual deployment. | It keeps deployment scripted while exposing more of the individual Azure operations. | [Read Azure CLI guide]({{ '/reference/deploy/azurecli_powershell_deploy/' | relative_url }}) | +| 3 | Bicep | You want to inspect or customize the infrastructure modules directly. | It is the same IaC layer that the AZD path builds on. | [Inspect Bicep flow]({{ '/reference/deploy/bicep_deploy/' | relative_url }}) | +| 4 | Terraform | Terraform is already the standard in your environment and you are comfortable handling image publishing as part of the rollout. | It matches Terraform-first operating models that already own their image publishing process. | [Review Terraform guide]({{ '/reference/deploy/terraform_deploy/' | relative_url }}) | + +## What a clean first deployment looks like + +These four steps keep you aligned with the repo's expectations and reduce the chance of backtracking later. + +1. **Prepare access**: confirm Azure subscription permissions, app registration creation rights, and access to container build resources before you begin. +2. **Choose the deployer**: default to AZD unless your environment already depends on a different provisioning workflow or you need native Python deployment details. +3. **Deploy infrastructure and app**: run the chosen workflow end to end so the app service, identity, storage, search, and runtime expectations stay in sync. +4. **Plan the upgrade path**: once the first environment is live, switch to the dedicated upgrade guidance for updates instead of replaying the initial setup flow. + +## What to line up before you run anything + +Most failed first deployments come from missing access, not from the deployer itself. + +| Prerequisite area | What you need | +| --- | --- | +| Azure and identity permissions | Subscription-level deployment rights plus the ability to create or coordinate an Entra application registration for the app. | +| Local toolchain | At minimum, line up Azure CLI, Azure Developer CLI, Python 3.12, PowerShell 7, and Visual Studio Code before starting the primary flow. | +| Platform model | Know whether you need sovereign cloud support, private networking, managed identity-specific configuration, or native Python hosting, and review the related docs before deploying. | + +## Python is part of the AZD toolchain + +The repo's AZD workflow runs Python during the `preprovision` and `postprovision` hooks defined in `deployers/azure.yaml`. Install Python 3.12 and confirm `python` works on Windows or `python3` works on Linux/macOS before running `azd up`. + +## Use the follow-on guides when the default path is not enough + +These documents sit alongside the main setup flow instead of replacing it. + +| Guide | Use it for | +| --- | --- | +| [Manual deployment]({{ '/setup_instructions_manual/' | relative_url }}) | Native Python App Service deployments or when you need the lower-level configuration path spelled out. | +| [Special deployment scenarios]({{ '/deploy/special-scenarios/' | relative_url }}) | Guidance for Azure Government, managed identities, enterprise networking, and other non-default rollout patterns. | +| [Upgrade existing deployments]({{ '/guides/upgrade-paths/' | relative_url }}) | Once you are live, use the upgrade guide to decide between code-only, image-only, and infrastructure-aware updates. | diff --git a/docs/start/faqs.md b/docs/start/faqs.md new file mode 100644 index 000000000..c3d96bf1c --- /dev/null +++ b/docs/start/faqs.md @@ -0,0 +1,165 @@ +--- +layout: page +title: "FAQ" +description: "Answers to the questions that come up most often when teams deploy, secure, and operate Simple Chat in Azure environments." +section: "Start" +permalink: /start/faqs/ +redirect_from: + - /faqs/ +--- + +Use this page as the fast triage layer: it focuses on the recurring questions that show up after a team has already deployed or started hardening the environment. + +## Start with the category that matches the failure mode + +Each category points to the section where the most likely causes and checks are grouped together. + +| Area | Start here when... | Section | +| --- | --- | --- | +| Network | Uploads, search, or admin actions break only after the app is placed behind a firewall, WAF, or private network boundary. | [Firewall and private endpoint issues](#networking-and-firewalls) | +| Identity | Users cannot sign in, role assignments do not behave as expected, or model enumeration fails because of tenant boundaries. | [Authentication and access](#authentication-and-access) | +| Admin UI | Users need to know where branding, home page text, health checks, Swagger, Support, and external links are configured. | [Branding, support, and docs](#admin-ui-and-support) | +| Ingestion and retrieval | Files fail during processing, indexes stay empty, or grounded answers are missing expected source material. | [Uploads and retrieval](#uploads-and-search) | +| Configuration | GPT, embeddings, DALL-E, or APIM-mode settings are the part of the system behaving unexpectedly. | [Model and endpoint configuration](#model-configuration) | + +## Firewall and private endpoint questions {#networking-and-firewalls} + +These connectivity issues usually surface after the app is moved behind network controls that only allow a subset of browser or outbound service traffic. + +### We put Simple Chat behind a firewall or WAF and uploads, search, or admin updates stopped working + +The browser still needs to reach the backend API routes that power the app. + +What to verify: + +- Simple Chat serves a frontend in the browser and a backend API from the app service, so blocking `/api/*` traffic breaks core features even when the page shell still loads. +- Allow browser-originated `GET`, `POST`, `PUT`, `PATCH`, and `DELETE` calls to your app service API routes. +- Use the repository OpenAPI specification under `artifacts/open_api/openapi.yaml` to understand which endpoints the frontend depends on. + +### Can I use Azure OpenAI and related services through private endpoints? + +Yes, but the app service must be able to resolve and reach those private addresses. + +What to verify: + +- Integrate the app service with a VNet that can reach the private endpoints for Azure OpenAI, AI Search, Cosmos DB, Storage, and any other required service. +- Make sure private DNS zones or custom DNS resolve those endpoints to the expected private IP addresses. +- Validate outbound connectivity from the app service, not just from an admin workstation. + +## Authentication and access questions {#authentication-and-access} + +When sign-in or model management fails, start by confirming the identity plane before you assume the app itself is broken. + +### Users are getting authentication errors or cannot log in + +Most login failures come from app registration or assignment drift. + +What to verify: + +- The redirect URI for `/.auth/login/aad/callback` is configured correctly in the Entra app registration. +- App Service Authentication is enabled, points at the correct app registration, and is set to require authentication. +- Users or groups are assigned to the enterprise application when assignment is required. +- Microsoft Graph permissions such as `User.Read`, `openid`, and `profile` are configured and have admin consent. +- `TENANT_ID` and `CLIENT_ID` values in the app settings match the intended tenant and application. + +### Fetch Models fails when the authentication app registration is in a different tenant than Azure OpenAI + +The data plane can still work even when cross-tenant management-plane model listing is blocked. + +Workaround: + +1. In Admin Settings under GPT, enable **Use APIM instead of direct to Azure OpenAI endpoint**. +2. Enter the Azure OpenAI endpoint URL, API version, and deployment name in the APIM fields instead of a true APIM proxy. +3. Save the settings and fetch models again. + +That route shifts model listing into the APIM-mode flow and avoids the cross-tenant management-plane enumeration failure. + +![Cross-tenant model support guidance screenshot.]({{ '/images/cross_tenant-model_support.png' | relative_url }}) + +## Branding, support, and documentation questions {#admin-ui-and-support} + +These answers point admins to the General settings that control the parts of Simple Chat users notice first. + +### Where do I change the application title, logo, favicon, or home page text? + +Branding and landing content are both in Admin Settings under General. + +What to do: + +- Open **Admin Settings > General > Branding** to change the application title, logo visibility, light and dark logos, home page logo size, and favicon. +- Open **Admin Settings > General > Home Page Text** to edit the landing page Markdown and alignment. +- Use [Configure Branding, Home Page, and Support Settings]({{ '/guides/configure-branding-and-support-settings/' | relative_url }}) for the full checklist. + +### How do I check whether the system is healthy? + +Enable one of the external health check routes before wiring a monitor to it. + +What to verify: + +- Use **Admin Settings > General > Health Check** to enable `/external/healthcheck` or `/external/healthcheckz`. +- Use `/external/healthcheck` when the monitoring path can reach a protected route. +- Use `/external/healthcheckz` only when the monitor cannot authenticate and the network path is trusted. +- Test from the monitoring network path, not only from an admin workstation. + +### Where are the API docs? + +Swagger is available at `/swagger` when the admin toggle is enabled. + +What to use: + +- Enable **Admin Settings > General > API Documentation > Enable Swagger/OpenAPI Documentation (/swagger)**. +- Open `/swagger` for the interactive browser. +- Use `/swagger.json` or `/swagger.yaml` for tooling. +- See the [API Reference]({{ '/reference/api_reference/' | relative_url }}) for endpoint details. + +### How do I show a classification banner, Support menu, or external resource links? + +These navigation and guidance features are configured from General settings. + +Where to go: + +- **Classification Banner**: set banner text, background color, and text color for the top-of-page sensitivity label. +- **Support**: enable Support, rename the menu, configure Send Feedback, and choose which Latest Features users can see. +- **External Links**: add links to policies, prompt guides, help desks, or other trusted resources. +- **System Settings**: tune maximum file size, conversation history limit, and the default system prompt. + +## Upload and search questions {#uploads-and-search} + +These problems usually come from permission gaps, failed indexing, or missing model configuration for embeddings and extraction. + +### File uploads are failing + +Start by checking the dependent services the pipeline writes to during ingestion. + +What to verify: + +- The app service has permission to reach AI Search, Document Intelligence, Storage, Speech, Video Indexer, and any other enabled service in the upload path. +- Managed identity role assignments or configured keys are valid and match the services you enabled. +- Application Insights and App Service logs show whether the failure happens during extraction, embedding, indexing, or storage. +- File Processing Logs in Admin Settings can expose the exact stage that failed. + +### RAG is not returning expected results or any results + +When search quality drops, verify indexing and embeddings before tuning prompts. + +What to verify: + +- Uploaded documents finished processing successfully in the workspace UI. +- The relevant Azure AI Search index contains the documents you expect and its count increases after uploads. +- The embedding deployment is configured correctly and reachable from the application. +- The Search Documents toggle is enabled in the chat UI and the user question is phrased in a way that maps to indexed content. + +## Model management questions {#model-configuration} + +These checks matter when the application loads but the configured AI capabilities do not behave the way you expect. + +### How do I update the GPT, embedding, or DALL-E models used by the application? + +Model selection is handled through admin configuration rather than code deployment. + +What to do: + +- Open **Admin Settings**. +- Go to the GPT, Embeddings, or Image Generation section that matches the model you want to change. +- Fetch the available deployments from the configured Azure OpenAI endpoint and select the desired deployment name. +- Save the settings. A code redeploy is not required when the endpoint stays the same. diff --git a/docs/troubleshooting/troubleshooting.md b/docs/troubleshooting/troubleshooting.md index 9256c77ba..eab2dc7eb 100644 --- a/docs/troubleshooting/troubleshooting.md +++ b/docs/troubleshooting/troubleshooting.md @@ -16,7 +16,7 @@ hero_pills: - Restart only when required hero_links: - label: Review workflows - url: /application_workflows/ + url: /reference/application-workflows/ style: primary - label: Check admin configuration url: /admin_configuration/ diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 9624d2e9b..360a0d9b1 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -1,52 +1,9 @@ --- -layout: showcase-page -title: "Tutorials" +layout: page +title: "Tutorials moved to Guides" +description: "Redirects the former Tutorials section to the unified Guides index." permalink: /tutorials/ -menubar: docs_menu -accent: amber -eyebrow: "Learn By Doing" -description: "Follow the tutorial track from first deployment through agents, document handling, and classification without needing to jump between the full reference sections." -hero_icons: - - bi-mortarboard - - bi-signpost-split - - bi-lightbulb -hero_pills: - - Guided product onboarding - - End-user and admin basics - - Short practical sequences -hero_links: - - label: Start the first tutorial - url: /tutorials/getting_started/ - style: primary - - label: Browse how-to guides - url: /how-to/ - style: secondary +redirect_to: /guides/ --- -This section is written for teams that want momentum first. Each page is a short path through one capability, with enough context to be useful and not so much detail that onboarding stalls. -
-
-
-

Getting Started

-

Deploy Simple Chat, configure the basics, upload a document, and run the first grounded conversation.

-

Open tutorial

-
-
-
-

Create Your First Agent

-

Define an agent role, tune its instructions, connect it to the right knowledge sources, and test the behavior.

-

Open tutorial

-
-
-
-

Upload and Use Documents

-

Learn the workspace types, supported content formats, processing states, and the best practices for keeping retrieval clean.

-

Open tutorial

-
-
-
-

Classify Documents

-

Set up document classification and use it to organize uploads, filter content, and improve team workflows.

-

Open tutorial

-
-
+This section has moved to [Guides]({{ '/guides/' | relative_url }}). diff --git a/functional_tests/test_docs_app_surface_coverage.py b/functional_tests/test_docs_app_surface_coverage.py new file mode 100644 index 000000000..5eeed2abc --- /dev/null +++ b/functional_tests/test_docs_app_surface_coverage.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +""" +Functional test for documentation coverage of the application surface. +Version: 0.260.020 +Implemented in: 0.250.230 + +This test ensures that every user-facing capability of SimpleChat has +documentation on the docs site. It is the mechanism that keeps documentation +current as pull requests land: adding a new settings toggle, admin settings +tab, action plugin, or chat control without documenting it fails this test. + +The application surface is captured in docs/_data/app_surface.yml, which is +generated by scripts/build_docs_inventory.py. This test also verifies that +inventory is still in sync with the application, so a stale inventory cannot be +used to hide missing documentation. +""" + +import io +import os +import re +import subprocess +import sys +from pathlib import Path + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +import yaml + +from test_support.versioning import assert_app_version_at_least + +REPO_ROOT = Path(__file__).resolve().parents[1] +DOCS_ROOT = REPO_ROOT / "docs" +APP_SURFACE = DOCS_ROOT / "_data" / "app_surface.yml" +INVENTORY_SCRIPT = REPO_ROOT / "scripts" / "build_docs_inventory.py" + +# Documentation folders that count as coverage. Engineering notes under +# docs/explanation/features and docs/explanation/fixes are deliberately excluded +# because they are internal change records, not user documentation, and they are +# excluded from the published site. +DOC_GLOBS = ( + "admin/*.md", + "guides/*.md", + "guides/**/*.md", + "reference/*.md", + "reference/**/*.md", + "how-to/*.md", + "how-to/**/*.md", + "tutorials/*.md", + "features/*.md", + "*.md", +) + +# Capability toggles that are internal implementation or telemetry switches +# rather than user-facing features. Each entry must state why it is exempt. +# Keep this list short and justified; it is the only escape hatch in this test. +CAPABILITY_EXEMPTIONS = { + "enable_analysis_deliverable_contract_telemetry": "Internal telemetry switch for deliverable contract diagnostics.", + "enable_tabular_parity_contract_telemetry": "Internal telemetry switch for tabular parity diagnostics.", + "enable_mixed_source_development_telemetry": "Internal development telemetry switch.", + "enable_dai_debug": "Internal Document Intelligence debug switch.", + "enable_tabular_generation_balanced_batches": "Internal tabular batching tuning flag.", + "enable_tabular_completion_driven_checkpointing": "Internal tabular checkpointing tuning flag.", + "enable_tabular_independent_batch_retries": "Internal tabular retry tuning flag.", + "enable_tabular_rolling_worker_pool": "Internal tabular worker pool tuning flag.", + "enable_tabular_analyze_durable_preflight": "Internal tabular preflight tuning flag.", + "enable_tabular_search_shared_preflight": "Internal tabular preflight tuning flag.", + "enable_tabular_compact_response_protocol": "Internal tabular protocol tuning flag.", + "enable_tabular_durable_run_confirmation": "Internal tabular durability tuning flag.", + "enable_tabular_generation_plan": "Internal tabular planning tuning flag.", + "enable_tabular_hierarchical_analysis": "Internal tabular planning tuning flag.", + "enable_tabular_mixed_deferred_composition_planning": "Internal tabular planning tuning flag.", + "enable_tabular_multifile_execution_unit_planning": "Internal tabular planning tuning flag.", + "enable_document_access_index_cache": "Internal Cosmos access-index rollout flag.", + "enable_document_access_index_container": "Internal Cosmos access-index rollout flag.", + "enable_document_access_index_reads": "Internal Cosmos access-index rollout flag.", + "enable_document_access_index_shadow_validation": "Internal Cosmos access-index rollout flag.", + "enable_document_access_index_write_through": "Internal Cosmos access-index rollout flag.", + "enable_startup_document_access_index_backfill": "Internal Cosmos access-index rollout flag.", + "enable_mixed_source_manifest": "Internal mixed-source rollout flag.", + "enable_mixed_source_relevance_candidates": "Internal mixed-source rollout flag.", + "enable_startup_app_maintenance": "Internal startup maintenance switch.", + "enable_default_embedding_model_plugin": "Internal plugin registration default.", +} + + +def load_inventory(): + """Load the generated application surface inventory.""" + if not APP_SURFACE.exists(): + raise AssertionError( + f"Missing {APP_SURFACE.relative_to(REPO_ROOT)}. " + "Run: python scripts/build_docs_inventory.py" + ) + return yaml.safe_load(io.open(APP_SURFACE, encoding="utf-8")) + + +def load_docs_corpus(): + """Concatenate the text of every published documentation page.""" + seen = set() + chunks = [] + + for pattern in DOC_GLOBS: + for path in DOCS_ROOT.glob(pattern): + if not path.is_file() or path in seen: + continue + parts = set(path.relative_to(DOCS_ROOT).parts) + if "_site" in parts or "fixes" in parts: + continue + seen.add(path) + chunks.append(io.open(path, encoding="utf-8", errors="ignore").read()) + + if not chunks: + raise AssertionError("No documentation pages were found to scan.") + + return "\n".join(chunks) + + +def report_missing(label, missing, total, remediation): + """Format a coverage failure message.""" + return ( + f"{label}: {len(missing)} of {total} are undocumented.\n" + f" Missing: {', '.join(sorted(missing)[:25])}" + f"{' ...' if len(missing) > 25 else ''}\n" + f" {remediation}" + ) + + +def test_inventory_is_in_sync(): + """The generated inventory must match the current application.""" + print("Checking docs/_data/app_surface.yml is in sync with the application...") + + if not INVENTORY_SCRIPT.exists(): + print(f" Skipped: {INVENTORY_SCRIPT.relative_to(REPO_ROOT)} not found.") + return True + + result = subprocess.run( + [sys.executable, str(INVENTORY_SCRIPT), "--check"], + cwd=str(REPO_ROOT), + capture_output=True, + text=True, + ) + + if result.returncode != 0: + print("Inventory is stale. Regenerate it with:") + print(" python scripts/build_docs_inventory.py") + print(result.stdout.strip()) + print(result.stderr.strip()) + return False + + print(" Inventory is current.") + return True + + +def test_admin_group_coverage(): + """Every admin settings group must have a documentation page. + + Admin Settings was reworked from 18 flat tabs into a grouped information + architecture of GROUP -> TAB -> SECTION, defined in admin_settings_nav.py. + Documentation is organized one page per group, because several tabs carry + only one or two settings sections and would make very thin pages. + """ + print("Checking admin settings group documentation coverage...") + + inventory = load_inventory() + groups = [group["id"] for group in inventory.get("admin_groups", [])] + missing = [group for group in groups if not (DOCS_ROOT / "admin" / f"{group}.md").exists()] + + if missing: + print(report_missing( + "Admin settings groups", missing, len(groups), + "Add a page at docs/admin/.md for each missing group.", + )) + return False + + print(f" All {len(groups)} admin settings groups are documented.") + return True + + +def test_admin_tab_coverage(): + """Every admin settings tab must be documented on its group's page. + + Tabs are documented as headings within their group page rather than as + separate pages, so each tab id must appear as a heading anchor. That keeps + deep links to a specific tab working. + """ + print("Checking admin settings tab documentation coverage...") + + inventory = load_inventory() + tabs = inventory.get("admin_tabs", []) + missing = [] + + for tab in tabs: + page = DOCS_ROOT / "admin" / f"{tab['group']}.md" + if not page.exists(): + missing.append(f"{tab['id']} (group page {tab['group']}.md missing)") + continue + + content = io.open(page, encoding="utf-8", errors="ignore").read() + # Accept an explicit kramdown heading anchor or an existing HTML anchor. + anchors = ( + "{#" + tab["id"] + "}", + 'id="' + tab["id"] + '"', + "{: #" + tab["id"] + "}", + ) + if not any(anchor in content for anchor in anchors): + missing.append(f"{tab['id']} (no anchor in {tab['group']}.md)") + + if missing: + print(report_missing( + "Admin settings tabs", missing, len(tabs), + "Give each tab a heading anchor on its group page, for example " + "'## Redis & Caching {#redis-caching}'.", + )) + return False + + print(f" All {len(tabs)} admin settings tabs are documented on their group pages.") + return True + + +def test_action_coverage(): + """Every action plugin must be documented.""" + print("Checking action documentation coverage...") + + inventory = load_inventory() + actions = [action["slug"] for action in inventory.get("actions", [])] + corpus = load_docs_corpus() + + missing = [] + for slug in actions: + page = DOCS_ROOT / "reference" / "actions" / f"{slug}.md" + if page.exists(): + continue + if slug in corpus: + continue + missing.append(slug) + + if missing: + print(report_missing( + "Actions", missing, len(actions), + "Add a page at docs/reference/actions/.md for each missing action.", + )) + return False + + print(f" All {len(actions)} actions are documented.") + return True + + +def test_chat_control_coverage(): + """Every chat control must appear in the chat interface reference.""" + print("Checking chat control documentation coverage...") + + inventory = load_inventory() + controls = [control["id"] for control in inventory.get("chat_controls", [])] + corpus = load_docs_corpus() + + missing = [control for control in controls if control not in corpus] + + if missing: + print(report_missing( + "Chat controls", missing, len(controls), + "Document each control in docs/reference/chat-controls.md.", + )) + return False + + print(f" All {len(controls)} chat controls are documented.") + return True + + +def test_capability_coverage(): + """Every user-facing capability toggle must be documented.""" + print("Checking capability toggle documentation coverage...") + + inventory = load_inventory() + capabilities = [capability["key"] for capability in inventory.get("capabilities", [])] + corpus = load_docs_corpus() + + missing = [ + key for key in capabilities + if key not in corpus and key not in CAPABILITY_EXEMPTIONS + ] + + if missing: + print(report_missing( + "Capability toggles", missing, len(capabilities), + "Document each toggle on the relevant docs/admin/.md page, or add a " + "justified entry to CAPABILITY_EXEMPTIONS in this test if it is an " + "internal-only flag.", + )) + return False + + documented = len(capabilities) - len(CAPABILITY_EXEMPTIONS) + print(f" {documented} user-facing capability toggles are documented " + f"({len(CAPABILITY_EXEMPTIONS)} internal flags exempt).") + return True + + +def test_exemptions_are_still_real(): + """Exempted toggles must still exist, so the list cannot rot.""" + print("Checking capability exemption list has no stale entries...") + + inventory = load_inventory() + capabilities = {capability["key"] for capability in inventory.get("capabilities", [])} + stale = sorted(key for key in CAPABILITY_EXEMPTIONS if key not in capabilities) + + if stale: + print(f" These exemptions reference toggles that no longer exist: {', '.join(stale)}") + print(" Remove them from CAPABILITY_EXEMPTIONS in this test.") + return False + + print(f" All {len(CAPABILITY_EXEMPTIONS)} exemptions reference real toggles.") + return True + + +if __name__ == "__main__": + assert_app_version_at_least("0.260.020") + + tests = [ + test_inventory_is_in_sync, + test_admin_group_coverage, + test_admin_tab_coverage, + test_action_coverage, + test_chat_control_coverage, + test_capability_coverage, + test_exemptions_are_still_real, + ] + + results = [] + for test in tests: + print(f"\nRunning {test.__name__}...") + try: + results.append(test()) + except Exception as error: # noqa: BLE001 - report and continue + print(f"Test raised an exception: {error}") + import traceback + traceback.print_exc() + results.append(False) + + print(f"\nResults: {sum(1 for r in results if r)}/{len(results)} checks passed") + sys.exit(0 if all(results) else 1) diff --git a/functional_tests/test_docs_site_quality.py b/functional_tests/test_docs_site_quality.py new file mode 100644 index 000000000..2af61090c --- /dev/null +++ b/functional_tests/test_docs_site_quality.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +""" +Functional test for documentation site quality and URL contracts. +Version: 0.250.230 +Implemented in: 0.250.230 + +This test protects the documentation site redesign from regressing: + + - The application deep-links to specific docs URLs from the in-app support + menu. Those URLs are a compatibility contract and must keep resolving. + - Browser runtime assets must be served locally, never from a CDN. + - Internal engineering notes must stay excluded from the published site so + they do not flood search results again. + - Every navigation entry must point at a page that exists. + - The search index must carry page content, not just titles. + +The test works against the docs source tree so it can run without building the +site. Where a built site is available at docs/_site it performs extra checks. +""" + +import io +import os +import re +import sys +from pathlib import Path + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +import yaml + +from test_support.versioning import assert_app_version_at_least + +REPO_ROOT = Path(__file__).resolve().parents[1] +DOCS_ROOT = REPO_ROOT / "docs" +SITE_ROOT = DOCS_ROOT / "_site" +CONFIG_FILE = DOCS_ROOT / "_config.yml" +SUPPORT_MENU = REPO_ROOT / "application" / "single_app" / "support_menu_config.py" + +# Hosts that must never serve browser runtime code or its companion assets. +FORBIDDEN_ASSET_HOSTS = ( + "cdn.jsdelivr.net", + "cdnjs.cloudflare.com", + "code.jquery.com", + "unpkg.com", + "cdn.datatables.net", + "stackpath.bootstrapcdn.com", + "fonts.googleapis.com", + "fonts.gstatic.com", + "esm.sh", + "skypack.dev", +) + +ASSET_SEARCH_DIRS = ("_layouts", "_includes") +ASSET_SEARCH_SUFFIXES = (".html", ".scss", ".css", ".js") + +DOCS_SITE_BASE = "https://microsoft.github.io/simplechat" + + +def load_config(): + """Load the Jekyll site configuration.""" + return yaml.safe_load(io.open(CONFIG_FILE, encoding="utf-8")) + + +def url_to_source_candidates(url): + """Map a site URL to the source files that could produce it.""" + relative = url.strip("/") + if not relative: + return [DOCS_ROOT / "index.md"] + + return [ + DOCS_ROOT / f"{relative}.md", + DOCS_ROOT / relative / "index.md", + DOCS_ROOT / f"{relative}.html", + DOCS_ROOT / relative / "index.html", + ] + + +def url_exists(url): + """Return True when a URL resolves to a source page or a built page.""" + if any(candidate.exists() for candidate in url_to_source_candidates(url)): + return True + + if SITE_ROOT.exists(): + relative = url.strip("/") + built = SITE_ROOT / relative / "index.html" if relative else SITE_ROOT / "index.html" + if built.exists(): + return True + + # Pages can also declare an explicit permalink rather than living at a + # matching path, so fall back to scanning front matter for the permalink. + target = "/" + url.strip("/") + "/" + for path in DOCS_ROOT.rglob("*.md"): + if "_site" in path.parts: + continue + try: + head = io.open(path, encoding="utf-8", errors="ignore").read(1200) + except OSError: + continue + match = re.search(r"^permalink:\s*(\S+)\s*$", head, re.MULTILINE) + if match and match.group(1).strip("\"'").rstrip("/") + "/" == target: + return True + + return False + + +def test_app_linked_urls_resolve(): + """URLs the application deep-links to must keep resolving.""" + print("Checking documentation URLs referenced by the application...") + + if not SUPPORT_MENU.exists(): + print(f" Skipped: {SUPPORT_MENU.name} not found.") + return True + + source = io.open(SUPPORT_MENU, encoding="utf-8", errors="ignore").read() + linked = sorted(set(re.findall(rf"{re.escape(DOCS_SITE_BASE)}(/[^\s'\"]*)", source))) + + if not linked: + print(" No documentation deep links found in the support menu.") + return True + + broken = [url for url in linked if not url_exists(url)] + + if broken: + print(f" {len(broken)} of {len(linked)} application deep links no longer resolve:") + for url in broken: + print(f" {DOCS_SITE_BASE}{url}") + print(" These URLs are referenced from the in-app support menu. Restore the " + "page or add a redirect_from entry on its replacement.") + return False + + print(f" All {len(linked)} application deep links resolve.") + return True + + +def test_no_cdn_browser_assets(): + """Browser runtime assets must be served locally.""" + print("Checking for CDN-hosted browser assets...") + + offenders = [] + + def scan(path): + text = io.open(path, encoding="utf-8", errors="ignore").read() + for host in FORBIDDEN_ASSET_HOSTS: + if host in text: + for number, line in enumerate(text.splitlines(), start=1): + if host in line: + offenders.append(f"{path.relative_to(REPO_ROOT)}:{number}: {host}") + + for directory in ASSET_SEARCH_DIRS: + for path in (DOCS_ROOT / directory).rglob("*"): + if path.is_file() and path.suffix in ASSET_SEARCH_SUFFIXES: + scan(path) + + assets_dir = DOCS_ROOT / "assets" + for path in assets_dir.rglob("*"): + if not path.is_file() or path.suffix not in ASSET_SEARCH_SUFFIXES: + continue + # Vendored third-party files legitimately carry upstream URLs in their + # license and homepage comments. + if "vendor" in path.parts: + continue + scan(path) + + if offenders: + print(f" {len(offenders)} CDN asset reference(s) found:") + for offender in offenders[:20]: + print(f" {offender}") + print(" Vendor the asset under docs/assets/vendor/ and reference it locally. " + "See .github/instructions/local_browser_assets.instructions.md") + return False + + print(" No CDN-hosted browser assets referenced.") + return True + + +def test_engineering_notes_excluded(): + """Internal engineering notes must stay out of the published site.""" + print("Checking engineering notes are excluded from the build...") + + config = load_config() + excluded = [str(item).rstrip("/") for item in config.get("exclude", [])] + required = ["explanation/features", "explanation/fixes"] + missing = [item for item in required if item not in excluded] + + if missing: + print(f" Missing exclude entries: {', '.join(missing)}") + print(" These trees hold internal engineering notes. Publishing them " + "previously made up 84% of the search index.") + return False + + if SITE_ROOT.exists(): + leaked = [] + for tree in required: + if (SITE_ROOT / Path(tree)).exists(): + leaked.append(tree) + if leaked: + print(f" Excluded trees were still built: {', '.join(leaked)}") + return False + + print(" Engineering notes are excluded.") + return True + + +def test_navigation_targets_exist(): + """Every navigation entry must point at a real page.""" + print("Checking navigation links resolve...") + + config = load_config() + navigation = config.get("navigation", {}) + + urls = [link["url"] for link in navigation.get("main_links", []) if "url" in link] + for section in navigation.get("sidebar_sections", []): + urls += [link["url"] for link in section.get("links", []) if "url" in link] + + internal = sorted({url for url in urls if url.startswith("/")}) + broken = [url for url in internal if not url_exists(url)] + + if broken: + print(f" {len(broken)} of {len(internal)} navigation links are broken:") + for url in broken: + print(f" {url}") + return False + + print(f" All {len(internal)} navigation links resolve.") + return True + + +def test_navigation_sections_match(): + """The top bar and sidebar must expose the same sections.""" + print("Checking top bar and sidebar agree on sections...") + + config = load_config() + navigation = config.get("navigation", {}) + + top = [link["title"] for link in navigation.get("main_links", [])] + sidebar = [section["title"] for section in navigation.get("sidebar_sections", [])] + + if len(top) != len(sidebar): + print(f" Top bar has {len(top)} entries {top} but the sidebar has " + f"{len(sidebar)} sections {sidebar}.") + print(" These two navigations previously disagreed, which is why the site " + "was hard to navigate. Keep them aligned.") + return False + + print(f" Both navigations expose {len(top)} sections: {', '.join(sidebar)}") + return True + + +def test_search_index_has_content(): + """The search index template must index page body text.""" + print("Checking the search index indexes page content...") + + index_template = DOCS_ROOT / "search-index.json" + if not index_template.exists(): + print(" Missing docs/search-index.json.") + return False + + template = io.open(index_template, encoding="utf-8").read() + + if "item.content" not in template: + print(" The search index does not reference item.content, so only page " + "titles would be searchable. That was the original search defect.") + return False + + if '"body"' not in template: + print(" The search index does not emit a body field.") + return False + + if not SITE_ROOT.exists(): + print(" Template indexes content. Build the site for the full check.") + return True + + built = SITE_ROOT / "search-index.json" + if not built.exists(): + print(" Built search index missing at docs/_site/search-index.json.") + return False + + entries = yaml.safe_load(io.open(built, encoding="utf-8")) + + without_description = [entry for entry in entries if not entry.get("description")] + with_body = [entry for entry in entries if entry.get("body")] + engineering = [ + entry for entry in entries + if "/explanation/fixes/" in entry.get("url", "") + or re.search(r"/explanation/features/v", entry.get("url", "")) + ] + filename_titles = [entry for entry in entries if entry.get("title", "").endswith(".md")] + + problems = [] + if without_description: + problems.append(f"{len(without_description)} entries have no description") + if engineering: + problems.append(f"{len(engineering)} engineering notes leaked into the index") + if filename_titles: + problems.append(f"{len(filename_titles)} entries are titled with a raw filename") + if not with_body: + problems.append("no entry carries body text") + + if problems: + print(" Search index problems: " + "; ".join(problems)) + return False + + print(f" Search index has {len(entries)} entries, all with descriptions, " + f"{len(with_body)} with body text, and no engineering notes.") + return True + + +if __name__ == "__main__": + assert_app_version_at_least("0.250.230") + + tests = [ + test_app_linked_urls_resolve, + test_no_cdn_browser_assets, + test_engineering_notes_excluded, + test_navigation_targets_exist, + test_navigation_sections_match, + test_search_index_has_content, + ] + + results = [] + for test in tests: + print(f"\nRunning {test.__name__}...") + try: + results.append(test()) + except Exception as error: # noqa: BLE001 - report and continue + print(f"Test raised an exception: {error}") + import traceback + traceback.print_exc() + results.append(False) + + print(f"\nResults: {sum(1 for r in results if r)}/{len(results)} checks passed") + sys.exit(0 if all(results) else 1) diff --git a/functional_tests/test_latest_release_docs_structure.py b/functional_tests/test_latest_release_docs_structure.py index a5d2748ea..ea3640721 100644 --- a/functional_tests/test_latest_release_docs_structure.py +++ b/functional_tests/test_latest_release_docs_structure.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ Functional test for latest-release documentation structure. -Version: 0.250.047 +Version: 0.260.001 Implemented in: 0.241.002; 0.241.003; 0.241.164; 0.241.165; 0.241.166; 0.241.167; 0.241.183; 0.241.184; 0.250.001; 0.250.034; 0.250.035; 0.250.036; 0.250.041; 0.250.042; 0.250.043; 0.250.044; 0.250.045; 0.250.046; 0.250.047 This test ensures the docs/latest-release landing page is driven by the latest @@ -23,62 +23,53 @@ LATEST_RELEASE_DIR = REPO_ROOT / "docs" / "latest-release" LATEST_RELEASE_IMAGE_DIR = REPO_ROOT / "docs" / "images" / "latest-release" ADMIN_CONFIGURATION_DOC = REPO_ROOT / "docs" / "admin_configuration.md" +ADMIN_DOCS_DIR = REPO_ROOT / "docs" / "admin" ADMIN_SETTINGS_IMAGE_DIR = REPO_ROOT / "docs" / "images" / "admin-settings" CURRENT_GUIDES = { - 'release-250-ai-access.md': 'Personalized Model and Agent Access', - 'release-250-tabular-analysis.md': 'Improved Tabular Analysis', - 'release-250-custom-pages.md': 'Custom Pages', - 'release-250-tableau-action.md': 'Tableau Action', - 'release-250-workflows.md': 'Personal and Group Workflows', - 'release-250-voice-assisted-inputs.md': 'Voice-Assisted Form Inputs', - 'release-250-m365-actions.md': 'Microsoft 365 Actions', - 'release-250-chat-uploads.md': 'Workspace-Backed Chat Uploads and Paste Support', - 'release-250-document-intelligence.md': 'Enhanced Document Intelligence', - 'release-250-file-sync.md': 'File Sync for SMB and Azure Files', - 'release-250-conversation-feed.md': 'Faster Conversation Lists', - 'release-250-group-file-sharing.md': 'Group File Sharing and Approvals', - 'release-250-profile-stats.md': 'Profile, Stats, and Preferences', - 'release-250-databricks-action.md': 'Databricks Action', - 'release-250-layered-masking.md': 'Layered Message Masking', - 'release-250-visio-msg-ingestion.md': 'Visio and Outlook MSG File Support', - 'release-250-assigned-knowledge.md': 'Assigned Knowledge for Agents', - 'release-250-deep-research.md': 'Deep Research and Source Review', - 'release-250-url-access.md': 'URL Access in Chat', - 'release-250-source-continuity.md': 'Conversation Source Continuity', - 'release-250-generated-documents.md': 'Generated Markdown, Word, and PowerPoint Files', - 'release-250-multi-inline-image-gen.md': 'Multi Inline Image Generation', - 'release-250-workspace-views.md': 'Workspace Cards and Folder Views', - 'release-250-follow-up-actions.md': 'Assistant Follow-Up Actions', - 'release-250-model-agent-avatars.md': 'Model and Agent Avatars', + 'release-260-enhanced-extraction.md': 'Sharper Document Extraction with Figure Descriptions', + 'release-260-office-embedded-images.md': 'Pictures Inside Word and PowerPoint Are Now Searchable', + 'release-260-workflow-task-sequences.md': 'Multi-Step Workflows With Alert Rules', + 'release-260-mcp-platform.md': 'Model Context Protocol Connections', + 'release-260-yamcs-action.md': 'Yamcs Mission Control Integration', + 'release-260-rocksdb-action.md': 'RocksDB Key-Value Store Action', + 'release-260-agent-instruction-references.md': 'Reference Actions and Knowledge Directly in Agent Instructions', + 'release-260-action-test-connection.md': 'Test Connection Before You Save an Action', + 'release-260-azure-blob-file-sync.md': 'Sync Documents From Azure Blob Storage', + 'release-260-terms-of-use.md': 'Terms of Use Acceptance', + 'release-260-audio-file-support.md': 'Upload Almost Any Audio File', + 'release-260-completion-notifications.md': 'Know When a Long Answer Finishes', + 'release-260-chat-ai-notice.md': 'AI Usage Guidance in Chat', + 'release-260-conversation-context-grounding.md': 'See Exactly What Shaped Each Answer', + 'release-260-used-documents-fork.md': 'Used Documents View and Conversation Forking', + 'release-260-conversation-contents-drawer.md': 'Jump Back to Any Earlier Prompt', + 'release-260-font-size-zoom.md': 'Choose Your Text Size', + 'release-260-message-audio-export.md': 'Download a Response as Audio', + 'release-260-public-workspace-display-name.md': 'Public Workspace Can Carry Your Own Name', + 'release-260-chat-scroll-508.md': 'Chat Stops Yanking You to the Bottom', } CURRENT_GUIDE_IMAGES = { - 'release-250-ai-access': ['release_250_ai_access.png'], - 'release-250-tabular-analysis': ['release_250_tabular_analysis.png'], - 'release-250-custom-pages': ['release_250_custom_pages.png'], - 'release-250-tableau-action': ['release_250_tableau_action.png'], - 'release-250-workflows': ['release_250_workflows.png'], - 'release-250-voice-assisted-inputs': ['release_250_voice_assisted_inputs.png'], - 'release-250-m365-actions': ['release_250_m365_actions.png'], - 'release-250-chat-uploads': ['release_250_chat_uploads.png'], - 'release-250-document-intelligence': ['release_250_document_intelligence.png'], - 'release-250-file-sync': ['release_250_file_sync.png'], - 'release-250-conversation-feed': ['release_250_conversation_feed.png'], - 'release-250-group-file-sharing': ['release_250_group_file_sharing.png'], - 'release-250-profile-stats': ['release_250_profile_stats.png'], - 'release-250-databricks-action': ['release_250_databricks_action.png'], - 'release-250-layered-masking': ['release_250_layered_masking.png'], - 'release-250-visio-msg-ingestion': ['release_250_visio_msg_ingestion.png'], - 'release-250-assigned-knowledge': ['release_250_assigned_knowledge.png'], - 'release-250-deep-research': ['release_250_deep_research.png'], - 'release-250-url-access': ['release_250_url_access.png'], - 'release-250-source-continuity': ['release_250_source_continuity.png'], - 'release-250-generated-documents': ['release_250_generated_documents.png'], - 'release-250-multi-inline-image-gen': ['release_250_multi_inline_image_gen.png'], - 'release-250-workspace-views': ['release_250_workspace_views.png'], - 'release-250-follow-up-actions': ['release_250_follow_up_actions.png'], - 'release-250-model-agent-avatars': ['release_250_model_agent_avatars.png'], + 'release-260-enhanced-extraction': ['release_260_enhanced_extraction_1.png', 'release_260_enhanced_extraction_2.png', 'release_260_enhanced_extraction_3.png'], + 'release-260-office-embedded-images': ['release_260_office_embedded_images_1.png', 'release_260_office_embedded_images_2.png', 'release_260_office_embedded_images_3.png'], + 'release-260-workflow-task-sequences': ['release_260_workflow_task_sequences_1.png', 'release_260_workflow_task_sequences_2.png', 'release_260_workflow_task_sequences_3.png'], + 'release-260-mcp-platform': ['release_260_mcp_platform_1.png', 'release_260_mcp_platform_2.png', 'release_260_mcp_platform_3.png'], + 'release-260-yamcs-action': ['release_260_yamcs_action_1.png', 'release_260_yamcs_action_2.png', 'release_260_yamcs_action_3.png'], + 'release-260-rocksdb-action': ['release_260_rocksdb_action_1.png', 'release_260_rocksdb_action_2.png', 'release_260_rocksdb_action_3.png'], + 'release-260-agent-instruction-references': ['release_260_agent_instruction_references_1.png', 'release_260_agent_instruction_references_2.png', 'release_260_agent_instruction_references_3.png'], + 'release-260-action-test-connection': ['release_260_action_test_connection_1.png', 'release_260_action_test_connection_2.png', 'release_260_action_test_connection_3.png'], + 'release-260-azure-blob-file-sync': ['release_260_azure_blob_file_sync_1.png', 'release_260_azure_blob_file_sync_2.png', 'release_260_azure_blob_file_sync_3.png'], + 'release-260-terms-of-use': ['release_260_terms_of_use_1.png', 'release_260_terms_of_use_2.png', 'release_260_terms_of_use_3.png'], + 'release-260-audio-file-support': ['release_260_audio_file_support_1.png', 'release_260_audio_file_support_2.png', 'release_260_audio_file_support_3.png'], + 'release-260-completion-notifications': ['release_260_completion_notifications_1.png', 'release_260_completion_notifications_2.png', 'release_260_completion_notifications_3.png'], + 'release-260-chat-ai-notice': ['release_260_chat_ai_notice_1.png', 'release_260_chat_ai_notice_2.png', 'release_260_chat_ai_notice_3.png'], + 'release-260-conversation-context-grounding': ['release_260_conversation_context_grounding_1.png', 'release_260_conversation_context_grounding_2.png', 'release_260_conversation_context_grounding_3.png'], + 'release-260-used-documents-fork': ['release_260_used_documents_fork_1.png', 'release_260_used_documents_fork_2.png', 'release_260_used_documents_fork_3.png'], + 'release-260-conversation-contents-drawer': ['release_260_conversation_contents_drawer_1.png', 'release_260_conversation_contents_drawer_2.png', 'release_260_conversation_contents_drawer_3.png'], + 'release-260-font-size-zoom': ['release_260_font_size_zoom_1.png', 'release_260_font_size_zoom_2.png', 'release_260_font_size_zoom_3.png'], + 'release-260-message-audio-export': ['release_260_message_audio_export_1.png', 'release_260_message_audio_export_2.png', 'release_260_message_audio_export_3.png'], + 'release-260-public-workspace-display-name': ['release_260_public_workspace_display_name_1.png', 'release_260_public_workspace_display_name_2.png', 'release_260_public_workspace_display_name_3.png'], + 'release-260-chat-scroll-508': ['release_260_chat_scroll_508_1.png', 'release_260_chat_scroll_508_2.png', 'release_260_chat_scroll_508_3.png'], } ADMIN_SETTINGS_IMAGES = [ @@ -115,45 +106,41 @@ def test_latest_release_docs_structure() -> bool: required_index_markers = [ 'layout: latest-release-index', 'title: "Latest Release Highlights"', - 'SimpleChat v0.250.001', - 'v0.241.001-v0.241.007', + 'SimpleChat v0.260.001', + 'v0.250.001', 'v0.239.001', ] missing_index_markers = [marker for marker in required_index_markers if marker not in index_content] assert not missing_index_markers, f"Missing latest-release index markers: {missing_index_markers}" assert release_data["current_release"]["slugs"] == [ - 'release-250-ai-access', - 'release-250-tabular-analysis', - 'release-250-custom-pages', - 'release-250-tableau-action', - 'release-250-workflows', - 'release-250-voice-assisted-inputs', - 'release-250-m365-actions', - 'release-250-chat-uploads', - 'release-250-document-intelligence', - 'release-250-file-sync', - 'release-250-conversation-feed', - 'release-250-group-file-sharing', - 'release-250-profile-stats', - 'release-250-databricks-action', - 'release-250-layered-masking', - 'release-250-visio-msg-ingestion', - 'release-250-assigned-knowledge', - 'release-250-deep-research', - 'release-250-url-access', - 'release-250-source-continuity', - 'release-250-generated-documents', - 'release-250-multi-inline-image-gen', - 'release-250-workspace-views', - 'release-250-follow-up-actions', - 'release-250-model-agent-avatars', + 'release-260-enhanced-extraction', + 'release-260-office-embedded-images', + 'release-260-workflow-task-sequences', + 'release-260-mcp-platform', + 'release-260-yamcs-action', + 'release-260-rocksdb-action', + 'release-260-agent-instruction-references', + 'release-260-action-test-connection', + 'release-260-azure-blob-file-sync', + 'release-260-terms-of-use', + 'release-260-audio-file-support', + 'release-260-completion-notifications', + 'release-260-chat-ai-notice', + 'release-260-conversation-context-grounding', + 'release-260-used-documents-fork', + 'release-260-conversation-contents-drawer', + 'release-260-font-size-zoom', + 'release-260-message-audio-export', + 'release-260-public-workspace-display-name', + 'release-260-chat-scroll-508', ] previous_groups = release_data["previous_release_groups"] - assert previous_groups[0]["release_version"] == "0.241.001 - 0.241.007" - assert previous_groups[1]["release_version"] == "0.239.001" - assert "guided-tutorials" in previous_groups[0]["slugs"] + assert previous_groups[0]["release_version"] == "0.250.001" + assert previous_groups[1]["release_version"] == "0.239.001 - 0.241.007" + assert "release-250-ai-access" in previous_groups[0]["slugs"] + assert "guided-tutorials" in previous_groups[1]["slugs"] assert "export-conversation" in previous_groups[1]["slugs"] lookup = release_data["lookup"] @@ -173,16 +160,22 @@ def test_latest_release_docs_structure() -> bool: assert image.get("label") != "Feature Guide", f"Redundant docs Feature Guide image remains: {slug}" assert "feature_card" not in image["path"], f"Redundant docs feature-card asset remains: {slug}" image_path = LATEST_RELEASE_IMAGE_DIR / image["path"].replace("/images/latest-release/", "") - if not image_path.name.startswith("release_250_"): - assert image_path.exists(), f"Missing docs image asset: {image['path']}" + assert image_path.exists(), f"Missing docs image asset: {image['path']}" admin_configuration_content = read_text(ADMIN_CONFIGURATION_DOC) + admin_tab_content = "\n".join(read_text(path) for path in sorted(ADMIN_DOCS_DIR.glob("*.md"))) + admin_docs_content = f"{admin_configuration_content}\n{admin_tab_content}" assert "## Admin Settings Execution Guide" in admin_configuration_content, "Admin execution guide missing." for image_name in ADMIN_SETTINGS_IMAGES: image_path = ADMIN_SETTINGS_IMAGE_DIR / image_name - image_reference = f"./images/admin-settings/{image_name}" + image_references = [ + f"./images/admin-settings/{image_name}", + f"admin-settings/{image_name}", + ] assert image_path.exists(), f"Missing admin settings docs image: {image_name}" - assert image_reference in admin_configuration_content, f"Admin settings doc missing image reference: {image_name}" + assert any(reference in admin_docs_content for reference in image_references), ( + f"Admin settings docs missing image reference: {image_name}" + ) for file_name, title in CURRENT_GUIDES.items(): guide_path = LATEST_RELEASE_DIR / file_name diff --git a/scripts/build_docs_inventory.py b/scripts/build_docs_inventory.py new file mode 100644 index 000000000..ad2b0f442 --- /dev/null +++ b/scripts/build_docs_inventory.py @@ -0,0 +1,516 @@ +# build_docs_inventory.py + +"""Build the documentation inventory for SimpleChat user-facing surfaces.""" + +from __future__ import annotations + +import argparse +import ast +import html +import re +import sys +from html.parser import HTMLParser +from pathlib import Path +from typing import Any + +try: + import yaml +except ImportError: + yaml = None + + +REPO_ROOT = Path(__file__).resolve().parents[1] +APP_ROOT = REPO_ROOT / "application" / "single_app" +TEMPLATES_DIR = APP_ROOT / "templates" +PLUGINS_DIR = APP_ROOT / "semantic_kernel_plugins" +DEFAULT_OUTPUT_PATH = REPO_ROOT / "docs" / "_data" / "app_surface.yml" + +GENERATED_HEADER = ( + "# Generated by scripts\\build_docs_inventory.py -- do not edit by hand.\n" + "# Regenerate with: python scripts\\build_docs_inventory.py\n" +) + +PLUGIN_INFRASTRUCTURE_FILES = { + "__init__.py", + "base_plugin.py", + "logged_plugin_loader.py", + "plugin_health_checker.py", + "plugin_invocation_logger.py", + "plugin_invocation_thoughts.py", + "plugin_loader.py", + "plugin_logging_examples.py", + "sql_odbc_utils.py", + "ui_test_plugin.py", +} + +CHAT_CONTROL_ID_RE = re.compile(r"^[a-zA-Z0-9\-_]*(?:btn|button|toggle)[a-zA-Z0-9\-_]*$", re.IGNORECASE) +ENABLE_SETTING_RE = re.compile(r"enable_[a-z0-9_]+") +HTML_TAG_RE = re.compile(r"<[^>]+>") +JINJA_RE = re.compile(r"{[#%{].*?[#}%]}", re.DOTALL) + + +class ChatControlParser(HTMLParser): + """Extract chat control elements and their labels from the chat template.""" + + def __init__(self, source_lines: list[str]) -> None: + super().__init__(convert_charrefs=True) + self.source_lines = source_lines + self.controls: list[dict[str, Any]] = [] + self._active_indexes: list[int] = [] + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + attr_map = {name: value or "" for name, value in attrs} + element_id = attr_map.get("id", "") + if not CHAT_CONTROL_ID_RE.match(element_id): + return + + line_number = self.getpos()[0] + control = { + "id": element_id, + "_tag": tag, + "_label_parts": [], + "_line_number": line_number, + "_title": attr_map.get("title", ""), + "_aria_label": attr_map.get("aria-label", ""), + } + self.controls.append(control) + self._active_indexes.append(len(self.controls) - 1) + + def handle_data(self, data: str) -> None: + for index in self._active_indexes: + self.controls[index]["_label_parts"].append(data) + + def handle_endtag(self, tag: str) -> None: + for index in range(len(self._active_indexes) - 1, -1, -1): + control = self.controls[self._active_indexes[index]] + if control["_tag"] == tag: + del self._active_indexes[index] + break + + def get_controls(self) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + controls: list[dict[str, Any]] = [] + sub_elements: list[dict[str, Any]] = [] + for raw_control in self.controls: + item: dict[str, Any] = {"id": raw_control["id"]} + label = first_non_empty( + raw_control["_title"], + raw_control["_aria_label"], + normalize_label(" ".join(raw_control["_label_parts"])), + ) + if label: + item["label"] = label + + enabled_by = self.get_nearby_settings(raw_control["_line_number"]) + if enabled_by: + item["enabled_by"] = enabled_by + + if is_chat_control_sub_element(raw_control["id"]): + sub_elements.append(item) + else: + controls.append(item) + + return ( + sorted(controls, key=lambda item: item["id"]), + sorted(sub_elements, key=lambda item: item["id"]), + ) + + def get_nearby_settings(self, line_number: int) -> list[str]: + start = max(0, line_number - 6) + end = min(len(self.source_lines), line_number + 5) + nearby_source = "\n".join(self.source_lines[start:end]) + return sorted(set(ENABLE_SETTING_RE.findall(nearby_source))) + + +def first_non_empty(*values: str) -> str: + """Return the first normalized non-empty string.""" + for value in values: + normalized = normalize_label(value) + if normalized: + return normalized + return "" + + +def normalize_label(value: str) -> str: + """Collapse markup, Jinja fragments, and whitespace into a readable label.""" + without_jinja = JINJA_RE.sub(" ", value) + without_tags = HTML_TAG_RE.sub(" ", without_jinja) + unescaped = html.unescape(without_tags) + return re.sub(r"\s+", " ", unescaped).strip() + + +def is_chat_control_sub_element(element_id: str) -> bool: + """Return True for child labels/spinners that support a parent control.""" + return element_id.endswith(("-label", "-spinner")) + + +def kebab_case(value: str) -> str: + """Convert snake-style file stems to kebab-case slugs.""" + return value.replace("_", "-") + + +def read_text(path: Path) -> str: + """Read a source file as UTF-8.""" + return path.read_text(encoding="utf-8") + + +def extract_version() -> str: + """Read the application version from config.py without importing application code.""" + config_text = read_text(APP_ROOT / "config.py") + match = re.search(r'^\s*VERSION\s*=\s*["\']([^"\']+)["\']', config_text, re.MULTILINE) + if not match: + raise ValueError("Unable to find VERSION in application\\single_app\\config.py.") + return match.group(1) + + +def extract_capabilities() -> list[dict[str, Any]]: + """Extract enable_* settings from the default settings dictionary.""" + settings_text = read_text(APP_ROOT / "functions_settings.py") + capabilities_by_key: dict[str, dict[str, Any]] = {} + pattern = re.compile(r"^\s*'(?Penable_[a-z0-9_]+)'\s*:\s*(?PTrue|False|true|false)?", re.MULTILINE) + for match in pattern.finditer(settings_text): + key = match.group("key") + if key in capabilities_by_key: + continue + + item: dict[str, Any] = {"key": key} + default_value = match.group("default") + if default_value is not None: + item["default"] = default_value.lower() == "true" + capabilities_by_key[key] = item + return sorted(capabilities_by_key.values(), key=lambda item: item["key"]) + + +def extract_admin_nav() -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]: + """Extract the Admin Settings navigation groups, tabs, and sections. + + The Admin Settings information architecture was reworked into a single + authoritative definition in admin_settings_nav.py, structured as + GROUP -> TAB -> SECTION. Before that, the tab strip was hand-written markup + in admin_settings.html and was scraped from the rendered HTML. + + Scraping no longer works: the template renders the strip from this + definition in a loop, so the markup yields a single literal + "{{ admin_tab.id }}" instead of real tabs. Read the definition directly. + """ + source = read_text(APP_ROOT / "admin_settings_nav.py") + match = re.search(r"^ADMIN_NAV\s*=\s*(\[.*?^\])", source, re.S | re.M) + if match is None: + raise ValueError("Unable to find ADMIN_NAV in application\\single_app\\admin_settings_nav.py.") + + nav = ast.literal_eval(match.group(1)) + + groups: list[dict[str, Any]] = [] + tabs: list[dict[str, Any]] = [] + sections: list[dict[str, Any]] = [] + + for group in nav: + group_tabs = group.get("tabs", []) or [] + groups.append({ + "id": group["id"], + "label": group.get("label", ""), + "tabs": sorted(tab["id"] for tab in group_tabs), + }) + + for tab in group_tabs: + tab_sections = tab.get("sections", []) or [] + tabs.append({ + "id": tab["id"], + "label": tab.get("label", ""), + "group": group["id"], + }) + for section in tab_sections: + sections.append({ + "id": section["id"], + "label": section.get("label", ""), + "tab": tab["id"], + "group": group["id"], + }) + + groups.sort(key=lambda item: item["id"]) + tabs.sort(key=lambda item: item["id"]) + sections.sort(key=lambda item: item["id"]) + return groups, tabs, sections + + +def extract_primary_class(path: Path) -> tuple[str | None, str | None]: + """Return the primary user-facing plugin class and its docstring first line.""" + tree = ast.parse(read_text(path), filename=str(path)) + classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef) and not node.name.startswith("_")] + plugin_classes = [node for node in classes if node.name.endswith("Plugin")] + selected = plugin_classes[0] if plugin_classes else (classes[0] if classes else None) + if selected is None: + return None, None + + docstring = ast.get_docstring(selected) or "" + summary = docstring.strip().splitlines()[0].strip() if docstring.strip() else None + return selected.name, summary + + +def extract_actions() -> list[dict[str, Any]]: + """Extract Semantic Kernel plugin action types.""" + actions: list[dict[str, Any]] = [] + for path in sorted(PLUGINS_DIR.glob("*.py"), key=lambda item: item.name): + if path.name in PLUGIN_INFRASTRUCTURE_FILES or path.name.endswith("_plugin_factory.py"): + continue + + stem = path.stem + base_name = stem.removesuffix("_plugin") + class_name, summary = extract_primary_class(path) + item: dict[str, Any] = { + "slug": kebab_case(base_name), + "module": path.name, + } + if class_name: + item["class_name"] = class_name + if summary: + item["summary"] = summary + actions.append(item) + + return sorted(actions, key=lambda item: item["slug"]) + + +def extract_chat_controls() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + """Extract interactive chat controls and supporting sub-elements.""" + chat_text = read_text(TEMPLATES_DIR / "chats.html") + parser = ChatControlParser(chat_text.splitlines()) + parser.feed(chat_text) + return parser.get_controls() + + +def get_ast_name(node: ast.AST | None) -> str | None: + """Return a dotted name for supported AST nodes.""" + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Attribute): + parent = get_ast_name(node.value) + return f"{parent}.{node.attr}" if parent else node.attr + if isinstance(node, ast.Call): + return get_ast_name(node.func) + return None + + +def is_route_decorator(node: ast.AST) -> bool: + """Return True when a decorator is a Flask or Blueprint route decorator.""" + return isinstance(node, ast.Call) and bool(get_ast_name(node.func) and get_ast_name(node.func).endswith(".route")) + + +def get_route_urls(node: ast.FunctionDef | ast.AsyncFunctionDef) -> list[str]: + """Extract static route URLs from a route function's decorators.""" + urls: list[str] = [] + for decorator in node.decorator_list: + if not is_route_decorator(decorator): + continue + if decorator.args and isinstance(decorator.args[0], ast.Constant) and isinstance(decorator.args[0].value, str): + urls.append(decorator.args[0].value) + return sorted(set(urls)) + + +def iter_rendered_templates(node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]: + """Return static template names rendered inside a function.""" + templates: set[str] = set() + for child in ast.walk(node): + if not isinstance(child, ast.Call) or get_ast_name(child.func) != "render_template": + continue + if child.args and isinstance(child.args[0], ast.Constant) and isinstance(child.args[0].value, str): + templates.add(child.args[0].value) + return templates + + +def extract_template_routes() -> dict[str, list[str]]: + """Map rendered templates to route URLs by statically inspecting route modules.""" + route_files = sorted(APP_ROOT.glob("route_*.py"), key=lambda item: item.name) + app_file = APP_ROOT / "app.py" + if app_file.exists(): + route_files.append(app_file) + + template_routes: dict[str, set[str]] = {} + for route_file in route_files: + tree = ast.parse(read_text(route_file), filename=str(route_file)) + for node in ast.walk(tree): + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + route_urls = get_route_urls(node) + if not route_urls: + continue + for template in iter_rendered_templates(node): + template_routes.setdefault(template, set()).update(route_urls) + + return {template: sorted(urls) for template, urls in template_routes.items()} + + +def extract_app_pages() -> list[dict[str, Any]]: + """Extract top-level user-facing template pages.""" + template_routes = extract_template_routes() + pages: list[dict[str, Any]] = [] + for path in sorted(TEMPLATES_DIR.glob("*.html"), key=lambda item: item.name): + if path.name.startswith("_") or path.name == "base.html": + continue + + route_urls = template_routes.get(path.name, []) + item: dict[str, Any] = { + "template": path.name, + "slug": kebab_case(path.stem), + } + if route_urls: + item["route_url"] = route_urls[0] + if len(route_urls) > 1: + item["route_urls"] = route_urls + pages.append(item) + + return sorted(pages, key=lambda item: item["template"]) + + +def extract_feature_surfaces() -> list[dict[str, str]]: + """Extract modal and partial feature surfaces.""" + surfaces: list[dict[str, str]] = [] + for path in sorted(TEMPLATES_DIR.glob("_*.html"), key=lambda item: item.name): + surfaces.append( + { + "template": path.name, + "slug": kebab_case(path.stem.removeprefix("_")), + } + ) + return sorted(surfaces, key=lambda item: item["template"]) + + +def build_inventory() -> dict[str, Any]: + """Build the complete application surface inventory.""" + chat_controls, sub_elements = extract_chat_controls() + admin_groups, admin_tabs, admin_sections = extract_admin_nav() + inventory = { + "capabilities": extract_capabilities(), + "admin_groups": admin_groups, + "admin_tabs": admin_tabs, + "admin_sections": admin_sections, + "actions": extract_actions(), + "chat_controls": chat_controls, + "sub_elements": sub_elements, + "app_pages": extract_app_pages(), + "feature_surfaces": extract_feature_surfaces(), + } + inventory["counts"] = { + "capabilities": len(inventory["capabilities"]), + "admin_groups": len(inventory["admin_groups"]), + "admin_tabs": len(inventory["admin_tabs"]), + "admin_sections": len(inventory["admin_sections"]), + "actions": len(inventory["actions"]), + "chat_controls": len(inventory["chat_controls"]), + "sub_elements": len(inventory["sub_elements"]), + "app_pages": len(inventory["app_pages"]), + "feature_surfaces": len(inventory["feature_surfaces"]), + } + return { + "counts": inventory["counts"], + "capabilities": inventory["capabilities"], + "admin_groups": inventory["admin_groups"], + "admin_tabs": inventory["admin_tabs"], + "admin_sections": inventory["admin_sections"], + "actions": inventory["actions"], + "chat_controls": inventory["chat_controls"], + "sub_elements": inventory["sub_elements"], + "app_pages": inventory["app_pages"], + "feature_surfaces": inventory["feature_surfaces"], + } + + +def dump_yaml(data: dict[str, Any]) -> str: + """Serialize inventory data to deterministic YAML. + + The application version is deliberately NOT recorded here. SimpleChat bumps + VERSION on every change, so embedding it would make this file differ on every + pull request and turn the docs coverage sync check into constant noise. This + file should change only when the application surface itself changes. + """ + body_data = dict(data) + if yaml is not None: + body = yaml.safe_dump( + body_data, + sort_keys=False, + allow_unicode=True, + default_flow_style=False, + width=120, + ) + else: + body = dump_yaml_manually(body_data) + return f"{GENERATED_HEADER}{body}" + + +def dump_yaml_manually(data: Any, indent: int = 0) -> str: + """Fallback deterministic YAML dumper for this inventory's simple data types.""" + lines: list[str] = [] + prefix = " " * indent + if isinstance(data, dict): + for key, value in data.items(): + if isinstance(value, (dict, list)): + lines.append(f"{prefix}{key}:") + lines.append(dump_yaml_manually(value, indent + 2).rstrip("\n")) + else: + lines.append(f"{prefix}{key}: {format_yaml_scalar(value)}") + elif isinstance(data, list): + for item in data: + if isinstance(item, dict): + lines.append(f"{prefix}- {next(iter(item))}: {format_yaml_scalar(next(iter(item.values())))}") + for key, value in list(item.items())[1:]: + if isinstance(value, (dict, list)): + lines.append(f"{prefix} {key}:") + lines.append(dump_yaml_manually(value, indent + 4).rstrip("\n")) + else: + lines.append(f"{prefix} {key}: {format_yaml_scalar(value)}") + else: + lines.append(f"{prefix}- {format_yaml_scalar(item)}") + return "\n".join(lines) + "\n" + + +def format_yaml_scalar(value: Any) -> str: + """Format a scalar for the manual YAML fallback.""" + if isinstance(value, bool): + return "true" if value else "false" + if value is None: + return "null" + if isinstance(value, (int, float)): + return str(value) + escaped = str(value).replace("\\", "\\\\").replace('"', '\\"') + return f'"{escaped}"' + + +def print_counts(inventory: dict[str, Any]) -> None: + """Print a concise extraction summary.""" + counts = inventory["counts"] + summary = ", ".join(f"{key}: {value}" for key, value in counts.items()) + print(f"Wrote docs inventory ({summary}).") + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description="Build the SimpleChat documentation surface inventory.") + parser.add_argument("--check", action="store_true", help="Exit non-zero if the inventory file is out of date.") + parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT_PATH, help="Destination YAML path.") + return parser.parse_args() + + +def main() -> int: + """Run the inventory builder.""" + args = parse_args() + output_path = args.output + if not output_path.is_absolute(): + output_path = REPO_ROOT / output_path + + inventory = build_inventory() + rendered = dump_yaml(inventory) + + if args.check: + existing = output_path.read_text(encoding="utf-8") if output_path.exists() else "" + if existing != rendered: + print(f"Inventory is out of date: {output_path}", file=sys.stderr) + return 1 + print(f"Inventory is in sync: {output_path}") + return 0 + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(rendered, encoding="utf-8", newline="\n") + print_counts(inventory) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/build_release_notes_pages.py b/scripts/build_release_notes_pages.py new file mode 100644 index 000000000..c0a21450a --- /dev/null +++ b/scripts/build_release_notes_pages.py @@ -0,0 +1,468 @@ +# build_release_notes_pages.py +"""Generate split Jekyll release note pages from the authoring source.""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path + + +SOURCE_PATH = Path("docs") / "explanation" / "release_notes.md" +DEFAULT_OUTPUT_DIR = Path("docs") / "explanation" / "release-notes" +VERSION_HEADING_RE = re.compile( + r"(?m)^#{2,4}\s*\*?\*?\(?v?(\d+\.\d+\.\d+)\)?\*?\*?.*$" +) +LATEST_INLINE_RELEASES = 12 +# Share of the page budget the inlined "latest releases" block may consume, so a +# single oversized rollup release cannot push the index past MAX_PAGE_BYTES. +INDEX_INLINE_BUDGET_RATIO = 0.5 +MAX_RELEASES_PER_PAGE = 40 +MAX_PAGE_BYTES = 250 * 1024 +EARLIER_MINOR_CUTOFF = 215 +GENERATED_BANNER = ( + "" +) + + +@dataclass(frozen=True) +class ReleaseSection: + """A single release notes version section.""" + + version: str + content: str + source_index: int + + @property + def minor_series(self) -> str: + parts = self.version.split(".") + return f"{parts[0]}.{parts[1]}" + + @property + def patch_number(self) -> int: + return int(self.version.split(".")[2]) + + +@dataclass(frozen=True) +class GeneratedPage: + """A generated Markdown page and its release sections.""" + + filename: str + title: str + description: str + content: str + releases: tuple[ReleaseSection, ...] + permalink: str | None = None + + @property + def size_bytes(self) -> int: + return len(self.content.encode("utf-8")) + + +def read_source(repo_root: Path) -> str: + source_file = repo_root / SOURCE_PATH + return source_file.read_text(encoding="utf-8") + + +def parse_release_notes(source_text: str) -> tuple[str, list[ReleaseSection]]: + source_text = strip_front_matter(source_text) + matches = list(VERSION_HEADING_RE.finditer(source_text)) + if not matches: + raise ValueError("No release version headings were found.") + + intro = source_text[: matches[0].start()].strip() + sections: list[ReleaseSection] = [] + for index, match in enumerate(matches): + section_end = matches[index + 1].start() if index + 1 < len(matches) else len(source_text) + content = source_text[match.start() : section_end].strip() + sections.append( + ReleaseSection( + version=match.group(1), + content=content, + source_index=index, + ) + ) + + return intro, sections + + +def strip_front_matter(source_text: str) -> str: + if not source_text.startswith("---\n"): + return source_text + + front_matter_end = source_text.find("\n---\n", 4) + if front_matter_end == -1: + return source_text + + return source_text[front_matter_end + len("\n---\n") :] + + +def front_matter(title: str, description: str, permalink: str | None = None) -> str: + lines = [ + "---", + f'title: "{escape_yaml_text(title)}"', + f'description: "{escape_yaml_text(description)}"', + 'section: "Reference"', + "layout: page", + ] + if permalink: + lines.append(f"permalink: {permalink}") + lines.append("---") + return "\n".join(lines) + + +def escape_yaml_text(value: str) -> str: + return value.replace("\\", "\\\\").replace('"', '\\"') + + +def make_page_content( + title: str, + description: str, + body: str, + permalink: str | None = None, +) -> str: + return ( + f"{front_matter(title, description, permalink)}\n\n" + f"{GENERATED_BANNER}\n\n" + f"{body.rstrip()}\n" + ) + + +def series_sort_key(series: str) -> tuple[int, int]: + major, minor = series.split(".") + return int(major), int(minor) + + +def version_sort_key(version: str) -> tuple[int, int, int]: + return tuple(int(part) for part in version.split(".")) + + +def is_earlier_release(release: ReleaseSection) -> bool: + return int(release.minor_series.split(".")[1]) <= EARLIER_MINOR_CUTOFF + + +def make_slug(page_name: str) -> str: + return f"/explanation/release-notes/{page_name.removesuffix('.md')}/" + + +def make_range_label(releases: tuple[ReleaseSection, ...]) -> str: + newest = releases[0].version + oldest = releases[-1].version + return newest if newest == oldest else f"{newest} – {oldest}" + + +def escape_liquid(text: str) -> str: + """Neutralize Liquid syntax that appears literally in release note prose. + + Release notes legitimately quote template syntax when describing template + work, for example "`admin_settings.html` `{% block scripts %}`". Jekyll would + otherwise parse that as a Liquid tag and fail the build with + "Unknown tag 'block'". Wrapping the content in a raw block tells Liquid to + emit it verbatim; markdown still renders normally inside a raw block. + + Any literal endraw in the source would close the block early, so it is + defanged first. That has never appeared in practice but the generator should + not be able to emit a broken page. + """ + if "{%" not in text and "{{" not in text: + return text + + safe = text.replace("{% endraw %}", "{% endraw %}") + return "{% raw %}\n" + safe + "\n{% endraw %}" + + +def page_body_for_releases( + heading: str, + releases: tuple[ReleaseSection, ...], + include_back_link: bool = True, +) -> str: + body_parts = [f"# {heading}"] + if include_back_link: + body_parts.append("[Back to release notes index]({{ '/explanation/release_notes/' | relative_url }})") + body_parts.extend(escape_liquid(release.content) for release in releases) + return "\n\n".join(body_parts) + + +def chunk_releases(releases: list[ReleaseSection]) -> list[tuple[ReleaseSection, ...]]: + chunks: list[tuple[ReleaseSection, ...]] = [] + current: list[ReleaseSection] = [] + + for release in releases: + candidate = [*current, release] + candidate_body = "\n\n".join(section.content for section in candidate) + candidate_size = len(candidate_body.encode("utf-8")) + if ( + current + and ( + len(candidate) > MAX_RELEASES_PER_PAGE + or candidate_size > MAX_PAGE_BYTES + ) + ): + chunks.append(tuple(current)) + current = [release] + else: + current = candidate + + if current: + chunks.append(tuple(current)) + + return chunks + + +def build_archive_pages(archive_releases: list[ReleaseSection]) -> list[GeneratedPage]: + grouped: dict[str, list[ReleaseSection]] = {} + earlier_releases: list[ReleaseSection] = [] + + for release in archive_releases: + if is_earlier_release(release): + earlier_releases.append(release) + else: + grouped.setdefault(release.minor_series, []).append(release) + + pages: list[GeneratedPage] = [] + for series in sorted(grouped, key=series_sort_key, reverse=True): + chunks = chunk_releases(grouped[series]) + for chunk_index, chunk in enumerate(chunks, start=1): + range_label = make_range_label(chunk) + if len(chunks) == 1: + filename = f"v{series}.md" + title = f"Release notes {series} series" + else: + filename = f"v{series}-part-{chunk_index}.md" + title = f"Release notes {range_label}" + description = f"SimpleChat release notes for {range_label}." + body = page_body_for_releases(title, chunk) + pages.append( + GeneratedPage( + filename=filename, + title=title, + description=description, + content=make_page_content(title, description, body), + releases=chunk, + ) + ) + + if earlier_releases: + earlier_chunk = tuple(earlier_releases) + range_label = make_range_label(earlier_chunk) + title = "Earlier releases" + description = f"SimpleChat release notes for earlier versions: {range_label}." + body = page_body_for_releases(title, earlier_chunk) + pages.append( + GeneratedPage( + filename="earlier-releases.md", + title=title, + description=description, + content=make_page_content(title, description, body), + releases=earlier_chunk, + ) + ) + + return pages + + +def build_toc_rows( + version_to_page: dict[int, tuple[ReleaseSection, str]], + page_titles: dict[str, str], +) -> list[str]: + rows = [ + "| Version | Page |", + "| --- | --- |", + ] + for source_index in sorted(version_to_page): + release, filename = version_to_page[source_index] + if filename == "index.md": + page_link = "{{ '/explanation/release_notes/' | relative_url }}" + page_title = "Release notes index" + else: + page_link = f"{{{{ '{make_slug(filename)}' | relative_url }}}}" + page_title = page_titles[filename] + rows.append(f"| v{release.version} | [{page_title}]({page_link}) |") + return rows + + +def select_inline_releases(sections: list[ReleaseSection]) -> int: + """Choose how many of the newest releases to inline on the index page. + + A fixed count is not safe. A consolidated rollup release can be very large on + its own -- v0.260.001 consolidated 117 incremental releases into one section + of roughly 120 KB -- so a fixed 12 would push the index far past the page + budget. Accumulate newest-first while staying inside a share of the budget, + always keeping at least the newest release so the index is never empty. + """ + budget = int(MAX_PAGE_BYTES * INDEX_INLINE_BUDGET_RATIO) + total = 0 + count = 0 + + for release in sections[:LATEST_INLINE_RELEASES]: + size = len(release.content.encode("utf-8")) + if count > 0 and total + size > budget: + break + total += size + count += 1 + + return max(count, 1) + + +def build_pages(source_text: str) -> list[GeneratedPage]: + intro, sections = parse_release_notes(source_text) + inline_count = select_inline_releases(sections) + latest_releases = tuple(sections[:inline_count]) + archive_releases = sections[inline_count:] + archive_pages = build_archive_pages(archive_releases) + + version_to_page: dict[int, tuple[ReleaseSection, str]] = {} + for release in latest_releases: + version_to_page[release.source_index] = (release, "index.md") + for page in archive_pages: + for release in page.releases: + version_to_page[release.source_index] = (release, page.filename) + page_titles = {page.filename: page.title for page in archive_pages} + + title = "Release notes" + description = "SimpleChat release notes index with latest updates and links to archived release pages." + latest_body = page_body_for_releases("Latest release notes", latest_releases, include_back_link=False) + latest_body = latest_body.replace("# Latest release notes", "## Latest release notes", 1) + toc = "\n".join(build_toc_rows(version_to_page, page_titles)) + index_body = "\n\n".join( + [ + "# Release notes", + intro, + ( + "This page includes the latest release notes inline. " + "Older release sections are split into smaller pages by minor series." + ), + "## Version index", + toc, + latest_body, + ] + ) + index_page = GeneratedPage( + filename="index.md", + title=title, + description=description, + content=make_page_content( + title, + description, + index_body, + permalink="/explanation/release_notes/", + ), + releases=latest_releases, + permalink="/explanation/release_notes/", + ) + + pages = [index_page, *archive_pages] + validate_pages(sections, pages) + return pages + + +def validate_pages(source_sections: list[ReleaseSection], pages: list[GeneratedPage]) -> None: + generated_sections = [release for page in pages for release in page.releases] + source_indexes = [release.source_index for release in generated_sections] + expected_indexes = [release.source_index for release in source_sections] + if sorted(source_indexes) != expected_indexes: + raise ValueError("Generated pages do not contain exactly the source release sections.") + + oversized = [page for page in pages if page.size_bytes > MAX_PAGE_BYTES] + if oversized: + details = ", ".join(f"{page.filename} ({page.size_bytes} bytes)" for page in oversized) + raise ValueError(f"Generated page exceeds {MAX_PAGE_BYTES} bytes: {details}") + + +def get_expected_files(pages: list[GeneratedPage], output_dir: Path) -> dict[Path, bytes]: + return { + output_dir / page.filename: page.content.encode("utf-8") + for page in pages + } + + +def check_output(expected_files: dict[Path, bytes], output_dir: Path) -> int: + existing_files = set(output_dir.glob("*.md")) if output_dir.exists() else set() + expected_paths = set(expected_files) + differences: list[str] = [] + + for path in sorted(expected_paths - existing_files): + differences.append(f"missing: {path}") + for path in sorted(existing_files - expected_paths): + differences.append(f"extra: {path}") + for path in sorted(expected_paths & existing_files): + if path.read_bytes() != expected_files[path]: + differences.append(f"different: {path}") + + if differences: + print("Release notes generated output is out of sync:") + for difference in differences: + print(f" {difference}") + return 1 + + print("Release notes generated output is in sync.") + return 0 + + +def write_output(expected_files: dict[Path, bytes], output_dir: Path) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + existing_files = set(output_dir.glob("*.md")) + expected_paths = set(expected_files) + + for stale_file in sorted(existing_files - expected_paths): + stale_file.unlink() + + for path, content in sorted(expected_files.items()): + path.write_bytes(content) + + +def print_summary(pages: list[GeneratedPage]) -> None: + largest_page = max(pages, key=lambda page: page.size_bytes) + total_releases = sum(len(page.releases) for page in pages) + print( + f"Generated {len(pages)} pages with {total_releases} release sections " + f"(max {MAX_RELEASES_PER_PAGE} releases/page, {MAX_PAGE_BYTES} bytes/page)." + ) + for page in pages: + print(f" {page.filename}: {len(page.releases)} releases, {page.size_bytes} bytes") + print(f"Largest page: {largest_page.filename} ({largest_page.size_bytes} bytes)") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Generate split release notes pages for the Jekyll docs site." + ) + parser.add_argument( + "--check", + action="store_true", + help="Exit non-zero if generated release notes pages are out of sync.", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=DEFAULT_OUTPUT_DIR, + help="Directory where generated release notes pages are written.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + repo_root = Path(__file__).resolve().parents[1] + output_dir = args.output_dir + if not output_dir.is_absolute(): + output_dir = repo_root / output_dir + + source_text = read_source(repo_root) + pages = build_pages(source_text) + expected_files = get_expected_files(pages, output_dir) + + if args.check: + return check_output(expected_files, output_dir) + + write_output(expected_files, output_dir) + print_summary(pages) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate_feature_pages.py b/scripts/generate_feature_pages.py new file mode 100644 index 000000000..48b4945de --- /dev/null +++ b/scripts/generate_feature_pages.py @@ -0,0 +1,117 @@ +# generate_feature_pages.py +"""Generate thin Jekyll feature collection pages from docs/_data/features.yml.""" + +import argparse +from pathlib import Path + +import yaml + + +REPO_ROOT = Path(__file__).resolve().parents[1] +FEATURES_DATA_PATH = REPO_ROOT / "docs" / "_data" / "features.yml" +FEATURES_DIR = REPO_ROOT / "docs" / "_features" + + +def load_features(): + """Load the feature catalog data.""" + with FEATURES_DATA_PATH.open("r", encoding="utf-8") as data_file: + features = yaml.safe_load(data_file) or {} + if not isinstance(features, dict): + raise ValueError("docs/_data/features.yml must contain a mapping of feature slugs.") + return features + + +def render_stub(slug, title): + """Render one deterministic feature page stub.""" + escaped_title = str(title).replace("\\", "\\\\").replace('"', '\\"') + return f'---\nslug: {slug}\ntitle: "{escaped_title}"\n---\n' + + +def build_expected_files(features): + """Return expected repo-relative page paths and contents.""" + expected_files = {} + for slug in sorted(features): + entry = features[slug] or {} + title = entry.get("name") or slug.replace("-", " ").title() + expected_files[FEATURES_DIR / f"{slug}.md"] = render_stub(slug, title) + return expected_files + + +def find_existing_stubs(): + """Return existing generated feature stubs.""" + if not FEATURES_DIR.exists(): + return set() + return {path for path in FEATURES_DIR.glob("*.md") if path.is_file()} + + +def check_files(expected_files): + """Return paths that would change if the generator ran.""" + existing_files = find_existing_stubs() + expected_paths = set(expected_files) + changed_paths = [] + + for path, expected_content in expected_files.items(): + if not path.exists() or path.read_text(encoding="utf-8") != expected_content: + changed_paths.append(path) + + changed_paths.extend(sorted(existing_files - expected_paths)) + return sorted(changed_paths) + + +def write_files(expected_files): + """Write expected stubs and delete stale stubs.""" + FEATURES_DIR.mkdir(parents=True, exist_ok=True) + existing_files = find_existing_stubs() + expected_paths = set(expected_files) + written_count = 0 + deleted_count = 0 + + for path, expected_content in expected_files.items(): + if not path.exists() or path.read_text(encoding="utf-8") != expected_content: + path.write_text(expected_content, encoding="utf-8", newline="\n") + written_count += 1 + + for stale_path in sorted(existing_files - expected_paths): + stale_path.unlink() + deleted_count += 1 + + return written_count, deleted_count + + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--check", + action="store_true", + help="Exit non-zero if feature stubs differ from generated output.", + ) + return parser.parse_args() + + +def main(): + """Run the feature page generator.""" + args = parse_args() + features = load_features() + expected_files = build_expected_files(features) + + if args.check: + changed_paths = check_files(expected_files) + if changed_paths: + print("Feature stubs are out of date:") + for path in changed_paths: + print(path.relative_to(REPO_ROOT)) + return 1 + print(f"Feature stubs are up to date: {len(expected_files)} files.") + return 0 + + written_count, deleted_count = write_files(expected_files) + print( + "Generated feature stubs: " + f"{len(expected_files)} expected, {written_count} written, {deleted_count} deleted." + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ui_tests/check_docs_links.js b/ui_tests/check_docs_links.js new file mode 100644 index 000000000..abbdc4a4f --- /dev/null +++ b/ui_tests/check_docs_links.js @@ -0,0 +1,146 @@ +// check_docs_links.js +/** + * Internal link checker for the built documentation site. + * + * Version: 0.250.230 + * Implemented in: 0.250.230 + * + * Walks a built Jekyll site and verifies that every internal href and src + * resolves to a file that exists. Unlike a naive checker, this resolves + * relative links against the containing page, which is where link rot from page + * moves actually shows up. + * + * Usage: + * node ui_tests/check_docs_links.js [--baseurl /simplechat] [--json] + */ + +const fs = require("fs"); +const path = require("path"); + +const siteRoot = path.resolve(process.argv[2] || "docs/_site"); +const baseurlArgIndex = process.argv.indexOf("--baseurl"); +const baseurl = baseurlArgIndex !== -1 ? process.argv[baseurlArgIndex + 1] : "/simplechat"; +const asJson = process.argv.includes("--json"); + +const SKIP_PREFIXES = ["http://", "https://", "mailto:", "data:", "javascript:", "//", "#", "tel:"]; + +// Directories holding sample code rather than documentation navigation. These +// files are published so readers can view and copy them, but their URLs only +// resolve when the sample runs inside SimpleChat: they reference application +// runtime paths such as /custom/assets/... and unrendered template variables +// such as {{ script_url }}. Checking them would report permanent false failures. +const SKIP_DIRECTORIES = ["how-to/custom_pages_examples", "guides/custom_pages_examples"]; + +function isSampleCode(pageUrl) { + return SKIP_DIRECTORIES.some((directory) => pageUrl.includes(`/${directory}/`)); +} + +function walk(directory, collected) { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const full = path.join(directory, entry.name); + if (entry.isDirectory()) { + walk(full, collected); + } else if (entry.name.endsWith(".html")) { + collected.push(full); + } + } + return collected; +} + +function resolveTarget(rawUrl, pageUrl) { + const cleaned = rawUrl.split("#")[0].split("?")[0].trim(); + if (!cleaned) { + return null; + } + + let relative; + if (cleaned.startsWith(`${baseurl}/`)) { + relative = cleaned.slice(baseurl.length + 1); + } else if (cleaned.startsWith("/")) { + relative = cleaned.slice(1); + } else { + const pageDirectory = path.posix.dirname(pageUrl); + relative = path.posix.normalize(path.posix.join(pageDirectory, cleaned)).replace(/^\/+/, ""); + } + + return decodeURIComponent(relative); +} + +function exists(relative) { + const candidate = path.join(siteRoot, relative.split("/").join(path.sep)); + if (fs.existsSync(candidate)) { + return !fs.statSync(candidate).isDirectory() || fs.existsSync(path.join(candidate, "index.html")); + } + return false; +} + +function main() { + if (!fs.existsSync(siteRoot)) { + console.error(`Built site not found at ${siteRoot}. Run: cd docs; bundle exec jekyll build`); + process.exit(2); + } + + const pages = walk(siteRoot, []); + const broken = new Map(); + let linkCount = 0; + let skippedSamples = 0; + + for (const page of pages) { + const html = fs.readFileSync(page, "utf8"); + const pageUrl = `/${path.relative(siteRoot, page).split(path.sep).join("/")}`; + + if (isSampleCode(pageUrl)) { + skippedSamples += 1; + continue; + } + + for (const match of html.matchAll(/(?:href|src)="([^"]+)"/g)) { + const rawUrl = match[1]; + if (SKIP_PREFIXES.some((prefix) => rawUrl.startsWith(prefix))) { + continue; + } + + const target = resolveTarget(rawUrl, pageUrl); + if (target === null) { + continue; + } + + linkCount += 1; + if (!exists(target)) { + if (!broken.has(rawUrl)) { + broken.set(rawUrl, new Set()); + } + broken.get(rawUrl).add(pageUrl); + } + } + } + + if (asJson) { + const payload = {}; + for (const [url, sources] of broken) { + payload[url] = Array.from(sources).sort(); + } + console.log(JSON.stringify({ pages: pages.length, links: linkCount, broken: payload }, null, 2)); + process.exit(broken.size === 0 ? 0 : 1); + } + + console.log(`Pages scanned: ${pages.length}`); + console.log(`Sample pages skipped: ${skippedSamples}`); + console.log(`Internal links: ${linkCount}`); + console.log(`Broken targets: ${broken.size}`); + + if (broken.size > 0) { + console.log(""); + for (const [url, sources] of Array.from(broken).sort()) { + const sourceList = Array.from(sources).sort(); + const shown = sourceList.slice(0, 2).join(", "); + const more = sourceList.length > 2 ? ` (+${sourceList.length - 2} more)` : ""; + console.log(` ${url}`); + console.log(` from: ${shown}${more}`); + } + } + + process.exit(broken.size === 0 ? 0 : 1); +} + +main(); diff --git a/ui_tests/test_docs_media_lightbox_source_validation.js b/ui_tests/test_docs_media_lightbox_source_validation.js new file mode 100644 index 000000000..852fe5f9c --- /dev/null +++ b/ui_tests/test_docs_media_lightbox_source_validation.js @@ -0,0 +1,131 @@ +// test_docs_media_lightbox_source_validation.js +/** + * Regression test for documentation media lightbox source validation. + * + * Version: 0.260.021 + * Implemented in: 0.260.021 + * + * The lightbox assigns an image URL that originates from a data attribute in + * the rendered page. That is DOM text flowing into a URL sink, which CodeQL + * flagged as js/xss-through-dom. The lightbox now resolves and validates the + * value first, requiring a same-origin http(s) URL with an image extension. + * + * This test executes the real safeMediaUrl logic from docs/assets/js/media.js + * against hostile and legitimate inputs, so the guard cannot be silently + * removed or weakened. + * + * Usage: + * node ui_tests/test_docs_media_lightbox_source_validation.js + */ + +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const MEDIA_JS = path.resolve(__dirname, "..", "docs", "assets", "js", "media.js"); + +const ORIGIN = "https://microsoft.github.io"; +const BASE_URI = `${ORIGIN}/simplechat/admin/safety/`; + +const BLOCKED = [ + ["javascript: URL", "javascript:alert(document.domain)"], + ["uppercase javascript URL", "JaVaScRiPt:alert(1)"], + ["data URL carrying markup", "data:text/html,"], + ["data URL image", "data:image/svg+xml;base64,PHN2Zy8+"], + ["off-site absolute URL", "https://evil.example.com/images/x.png"], + ["protocol-relative off-site URL", "//evil.example.com/images/x.png"], + ["same-origin non-image path", "/simplechat/admin/safety/"], + ["traversal to a non-image", "../../../etc/passwd"], + ["empty string", ""], + ["null", null], + ["non-string", 42] +]; + +const ALLOWED = [ + ["absolute site image path", "/simplechat/images/admin-settings/safety.png"], + ["relative image path", "../../images/latest-release/release_260_yamcs_action_1.png"], + ["same-origin absolute URL", `${ORIGIN}/simplechat/images/architecture.png`], + ["uppercase extension", "/simplechat/images/admin-settings/safety.PNG"], + ["jpeg image", "/simplechat/images/foo.jpeg"], + ["webp image", "/simplechat/images/foo.webp"] +]; + +function loadSafeMediaUrl() { + const source = fs.readFileSync(MEDIA_JS, "utf8"); + + const match = source.match(/function safeMediaUrl\(rawSource\) \{[\s\S]*?\n {4}\}/); + if (!match) { + throw new Error( + "safeMediaUrl not found in docs/assets/js/media.js. The lightbox must " + + "validate media URLs before assigning them; see js/xss-through-dom." + ); + } + + const sandbox = { + URL, + document: { baseURI: BASE_URI }, + window: { location: { origin: ORIGIN } } + }; + vm.createContext(sandbox); + vm.runInContext(`${match[0]}\nthis.safeMediaUrl = safeMediaUrl;`, sandbox); + return sandbox.safeMediaUrl; +} + +/** + * Compare origins by parsing, not by string prefix. + * + * A prefix check such as startsWith(ORIGIN) is weak URL matching: a host like + * "microsoft.github.io.example.com" shares the prefix without sharing the + * origin. Parsing and comparing the origin field avoids that class of mistake. + */ +function isSameOrigin(candidate) { + try { + return new URL(candidate).origin === ORIGIN; + } catch (error) { + return false; + } +} + +function main() { + let safeMediaUrl; + try { + safeMediaUrl = loadSafeMediaUrl(); + } catch (error) { + console.error(`FAILED: ${error.message}`); + process.exit(1); + } + + const failures = []; + let passed = 0; + + for (const [label, value] of BLOCKED) { + const result = safeMediaUrl(value); + if (result === null) { + passed += 1; + } else { + failures.push(`should have rejected ${label} (${JSON.stringify(value)}) but returned ${result}`); + } + } + + for (const [label, value] of ALLOWED) { + const result = safeMediaUrl(value); + if (typeof result === "string" && isSameOrigin(result)) { + passed += 1; + } else { + failures.push(`should have accepted ${label} (${JSON.stringify(value)}) but returned ${result}`); + } + } + + console.log(`Media lightbox source validation: ${passed}/${BLOCKED.length + ALLOWED.length} checks passed`); + + if (failures.length > 0) { + console.log("\nFailures:"); + failures.forEach((failure) => console.log(` - ${failure}`)); + process.exit(1); + } + + console.log("All media lightbox source validation checks passed."); + process.exit(0); +} + +main(); diff --git a/ui_tests/test_docs_site_responsive.js b/ui_tests/test_docs_site_responsive.js new file mode 100644 index 000000000..09526121c --- /dev/null +++ b/ui_tests/test_docs_site_responsive.js @@ -0,0 +1,322 @@ +// test_docs_site_responsive.js +/** + * Responsive and search verification for the Simple Chat documentation site. + * + * Version: 0.250.230 + * Implemented in: 0.250.230 + * + * Verifies that the redesigned documentation site works on phone, tablet, and + * desktop viewports, that search is reachable and functional at every size, and + * that wide content such as settings tables does not overflow horizontally. + * + * The site is served from a local static server. Because the site is published + * under the /simplechat baseurl, the server root must expose the built site at + * /simplechat. + * + * Usage: + * cd docs + * bundle exec jekyll build + * node ../ui_tests/test_docs_site_responsive.js [baseUrl] + */ + +const { chromium } = require("playwright"); + +const BASE_URL = process.argv[2] || "http://127.0.0.1:4111/simplechat"; + +const VIEWPORTS = [ + { name: "phone-small", width: 360, height: 640 }, + { name: "phone-large", width: 390, height: 844 }, + { name: "tablet", width: 768, height: 1024 }, + { name: "desktop", width: 1280, height: 800 }, + { name: "wide", width: 1920, height: 1080 } +]; + +const PAGES = [ + { name: "home", path: "/" }, + { name: "admin-index", path: "/admin/" }, + { name: "admin-knowledge", path: "/admin/knowledge/" }, + { name: "release-notes", path: "/explanation/release_notes/" }, + { name: "search", path: "/search/" }, + { name: "media-status", path: "/contributing/media-status/" } +]; + +// Allow a small tolerance for sub-pixel rounding and scrollbar width. +const OVERFLOW_TOLERANCE = 2; + +const failures = []; +const passes = []; + +function record(ok, label, detail) { + if (ok) { + passes.push(label); + } else { + failures.push(`${label}${detail ? ` -- ${detail}` : ""}`); + } +} + +async function checkOverflow(page, viewport, pageName) { + const result = await page.evaluate(() => { + const doc = document.documentElement; + const offenders = []; + document.querySelectorAll("body *").forEach((element) => { + const rect = element.getBoundingClientRect(); + if (rect.width > 0 && rect.right > window.innerWidth + 2) { + offenders.push({ + tag: element.tagName.toLowerCase(), + cls: (element.className || "").toString().slice(0, 60), + right: Math.round(rect.right) + }); + } + }); + return { + scrollWidth: doc.scrollWidth, + innerWidth: window.innerWidth, + offenders: offenders.slice(0, 5) + }; + }); + + const overflow = result.scrollWidth - result.innerWidth; + const ok = overflow <= OVERFLOW_TOLERANCE; + const detail = ok + ? "" + : `scrollWidth ${result.scrollWidth} vs viewport ${result.innerWidth} (+${overflow}px); offenders: ` + + result.offenders.map((o) => `${o.tag}.${o.cls}@${o.right}`).join(", "); + + record(ok, `[${viewport.name}] ${pageName}: no horizontal overflow`, detail); +} + +async function checkSearchReachable(page, viewport, pageName) { + const reachable = await page.evaluate(() => { + const isVisible = (element) => { + if (element === null) { + return false; + } + const style = window.getComputedStyle(element); + if (style.display === "none" || style.visibility === "hidden") { + return false; + } + const rect = element.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }; + + return { + topbar: isVisible(document.getElementById("docs-topbar-search-input")), + mobile: isVisible(document.getElementById("docs-mobile-search-toggle")) + }; + }); + + const ok = reachable.topbar || reachable.mobile; + record( + ok, + `[${viewport.name}] ${pageName}: search is reachable`, + ok ? "" : "neither the top bar search input nor the mobile search toggle is visible" + ); +} + +async function checkNavReachable(page, viewport, pageName) { + const reachable = await page.evaluate(() => { + // offsetParent is null for position: fixed elements, so it cannot be + // used to test visibility of the fixed sidebar. Measure geometry and + // computed style instead. + const isVisible = (element) => { + if (element === null) { + return false; + } + const style = window.getComputedStyle(element); + if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") { + return false; + } + const rect = element.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0 && rect.right > 0 && rect.left < window.innerWidth; + }; + + return { + sidebar: isVisible(document.getElementById("sidebar-nav")), + toggle: isVisible(document.getElementById("docs-mobile-menu-toggle")) + }; + }); + + const ok = reachable.sidebar || reachable.toggle; + record( + ok, + `[${viewport.name}] ${pageName}: navigation is reachable`, + ok ? "" : "neither the sidebar nor the mobile menu toggle is visible" + ); +} + +async function checkSearchWorks(browser) { + const context = await browser.newContext({ viewport: { width: 1280, height: 800 } }); + const page = await context.newPage(); + + await page.goto(`${BASE_URL}/search/?q=file%20sync`, { waitUntil: "networkidle" }); + await page.waitForTimeout(900); + + const results = await page.evaluate(() => { + const nodes = document.querySelectorAll(".docs-search-page-results .docs-search-result"); + return Array.from(nodes).slice(0, 5).map((node) => { + const title = node.querySelector(".docs-search-result-title"); + return title ? title.textContent.trim() : ""; + }); + }); + + record(results.length > 0, "search page returns results for 'file sync'", + results.length > 0 ? "" : "no results rendered"); + + if (results.length > 0) { + const relevant = results.some((title) => title.toLowerCase().includes("file sync")); + record(relevant, "top search results are relevant to 'file sync'", + relevant ? "" : `got: ${results.join(" | ")}`); + console.log(` search results: ${results.join(" | ")}`); + } + + const highlighted = await page.evaluate(() => document.querySelectorAll(".docs-search-result mark").length); + record(highlighted > 0, "search results highlight matched terms", + highlighted > 0 ? "" : "no elements found"); + + await context.close(); +} + +async function checkMobileSearchSheet(browser) { + const context = await browser.newContext({ viewport: { width: 390, height: 844 } }); + const page = await context.newPage(); + + await page.goto(`${BASE_URL}/`, { waitUntil: "networkidle" }); + + const toggle = await page.$("#docs-mobile-search-toggle"); + record(toggle !== null, "mobile search toggle exists", toggle ? "" : "#docs-mobile-search-toggle not found"); + + if (toggle) { + await toggle.click(); + await page.waitForTimeout(400); + + const sheetOpen = await page.evaluate(() => { + const sheet = document.getElementById("docs-search-sheet"); + return sheet !== null && sheet.classList.contains("is-open") && !sheet.classList.contains("d-none"); + }); + record(sheetOpen, "mobile search sheet opens", sheetOpen ? "" : "sheet did not open"); + + if (sheetOpen) { + await page.fill("#docs-search-sheet-input", "terraform"); + await page.waitForTimeout(900); + + const count = await page.evaluate( + () => document.querySelectorAll("#docs-search-sheet .docs-search-result").length + ); + record(count > 0, "mobile search sheet returns results", count > 0 ? "" : "no results in sheet"); + } + } + + await context.close(); +} + +async function checkMediaPlaceholders(browser) { + const context = await browser.newContext({ viewport: { width: 1280, height: 800 } }); + const page = await context.newPage(); + + // Use a group page that still has unfilled media slots, so the placeholder + // rendering path is genuinely exercised. + await page.goto(`${BASE_URL}/admin/backup-recovery/`, { waitUntil: "domcontentloaded" }); + + const counts = await page.evaluate(() => ({ + placeholders: document.querySelectorAll(".docs-media--placeholder").length, + videoPlanned: document.querySelectorAll(".docs-media--video-planned").length, + pathHints: Array.from(document.querySelectorAll(".docs-media-placeholder-path code")) + .map((node) => node.textContent.trim()) + .slice(0, 3) + })); + + record(counts.placeholders > 0, "media placeholders render on admin pages", + counts.placeholders > 0 ? "" : "no placeholder cards found"); + record(counts.videoPlanned > 0, "video placeholder renders its planned state", + counts.videoPlanned > 0 ? "" : "no video-planned card found"); + record(counts.pathHints.length > 0, "placeholder names the exact file to create", + counts.pathHints.length > 0 ? "" : "no expected-path hint rendered"); + + if (counts.pathHints.length > 0) { + console.log(` expected asset paths: ${counts.pathHints.join(", ")}`); + } + + await context.close(); +} + +async function checkNoCdnAssets(browser) { + const context = await browser.newContext({ viewport: { width: 1280, height: 800 } }); + const page = await context.newPage(); + + const external = []; + page.on("request", (request) => { + const url = request.url(); + if (!url.startsWith(BASE_URL) && !url.startsWith("data:") && !url.startsWith("http://127.0.0.1")) { + external.push(url); + } + }); + + await page.goto(`${BASE_URL}/`, { waitUntil: "networkidle" }); + + const scriptAndStyle = external.filter((url) => /\.js(\?|$)|\.css(\?|$)|fonts\.googleapis|fonts\.gstatic/.test(url)); + record(scriptAndStyle.length === 0, "no external script or style requests", + scriptAndStyle.length === 0 ? "" : scriptAndStyle.join(", ")); + + if (external.length > 0) { + console.log(` other external requests: ${external.slice(0, 5).join(", ")}`); + } + + await context.close(); +} + +async function main() { + const browser = await chromium.launch(); + + console.log(`Testing documentation site at ${BASE_URL}\n`); + + for (const viewport of VIEWPORTS) { + console.log(`Viewport ${viewport.name} (${viewport.width}x${viewport.height})`); + const context = await browser.newContext({ + viewport: { width: viewport.width, height: viewport.height } + }); + const page = await context.newPage(); + + for (const target of PAGES) { + const response = await page.goto(`${BASE_URL}${target.path}`, { waitUntil: "domcontentloaded" }); + + if (!response || !response.ok()) { + record(false, `[${viewport.name}] ${target.name}: page loads`, + `status ${response ? response.status() : "no response"} for ${target.path}`); + continue; + } + + await page.waitForTimeout(150); + await checkOverflow(page, viewport, target.name); + await checkSearchReachable(page, viewport, target.name); + await checkNavReachable(page, viewport, target.name); + } + + await context.close(); + } + + console.log("\nFunctional checks"); + await checkSearchWorks(browser); + await checkMobileSearchSheet(browser); + await checkMediaPlaceholders(browser); + await checkNoCdnAssets(browser); + + await browser.close(); + + console.log(`\n${"=".repeat(60)}`); + console.log(`Passed: ${passes.length}`); + console.log(`Failed: ${failures.length}`); + + if (failures.length > 0) { + console.log("\nFailures:"); + failures.forEach((failure) => console.log(` - ${failure}`)); + process.exit(1); + } + + console.log("\nAll documentation site checks passed."); + process.exit(0); +} + +main().catch((error) => { + console.error("Test run failed:", error); + process.exit(1); +});