- Modified loadChatHistory() to check for active project before fetching all sessions - When active project exists, use project.sessions instead of fetching from API - Added detailed console logging to debug session filtering - This prevents ALL sessions from appearing in every project's sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
767 lines
26 KiB
JavaScript
767 lines
26 KiB
JavaScript
/**
|
||
* Enhanced Chat Interface - Similar to chat.z.ai
|
||
* Features: Better input, chat history, session resumption, smooth animations
|
||
*/
|
||
|
||
// ============================================
|
||
// Enhanced Chat Input Experience
|
||
// ============================================
|
||
|
||
// Auto-focus chat input when switching to chat view
|
||
function focusChatInput() {
|
||
setTimeout(() => {
|
||
const input = document.getElementById('chat-input');
|
||
if (input) {
|
||
input.focus();
|
||
// Move cursor to end
|
||
input.setSelectionRange(input.value.length, input.value.length);
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
// Smooth textarea resize with animation
|
||
function enhanceChatInput() {
|
||
const input = document.getElementById('chat-input');
|
||
if (!input) return;
|
||
|
||
// Auto-resize with smooth transition
|
||
input.style.transition = 'height 0.2s ease';
|
||
input.addEventListener('input', function() {
|
||
this.style.height = 'auto';
|
||
const newHeight = Math.min(this.scrollHeight, 200);
|
||
this.style.height = newHeight + 'px';
|
||
});
|
||
|
||
// Focus animation
|
||
input.addEventListener('focus', function() {
|
||
this.parentElement.classList.add('input-focused');
|
||
});
|
||
|
||
input.addEventListener('blur', function() {
|
||
this.parentElement.classList.remove('input-focused');
|
||
});
|
||
}
|
||
|
||
// ============================================
|
||
// Chat History & Session Management
|
||
// ============================================
|
||
|
||
// Auto-load chat history when page loads
|
||
// Make this a named function so it can be called to refresh the sidebar
|
||
// @param {Array} sessionsToRender - Optional: specific sessions to render (for project filtering)
|
||
async function loadChatHistory(sessionsToRender = null) {
|
||
try {
|
||
const historyList = document.getElementById('chat-history-list');
|
||
if (!historyList) return;
|
||
|
||
// Skip update if we're showing "Loading session..." to avoid conflicts
|
||
// with attachToSession's loading state
|
||
if (historyList.textContent.includes('Loading session')) {
|
||
console.log('[loadChatHistory] Skipping update - showing loading state');
|
||
return;
|
||
}
|
||
|
||
let allSessions;
|
||
|
||
if (sessionsToRender) {
|
||
// Use provided sessions (for project filtering)
|
||
allSessions = sessionsToRender;
|
||
console.log('[loadChatHistory] Rendering provided sessions:', allSessions.length);
|
||
} else {
|
||
// CRITICAL FIX: If no sessions provided, check if there's an active project
|
||
// If there is, we should NOT fetch from API - instead wait for project to provide sessions
|
||
if (window.projectManager && window.projectManager.activeProjectId) {
|
||
const activeProject = window.projectManager.projects.get(
|
||
window.projectManager.activeProjectId.replace('project-', '')
|
||
);
|
||
if (activeProject) {
|
||
// Use the active project's sessions
|
||
allSessions = activeProject.sessions || [];
|
||
console.log('[loadChatHistory] Using active project sessions:', allSessions.length, 'project:', activeProject.name);
|
||
} else {
|
||
// No project found, fetch from API as fallback
|
||
const res = await fetch('/claude/api/claude/sessions');
|
||
const data = await res.json();
|
||
allSessions = [
|
||
...(data.active || []).map(s => ({...s, status: 'active'})),
|
||
...(data.historical || []).map(s => ({...s, status: 'historical'}))
|
||
];
|
||
console.log('[loadChatHistory] No active project found, loaded from API:', allSessions.length);
|
||
}
|
||
} else {
|
||
// No project manager or no active project, fetch all sessions
|
||
const res = await fetch('/claude/api/claude/sessions');
|
||
const data = await res.json();
|
||
allSessions = [
|
||
...(data.active || []).map(s => ({...s, status: 'active'})),
|
||
...(data.historical || []).map(s => ({...s, status: 'historical'}))
|
||
];
|
||
console.log('[loadChatHistory] No active project, loaded all sessions from API:', allSessions.length);
|
||
}
|
||
}
|
||
|
||
// Sort by creation date (newest first)
|
||
allSessions.sort((a, b) => new Date(b.createdAt || b.created_at) - new Date(a.createdAt || a.created_at));
|
||
|
||
// CRITICAL DEBUG: Log session details for debugging
|
||
console.log('[loadChatHistory] Total sessions to render:', allSessions.length);
|
||
allSessions.forEach((s, i) => {
|
||
console.log(`[loadChatHistory] Session ${i}:`, {
|
||
id: s.id.substring(0, 8),
|
||
workingDir: s.workingDir,
|
||
project: s.metadata?.project,
|
||
status: s.status
|
||
});
|
||
});
|
||
|
||
if (allSessions.length === 0) {
|
||
historyList.innerHTML = '<div class="chat-history-empty">No sessions in this project</div>';
|
||
return;
|
||
}
|
||
|
||
historyList.innerHTML = allSessions.map(session => {
|
||
const title = session.metadata?.project ||
|
||
session.project ||
|
||
session.id.substring(0, 12) + '...';
|
||
const date = new Date(session.createdAt || session.created_at).toLocaleDateString();
|
||
const isActive = session.id === (window.attachedSessionId || null);
|
||
|
||
return `
|
||
<div class="chat-history-item ${isActive ? 'active' : ''} ${session.status === 'historical' ? 'historical' : ''}"
|
||
data-session-id="${session.id}">
|
||
<div class="chat-history-main" onclick="${session.status === 'historical' ? `resumeSession('${session.id}')` : `attachToSession('${session.id}')`}">
|
||
<div class="chat-history-icon">
|
||
${session.status === 'historical' ? '📁' : '💬'}
|
||
</div>
|
||
<div class="chat-history-content">
|
||
<div class="chat-history-title">${title}</div>
|
||
<div class="chat-history-meta">
|
||
<span class="chat-history-date">${date}</span>
|
||
<span class="chat-history-status ${session.status}">
|
||
${session.status === 'historical' ? 'Historical' : 'Active'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
${session.status === 'historical' ? '<span class="resume-badge">Resume</span>' : ''}
|
||
</div>
|
||
<button class="chat-history-archive" onclick="event.stopPropagation(); archiveSession('${session.id}')" title="Archive session">
|
||
📦
|
||
</button>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
// CRITICAL FIX: Also update session tabs with the same sessions
|
||
if (window.sessionTabs && typeof window.sessionTabs.setSessions === 'function') {
|
||
window.sessionTabs.setSessions(allSessions);
|
||
console.log('[loadChatHistory] Updated session tabs with', allSessions.length, 'sessions');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('[loadChatHistory] Error loading chat history:', error);
|
||
}
|
||
}
|
||
|
||
// Auto-load chat history when page loads
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', loadChatHistory);
|
||
} else {
|
||
// DOM already loaded, load immediately
|
||
loadChatHistory();
|
||
}
|
||
|
||
// Resume historical session
|
||
async function resumeSession(sessionId) {
|
||
console.log('Resuming historical session:', sessionId);
|
||
|
||
// Show loading message
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('📂 Loading historical session...');
|
||
}
|
||
|
||
try {
|
||
// Load the historical session
|
||
const res = await fetch('/claude/api/claude/sessions/' + sessionId);
|
||
|
||
// Check if response is OK
|
||
if (!res.ok) {
|
||
const errorText = await res.text();
|
||
console.error('Session fetch error:', res.status, errorText);
|
||
|
||
// Handle 404 - session not found
|
||
if (res.status === 404) {
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('❌ Session not found. It may have been deleted or the ID is incorrect.');
|
||
}
|
||
return;
|
||
}
|
||
|
||
throw new Error(`HTTP ${res.status}: ${errorText}`);
|
||
}
|
||
|
||
// Parse JSON with error handling
|
||
let data;
|
||
try {
|
||
data = await res.json();
|
||
} catch (jsonError) {
|
||
const responseText = await res.text();
|
||
console.error('JSON parse error:', jsonError);
|
||
console.error('Response text:', responseText);
|
||
throw new Error('Invalid JSON response from server');
|
||
}
|
||
|
||
// CRITICAL FIX: API returns session directly, not wrapped in {session: ...}
|
||
const session = data.session || data;
|
||
|
||
if (session && session.id) {
|
||
if (typeof attachToSession === 'function') {
|
||
attachToSession(sessionId);
|
||
}
|
||
|
||
// Update UI
|
||
const sessionIdEl = document.getElementById('current-session-id');
|
||
if (sessionIdEl) sessionIdEl.textContent = sessionId;
|
||
|
||
// Load session messages
|
||
if (typeof clearChatDisplay === 'function') {
|
||
clearChatDisplay();
|
||
}
|
||
|
||
// Add historical messages
|
||
if (session.outputBuffer && session.outputBuffer.length > 0) {
|
||
session.outputBuffer.forEach(entry => {
|
||
if (typeof appendMessage === 'function') {
|
||
appendMessage('assistant', entry.content, false);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Show resume message
|
||
const sessionDate = new Date(session.createdAt || session.created_at);
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('✅ Resumed historical session from ' + sessionDate.toLocaleString());
|
||
appendSystemMessage('ℹ️ This is a read-only historical session. Start a new chat to continue working.');
|
||
}
|
||
|
||
// Update active state in sidebar
|
||
if (typeof loadChatHistory === 'function') {
|
||
loadChatHistory();
|
||
}
|
||
|
||
// Subscribe to session (for any future updates)
|
||
if (typeof subscribeToSession === 'function') {
|
||
subscribeToSession(sessionId);
|
||
}
|
||
} else {
|
||
throw new Error('No session data in response');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error resuming session:', error);
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('❌ Failed to resume session: ' + error.message);
|
||
|
||
// Remove the loading message
|
||
const messagesContainer = document.getElementById('chat-messages');
|
||
if (messagesContainer) {
|
||
const loadingMessages = messagesContainer.querySelectorAll('.chat-system');
|
||
loadingMessages.forEach(msg => {
|
||
if (msg.textContent.includes('Loading historical session')) {
|
||
msg.remove();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// Enhanced Message Rendering
|
||
// ============================================
|
||
|
||
// Enhanced append with animations
|
||
function appendMessageWithAnimation(role, content, animate = true) {
|
||
const messagesContainer = document.getElementById('chat-messages');
|
||
if (!messagesContainer) return;
|
||
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `chat-message chat-message-${role} ${animate ? 'message-appear' : ''}`;
|
||
|
||
const avatar = role === 'user' ? '👤' : '🤖';
|
||
const label = role === 'user' ? 'You' : 'Claude';
|
||
|
||
// Strip dyad tags for display
|
||
const displayContent = stripDyadTags(content);
|
||
|
||
messageDiv.innerHTML = `
|
||
<div class="message-avatar">${avatar}</div>
|
||
<div class="message-content">
|
||
<div class="message-header">
|
||
<span class="message-label">${label}</span>
|
||
<span class="message-time">${new Date().toLocaleTimeString()}</span>
|
||
</div>
|
||
<div class="message-text">${formatMessageText(displayContent)}</div>
|
||
</div>
|
||
`;
|
||
|
||
messagesContainer.appendChild(messageDiv);
|
||
|
||
// Scroll to bottom
|
||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||
|
||
// Update token usage
|
||
if (typeof updateTokenUsage === 'function') {
|
||
updateTokenUsage(content.length);
|
||
}
|
||
}
|
||
|
||
// Strip dyad tags from message for display
|
||
function stripDyadTags(content) {
|
||
let stripped = content;
|
||
|
||
// Remove dyad-write tags and replace with placeholder
|
||
stripped = stripped.replace(/<dyad-write\s+path="[^"]+">([\s\S]*?)<\/dyad-write>/g, (match, content) => {
|
||
return `
|
||
<div class="code-operation">
|
||
<div class="operation-header">
|
||
<span class="operation-icon">📄</span>
|
||
<span class="operation-label">Code generated</span>
|
||
</div>
|
||
<pre class="operation-code"><code>${escapeHtml(content.trim())}</code></pre>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
// Remove other dyad tags
|
||
stripped = stripped.replace(/<dyad-[^>]+>/g, (match) => {
|
||
const tagType = match.match(/dyad-(\w+)/)?.[1] || 'operation';
|
||
const icons = {
|
||
'rename': '✏️',
|
||
'delete': '🗑️',
|
||
'add-dependency': '📦',
|
||
'command': '⚡'
|
||
};
|
||
return `<span class="tag-placeholder">${icons[tagType] || '⚙️'} ${tagType}</span>`;
|
||
});
|
||
|
||
return stripped;
|
||
}
|
||
|
||
// Format message text with markdown-like rendering
|
||
function formatMessageText(text) {
|
||
// Basic markdown-like formatting
|
||
let formatted = escapeHtml(text);
|
||
|
||
// Code blocks
|
||
formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
||
return `<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`;
|
||
});
|
||
|
||
// Inline code
|
||
formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||
|
||
// Bold
|
||
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||
|
||
// Links
|
||
formatted = formatted.replace(/https?:\/\/[^\s]+/g, '<a href="$&" target="_blank">$&</a>');
|
||
|
||
// Line breaks
|
||
formatted = formatted.replace(/\n/g, '<br>');
|
||
|
||
return formatted;
|
||
}
|
||
|
||
// Escape HTML
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// ============================================
|
||
// Quick Actions & Suggestions
|
||
// ============================================
|
||
|
||
// Show quick action suggestions
|
||
function showQuickActions() {
|
||
const messagesContainer = document.getElementById('chat-messages');
|
||
if (!messagesContainer) return;
|
||
|
||
const quickActions = document.createElement('div');
|
||
quickActions.className = 'quick-actions';
|
||
quickActions.innerHTML = `
|
||
<div class="quick-actions-title">💡 Quick Actions</div>
|
||
<div class="quick-actions-grid">
|
||
<button class="quick-action-btn" onclick="executeQuickAction('create-react')">
|
||
<span class="action-icon">⚛️</span>
|
||
<span class="action-label">Create React App</span>
|
||
</button>
|
||
<button class="quick-action-btn" onclick="executeQuickAction('create-nextjs')">
|
||
<span class="action-icon">▲</span>
|
||
<span class="action-label">Create Next.js App</span>
|
||
</button>
|
||
<button class="quick-action-btn" onclick="executeQuickAction('create-vue')">
|
||
<span class="action-icon">💚</span>
|
||
<span class="action-label">Create Vue App</span>
|
||
</button>
|
||
<button class="quick-action-btn" onclick="executeQuickAction('create-html')">
|
||
<span class="action-icon">📄</span>
|
||
<span class="action-label">Create HTML Page</span>
|
||
</button>
|
||
<button class="quick-action-btn" onclick="executeQuickAction('explain-code')">
|
||
<span class="action-icon">📖</span>
|
||
<span class="action-label">Explain Codebase</span>
|
||
</button>
|
||
<button class="quick-action-btn" onclick="executeQuickAction('fix-bug')">
|
||
<span class="action-icon">🐛</span>
|
||
<span class="action-label">Fix Bug</span>
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
messagesContainer.appendChild(quickActions);
|
||
}
|
||
|
||
// Execute quick action
|
||
function executeQuickAction(action) {
|
||
const actions = {
|
||
'create-react': 'Create a React app with components and routing',
|
||
'create-nextjs': 'Create a Next.js app with server-side rendering',
|
||
'create-vue': 'Create a Vue 3 app with composition API',
|
||
'create-html': 'Create a responsive HTML5 page with modern styling',
|
||
'explain-code': 'Explain the codebase structure and main files',
|
||
'fix-bug': 'Help me fix a bug in my code'
|
||
};
|
||
|
||
const prompt = actions[action];
|
||
if (prompt) {
|
||
const input = document.getElementById('chat-input');
|
||
if (input) {
|
||
input.value = prompt;
|
||
input.focus();
|
||
// Auto-send after short delay
|
||
setTimeout(() => {
|
||
if (typeof sendChatMessage === 'function') {
|
||
sendChatMessage();
|
||
}
|
||
}, 300);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// Enhanced Chat View Loading
|
||
// ============================================
|
||
|
||
// Hook into loadChatView to add enhancements
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Wait for chat-functions.js to load
|
||
setTimeout(() => {
|
||
// Override loadChatView to add enhancements
|
||
if (typeof window.loadChatView === 'function') {
|
||
const originalLoadChatView = window.loadChatView;
|
||
window.loadChatView = async function() {
|
||
// Call original function first
|
||
await originalLoadChatView.call(this);
|
||
|
||
// Add our enhancements
|
||
setTimeout(() => {
|
||
enhanceChatInput();
|
||
focusChatInput();
|
||
|
||
// Show quick actions on first load
|
||
const messagesContainer = document.getElementById('chat-messages');
|
||
if (messagesContainer && messagesContainer.querySelector('.chat-welcome')) {
|
||
showQuickActions();
|
||
}
|
||
}, 100);
|
||
};
|
||
}
|
||
}, 1000);
|
||
});
|
||
|
||
// Auto-start enhancements when chat view is active
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.target.id === 'chat-view' && mutation.target.classList.contains('active')) {
|
||
enhanceChatInput();
|
||
focusChatInput();
|
||
}
|
||
});
|
||
});
|
||
|
||
// Start observing after DOM loads
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
setTimeout(() => {
|
||
const chatView = document.getElementById('chat-view');
|
||
if (chatView) observer.observe(chatView, { attributes: true });
|
||
}, 1500);
|
||
});
|
||
} else {
|
||
setTimeout(() => {
|
||
const chatView = document.getElementById('chat-view');
|
||
if (chatView) observer.observe(chatView, { attributes: true });
|
||
}, 1500);
|
||
}
|
||
|
||
// ============================================
|
||
// Archive & Merge Sessions
|
||
// ============================================
|
||
|
||
// Track selected sessions for merge
|
||
window.selectedSessionsForMerge = new Set();
|
||
|
||
/**
|
||
* Archive a session
|
||
*/
|
||
async function archiveSession(sessionId) {
|
||
console.log('[Archive] Archiving session:', sessionId);
|
||
|
||
const confirmed = confirm('Archive this session? It will be hidden from the main list but can be unarchived later.');
|
||
if (!confirmed) return;
|
||
|
||
try {
|
||
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/archive`, {
|
||
method: 'PATCH',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
if (!res.ok) {
|
||
// Try to get more error details
|
||
let errorMessage = 'Failed to archive session';
|
||
try {
|
||
const errorData = await res.json();
|
||
errorMessage = errorData.message || errorData.error || errorMessage;
|
||
} catch (e) {
|
||
errorMessage = `HTTP ${res.status}: ${res.statusText}`;
|
||
}
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
// Refresh the session list
|
||
if (typeof loadChatHistory === 'function') {
|
||
await loadChatHistory();
|
||
}
|
||
|
||
// Show success message
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('✅ Session archived successfully');
|
||
}
|
||
} catch (error) {
|
||
console.error('[Archive] Error:', error);
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('❌ Failed to archive session: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Toggle session selection for merge
|
||
*/
|
||
function toggleSessionSelection(sessionId) {
|
||
if (window.selectedSessionsForMerge.has(sessionId)) {
|
||
window.selectedSessionsForMerge.delete(sessionId);
|
||
} else {
|
||
window.selectedSessionsForMerge.add(sessionId);
|
||
}
|
||
|
||
// Update UI
|
||
const item = document.querySelector(`[data-session-id="${sessionId}"]`);
|
||
if (item) {
|
||
item.classList.toggle('selected-for-merge', window.selectedSessionsForMerge.has(sessionId));
|
||
}
|
||
|
||
// Show/hide merge button
|
||
updateMergeButtonVisibility();
|
||
}
|
||
|
||
/**
|
||
* Update merge button visibility based on selection
|
||
*/
|
||
function updateMergeButtonVisibility() {
|
||
const mergeBtn = document.getElementById('merge-sessions-btn');
|
||
if (!mergeBtn) return;
|
||
|
||
if (window.selectedSessionsForMerge.size >= 2) {
|
||
mergeBtn.style.display = 'flex';
|
||
mergeBtn.textContent = `🔀 Emerge ${window.selectedSessionsForMerge.size} Sessions`;
|
||
} else {
|
||
mergeBtn.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Merge selected sessions
|
||
*/
|
||
async function mergeSessions() {
|
||
const sessionIds = Array.from(window.selectedSessionsForMerge);
|
||
|
||
if (sessionIds.length < 2) {
|
||
alert('Please select at least 2 sessions to merge');
|
||
return;
|
||
}
|
||
|
||
console.log('[Merge] Merging sessions:', sessionIds);
|
||
|
||
const confirmed = confirm(`Merge ${sessionIds.length} sessions into one? This will create a new session with all messages from the selected sessions.`);
|
||
if (!confirmed) return;
|
||
|
||
try {
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('🔀 Merging sessions...');
|
||
}
|
||
|
||
const res = await fetch('/claude/api/claude/sessions/merge', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ sessionIds })
|
||
});
|
||
|
||
if (!res.ok) throw new Error('Failed to merge sessions');
|
||
|
||
const data = await res.json();
|
||
|
||
if (data.success && data.session) {
|
||
// Clear selection
|
||
window.selectedSessionsForMerge.clear();
|
||
updateMergeButtonVisibility();
|
||
|
||
// Remove all selected classes
|
||
document.querySelectorAll('.selected-for-merge').forEach(el => {
|
||
el.classList.remove('selected-for-merge');
|
||
});
|
||
|
||
// Refresh the session list
|
||
if (typeof loadChatHistory === 'function') {
|
||
await loadChatHistory();
|
||
}
|
||
|
||
// Attach to the new merged session
|
||
if (typeof attachToSession === 'function') {
|
||
await attachToSession(data.session.id);
|
||
}
|
||
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('✅ Sessions merged successfully!');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('[Merge] Error:', error);
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('❌ Failed to merge sessions: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show archived sessions view
|
||
*/
|
||
async function showArchivedSessions() {
|
||
console.log('[Archive] Loading archived sessions...');
|
||
|
||
try {
|
||
const res = await fetch('/claude/api/claude/sessions?archived=true');
|
||
const data = await res.json();
|
||
|
||
const archivedSessions = data.archived || [];
|
||
const historyList = document.getElementById('chat-history-list');
|
||
|
||
if (!historyList) return;
|
||
|
||
if (archivedSessions.length === 0) {
|
||
historyList.innerHTML = `
|
||
<div class="chat-history-empty">
|
||
<div>No archived sessions</div>
|
||
<button onclick="loadChatHistory()" style="margin-top: 12px; padding: 8px 16px; background: #333; border: none; border-radius: 6px; color: #fff; cursor: pointer;">
|
||
← Back to Sessions
|
||
</button>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
// Update header to show back button
|
||
const historyHeader = document.querySelector('.chat-history-header h3');
|
||
if (historyHeader) {
|
||
historyHeader.innerHTML = `
|
||
<button onclick="loadChatHistory()" style="background: none; border: none; color: #888; cursor: pointer; font-size: 16px;">
|
||
← Back
|
||
</button>
|
||
<span style="margin-left: 8px;">Archived Sessions</span>
|
||
`;
|
||
}
|
||
|
||
historyList.innerHTML = archivedSessions.map(session => {
|
||
const title = session.metadata?.project ||
|
||
session.workingDir?.split('/').pop() ||
|
||
session.id.substring(0, 12) + '...';
|
||
const archivedDate = new Date(session.archivedAt).toLocaleDateString();
|
||
|
||
return `
|
||
<div class="chat-history-item historical" data-session-id="${session.id}">
|
||
<div class="chat-history-main" onclick="resumeSession('${session.id}')">
|
||
<div class="chat-history-icon">📦</div>
|
||
<div class="chat-history-content">
|
||
<div class="chat-history-title">${title}</div>
|
||
<div class="chat-history-meta">
|
||
<span class="chat-history-date">Archived: ${archivedDate}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button class="chat-history-unarchive" onclick="event.stopPropagation(); unarchiveSession('${session.id}')" title="Unarchive session">
|
||
📤
|
||
</button>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
} catch (error) {
|
||
console.error('[Archive] Error loading archived sessions:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Unarchive a session
|
||
*/
|
||
async function unarchiveSession(sessionId) {
|
||
console.log('[Archive] Unarchiving session:', sessionId);
|
||
|
||
try {
|
||
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/unarchive`, {
|
||
method: 'PATCH',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
if (!res.ok) throw new Error('Failed to unarchive session');
|
||
|
||
// Refresh the archived list
|
||
await showArchivedSessions();
|
||
|
||
// Show success message
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('✅ Session unarchived successfully');
|
||
}
|
||
} catch (error) {
|
||
console.error('[Archive] Error:', error);
|
||
if (typeof appendSystemMessage === 'function') {
|
||
appendSystemMessage('❌ Failed to unarchive session: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Export functions
|
||
if (typeof window !== 'undefined') {
|
||
window.resumeSession = resumeSession;
|
||
window.executeQuickAction = executeQuickAction;
|
||
window.showQuickActions = showQuickActions;
|
||
window.enhanceChatInput = enhanceChatInput;
|
||
window.focusChatInput = focusChatInput;
|
||
window.appendMessageWithAnimation = appendMessageWithAnimation;
|
||
window.archiveSession = archiveSession;
|
||
window.toggleSessionSelection = toggleSessionSelection;
|
||
window.mergeSessions = mergeSessions;
|
||
window.showArchivedSessions = showArchivedSessions;
|
||
window.unarchiveSession = unarchiveSession;
|
||
}
|