Implement multi-tasking (you can chat with a horny catgirl and chad thundercock at the same time)

This commit is contained in:
2026-01-07 15:46:38 +01:00
parent 2a99f79368
commit e61a5ae49d
4 changed files with 810 additions and 175 deletions
+190 -60
View File
@@ -15,9 +15,15 @@ const msgInput = document.getElementById("msgInput");
const sendBtn = document.getElementById("send");
const authScreens = document.querySelectorAll(".auth-screen");
const toggleButtons = document.querySelectorAll(".auth-toggle .toggle");
const chatList = document.getElementById("chatList");
const newChatBtn = document.getElementById("newChat");
const renameChatBtn = document.getElementById("renameChat");
const deleteChatBtn = document.getElementById("deleteChat");
const activeChatTitle = document.getElementById("activeChatTitle");
let isProcessing = false;
let chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || [];
let chatSessions = [];
let activeChatId = null;
const displayModels = async () => {
const select = document.getElementById("model");
@@ -40,12 +46,12 @@ const displayModels = async () => {
});
}
async function post(url, data) {
async function request(url, data, method = "POST") {
try {
const res = await fetch(url, {
method: "POST",
method,
headers: {"Content-Type": "application/json"},
body: JSON.stringify(data)
body: data ? JSON.stringify(data) : undefined
});
return await res.json();
} catch (err) {
@@ -53,6 +59,10 @@ async function post(url, data) {
}
}
async function post(url, data) {
return request(url, data, "POST");
}
async function get(url) {
try {
const res = await fetch(url);
@@ -69,50 +79,35 @@ function setAuthMessage(msg, state = "") {
function setMessageContent(element, content) {
if (window.marked && window.DOMPurify) {
const rendered = marked.parse(content, { breaks: true });
const rendered = marked.parse(content, {breaks: true});
element.innerHTML = DOMPurify.sanitize(rendered);
return;
}
element.textContent = content;
}
function addMessage(content, isUser = false, isLoading = false, saveToHistory = true) {
const msgDiv = document.createElement('div');
msgDiv.className = `msg ${isUser ? 'user' : 'bot'}${isLoading ? ' loading' : ''}`;
if (isLoading) {
msgDiv.innerHTML = `
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
`;
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
`;
} else {
const header = document.createElement('div');
header.className = 'msg-header';
header.textContent = isUser ? 'You' : 'DeepFake';
const body = document.createElement('div');
body.className = 'msg-content';
setMessageContent(body, content);
msgDiv.appendChild(header);
msgDiv.appendChild(body);
if (saveToHistory) {
chatHistory.push({
content: content,
isUser: isUser,
timestamp: Date.now()
});
localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
}
}
messagesDiv.appendChild(msgDiv);
setTimeout(() => {
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}, 100);
@@ -120,17 +115,57 @@ function addMessage(content, isUser = false, isLoading = false, saveToHistory =
return msgDiv;
}
function loadChatHistory() {
function clearMessages() {
messagesDiv.innerHTML = '';
chatHistory.forEach(msg => {
addMessage(msg.content, msg.isUser, false, false);
});
}
function clearChatHistory() {
chatHistory = [];
localStorage.removeItem('chatHistory');
messagesDiv.innerHTML = '';
function getChatById(chatId) {
return chatSessions.find(chat => chat.id === chatId);
}
function bumpChat(chatId) {
const index = chatSessions.findIndex(chat => chat.id === chatId);
if (index <= 0) return;
const [chat] = chatSessions.splice(index, 1);
chatSessions.unshift(chat);
renderChatList();
}
function updateChatActionState() {
const hasChat = Boolean(activeChatId);
renameChatBtn.disabled = !hasChat;
deleteChatBtn.disabled = !hasChat;
}
function renderChatList() {
chatList.innerHTML = '';
if (!chatSessions.length) {
const empty = document.createElement('p');
empty.className = 'status';
empty.textContent = 'No chats yet. Start a new one!';
chatList.appendChild(empty);
return;
}
chatSessions.forEach(chat => {
const item = document.createElement('div');
item.className = `chat-item${chat.id === activeChatId ? ' active' : ''}`;
const title = document.createElement('span');
title.textContent = chat.title;
const actions = document.createElement('button');
actions.type = 'button';
actions.textContent = '✕';
actions.title = 'Delete chat';
actions.addEventListener('click', (event) => {
event.stopPropagation();
deleteChat(chat.id);
});
item.appendChild(title);
item.appendChild(actions);
item.addEventListener('click', () => setActiveChat(chat.id));
chatList.appendChild(item);
});
}
function setLoadingState(loading) {
@@ -157,12 +192,96 @@ function showAuthScreen(target) {
setAuthMessage('', '');
}
function setActiveChat(chatId) {
activeChatId = chatId;
const chat = getChatById(chatId);
activeChatTitle.textContent = chat ? chat.title : 'New chat';
updateChatActionState();
renderChatList();
if (chatId) {
loadChatMessages(chatId);
} else {
clearMessages();
}
}
async function loadChatSessions() {
const res = await get('/chats');
if (res.error) return;
chatSessions = res.chats || [];
renderChatList();
const activeExists = chatSessions.find(chat => chat.id === activeChatId);
if (activeExists) {
setActiveChat(activeChatId);
return;
}
if (!chatSessions.length) {
await createNewChat();
return;
}
setActiveChat(chatSessions[0].id);
}
async function loadChatMessages(chatId) {
const res = await get(`/chats/${chatId}/messages`);
if (res.error) return;
clearMessages();
(res.messages || []).forEach(msg => {
addMessage(msg.content, msg.role === 'user', false, false);
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
async function createNewChat() {
const res = await post('/chats', {title: 'New chat'});
if (res.error || !res.chat) return;
chatSessions.unshift(res.chat);
setActiveChat(res.chat.id);
renderChatList();
}
async function renameChat(chatId) {
const chat = getChatById(chatId);
if (!chat) return;
const nextTitle = prompt('Rename chat', chat.title);
if (!nextTitle) return;
const trimmedTitle = nextTitle.trim();
if (!trimmedTitle) return;
const res = await request(`/chats/${chatId}`, {title: trimmedTitle}, "PUT");
if (res.error) return;
chat.title = trimmedTitle;
activeChatTitle.textContent = trimmedTitle;
renderChatList();
}
async function deleteChat(chatId) {
const chat = getChatById(chatId);
if (!chat) return;
const confirmDelete = confirm(`Delete "${chat.title}"? This cannot be undone.`);
if (!confirmDelete) return;
const res = await request(`/chats/${chatId}`, null, "DELETE");
if (res.error) return;
chatSessions = chatSessions.filter(item => item.id !== chatId);
if (activeChatId === chatId) {
if (chatSessions.length) {
setActiveChat(chatSessions[0].id);
} else {
activeChatId = null;
await createNewChat();
}
}
renderChatList();
}
async function checkSession() {
const res = await get('/session');
if (res.user) {
authDiv.style.display = "none";
chatDiv.style.display = "grid";
loadChatHistory();
chatDiv.style.display = "block";
await loadChatSessions();
msgInput.focus();
return true;
}
@@ -170,23 +289,11 @@ async function checkSession() {
}
async function loadServerChat() {
const res = await get("/chat/history");
if (res.history && res.history.length) {
messagesDiv.innerHTML = '';
res.history.forEach(msg => {
addMessage(
msg.content,
msg.role === "user",
false,
false
);
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
await loadChatSessions();
}
async function handleAuth(endpoint, credentials) {
const { username, password } = credentials;
const {username, password} = credentials;
const submitBtn = endpoint === "login" ? loginSubmit : registerSubmit;
if (!username || !password) {
@@ -207,7 +314,7 @@ async function handleAuth(endpoint, credentials) {
if (endpoint === "login") {
setAuthMessage("Login successful! Redirecting to chat...", 'success');
authDiv.style.display = "none";
chatDiv.style.display = "grid";
chatDiv.style.display = "block";
await loadServerChat();
msgInput.focus();
} else {
@@ -223,6 +330,17 @@ async function sendMessage() {
if (isProcessing) return;
const msg = msgInput.value.trim();
if (!msg) return;
if (!activeChatId) {
await createNewChat();
}
const currentChat = getChatById(activeChatId);
if (currentChat && currentChat.title === 'New chat') {
const autoTitle = msg.length > 40 ? `${msg.slice(0, 40)}…` : msg;
currentChat.title = autoTitle;
activeChatTitle.textContent = autoTitle;
renderChatList();
await request(`/chats/${activeChatId}`, {title: autoTitle}, "PUT");
}
addMessage(msg, true);
msgInput.value = "";
msgInput.style.height = 'auto';
@@ -230,12 +348,13 @@ async function sendMessage() {
const loadingMsg = addMessage('', false, true, false);
try {
const model = document.getElementById("model").value;
const res = await post("/chat", {message: msg, model});
const res = await post("/chat", {message: msg, model, chatId: activeChatId});
if (messagesDiv.contains(loadingMsg)) {
messagesDiv.removeChild(loadingMsg);
}
if (res.reply) {
addMessage(res.reply, false);
bumpChat(activeChatId);
} else {
addMessage(`[Error: ${res.error}]`, false);
}
@@ -274,7 +393,9 @@ document.getElementById("logout").onclick = async () => {
await post("/logout", {});
chatDiv.style.display = "none";
authDiv.style.display = "grid";
clearChatHistory();
clearMessages();
chatSessions = [];
activeChatId = null;
setAuthMessage("Logged out.", 'success');
showAuthScreen('login');
loginUsernameInput.value = "";
@@ -290,16 +411,25 @@ msgInput.addEventListener("keydown", (e) => {
}
});
document.getElementById("clearChat").onclick = async () => {
await post("/chat/clear", {});
clearChatHistory();
};
newChatBtn.addEventListener('click', () => createNewChat());
msgInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
renameChatBtn.addEventListener('click', () => {
if (activeChatId) {
renameChat(activeChatId);
}
});
deleteChatBtn.addEventListener('click', () => {
if (activeChatId) {
deleteChat(activeChatId);
}
});
+
msgInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
window.addEventListener('load', () => {
checkSession();
displayModels();