Sessions: Sidebar, Session-Wechsel, Archivieren-Button, alles session-bezogen

This commit is contained in:
Meleki committed 2026-02-26 18:37:24 +01:00
1 parent 8ddee38032
commit 25f07caf83
15 files changed
+442 -78

No files matched your search

+30 -9
View File
@@ -1,31 +1,31 @@
// ── API Calls ──
async function apiChat(model, messages, systemPrompt) {
async function apiChat(model, messages, systemPrompt, sessionId) {
return fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, messages, systemPrompt })
body: JSON.stringify({ model, messages, systemPrompt, sessionId })
});
}
async function apiHistory(sessionId = 'null') {
async function apiHistory(sessionId) {
const resp = await fetch(`/api/history?sessionId=${sessionId}`);
return resp.json();
}
async function apiHistoryPoll(afterId) {
const resp = await fetch(`/api/history/poll?afterId=${afterId}`);
async function apiHistoryPoll(sessionId, afterId) {
const resp = await fetch(`/api/history/poll?sessionId=${sessionId}&afterId=${afterId}`);
return resp.json();
}
async function apiArchive() {
const resp = await fetch('/api/archive', { method: 'POST' });
async function apiArchive(sessionId) {
const resp = await fetch(`/api/archive?sessionId=${sessionId}`, { method: 'POST' });
if (resp.status === 204) return null;
return resp.json();
}
async function apiListArchives() {
const resp = await fetch('/api/archives');
async function apiListArchives(sessionId) {
const resp = await fetch(`/api/archives?sessionId=${sessionId}`);
return resp.json();
}
@@ -33,3 +33,24 @@ async function apiGetArchive(archiveId) {
const resp = await fetch(`/api/archive/${archiveId}`);
return resp.json();
}
// ── Session API ──
async function apiGetSessions() {
const resp = await fetch('/api/sessions');
return resp.json();
}
async function apiCreateSession() {
const resp = await fetch('/api/sessions', { method: 'POST' });
return resp.json();
}
async function apiUpdateSession(sessionId, data) {
const resp = await fetch(`/api/sessions/${sessionId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return resp.json();
}
+8 -6
View File
@@ -1,13 +1,18 @@
// ── App Init ──
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', async () => {
// Session initialisieren (vor allem anderen)
await initSession();
// System Prompt Toggle
$('#showSystem').addEventListener('change', () => {
$('#systemPrompt').classList.toggle('visible', $('#showSystem').checked);
});
// Send Button
// Buttons
$('#send').addEventListener('click', sendMessage);
$('#clearBtn').addEventListener('click', clearChat);
$('#newSessionBtn').addEventListener('click', createNewSession);
// Ctrl+Enter
$('#input').addEventListener('keydown', (e) => {
@@ -20,9 +25,6 @@ document.addEventListener('DOMContentLoaded', () => {
this.style.height = Math.min(this.scrollHeight, 150) + 'px';
});
// Clear Button
$('#clearBtn').addEventListener('click', clearChat);
// History laden
loadHistory();
await loadHistory();
});
+7 -8
View File
@@ -4,17 +4,16 @@ const messages = [];
let isStreaming = false;
async function clearChat() {
const archive = await apiArchive();
const archive = await apiArchive(getCurrentSessionId());
if (!archive) return;
messages.length = 0;
chatEl().innerHTML = '';
lastKnownMessageId = 0;
loadedArchiveCount = 0;
if (archive) {
setMeta(`Archiviert: ${archive.chainCount} Chains, ${archive.messageCount} Messages`);
showArchiveLoadButton();
} else {
setMeta('');
}
setMeta(`Archiviert: ${archive.chainCount} Chains, ${archive.messageCount} Messages`);
showArchiveLoadButton();
}
// ── Send Message ──
@@ -40,7 +39,7 @@ async function sendMessage() {
let currentToolBox = null;
try {
const resp = await apiChat(model, messages, systemPrompt);
const resp = await apiChat(model, messages, systemPrompt, getCurrentSessionId());
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
+5 -4
View File
@@ -6,12 +6,13 @@ let loadedArchiveCount = 0;
async function loadHistory() {
try {
const archives = await apiListArchives();
const sid = getCurrentSessionId();
const archives = await apiListArchives(sid);
if (archives.length > 0) {
showArchiveLoadButton();
}
const data = await apiHistory('null');
const data = await apiHistory(sid);
data.reverse();
for (const entry of data) {
@@ -82,7 +83,7 @@ function getOrCreateArchiveDivider() {
async function loadLatestArchive() {
try {
const archives = await apiListArchives();
const archives = await apiListArchives(getCurrentSessionId());
if (archives.length <= loadedArchiveCount) return;
const latest = archives[loadedArchiveCount];
@@ -163,7 +164,7 @@ async function pollHistory() {
if (isStreaming) return;
try {
const newMessages = await apiHistoryPoll(lastKnownMessageId);
const newMessages = await apiHistoryPoll(getCurrentSessionId(), lastKnownMessageId);
if (newMessages.length > 0) {
renderMessages(newMessages, true);
scrollDown();
+108
View File
@@ -0,0 +1,108 @@
// ── Session State ──
let currentSessionId = null;
function getCurrentSessionId() {
return currentSessionId;
}
async function initSession() {
// SessionId aus localStorage oder neue erstellen
const stored = localStorage.getItem('sessionId');
if (stored) {
currentSessionId = stored;
} else {
const session = await apiCreateSession();
currentSessionId = session.sessionId;
localStorage.setItem('sessionId', currentSessionId);
}
await refreshSessionList();
}
async function createNewSession() {
const session = await apiCreateSession();
currentSessionId = session.sessionId;
localStorage.setItem('sessionId', currentSessionId);
// Chat leeren und neu laden
messages.length = 0;
chatEl().innerHTML = '';
lastKnownMessageId = 0;
loadedArchiveCount = 0;
setMeta('');
await refreshSessionList();
await loadHistory();
}
async function switchSession(sessionId) {
if (sessionId === currentSessionId) return;
currentSessionId = sessionId;
localStorage.setItem('sessionId', currentSessionId);
// Chat leeren und Session laden
messages.length = 0;
chatEl().innerHTML = '';
lastKnownMessageId = 0;
loadedArchiveCount = 0;
setMeta('');
await refreshSessionList();
await loadHistory();
}
async function refreshSessionList() {
const sessions = await apiGetSessions();
const list = $('#sessionList');
list.innerHTML = '';
for (const s of sessions) {
const div = document.createElement('div');
div.className = 'session-item' + (s.sessionId === currentSessionId ? ' active' : '');
div.dataset.sessionId = s.sessionId;
const name = document.createElement('span');
name.className = 'session-name';
name.textContent = s.name || 'Unbenannt';
name.addEventListener('dblclick', () => renameSession(s.sessionId, name));
const date = document.createElement('span');
date.className = 'session-date';
date.textContent = new Date(s.updatedAt).toLocaleString('de-AT', {
day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'
});
div.appendChild(name);
div.appendChild(date);
div.addEventListener('click', () => switchSession(s.sessionId));
list.appendChild(div);
}
}
async function renameSession(sessionId, nameEl) {
const input = document.createElement('input');
input.type = 'text';
input.className = 'session-rename';
input.value = nameEl.textContent;
nameEl.replaceWith(input);
input.focus();
input.select();
const save = async () => {
const newName = input.value.trim();
if (newName) {
await apiUpdateSession(sessionId, { name: newName });
}
await refreshSessionList();
};
input.addEventListener('blur', save);
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') input.blur();
if (e.key === 'Escape') { input.value = nameEl.textContent; input.blur(); }
});
}