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,171 +556,192 @@ class Odidere {
|
|||||||
this.renderer.setAutoScroll(true);
|
this.renderer.setAutoScroll(true);
|
||||||
this.currentController = new AbortController();
|
this.currentController = new AbortController();
|
||||||
|
|
||||||
let streamingBubbles = {
|
this._currentRun = (async () => {
|
||||||
assistantId: null,
|
let streamingBubbles = {
|
||||||
message: null,
|
assistantId: null,
|
||||||
reasoning: null,
|
message: null,
|
||||||
text: null,
|
reasoning: null,
|
||||||
};
|
text: null,
|
||||||
let done = false;
|
};
|
||||||
let pendingTools = null;
|
let pendingTools = null;
|
||||||
|
|
||||||
const streamingMeta = (usage, timings) => {
|
const streamingMeta = (usage, timings) => {
|
||||||
const meta = {};
|
|
||||||
if (voice) meta.voice = voice;
|
|
||||||
if (usage) meta.usage = usage;
|
|
||||||
if (timings) meta.timings = timings;
|
|
||||||
return meta;
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasNewInput =
|
|
||||||
text || this.attachments.length > 0 || this.locationData;
|
|
||||||
|
|
||||||
const activePath = this.conversation.getActivePath();
|
|
||||||
let messages;
|
|
||||||
let userMessage;
|
|
||||||
|
|
||||||
if (hasNewInput) {
|
|
||||||
userMessage = await this._buildUserMessage(text, this.attachments);
|
|
||||||
messages = [
|
|
||||||
...activePath.map(({ parent, children, id, meta, ...msg }) => msg),
|
|
||||||
userMessage,
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
messages = activePath.map(
|
|
||||||
({ parent, children, id, meta, ...msg }) => msg,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { provider, model } = this.settings.getModel();
|
|
||||||
const payload = { messages, provider, model };
|
|
||||||
const systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY);
|
|
||||||
if (systemMessage) {
|
|
||||||
payload.system_message = systemMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear attachments and location after building payload.
|
|
||||||
this.clearAttachments();
|
|
||||||
this.clearLocation();
|
|
||||||
|
|
||||||
// For text-only requests with new input, add to history and
|
|
||||||
// render immediately.
|
|
||||||
if (hasNewInput) {
|
|
||||||
const meta = {};
|
const meta = {};
|
||||||
if (detectedLanguage) meta.language = detectedLanguage;
|
|
||||||
if (voice) meta.voice = voice;
|
if (voice) meta.voice = voice;
|
||||||
const appended = this.conversation.append([userMessage], meta);
|
if (usage) meta.usage = usage;
|
||||||
this.renderer.renderMessages(appended, meta);
|
if (timings) meta.timings = timings;
|
||||||
}
|
return meta;
|
||||||
|
};
|
||||||
|
|
||||||
const res = await fetch(STREAM_ENDPOINT, {
|
try {
|
||||||
method: 'POST',
|
const hasNewInput =
|
||||||
headers: { 'Content-Type': 'application/json' },
|
text || this.attachments.length > 0 || this.locationData;
|
||||||
body: JSON.stringify(payload),
|
|
||||||
signal: this.currentController.signal,
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const err = await res.text();
|
|
||||||
throw new Error(`server error ${res.status}: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const { eventType, event } of this._parseSSEStream(res)) {
|
const activePath = this.conversation.getActivePath();
|
||||||
if (event.error) {
|
let messages;
|
||||||
throw new Error(event.error);
|
let userMessage;
|
||||||
|
|
||||||
|
if (hasNewInput) {
|
||||||
|
userMessage = await this._buildUserMessage(text, this.attachments);
|
||||||
|
messages = [
|
||||||
|
...activePath.map(({ parent, children, id, meta, ...msg }) => msg),
|
||||||
|
userMessage,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
messages = activePath.map(
|
||||||
|
({ parent, children, id, meta, ...msg }) => msg,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const { provider, model } = this.settings.getModel();
|
||||||
eventType === StreamEventDelta ||
|
const payload = { messages, provider, model };
|
||||||
eventType === StreamEventReasoningDelta
|
const systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY);
|
||||||
) {
|
if (systemMessage) {
|
||||||
if (!streamingBubbles.message) {
|
payload.system_message = systemMessage;
|
||||||
streamingBubbles.assistantId = crypto.randomUUID();
|
|
||||||
streamingBubbles.message = {
|
|
||||||
id: streamingBubbles.assistantId,
|
|
||||||
role: 'assistant',
|
|
||||||
content: [{ type: ContentTypeText, text: '' }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType === StreamEventReasoningDelta) {
|
|
||||||
if (!streamingBubbles.reasoning) {
|
|
||||||
const $el = this.renderer.createStreamingReasoning(
|
|
||||||
streamingBubbles.assistantId,
|
|
||||||
streamingMeta(event.usage, event.timings),
|
|
||||||
);
|
|
||||||
streamingBubbles.reasoning = { $el };
|
|
||||||
streamingBubbles.message.reasoning_content = '';
|
|
||||||
}
|
|
||||||
streamingBubbles.message.reasoning_content +=
|
|
||||||
event.reasoning_delta || '';
|
|
||||||
this.renderer.appendReasoningDelta(
|
|
||||||
streamingBubbles.reasoning.$el,
|
|
||||||
event.reasoning_delta || '',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!streamingBubbles.text) {
|
|
||||||
const $el = this.renderer.createStreamingText(
|
|
||||||
streamingBubbles.message,
|
|
||||||
streamingMeta(event.usage, event.timings),
|
|
||||||
);
|
|
||||||
streamingBubbles.text = { $el };
|
|
||||||
}
|
|
||||||
streamingBubbles.message.content[0].text += event.delta || '';
|
|
||||||
this.renderer.appendDelta(
|
|
||||||
streamingBubbles.text.$el,
|
|
||||||
event.delta || '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = event.message;
|
// Clear attachments and location after building payload.
|
||||||
if (!message) continue;
|
this.clearAttachments();
|
||||||
|
this.clearLocation();
|
||||||
|
|
||||||
if (eventType === StreamEventDone) {
|
// For text-only requests with new input, add to history and
|
||||||
if (message.role !== 'assistant') continue;
|
// render immediately.
|
||||||
|
if (hasNewInput) {
|
||||||
|
const meta = {};
|
||||||
|
if (detectedLanguage) meta.language = detectedLanguage;
|
||||||
|
if (voice) meta.voice = voice;
|
||||||
|
const appended = this.conversation.append([userMessage], meta);
|
||||||
|
this.renderer.renderMessages(appended, meta);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.usage?.total_tokens) {
|
const res = await fetch(STREAM_ENDPOINT, {
|
||||||
this.totalTokens = event.usage.total_tokens;
|
method: 'POST',
|
||||||
this.updateContext();
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
signal: this.currentController.signal,
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.text();
|
||||||
|
throw new Error(`server error ${res.status}: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const { eventType, event } of this._parseSSEStream(res)) {
|
||||||
|
if (event.error) {
|
||||||
|
throw new Error(event.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = streamingMeta(event.usage, event.timings);
|
if (
|
||||||
let finalMessage = message;
|
eventType === StreamEventDelta ||
|
||||||
|
eventType === StreamEventReasoningDelta
|
||||||
|
) {
|
||||||
|
if (!streamingBubbles.message) {
|
||||||
|
streamingBubbles.assistantId = crypto.randomUUID();
|
||||||
|
streamingBubbles.message = {
|
||||||
|
id: streamingBubbles.assistantId,
|
||||||
|
role: 'assistant',
|
||||||
|
content: [{ type: ContentTypeText, text: '' }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (streamingBubbles.message) {
|
if (eventType === StreamEventReasoningDelta) {
|
||||||
finalMessage.id = streamingBubbles.assistantId;
|
if (!streamingBubbles.reasoning) {
|
||||||
const appended = this.conversation.append([finalMessage], meta);
|
const $el = this.renderer.createStreamingReasoning(
|
||||||
finalMessage = appended[0];
|
streamingBubbles.assistantId,
|
||||||
|
streamingMeta(event.usage, event.timings),
|
||||||
if (streamingBubbles.reasoning) {
|
);
|
||||||
this.renderer.finalizeStreamingReasoning(
|
streamingBubbles.reasoning = { $el };
|
||||||
|
streamingBubbles.message.reasoning_content = '';
|
||||||
|
}
|
||||||
|
streamingBubbles.message.reasoning_content +=
|
||||||
|
event.reasoning_delta || '';
|
||||||
|
this.renderer.appendReasoningDelta(
|
||||||
streamingBubbles.reasoning.$el,
|
streamingBubbles.reasoning.$el,
|
||||||
finalMessage,
|
event.reasoning_delta || '',
|
||||||
meta,
|
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
if (streamingBubbles.text && !finalMessage.tool_calls?.length) {
|
if (!streamingBubbles.text) {
|
||||||
this.renderer.finalizeStreamingText(
|
const $el = this.renderer.createStreamingText(
|
||||||
|
streamingBubbles.message,
|
||||||
|
streamingMeta(event.usage, event.timings),
|
||||||
|
);
|
||||||
|
streamingBubbles.text = { $el };
|
||||||
|
}
|
||||||
|
streamingBubbles.message.content[0].text += event.delta || '';
|
||||||
|
this.renderer.appendDelta(
|
||||||
streamingBubbles.text.$el,
|
streamingBubbles.text.$el,
|
||||||
finalMessage,
|
event.delta || '',
|
||||||
meta,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalMessage.tool_calls?.length > 0) {
|
const message = event.message;
|
||||||
|
if (!message) continue;
|
||||||
|
|
||||||
|
if (eventType === StreamEventDone) {
|
||||||
|
if (message.role !== 'assistant') continue;
|
||||||
|
|
||||||
|
if (event.usage?.total_tokens) {
|
||||||
|
this.totalTokens = event.usage.total_tokens;
|
||||||
|
this.updateContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = streamingMeta(event.usage, event.timings);
|
||||||
|
let finalMessage = message;
|
||||||
|
|
||||||
|
if (streamingBubbles.message) {
|
||||||
|
finalMessage.id = streamingBubbles.assistantId;
|
||||||
|
const appended = this.conversation.append([finalMessage], meta);
|
||||||
|
finalMessage = appended[0];
|
||||||
|
|
||||||
|
if (streamingBubbles.reasoning) {
|
||||||
|
this.renderer.finalizeStreamingReasoning(
|
||||||
|
streamingBubbles.reasoning.$el,
|
||||||
|
finalMessage,
|
||||||
|
meta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (streamingBubbles.text && !finalMessage.tool_calls?.length) {
|
||||||
|
this.renderer.finalizeStreamingText(
|
||||||
|
streamingBubbles.text.$el,
|
||||||
|
finalMessage,
|
||||||
|
meta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalMessage.tool_calls?.length > 0) {
|
||||||
|
if (!streamingBubbles.message) {
|
||||||
|
const appended = this.conversation.append([finalMessage], meta);
|
||||||
|
finalMessage = appended[0];
|
||||||
|
}
|
||||||
|
pendingTools = {
|
||||||
|
assistant: finalMessage,
|
||||||
|
results: [],
|
||||||
|
expected: finalMessage.tool_calls.length,
|
||||||
|
$el: streamingBubbles.text?.$el || null,
|
||||||
|
};
|
||||||
|
streamingBubbles = {
|
||||||
|
assistantId: null,
|
||||||
|
message: null,
|
||||||
|
reasoning: null,
|
||||||
|
text: null,
|
||||||
|
};
|
||||||
|
if (extractText(finalMessage.content)) {
|
||||||
|
this.tts.enqueue(
|
||||||
|
extractText(finalMessage.content),
|
||||||
|
voice || '',
|
||||||
|
finalMessage.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!streamingBubbles.message) {
|
if (!streamingBubbles.message) {
|
||||||
const appended = this.conversation.append([finalMessage], meta);
|
const appended = this.conversation.append([finalMessage], meta);
|
||||||
finalMessage = appended[0];
|
finalMessage = appended[0];
|
||||||
|
this.renderer.renderMessages(appended, meta);
|
||||||
}
|
}
|
||||||
pendingTools = {
|
|
||||||
assistant: finalMessage,
|
|
||||||
results: [],
|
|
||||||
expected: finalMessage.tool_calls.length,
|
|
||||||
$el: streamingBubbles.text?.$el || null,
|
|
||||||
};
|
|
||||||
streamingBubbles = {
|
streamingBubbles = {
|
||||||
assistantId: null,
|
assistantId: null,
|
||||||
message: null,
|
message: null,
|
||||||
@@ -715,104 +758,93 @@ class Odidere {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streamingBubbles.message) {
|
// Collect tool results.
|
||||||
const appended = this.conversation.append([finalMessage], meta);
|
if (
|
||||||
finalMessage = appended[0];
|
message.role === 'tool' &&
|
||||||
this.renderer.renderMessages(appended, meta);
|
pendingTools &&
|
||||||
|
pendingTools.assistant
|
||||||
|
) {
|
||||||
|
const appended = this.conversation.append([message]);
|
||||||
|
pendingTools.results.push(appended[0]);
|
||||||
|
|
||||||
|
if (pendingTools.results.length >= pendingTools.expected) {
|
||||||
|
if (pendingTools.$el) {
|
||||||
|
this.renderer.finalizeStreamingText(
|
||||||
|
pendingTools.$el,
|
||||||
|
pendingTools.assistant,
|
||||||
|
pendingTools.assistant.meta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.renderer.renderMessages([
|
||||||
|
pendingTools.assistant,
|
||||||
|
...pendingTools.results,
|
||||||
|
]);
|
||||||
|
pendingTools = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
streamingBubbles = {
|
|
||||||
assistantId: null,
|
|
||||||
message: null,
|
|
||||||
reasoning: null,
|
|
||||||
text: null,
|
|
||||||
};
|
|
||||||
if (extractText(finalMessage.content)) {
|
|
||||||
this.tts.enqueue(
|
|
||||||
extractText(finalMessage.content),
|
|
||||||
voice || '',
|
|
||||||
finalMessage.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect tool results.
|
this.setLoadingState(false);
|
||||||
if (message.role === 'tool' && pendingTools && pendingTools.assistant) {
|
} catch (e) {
|
||||||
const appended = this.conversation.append([message]);
|
if (e.name !== 'AbortError') {
|
||||||
pendingTools.results.push(appended[0]);
|
console.error('failed to send request:', e);
|
||||||
|
this.renderer.renderError(`Error: ${e.message}`);
|
||||||
|
}
|
||||||
|
this.setLoadingState(false);
|
||||||
|
|
||||||
if (pendingTools.results.length >= pendingTools.expected) {
|
// Persist a streaming message interrupted by stop or error.
|
||||||
if (pendingTools.$el) {
|
if (streamingBubbles.message) {
|
||||||
this.renderer.finalizeStreamingText(
|
const meta = streamingMeta(null, null);
|
||||||
pendingTools.$el,
|
const finalMessage = streamingBubbles.message;
|
||||||
pendingTools.assistant,
|
const hasContent = !!extractText(finalMessage.content);
|
||||||
pendingTools.assistant.meta,
|
const hasToolCalls = finalMessage.tool_calls?.length > 0;
|
||||||
|
|
||||||
|
if (hasContent || hasToolCalls) {
|
||||||
|
const appended = this.conversation.append([finalMessage], meta);
|
||||||
|
if (streamingBubbles.reasoning) {
|
||||||
|
this.renderer.finalizeStreamingReasoning(
|
||||||
|
streamingBubbles.reasoning.$el,
|
||||||
|
appended[0],
|
||||||
|
meta,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.renderer.renderMessages([
|
if (streamingBubbles.text) {
|
||||||
pendingTools.assistant,
|
this.renderer.finalizeStreamingText(
|
||||||
...pendingTools.results,
|
streamingBubbles.text.$el,
|
||||||
]);
|
appended[0],
|
||||||
pendingTools = null;
|
meta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
streamingBubbles.reasoning?.$el?.remove();
|
||||||
|
streamingBubbles.text?.$el?.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
done = true;
|
// Finalize pending tool calls interrupted by stop or error.
|
||||||
this.setLoadingState(false);
|
if (pendingTools) {
|
||||||
} catch (e) {
|
if (pendingTools.$el) {
|
||||||
if (e.name !== 'AbortError') {
|
|
||||||
console.error('failed to send request:', e);
|
|
||||||
this.renderer.renderError(`Error: ${e.message}`);
|
|
||||||
}
|
|
||||||
this.setLoadingState(false);
|
|
||||||
} finally {
|
|
||||||
this.currentController = null;
|
|
||||||
|
|
||||||
// Persist a streaming message interrupted by stop or error.
|
|
||||||
if (!done && streamingBubbles.message) {
|
|
||||||
const meta = streamingMeta(null, null);
|
|
||||||
const finalMessage = streamingBubbles.message;
|
|
||||||
const hasContent = !!extractText(finalMessage.content);
|
|
||||||
const hasToolCalls = finalMessage.tool_calls?.length > 0;
|
|
||||||
|
|
||||||
if (hasContent || hasToolCalls) {
|
|
||||||
const appended = this.conversation.append([finalMessage], meta);
|
|
||||||
if (streamingBubbles.reasoning) {
|
|
||||||
this.renderer.finalizeStreamingReasoning(
|
|
||||||
streamingBubbles.reasoning.$el,
|
|
||||||
appended[0],
|
|
||||||
meta,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (streamingBubbles.text) {
|
|
||||||
this.renderer.finalizeStreamingText(
|
this.renderer.finalizeStreamingText(
|
||||||
streamingBubbles.text.$el,
|
pendingTools.$el,
|
||||||
appended[0],
|
pendingTools.assistant,
|
||||||
meta,
|
pendingTools.assistant.meta ?? {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
this.renderer.renderMessages([
|
||||||
streamingBubbles.reasoning?.$el?.remove();
|
|
||||||
streamingBubbles.text?.$el?.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize pending tool calls interrupted by stop or error.
|
|
||||||
if (!done && pendingTools) {
|
|
||||||
if (pendingTools.$el) {
|
|
||||||
this.renderer.finalizeStreamingText(
|
|
||||||
pendingTools.$el,
|
|
||||||
pendingTools.assistant,
|
pendingTools.assistant,
|
||||||
pendingTools.assistant.meta ?? {},
|
...pendingTools.results,
|
||||||
);
|
]);
|
||||||
|
pendingTools = null;
|
||||||
}
|
}
|
||||||
this.renderer.renderMessages([
|
} finally {
|
||||||
pendingTools.assistant,
|
this.currentController = null;
|
||||||
...pendingTools.results,
|
|
||||||
]);
|
|
||||||
pendingTools = null;
|
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._currentRun;
|
||||||
|
} finally {
|
||||||
|
this._currentRun = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,7 +1090,7 @@ class Odidere {
|
|||||||
this.$location.classList.add('compose__action-btn--location', 'active');
|
this.$location.classList.add('compose__action-btn--location', 'active');
|
||||||
this.updateSendButtonState();
|
this.updateSendButtonState();
|
||||||
},
|
},
|
||||||
() => { },
|
() => {},
|
||||||
GeolocationOptions,
|
GeolocationOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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