193 lines
7.9 KiB
JavaScript
193 lines
7.9 KiB
JavaScript
const chatBox = document.getElementById('chatBox');
|
|
const chatInput = document.getElementById('chatInput');
|
|
const sendButton = document.getElementById('sendButton');
|
|
const chatList = document.getElementById('chatList');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const usageText = document.getElementById('usage-text');
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
|
|
let currentAssistantMessageDiv = null; // To hold the streaming message div
|
|
|
|
export function displayMessage(text, sender, options = { isMarkdown: false, isStreaming: false }) {
|
|
// Sanitize text before inserting, especially if using innerHTML
|
|
// For user messages, simple textContent is safer. For Markdown, use DOMPurify.
|
|
const sanitizedText = sender === 'user' ? text : (options.isMarkdown ? DOMPurify.sanitize(marked.parse(text)) : text);
|
|
|
|
const messageElement = document.createElement('div');
|
|
messageElement.classList.add('chat-message', sender);
|
|
|
|
if (sender === 'assistant' && options.isMarkdown) {
|
|
messageElement.innerHTML = sanitizedText; // Use sanitized HTML
|
|
} else {
|
|
messageElement.textContent = sanitizedText; // Safer for plain text
|
|
}
|
|
|
|
const isScrolledToBottom = chatBox.scrollHeight - chatBox.clientHeight <= chatBox.scrollTop + 5; // Tolerance
|
|
|
|
// If starting a new streaming message, create the container
|
|
if (sender === 'assistant' && options.isStreaming && !currentAssistantMessageDiv) {
|
|
currentAssistantMessageDiv = document.createElement('div');
|
|
currentAssistantMessageDiv.classList.add('chat-message', 'assistant');
|
|
chatBox.appendChild(currentAssistantMessageDiv);
|
|
}
|
|
|
|
// Update or append message
|
|
if (currentAssistantMessageDiv && sender === 'assistant' && options.isStreaming) {
|
|
currentAssistantMessageDiv.innerHTML = sanitizedText; // Update streaming div
|
|
} else {
|
|
chatBox.appendChild(messageElement); // Append regular message or finalize stream
|
|
currentAssistantMessageDiv = null; // Reset stream div reference
|
|
}
|
|
|
|
// Scroll to bottom only if the user was already near the bottom
|
|
if (isScrolledToBottom) {
|
|
chatBox.scrollTop = chatBox.scrollHeight;
|
|
}
|
|
}
|
|
|
|
// Function to update the streaming message div content incrementally
|
|
export function updateStreamingMessage(contentChunk, isMarkdown = true) {
|
|
if (!currentAssistantMessageDiv) {
|
|
// Create the div if it doesn't exist (e.g., first chunk)
|
|
currentAssistantMessageDiv = document.createElement('div');
|
|
currentAssistantMessageDiv.classList.add('chat-message', 'assistant');
|
|
chatBox.appendChild(currentAssistantMessageDiv);
|
|
}
|
|
|
|
// Append chunk (if plain text) or re-render markdown (simpler for now)
|
|
// For true streaming of markdown, need more complex parsing
|
|
let currentContent = currentAssistantMessageDiv.dataset.rawContent || '';
|
|
currentContent += contentChunk;
|
|
currentAssistantMessageDiv.dataset.rawContent = currentContent; // Store raw text
|
|
|
|
if (isMarkdown && window.marked && window.DOMPurify) {
|
|
currentAssistantMessageDiv.innerHTML = DOMPurify.sanitize(marked.parse(currentContent));
|
|
} else {
|
|
currentAssistantMessageDiv.textContent = currentContent;
|
|
}
|
|
|
|
// Keep scrolled to bottom during streaming
|
|
chatBox.scrollTop = chatBox.scrollHeight;
|
|
}
|
|
|
|
// Function to finalize the streaming message div
|
|
export function finalizeStreamingMessage() {
|
|
if (currentAssistantMessageDiv) {
|
|
delete currentAssistantMessageDiv.dataset.rawContent; // Clean up temp data
|
|
}
|
|
currentAssistantMessageDiv = null;
|
|
}
|
|
|
|
|
|
export function clearChatBox() {
|
|
chatBox.innerHTML = '';
|
|
// Optionally add a default message
|
|
// displayMessage("Neuer Chat gestartet.", "assistant");
|
|
}
|
|
|
|
export function clearChatInput() {
|
|
chatInput.value = '';
|
|
autoResizeTextarea(); // Reset height
|
|
}
|
|
|
|
export function disableInput(isLoading = true) {
|
|
chatInput.disabled = true;
|
|
sendButton.disabled = true;
|
|
chatInput.placeholder = isLoading ? 'Warte auf Antwort...' : 'Schreibe eine Nachricht...';
|
|
if (isLoading && loadingIndicator) loadingIndicator.style.display = 'inline-block'; // Show spinner
|
|
if (isLoading) sendButton.style.display = 'none'; // Hide send button
|
|
}
|
|
|
|
export function enableInput() {
|
|
chatInput.disabled = false;
|
|
sendButton.disabled = false;
|
|
chatInput.placeholder = 'Schreibe eine Nachricht...';
|
|
if (loadingIndicator) loadingIndicator.style.display = 'none'; // Hide spinner
|
|
sendButton.style.display = 'flex'; // Show send button
|
|
chatInput.focus();
|
|
}
|
|
|
|
export function addChatToList(chatData, isActive, loadChatCallback, deleteChatCallback) {
|
|
const container = document.createElement('div');
|
|
container.classList.add('chat-session-button-container');
|
|
container.dataset.chatId = chatData.id; // Add ID to container for easier selection
|
|
if (isActive) {
|
|
container.classList.add('active');
|
|
}
|
|
|
|
const button = document.createElement('button');
|
|
button.classList.add('chat-session-button');
|
|
button.textContent = chatData.name || `Chat ${chatData.id.slice(-4)}`; // Use name or part of ID
|
|
button.addEventListener('click', () => loadChatCallback(chatData.id));
|
|
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.classList.add('delete-chat-button');
|
|
deleteBtn.setAttribute('aria-label', `Delete chat ${chatData.name || chatData.id}`);
|
|
deleteBtn.innerHTML = '🗑️'; // Use trash icon
|
|
deleteBtn.addEventListener('click', (event) => {
|
|
event.stopPropagation(); // Prevent chat loading
|
|
deleteChatCallback(chatData.id);
|
|
});
|
|
|
|
container.appendChild(button);
|
|
container.appendChild(deleteBtn);
|
|
// Prepend to the top of the list for newest first
|
|
chatList.insertBefore(container, chatList.firstChild);
|
|
}
|
|
|
|
export function removeChatFromList(chatId) {
|
|
const chatElement = chatList.querySelector(`.chat-session-button-container[data-chat-id="${chatId}"]`);
|
|
if (chatElement) {
|
|
chatElement.remove();
|
|
}
|
|
}
|
|
|
|
export function updateChatNameInList(chatId, name) {
|
|
const button = chatList.querySelector(`.chat-session-button-container[data-chat-id="${chatId}"] .chat-session-button`);
|
|
const deleteButton = chatList.querySelector(`.chat-session-button-container[data-chat-id="${chatId}"] .delete-chat-button`);
|
|
if (button) {
|
|
button.textContent = name || `Chat ${chatId.slice(-4)}`;
|
|
}
|
|
if (deleteButton) {
|
|
deleteButton.setAttribute('aria-label', `Delete chat ${name || chatId}`);
|
|
}
|
|
}
|
|
|
|
export function setActiveChatInList(chatId) {
|
|
document.querySelectorAll('.chat-session-button-container').forEach(container => {
|
|
container.classList.toggle('active', container.dataset.chatId === chatId);
|
|
});
|
|
}
|
|
|
|
export function updateProgressBar(currentUsage, maxUsage) {
|
|
// Ensure values are numbers and maxUsage is positive
|
|
currentUsage = Number(currentUsage) || 0;
|
|
maxUsage = Number(maxUsage) || 1; // Avoid division by zero
|
|
maxUsage = Math.max(1, maxUsage); // Ensure maxUsage is at least 1
|
|
|
|
const percentage = Math.min(100, Math.max(0, (currentUsage / maxUsage) * 100)); // Clamp between 0 and 100
|
|
|
|
if (progressBar) {
|
|
progressBar.style.width = `${percentage}%`;
|
|
}
|
|
if (usageText) {
|
|
// Format maxUsage for readability if large?
|
|
usageText.textContent = `Token Usage: ${currentUsage} / ${maxUsage}`;
|
|
}
|
|
}
|
|
|
|
// Auto-resize textarea helper
|
|
export function autoResizeTextarea() {
|
|
chatInput.style.overflowY = 'hidden'; // Prevent scrollbar flash
|
|
chatInput.style.height = 'auto';
|
|
const scrollHeight = chatInput.scrollHeight;
|
|
const maxHeight = parseInt(window.getComputedStyle(chatInput).maxHeight, 10);
|
|
|
|
if (maxHeight > 0 && scrollHeight > maxHeight) {
|
|
chatInput.style.height = `${maxHeight}px`;
|
|
chatInput.style.overflowY = 'auto';
|
|
} else {
|
|
chatInput.style.height = `${scrollHeight}px`;
|
|
chatInput.style.overflowY = 'hidden';
|
|
}
|
|
} |