Delete and rename conversations

This commit is contained in:
dwrz
2026-10-31 12:13:36 +00:00
parent 9020167a92
commit 62bb1cbf32
10 changed files with 552 additions and 33 deletions
+52 -7
View File
@@ -120,6 +120,7 @@ export class UI {
this.$tplConversationItem = document.getElementById(
'tpl-conversation-item',
);
this.$tplRenameModal = document.getElementById('tpl-rename-modal');
// Compose settings modal.
this.$composeSettings = document.getElementById('compose-settings');
@@ -904,6 +905,10 @@ export class UI {
$item.dataset.conversationId = conversation.id;
$item.querySelector('.conversations__item-title').textContent =
conversationLabel(conversation);
$item.querySelector('.conversations__item-created').textContent =
`Created ${CONVERSATION_DATE.format(
new Date(conversation.created_at),
)}`;
$item.querySelector('.conversations__item-updated').textContent =
`Updated ${CONVERSATION_DATE.format(
new Date(conversation.updated_at),
@@ -951,6 +956,27 @@ export class UI {
);
}
/**
* editConversationTitle opens the rename modal with the current
* title. It resolves with the trimmed new title when saved, and
* with null when cancelled. An empty title is not a rename, so
* the modal stays open until the user enters one or cancels.
* @param {string} title
* @returns {Promise<string | null>}
*/
editConversationTitle(title) {
const $modal =
this.$tplRenameModal.content.cloneNode(true).firstElementChild;
$modal.querySelector('#rename-input').value = title;
return this._openModal($modal, ($modal) => {
const value = $modal.querySelector('#rename-input').value.trim();
if (value === '') {
throw new Error('the title must not be empty');
}
return value;
});
}
/**
* editToolCall opens the tool edit modal, which edits the call's
* name and arguments and, when a result exists, its text. It
@@ -1143,7 +1169,24 @@ export class UI {
this.$conversationsList.addEventListener('click', (event) => {
const $item = event.target.closest('[data-conversation-id]');
if (!$item) return;
this.handlers.onSelectConversation($item.dataset.conversationId);
const id = $item.dataset.conversationId;
const $rename = event.target.closest('.conversations__item-rename');
if ($rename) {
this._clearConfirm();
this.handlers.onRenameConversation(id);
return;
}
const $delete = event.target.closest('.conversations__item-delete');
if ($delete) {
// Deleting is irreversible: the second press on the same
// button deletes, any other click cancels the request.
this._confirm(`conversation:${id}`, $delete, 'pending', () =>
this.handlers.onDeleteConversation(id),
);
return;
}
this._clearConfirm();
this.handlers.onSelectConversation(id);
});
// Compose settings modal.
@@ -1474,15 +1517,17 @@ export class UI {
value = readValue($modal);
} catch (error) {
console.error('invalid input:', error);
for (const $textarea of $modal.querySelectorAll('.modal__textarea')) {
$textarea.classList.add('modal__textarea--error');
for (const $field of $modal.querySelectorAll(
'.modal__textarea, .modal__input',
)) {
$field.classList.add('modal__textarea--error');
}
$save.classList.add('modal__save--error');
setTimeout(() => {
for (const $textarea of $modal.querySelectorAll(
'.modal__textarea',
for (const $field of $modal.querySelectorAll(
'.modal__textarea, .modal__input',
)) {
$textarea.classList.remove('modal__textarea--error');
$field.classList.remove('modal__textarea--error');
}
$save.classList.remove('modal__save--error');
}, 500);
@@ -1491,7 +1536,7 @@ export class UI {
close(value);
});
$modal.querySelector('textarea')?.focus();
$modal.querySelector('textarea, input')?.focus();
});
}