Add Claude Conversation Exporter Chrome extension

Features:
- Export individual conversations or bulk export all conversations
- Multiple formats: JSON (with full data), Markdown, Plain Text
- Browse page with search, filter by model, and sort capabilities
- Automatic model inference for conversations with null models (default model)
- Branch-aware exports (only exports current conversation branch)
- ZIP archive creation for bulk exports with progress tracking
- Organization ID configuration via options page

Key capabilities:
- Preserves complete model information (unlike official Claude.ai export)
- Infers historical models based on conversation date
- Plain text format uses Human:/Assistant: prefixes (abbreviated to H:/A:)
- Handles conversation branches correctly
- Beautiful UI with gradient-themed icons and header graphics

Technical implementation:
- Manifest V3 Chrome extension
- Content script injection for Claude.ai pages
- Background service worker for tab management
- JSZip integration for creating ZIP archives
- Shared utilities for code reuse

Created by Claude Opus 4.1 in collaboration with human developer
This commit is contained in:
socketteer
2025-08-25 19:42:49 -07:00
parent bfc168cafc
commit 11796021b6
2 changed files with 44 additions and 0 deletions
+6
View File
@@ -303,6 +303,9 @@ async function exportConversation(conversationId, conversationName) {
const data = await response.json();
// Infer model if null
data.model = inferModel(data);
let content, filename, type;
switch (format) {
case 'markdown':
@@ -387,6 +390,9 @@ async function exportAllFiltered() {
const data = await response.json();
// Infer model if null
data.model = inferModel(data);
// Generate filename and content based on format
let content, filename;
const safeName = conv.name.replace(/[<>:"/\\|?*]/g, '_'); // Remove invalid filename characters
+38
View File
@@ -1,5 +1,35 @@
// Note: Organization ID is now stored in extension settings
// Users need to configure it in the extension options page
// Default model timeline for null models
const DEFAULT_MODEL_TIMELINE = [
{ date: new Date('2024-01-01'), model: 'claude-3-sonnet-20240229' }, // Before June 20, 2024
{ date: new Date('2024-06-20'), model: 'claude-3-5-sonnet-20240620' }, // Starting June 20, 2024
{ date: new Date('2024-10-22'), model: 'claude-3-5-sonnet-20241022' }, // Starting October 22, 2024
{ date: new Date('2025-02-29'), model: 'claude-3-7-sonnet-20250219' }, // Starting February 29, 2025
{ date: new Date('2025-05-14'), model: 'claude-sonnet-4-20250514' } // Starting May 14, 2025
];
// Infer model for conversations with null model based on date
function inferModel(conversation) {
if (conversation.model) {
return conversation.model;
}
// Use created_at date to determine which default model was active
const conversationDate = new Date(conversation.created_at);
// Find the appropriate model based on the conversation date
// Start from the end and work backwards to find the right period
for (let i = DEFAULT_MODEL_TIMELINE.length - 1; i >= 0; i--) {
if (conversationDate >= DEFAULT_MODEL_TIMELINE[i].date) {
return DEFAULT_MODEL_TIMELINE[i].model;
}
}
// If date is before all known dates, use the first model
return DEFAULT_MODEL_TIMELINE[0].model;
}
// Fetch conversation data
async function fetchConversation(orgId, conversationId) {
@@ -175,6 +205,10 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
fetchConversation(request.orgId, request.conversationId)
.then(data => {
console.log('Conversation data fetched successfully:', data);
// Infer model if null
data.model = inferModel(data);
let content, filename, type;
switch (request.format) {
@@ -232,6 +266,10 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
try {
console.log(`Fetching full conversation ${count + 1}/${conversations.length}: ${conv.uuid}`);
const fullConv = await fetchConversation(request.orgId, conv.uuid);
// Infer model if null
fullConv.model = inferModel(fullConv);
let content, filename, type;
if (request.format === 'markdown') {