Use checkboxes in the explorer to select files and directories
`;
} else {
Array.from(selected).forEach(path => {
const card = this.createFileCard(path);
selectedCardsEl.appendChild(card);
});
}
}
// Estimate token count (rough approximation)
const totalChars = Array.from(selected).join('\n').length;
const tokens = Math.ceil(totalChars / 4);
if (tokenCountEl) tokenCountEl.textContent = tokens.toString();
}
// Select or deselect all children of a directory recursively
async selectDirectoryChildren(dirPath, select) {
// Get all children from API to update the selection state
// This only selects FILES, not directories (API returns only files)
await this.selectDirectoryChildrenFromAPI(dirPath, select);
// Note: We don't call updateSubdirectorySelection() here because
// subdirectories might not be in the DOM yet. The caller should
// call it after expanding the directory.
// Update any currently visible children in the DOM
this.updateVisibleCheckboxes();
// Update the selection UI once at the end
this.updateSelectionUI();
}
// Update selectedDirs for all subdirectories under a path
updateSubdirectorySelection(dirPath, select) {
// Find all visible subdirectories under this path
const treeItems = document.querySelectorAll('.tree-item');
treeItems.forEach(item => {
const itemPath = item.dataset.path;
const itemType = item.dataset.type;
// Check if this is a subdirectory of dirPath
if (itemType === 'directory' && itemPath !== dirPath && itemPath.startsWith(dirPath + '/')) {
if (select) {
selectedDirs.add(itemPath);
} else {
selectedDirs.delete(itemPath);
}
}
});
}
// Update all visible checkboxes to match the current selection state
updateVisibleCheckboxes() {
const treeItems = document.querySelectorAll('.tree-item');
treeItems.forEach(item => {
const itemPath = item.dataset.path;
const itemType = item.dataset.type;
const checkbox = item.querySelector('.tree-checkbox');
if (checkbox && itemPath) {
if (itemType === 'file') {
// For files: check if the file path is in selected set
checkbox.checked = selected.has(itemPath);
} else if (itemType === 'directory') {
// For directories: check if all file children are selected
checkbox.checked = this.areAllChildrenSelected(itemPath);
}
}
});
}
// Check if a directory should be checked
// A directory is checked if:
// 1. It's in the selectedDirs set (explicitly selected), OR
// 2. Any parent directory is in selectedDirs (cascading)
areAllChildrenSelected(dirPath) {
// Check if this directory is explicitly selected
if (selectedDirs.has(dirPath)) {
return true;
}
// Check if any parent directory is selected (cascading)
if (this.isParentDirectorySelected(dirPath)) {
return true;
}
return false;
}
// Check if any parent directory of this path is selected
isParentDirectorySelected(dirPath) {
// Walk up the directory tree
let currentPath = dirPath;
while (currentPath.includes('/')) {
// Get parent directory
const parentPath = currentPath.substring(0, currentPath.lastIndexOf('/'));
// Check if parent is in selectedDirs
if (selectedDirs.has(parentPath)) {
return true;
}
currentPath = parentPath;
}
return false;
}
// Select directory children using API to get complete recursive list
async selectDirectoryChildrenFromAPI(dirPath, select) {
try {
const response = await fetch(`/api/heroprompt/workspaces/${encodeURIComponent(currentWs)}/list?path=${encodeURIComponent(dirPath)}`);
if (response.ok) {
const data = await response.json();
if (data.children) {
data.children.forEach(child => {
const childPath = child.path;
if (select) {
selected.add(childPath);
} else {
selected.delete(childPath);
}
});
}
} else {
console.error('Failed to fetch directory children:', response.status, response.statusText);
const errorText = await response.text();
console.error('Error response:', errorText);
}
} catch (error) {
console.error('Error selecting directory children:', error);
}
}
createFileCard(path) {
const card = document.createElement('div');
card.className = 'file-card';
// Get file info
const fileName = path.split('/').pop();
const extension = getFileExtension(fileName);
const isDirectory = this.isDirectory(path);
card.dataset.type = isDirectory ? 'directory' : 'file';
if (extension) {
card.dataset.extension = extension;
}
// Get file stats (mock data for now - could be enhanced with real file stats)
const stats = this.getFileStats(path);
// Show full path for directories to help differentiate between same-named directories
const displayPath = isDirectory ? path : path;
card.innerHTML = `
${isDirectory ? '📁' : getFileIcon(extension)}
${fileName}
${displayPath}
📄${isDirectory ? 'Directory' : 'File'}
${extension ? `
🏷️${extension.toUpperCase()}
` : ''}
📏${stats.size}
📅${stats.modified}
${!isDirectory ? `
` : ''}
`;
return card;
}
isDirectory(path) {
// Check if path corresponds to a directory in the tree
const treeItem = qs(`[data-path="${path}"]`);
return treeItem && treeItem.dataset.type === 'directory';
}
getFileStats(path) {
// Mock file stats - in a real implementation, this would come from the API
return {
size: formatFileSize(Math.floor(Math.random() * 100000) + 1000),
modified: formatDate(new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000))
};
}
async previewFileInModal(filePath) {
// Create and show modal for file preview
const modal = document.createElement('div');
modal.className = 'modal fade file-preview-modal';
modal.id = 'filePreviewModal';
modal.innerHTML = `
`;
}
}
async function copyPrompt() {
const outputEl = el('promptOutput');
if (!outputEl) {
console.warn('Prompt output element not found');
showStatus('Copy failed - element not found', 'error');
return;
}
// Grab the visible prompt text, stripping HTML and empty-state placeholders
const text = outputEl.innerText.trim();
if (!text || text.includes('Generated prompt will appear here') || text.includes('No files selected')) {
showStatus('Nothing to copy', 'warning');
return;
}
// Try the modern Clipboard API first
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(text);
showStatus('Prompt copied to clipboard!', 'success');
return;
} catch (e) {
console.warn('Clipboard API failed, falling back', e);
}
}
// Fallback to hidden textarea method
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed'; // avoid scrolling to bottom
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
const successful = document.execCommand('copy');
showStatus(successful ? 'Prompt copied!' : 'Copy failed', successful ? 'success' : 'error');
} catch (e) {
console.error('Fallback copy failed', e);
showStatus('Copy failed', 'error');
} finally {
document.body.removeChild(textarea);
}
}
/* Helper – show a transient message inside the output pane */
function showStatus(msg, type = 'info') {
const out = el('promptOutput');
if (!out) return;
const original = out.innerHTML;
const statusClass = type === 'success' ? 'success-message' :
type === 'error' ? 'error-message' :
type === 'warning' ? 'warning-message' : 'info-message';
out.innerHTML = `
${msg}
`;
setTimeout(() => {
out.innerHTML = original;
}, 2000);
}
// Global fallback function for clipboard operations
function fallbackCopyToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
console.log('Fallback: Content copied to clipboard');
} catch (err) {
console.error('Fallback: Failed to copy content', err);
}
document.body.removeChild(textArea);
}
// Confirmation modal helper
function showConfirmationModal(message, onConfirm) {
const messageEl = el('confirmDeleteMessage');
const confirmBtn = el('confirmDeleteBtn');
if (messageEl) messageEl.textContent = message;
// Remove any existing event listeners
const newConfirmBtn = confirmBtn.cloneNode(true);
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
// Add new event listener
newConfirmBtn.addEventListener('click', () => {
hideModal('confirmDeleteModal');
onConfirm();
});
showModal('confirmDeleteModal');
}
// Workspace management functions
async function deleteWorkspace(workspaceName) {
try {
const encodedName = encodeURIComponent(workspaceName);
const response = await fetch(`/api/heroprompt/workspaces/${encodedName}/delete`, {
method: 'POST'
});
if (!response.ok) {
const errorText = await response.text();
console.error('Delete failed:', response.status, errorText);
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
// If we deleted the current workspace, switch to another one
if (workspaceName === currentWs) {
const names = await api('/api/heroprompt/workspaces');
if (names && Array.isArray(names) && names.length > 0) {
currentWs = names[0];
localStorage.setItem('heroprompt-current-ws', currentWs);
await reloadWorkspaces();
// Load directories for new current workspace
await loadWorkspaceDirectories();
}
}
return { success: true };
} catch (e) {
console.warn('Delete workspace failed', e);
return { error: 'Failed to delete workspace' };
}
}
async function updateWorkspace(workspaceName, newName) {
try {
const formData = new FormData();
if (newName && newName !== workspaceName) {
formData.append('name', newName);
}
const encodedName = encodeURIComponent(workspaceName);
const response = await fetch(`/api/heroprompt/workspaces/${encodedName}`, {
method: 'PUT',
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error('Update failed:', response.status, errorText);
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const result = await response.json();
// Update current workspace if it was renamed
if (workspaceName === currentWs && result.name && result.name !== workspaceName) {
currentWs = result.name;
localStorage.setItem('heroprompt-current-ws', currentWs);
}
await reloadWorkspaces();
return result;
} catch (e) {
console.warn('Update workspace failed', e);
return { error: 'Failed to update workspace' };
}
}
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', function () {
// Initialize file tree
const treeContainer = el('tree');
if (treeContainer) {
fileTree = new SimpleFileTree(treeContainer);
}
// Initialize workspaces
initWorkspace();
reloadWorkspaces();
// Tab switching
qsa('.tab').forEach(tab => {
tab.addEventListener('click', function (e) {
e.preventDefault();
const tabName = this.getAttribute('data-tab');
switchTab(tabName);
});
});
// Workspace selector
const workspaceSelect = el('workspaceSelect');
if (workspaceSelect) {
workspaceSelect.addEventListener('change', async (e) => {
currentWs = e.target.value;
localStorage.setItem('heroprompt-current-ws', currentWs);
// Load directories for the new workspace
await loadWorkspaceDirectories();
});
}
// Explorer controls
const collapseAllBtn = el('collapseAll');
if (collapseAllBtn) {
collapseAllBtn.addEventListener('click', () => {
if (fileTree) fileTree.collapseAll();
});
}
const refreshExplorerBtn = el('refreshExplorer');
if (refreshExplorerBtn) {
refreshExplorerBtn.addEventListener('click', async () => {
// Save currently expanded directories before refresh
const previouslyExpanded = new Set(expandedDirs);
// Reload workspace directories
await loadWorkspaceDirectories();
// Re-expand previously expanded directories
if (fileTree && previouslyExpanded.size > 0) {
// Wait a bit for the DOM to be ready
await new Promise(resolve => setTimeout(resolve, 100));
// Re-expand each previously expanded directory
for (const dirPath of previouslyExpanded) {
const dirElement = document.querySelector(`[data-path="${dirPath}"][data-type="directory"]`);
if (dirElement && !expandedDirs.has(dirPath)) {
// Expand this directory
await fileTree.toggleDirectory(dirPath);
}
}
}
});
}
const selectAllBtn = el('selectAll');
if (selectAllBtn) {
selectAllBtn.addEventListener('click', () => {
if (fileTree) fileTree.selectAll();
});
}
const clearSelectionBtn = el('clearSelection');
if (clearSelectionBtn) {
clearSelectionBtn.addEventListener('click', () => {
if (fileTree) fileTree.clearSelection();
});
}
const clearAllSelectionBtn = el('clearAllSelection');
if (clearAllSelectionBtn) {
clearAllSelectionBtn.addEventListener('click', () => {
if (fileTree) fileTree.clearSelection();
});
}
// Search functionality
const searchInput = el('search');
const clearSearchBtn = el('clearSearch');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
if (fileTree) {
fileTree.search(e.target.value);
}
});
}
if (clearSearchBtn) {
clearSearchBtn.addEventListener('click', () => {
if (searchInput) {
searchInput.value = '';
if (fileTree) {
fileTree.search('');
}
}
});
}
// Prompt generation
const generatePromptBtn = el('generatePrompt');
if (generatePromptBtn) {
generatePromptBtn.addEventListener('click', generatePrompt);
}
const copyPromptBtn = el('copyPrompt');
if (copyPromptBtn) {
copyPromptBtn.addEventListener('click', copyPrompt);
}
// Workspace creation modal
const wsCreateBtn = el('wsCreateBtn');
if (wsCreateBtn) {
wsCreateBtn.addEventListener('click', () => {
const nameEl = el('wcName');
const errorEl = el('wcError');
if (nameEl) nameEl.value = '';
if (errorEl) errorEl.textContent = '';
showModal('wsCreate');
});
}
const wcCreateBtn = el('wcCreate');
if (wcCreateBtn) {
wcCreateBtn.addEventListener('click', async () => {
const name = el('wcName')?.value?.trim() || '';
const errorEl = el('wcError');
if (!name) {
if (errorEl) errorEl.textContent = 'Workspace name is required.';
return;
}
const formData = { name: name };
const resp = await post('/api/heroprompt/workspaces', formData);
if (resp.error) {
if (errorEl) errorEl.textContent = resp.error;
return;
}
currentWs = resp.name || currentWs;
localStorage.setItem('heroprompt-current-ws', currentWs);
await reloadWorkspaces();
// Clear the file tree since new workspace has no directories yet
if (fileTree) {
const treeEl = el('tree');
if (treeEl) {
treeEl.innerHTML = `
No directories added yet
Use the "Add Dir" button to add directories to this workspace
`;
}
}
hideModal('wsCreate');
});
}
// Workspace details modal
const wsDetailsBtn = el('wsDetailsBtn');
if (wsDetailsBtn) {
wsDetailsBtn.addEventListener('click', async () => {
const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
if (info && !info.error) {
const nameEl = el('wdName');
const errorEl = el('wdError');
if (nameEl) nameEl.value = info.name || currentWs;
if (errorEl) errorEl.textContent = '';
showModal('wsDetails');
}
});
}
// Workspace details update
const wdUpdateBtn = el('wdUpdate');
if (wdUpdateBtn) {
wdUpdateBtn.addEventListener('click', async () => {
const name = el('wdName')?.value?.trim() || '';
const errorEl = el('wdError');
if (!name) {
if (errorEl) errorEl.textContent = 'Workspace name is required.';
return;
}
const result = await updateWorkspace(currentWs, name);
if (result.error) {
if (errorEl) errorEl.textContent = result.error;
return;
}
hideModal('wsDetails');
});
}
// Workspace details delete
const wdDeleteBtn = el('wdDelete');
if (wdDeleteBtn) {
wdDeleteBtn.addEventListener('click', async () => {
showConfirmationModal(`Are you sure you want to delete workspace "${currentWs}"?`, async () => {
const result = await deleteWorkspace(currentWs);
if (result.error) {
const errorEl = el('wdError');
if (errorEl) errorEl.textContent = result.error;
return;
}
hideModal('wsDetails');
});
});
}
// Add Directory functionality
const addDirBtn = el('addDirBtn');
if (addDirBtn) {
addDirBtn.addEventListener('click', () => {
const pathEl = el('addDirPath');
const errorEl = el('addDirError');
if (pathEl) pathEl.value = '';
if (errorEl) errorEl.textContent = '';
showModal('addDirModal');
});
}
const addDirConfirm = el('addDirConfirm');
if (addDirConfirm) {
addDirConfirm.addEventListener('click', async () => {
const path = el('addDirPath')?.value?.trim() || '';
const errorEl = el('addDirError');
if (!path) {
if (errorEl) errorEl.textContent = 'Directory path is required.';
return;
}
// Add directory via API
const result = await post(`/api/heroprompt/workspaces/${encodeURIComponent(currentWs)}/dirs`, {
path: path
});
if (result.error) {
if (errorEl) errorEl.textContent = result.error;
return;
}
// Success - close modal and refresh the file tree
hideModal('addDirModal');
// Reload workspace directories to show the newly added directory
await loadWorkspaceDirectories();
// Show success message
showStatus('Directory added successfully!', 'success');
});
}
// Chat functionality
initChatInterface();
});
// Chat Interface Implementation
function initChatInterface() {
const chatInput = el('chatInput');
const sendBtn = el('sendChat');
const messagesContainer = el('chatMessages');
const charCount = el('charCount');
const chatStatus = el('chatStatus');
const typingIndicator = el('typingIndicator');
const newChatBtn = el('newChatBtn');
const chatList = el('chatList');
let chatHistory = [];
let isTyping = false;
let conversations = JSON.parse(localStorage.getItem('heroprompt-conversations') || '[]');
let currentConversationId = null;
// Initialize chat input functionality
if (chatInput && sendBtn) {
// Auto-resize textarea
chatInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
// Update character count
if (charCount) {
const count = this.value.length;
charCount.textContent = count;
charCount.className = 'char-count';
if (count > 2000) charCount.classList.add('warning');
if (count > 4000) charCount.classList.add('error');
}
// Enable/disable send button
sendBtn.disabled = this.value.trim().length === 0;
});
// Handle Enter key
chatInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!sendBtn.disabled) {
sendMessage();
}
}
});
// Send button click
sendBtn.addEventListener('click', sendMessage);
}
// Chat action buttons
const clearChatBtn = el('clearChat');
const exportChatBtn = el('exportChat');
if (newChatBtn) {
newChatBtn.addEventListener('click', startNewChat);
}
if (clearChatBtn) {
clearChatBtn.addEventListener('click', clearChat);
}
if (exportChatBtn) {
exportChatBtn.addEventListener('click', exportChat);
}
async function sendMessage() {
const message = chatInput.value.trim();
if (!message || isTyping) return;
// Add user message to chat
addMessage('user', message);
chatInput.value = '';
chatInput.style.height = 'auto';
sendBtn.disabled = true;
if (charCount) charCount.textContent = '0';
// Show typing indicator
showTypingIndicator();
updateChatStatus('typing', 'AI is thinking...');
try {
// Simulate API call - replace with actual API endpoint
const response = await simulateAIResponse(message);
// Hide typing indicator
hideTypingIndicator();
// Add AI response
addMessage('assistant', response);
updateChatStatus('ready', 'Ready');
} catch (error) {
hideTypingIndicator();
addMessage('assistant', 'Sorry, I encountered an error. Please try again.');
updateChatStatus('error', 'Error occurred');
console.error('Chat error:', error);
}
}
function addMessage(role, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${role}`;
const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
messageDiv.innerHTML = `
${formatMessageContent(content)}
${timestamp}
${role === 'assistant' ? `
` : ''}
`;
messageDiv.id = messageId;
// Remove welcome message if it exists
const welcomeMessage = messagesContainer.querySelector('.welcome-message');
if (welcomeMessage) {
welcomeMessage.remove();
}
messagesContainer.appendChild(messageDiv);
// Store in chat history
chatHistory.push({
id: messageId,
role: role,
content: content,
timestamp: new Date().toISOString()
});
// Save to conversation
if (window.saveMessageToConversation) {
window.saveMessageToConversation(role, content);
}
// Auto-scroll to bottom
scrollToBottom();
}
function formatMessageContent(content) {
// Basic markdown-like formatting
let formatted = content
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/```([\s\S]*?)```/g, '
$1
')
.replace(/\n/g, ' ');
return formatted;
}
function showTypingIndicator() {
if (typingIndicator) {
typingIndicator.style.display = 'flex';
isTyping = true;
}
}
function hideTypingIndicator() {
if (typingIndicator) {
typingIndicator.style.display = 'none';
isTyping = false;
}
}
function updateChatStatus(type, message) {
if (chatStatus) {
chatStatus.textContent = message;
chatStatus.className = `chat-status ${type}`;
}
}
function scrollToBottom() {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
function startNewChat() {
clearChat();
addMessage('assistant', 'Hello! I\'m ready to help you with your code. What would you like to know?');
}
function clearChat() {
chatHistory = [];
if (messagesContainer) {
messagesContainer.innerHTML = `
Welcome to AI Assistant
I'm here to help you with your code! You can:
Ask questions about your selected files
Request code explanations and improvements
Get suggestions for best practices
Debug issues and optimize performance
Select some files from the explorer and start chatting!
`;
}
updateChatStatus('ready', 'Ready');
}
function exportChat() {
if (chatHistory.length === 0) {
alert('No chat history to export');
return;
}
const exportData = {
timestamp: new Date().toISOString(),
messages: chatHistory,
workspace: currentWs,
selectedFiles: Array.from(selected)
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chat-export-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Simulate AI response - replace with actual API call
async function simulateAIResponse(userMessage) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
// Get context from selected files
const context = selected.size > 0 ?
`Based on your selected files (${Array.from(selected).join(', ')}), ` : '';
// Simple response generation - replace with actual AI API
const responses = [
`${context}I can help you analyze and improve your code. What specific aspect would you like me to focus on?`,
`${context}I notice you're working with these files. Would you like me to review the code structure or suggest improvements?`,
`${context}I can help explain the code, identify potential issues, or suggest optimizations. What would you like to know?`,
`${context}Let me analyze your code and provide insights. Is there a particular functionality you'd like me to examine?`
];
if (userMessage.toLowerCase().includes('error') || userMessage.toLowerCase().includes('bug')) {
return `${context}I can help you debug issues. Please share the specific error message or describe the unexpected behavior you're experiencing.`;
}
if (userMessage.toLowerCase().includes('optimize') || userMessage.toLowerCase().includes('performance')) {
return `${context}For performance optimization, I can analyze your code for bottlenecks, suggest algorithmic improvements, and recommend best practices.`;
}
if (userMessage.toLowerCase().includes('explain') || userMessage.toLowerCase().includes('how')) {
return `${context}I'd be happy to explain the code functionality. Which specific part would you like me to break down?`;
}
return responses[Math.floor(Math.random() * responses.length)];
}
}
// Global helper function for message formatting
function formatMessageContent(content) {
// Basic markdown-like formatting
let formatted = content
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/```([\s\S]*?)```/g, '
$1
')
.replace(/\n/g, ' ');
return formatted;
}
// Global functions for message actions
function copyMessage(messageId) {
const messageEl = document.getElementById(messageId);
if (!messageEl) return;
const textEl = messageEl.querySelector('.message-text');
if (!textEl) return;
const text = textEl.textContent || textEl.innerText;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
showMessageFeedback(messageId, 'Copied!');
}).catch(err => {
console.error('Copy failed:', err);
fallbackCopyToClipboard(text);
});
} else {
fallbackCopyToClipboard(text);
}
}
function regenerateMessage(messageId) {
const messageEl = document.getElementById(messageId);
if (!messageEl) return;
// Find the previous user message
let prevMessage = messageEl.previousElementSibling;
while (prevMessage && !prevMessage.classList.contains('user')) {
prevMessage = prevMessage.previousElementSibling;
}
if (prevMessage) {
const userText = prevMessage.querySelector('.message-text').textContent;
// Remove the current AI message
messageEl.remove();
// Show typing indicator and regenerate
const typingIndicator = el('typingIndicator');
if (typingIndicator) {
typingIndicator.style.display = 'flex';
}
// Simulate regeneration
setTimeout(async () => {
try {
const response = await simulateAIResponse(userText);
if (typingIndicator) {
typingIndicator.style.display = 'none';
}
// Create a new message manually
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message assistant';
const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const newMessageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
messageDiv.innerHTML = `
${formatMessageContent(response)}
${timestamp}
`;
messageDiv.id = newMessageId;
const messagesContainer = el('chatMessages');
if (messagesContainer) {
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
} catch (error) {
if (typingIndicator) {
typingIndicator.style.display = 'none';
}
console.error('Regeneration error:', error);
}
}, 1500);
}
}
function showMessageFeedback(messageId, text) {
const messageEl = document.getElementById(messageId);
if (!messageEl) return;
const actionsEl = messageEl.querySelector('.message-actions');
if (!actionsEl) return;
const originalHTML = actionsEl.innerHTML;
actionsEl.innerHTML = `${text}`;
setTimeout(() => {
actionsEl.innerHTML = originalHTML;
}, 2000);
}
// Chat List Management Functions
function initChatList() {
const chatList = el('chatList');
const newChatBtn = el('newChatBtn');
if (!chatList) return;
let conversations = JSON.parse(localStorage.getItem('heroprompt-conversations') || '[]');
let currentConversationId = localStorage.getItem('heroprompt-current-conversation') || null;
function renderChatList() {
if (conversations.length === 0) {
chatList.innerHTML = `