Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds a conversation export feature that allows users to export their conversations in JSON or Markdown format, with options for single-file or ZIP packaging. The export wizard provides a multi-step interface for selecting conversations, choosing formats, and downloading the results.
However, the PR contains significant unrelated changes including:
- Retention policy settings UI for public and group workspaces
- MIME type registration for font files
- Custom logo image files
Changes:
- Added conversation export backend endpoint with sanitization and active thread filtering
- Implemented multi-step export wizard with Bootstrap modal UI
- Added export buttons to conversation lists and sidebar with selection mode support
- Included functional tests validating export logic and feature documentation
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| functional_tests/test_conversation_export.py | Comprehensive tests for export sanitization, markdown generation, ZIP packaging, and active thread filtering |
| docs/explanation/features/CONVERSATION_EXPORT.md | Complete feature documentation covering architecture, usage, and security |
| application/single_app/route_backend_conversation_export.py | Backend API endpoint with conversation/message sanitization and format conversion |
| application/single_app/app.py | Route registration for export endpoint |
| application/single_app/templates/chats.html | Export wizard modal and button integration |
| application/single_app/templates/_sidebar_nav.html | Export button in sidebar with compact styling |
| application/single_app/templates/_sidebar_short_nav.html | Export button in short sidebar variant |
| application/single_app/static/js/chat/chat-export.js | Export wizard module with multi-step UI logic |
| application/single_app/static/js/chat/chat-conversations.js | Export button integration in main conversations list |
| application/single_app/static/js/chat/chat-sidebar-conversations.js | Export menu item and button handlers in sidebar |
| application/single_app/static/css/sidebar.css | Compact button styling for selection mode actions |
| application/single_app/templates/public_workspaces.html | UNRELATED: Retention policy settings tab |
| application/single_app/templates/group_workspaces.html | UNRELATED: Retention policy settings tab |
| application/single_app/static/js/public/public_workspace.js | UNRELATED: Retention policy functions + broken import |
| application/single_app/config.py | UNRELATED: MIME type registration for fonts |
| application/single_app/static/images/custom_logo.png | UNRELATED: Custom logo image |
| application/single_app/static/images/custom_logo_dark.png | UNRELATED: Custom logo dark mode image |
| message_query = f""" | ||
| SELECT * FROM c | ||
| WHERE c.conversation_id = '{conv_id}' | ||
| ORDER BY c.timestamp ASC | ||
| """ | ||
| messages = list(cosmos_messages_container.query_items( | ||
| query=message_query, |
There was a problem hiding this comment.
While not a critical security issue (since the conversation ID is validated against the database first), this query should use parameterized queries following the pattern seen elsewhere in the codebase (e.g., functions_retention_policy.py:607-612). Use WHERE c.conversation_id = @conversation_id with a parameters array like [{"name": "@conversation_id", "value": conv_id}] passed to query_items.
| message_query = f""" | |
| SELECT * FROM c | |
| WHERE c.conversation_id = '{conv_id}' | |
| ORDER BY c.timestamp ASC | |
| """ | |
| messages = list(cosmos_messages_container.query_items( | |
| query=message_query, | |
| message_query = """ | |
| SELECT * FROM c | |
| WHERE c.conversation_id = @conversation_id | |
| ORDER BY c.timestamp ASC | |
| """ | |
| messages = list(cosmos_messages_container.query_items( | |
| query=message_query, | |
| parameters=[{"name": "@conversation_id", "value": conv_id}], |
| @@ -260,6 +265,73 @@ <h5>Public Prompts</h5> | |||
| <div class="d-flex align-items-center gap-3 mb-3 mt-3"><div><label class="visually-hidden">Items per page:</label><select id="public-prompts-page-size-select" class="form-select form-select-sm d-inline-block" style="width:auto;"><option value="10" selected>10</option><option value="20">20</option><option value="50">50</option></select><span class="ms-1 small text-muted">items per page</span></div><div id="public-prompts-pagination-container" class="d-flex gap-1 ms-auto"></div></div> | |||
| </div> | |||
| </div> | |||
|
|
|||
| {% if app_settings.enable_retention_policy_public %} | |||
| <!-- Settings Tab --> | |||
| <div class="tab-pane fade" id="public-settings-tab" role="tabpanel"> | |||
| <div class="card p-4 my-3"> | |||
| <h5 class="mb-3"><i class="bi bi-hourglass-split me-2"></i>Retention Policy Settings</h5> | |||
| <p class="text-muted">Configure how long to keep conversations and documents in this public workspace. Items older than the specified period will be automatically deleted.</p> | |||
|
|
|||
| <div class="alert alert-info"> | |||
| <i class="bi bi-info-circle me-2"></i> | |||
| <strong>Default:</strong> You can use the organization default or set a custom retention period. Choose "No automatic deletion" to keep items indefinitely. | |||
| </div> | |||
|
|
|||
| <div class="row g-3"> | |||
| <div class="col-md-6"> | |||
| <label for="public-conversation-retention-days" class="form-label">Conversation Retention</label> | |||
| <select class="form-select" id="public-conversation-retention-days" name="conversation_retention_days"> | |||
| <option value="default">Using organization default</option> | |||
| <option value="none">No automatic deletion</option> | |||
| <option value="7">7 days (1 week)</option> | |||
| <option value="14">14 days (2 weeks)</option> | |||
| <option value="30">30 days (1 month)</option> | |||
| <option value="60">60 days (2 months)</option> | |||
| <option value="90">90 days (3 months)</option> | |||
| <option value="180">180 days (6 months)</option> | |||
| <option value="365">365 days (1 year)</option> | |||
| <option value="730">730 days (2 years)</option> | |||
| <option value="1095">1095 days (3 years)</option> | |||
| <option value="3650">3650 days (10 years)</option> | |||
| </select> | |||
| <small class="form-text text-muted">Conversations older than this will be automatically deleted.</small> | |||
| </div> | |||
|
|
|||
| <div class="col-md-6"> | |||
| <label for="public-document-retention-days" class="form-label">Document Retention</label> | |||
| <select class="form-select" id="public-document-retention-days" name="document_retention_days"> | |||
| <option value="default">Using organization default</option> | |||
| <option value="none">No automatic deletion</option> | |||
| <option value="7">7 days (1 week)</option> | |||
| <option value="14">14 days (2 weeks)</option> | |||
| <option value="30">30 days (1 month)</option> | |||
| <option value="60">60 days (2 months)</option> | |||
| <option value="90">90 days (3 months)</option> | |||
| <option value="180">180 days (6 months)</option> | |||
| <option value="365">365 days (1 year)</option> | |||
| <option value="730">730 days (2 years)</option> | |||
| <option value="1095">1095 days (3 years)</option> | |||
| <option value="3650">3650 days (10 years)</option> | |||
| </select> | |||
| <small class="form-text text-muted">Documents older than this will be automatically deleted.</small> | |||
| </div> | |||
| </div> | |||
|
|
|||
| <div class="mt-3"> | |||
| <button type="button" class="btn btn-primary" onclick="savePublicRetentionSettings()"> | |||
| <i class="bi bi-save me-1"></i>Save Retention Settings | |||
| </button> | |||
| <span id="public-retention-save-status" class="ms-3"></span> | |||
| </div> | |||
|
|
|||
| <div class="alert alert-warning mt-3 mb-0"> | |||
| <i class="bi bi-exclamation-triangle me-2"></i> | |||
| <strong>Important:</strong> Deleted conversations will be archived if archiving is enabled. All deletions are logged in activity history. | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {% endif %} | |||
There was a problem hiding this comment.
This code appears to be unrelated to the conversation export feature described in the PR title. These retention policy settings for public workspaces should be in a separate PR focused on retention policy functionality, not mixed with the conversation export feature.
| @@ -735,6 +751,79 @@ <h5 class="mb-0 d-flex align-items-center gap-2">Group Actions</h5> | |||
| </div> | |||
| <!-- End GROUP ACTIONS TAB --> | |||
| {% endif %} | |||
|
|
|||
| {% if app_settings.enable_retention_policy_group %} | |||
| <!-- ============= GROUP SETTINGS TAB ============= --> | |||
| <div | |||
| class="tab-pane fade" | |||
| id="group-settings-tab" | |||
| role="tabpanel" | |||
| aria-labelledby="group-settings-tab-btn" | |||
| > | |||
| <div class="card p-4 my-3"> | |||
| <h5 class="mb-3"><i class="bi bi-hourglass-split me-2"></i>Retention Policy Settings</h5> | |||
| <p class="text-muted">Configure how long to keep conversations and documents in this group workspace. Items older than the specified period will be automatically deleted.</p> | |||
|
|
|||
| <div class="alert alert-info"> | |||
| <i class="bi bi-info-circle me-2"></i> | |||
| <strong>Default:</strong> You can use the organization default or set a custom retention period. Choose "No automatic deletion" to keep items indefinitely. | |||
| </div> | |||
|
|
|||
| <div class="row g-3"> | |||
| <div class="col-md-6"> | |||
| <label for="group-conversation-retention-days" class="form-label">Conversation Retention</label> | |||
| <select class="form-select" id="group-conversation-retention-days" name="conversation_retention_days"> | |||
| <option value="default">Using organization default</option> | |||
| <option value="none">No automatic deletion</option> | |||
| <option value="7">7 days (1 week)</option> | |||
| <option value="14">14 days (2 weeks)</option> | |||
| <option value="30">30 days (1 month)</option> | |||
| <option value="60">60 days (2 months)</option> | |||
| <option value="90">90 days (3 months)</option> | |||
| <option value="180">180 days (6 months)</option> | |||
| <option value="365">365 days (1 year)</option> | |||
| <option value="730">730 days (2 years)</option> | |||
| <option value="1095">1095 days (3 years)</option> | |||
| <option value="3650">3650 days (10 years)</option> | |||
| </select> | |||
| <small class="form-text text-muted">Conversations older than this will be automatically deleted.</small> | |||
| </div> | |||
|
|
|||
| <div class="col-md-6"> | |||
| <label for="group-document-retention-days" class="form-label">Document Retention</label> | |||
| <select class="form-select" id="group-document-retention-days" name="document_retention_days"> | |||
| <option value="default">Using organization default</option> | |||
| <option value="none">No automatic deletion</option> | |||
| <option value="7">7 days (1 week)</option> | |||
| <option value="14">14 days (2 weeks)</option> | |||
| <option value="30">30 days (1 month)</option> | |||
| <option value="60">60 days (2 months)</option> | |||
| <option value="90">90 days (3 months)</option> | |||
| <option value="180">180 days (6 months)</option> | |||
| <option value="365">365 days (1 year)</option> | |||
| <option value="730">730 days (2 years)</option> | |||
| <option value="1095">1095 days (3 years)</option> | |||
| <option value="3650">3650 days (10 years)</option> | |||
| </select> | |||
| <small class="form-text text-muted">Documents older than this will be automatically deleted.</small> | |||
| </div> | |||
| </div> | |||
|
|
|||
| <div class="mt-3"> | |||
| <button type="button" class="btn btn-primary" onclick="saveGroupRetentionSettings()"> | |||
| <i class="bi bi-save me-1"></i>Save Retention Settings | |||
| </button> | |||
| <span id="group-retention-save-status" class="ms-3"></span> | |||
| </div> | |||
|
|
|||
| <div class="alert alert-warning mt-3 mb-0"> | |||
| <i class="bi bi-exclamation-triangle me-2"></i> | |||
| <strong>Important:</strong> Deleted conversations will be archived if archiving is enabled. All deletions are logged in activity history. | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <!-- End GROUP SETTINGS TAB --> | |||
| {% endif %} | |||
There was a problem hiding this comment.
This code appears to be unrelated to the conversation export feature described in the PR title. These retention policy settings for group workspaces should be in a separate PR focused on retention policy functionality, not mixed with the conversation export feature.
| # Register font MIME types so Flask serves them correctly (required for | ||
| # X-Content-Type-Options: nosniff to not block Bootstrap Icons) | ||
| mimetypes.add_type('font/woff', '.woff') | ||
| mimetypes.add_type('font/woff2', '.woff2') | ||
| mimetypes.add_type('font/ttf', '.ttf') | ||
| mimetypes.add_type('font/otf', '.otf') |
There was a problem hiding this comment.
This MIME type registration code appears to be unrelated to the conversation export feature. The comment mentions "required for X-Content-Type-Options: nosniff to not block Bootstrap Icons", which is a general fix for font serving, not specific to conversation export. This should be in a separate PR or at least clearly documented as a related fix.
paullizer
left a comment
There was a problem hiding this comment.
please update release_notes.md with the feature
There was a problem hiding this comment.
this is great! please also update the release_notes.md
|
I updated the release notes to include the Export Conversations and Retention policy UI features. |
| @@ -1,4 +1,6 @@ | |||
| // static/js/public_workspace.js | |||
| import { showToast } from "./chat/chat-toast.js"; | |||
There was a problem hiding this comment.
The import path is incorrect. The file public_workspace.js is located at static/js/public/, but it's trying to import from ./chat/chat-toast.js, which resolves to static/js/public/chat/chat-toast.js (which doesn't exist).
The correct relative import path should be ../chat/chat-toast.js to go up one directory level from public/ to js/ and then into chat/.
| import { showToast } from "./chat/chat-toast.js"; | |
| import { showToast } from "../chat/chat-toast.js"; |
| Version: 0.237.050 | ||
| Implemented in: 0.237.050 |
There was a problem hiding this comment.
Version mismatch: The functional test specifies version 0.237.050, but the current version in config.py is 0.238.024. According to the coding guideline for version management (CodingGuidelineID: 1000002), functional tests must include the current version from config.py.
Update both the header comment version references to match 0.238.024.
| ## Overview | ||
| The Conversation Export feature allows users to export one or multiple conversations directly from the Chats experience. A multi-step wizard modal guides users through format selection, output packaging, and downloading the final file. | ||
|
|
||
| **Version Implemented:** 0.237.050 |
There was a problem hiding this comment.
Version mismatch: The feature documentation specifies version 0.237.050, but the current version in config.py is 0.238.024. According to the coding guideline for version management (CodingGuidelineID: 1000002), feature documentation must include the current version from config.py.
Update the version reference to match 0.238.024.
No description provided.