Add per-message export feature for Markdown and Word formats#783
Add per-message export feature for Markdown and Word formats#783eldong wants to merge 17 commits intoDevelopmentfrom
Conversation
eldong
commented
Mar 9, 2026
- Implemented export options in the dropdown menu for individual chat messages.
- Added backend endpoint for exporting messages as Word documents.
- Created client-side functions for exporting messages to Markdown and Word.
- Updated release notes and documentation to reflect new features.
- Implemented export options in the dropdown menu for individual chat messages. - Added backend endpoint for exporting messages as Word documents. - Created client-side functions for exporting messages to Markdown and Word. - Updated release notes and documentation to reflect new features.
There was a problem hiding this comment.
Pull request overview
Adds per-message actions to the chat message “three dots” menu, enabling single-message export to Markdown (client-side) and Word (via a new backend endpoint), plus quick “Use as Prompt” and “Open in Email” actions.
Changes:
- Adds new per-message dropdown actions for both AI and user messages in the chat UI.
- Introduces
POST /api/message/export-wordto generate a.docxexport for a single message. - Adds feature documentation and release note entry; bumps app version to
0.239.007.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/explanation/release_notes.md | Adds release note entry for per-message export feature in v0.239.007. |
| docs/explanation/features/MESSAGE_EXPORT.md | New feature documentation describing per-message export options and architecture. |
| application/single_app/static/js/chat/chat-messages.js | Adds dropdown items + click handlers (dynamic import) for new per-message actions. |
| application/single_app/static/js/chat/chat-message-export.js | New client-side module implementing Markdown export, Word export call, prompt insertion, and mailto. |
| application/single_app/route_backend_conversation_export.py | Adds /api/message/export-word endpoint and basic Markdown→DOCX formatting helpers. |
| application/single_app/config.py | Bumps VERSION to 0.239.007. |
You can also share your feedback on Copilot code review. Take the survey.
| except Exception as e: | ||
| debug_print(f"Message export error: {str(e)}") | ||
| return jsonify({'error': f'Export failed: {str(e)}'}), 500 |
There was a problem hiding this comment.
The error response includes the raw exception string (Export failed: {str(e)}), which can leak internal details to the client. Consider logging the exception server-side and returning a generic message (optionally with a correlation id) to the caller.
| ## Overview | ||
| The Per-Message Export feature adds export and action options directly to the three-dots dropdown menu on individual chat messages. Users can export a single message to Markdown or Word, insert it into the chat prompt, or open it in their default email client — all without leaving the chat interface. | ||
|
|
||
| **Version Implemented:** 0.239.005–0.239.007 |
There was a problem hiding this comment.
The version metadata here is a range (0.239.005–0.239.007), but the feature is being released as part of v0.239.007 (and config.py was bumped to that). To keep docs unambiguous, please switch this to a single implemented version (e.g., 0.239.007) and align it with the release notes entry.
| **Version Implemented:** 0.239.005–0.239.007 | |
| **Version Implemented:** 0.239.007 |
(Pt, RGBColor, WD_ALIGN_PARAGRAPH, and _re) are not sued so removed them Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
You can also share your feedback on Copilot code review. Take the survey.
| except Exception as e: | ||
| debug_print(f"Message export error: {str(e)}") | ||
| return jsonify({'error': f'Export failed: {str(e)}'}), 500 |
There was a problem hiding this comment.
The 500 error response includes the raw exception text (Export failed: {str(e)}), which can leak internal details to the browser. Log the exception server-side and return a generic error message to the client instead.
| @app.route('/api/message/export-word', methods=['POST']) | ||
| @swagger_route(security=get_auth_security()) | ||
| @login_required | ||
| @user_required | ||
| def api_export_message_word(): | ||
| """ |
There was a problem hiding this comment.
This PR adds a new user-facing export endpoint (/api/message/export-word) and Markdown-to-DOCX conversion logic, but there’s no corresponding functional test alongside the existing functional_tests/test_conversation_export.py coverage. Please add a functional test (e.g., functional_tests/test_message_export.py) that validates permission checks (403), not-found cases (404), and successful .docx generation for a simple message.
| **Version Implemented:** 0.239.005–0.239.007 | ||
|
|
There was a problem hiding this comment.
For consistency with other feature docs (and the version bump/release note entry in this PR), the feature should list a single implemented version. Consider changing 0.239.005–0.239.007 to 0.239.007 so it’s clear which release introduced the feature.
| - **Icon:** `bi-markdown` | ||
| - **Behavior:** Entirely client-side. Grabs the message content from the existing hidden textarea (AI messages) or `.message-text` element (user messages), wraps it with a role/timestamp header, and triggers a `.md` file download via Blob URL. | ||
| - **Filename pattern:** `message_export_YYYYMMDD_HHMMSS.md` |
There was a problem hiding this comment.
The doc says Markdown export includes a role/timestamp header, but the current frontend implementation doesn’t have access to a rendered timestamp (no .message-timestamp in message DOM), so the timestamp will be blank. Either adjust the implementation to reliably include timestamps or update this doc to reflect actual behavior.
Co-authored-by: eldong <11573590+eldong@users.noreply.github.com>
Move `python-docx` import to top-level in route_backend_conversation_export.py
Co-authored-by: eldong <11573590+eldong@users.noreply.github.com>
Fix misleading JSDoc comment on `copyAsPrompt()` to match actual behavior
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
You can also share your feedback on Copilot code review. Take the survey.
| function getMessageMarkdown(messageDiv, role) { | ||
| if (role === 'assistant') { | ||
| // AI messages have a hidden textarea with the markdown content | ||
| const hiddenTextarea = messageDiv.querySelector('textarea[id^="copy-md-"]'); | ||
| if (hiddenTextarea) { | ||
| return hiddenTextarea.value; | ||
| } | ||
| } | ||
| // For user messages (or fallback), grab the text from the message bubble | ||
| const messageText = messageDiv.querySelector('.message-text'); | ||
| if (messageText) { | ||
| return messageText.innerText; | ||
| } | ||
| return ''; | ||
| } |
There was a problem hiding this comment.
This new module uses 4-space indentation, but the surrounding JS code in this repo (e.g., chat-toast.js and chat-messages.js) consistently uses 2 spaces. For consistency/readability, please reformat this file to match the existing indentation style used in the static/js codebase.
| ### **(v0.239.007)** | ||
|
|
||
| #### New Features | ||
|
|
||
| * **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 role/timestamp 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) |
There was a problem hiding this comment.
Release notes entry is under v0.239.007, but this PR bumps config VERSION to 0.239.009. Please align the release notes section header to the version being released (and keep version numbers consistent across config + release notes).
application/single_app/config.py
Outdated
| EXECUTOR_MAX_WORKERS = 30 | ||
| SESSION_TYPE = 'filesystem' | ||
| VERSION = "0.239.005" | ||
| VERSION = "0.239.009" |
There was a problem hiding this comment.
VERSION was bumped from 0.239.005 to 0.239.009 in a single PR. Repo versioning guidance is to increment only the patch (third) component by 1 per change; please adjust the version accordingly and ensure it matches the release notes section header.
| VERSION = "0.239.009" | |
| VERSION = "0.239.006" |
…ownership failures) Co-authored-by: eldong <11573590+eldong@users.noreply.github.com>
[WIP] Add functional test script for export feature
Co-authored-by: eldong <11573590+eldong@users.noreply.github.com>
Remove false timestamp claims from per-message export feature
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
You can also share your feedback on Copilot code review. Take the survey.
| def _build_markdown_export(role, content, sender, timestamp): | ||
| """Replicate the client-side Markdown export logic from chat-message-export.js.""" | ||
| lines = [] | ||
| lines.append(f"### {sender}") | ||
| if timestamp: | ||
| lines.append(f"*{timestamp}*") | ||
| lines.append('') | ||
| lines.append(content) | ||
| lines.append('') | ||
| return '\n'.join(lines) |
There was a problem hiding this comment.
The Markdown export helper/test currently expects a timestamp line (and test_happy_path_markdown_export asserts it), but the actual client implementation in chat-message-export.js does not include timestamps in Markdown exports (only a role header + content). This makes the functional test incorrect; either update the test/helper to match the shipped behavior, or add timestamp support in the client export and update docs/release notes accordingly.
| ## Cross-References | ||
| - Related feature: [Conversation Export](CONVERSATION_EXPORT.md) — exports entire conversations | ||
| - Backend shares infrastructure with conversation export (`route_backend_conversation_export.py`) | ||
| - Functional tests: `functional_tests/test_message_export.py` (when created) |
There was a problem hiding this comment.
The cross-reference to functional_tests/test_message_export.py appears to be stale—this PR adds functional_tests/test_per_message_export.py. Update this link so readers can find the actual functional test script.
| - Functional tests: `functional_tests/test_message_export.py` (when created) | |
| - Functional tests: `functional_tests/test_per_message_export.py` |
| ### **(v0.239.007)** | ||
|
|
||
| #### New Features | ||
|
|
||
| * **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) |
There was a problem hiding this comment.
Release notes add Per-Message Export under v0.239.007, but this PR also bumps VERSION in config.py to 0.239.010. Please align the release notes section with the actual version being released (either adjust the version bump, or move/add the entry under the correct version heading).
| except Exception as e: | ||
| debug_print(f"Message export error: {str(e)}") | ||
| return jsonify({'error': f'Export failed: {str(e)}'}), 500 |
There was a problem hiding this comment.
The 500 handler returns the raw exception string to the client (Export failed: {str(e)}), which can leak internal details (Cosmos errors, stack context, etc.). Consider logging the exception server-side and returning a generic error message to the browser while keeping the status code 500.
| Version: 0.239.008 | ||
| Implemented in: 0.239.008 |
There was a problem hiding this comment.
Functional test header versions are out of sync with the current app version in application/single_app/config.py (currently 0.239.010). Per the repo’s functional test versioning convention, update the Version: field to the current config version and set Implemented in: to the version where this feature is introduced in this PR.
docs: remove overstated timestamp claim from per-message Markdown export release note