2024-04-28 18:47:53 +02:00
import { getStringHash , debounce , waitUntilCondition , extractAllWords } from '../../utils.js' ;
2024-04-11 22:38:44 +02:00
import { getContext , getApiUrl , extension _settings , doExtrasFetch , modules , renderExtensionTemplateAsync } from '../../extensions.js' ;
2024-03-31 20:22:27 +02:00
import {
activateSendButtons ,
deactivateSendButtons ,
animation _duration ,
eventSource ,
event _types ,
extension _prompt _roles ,
extension _prompt _types ,
generateQuietPrompt ,
is _send _press ,
saveSettingsDebounced ,
2024-06-15 00:40:16 +02:00
substituteParamsExtended ,
2024-03-31 20:22:27 +02:00
generateRaw ,
getMaxContextSize ,
} from '../../../script.js' ;
2023-12-02 19:04:51 +01:00
import { is _group _generating , selected _group } from '../../group-chats.js' ;
2023-10-09 21:49:35 +02:00
import { loadMovingUIState } from '../../power-user.js' ;
2023-12-02 19:04:51 +01:00
import { dragElement } from '../../RossAscends-mods.js' ;
2024-04-13 20:33:19 +02:00
import { getTextTokens , getTokenCountAsync , tokenizers } from '../../tokenizers.js' ;
2024-04-28 18:47:53 +02:00
import { debounce _timeout } from '../../constants.js' ;
2024-05-12 21:15:05 +02:00
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 { resolveVariable } from '../../variables.js' ;
2024-06-17 07:04:10 +02:00
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js' ;
2023-07-20 19:32:15 +02:00
export { MODULE _NAME } ;
const MODULE _NAME = '1_memory' ;
let lastCharacterId = null ;
let lastGroupId = null ;
let lastChatId = null ;
let lastMessageHash = null ;
let lastMessageId = null ;
let inApiCall = false ;
2023-07-30 22:10:37 +02:00
const formatMemoryValue = function ( value ) {
if ( ! value ) {
return '' ;
}
value = value . trim ( ) ;
if ( extension _settings . memory . template ) {
2024-06-15 00:40:16 +02:00
return substituteParamsExtended ( extension _settings . memory . template , { summary : value } ) ;
2023-07-30 22:10:37 +02:00
} else {
return ` Summary: ${ value } ` ;
}
2023-12-02 20:11:06 +01:00
} ;
2023-07-30 22:10:37 +02:00
2024-04-28 06:21:47 +02:00
const saveChatDebounced = debounce ( ( ) => getContext ( ) . saveChat ( ) , debounce _timeout . relaxed ) ;
2023-07-20 19:32:15 +02:00
const summary _sources = {
'extras' : 'extras' ,
'main' : 'main' ,
} ;
2024-03-31 20:22:27 +02:00
const prompt _builders = {
DEFAULT : 0 ,
RAW _BLOCKING : 1 ,
RAW _NON _BLOCKING : 2 ,
} ;
const defaultPrompt = '[Pause your roleplay. 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.]' ;
2023-07-30 22:10:37 +02:00
const defaultTemplate = '[Summary: {{summary}}]' ;
2023-07-20 19:32:15 +02:00
const defaultSettings = {
memoryFrozen : false ,
2023-10-11 12:44:22 +02:00
SkipWIAN : false ,
2023-07-20 19:32:15 +02:00
source : summary _sources . extras ,
prompt : defaultPrompt ,
2023-07-30 22:10:37 +02:00
template : defaultTemplate ,
2023-09-08 00:26:26 +02:00
position : extension _prompt _types . IN _PROMPT ,
2024-03-23 18:18:43 +01:00
role : extension _prompt _roles . SYSTEM ,
2023-07-30 22:10:37 +02:00
depth : 2 ,
2023-07-20 19:32:15 +02:00
promptWords : 200 ,
promptMinWords : 25 ,
promptMaxWords : 1000 ,
promptWordsStep : 25 ,
promptInterval : 10 ,
2023-08-21 22:56:22 +02:00
promptMinInterval : 0 ,
2024-03-31 20:22:27 +02:00
promptMaxInterval : 250 ,
2023-07-20 19:32:15 +02:00
promptIntervalStep : 1 ,
2023-07-30 22:10:37 +02:00
promptForceWords : 0 ,
promptForceWordsStep : 100 ,
promptMinForceWords : 0 ,
promptMaxForceWords : 10000 ,
2024-03-31 20:22:27 +02:00
overrideResponseLength : 0 ,
overrideResponseLengthMin : 0 ,
overrideResponseLengthMax : 4096 ,
overrideResponseLengthStep : 16 ,
maxMessagesPerRequest : 0 ,
maxMessagesPerRequestMin : 0 ,
maxMessagesPerRequestMax : 250 ,
maxMessagesPerRequestStep : 1 ,
2024-04-03 01:13:09 +02:00
prompt _builder : prompt _builders . DEFAULT ,
2023-07-20 19:32:15 +02:00
} ;
function loadSettings ( ) {
if ( Object . keys ( extension _settings . memory ) . length === 0 ) {
Object . assign ( extension _settings . memory , defaultSettings ) ;
}
2023-07-30 22:10:37 +02:00
for ( const key of Object . keys ( defaultSettings ) ) {
if ( extension _settings . memory [ key ] === undefined ) {
extension _settings . memory [ key ] = defaultSettings [ key ] ;
}
2023-07-20 19:32:15 +02:00
}
$ ( '#summary_source' ) . val ( extension _settings . memory . source ) . trigger ( 'change' ) ;
$ ( '#memory_frozen' ) . prop ( 'checked' , extension _settings . memory . memoryFrozen ) . trigger ( 'input' ) ;
2023-10-11 12:44:22 +02:00
$ ( '#memory_skipWIAN' ) . prop ( 'checked' , extension _settings . memory . SkipWIAN ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
$ ( '#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' ) ;
2023-07-30 22:10:37 +02:00
$ ( '#memory_template' ) . val ( extension _settings . memory . template ) . trigger ( 'input' ) ;
$ ( '#memory_depth' ) . val ( extension _settings . memory . depth ) . trigger ( 'input' ) ;
2024-03-23 18:18:43 +01:00
$ ( '#memory_role' ) . val ( extension _settings . memory . role ) . trigger ( 'input' ) ;
2023-07-30 22:10:37 +02:00
$ ( ` 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' ) ;
2024-03-31 20:22:27 +02:00
$ ( ` 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' ) ;
2023-12-15 20:35:25 +01:00
switchSourceControls ( extension _settings . memory . source ) ;
2023-07-20 19:32:15 +02:00
}
2024-04-01 01:11:52 +02:00
async function onPromptForceWordsAutoClick ( ) {
const context = getContext ( ) ;
const maxPromptLength = getMaxContextSize ( extension _settings . memory . overrideResponseLength ) ;
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 ;
2024-04-13 20:33:19 +02:00
const tokensPerWord = await getTokenCountAsync ( allMessages . join ( '\n' ) ) / messagesWordCount ;
2024-04-01 01:11:52 +02:00
const wordsPerToken = 1 / tokensPerWord ;
const maxPromptLengthWords = Math . round ( maxPromptLength * wordsPerToken ) ;
// How many words should pass so that messages will start be dropped out of context;
const wordsPerPrompt = Math . floor ( maxPromptLength / tokensPerWord ) ;
// How many words will be needed to fit the allowance buffer
const summaryPromptWords = extractAllWords ( extension _settings . memory . prompt ) . length ;
const promptAllowanceWords = maxPromptLengthWords - extension _settings . memory . promptWords - summaryPromptWords ;
2024-04-01 17:57:42 +02:00
const averageMessagesPerPrompt = Math . floor ( promptAllowanceWords / averageMessageWordCount ) ;
2024-04-01 01:11:52 +02:00
const maxMessagesPerSummary = extension _settings . memory . maxMessagesPerRequest || 0 ;
2024-04-01 17:57:42 +02:00
const targetMessagesInPrompt = maxMessagesPerSummary > 0 ? maxMessagesPerSummary : Math . max ( 0 , averageMessagesPerPrompt ) ;
const targetSummaryWords = ( targetMessagesInPrompt * averageMessageWordCount ) + ( promptAllowanceWords / 4 ) ;
2024-04-01 01:11:52 +02:00
console . table ( {
maxPromptLength ,
maxPromptLengthWords ,
promptAllowanceWords ,
2024-04-01 17:57:42 +02:00
averageMessagesPerPrompt ,
targetMessagesInPrompt ,
2024-04-01 01:11:52 +02:00
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' ) ;
}
2024-03-31 20:22:27 +02:00
async function onPromptIntervalAutoClick ( ) {
const context = getContext ( ) ;
const maxPromptLength = getMaxContextSize ( extension _settings . memory . overrideResponseLength ) ;
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 ;
2024-04-13 20:33:19 +02:00
const messagesTokenCount = await getTokenCountAsync ( allMessages . join ( '\n' ) ) ;
2024-03-31 20:22:27 +02:00
const tokensPerWord = messagesTokenCount / messagesWordCount ;
const averageMessageTokenCount = messagesTokenCount / allMessages . length ;
const targetSummaryTokens = Math . round ( extension _settings . memory . promptWords * tokensPerWord ) ;
2024-04-13 20:33:19 +02:00
const promptTokens = await getTokenCountAsync ( extension _settings . memory . prompt ) ;
2024-03-31 20:22:27 +02:00
const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens ;
const maxMessagesPerSummary = extension _settings . memory . maxMessagesPerRequest || 0 ;
const averageMessagesPerPrompt = Math . floor ( promptAllowance / averageMessageTokenCount ) ;
2024-04-01 17:57:42 +02:00
const targetMessagesInPrompt = maxMessagesPerSummary > 0 ? maxMessagesPerSummary : Math . max ( 0 , averageMessagesPerPrompt ) ;
const adjustedAverageMessagesPerPrompt = targetMessagesInPrompt + ( averageMessagesPerPrompt - targetMessagesInPrompt ) / 4 ;
2024-03-31 20:22:27 +02:00
console . table ( {
maxPromptLength ,
promptAllowance ,
targetSummaryTokens ,
promptTokens ,
messagesWordCount ,
messagesTokenCount ,
tokensPerWord ,
averageMessageTokenCount ,
averageMessagesPerPrompt ,
2024-04-01 17:57:42 +02:00
targetMessagesInPrompt ,
2024-03-31 20:22:27 +02:00
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' ) ;
}
2023-07-20 19:32:15 +02:00
function onSummarySourceChange ( event ) {
const value = event . target . value ;
extension _settings . memory . source = value ;
2023-12-15 20:35:25 +01:00
switchSourceControls ( value ) ;
saveSettingsDebounced ( ) ;
}
function switchSourceControls ( value ) {
2024-03-31 13:27:43 +02:00
$ ( '#memory_settings [data-summary-source]' ) . each ( ( _ , element ) => {
const source = $ ( element ) . data ( 'summary-source' ) ;
2023-07-20 19:32:15 +02:00
$ ( element ) . toggle ( source === value ) ;
} ) ;
}
function onMemoryFrozenInput ( ) {
const value = Boolean ( $ ( this ) . prop ( 'checked' ) ) ;
extension _settings . memory . memoryFrozen = value ;
saveSettingsDebounced ( ) ;
}
2023-10-11 12:44:22 +02:00
function onMemorySkipWIANInput ( ) {
const value = Boolean ( $ ( this ) . prop ( 'checked' ) ) ;
extension _settings . memory . SkipWIAN = value ;
saveSettingsDebounced ( ) ;
}
2023-07-20 19:32:15 +02:00
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 ( ) ;
}
2024-03-31 20:22:27 +02:00
function onMemoryPromptRestoreClick ( ) {
$ ( '#memory_prompt' ) . val ( defaultPrompt ) . trigger ( 'input' ) ;
}
2023-07-20 19:32:15 +02:00
function onMemoryPromptInput ( ) {
const value = $ ( this ) . val ( ) ;
extension _settings . memory . prompt = value ;
saveSettingsDebounced ( ) ;
}
2023-07-30 22:10:37 +02:00
function onMemoryTemplateInput ( ) {
const value = $ ( this ) . val ( ) ;
extension _settings . memory . template = value ;
2023-09-21 20:13:24 +02:00
reinsertMemory ( ) ;
2023-07-30 22:10:37 +02:00
saveSettingsDebounced ( ) ;
}
function onMemoryDepthInput ( ) {
const value = $ ( this ) . val ( ) ;
extension _settings . memory . depth = Number ( value ) ;
2023-09-21 20:13:24 +02:00
reinsertMemory ( ) ;
2023-07-30 22:10:37 +02:00
saveSettingsDebounced ( ) ;
}
2024-03-23 18:18:43 +01:00
function onMemoryRoleInput ( ) {
const value = $ ( this ) . val ( ) ;
extension _settings . memory . role = Number ( value ) ;
reinsertMemory ( ) ;
saveSettingsDebounced ( ) ;
}
2023-07-30 22:10:37 +02:00
function onMemoryPositionChange ( e ) {
const value = e . target . value ;
extension _settings . memory . position = value ;
2023-09-21 20:13:24 +02:00
reinsertMemory ( ) ;
2023-07-30 22:10:37 +02:00
saveSettingsDebounced ( ) ;
}
function onMemoryPromptWordsForceInput ( ) {
const value = $ ( this ) . val ( ) ;
extension _settings . memory . promptForceWords = Number ( value ) ;
$ ( '#memory_prompt_words_force_value' ) . text ( extension _settings . memory . promptForceWords ) ;
saveSettingsDebounced ( ) ;
}
2024-03-31 20:22:27 +02:00
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 ( ) ;
}
2023-07-20 19:32:15 +02:00
function saveLastValues ( ) {
const context = getContext ( ) ;
lastGroupId = context . groupId ;
lastCharacterId = context . characterId ;
lastChatId = context . chatId ;
lastMessageId = context . chat ? . length ? ? null ;
lastMessageHash = getStringHash ( ( context . chat . length && context . chat [ context . chat . length - 1 ] [ 'mes' ] ) ? ? '' ) ;
}
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 '' ;
}
2024-03-31 20:22:27 +02:00
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 ;
}
2023-07-20 19:32:15 +02:00
async function onChatEvent ( ) {
// Module not enabled
if ( extension _settings . memory . source === summary _sources . extras ) {
if ( ! modules . includes ( 'summarize' ) ) {
return ;
}
}
const context = getContext ( ) ;
const chat = context . chat ;
// no characters or group selected
if ( ! context . groupId && context . characterId === undefined ) {
return ;
}
// Generation is in progress, summary prevented
if ( is _send _press ) {
return ;
}
// Chat/character/group changed
if ( ( context . groupId && lastGroupId !== context . groupId ) || ( context . characterId !== lastCharacterId ) || ( context . chatId !== lastChatId ) ) {
const latestMemory = getLatestMemoryFromChat ( chat ) ;
setMemoryContext ( latestMemory , false ) ;
saveLastValues ( ) ;
return ;
}
// Currently summarizing or frozen state - skip
if ( inApiCall || extension _settings . memory . memoryFrozen ) {
return ;
}
// No new messages - do nothing
if ( chat . length === 0 || ( lastMessageId === chat . length && getStringHash ( chat [ chat . length - 1 ] . mes ) === lastMessageHash ) ) {
return ;
}
// Messages has been deleted - rewrite the context with the latest available memory
if ( chat . length < lastMessageId ) {
const latestMemory = getLatestMemoryFromChat ( chat ) ;
setMemoryContext ( latestMemory , false ) ;
}
// Message has been edited / regenerated - delete the saved memory
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 ;
}
try {
await summarizeChat ( context ) ;
}
catch ( error ) {
console . log ( error ) ;
}
finally {
saveLastValues ( ) ;
}
}
async function forceSummarizeChat ( ) {
2023-12-16 21:33:09 +01:00
if ( extension _settings . memory . source === summary _sources . extras ) {
toastr . warning ( 'Force summarization is not supported for Extras API' ) ;
return ;
}
2023-07-20 19:32:15 +02:00
const context = getContext ( ) ;
2023-12-02 20:11:06 +01:00
const skipWIAN = extension _settings . memory . SkipWIAN ;
console . log ( ` Skipping WIAN? ${ skipWIAN } ` ) ;
2023-07-20 19:32:15 +02:00
if ( ! context . chatId ) {
toastr . warning ( 'No chat selected' ) ;
2024-05-12 21:15:05 +02:00
return '' ;
2023-07-20 19:32:15 +02:00
}
toastr . info ( 'Summarizing chat...' , 'Please wait' ) ;
2023-10-11 12:44:22 +02:00
const value = await summarizeChatMain ( context , true , skipWIAN ) ;
2023-07-20 19:32:15 +02:00
if ( ! value ) {
toastr . warning ( 'Failed to summarize chat' ) ;
2024-05-12 21:15:05 +02:00
return '' ;
}
return value ;
}
/ * *
* Callback for the summarize command .
* @ param { object } args Command arguments
* @ param { string } text Text to summarize
* /
async function summarizeCallback ( args , text ) {
text = text . trim ( ) ;
// Using forceSummarizeChat to summarize the current chat
if ( ! text ) {
return await forceSummarizeChat ( ) ;
}
const source = args . source || extension _settings . memory . source ;
2024-06-28 03:55:03 +02:00
const prompt = substituteParamsExtended ( ( args . prompt || extension _settings . memory . prompt ) , { words : extension _settings . memory . promptWords } ) ;
2024-05-12 21:15:05 +02:00
try {
switch ( source ) {
case summary _sources . extras :
return await callExtrasSummarizeAPI ( text ) ;
case summary _sources . main :
return await generateRaw ( text , '' , false , false , prompt , extension _settings . memory . overrideResponseLength ) ;
default :
toastr . warning ( 'Invalid summarization source specified' ) ;
return '' ;
}
} catch ( error ) {
toastr . error ( String ( error ) , 'Failed to summarize text' ) ;
console . log ( error ) ;
return '' ;
2023-07-20 19:32:15 +02:00
}
}
async function summarizeChat ( context ) {
2023-12-02 20:11:06 +01:00
const skipWIAN = extension _settings . memory . SkipWIAN ;
2023-07-20 19:32:15 +02:00
switch ( extension _settings . memory . source ) {
case summary _sources . extras :
await summarizeChatExtras ( context ) ;
break ;
case summary _sources . main :
2023-10-11 12:44:22 +02:00
await summarizeChatMain ( context , false , skipWIAN ) ;
2023-07-20 19:32:15 +02:00
break ;
default :
break ;
}
}
2023-10-11 12:44:22 +02:00
async function summarizeChatMain ( context , force , skipWIAN ) {
2023-08-21 22:56:22 +02:00
if ( extension _settings . memory . promptInterval === 0 && ! force ) {
console . debug ( 'Prompt interval is set to 0, skipping summarization' ) ;
return ;
}
2023-07-20 19:32:15 +02:00
try {
2023-08-03 16:17:58 +02:00
// Wait for group to finish generating
if ( selected _group ) {
await waitUntilCondition ( ( ) => is _group _generating === false , 1000 , 10 ) ;
}
2023-07-20 19:32:15 +02:00
// Wait for the send button to be released
2023-08-01 14:53:10 +02:00
waitUntilCondition ( ( ) => is _send _press === false , 30000 , 100 ) ;
2023-07-20 19:32:15 +02:00
} 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 ;
2023-07-30 22:10:37 +02:00
let wordsSinceLastSummary = 0 ;
let conditionSatisfied = false ;
2023-07-20 19:32:15 +02:00
for ( let i = context . chat . length - 1 ; i >= 0 ; i -- ) {
if ( context . chat [ i ] . extra && context . chat [ i ] . extra . memory ) {
break ;
}
messagesSinceLastSummary ++ ;
2023-07-30 22:10:37 +02:00
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 ;
2023-07-20 19:32:15 +02:00
}
2023-07-30 22:10:37 +02:00
if ( ! conditionSatisfied && ! force ) {
console . debug ( ` Summary conditions not satisfied (messages: ${ messagesSinceLastSummary } , interval: ${ extension _settings . memory . promptInterval } , words: ${ wordsSinceLastSummary } , force words: ${ extension _settings . memory . promptForceWords } ) ` ) ;
2023-07-20 19:32:15 +02:00
return ;
}
2023-07-30 22:10:37 +02:00
console . log ( 'Summarizing chat, messages since last summary: ' + messagesSinceLastSummary , 'words since last summary: ' + wordsSinceLastSummary ) ;
2024-06-15 00:40:16 +02:00
const prompt = substituteParamsExtended ( extension _settings . memory . prompt , { words : extension _settings . memory . promptWords } ) ;
2023-07-20 19:32:15 +02:00
if ( ! prompt ) {
console . debug ( 'Summarization prompt is empty. Skipping summarization.' ) ;
return ;
}
2024-03-31 20:22:27 +02:00
2023-12-02 20:11:06 +01:00
console . log ( 'sending summary prompt' ) ;
2024-03-31 20:22:27 +02:00
let summary = '' ;
let index = null ;
if ( prompt _builders . DEFAULT === extension _settings . memory . prompt _builder ) {
summary = await generateQuietPrompt ( prompt , false , skipWIAN , '' , '' , extension _settings . memory . overrideResponseLength ) ;
}
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 {
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 ;
}
summary = await generateRaw ( rawPrompt , '' , false , false , prompt , extension _settings . memory . overrideResponseLength ) ;
index = lastUsedIndex ;
} finally {
if ( lock ) {
activateSendButtons ( ) ;
}
}
}
2023-07-20 19:32:15 +02:00
const newContext = getContext ( ) ;
// something changed during summarization request
if ( newContext . groupId !== context . groupId
|| newContext . chatId !== context . chatId
|| ( ! newContext . groupId && ( newContext . characterId !== context . characterId ) ) ) {
console . log ( 'Context changed, summary discarded' ) ;
return ;
}
2024-03-31 20:22:27 +02:00
setMemoryContext ( summary , true , index ) ;
2023-07-20 19:32:15 +02:00
return summary ;
}
2024-03-31 20:22:27 +02:00
/ * *
* Get the raw summarization prompt from the chat context .
* @ param { object } context ST context
* @ param { string } prompt Summarization system prompt
* @ returns { Promise < { rawPrompt : string , lastUsedIndex : number } > } Raw summarization prompt
* /
async function getRawSummaryPrompt ( context , prompt ) {
/ * *
* Get the memory string from the chat buffer .
* @ param { boolean } includeSystem Include prompt into the memory string
* @ returns { string } Memory string
* /
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 ( ) ; // We always exclude the last message from the buffer
const chatBuffer = [ ] ;
const PADDING = 64 ;
const PROMPT _SIZE = getMaxContextSize ( extension _settings . memory . overrideResponseLength ) ;
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 ) ;
2024-04-13 20:33:19 +02:00
const tokens = await getTokenCountAsync ( getMemoryString ( true ) , PADDING ) ;
2024-03-31 20:22:27 +02:00
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 } ;
}
2023-07-20 19:32:15 +02:00
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 ( ) ;
2023-12-20 00:56:35 +01:00
const memoryBuffer = [ ] ;
const CONTEXT _SIZE = 1024 - 64 ;
2023-07-20 19:32:15 +02:00
2023-12-20 00:56:35 +01:00
for ( const message of reversedChat ) {
2023-07-20 19:32:15 +02:00
// we reached the point of latest memory
2023-12-20 00:56:35 +01:00
if ( longMemory && message . extra && message . extra . memory == longMemory ) {
2023-07-20 19:32:15 +02:00
break ;
}
// don't care about system
2023-12-20 00:56:35 +01:00
if ( message . is _system ) {
2023-07-20 19:32:15 +02:00
continue ;
}
// determine the sender's name
2023-12-20 00:56:35 +01:00
const entry = ` ${ message . name } : \n ${ message . mes } ` ;
2023-07-20 19:32:15 +02:00
memoryBuffer . push ( entry ) ;
// check if token limit was reached
2023-12-20 00:56:35 +01:00
const tokens = getTextTokens ( tokenizers . GPT2 , getMemoryString ( ) ) . length ;
if ( tokens >= CONTEXT _SIZE ) {
2023-07-20 19:32:15 +02:00
break ;
}
}
const resultingString = getMemoryString ( ) ;
2023-12-20 00:56:35 +01:00
const resultingTokens = getTextTokens ( tokenizers . GPT2 , resultingString ) . length ;
2023-07-20 19:32:15 +02:00
2023-12-20 00:56:35 +01:00
if ( ! resultingString || resultingTokens < CONTEXT _SIZE ) {
console . debug ( 'Not enough context to summarize' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
// perform the summarization API call
try {
inApiCall = true ;
2024-05-12 21:15:05 +02:00
const summary = await callExtrasSummarizeAPI ( resultingString ) ;
const newContext = getContext ( ) ;
// something changed during summarization request
if ( newContext . groupId !== context . groupId
|| newContext . chatId !== context . chatId
|| ( ! newContext . groupId && ( newContext . characterId !== context . characterId ) ) ) {
console . log ( 'Context changed, summary discarded' ) ;
return ;
2023-07-20 19:32:15 +02:00
}
2024-05-12 21:15:05 +02:00
setMemoryContext ( summary , true ) ;
2023-07-20 19:32:15 +02:00
}
catch ( error ) {
console . log ( error ) ;
}
finally {
inApiCall = false ;
}
}
2024-05-12 21:15:05 +02:00
/ * *
* Call the Extras API to summarize the provided text .
* @ param { string } text Text to summarize
* @ returns { Promise < string > } Summarized text
* /
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' ) ;
}
2023-07-20 19:32:15 +02:00
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 ) ;
}
2024-03-31 20:22:27 +02:00
function onMemoryPromptBuilderInput ( e ) {
const value = Number ( e . target . value ) ;
extension _settings . memory . prompt _builder = value ;
saveSettingsDebounced ( ) ;
}
2023-09-21 20:13:24 +02:00
function reinsertMemory ( ) {
2024-03-31 20:22:27 +02:00
const existingValue = String ( $ ( '#memory_contents' ) . val ( ) ) ;
2023-09-21 20:13:24 +02:00
setMemoryContext ( existingValue , false ) ;
}
2024-03-31 20:22:27 +02:00
/ * *
* Set the summary value to the context and save it to the chat message extra .
* @ param { string } value Value of a summary
* @ param { boolean } saveToMessage Should the summary be saved to the chat message extra
* @ param { number | null } index Index of the chat message to save the summary to . If null , the pre - last message is used .
* /
function setMemoryContext ( value , saveToMessage , index = null ) {
2023-07-20 19:32:15 +02:00
const context = getContext ( ) ;
2024-03-23 18:18:43 +01:00
context . setExtensionPrompt ( MODULE _NAME , formatMemoryValue ( value ) , extension _settings . memory . position , extension _settings . memory . depth , false , extension _settings . memory . role ) ;
2023-07-20 19:32:15 +02:00
$ ( '#memory_contents' ) . val ( value ) ;
2024-05-13 22:17:28 +02:00
console . log ( 'Summary set to: ' + value , 'Position: ' + extension _settings . memory . position , 'Depth: ' + extension _settings . memory . depth , 'Role: ' + extension _settings . memory . role ) ;
2023-07-20 19:32:15 +02:00
if ( saveToMessage && context . chat . length ) {
2024-03-31 20:22:27 +02:00
const idx = index ? ? context . chat . length - 2 ;
2023-07-20 19:32:15 +02:00
const mes = context . chat [ idx < 0 ? 0 : idx ] ;
if ( ! mes . extra ) {
mes . extra = { } ;
}
mes . extra . memory = value ;
saveChatDebounced ( ) ;
}
}
2023-10-09 21:49:35 +02:00
function doPopout ( e ) {
const target = e . target ;
//repurposes the zoomed avatar template to server as a floating div
2023-12-02 19:04:51 +01:00
if ( $ ( '#summaryExtensionPopout' ) . length === 0 ) {
2023-12-02 20:11:06 +01:00
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' ) ;
2023-10-09 21:49:35 +02:00
const template = $ ( '#zoomed_avatar_template' ) . html ( ) ;
const controlBarHtml = ` <div class="panelControlBar flex-container">
< div id = "summaryExtensionPopoutheader" class = "fa-solid fa-grip drag-grabber hoverglow" > < / d i v >
2023-10-13 17:29:41 +02:00
< div id = "summaryExtensionPopoutClose" class = "fa-solid fa-circle-xmark hoverglow dragClose" > < / d i v >
2023-12-02 20:11:06 +01:00
< / d i v > ` ;
2023-10-09 21:49:35 +02:00
const newElement = $ ( template ) ;
2023-10-13 17:29:41 +02:00
newElement . attr ( 'id' , 'summaryExtensionPopout' )
. removeClass ( 'zoomed_avatar' )
. addClass ( 'draggable' )
2023-12-02 20:11:06 +01:00
. empty ( ) ;
2023-10-09 21:49:35 +02:00
const prevSummaryBoxContents = $ ( '#memory_contents' ) . val ( ) ; //copy summary box before emptying
originalElement . empty ( ) ;
2023-12-02 20:11:06 +01:00
originalElement . html ( '<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>' ) ;
newElement . append ( controlBarHtml ) . append ( originalHTMLClone ) ;
2023-10-09 21:49:35 +02:00
$ ( 'body' ) . append ( newElement ) ;
2023-12-02 20:11:06 +01:00
$ ( '#summaryExtensionDrawerContents' ) . addClass ( 'scrollableInnerFull' ) ;
2023-10-09 21:49:35 +02:00
setMemoryContext ( prevSummaryBoxContents , false ) ; //paste prev summary box contents into popout box
setupListeners ( ) ;
loadSettings ( ) ;
loadMovingUIState ( ) ;
2023-12-11 15:23:21 +01:00
$ ( '#summaryExtensionPopout' ) . fadeIn ( animation _duration ) ;
2023-10-09 21:49:35 +02:00
dragElement ( newElement ) ;
//setup listener for close button to restore extensions menu
$ ( '#summaryExtensionPopoutClose' ) . off ( 'click' ) . on ( 'click' , function ( ) {
2023-12-02 20:11:06 +01:00
$ ( '#summaryExtensionDrawerContents' ) . removeClass ( 'scrollableInnerFull' ) ;
const summaryPopoutHTML = $ ( '#summaryExtensionDrawerContents' ) ;
2023-12-11 15:23:21 +01:00
$ ( '#summaryExtensionPopout' ) . fadeOut ( animation _duration , ( ) => {
2023-10-09 21:49:35 +02:00
originalElement . empty ( ) ;
originalElement . html ( summaryPopoutHTML ) ;
2023-12-02 20:11:06 +01:00
$ ( '#summaryExtensionPopout' ) . remove ( ) ;
} ) ;
2023-10-09 21:49:35 +02:00
loadSettings ( ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-10-09 21:49:35 +02:00
} else {
2023-12-02 20:11:06 +01:00
console . debug ( 'saw existing popout, removing' ) ;
2023-12-11 15:23:21 +01:00
$ ( '#summaryExtensionPopout' ) . fadeOut ( animation _duration , ( ) => { $ ( '#summaryExtensionPopoutClose' ) . trigger ( 'click' ) ; } ) ;
2023-10-09 21:49:35 +02:00
}
}
function setupListeners ( ) {
//setup shared listeners for popout and regular ext menu
$ ( '#memory_restore' ) . off ( 'click' ) . on ( 'click' , onMemoryRestoreClick ) ;
$ ( '#memory_contents' ) . off ( 'click' ) . on ( 'input' , onMemoryContentInput ) ;
$ ( '#memory_frozen' ) . off ( 'click' ) . on ( 'input' , onMemoryFrozenInput ) ;
2023-10-11 12:44:22 +02:00
$ ( '#memory_skipWIAN' ) . off ( 'click' ) . on ( 'input' , onMemorySkipWIANInput ) ;
2023-10-09 21:49:35 +02:00
$ ( '#summary_source' ) . off ( 'click' ) . on ( 'change' , onSummarySourceChange ) ;
$ ( '#memory_prompt_words' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptWordsInput ) ;
$ ( '#memory_prompt_interval' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptIntervalInput ) ;
$ ( '#memory_prompt' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptInput ) ;
$ ( '#memory_force_summarize' ) . off ( 'click' ) . on ( 'click' , forceSummarizeChat ) ;
$ ( '#memory_template' ) . off ( 'click' ) . on ( 'input' , onMemoryTemplateInput ) ;
$ ( '#memory_depth' ) . off ( 'click' ) . on ( 'input' , onMemoryDepthInput ) ;
2024-03-23 18:18:43 +01:00
$ ( '#memory_role' ) . off ( 'click' ) . on ( 'input' , onMemoryRoleInput ) ;
2023-10-09 21:49:35 +02:00
$ ( 'input[name="memory_position"]' ) . off ( 'click' ) . on ( 'change' , onMemoryPositionChange ) ;
$ ( '#memory_prompt_words_force' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptWordsForceInput ) ;
2024-03-31 20:22:27 +02:00
$ ( '#memory_prompt_builder_default' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptBuilderInput ) ;
$ ( '#memory_prompt_builder_raw_blocking' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptBuilderInput ) ;
$ ( '#memory_prompt_builder_raw_non_blocking' ) . off ( 'click' ) . on ( 'input' , onMemoryPromptBuilderInput ) ;
$ ( '#memory_prompt_restore' ) . off ( 'click' ) . on ( 'click' , onMemoryPromptRestoreClick ) ;
$ ( '#memory_prompt_interval_auto' ) . off ( 'click' ) . on ( 'click' , onPromptIntervalAutoClick ) ;
2024-04-01 01:11:52 +02:00
$ ( '#memory_prompt_words_auto' ) . off ( 'click' ) . on ( 'click' , onPromptForceWordsAutoClick ) ;
2024-03-31 20:22:27 +02:00
$ ( '#memory_override_response_length' ) . off ( 'click' ) . on ( 'input' , onOverrideResponseLengthInput ) ;
$ ( '#memory_max_messages_per_request' ) . off ( 'click' ) . on ( 'input' , onMaxMessagesPerRequestInput ) ;
2023-12-02 19:04:51 +01:00
$ ( '#summarySettingsBlockToggle' ) . off ( 'click' ) . on ( 'click' , function ( ) {
2023-12-02 20:11:06 +01:00
console . log ( 'saw settings button click' ) ;
2023-12-02 19:04:51 +01:00
$ ( '#summarySettingsBlock' ) . slideToggle ( 200 , 'swing' ) ; //toggleClass("hidden");
2023-10-09 21:49:35 +02:00
} ) ;
}
2024-04-11 22:38:44 +02:00
jQuery ( async function ( ) {
async function addExtensionControls ( ) {
const settingsHtml = await renderExtensionTemplateAsync ( 'memory' , 'settings' , { defaultSettings } ) ;
2024-06-24 21:15:08 +02:00
$ ( '#summarize_container' ) . append ( settingsHtml ) ;
2023-10-09 21:49:35 +02:00
setupListeners ( ) ;
2023-12-02 19:04:51 +01:00
$ ( '#summaryExtensionPopoutButton' ) . off ( 'click' ) . on ( 'click' , function ( e ) {
2023-10-09 21:49:35 +02:00
doPopout ( e ) ;
e . stopPropagation ( ) ;
} ) ;
2023-07-20 19:32:15 +02:00
}
2024-04-11 22:38:44 +02:00
await addExtensionControls ( ) ;
2023-07-20 19:32:15 +02:00
loadSettings ( ) ;
eventSource . on ( event _types . MESSAGE _RECEIVED , onChatEvent ) ;
eventSource . on ( event _types . MESSAGE _DELETED , onChatEvent ) ;
eventSource . on ( event _types . MESSAGE _EDITED , onChatEvent ) ;
eventSource . on ( event _types . MESSAGE _SWIPED , onChatEvent ) ;
eventSource . on ( event _types . CHAT _CHANGED , onChatEvent ) ;
2024-05-12 21:15:05 +02:00
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( {
name : 'summarize' ,
callback : summarizeCallback ,
namedArgumentList : [
new SlashCommandNamedArgument ( 'source' , 'API to use for summarization' , [ ARGUMENT _TYPE . STRING ] , false , false , '' , [ 'main' , 'extras' ] ) ,
2024-06-17 07:04:10 +02:00
SlashCommandNamedArgument . fromProps ( {
name : 'prompt' ,
description : 'prompt to use for summarization' ,
2024-06-28 03:55:03 +02:00
typeList : [ ARGUMENT _TYPE . STRING ] ,
2024-06-17 07:04:10 +02:00
defaultValue : '' ,
} ) ,
2024-05-12 21:15:05 +02:00
] ,
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.' ,
2024-05-30 00:48:27 +02:00
returns : ARGUMENT _TYPE . STRING ,
2024-05-12 21:15:05 +02:00
} ) ) ;
2023-07-20 19:32:15 +02:00
} ) ;