| import { getStringHash, debounce, waitUntilCondition, extractAllWords, isTrueBoolean } from '../../utils.js'; |
| import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| import { |
| activateSendButtons, |
| deactivateSendButtons, |
| animation_duration, |
| eventSource, |
| event_types, |
| extension_prompt_roles, |
| extension_prompt_types, |
| generateQuietPrompt, |
| is_send_press, |
| saveSettingsDebounced, |
| substituteParamsExtended, |
| generateRaw, |
| getMaxContextSize, |
| setExtensionPrompt, |
| streamingProcessor, |
| animation_easing, |
| } from '../../../script.js'; |
| import { is_group_generating, selected_group } from '../../group-chats.js'; |
| import { loadMovingUIState, power_user } from '../../power-user.js'; |
| import { dragElement } from '../../RossAscends-mods.js'; |
| import { getTextTokens, getTokenCountAsync, tokenizers } from '../../tokenizers.js'; |
| import { debounce_timeout } from '../../constants.js'; |
| import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| import { macros, MacroCategory } from '../../macros/macro-system.js'; |
| import { countWebLlmTokens, generateWebLlmChatPrompt, getWebLlmContextSize, isWebLlmSupported } from '../shared.js'; |
| import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| import { removeReasoningFromString } from '../../reasoning.js'; |
| import { MacrosParser } from '/scripts/macros.js'; |
| export { MODULE_NAME }; |
|
|
| const MODULE_NAME = '1_memory'; |
|
|
| let lastMessageHash = null; |
| let lastMessageId = null; |
| let inApiCall = false; |
|
|
| |
| |
| |
| |
| |
| |
| async function countSourceTokens(text, padding = 0) { |
| if (extension_settings.memory.source === summary_sources.webllm) { |
| const count = await countWebLlmTokens(text); |
| return count + padding; |
| } |
|
|
| if (extension_settings.memory.source === summary_sources.extras) { |
| const count = getTextTokens(tokenizers.GPT2, text).length; |
| return count + padding; |
| } |
|
|
| return await getTokenCountAsync(text, padding); |
| } |
|
|
| async function getSourceContextSize() { |
| const overrideLength = extension_settings.memory.overrideResponseLength; |
|
|
| if (extension_settings.memory.source === summary_sources.webllm) { |
| const maxContext = await getWebLlmContextSize(); |
| return overrideLength > 0 ? (maxContext - overrideLength) : Math.round(maxContext * 0.75); |
| } |
|
|
| if (extension_settings.source === summary_sources.extras) { |
| return 1024 - 64; |
| } |
|
|
| return getMaxContextSize(overrideLength); |
| } |
|
|
| const formatMemoryValue = function (value) { |
| if (!value) { |
| return ''; |
| } |
|
|
| value = value.trim(); |
|
|
| if (extension_settings.memory.template) { |
| return substituteParamsExtended(extension_settings.memory.template, { summary: value }); |
| } else { |
| return `Summary: ${value}`; |
| } |
| }; |
|
|
| const saveChatDebounced = debounce(() => getContext().saveChat(), debounce_timeout.relaxed); |
|
|
| const summary_sources = { |
| 'extras': 'extras', |
| 'main': 'main', |
| 'webllm': 'webllm', |
| }; |
|
|
| const prompt_builders = { |
| DEFAULT: 0, |
| RAW_BLOCKING: 1, |
| RAW_NON_BLOCKING: 2, |
| }; |
|
|
| const defaultPrompt = 'Ignore previous instructions. Summarize the most important facts and events in the story so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.'; |
| const defaultTemplate = '[Summary: {{summary}}]'; |
|
|
| const defaultSettings = { |
| memoryFrozen: false, |
| SkipWIAN: false, |
| source: summary_sources.extras, |
| prompt: defaultPrompt, |
| template: defaultTemplate, |
| position: extension_prompt_types.IN_PROMPT, |
| role: extension_prompt_roles.SYSTEM, |
| scan: false, |
| depth: 2, |
| promptWords: 200, |
| promptMinWords: 25, |
| promptMaxWords: 1000, |
| promptWordsStep: 25, |
| promptInterval: 10, |
| promptMinInterval: 0, |
| promptMaxInterval: 250, |
| promptIntervalStep: 1, |
| promptForceWords: 0, |
| promptForceWordsStep: 100, |
| promptMinForceWords: 0, |
| promptMaxForceWords: 10000, |
| overrideResponseLength: 0, |
| overrideResponseLengthMin: 0, |
| overrideResponseLengthMax: 4096, |
| overrideResponseLengthStep: 16, |
| maxMessagesPerRequest: 0, |
| maxMessagesPerRequestMin: 0, |
| maxMessagesPerRequestMax: 250, |
| maxMessagesPerRequestStep: 1, |
| prompt_builder: prompt_builders.DEFAULT, |
| }; |
|
|
| function loadSettings() { |
| if (Object.keys(extension_settings.memory).length === 0) { |
| Object.assign(extension_settings.memory, defaultSettings); |
| } |
|
|
| for (const key of Object.keys(defaultSettings)) { |
| if (extension_settings.memory[key] === undefined) { |
| extension_settings.memory[key] = defaultSettings[key]; |
| } |
| } |
|
|
| $('#summary_source').val(extension_settings.memory.source).trigger('change'); |
| $('#memory_frozen').prop('checked', extension_settings.memory.memoryFrozen).trigger('input'); |
| $('#memory_skipWIAN').prop('checked', extension_settings.memory.SkipWIAN).trigger('input'); |
| $('#memory_prompt').val(extension_settings.memory.prompt).trigger('input'); |
| $('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input'); |
| $('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input'); |
| $('#memory_template').val(extension_settings.memory.template).trigger('input'); |
| $('#memory_depth').val(extension_settings.memory.depth).trigger('input'); |
| $('#memory_role').val(extension_settings.memory.role).trigger('input'); |
| $(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input'); |
| $('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input'); |
| $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); |
| $('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input'); |
| $('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input'); |
| $('#memory_include_wi_scan').prop('checked', extension_settings.memory.scan).trigger('input'); |
| switchSourceControls(extension_settings.memory.source); |
| } |
|
|
| async function onPromptForceWordsAutoClick() { |
| const context = getContext(); |
| const maxPromptLength = await getSourceContextSize(); |
| const chat = context.chat; |
| const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); |
| const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; |
| const averageMessageWordCount = messagesWordCount / allMessages.length; |
| const tokensPerWord = await countSourceTokens(allMessages.join('\n')) / messagesWordCount; |
| const wordsPerToken = 1 / tokensPerWord; |
| const maxPromptLengthWords = Math.round(maxPromptLength * wordsPerToken); |
| |
| const wordsPerPrompt = Math.floor(maxPromptLength / tokensPerWord); |
| |
| const summaryPromptWords = extractAllWords(extension_settings.memory.prompt).length; |
| const promptAllowanceWords = maxPromptLengthWords - extension_settings.memory.promptWords - summaryPromptWords; |
| const averageMessagesPerPrompt = Math.floor(promptAllowanceWords / averageMessageWordCount); |
| const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0; |
| const targetMessagesInPrompt = maxMessagesPerSummary > 0 ? maxMessagesPerSummary : Math.max(0, averageMessagesPerPrompt); |
| const targetSummaryWords = (targetMessagesInPrompt * averageMessageWordCount) + (promptAllowanceWords / 4); |
|
|
| console.table({ |
| maxPromptLength, |
| maxPromptLengthWords, |
| promptAllowanceWords, |
| averageMessagesPerPrompt, |
| targetMessagesInPrompt, |
| targetSummaryWords, |
| wordsPerPrompt, |
| wordsPerToken, |
| tokensPerWord, |
| messagesWordCount, |
| }); |
|
|
| const ROUNDING = 100; |
| extension_settings.memory.promptForceWords = Math.max(1, Math.floor(targetSummaryWords / ROUNDING) * ROUNDING); |
| $('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input'); |
| } |
|
|
| async function onPromptIntervalAutoClick() { |
| const context = getContext(); |
| const maxPromptLength = await getSourceContextSize(); |
| const chat = context.chat; |
| const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes); |
| const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length; |
| const messagesTokenCount = await countSourceTokens(allMessages.join('\n')); |
| const tokensPerWord = messagesTokenCount / messagesWordCount; |
| const averageMessageTokenCount = messagesTokenCount / allMessages.length; |
| const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord); |
| const promptTokens = await countSourceTokens(extension_settings.memory.prompt); |
| const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens; |
| const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0; |
| const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount); |
| const targetMessagesInPrompt = maxMessagesPerSummary > 0 ? maxMessagesPerSummary : Math.max(0, averageMessagesPerPrompt); |
| const adjustedAverageMessagesPerPrompt = targetMessagesInPrompt + (averageMessagesPerPrompt - targetMessagesInPrompt) / 4; |
|
|
| console.table({ |
| maxPromptLength, |
| promptAllowance, |
| targetSummaryTokens, |
| promptTokens, |
| messagesWordCount, |
| messagesTokenCount, |
| tokensPerWord, |
| averageMessageTokenCount, |
| averageMessagesPerPrompt, |
| targetMessagesInPrompt, |
| adjustedAverageMessagesPerPrompt, |
| maxMessagesPerSummary, |
| }); |
|
|
| const ROUNDING = 5; |
| extension_settings.memory.promptInterval = Math.max(1, Math.floor(adjustedAverageMessagesPerPrompt / ROUNDING) * ROUNDING); |
|
|
| $('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input'); |
| } |
|
|
| function onSummarySourceChange(event) { |
| const value = event.target.value; |
| extension_settings.memory.source = value; |
| switchSourceControls(value); |
| saveSettingsDebounced(); |
| } |
|
|
| function switchSourceControls(value) { |
| $('#summaryExtensionDrawerContents [data-summary-source], #memory_settings [data-summary-source]').each((_, element) => { |
| const source = element.dataset.summarySource.split(',').map(s => s.trim()); |
| $(element).toggle(source.includes(value)); |
| }); |
| } |
|
|
| function onMemoryFrozenInput() { |
| const value = Boolean($(this).prop('checked')); |
| extension_settings.memory.memoryFrozen = value; |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemorySkipWIANInput() { |
| const value = Boolean($(this).prop('checked')); |
| extension_settings.memory.SkipWIAN = value; |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryPromptWordsInput() { |
| const value = $(this).val(); |
| extension_settings.memory.promptWords = Number(value); |
| $('#memory_prompt_words_value').text(extension_settings.memory.promptWords); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryPromptIntervalInput() { |
| const value = $(this).val(); |
| extension_settings.memory.promptInterval = Number(value); |
| $('#memory_prompt_interval_value').text(extension_settings.memory.promptInterval); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryPromptRestoreClick() { |
| $('#memory_prompt').val(defaultPrompt).trigger('input'); |
| } |
|
|
| function onMemoryPromptInput() { |
| const value = $(this).val(); |
| extension_settings.memory.prompt = value; |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryTemplateInput() { |
| const value = $(this).val(); |
| extension_settings.memory.template = value; |
| reinsertMemory(); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryDepthInput() { |
| const value = $(this).val(); |
| extension_settings.memory.depth = Number(value); |
| reinsertMemory(); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryRoleInput() { |
| const value = $(this).val(); |
| extension_settings.memory.role = Number(value); |
| reinsertMemory(); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryPositionChange(e) { |
| const value = e.target.value; |
| extension_settings.memory.position = value; |
| reinsertMemory(); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryIncludeWIScanInput() { |
| const value = !!$(this).prop('checked'); |
| extension_settings.memory.scan = value; |
| reinsertMemory(); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMemoryPromptWordsForceInput() { |
| const value = $(this).val(); |
| extension_settings.memory.promptForceWords = Number(value); |
| $('#memory_prompt_words_force_value').text(extension_settings.memory.promptForceWords); |
| saveSettingsDebounced(); |
| } |
|
|
| function onOverrideResponseLengthInput() { |
| const value = $(this).val(); |
| extension_settings.memory.overrideResponseLength = Number(value); |
| $('#memory_override_response_length_value').text(extension_settings.memory.overrideResponseLength); |
| saveSettingsDebounced(); |
| } |
|
|
| function onMaxMessagesPerRequestInput() { |
| const value = $(this).val(); |
| extension_settings.memory.maxMessagesPerRequest = Number(value); |
| $('#memory_max_messages_per_request_value').text(extension_settings.memory.maxMessagesPerRequest); |
| saveSettingsDebounced(); |
| } |
|
|
| |
| |
| |
| |
| |
| function getLatestMemoryFromChat(chat) { |
| if (!Array.isArray(chat) || !chat.length) { |
| return ''; |
| } |
|
|
| const reversedChat = chat.slice().reverse(); |
| reversedChat.shift(); |
| for (let mes of reversedChat) { |
| if (mes.extra && mes.extra.memory) { |
| return mes.extra.memory; |
| } |
| } |
|
|
| return ''; |
| } |
|
|
| |
| |
| |
| |
| |
| function getIndexOfLatestChatSummary(chat) { |
| if (!Array.isArray(chat) || !chat.length) { |
| return -1; |
| } |
|
|
| const reversedChat = chat.slice().reverse(); |
| reversedChat.shift(); |
| for (let mes of reversedChat) { |
| if (mes.extra && mes.extra.memory) { |
| return chat.indexOf(mes); |
| } |
| } |
|
|
| return -1; |
| } |
|
|
| |
| |
| |
| |
| |
| function isContextChanged(context) { |
| const newContext = getContext(); |
| if (newContext.groupId !== context.groupId |
| || newContext.chatId !== context.chatId |
| || (!newContext.groupId && (newContext.characterId !== context.characterId))) { |
| console.log('Context changed, summary discarded'); |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| function onChatChanged() { |
| const context = getContext(); |
| const latestMemory = getLatestMemoryFromChat(context.chat); |
| setMemoryContext(latestMemory, false); |
| } |
|
|
| async function onChatEvent() { |
| |
| if (extension_settings.memory.source === summary_sources.extras && !modules.includes('summarize')) { |
| return; |
| } |
|
|
| |
| if (extension_settings.memory.source === summary_sources.webllm && !isWebLlmSupported()) { |
| return; |
| } |
|
|
| |
| if (streamingProcessor && !streamingProcessor.isFinished) { |
| return; |
| } |
|
|
| |
| if (inApiCall || extension_settings.memory.memoryFrozen) { |
| return; |
| } |
|
|
| const context = getContext(); |
| const chat = context.chat; |
|
|
| |
| if (chat.length === 0 || (lastMessageId === chat.length && getStringHash(chat[chat.length - 1].mes) === lastMessageHash)) { |
| return; |
| } |
|
|
| |
| if (chat.length < lastMessageId) { |
| const latestMemory = getLatestMemoryFromChat(chat); |
| setMemoryContext(latestMemory, false); |
| } |
|
|
| |
| if (chat.length |
| && chat[chat.length - 1].extra |
| && chat[chat.length - 1].extra.memory |
| && lastMessageId === chat.length |
| && getStringHash(chat[chat.length - 1].mes) !== lastMessageHash) { |
| delete chat[chat.length - 1].extra.memory; |
| } |
|
|
| summarizeChat(context) |
| .catch(console.error) |
| .finally(() => { |
| lastMessageId = context.chat?.length ?? null; |
| lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? ''); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| async function forceSummarizeChat(quiet) { |
| if (extension_settings.memory.source === summary_sources.extras) { |
| toastr.warning('Force summarization is not supported for Extras API'); |
| return; |
| } |
|
|
| const context = getContext(); |
| const skipWIAN = extension_settings.memory.SkipWIAN; |
|
|
| const toast = quiet ? jQuery() : toastr.info('Summarizing chat...', 'Please wait', { timeOut: 0, extendedTimeOut: 0 }); |
| const value = extension_settings.memory.source === summary_sources.main |
| ? await summarizeChatMain(context, true, skipWIAN) |
| : await summarizeChatWebLLM(context, true); |
|
|
| toastr.clear(toast); |
|
|
| if (!value) { |
| toastr.warning('Failed to summarize chat'); |
| return ''; |
| } |
|
|
| return value; |
| } |
|
|
| |
| |
| |
| |
| |
| async function summarizeCallback(args, text) { |
| text = text.trim(); |
|
|
| |
| if (!text) { |
| const quiet = isTrueBoolean(args.quiet); |
| return await forceSummarizeChat(quiet); |
| } |
|
|
| const source = args.source || extension_settings.memory.source; |
| const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords }); |
|
|
| try { |
| switch (source) { |
| case summary_sources.extras: |
| return await callExtrasSummarizeAPI(text); |
| case summary_sources.main: |
| return removeReasoningFromString(await generateRaw({ prompt: text, systemPrompt: prompt, responseLength: extension_settings.memory.overrideResponseLength })); |
| case summary_sources.webllm: { |
| const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content); |
| const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; |
| return await generateWebLlmChatPrompt(messages, params); |
| } |
| default: |
| toastr.warning('Invalid summarization source specified'); |
| return ''; |
| } |
| } catch (error) { |
| toastr.error(String(error), 'Failed to summarize text'); |
| console.log(error); |
| return ''; |
| } |
| } |
|
|
| async function summarizeChat(context) { |
| const skipWIAN = extension_settings.memory.SkipWIAN; |
| switch (extension_settings.memory.source) { |
| case summary_sources.extras: |
| await summarizeChatExtras(context); |
| break; |
| case summary_sources.main: |
| await summarizeChatMain(context, false, skipWIAN); |
| break; |
| case summary_sources.webllm: |
| await summarizeChatWebLLM(context, false); |
| break; |
| default: |
| break; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function getSummaryPromptForNow(context, force) { |
| if (extension_settings.memory.promptInterval === 0 && !force) { |
| console.debug('Prompt interval is set to 0, skipping summarization'); |
| return ''; |
| } |
|
|
| try { |
| |
| if (selected_group) { |
| await waitUntilCondition(() => is_group_generating === false, 1000, 10); |
| } |
| |
| await waitUntilCondition(() => is_send_press === false, 30000, 100); |
| } catch { |
| console.debug('Timeout waiting for is_send_press'); |
| return ''; |
| } |
|
|
| if (!context.chat.length) { |
| console.debug('No messages in chat to summarize'); |
| return ''; |
| } |
|
|
| if (context.chat.length < extension_settings.memory.promptInterval && !force) { |
| console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`); |
| return ''; |
| } |
|
|
| let messagesSinceLastSummary = 0; |
| let wordsSinceLastSummary = 0; |
| let conditionSatisfied = false; |
| for (let i = context.chat.length - 1; i >= 0; i--) { |
| if (context.chat[i].extra && context.chat[i].extra.memory) { |
| break; |
| } |
| messagesSinceLastSummary++; |
| wordsSinceLastSummary += extractAllWords(context.chat[i].mes).length; |
| } |
|
|
| if (messagesSinceLastSummary >= extension_settings.memory.promptInterval) { |
| conditionSatisfied = true; |
| } |
|
|
| if (extension_settings.memory.promptForceWords && wordsSinceLastSummary >= extension_settings.memory.promptForceWords) { |
| conditionSatisfied = true; |
| } |
|
|
| if (!conditionSatisfied && !force) { |
| console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`); |
| return ''; |
| } |
|
|
| console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary); |
| const prompt = substituteParamsExtended(extension_settings.memory.prompt, { words: extension_settings.memory.promptWords }); |
|
|
| if (!prompt) { |
| console.debug('Summarization prompt is empty. Skipping summarization.'); |
| return ''; |
| } |
|
|
| return prompt; |
| } |
|
|
| async function summarizeChatWebLLM(context, force) { |
| if (!isWebLlmSupported()) { |
| return; |
| } |
|
|
| const prompt = await getSummaryPromptForNow(context, force); |
|
|
| if (!prompt) { |
| return; |
| } |
|
|
| const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt); |
|
|
| if (lastUsedIndex === null || lastUsedIndex === -1) { |
| if (force) { |
| toastr.info('To try again, remove the latest summary.', 'No messages found to summarize'); |
| } |
|
|
| return null; |
| } |
|
|
| const messages = [ |
| { role: 'system', content: prompt }, |
| { role: 'user', content: rawPrompt }, |
| ]; |
|
|
| const params = {}; |
|
|
| if (extension_settings.memory.overrideResponseLength > 0) { |
| params.max_tokens = extension_settings.memory.overrideResponseLength; |
| } |
|
|
| try { |
| inApiCall = true; |
| const summary = await generateWebLlmChatPrompt(messages, params); |
|
|
| if (!summary) { |
| console.warn('Empty summary received'); |
| return; |
| } |
|
|
| |
| if (isContextChanged(context)) { |
| return; |
| } |
|
|
| setMemoryContext(summary, true, lastUsedIndex); |
| return summary; |
| } finally { |
| inApiCall = false; |
| } |
| } |
|
|
| async function summarizeChatMain(context, force, skipWIAN) { |
| const prompt = await getSummaryPromptForNow(context, force); |
|
|
| if (!prompt) { |
| return; |
| } |
|
|
| console.log('sending summary prompt'); |
| let summary = ''; |
| let index = null; |
|
|
| if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { |
| try { |
| inApiCall = true; |
| |
| const params = { |
| quietPrompt: prompt, |
| skipWIAN: skipWIAN, |
| responseLength: extension_settings.memory.overrideResponseLength, |
| }; |
| summary = await generateQuietPrompt(params); |
| } finally { |
| inApiCall = false; |
| } |
| } |
|
|
| if ([prompt_builders.RAW_BLOCKING, prompt_builders.RAW_NON_BLOCKING].includes(extension_settings.memory.prompt_builder)) { |
| const lock = extension_settings.memory.prompt_builder === prompt_builders.RAW_BLOCKING; |
| try { |
| inApiCall = true; |
| if (lock) { |
| deactivateSendButtons(); |
| } |
|
|
| const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt); |
|
|
| if (lastUsedIndex === null || lastUsedIndex === -1) { |
| if (force) { |
| toastr.info('To try again, remove the latest summary.', 'No messages found to summarize'); |
| } |
|
|
| return null; |
| } |
|
|
| |
| const params = { |
| prompt: rawPrompt, |
| systemPrompt: prompt, |
| responseLength: extension_settings.memory.overrideResponseLength, |
| }; |
| const rawSummary = await generateRaw(params); |
| summary = removeReasoningFromString(rawSummary); |
| index = lastUsedIndex; |
| } finally { |
| inApiCall = false; |
| if (lock) { |
| activateSendButtons(); |
| } |
| } |
| } |
|
|
| if (!summary) { |
| console.warn('Empty summary received'); |
| return; |
| } |
|
|
| if (isContextChanged(context)) { |
| return; |
| } |
|
|
| setMemoryContext(summary, true, index); |
| return summary; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function getRawSummaryPrompt(context, prompt) { |
| |
| |
| |
| |
| |
| function getMemoryString(includeSystem) { |
| const delimiter = '\n\n'; |
| const stringBuilder = []; |
| const bufferString = chatBuffer.slice().join(delimiter); |
|
|
| if (includeSystem) { |
| stringBuilder.push(prompt); |
| } |
|
|
| if (latestSummary) { |
| stringBuilder.push(latestSummary); |
| } |
|
|
| stringBuilder.push(bufferString); |
|
|
| return stringBuilder.join(delimiter).trim(); |
| } |
|
|
| const chat = context.chat.slice(); |
| const latestSummary = getLatestMemoryFromChat(chat); |
| const latestSummaryIndex = getIndexOfLatestChatSummary(chat); |
| chat.pop(); |
| const chatBuffer = []; |
| const PADDING = 64; |
| const PROMPT_SIZE = await getSourceContextSize(); |
| let latestUsedMessage = null; |
|
|
| for (let index = latestSummaryIndex + 1; index < chat.length; index++) { |
| const message = chat[index]; |
|
|
| if (!message) { |
| break; |
| } |
|
|
| if (message.is_system || !message.mes) { |
| continue; |
| } |
|
|
| const entry = `${message.name}:\n${message.mes}`; |
| chatBuffer.push(entry); |
|
|
| const tokens = await countSourceTokens(getMemoryString(true), PADDING); |
|
|
| if (tokens > PROMPT_SIZE) { |
| chatBuffer.pop(); |
| break; |
| } |
|
|
| latestUsedMessage = message; |
|
|
| if (extension_settings.memory.maxMessagesPerRequest > 0 && chatBuffer.length >= extension_settings.memory.maxMessagesPerRequest) { |
| break; |
| } |
| } |
|
|
| const lastUsedIndex = context.chat.indexOf(latestUsedMessage); |
| const rawPrompt = getMemoryString(false); |
| return { rawPrompt, lastUsedIndex }; |
| } |
|
|
| async function summarizeChatExtras(context) { |
| function getMemoryString() { |
| return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim(); |
| } |
|
|
| const chat = context.chat; |
| const longMemory = getLatestMemoryFromChat(chat); |
| const reversedChat = chat.slice().reverse(); |
| reversedChat.shift(); |
| const memoryBuffer = []; |
| const CONTEXT_SIZE = await getSourceContextSize(); |
|
|
| for (const message of reversedChat) { |
| |
| if (longMemory && message.extra && message.extra.memory == longMemory) { |
| break; |
| } |
|
|
| |
| if (message.is_system) { |
| continue; |
| } |
|
|
| |
| const entry = `${message.name}:\n${message.mes}`; |
| memoryBuffer.push(entry); |
|
|
| |
| const tokens = await countSourceTokens(getMemoryString()); |
| if (tokens >= CONTEXT_SIZE) { |
| break; |
| } |
| } |
|
|
| const resultingString = getMemoryString(); |
| const resultingTokens = await countSourceTokens(resultingString); |
|
|
| if (!resultingString || resultingTokens < CONTEXT_SIZE) { |
| console.debug('Not enough context to summarize'); |
| return; |
| } |
|
|
| |
| try { |
| inApiCall = true; |
| const summary = await callExtrasSummarizeAPI(resultingString); |
|
|
| if (!summary) { |
| console.warn('Empty summary received'); |
| return; |
| } |
|
|
| if (isContextChanged(context)) { |
| return; |
| } |
|
|
| setMemoryContext(summary, true); |
| } |
| catch (error) { |
| console.log(error); |
| } |
| finally { |
| inApiCall = false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| async function callExtrasSummarizeAPI(text) { |
| if (!modules.includes('summarize')) { |
| throw new Error('Summarize module is not enabled in Extras API'); |
| } |
|
|
| const url = new URL(getApiUrl()); |
| url.pathname = '/api/summarize'; |
|
|
| const apiResult = await doExtrasFetch(url, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'Bypass-Tunnel-Reminder': 'bypass', |
| }, |
| body: JSON.stringify({ |
| text: text, |
| params: {}, |
| }), |
| }); |
|
|
| if (apiResult.ok) { |
| const data = await apiResult.json(); |
| const summary = data.summary; |
| return summary; |
| } |
|
|
| throw new Error('Extras API call failed'); |
| } |
|
|
| function onMemoryRestoreClick() { |
| const context = getContext(); |
| const content = $('#memory_contents').val(); |
| const reversedChat = context.chat.slice().reverse(); |
| reversedChat.shift(); |
|
|
| for (let mes of reversedChat) { |
| if (mes.extra && mes.extra.memory == content) { |
| delete mes.extra.memory; |
| break; |
| } |
| } |
|
|
| const newContent = getLatestMemoryFromChat(context.chat); |
| setMemoryContext(newContent, false); |
| } |
|
|
| function onMemoryContentInput() { |
| const value = $(this).val(); |
| setMemoryContext(value, true); |
| } |
|
|
| function onMemoryPromptBuilderInput(e) { |
| const value = Number(e.target.value); |
| extension_settings.memory.prompt_builder = value; |
| saveSettingsDebounced(); |
| } |
|
|
| function reinsertMemory() { |
| const existingValue = String($('#memory_contents').val()); |
| setMemoryContext(existingValue, false); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function setMemoryContext(value, saveToMessage, index = null) { |
| setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, extension_settings.memory.scan, extension_settings.memory.role); |
| $('#memory_contents').val(value); |
|
|
| const summaryLog = value |
| ? `Summary set to: ${value}. Position: ${extension_settings.memory.position}. Depth: ${extension_settings.memory.depth}. Role: ${extension_settings.memory.role}` |
| : 'Summary has no content'; |
| console.debug(summaryLog); |
|
|
| const context = getContext(); |
| if (saveToMessage && context.chat.length) { |
| const idx = index ?? context.chat.length - 2; |
| const mes = context.chat[idx < 0 ? 0 : idx]; |
|
|
| if (!mes.extra) { |
| mes.extra = {}; |
| } |
|
|
| mes.extra.memory = value; |
| saveChatDebounced(); |
| } |
| } |
|
|
| function doPopout(e) { |
| const target = e.target; |
| |
| if ($('#summaryExtensionPopout').length === 0) { |
| console.debug('did not see popout yet, creating'); |
| const originalHTMLClone = $(target).parent().parent().parent().find('.inline-drawer-content').html(); |
| const originalElement = $(target).parent().parent().parent().find('.inline-drawer-content'); |
| const template = $('#zoomed_avatar_template').html(); |
| const controlBarHtml = `<div class="panelControlBar flex-container"> |
| <div id="summaryExtensionPopoutheader" class="fa-solid fa-grip drag-grabber hoverglow"></div> |
| <div id="summaryExtensionPopoutClose" class="fa-solid fa-circle-xmark hoverglow dragClose"></div> |
| </div>`; |
| const newElement = $(template); |
| newElement.attr('id', 'summaryExtensionPopout') |
| .css('opacity', 0) |
| .removeClass('zoomed_avatar') |
| .addClass('draggable') |
| .empty(); |
| const prevSummaryBoxContents = $('#memory_contents').val().toString(); |
| originalElement.empty(); |
| originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>'); |
| newElement.append(controlBarHtml).append(originalHTMLClone); |
| $('#movingDivs').append(newElement); |
| newElement.transition({ opacity: 1, duration: animation_duration, easing: animation_easing }); |
| $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull'); |
| setMemoryContext(prevSummaryBoxContents, false); |
| setupListeners(); |
| loadSettings(); |
| loadMovingUIState(); |
|
|
| dragElement(newElement); |
|
|
| |
| $('#summaryExtensionPopoutClose').off('click').on('click', function () { |
| $('#summaryExtensionDrawerContents').removeClass('scrollableInnerFull'); |
| const summaryPopoutHTML = $('#summaryExtensionDrawerContents'); |
| $('#summaryExtensionPopout').fadeOut(animation_duration, () => { |
| originalElement.empty(); |
| originalElement.append(summaryPopoutHTML); |
| $('#summaryExtensionPopout').remove(); |
| }); |
| loadSettings(); |
| }); |
| } else { |
| console.debug('saw existing popout, removing'); |
| $('#summaryExtensionPopout').fadeOut(animation_duration, () => { $('#summaryExtensionPopoutClose').trigger('click'); }); |
| } |
| } |
|
|
| function setupListeners() { |
| |
| $('#memory_restore').off('click').on('click', onMemoryRestoreClick); |
| $('#memory_contents').off('input').on('input', onMemoryContentInput); |
| $('#memory_frozen').off('input').on('input', onMemoryFrozenInput); |
| $('#memory_skipWIAN').off('input').on('input', onMemorySkipWIANInput); |
| $('#summary_source').off('change').on('change', onSummarySourceChange); |
| $('#memory_prompt_words').off('input').on('input', onMemoryPromptWordsInput); |
| $('#memory_prompt_interval').off('input').on('input', onMemoryPromptIntervalInput); |
| $('#memory_prompt').off('input').on('input', onMemoryPromptInput); |
| $('#memory_force_summarize').off('click').on('click', () => forceSummarizeChat(false)); |
| $('#memory_template').off('input').on('input', onMemoryTemplateInput); |
| $('#memory_depth').off('input').on('input', onMemoryDepthInput); |
| $('#memory_role').off('input').on('input', onMemoryRoleInput); |
| $('input[name="memory_position"]').off('change').on('change', onMemoryPositionChange); |
| $('#memory_prompt_words_force').off('input').on('input', onMemoryPromptWordsForceInput); |
| $('#memory_prompt_builder_default').off('input').on('input', onMemoryPromptBuilderInput); |
| $('#memory_prompt_builder_raw_blocking').off('input').on('input', onMemoryPromptBuilderInput); |
| $('#memory_prompt_builder_raw_non_blocking').off('input').on('input', onMemoryPromptBuilderInput); |
| $('#memory_prompt_restore').off('click').on('click', onMemoryPromptRestoreClick); |
| $('#memory_prompt_interval_auto').off('click').on('click', onPromptIntervalAutoClick); |
| $('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick); |
| $('#memory_override_response_length').off('input').on('input', onOverrideResponseLengthInput); |
| $('#memory_max_messages_per_request').off('input').on('input', onMaxMessagesPerRequestInput); |
| $('#memory_include_wi_scan').off('input').on('input', onMemoryIncludeWIScanInput); |
| $('#summarySettingsBlockToggle').off('click').on('click', function () { |
| $('#summarySettingsBlock').slideToggle(200, 'swing'); |
| }); |
| } |
|
|
| jQuery(async function () { |
| async function addExtensionControls() { |
| const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings }); |
| $('#summarize_container').append(settingsHtml); |
| setupListeners(); |
| $('#summaryExtensionPopoutButton').off('click').on('click', function (e) { |
| doPopout(e); |
| e.stopPropagation(); |
| }); |
| } |
|
|
| await addExtensionControls(); |
| loadSettings(); |
| eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onChatEvent); |
| for (const event of [event_types.MESSAGE_DELETED, event_types.MESSAGE_UPDATED, event_types.MESSAGE_SWIPED]) { |
| eventSource.on(event, onChatEvent); |
| } |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| name: 'summarize', |
| callback: summarizeCallback, |
| namedArgumentList: [ |
| new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', Object.values(summary_sources)), |
| SlashCommandNamedArgument.fromProps({ |
| name: 'prompt', |
| description: 'prompt to use for summarization', |
| typeList: [ARGUMENT_TYPE.STRING], |
| defaultValue: '', |
| }), |
| SlashCommandNamedArgument.fromProps({ |
| name: 'quiet', |
| description: 'suppress the toast message when summarizing the chat', |
| typeList: [ARGUMENT_TYPE.BOOLEAN], |
| defaultValue: 'false', |
| enumList: commonEnumProviders.boolean('trueFalse')(), |
| }), |
| ], |
| unnamedArgumentList: [ |
| new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''), |
| ], |
| helpString: 'Summarizes the given text. If no text is provided, the current chat will be summarized. Can specify the source and the prompt to use.', |
| returns: ARGUMENT_TYPE.STRING, |
| })); |
|
|
| if (power_user.experimental_macro_engine) { |
| macros.register('summary', { |
| category: MacroCategory.CHAT, |
| description: 'Returns the latest memory/summary from the current chat.', |
| handler: () => getLatestMemoryFromChat(getContext().chat), |
| }); |
| } else { |
| |
| MacrosParser.registerMacro('summary', |
| () => getLatestMemoryFromChat(getContext().chat), |
| 'Returns the latest memory/summary from the current chat.'); |
| } |
| }); |
|
|