`;
} else {
@@ -448,9 +587,37 @@
-
+
${renderMarkdown(content)}
+
+
+
+
+
+
+
+
`;
@@ -471,18 +638,14 @@
function appendToMessage(element, content) {
const contentDiv = element.querySelector('.prose');
if (contentDiv) {
- // Check if it's a thinking phase message
const currentText = contentDiv.textContent;
- // Handle thinking phase specially
- if (content.includes('[Thinking') || content.includes('[Searching') || content.includes('[Generating')) {
+ if (content.includes('[Thinking') || content.includes('[Searching') || content.includes('[Generating') || content.includes('[Creating')) {
contentDiv.classList.add('thinking-content', 'rounded-lg', 'px-4', 'py-2');
} else if (!content.includes('[') && contentDiv.classList.contains('thinking-content')) {
- // Remove thinking style for regular content
contentDiv.classList.remove('thinking-content');
}
- // Append content
const fullContent = currentText + content;
contentDiv.innerHTML = renderMarkdown(fullContent);
@@ -491,11 +654,10 @@
}
function renderMarkdown(text) {
- // Simple markdown rendering
let html = escapeHtml(text);
// Code blocks
- html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
');
+ html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
');
// Inline code
html = html.replace(/`([^`]+)`/g, '
$1');
@@ -512,6 +674,145 @@
return html;
}
+ // Message Actions
+ function copyMessage(msgId) {
+ const wrapper = document.querySelector(`[data-msg-id="${msgId}"]`);
+ const contentEl = wrapper?.querySelector('.user-content, .assistant-content');
+ if (contentEl) {
+ navigator.clipboard.writeText(contentEl.textContent).then(() => {
+ showToast('Copied to clipboard');
+ });
+ }
+ }
+
+ function editMessage(msgId) {
+ const wrapper = document.querySelector(`[data-msg-id="${msgId}"]`);
+ const contentEl = wrapper?.querySelector('.user-content');
+ if (!contentEl) return;
+
+ const originalContent = contentEl.textContent;
+
+ // Create edit form
+ const editForm = document.createElement('div');
+ editForm.className = 'mt-2';
+ editForm.innerHTML = `
+
+
+
+
+
+ `;
+
+ contentEl.style.display = 'none';
+ wrapper.querySelector('.bg-balloon-purple\\/30').appendChild(editForm);
+
+ // Focus textarea
+ const textarea = editForm.querySelector('textarea');
+ textarea.focus();
+ textarea.selectionStart = textarea.value.length;
+
+ // Save handler
+ editForm.querySelector('.save-edit').addEventListener('click', () => {
+ const newContent = textarea.value.trim();
+ if (newContent && newContent !== originalContent) {
+ contentEl.textContent = newContent;
+ lastUserMessage = newContent;
+ // Update history
+ const idx = messageHistory.findIndex(m => m.role === 'user' && m.content === originalContent);
+ if (idx !== -1) messageHistory[idx].content = newContent;
+
+ // Remove all messages after this one
+ let found = false;
+ const toRemove = [];
+ messagesList.querySelectorAll('.message-wrapper').forEach(el => {
+ if (found) toRemove.push(el);
+ if (el.dataset.msgId === msgId) found = true;
+ });
+ toRemove.forEach(el => el.remove());
+
+ // Regenerate response
+ generateResponse(true);
+ }
+ contentEl.style.display = '';
+ editForm.remove();
+ });
+
+ // Cancel handler
+ editForm.querySelector('.cancel-edit').addEventListener('click', () => {
+ contentEl.style.display = '';
+ editForm.remove();
+ });
+ }
+
+ function regenerateResponse() {
+ if (isGenerating) return;
+
+ // Remove last assistant message
+ const lastAssistant = messageHistory.findLast(m => m.role === 'assistant');
+ if (lastAssistant) {
+ const wrappers = messagesList.querySelectorAll('.message-wrapper');
+ for (let i = wrappers.length - 1; i >= 0; i--) {
+ if (wrappers[i].dataset.role === 'assistant') {
+ wrappers[i].remove();
+ break;
+ }
+ }
+ messageHistory = messageHistory.filter(m => m !== lastAssistant);
+ }
+
+ generateResponse(true);
+ }
+
+ function continueGeneration() {
+ if (isGenerating) return;
+
+ // Add a continuation prompt
+ messageHistory.push({ role: 'user', content: 'Continue' });
+ lastUserMessage = 'Continue';
+ generateResponse();
+ }
+
+ function rateMessage(msgId, rating) {
+ const wrapper = document.querySelector(`[data-msg-id="${msgId}"]`);
+ const positiveBtn = wrapper?.querySelector('.positive');
+ const negativeBtn = wrapper?.querySelector('.negative');
+
+ if (rating === 'positive') {
+ positiveBtn?.classList.toggle('active');
+ negativeBtn?.classList.remove('active');
+ showToast('Thanks for the feedback!');
+ } else {
+ negativeBtn?.classList.toggle('active');
+ positiveBtn?.classList.remove('active');
+ showToast('Thanks for the feedback - we\'ll work on improving');
+ }
+
+ // TODO: Send rating to backend for analytics
+ }
+
+ function editChatTitle() {
+ const currentTitle = chatTitle.textContent;
+ const newTitle = prompt('Enter new chat title:', currentTitle);
+ if (newTitle && newTitle !== currentTitle && currentConversationId) {
+ fetch(`/api/conversations/${currentConversationId}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ title: newTitle })
+ }).then(() => {
+ chatTitle.textContent = newTitle;
+ loadConversations();
+ });
+ }
+ }
+
+ function showToast(message) {
+ const toast = document.createElement('div');
+ toast.className = 'copy-toast';
+ toast.textContent = message;
+ document.body.appendChild(toast);
+ setTimeout(() => toast.remove(), 2000);
+ }
+
function scrollToBottom() {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
@@ -540,7 +841,6 @@
const data = await response.json();
attachments.push(data.id);
- // Show preview
const preview = document.createElement('div');
preview.className = 'relative group';
@@ -557,7 +857,6 @@
`;
}
- // Remove button
const removeBtn = document.createElement('button');
removeBtn.className = 'absolute -top-2 -right-2 w-5 h-5 bg-balloon-red rounded-full text-xs opacity-0 group-hover:opacity-100 transition-opacity';
removeBtn.innerHTML = '×';
@@ -581,6 +880,7 @@
async function startNewChat() {
currentConversationId = null;
+ messageHistory = [];
messagesList.innerHTML = '';
welcomeMessage.classList.remove('hidden');
chatTitle.textContent = 'New Chat';