KI-Chat: Verlaufs-Titel inline umbenennbar

Pro Verlauf ein Stift-Button (neben dem Löschen-Button). Klick macht den
Titel inline editierbar; Enter/Blur speichert via PATCH /chat/api/threads/:id,
Escape bricht ab. Auch für client-seitig neu angelegte Threads.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-07 14:55:06 +00:00
co-authored by Claude
parent d2545c4faf
commit 26a61aa302
2 changed files with 76 additions and 0 deletions
+69
View File
@@ -145,6 +145,12 @@
'<div class="truncate font-medium">' + escapeHtml(titel || '(neuer Chat)') + '</div>' + '<div class="truncate font-medium">' + escapeHtml(titel || '(neuer Chat)') + '</div>' +
'<div class="text-[11px] text-gray-400">gerade</div>' + '<div class="text-[11px] text-gray-400">gerade</div>' +
'</a>' + '</a>' +
'<button type="button" data-rename-thread="' + id +
'" class="threadRename shrink-0 p-1.5 rounded text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100" title="Namen ändern" aria-label="Namen ändern">' +
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">' +
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>' +
'</svg>' +
'</button>' +
'<button type="button" data-delete-thread="' + id + '<button type="button" data-delete-thread="' + id +
'" class="threadDelete shrink-0 mr-2 p-1.5 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100" title="Verlauf löschen" aria-label="Verlauf löschen">' + '" class="threadDelete shrink-0 mr-2 p-1.5 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100" title="Verlauf löschen" aria-label="Verlauf löschen">' +
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">' + '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">' +
@@ -295,6 +301,69 @@
newThreadBtn.addEventListener('click', resetToNewChat); newThreadBtn.addEventListener('click', resetToNewChat);
} }
// Rename a thread inline: replace the title with an input, save on
// Enter/blur (PATCH), cancel on Escape.
async function saveRename(input, link, id, original) {
const val = input.value.trim();
if (!val) { link.querySelector('.font-medium').textContent = original; return; }
try {
const res = await fetch('/chat/api/threads/' + id, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ titel: val }),
});
if (!res.ok) throw new Error('Speichern fehlgeschlagen');
link.querySelector('.font-medium').textContent = val;
} catch (err) {
link.querySelector('.font-medium').textContent = original;
alert(err.message);
}
}
function startRename(li, id) {
const link = li.querySelector('.threadLink');
const titleEl = link.querySelector('.font-medium');
const original = titleEl.textContent;
const input = document.createElement('input');
input.type = 'text';
input.value = original;
input.className = 'font-medium w-full bg-transparent border border-blue-400 rounded px-1 py-0.5 text-sm text-gray-800 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500';
titleEl.replaceWith(input);
input.focus();
input.select();
let done = false;
const commit = () => {
if (done) return; done = true;
const title = document.createElement('div');
title.className = 'truncate font-medium';
title.textContent = input.value.trim() || original;
input.replaceWith(title);
if (input.value.trim() && input.value.trim() !== original) {
saveRename(input, link, id, original);
} else {
title.textContent = original;
}
};
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); commit(); }
else if (e.key === 'Escape') { e.preventDefault(); done = true; const title = document.createElement('div'); title.className = 'truncate font-medium'; title.textContent = original; input.replaceWith(title); }
});
input.addEventListener('blur', commit);
}
if (threadList) {
threadList.addEventListener('click', (e) => {
const renameBtn = e.target.closest('[data-rename-thread]');
if (renameBtn) {
e.preventDefault();
e.stopPropagation();
const id = Number(renameBtn.getAttribute('data-rename-thread'));
const li = threadList.querySelector('li[data-thread-li="' + id + '"]');
if (li) startRename(li, id);
}
});
}
// Delete a thread from the sidebar. Event delegation: works for server-rendered // Delete a thread from the sidebar. Event delegation: works for server-rendered
// rows and ones added client-side (prependThread). // rows and ones added client-side (prependThread).
if (threadList) { if (threadList) {
+7
View File
@@ -66,6 +66,13 @@
<div class="truncate font-medium"><%= t.titel || '(ohne Titel)' %></div> <div class="truncate font-medium"><%= t.titel || '(ohne Titel)' %></div>
<div class="text-[11px] text-gray-400"><%= new Date(t.updated_at).toLocaleDateString('de-DE') %></div> <div class="text-[11px] text-gray-400"><%= new Date(t.updated_at).toLocaleDateString('de-DE') %></div>
</a> </a>
<button type="button" data-rename-thread="<%= t.id %>"
class="threadRename shrink-0 p-1.5 rounded text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100"
title="Namen ändern" aria-label="Namen ändern">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button type="button" data-delete-thread="<%= t.id %>" <button type="button" data-delete-thread="<%= t.id %>"
class="threadDelete shrink-0 mr-2 p-1.5 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100" class="threadDelete shrink-0 mr-2 p-1.5 rounded text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 opacity-0 group-hover:opacity-100 focus:opacity-100"
title="Verlauf löschen" aria-label="Verlauf löschen"> title="Verlauf löschen" aria-label="Verlauf löschen">