Files
webpage/static/chat.js

278 lines
9.5 KiB
JavaScript

const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('user-input');
let messageHistory = [];
let isFirstMessage = true;
const MAX_CHAR_LIMIT = 24000; // Dein Volumen-Limit in Zeichen (ca. 2000-3000 Token)
// Hilfsfunktion: Berechnet das aktuelle Volumen der History
function getHistoryVolume() {
return messageHistory.reduce((sum, msg) => sum + msg.content.length, 0);
}
function safeScrollToBottom() {
if (isEditingSystemPrompt) return; // Wenn wir editieren, tun wir GAR NICHTS
const chatMessages = document.getElementById('chat-messages');
if (chatMessages) {
chatMessages.scrollTo({
top: chatMessages.scrollHeight,
behavior: 'smooth'
});
}
}
async function sendMessage() {
const userInput = document.getElementById('user-input');
const sendBtn = document.querySelector('.send-btn');
const systemInput = document.getElementById('system-prompt-input');
const systemContainer = document.getElementById('system-prompt-container');
const chatMessages = document.getElementById('chat-messages');
const text = userInput.value.trim();
if (!text) return;
// --- 1. VOLUMEN-CHECK VOR DEM SENDEN ---
// Wir prüfen das aktuelle Volumen + die neue Nachricht
const currentVolume = getHistoryVolume();
if (currentVolume + text.length > MAX_CHAR_LIMIT) {
appendMessage('system', "⚠️ Kontext-Limit erreicht. Das Volumen der Konversation ist zu groß für die Demo.");
userInput.disabled = true;
sendBtn.disabled = true;
return;
}
// --- 1. UI SPERREN (Warte-Modus) ---
userInput.disabled = true;
sendBtn.disabled = true;
const originalBtnText = sendBtn.innerText;
sendBtn.innerText = '...';
// --- 2. SYSTEM-PROMPT LOGIK (Nur beim ersten Mal) ---
if (isFirstMessage) {
const systemText = systemInput.value.trim();
// 1. In die API-History pushen
messageHistory.push({ "role": "system", "content": systemText });
// 2. Optisch in den Chat einfügen (als System-Nachricht)
appendMessage('system', "⚙️ System-Prompt\n" + systemText);
// Editor ausblenden
if (systemContainer) systemContainer.classList.add('locked');
isFirstMessage = false;
}
// --- 3. USER-NACHRICHT ---
messageHistory.push({ "role": "user", "content": text });
appendMessage('user', text);
userInput.value = '';
// --- 4. LADE-ANIMATION ANZEIGEN ---
const typingDiv = document.createElement('div');
typingDiv.className = 'message typing-indicator';
typingDiv.id = 'temp-typing';
typingDiv.innerHTML = '<span></span><span></span><span></span>';
chatMessages.appendChild(typingDiv);
safeScrollToBottom();
try {
const response = await fetch('/api/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"model": "trinity-test",
"messages": messageHistory,
"stream": false
})
});
if (!response.ok) throw new Error('Server antwortet nicht');
const data = await response.json();
// Animation entfernen
const temp = document.getElementById('temp-typing');
if (temp) temp.remove();
// Antwort verarbeiten
const aiMessage = data.choices[0].message;
messageHistory.push(aiMessage);
appendMessage('ai', aiMessage.content);
// --- 3. STATUS-UPDATE FÜR DEN NUTZER ---
const newTotalVolume = getHistoryVolume();
const fillPercentage = Math.round((newTotalVolume / MAX_CHAR_LIMIT) * 100);
console.log(`Kontext-Auslastung: ${fillPercentage}%`);
if (newTotalVolume > MAX_CHAR_LIMIT * 0.9) {
appendMessage('system', `Achtung: Der Speicher ist zu ${fillPercentage}% voll.`);
}
} catch (err) {
const temp = document.getElementById('temp-typing');
if (temp) temp.remove();
appendMessage('ai', 'Fehler: ' + err.message);
console.error(err);
} finally {
// UI nur freigeben, wenn noch Platz ist
if (getHistoryVolume() < MAX_CHAR_LIMIT) {
userInput.disabled = false;
sendBtn.disabled = false;
sendBtn.innerText = originalBtnText;
userInput.focus();
} else {
userInput.placeholder = "Kontext voll.";
}
}
}
// Erweitere deine appendMessage Funktion um den 'system' Typ
function appendMessage(role, text) {
const chatMessages = document.getElementById('chat-messages');
const msgDiv = document.createElement('div');
// role kann 'user', 'ai' oder 'system' sein
msgDiv.className = `message ${role}-message`;
msgDiv.innerText = text;
chatMessages.appendChild(msgDiv);
safeScrollToBottom();
}
// ======================================================
// Navigations-Logik für Chat
function setView(viewMode) {
const main = document.getElementById('chatMain'); // Sicherstellen, dass <main id="chatMain"> existiert!
const nav = document.getElementById('navLinks'); // Konsistent mit common.js
if (!main) return;
main.classList.remove('view-chat-only', 'view-article-only', 'view-split');
main.classList.add('view-' + viewMode);
if (nav) nav.classList.remove('mobile-open');
}
// Auch die toggle-Funktion für den Chat-Hamburger:
function toggleViewMenu() {
const nav = document.getElementById('navLinks');
if (nav) nav.classList.toggle('mobile-open');
}
// Initialisierung
document.addEventListener('DOMContentLoaded', () => {
// Erzwingt Split-View beim Start
//setView('split');
// ... dein bestehender Code ...
const savedTheme = localStorage.getItem('theme') || 'auto';
applyTheme(savedTheme);
const navLinksContainer = document.getElementById('navLinks');
const hamburger = document.querySelector('.hamburger');
if (navLinksContainer && hamburger) {
const navLinks = navLinksContainer.querySelectorAll('a');
navLinks.forEach(link => {
link.addEventListener('click', () => {
if (navLinksContainer.classList.contains('mobile-open')) {
navLinksContainer.classList.remove('mobile-open');
hamburger.classList.remove('active');
}
});
});
}
const resizer = document.getElementById('dragMe');
const leftSide = document.getElementById('resizableArticle');
//const leftSide = document.querySelector('.chat-article');
const container = document.getElementById('chatMain');
if (resizer && leftSide && container) {
resizer.addEventListener('pointerdown', function(e) {
document.body.classList.add('resizing');
e.preventDefault();
const startX = e.clientX;
const startWidth = leftSide.getBoundingClientRect().width;
const containerWidth = container.getBoundingClientRect().width;
//document.body.style.cursor = 'col-resize';
//leftSide.style.userSelect = 'none';
document.body.classList.add('resizing');
function onPointerMove(e) {
const deltaX = e.clientX - startX;
let newWidthPercent = ((startWidth + deltaX) / containerWidth) * 100;
newWidthPercent = Math.max(15, Math.min(75, newWidthPercent));
leftSide.style.flexBasis = newWidthPercent + '%';
}
function onPointerUp() {
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
//document.body.style.removeProperty('cursor');
document.body.classList.remove('resizing');
leftSide.style.removeProperty('user-select');
}
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
});
}
const systemInput = document.getElementById('system-prompt-input');
if (systemInput) {
// Funktion zur Höhenanpassung
const autoGrow = (el) => {
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
};
// Beim Tippen mitwachsen
systemInput.addEventListener('input', () => autoGrow(systemInput));
// Initial beim Laden anpassen (falls schon Text drin steht)
autoGrow(systemInput);
}
});
let isEditingSystemPrompt = false;
const systemInput = document.getElementById('system-prompt-input');
const systemContainer = document.getElementById('system-prompt-container');
if (systemInput) {
systemInput.addEventListener('focus', () => {
if (window.innerWidth < 600) {
isEditingSystemPrompt = true;
systemContainer.classList.add('mobile-edit-mode');
document.body.style.overflow = 'hidden';
}
});
const exitEditMode = () => {
isEditingSystemPrompt = false;
systemContainer.classList.remove('mobile-edit-mode');
document.body.style.overflow = '';
// Einmaliges Auto-Grow nach dem Schließen
systemInput.style.height = "auto";
systemInput.style.height = systemInput.scrollHeight + "px";
};
systemInput.addEventListener('blur', () => {
// Delay, um dem Klick auf den Button Vorrang zu geben
setTimeout(exitEditMode, 200);
});
}