Fix conversation reset during streaming
This commit is contained in:
@@ -77,6 +77,8 @@ class Odidere {
|
|||||||
this.isScrollPending = false;
|
this.isScrollPending = false;
|
||||||
this.isStopPending = false;
|
this.isStopPending = false;
|
||||||
this.pendingDeleteId = null;
|
this.pendingDeleteId = null;
|
||||||
|
// Promise for the currently in-flight runTurn, so reset() can await it.
|
||||||
|
this._currentRun = null;
|
||||||
this.currentController = null;
|
this.currentController = null;
|
||||||
this.totalTokens = 0;
|
this.totalTokens = 0;
|
||||||
|
|
||||||
@@ -128,13 +130,26 @@ class Odidere {
|
|||||||
this.wakeLock.release();
|
this.wakeLock.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
/**
|
||||||
|
* Reset stops any in-flight turn, waits for it to settle, then
|
||||||
|
* clears the conversation, DOM, attachments, location, and input.
|
||||||
|
* The abort and await pattern ensures message streaming in runTurn completes
|
||||||
|
* before conversation.clear().
|
||||||
|
*/
|
||||||
|
async reset() {
|
||||||
this.tts.stop();
|
this.tts.stop();
|
||||||
if (this.currentController) {
|
if (this.currentController) {
|
||||||
this.currentController.abort();
|
this.currentController.abort();
|
||||||
this.currentController = null;
|
this.currentController = null;
|
||||||
}
|
}
|
||||||
this.setLoadingState(false);
|
this.setLoadingState(false);
|
||||||
|
|
||||||
|
// Wait for the current run to settle before clearing.
|
||||||
|
if (this._currentRun) {
|
||||||
|
try {
|
||||||
|
await this._currentRun;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
this.pendingDeleteId = null;
|
this.pendingDeleteId = null;
|
||||||
this.conversation.clear();
|
this.conversation.clear();
|
||||||
this.renderer.setAutoScroll(true);
|
this.renderer.setAutoScroll(true);
|
||||||
@@ -523,7 +538,14 @@ class Odidere {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* runTurn sends a chat request to the streaming SSE endpoint.
|
* Build and send a chat request, then consume the streaming SSE response.
|
||||||
|
*
|
||||||
|
* The body executes inside an async IIFE stored as this._currentRun so
|
||||||
|
* reset() can abort the stream and await the IIFE before clearing state.
|
||||||
|
* On interruption (stop or reset), the catch block persists any partially-
|
||||||
|
* received assistant message or pending tool calls; on reset, clear() runs
|
||||||
|
* afterward, so the persisted data is immediately discarded.
|
||||||
|
*
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {string} [options.text]
|
* @param {string} [options.text]
|
||||||
* @param {string} [options.detectedLanguage]
|
* @param {string} [options.detectedLanguage]
|
||||||
@@ -534,13 +556,13 @@ class Odidere {
|
|||||||
this.renderer.setAutoScroll(true);
|
this.renderer.setAutoScroll(true);
|
||||||
this.currentController = new AbortController();
|
this.currentController = new AbortController();
|
||||||
|
|
||||||
|
this._currentRun = (async () => {
|
||||||
let streamingBubbles = {
|
let streamingBubbles = {
|
||||||
assistantId: null,
|
assistantId: null,
|
||||||
message: null,
|
message: null,
|
||||||
reasoning: null,
|
reasoning: null,
|
||||||
text: null,
|
text: null,
|
||||||
};
|
};
|
||||||
let done = false;
|
|
||||||
let pendingTools = null;
|
let pendingTools = null;
|
||||||
|
|
||||||
const streamingMeta = (usage, timings) => {
|
const streamingMeta = (usage, timings) => {
|
||||||
@@ -737,7 +759,11 @@ class Odidere {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect tool results.
|
// Collect tool results.
|
||||||
if (message.role === 'tool' && pendingTools && pendingTools.assistant) {
|
if (
|
||||||
|
message.role === 'tool' &&
|
||||||
|
pendingTools &&
|
||||||
|
pendingTools.assistant
|
||||||
|
) {
|
||||||
const appended = this.conversation.append([message]);
|
const appended = this.conversation.append([message]);
|
||||||
pendingTools.results.push(appended[0]);
|
pendingTools.results.push(appended[0]);
|
||||||
|
|
||||||
@@ -758,7 +784,6 @@ class Odidere {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done = true;
|
|
||||||
this.setLoadingState(false);
|
this.setLoadingState(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'AbortError') {
|
if (e.name !== 'AbortError') {
|
||||||
@@ -766,11 +791,9 @@ class Odidere {
|
|||||||
this.renderer.renderError(`Error: ${e.message}`);
|
this.renderer.renderError(`Error: ${e.message}`);
|
||||||
}
|
}
|
||||||
this.setLoadingState(false);
|
this.setLoadingState(false);
|
||||||
} finally {
|
|
||||||
this.currentController = null;
|
|
||||||
|
|
||||||
// Persist a streaming message interrupted by stop or error.
|
// Persist a streaming message interrupted by stop or error.
|
||||||
if (!done && streamingBubbles.message) {
|
if (streamingBubbles.message) {
|
||||||
const meta = streamingMeta(null, null);
|
const meta = streamingMeta(null, null);
|
||||||
const finalMessage = streamingBubbles.message;
|
const finalMessage = streamingBubbles.message;
|
||||||
const hasContent = !!extractText(finalMessage.content);
|
const hasContent = !!extractText(finalMessage.content);
|
||||||
@@ -799,7 +822,7 @@ class Odidere {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finalize pending tool calls interrupted by stop or error.
|
// Finalize pending tool calls interrupted by stop or error.
|
||||||
if (!done && pendingTools) {
|
if (pendingTools) {
|
||||||
if (pendingTools.$el) {
|
if (pendingTools.$el) {
|
||||||
this.renderer.finalizeStreamingText(
|
this.renderer.finalizeStreamingText(
|
||||||
pendingTools.$el,
|
pendingTools.$el,
|
||||||
@@ -813,6 +836,15 @@ class Odidere {
|
|||||||
]);
|
]);
|
||||||
pendingTools = null;
|
pendingTools = null;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
this.currentController = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._currentRun;
|
||||||
|
} finally {
|
||||||
|
this._currentRun = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1226,6 +1258,7 @@ class Odidere {
|
|||||||
if ($btn) $btn.classList.remove('pending');
|
if ($btn) $btn.classList.remove('pending');
|
||||||
}
|
}
|
||||||
this.pendingDeleteId = null;
|
this.pendingDeleteId = null;
|
||||||
|
this._currentRun = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cancelAllPending() {
|
_cancelAllPending() {
|
||||||
|
|||||||
Reference in New Issue
Block a user