Fix interrupted streaming message deletion

This commit is contained in:
dwrz
2026-06-30 11:44:19 +00:00
parent c500b23372
commit 95326ce02f
2 changed files with 40 additions and 26 deletions

View File

@@ -1511,6 +1511,7 @@ class Odidere {
this.currentController = new AbortController(); this.currentController = new AbortController();
let streamingMessage = null; let streamingMessage = null;
let done = false;
try { try {
const hasNewInput = const hasNewInput =
@@ -1638,8 +1639,7 @@ class Odidere {
// Handle errors sent mid-stream. // Handle errors sent mid-stream.
if (event.error) { if (event.error) {
this.#renderError(event.error); throw new Error(event.error);
continue;
} }
if ( if (
@@ -1786,36 +1786,35 @@ class Odidere {
} }
} }
done = true;
this.#setLoadingState(false); this.#setLoadingState(false);
} catch (e) { } catch (e) {
if (e.name === 'AbortError') { if (e.name !== 'AbortError') {
console.debug('request aborted'); console.error('failed to send request:', e);
if (streamingMessage) { this.#renderError(`Error: ${e.message}`);
const $msg = streamingMessage.$el;
const $deleteBtn = document.createElement('button');
$deleteBtn.type = 'button';
$deleteBtn.className =
'message__action-btn message__action-btn--delete';
$deleteBtn.dataset.action = 'delete';
$deleteBtn.setAttribute('aria-label', 'Delete message');
$deleteBtn.innerHTML = `<svg class="icon"><use href="${ICONS_URL}#trash"></use></svg>`;
$deleteBtn.addEventListener('click', (e) =>
this.#handleDeleteClick(e, streamingMessage.message.id),
);
$msg
.querySelector('.message__actions-buttons')
.insertBefore(
$deleteBtn,
$msg.querySelector('.message__actions-buttons').firstChild,
);
}
return;
} }
console.error('failed to send request:', e);
this.#renderError(`Error: ${e.message}`);
this.#setLoadingState(false); this.#setLoadingState(false);
} finally { } finally {
this.currentController = null; this.currentController = null;
if (!done && streamingMessage) {
const $msg = streamingMessage.$el;
const $deleteBtn = document.createElement('button');
$deleteBtn.type = 'button';
$deleteBtn.className =
'message__action-btn message__action-btn--delete';
$deleteBtn.dataset.action = 'delete';
$deleteBtn.setAttribute('aria-label', 'Delete message');
$deleteBtn.innerHTML = `<svg class="icon"><use href="${ICONS_URL}#trash"></use></svg>`;
$deleteBtn.addEventListener('click', (e) =>
this.#handleDeleteClick(e, streamingMessage.message.id),
);
$msg
.querySelector('.message__actions-buttons')
.insertBefore(
$deleteBtn,
$msg.querySelector('.message__actions-buttons').firstChild,
);
}
} }
} }
@@ -2735,6 +2734,11 @@ class Odidere {
this.$tplErrorMessage.content.cloneNode(true).firstElementChild; this.$tplErrorMessage.content.cloneNode(true).firstElementChild;
$msg.querySelector('.message__content').textContent = message; $msg.querySelector('.message__content').textContent = message;
const $delete = $msg.querySelector('[data-action="delete"]');
if ($delete) {
$delete.addEventListener('click', () => $msg.remove());
}
this.$chat.insertBefore($msg, this.$chatLoading); this.$chat.insertBefore($msg, this.$chatLoading);
this.#scrollToBottom(); this.#scrollToBottom();
} }

View File

@@ -95,6 +95,16 @@
<div class="message__content"></div> <div class="message__content"></div>
<div class="message__actions"> <div class="message__actions">
<svg class="message__icon" aria-hidden="true"><use href="/static/icons.svg#assistant"></use></svg> <svg class="message__icon" aria-hidden="true"><use href="/static/icons.svg#assistant"></use></svg>
<div class="message__actions-buttons">
<button
type="button"
class="message__action-btn message__action-btn--delete"
data-action="delete"
aria-label="Delete message"
>
<svg class="icon"><use href="/static/icons.svg#trash"></use></svg>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>