feat: add chat history import and export functionality

Implemented full chat history import/export with JSON format:
- Export button saves current conversation to JSON file
- Import button loads conversation from JSON file
- File dialog integration using tauri-plugin-dialog
- Message count feedback on successful import
- Automatic history reload after import
- Preserves all message data including swipes and timestamps
- Smart error handling (ignores cancelled dialogs)

Backend (Rust):
- export_chat_history: Opens save dialog, writes JSON to selected path
- import_chat_history: Opens file picker, parses JSON, saves to current character
- Message migration for backward compatibility
- Returns helpful feedback (file path on export, message count on import)

Frontend (JavaScript):
- Export/import buttons in header with up/down arrow icons
- Status updates during operations
- Auto-reload chat view after import
- Error handling with user-friendly messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-14 18:03:34 -07:00
parent 84d3e0df67
commit 83e9793dce
3 changed files with 139 additions and 1 deletions

View File

@@ -30,6 +30,18 @@
</button>
</div>
<div class="header-controls">
<button id="import-chat-btn" class="icon-btn" title="Import conversation">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M8 11V3M5 8l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 13h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
<button id="export-chat-btn" class="icon-btn" title="Export conversation">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M8 3v8M5 6l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 13h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
<button id="clear-btn" class="icon-btn" title="Clear conversation">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 4h10M6 4V3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v1M5 4v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>

View File

@@ -201,6 +201,47 @@ function loadSavedFontSize() {
applyFontSize(savedSize);
}
// Export chat history
async function exportChatHistory() {
try {
setStatus('Exporting chat...', 'default');
const filePath = await invoke('export_chat_history');
setStatus('Chat exported successfully!', 'success');
setTimeout(() => setStatus('Ready'), 2000);
console.log('Chat exported to:', filePath);
} catch (error) {
console.error('Export failed:', error);
if (error && !error.toString().includes('cancelled')) {
setStatus(`Export failed: ${error}`, 'error');
setTimeout(() => setStatus('Ready'), 3000);
} else {
setStatus('Ready');
}
}
}
// Import chat history
async function importChatHistory() {
try {
setStatus('Importing chat...', 'default');
const messageCount = await invoke('import_chat_history');
// Reload the chat history
await loadChatHistory();
setStatus(`Imported ${messageCount} messages successfully!`, 'success');
setTimeout(() => setStatus('Ready'), 2000);
} catch (error) {
console.error('Import failed:', error);
if (error === 'No file selected' || error.toString().includes('cancelled')) {
setStatus('Ready');
} else {
setStatus(`Import failed: ${error}`, 'error');
setTimeout(() => setStatus('Ready'), 3000);
}
}
}
// Helper function to get avatar URL
async function getAvatarUrl(avatarFilename) {
if (!avatarFilename) return null;
@@ -1206,6 +1247,8 @@ function setupAppControls() {
document.getElementById('close-settings-btn').addEventListener('click', hideSettings);
document.getElementById('settings-overlay').addEventListener('click', hideSettings);
document.getElementById('clear-btn').addEventListener('click', clearHistory);
document.getElementById('export-chat-btn').addEventListener('click', exportChatHistory);
document.getElementById('import-chat-btn').addEventListener('click', importChatHistory);
characterSelect.addEventListener('change', handleCharacterSwitch);
newCharacterBtn.addEventListener('click', handleNewCharacter);
document.getElementById('delete-character-btn').addEventListener('click', handleDeleteCharacter);