From f5d2e50f5e5417579d105f67892d4c0e35c5e097 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 7 Dec 2023 16:20:54 -0500 Subject: [PATCH 01/12] Remove isGenerationAborted Just check the AbortSignal. --- public/scripts/group-chats.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index b906cf2cf..7acf4ae9e 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -658,7 +658,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { let activationText = ''; let isUserInput = false; let isGenerationDone = false; - let isGenerationAborted = false; if (userInput?.length && !by_auto_mode) { isUserInput = true; @@ -673,14 +672,8 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { const resolveOriginal = params.resolve; const rejectOriginal = params.reject; - if (params.signal instanceof AbortSignal) { - if (params.signal.aborted) { + if (params.signal instanceof AbortSignal && params.signal.aborted) { throw new Error('Already aborted signal passed. Group generation stopped'); - } - - params.signal.onabort = () => { - isGenerationAborted = true; - }; } if (typeof params.resolve === 'function') { @@ -760,7 +753,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { // TODO: This is awful. Refactor this while (true) { deactivateSendButtons(); - if (isGenerationAborted) { + if (params.signal instanceof AbortSignal && params.signal.aborted) { throw new Error('Group generation aborted'); } From 03884b29adc7d5da31cd70309bb3f29c24977914 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 7 Dec 2023 17:32:07 -0500 Subject: [PATCH 02/12] Always call resolve in Generate() This lets us get rid of the janky hack in group-chats to tell when a message is done generating. --- public/script.js | 27 ++++--- public/scripts/group-chats.js | 128 +++++++--------------------------- 2 files changed, 44 insertions(+), 111 deletions(-) diff --git a/public/script.js b/public/script.js index 373b5c9f7..0902d0771 100644 --- a/public/script.js +++ b/public/script.js @@ -2916,6 +2916,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, abortController = new AbortController(); } + // Set empty promise resolution functions + if (typeof resolve !== 'function') { + resolve = () => { }; + } + if (typeof reject !== 'function') { + reject = () => { }; + } + // OpenAI doesn't need instruct mode. Use OAI main prompt instead. const isInstruct = power_user.instruct.enabled && main_api !== 'openai'; const isImpersonate = type == 'impersonate'; @@ -2927,12 +2935,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (interruptedByCommand) { //$("#send_textarea").val('').trigger('input'); unblockGeneration(); + resolve(); return; } if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) { toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(); + resolve(); return; } @@ -2942,11 +2952,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, textgen_settings.type !== MANCER) { toastr.error('Streaming is not supported for the Legacy API. Update Ooba and use --extensions openai to enable streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(); + resolve(); return; } if (isHordeGenerationNotAllowed()) { unblockGeneration(); + resolve(); return; } @@ -2955,14 +2967,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, hideSwipeButtons(); } - // Set empty promise resolution functions - if (typeof resolve !== 'function') { - resolve = () => { }; - } - if (typeof reject !== 'function') { - reject = () => { }; - } - if (selected_group && !is_group_generating && !dryRun) { generateGroupWrapper(false, type, { resolve, reject, quiet_prompt, force_chid, signal: abortController.signal, quietImage }); return; @@ -2987,6 +2991,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } else { console.log('No enabled members found'); unblockGeneration(); + resolve(); return; } } @@ -3151,6 +3156,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (aborted) { console.debug('Generation aborted by extension interceptors'); unblockGeneration(); + resolve(); return; } } else { @@ -3206,6 +3212,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } catch { unblockGeneration(); + resolve(); return; } if (horde_settings.auto_adjust_context_length) { @@ -3925,6 +3932,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, console.debug('swiping right automatically'); is_send_press = false; swipe_right(); + // TODO: do we want to resolve after an auto-swipe? + resolve(); return; } } @@ -3950,8 +3959,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (type !== 'quiet') { triggerAutoContinue(messageChunk, isImpersonate); - resolve(); } + resolve(); } function onError(exception) { diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 7acf4ae9e..21929684f 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -8,7 +8,6 @@ import { extractAllWords, saveBase64AsFile, PAGINATION_TEMPLATE, - waitUntilCondition, getBase64Async, } from './utils.js'; import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; @@ -46,7 +45,6 @@ import { updateChatMetadata, isStreamingEnabled, getThumbnailUrl, - streamingProcessor, getRequestHeaders, setMenuType, menu_type, @@ -653,41 +651,20 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { // id of this specific batch for regeneration purposes group_generation_id = Date.now(); const lastMessage = chat[chat.length - 1]; - let messagesBefore = chat.length; - let lastMessageText = lastMessage?.mes || ''; let activationText = ''; let isUserInput = false; - let isGenerationDone = false; if (userInput?.length && !by_auto_mode) { isUserInput = true; activationText = userInput; - messagesBefore++; } else { if (lastMessage && !lastMessage.is_system) { activationText = lastMessage.mes; } } - const resolveOriginal = params.resolve; - const rejectOriginal = params.reject; - if (params.signal instanceof AbortSignal && params.signal.aborted) { - throw new Error('Already aborted signal passed. Group generation stopped'); - } - - if (typeof params.resolve === 'function') { - params.resolve = function () { - isGenerationDone = true; - resolveOriginal.apply(this, arguments); - }; - } - - if (typeof params.reject === 'function') { - params.reject = function () { - isGenerationDone = true; - rejectOriginal.apply(this, arguments); - }; + throw new Error('Already aborted signal passed. Group generation stopped'); } const activationStrategy = Number(group.activation_strategy ?? group_activation_strategy.NATURAL); @@ -735,90 +712,37 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { // now the real generation begins: cycle through every activated character for (const chId of activatedMembers) { deactivateSendButtons(); - isGenerationDone = false; const generateType = type == 'swipe' || type == 'impersonate' || type == 'quiet' || type == 'continue' ? type : 'group_chat'; setCharacterId(chId); setCharacterName(characters[chId].name); - await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); + // Wait for generation to finish + await new Promise(async (resolve, reject) => { + await Generate(generateType, { + automatic_trigger: by_auto_mode, + ...(params || {}), + resolve: function(...args) { + if (typeof params.resolve === 'function') { + params.resolve(...args); + } + resolve(); + }, + reject: function(...args) { + if (typeof params.reject === 'function') { + params.reject(...args); + } + reject(); + }, + }); - if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { - // update indicator and scroll down - typingIndicator - .find('.typing_indicator_name') - .text(characters[chId].name); - typingIndicator.show(); - } - - // TODO: This is awful. Refactor this - while (true) { - deactivateSendButtons(); - if (params.signal instanceof AbortSignal && params.signal.aborted) { - throw new Error('Group generation aborted'); + if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { + // update indicator and scroll down + typingIndicator + .find('.typing_indicator_name') + .text(characters[chId].name); + typingIndicator.show(); } - - // if not swipe - check if message generated already - if (generateType === 'group_chat' && chat.length == messagesBefore) { - await delay(100); - } - // if swipe - see if message changed - else if (type === 'swipe') { - if (isStreamingEnabled()) { - if (streamingProcessor && !streamingProcessor.isFinished) { - await delay(100); - } - else { - break; - } - } - else { - if (lastMessageText === chat[chat.length - 1].mes) { - await delay(100); - } - else { - break; - } - } - } - else if (type === 'impersonate') { - if (isStreamingEnabled()) { - if (streamingProcessor && !streamingProcessor.isFinished) { - await delay(100); - } - else { - break; - } - } - else { - if (!$('#send_textarea').val() || $('#send_textarea').val() == userInput) { - await delay(100); - } - else { - break; - } - } - } - else if (type === 'quiet') { - if (isGenerationDone) { - break; - } else { - await delay(100); - } - } - else if (isStreamingEnabled()) { - if (streamingProcessor && !streamingProcessor.isFinished) { - await delay(100); - } else { - await waitUntilCondition(() => streamingProcessor == null, 1000, 10); - messagesBefore++; - break; - } - } - else { - messagesBefore++; - break; - } - } + }); } } finally { typingIndicator.hide(); From 33f969f097d4cc2cd0dc5b94043d94d59a712440 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 7 Dec 2023 17:46:15 -0500 Subject: [PATCH 03/12] Have Generate() return a promise Generate(), being async, now returns a promise-within-a-promise. If called with `let p = await Generate(...)`, it'll wait for generation to *start*. If you then `await p`, you'll wait for generation to *finish*. This makes it much easier to tell exactly when generation's done. generateGroupWrapper has been similarly modified. --- public/script.js | 514 ++++++++++++++++------------------ public/scripts/group-chats.js | 42 +-- 2 files changed, 256 insertions(+), 300 deletions(-) diff --git a/public/script.js b/public/script.js index 0902d0771..98481d21c 100644 --- a/public/script.js +++ b/public/script.js @@ -2307,26 +2307,8 @@ function getStoppingStrings(isImpersonate, isContinue) { */ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null) { console.log('got into genQuietPrompt'); - return await new Promise( - async function promptPromise(resolve, reject) { - if (quietToLoud === true) { - try { - await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: true, skipWIAN: skipWIAN, force_name2: true, quietImage: quietImage }); - } - catch { - reject(); - } - } - else { - try { - console.log('going to generate non-QuietToLoud'); - await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: false, skipWIAN: skipWIAN, force_name2: true, quietImage: quietImage }); - } - catch { - reject(); - } - } - }); + const generateFinished = await Generate('quiet', { quiet_prompt, quietToLoud, skipWIAN: skipWIAN, force_name2: true, quietImage: quietImage }); + await generateFinished; } async function processCommands(message, type, dryRun) { @@ -2906,7 +2888,8 @@ export async function generateRaw(prompt, api, instructOverride) { return message; } -async function Generate(type, { automatic_trigger, force_name2, resolve, reject, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage } = {}, dryRun = false) { +// Returns a promise that resolves when the text is done generating. +async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage } = {}, dryRun = false) { console.log('Generate entered'); setGenerationProgress(0); generation_started = new Date(); @@ -2916,14 +2899,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, abortController = new AbortController(); } - // Set empty promise resolution functions - if (typeof resolve !== 'function') { - resolve = () => { }; - } - if (typeof reject !== 'function') { - reject = () => { }; - } - // OpenAI doesn't need instruct mode. Use OAI main prompt instead. const isInstruct = power_user.instruct.enabled && main_api !== 'openai'; const isImpersonate = type == 'impersonate'; @@ -2935,15 +2910,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (interruptedByCommand) { //$("#send_textarea").val('').trigger('input'); unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) { toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } if (main_api === 'textgenerationwebui' && @@ -2952,14 +2925,12 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, textgen_settings.type !== MANCER) { toastr.error('Streaming is not supported for the Legacy API. Update Ooba and use --extensions openai to enable streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } if (isHordeGenerationNotAllowed()) { unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } // Hide swipes if not in a dry run. @@ -2968,8 +2939,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } if (selected_group && !is_group_generating && !dryRun) { - generateGroupWrapper(false, type, { resolve, reject, quiet_prompt, force_chid, signal: abortController.signal, quietImage }); - return; + // TODO: await here! + return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage }); } else if (selected_group && !is_group_generating && dryRun) { const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index])); const group = groups.find((x) => x.id === selected_group); @@ -2991,8 +2962,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } else { console.log('No enabled members found'); unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } } @@ -3156,8 +3126,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (aborted) { console.debug('Generation aborted by extension interceptors'); unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } } else { console.debug('Skipping extension interceptors for dry run'); @@ -3212,8 +3181,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } catch { unblockGeneration(); - resolve(); - return; + return Promise.resolve(); } if (horde_settings.auto_adjust_context_length) { this_max_context = (adjustedParams.maxContextLength - adjustedParams.maxLength); @@ -3373,7 +3341,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } const originalType = type; - runGenerate(cyclePrompt); + return runGenerate(cyclePrompt); async function runGenerate(cycleGenerationPrompt = '') { if (!dryRun) { @@ -3721,258 +3689,260 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } } - if (true === dryRun) return onSuccess({ error: 'dryRun' }); + return new Promise(async (resolve, reject) => { + if (true === dryRun) return onSuccess({ error: 'dryRun' }); - if (power_user.console_log_prompts) { - console.log(generate_data.prompt); - } + if (power_user.console_log_prompts) { + console.log(generate_data.prompt); + } - let generate_url = getGenerateUrl(main_api); - console.debug('rungenerate calling API'); + let generate_url = getGenerateUrl(main_api); + console.debug('rungenerate calling API'); - showStopButton(); + showStopButton(); - //set array object for prompt token itemization of this message - let currentArrayEntry = Number(thisPromptBits.length - 1); - let additionalPromptStuff = { - ...thisPromptBits[currentArrayEntry], - rawPrompt: generate_data.prompt || generate_data.input, - mesId: getNextMessageId(type), - allAnchors: allAnchors, - summarizeString: (extension_prompts['1_memory']?.value || ''), - authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), - smartContextString: (extension_prompts['chromadb']?.value || ''), - worldInfoString: worldInfoString, - storyString: storyString, - beforeScenarioAnchor: beforeScenarioAnchor, - afterScenarioAnchor: afterScenarioAnchor, - examplesString: examplesString, - mesSendString: mesSendString, - generatedPromptCache: generatedPromptCache, - promptBias: promptBias, - finalPrompt: finalPrompt, - charDescription: description, - charPersonality: personality, - scenarioText: scenario, - this_max_context: this_max_context, - padding: power_user.token_padding, - main_api: main_api, - instruction: isInstruct ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.instruct.system_prompt) : '', - userPersona: (power_user.persona_description || ''), - }; + //set array object for prompt token itemization of this message + let currentArrayEntry = Number(thisPromptBits.length - 1); + let additionalPromptStuff = { + ...thisPromptBits[currentArrayEntry], + rawPrompt: generate_data.prompt || generate_data.input, + mesId: getNextMessageId(type), + allAnchors: allAnchors, + summarizeString: (extension_prompts['1_memory']?.value || ''), + authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), + smartContextString: (extension_prompts['chromadb']?.value || ''), + worldInfoString: worldInfoString, + storyString: storyString, + beforeScenarioAnchor: beforeScenarioAnchor, + afterScenarioAnchor: afterScenarioAnchor, + examplesString: examplesString, + mesSendString: mesSendString, + generatedPromptCache: generatedPromptCache, + promptBias: promptBias, + finalPrompt: finalPrompt, + charDescription: description, + charPersonality: personality, + scenarioText: scenario, + this_max_context: this_max_context, + padding: power_user.token_padding, + main_api: main_api, + instruction: isInstruct ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.instruct.system_prompt) : '', + userPersona: (power_user.persona_description || ''), + }; - thisPromptBits = additionalPromptStuff; + thisPromptBits = additionalPromptStuff; - //console.log(thisPromptBits); - const itemizedIndex = itemizedPrompts.findIndex((item) => item.mesId === thisPromptBits['mesId']); + //console.log(thisPromptBits); + const itemizedIndex = itemizedPrompts.findIndex((item) => item.mesId === thisPromptBits['mesId']); - if (itemizedIndex !== -1) { - itemizedPrompts[itemizedIndex] = thisPromptBits; - } - else { - itemizedPrompts.push(thisPromptBits); - } - - console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); - /** @type {Promise} */ - let streamingGeneratorPromise = Promise.resolve(); - - if (main_api == 'openai') { - if (isStreamingEnabled() && type !== 'quiet') { - streamingGeneratorPromise = sendOpenAIRequest(type, generate_data.prompt, streamingProcessor.abortController.signal); + if (itemizedIndex !== -1) { + itemizedPrompts[itemizedIndex] = thisPromptBits; } else { - sendOpenAIRequest(type, generate_data.prompt, abortController.signal).then(onSuccess).catch(onError); + itemizedPrompts.push(thisPromptBits); } - } - else if (main_api == 'koboldhorde') { - generateHorde(finalPrompt, generate_data, abortController.signal, true).then(onSuccess).catch(onError); - } - else if (main_api == 'textgenerationwebui' && isStreamingEnabled() && type !== 'quiet') { - streamingGeneratorPromise = generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal); - } - else if (main_api == 'novel' && isStreamingEnabled() && type !== 'quiet') { - streamingGeneratorPromise = generateNovelWithStreaming(generate_data, streamingProcessor.abortController.signal); - } - else if (main_api == 'kobold' && isStreamingEnabled() && type !== 'quiet') { - streamingGeneratorPromise = generateKoboldWithStreaming(generate_data, streamingProcessor.abortController.signal); - } - else { - try { - const response = await fetch(generate_url, { - method: 'POST', - headers: getRequestHeaders(), - cache: 'no-cache', - body: JSON.stringify(generate_data), - signal: abortController.signal, - }); - if (!response.ok) { - const error = await response.json(); - throw error; + console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); + /** @type {Promise} */ + let streamingGeneratorPromise = Promise.resolve(); + + if (main_api == 'openai') { + if (isStreamingEnabled() && type !== 'quiet') { + streamingGeneratorPromise = sendOpenAIRequest(type, generate_data.prompt, streamingProcessor.abortController.signal); + } + else { + sendOpenAIRequest(type, generate_data.prompt, abortController.signal).then(onSuccess).catch(onError); + } + } + else if (main_api == 'koboldhorde') { + generateHorde(finalPrompt, generate_data, abortController.signal, true).then(onSuccess).catch(onError); + } + else if (main_api == 'textgenerationwebui' && isStreamingEnabled() && type !== 'quiet') { + streamingGeneratorPromise = generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal); + } + else if (main_api == 'novel' && isStreamingEnabled() && type !== 'quiet') { + streamingGeneratorPromise = generateNovelWithStreaming(generate_data, streamingProcessor.abortController.signal); + } + else if (main_api == 'kobold' && isStreamingEnabled() && type !== 'quiet') { + streamingGeneratorPromise = generateKoboldWithStreaming(generate_data, streamingProcessor.abortController.signal); + } + else { + try { + const response = await fetch(generate_url, { + method: 'POST', + headers: getRequestHeaders(), + cache: 'no-cache', + body: JSON.stringify(generate_data), + signal: abortController.signal, + }); + + if (!response.ok) { + const error = await response.json(); + throw error; + } + + const data = await response.json(); + onSuccess(data); + } catch (error) { + onError(error); + } + } + + if (isStreamingEnabled() && type !== 'quiet') { + try { + const streamingGenerator = await streamingGeneratorPromise; + streamingProcessor.generator = streamingGenerator; + hideSwipeButtons(); + let getMessage = await streamingProcessor.generate(); + let messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); + + if (isContinue) { + getMessage = continue_mag + getMessage; + } + + if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) { + await streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage); + streamingProcessor = null; + triggerAutoContinue(messageChunk, isImpersonate); + } + resolve(); + } catch (err) { + onError(err); } - const data = await response.json(); - onSuccess(data); - } catch (error) { - onError(error); } - } - if (isStreamingEnabled() && type !== 'quiet') { - try { - const streamingGenerator = await streamingGeneratorPromise; - streamingProcessor.generator = streamingGenerator; - hideSwipeButtons(); - let getMessage = await streamingProcessor.generate(); - let messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); + async function onSuccess(data) { + let messageChunk = ''; - if (isContinue) { - getMessage = continue_mag + getMessage; + if (data.error == 'dryRun') { + generatedPromptCache = ''; + resolve(); + return; } - if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) { - await streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage); - streamingProcessor = null; + if (!data.error) { + //const getData = await response.json(); + let getMessage = extractMessageFromData(data); + let title = extractTitleFromData(data); + kobold_horde_model = title; + + const swipes = extractMultiSwipes(data, type); + + messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); + + if (isContinue) { + getMessage = continue_mag + getMessage; + } + + //Formating + const displayIncomplete = type === 'quiet' && !quietToLoud; + getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete); + + if (getMessage.length > 0) { + if (isImpersonate) { + $('#send_textarea').val(getMessage).trigger('input'); + generatedPromptCache = ''; + await eventSource.emit(event_types.IMPERSONATE_READY, getMessage); + } + else if (type == 'quiet') { + resolve(getMessage); + } + else { + // Without streaming we'll be having a full message on continuation. Treat it as a last chunk. + if (originalType !== 'continue') { + ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes)); + } + else { + ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes)); + } + } + activateSendButtons(); + + if (type !== 'quiet') { + playMessageSound(); + } + + generate_loop_counter = 0; + } else { + ++generate_loop_counter; + + if (generate_loop_counter > MAX_GENERATION_LOOPS) { + throwCircuitBreakerError(); + } + + // regenerate with character speech reenforced + // to make sure we leave on swipe type while also adding the name2 appendage + setTimeout(() => { + Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, skipWIAN, force_chid }); + }, generate_loop_counter * 1000); + } + + if (power_user.auto_swipe) { + console.debug('checking for autoswipeblacklist on non-streaming message'); + function containsBlacklistedWords(getMessage, blacklist, threshold) { + console.debug('checking blacklisted words'); + const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); + const matches = getMessage.match(regex) || []; + return matches.length >= threshold; + } + + const generatedTextFiltered = (getMessage) => { + if (power_user.auto_swipe_blacklist_threshold) { + if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { + console.debug('Generated text has blacklisted words'); + return true; + } + } + + return false; + }; + if (generatedTextFiltered(getMessage)) { + console.debug('swiping right automatically'); + is_send_press = false; + swipe_right(); + // TODO: do we want to resolve after an auto-swipe? + resolve(); + return; + } + } + } else { + generatedPromptCache = ''; + activateSendButtons(); + //console.log('runGenerate calling showSwipeBtns'); + showSwipeButtons(); + + if (data?.response) { + toastr.error(data.response, 'API Error'); + } + } + console.debug('/api/chats/save called by /Generate'); + + await saveChatConditional(); + is_send_press = false; + hideStopButton(); + activateSendButtons(); + showSwipeButtons(); + setGenerationProgress(0); + streamingProcessor = null; + + if (type !== 'quiet') { triggerAutoContinue(messageChunk, isImpersonate); } resolve(); - } catch (err) { - onError(err); } - } - - async function onSuccess(data) { - let messageChunk = ''; - - if (data.error == 'dryRun') { - generatedPromptCache = ''; - resolve(); - return; - } - - if (!data.error) { - //const getData = await response.json(); - let getMessage = extractMessageFromData(data); - let title = extractTitleFromData(data); - kobold_horde_model = title; - - const swipes = extractMultiSwipes(data, type); - - messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); - - if (isContinue) { - getMessage = continue_mag + getMessage; + function onError(exception) { + if (typeof exception?.error?.message === 'string') { + toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 }); } - //Formating - const displayIncomplete = type === 'quiet' && !quietToLoud; - getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete); - - if (getMessage.length > 0) { - if (isImpersonate) { - $('#send_textarea').val(getMessage).trigger('input'); - generatedPromptCache = ''; - await eventSource.emit(event_types.IMPERSONATE_READY, getMessage); - } - else if (type == 'quiet') { - resolve(getMessage); - } - else { - // Without streaming we'll be having a full message on continuation. Treat it as a last chunk. - if (originalType !== 'continue') { - ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes)); - } - else { - ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes)); - } - } - activateSendButtons(); - - if (type !== 'quiet') { - playMessageSound(); - } - - generate_loop_counter = 0; - } else { - ++generate_loop_counter; - - if (generate_loop_counter > MAX_GENERATION_LOOPS) { - throwCircuitBreakerError(); - } - - // regenerate with character speech reenforced - // to make sure we leave on swipe type while also adding the name2 appendage - setTimeout(() => { - Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, skipWIAN, force_chid }); - }, generate_loop_counter * 1000); - } - - if (power_user.auto_swipe) { - console.debug('checking for autoswipeblacklist on non-streaming message'); - function containsBlacklistedWords(getMessage, blacklist, threshold) { - console.debug('checking blacklisted words'); - const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); - const matches = getMessage.match(regex) || []; - return matches.length >= threshold; - } - - const generatedTextFiltered = (getMessage) => { - if (power_user.auto_swipe_blacklist_threshold) { - if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { - console.debug('Generated text has blacklisted words'); - return true; - } - } - - return false; - }; - if (generatedTextFiltered(getMessage)) { - console.debug('swiping right automatically'); - is_send_press = false; - swipe_right(); - // TODO: do we want to resolve after an auto-swipe? - resolve(); - return; - } - } - } else { - generatedPromptCache = ''; - activateSendButtons(); - //console.log('runGenerate calling showSwipeBtns'); - showSwipeButtons(); - - if (data?.response) { - toastr.error(data.response, 'API Error'); - } + reject(exception); + unblockGeneration(); + console.log(exception); + streamingProcessor = null; } - console.debug('/api/chats/save called by /Generate'); - - await saveChatConditional(); - is_send_press = false; - hideStopButton(); - activateSendButtons(); - showSwipeButtons(); - setGenerationProgress(0); - streamingProcessor = null; - - if (type !== 'quiet') { - triggerAutoContinue(messageChunk, isImpersonate); - } - resolve(); - } - - function onError(exception) { - if (typeof exception?.error?.message === 'string') { - toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 }); - } - - reject(exception); - unblockGeneration(); - console.log(exception); - streamingProcessor = null; - } + }); } //rungenerate ends } else { //generate's primary loop ends, after this is error handling for no-connection or safety-id diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 21929684f..1bbb20087 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -612,11 +612,11 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { if (online_status === 'no_connection') { is_group_generating = false; setSendButtonState(false); - return; + return Promise.resolve(); } if (is_group_generating) { - return false; + return Promise.resolve(); } // Auto-navigate back to group menu @@ -630,7 +630,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { if (!group || !Array.isArray(group.members) || !group.members.length) { sendSystemMessage(system_message_types.EMPTY, '', { isSmallSys: true }); - return; + return Promise.resolve(); } try { @@ -717,32 +717,16 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { setCharacterName(characters[chId].name); // Wait for generation to finish - await new Promise(async (resolve, reject) => { - await Generate(generateType, { - automatic_trigger: by_auto_mode, - ...(params || {}), - resolve: function(...args) { - if (typeof params.resolve === 'function') { - params.resolve(...args); - } - resolve(); - }, - reject: function(...args) { - if (typeof params.reject === 'function') { - params.reject(...args); - } - reject(); - }, - }); + const generateFinished = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); + await generateFinished; - if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { - // update indicator and scroll down - typingIndicator - .find('.typing_indicator_name') - .text(characters[chId].name); - typingIndicator.show(); - } - }); + if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { + // update indicator and scroll down + typingIndicator + .find('.typing_indicator_name') + .text(characters[chId].name); + typingIndicator.show(); + } } } finally { typingIndicator.hide(); @@ -755,6 +739,8 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { activateSendButtons(); showSwipeButtons(); } + + return Promise.resolve(); } function getLastMessageGenerationId() { From 5fd466b53fb8ac24a18bd6ffba1c658b5a9f31f2 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 10 Dec 2023 13:54:39 -0500 Subject: [PATCH 04/12] Fix generateQuietPrompt --- public/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 98481d21c..1d08d4525 100644 --- a/public/script.js +++ b/public/script.js @@ -2308,7 +2308,7 @@ function getStoppingStrings(isImpersonate, isContinue) { export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null) { console.log('got into genQuietPrompt'); const generateFinished = await Generate('quiet', { quiet_prompt, quietToLoud, skipWIAN: skipWIAN, force_name2: true, quietImage: quietImage }); - await generateFinished; + return generateFinished; } async function processCommands(message, type, dryRun) { From ae9445e5002904aaa1d582c95b0e9694be6bc3a9 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 10 Dec 2023 13:56:31 -0500 Subject: [PATCH 05/12] Reject on data.error --- public/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/script.js b/public/script.js index 1d08d4525..da86a9875 100644 --- a/public/script.js +++ b/public/script.js @@ -3915,6 +3915,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu if (data?.response) { toastr.error(data.response, 'API Error'); } + reject(data.response); } console.debug('/api/chats/save called by /Generate'); From 315d981804d581a8c29b3b11d9fe61478831e8df Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 10 Dec 2023 18:13:18 -0500 Subject: [PATCH 06/12] Reject generation on circuit breaker error --- public/script.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index da86a9875..21c9c3dc3 100644 --- a/public/script.js +++ b/public/script.js @@ -3868,13 +3868,16 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu ++generate_loop_counter; if (generate_loop_counter > MAX_GENERATION_LOOPS) { - throwCircuitBreakerError(); + reject(new Error('Generate circuit breaker interruption')); + if (type !== 'quiet') { + throwCircuitBreakerError(); + } } // regenerate with character speech reenforced // to make sure we leave on swipe type while also adding the name2 appendage setTimeout(() => { - Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, skipWIAN, force_chid }); + Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, skipWIAN, force_chid }); }, generate_loop_counter * 1000); } From 3d7c9014644f57b942e62d76df1cd3e35d5dcd92 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 10 Dec 2023 18:35:46 -0500 Subject: [PATCH 07/12] Remove looping backoff behavior --- public/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 21c9c3dc3..8e013027e 100644 --- a/public/script.js +++ b/public/script.js @@ -3878,7 +3878,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu // to make sure we leave on swipe type while also adding the name2 appendage setTimeout(() => { Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, skipWIAN, force_chid }); - }, generate_loop_counter * 1000); + }, 1000); } if (power_user.auto_swipe) { From 3ab1962b8455763ba1bd917ee3879d2d3bb528d6 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sun, 10 Dec 2023 18:43:17 -0500 Subject: [PATCH 08/12] Improve circuit breaker We now track the loop counter as a parameter of Generate that we decrement with every recursive call, rather than a global variable, and it *should* now work with quiet prompt generation. --- public/script.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/public/script.js b/public/script.js index 8e013027e..c88e730e3 100644 --- a/public/script.js +++ b/public/script.js @@ -693,7 +693,6 @@ let abortController; //css var css_mes_bg = $('
').css('background'); var css_send_form_display = $('
').css('display'); -let generate_loop_counter = 0; const MAX_GENERATION_LOOPS = 5; var kobold_horde_model = ''; @@ -2889,7 +2888,7 @@ export async function generateRaw(prompt, api, instructOverride) { } // Returns a promise that resolves when the text is done generating. -async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage } = {}, dryRun = false) { +async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops } = {}, dryRun = false) { console.log('Generate entered'); setGenerationProgress(0); generation_started = new Date(); @@ -2939,8 +2938,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu } if (selected_group && !is_group_generating && !dryRun) { - // TODO: await here! - return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage }); + // Returns the promise that generateGroupWrapper returns; resolves when generation is done + return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, maxLoops }); } else if (selected_group && !is_group_generating && dryRun) { const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index])); const group = groups.find((x) => x.id === selected_group); @@ -3862,23 +3861,25 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu if (type !== 'quiet') { playMessageSound(); } - - generate_loop_counter = 0; } else { - ++generate_loop_counter; + // If maxLoops is not passed in (e.g. first time generating), set it to MAX_GENERATION_LOOPS + maxLoops ??= MAX_GENERATION_LOOPS; - if (generate_loop_counter > MAX_GENERATION_LOOPS) { + if (maxLoops === 0) { reject(new Error('Generate circuit breaker interruption')); if (type !== 'quiet') { throwCircuitBreakerError(); } + return; } // regenerate with character speech reenforced // to make sure we leave on swipe type while also adding the name2 appendage - setTimeout(() => { - Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, skipWIAN, force_chid }); - }, 1000); + delay(1000).then(async () => { + // The first await is for waiting for the generate to start. The second one is waiting for it to finish + const result = await await Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, skipWIAN, force_chid, maxLoops: maxLoops - 1 }); + resolve(result); + }); } if (power_user.auto_swipe) { @@ -4414,7 +4415,6 @@ function getGenerateUrl(api) { function throwCircuitBreakerError() { callPopup(`Could not extract reply in ${MAX_GENERATION_LOOPS} attempts. Try generating again`, 'text'); - generate_loop_counter = 0; unblockGeneration(); throw new Error('Generate circuit breaker interruption'); } From 0fcf8fd491a746ff8b78f22637496262c2d21f9c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:23:00 +0200 Subject: [PATCH 09/12] Typing indicator fixed --- public/scripts/group-chats.js | 8 ++++---- public/style.css | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 1bbb20087..bb5773de1 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -716,10 +716,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { setCharacterId(chId); setCharacterName(characters[chId].name); - // Wait for generation to finish - const generateFinished = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); - await generateFinished; - if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { // update indicator and scroll down typingIndicator @@ -727,6 +723,10 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { .text(characters[chId].name); typingIndicator.show(); } + + // Wait for generation to finish + const generateFinished = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); + await generateFinished; } } finally { typingIndicator.hide(); diff --git a/public/style.css b/public/style.css index b7ef54902..d3aadc30a 100644 --- a/public/style.css +++ b/public/style.css @@ -636,6 +636,7 @@ hr { display: none; order: 2; padding-right: 2px; + place-self: center; } #options_button { From e96fb0c1b59b6c0de13a05379f6620ba48fd4bf5 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:42 +0200 Subject: [PATCH 10/12] Fix group wrapper not resolving to a valid text --- public/scripts/extensions/stable-diffusion/index.js | 6 ++++++ public/scripts/group-chats.js | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 9a57bcd8e..b5c885c8c 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -1781,6 +1781,12 @@ async function generateMultimodalPrompt(generationType, quietPrompt) { */ async function generatePrompt(quietPrompt) { const reply = await generateQuietPrompt(quietPrompt, false, false); + + if (!reply) { + toastr.error('Prompt generation produced no text. Make sure you\'re using a valid instruct template and try again', 'Image Generation'); + throw new Error('Prompt generation failed.'); + } + return processReply(reply); } diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index bb5773de1..c76d53f3f 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -627,6 +627,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { const group = groups.find((x) => x.id === selected_group); let typingIndicator = $('#chat .typing_indicator'); + let textResult = ''; if (!group || !Array.isArray(group.members) || !group.members.length) { sendSystemMessage(system_message_types.EMPTY, '', { isSmallSys: true }); @@ -726,7 +727,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { // Wait for generation to finish const generateFinished = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); - await generateFinished; + textResult = await generateFinished; } } finally { typingIndicator.hide(); @@ -740,7 +741,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { showSwipeButtons(); } - return Promise.resolve(); + return Promise.resolve(textResult); } function getLastMessageGenerationId() { From 0302686a962c75fdcc7636c08232d3269e767c17 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:07:33 +0200 Subject: [PATCH 11/12] Return from Generate if calling circuit breaker --- public/script.js | 1 + public/scripts/extensions/stable-diffusion/index.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index c88e730e3..e4ff2e0ad 100644 --- a/public/script.js +++ b/public/script.js @@ -3880,6 +3880,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu const result = await await Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, skipWIAN, force_chid, maxLoops: maxLoops - 1 }); resolve(result); }); + return; } if (power_user.auto_swipe) { diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index b5c885c8c..9935d43e4 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -1781,13 +1781,14 @@ async function generateMultimodalPrompt(generationType, quietPrompt) { */ async function generatePrompt(quietPrompt) { const reply = await generateQuietPrompt(quietPrompt, false, false); + const processedReply = processReply(reply); - if (!reply) { + if (!processedReply) { toastr.error('Prompt generation produced no text. Make sure you\'re using a valid instruct template and try again', 'Image Generation'); throw new Error('Prompt generation failed.'); } - return processReply(reply); + return processedReply; } async function sendGenerationRequest(generationType, prompt, characterName = null, callback) { From d38a4dc6c1bf2e165bb6fb3d7835389cd5dab9c3 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:03:31 +0200 Subject: [PATCH 12/12] Fix abort group generation --- public/scripts/group-chats.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index c76d53f3f..dad8b167f 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -609,6 +609,12 @@ function getGroupChatNames(groupId) { } async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { + function throwIfAborted() { + if (params.signal instanceof AbortSignal && params.signal.aborted) { + throw new Error('AbortSignal was fired. Group generation stopped'); + } + } + if (online_status === 'no_connection') { is_group_generating = false; setSendButtonState(false); @@ -635,6 +641,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { } try { + throwIfAborted(); hideSwipeButtons(); is_group_generating = true; setCharacterName(''); @@ -664,10 +671,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { } } - if (params.signal instanceof AbortSignal && params.signal.aborted) { - throw new Error('Already aborted signal passed. Group generation stopped'); - } - const activationStrategy = Number(group.activation_strategy ?? group_activation_strategy.NATURAL); const enabledMembers = group.members.filter(x => !group.disabled_members.includes(x)); let activatedMembers = []; @@ -712,6 +715,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { // now the real generation begins: cycle through every activated character for (const chId of activatedMembers) { + throwIfAborted(); deactivateSendButtons(); const generateType = type == 'swipe' || type == 'impersonate' || type == 'quiet' || type == 'continue' ? type : 'group_chat'; setCharacterId(chId);