diff --git a/src/App.tsx b/src/App.tsx
index a44f2cf..73b8d71 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -84,7 +84,7 @@ export default function App() {
onClose={() => setSidebarOpen(false)}
/>
@@ -54,6 +65,16 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
{soundEnabled ? : }
)}
+ {messages && messages.length > 0 && (
+
+ )}
{status === 'connected' ? (
diff --git a/src/lib/exportChat.ts b/src/lib/exportChat.ts
new file mode 100644
index 0000000..3e747f6
--- /dev/null
+++ b/src/lib/exportChat.ts
@@ -0,0 +1,79 @@
+import type { ChatMessage } from '../types';
+
+/**
+ * Convert a list of chat messages into a Markdown string suitable for export.
+ */
+export function messagesToMarkdown(messages: ChatMessage[], sessionLabel?: string): string {
+ const lines: string[] = [];
+
+ if (sessionLabel) {
+ lines.push(`# ${sessionLabel}`, '');
+ }
+
+ const exportDate = new Date().toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
+ lines.push(`> Exported from PinchChat on ${exportDate}`, '');
+
+ for (const msg of messages) {
+ const time = new Date(msg.timestamp).toLocaleString('en-US', {
+ dateStyle: 'medium',
+ timeStyle: 'short',
+ });
+
+ const roleLabel = msg.role === 'user'
+ ? (msg.isSystemEvent ? '⚙️ System Event' : '👤 User')
+ : '🤖 Assistant';
+
+ lines.push(`## ${roleLabel} — ${time}`, '');
+
+ if (msg.blocks && msg.blocks.length > 0) {
+ for (const block of msg.blocks) {
+ switch (block.type) {
+ case 'text':
+ lines.push(block.text, '');
+ break;
+ case 'thinking':
+ lines.push('💭 Thinking
', '', block.text, '', ' ', '');
+ break;
+ case 'tool_use':
+ lines.push(`**🔧 Tool: \`${block.name}\`**`, '');
+ if (block.input && Object.keys(block.input).length > 0) {
+ lines.push('```json', JSON.stringify(block.input, null, 2), '```', '');
+ }
+ break;
+ case 'tool_result':
+ if (block.content) {
+ const label = block.name ? `Result (${block.name})` : 'Result';
+ lines.push(`📋 ${label}
`, '');
+ lines.push('```', block.content.slice(0, 5000), '```', '');
+ lines.push(' ', '');
+ }
+ break;
+ case 'image':
+ lines.push('*[Image]*', '');
+ break;
+ }
+ }
+ } else if (msg.content) {
+ lines.push(msg.content, '');
+ }
+
+ lines.push('---', '');
+ }
+
+ return lines.join('\n');
+}
+
+/**
+ * Trigger a browser file download with the given content.
+ */
+export function downloadFile(content: string, filename: string, mimeType = 'text/markdown') {
+ const blob = new Blob([content], { type: mimeType });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+}
diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts
index 3236841..4a29f03 100644
--- a/src/lib/i18n.ts
+++ b/src/lib/i18n.ts
@@ -94,6 +94,9 @@ const en = {
'error.reload': 'Reload page',
'shortcuts.navigationSection': 'Navigation',
'shortcuts.generalSection': 'General',
+
+ // Export
+ 'header.export': 'Export conversation as Markdown',
} as const;
const fr: Record = {
@@ -172,6 +175,8 @@ const fr: Record = {
'error.reload': 'Recharger',
'shortcuts.navigationSection': 'Navigation',
'shortcuts.generalSection': 'Général',
+
+ 'header.export': 'Exporter la conversation en Markdown',
};
export type TranslationKey = keyof typeof en;