added feature: Export Chat Transcript (Download as Markdown / JSON) - #114
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
👋 Thanks for opening a PR, @akash3911!Your PR has entered the 🚦 PR Review Pipeline.
What happens next
A pipeline status comment will appear below and update automatically as your PR progresses. While you wait
This comment is posted only once. |
WalkthroughThe PR implements client-side chat history export. A new module provides Markdown and JSON formatters, filename generation, and browser download logic. The chat panel component adds a dropdown menu in the header offering both export formats, with validation and toast feedback for success or errors. ChangesChat Export Implementation
Sequence DiagramsequenceDiagram
participant User
participant AIChatPanel
participant DropdownMenu
participant ExportHandlers
participant ExportUtils
User->>AIChatPanel: clicks Export dropdown
AIChatPanel->>DropdownMenu: renders Markdown/JSON options
User->>DropdownMenu: selects Markdown or JSON
DropdownMenu->>ExportHandlers: triggers handler
ExportHandlers->>ExportHandlers: validate messages exist
ExportHandlers->>ExportUtils: call export function
ExportUtils->>ExportUtils: format and download
ExportHandlers->>User: show success/error toast
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/playground/components/ai-chat-panel.tsx`:
- Around line 361-369: Add explicit aria-label attributes to the icon-only
<Button> elements in ai-chat-panel (the ones using the <Download> icon and the
other icon-only button around lines referenced) so screen readers get a reliable
description instead of relying on title; update the <Button> components that
currently have title="Export chat" (and the other icon-only button) to include a
matching aria-label (e.g., aria-label="Export chat") while preserving
disabled={messages.length === 0}, size="icon", variant="ghost", and existing
className props.
In `@modules/playground/lib/chat-export.ts`:
- Around line 63-64: The exported JSON currently sets messageCount to
messages.length before synthetic entries are removed, causing a mismatch; after
the synthetic-message filtering step (the code that removes synthetic messages
from messages), recompute and set messageCount using the filtered array (e.g.,
use filteredMessages.length or the post-filter messages variable) so the
messageCount and messages fields in the export match; update any other places
that rely on messages.length to use the filtered collection instead.
- Around line 84-91: The map that builds toolInvocations is exporting raw
ti.input (in the toolInvocations map over rawParts), which may contain
hidden/internal payloads; change it to export only a sanitized/visible input
(e.g., use a new helper like sanitizeToolInput(ti) or prefer an explicit field
such as ti.inputVisible or ti.visibleInput) and ensure the sanitizer strips
internal fields before serialization; update the mapping (the toolInvocations
block) to call that sanitizer or select only safe properties (toolName,
toolCallId, state, visibleInput) instead of exporting ti.input verbatim.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0220ae3c-8abe-4600-bd1d-19d741b4f3fe
📒 Files selected for processing (2)
modules/playground/components/ai-chat-panel.tsxmodules/playground/lib/chat-export.ts
| <Button | ||
| size="icon" | ||
| variant="ghost" | ||
| className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors" | ||
| title="Export chat" | ||
| disabled={messages.length === 0} | ||
| > | ||
| <Download className="h-4 w-4" /> | ||
| </Button> |
There was a problem hiding this comment.
Add explicit aria-label to icon-only action buttons.
These controls currently rely on title; screen-reader support for title is inconsistent. Add aria-label for reliable accessibility.
Suggested fix
<Button
size="icon"
variant="ghost"
className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
title="Export chat"
+ aria-label="Export chat"
disabled={messages.length === 0}
>
<Download className="h-4 w-4" />
</Button>
...
- <Button size="icon" variant="ghost" className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors" onClick={clearChat} title="Clear chat">
+ <Button size="icon" variant="ghost" className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors" onClick={clearChat} title="Clear chat" aria-label="Clear chat">
<Trash2 className="h-4 w-4" />
</Button>Also applies to: 380-382
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/playground/components/ai-chat-panel.tsx` around lines 361 - 369, Add
explicit aria-label attributes to the icon-only <Button> elements in
ai-chat-panel (the ones using the <Download> icon and the other icon-only button
around lines referenced) so screen readers get a reliable description instead of
relying on title; update the <Button> components that currently have
title="Export chat" (and the other icon-only button) to include a matching
aria-label (e.g., aria-label="Export chat") while preserving
disabled={messages.length === 0}, size="icon", variant="ghost", and existing
className props.
| messageCount: messages.length, | ||
| messages: messages |
There was a problem hiding this comment.
messageCount can be incorrect after filtering synthetic messages.
Line 63 uses the original array length, but Line 94 removes entries. Metadata can disagree with messages.length in the exported JSON.
Suggested fix
export function exportToJSON(messages: any[]): string {
+ const exportedMessages = messages
+ .map((msg) => {
+ // ...existing mapping logic
+ })
+ .filter(Boolean);
+
const exportData = {
version: "1.0",
exportedAt: new Date().toISOString(),
- messageCount: messages.length,
- messages: messages
- .map((msg) => {
- // ...
- })
- .filter(Boolean),
+ messageCount: exportedMessages.length,
+ messages: exportedMessages,
};Also applies to: 94-94
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/playground/lib/chat-export.ts` around lines 63 - 64, The exported
JSON currently sets messageCount to messages.length before synthetic entries are
removed, causing a mismatch; after the synthetic-message filtering step (the
code that removes synthetic messages from messages), recompute and set
messageCount using the filtered array (e.g., use filteredMessages.length or the
post-filter messages variable) so the messageCount and messages fields in the
export match; update any other places that rely on messages.length to use the
filtered collection instead.
| toolInvocations: rawParts | ||
| .filter((p: any) => p.type?.startsWith("tool-") || p.type === "tool-invocation") | ||
| .map((ti: any) => ({ | ||
| toolName: ti.toolName ?? ti.type?.split("-").slice(1).join("-"), | ||
| toolCallId: ti.toolCallId, | ||
| input: ti.input, | ||
| state: ti.state, | ||
| })), |
There was a problem hiding this comment.
Avoid exporting non-visible tool inputs by default.
Line 89 serializes ti.input, which can include hidden/internal payloads not shown in the transcript UI. That creates a privacy leak risk in exported files.
Suggested fix
return {
id: msg.id,
role: msg.role,
content: textContent,
timestamp: msg.createdAt ? new Date(msg.createdAt).toISOString() : null,
toolInvocations: rawParts
.filter((p: any) => p.type?.startsWith("tool-") || p.type === "tool-invocation")
.map((ti: any) => ({
toolName: ti.toolName ?? ti.type?.split("-").slice(1).join("-"),
toolCallId: ti.toolCallId,
- input: ti.input,
state: ti.state,
})),
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/playground/lib/chat-export.ts` around lines 84 - 91, The map that
builds toolInvocations is exporting raw ti.input (in the toolInvocations map
over rawParts), which may contain hidden/internal payloads; change it to export
only a sanitized/visible input (e.g., use a new helper like
sanitizeToolInput(ti) or prefer an explicit field such as ti.inputVisible or
ti.visibleInput) and ensure the sanitizer strips internal fields before
serialization; update the mapping (the toolInvocations block) to call that
sanitizer or select only safe properties (toolName, toolCallId, state,
visibleInput) instead of exporting ti.input verbatim.
|
@piyushdotcomm can you review the PR |
piyushdotcomm
left a comment
There was a problem hiding this comment.
show me a screenrecording or screenshot
Summary
.md).json)Why it changed
This feature allows users to save, share, and reuse chat conversations outside the application. It improves usability by providing portable conversation exports without requiring backend changes.
Type of change
Related issue
Closes #107
Validation
npm run lintnpm testnpm run buildList any additional manual verification you performed:
Checklist
Summary by CodeRabbit