2023-07-20 19:32:15 +02:00
import { saveSettingsDebounced , getCurrentChatId , system _message _types , extension _prompt _types , eventSource , event _types , getRequestHeaders , CHARACTERS _PER _TOKEN _RATIO , substituteParams , max _context , } from "../../../script.js" ;
import { humanizedDateTime } from "../../RossAscends-mods.js" ;
import { getApiUrl , extension _settings , getContext , doExtrasFetch } from "../../extensions.js" ;
2023-08-14 21:19:14 +02:00
import { getFileText , onlyUnique , splitRecursive } from "../../utils.js" ;
2023-07-20 19:32:15 +02:00
export { MODULE _NAME } ;
const MODULE _NAME = 'chromadb' ;
2023-08-14 21:19:14 +02:00
const dbStore = localforage . createInstance ( { name : 'SillyTavern_ChromaDB' } ) ;
2023-07-20 19:32:15 +02:00
const defaultSettings = {
strategy : 'original' ,
sort _strategy : 'date' ,
keep _context : 10 ,
keep _context _min : 1 ,
keep _context _max : 500 ,
keep _context _step : 1 ,
n _results : 20 ,
n _results _min : 0 ,
n _results _max : 500 ,
n _results _step : 1 ,
chroma _depth : 20 ,
chroma _depth _min : - 1 ,
chroma _depth _max : 500 ,
chroma _depth _step : 1 ,
chroma _default _msg : "In a past conversation: [{{memories}}]" ,
chroma _default _hhaa _wrapper : "Previous messages exchanged between {{user}} and {{char}}:\n{{memories}}" ,
chroma _default _hhaa _memory : "- {{name}}: {{message}}\n" ,
hhaa _token _limit : 512 ,
split _length : 384 ,
split _length _min : 64 ,
split _length _max : 4096 ,
split _length _step : 64 ,
file _split _length : 1024 ,
file _split _length _min : 512 ,
file _split _length _max : 4096 ,
file _split _length _step : 128 ,
keep _context _proportion : 0.5 ,
keep _context _proportion _min : 0.0 ,
keep _context _proportion _max : 1.0 ,
keep _context _proportion _step : 0.05 ,
auto _adjust : true ,
freeze : false ,
query _last _only : true ,
} ;
const postHeaders = {
'Content-Type' : 'application/json' ,
'Bypass-Tunnel-Reminder' : 'bypass' ,
} ;
async function invalidateMessageSyncState ( messageId ) {
console . log ( 'CHROMADB: invalidating message sync state' , messageId ) ;
const state = await getChatSyncState ( ) ;
state [ messageId ] = 0 ;
2023-08-14 21:19:14 +02:00
await dbStore . setItem ( getCurrentChatId ( ) , state ) ;
2023-07-20 19:32:15 +02:00
}
async function getChatSyncState ( ) {
const currentChatId = getCurrentChatId ( ) ;
if ( ! checkChatId ( currentChatId ) ) {
return ;
}
const context = getContext ( ) ;
2023-08-14 21:19:14 +02:00
const chatState = ( await dbStore . getItem ( currentChatId ) ) || [ ] ;
2023-07-20 19:32:15 +02:00
// if the chat length has decreased, it means that some messages were deleted
if ( chatState . length > context . chat . length ) {
for ( let i = context . chat . length ; i < chatState . length ; i ++ ) {
// if the synced message was deleted, notify the user
if ( chatState [ i ] ) {
toastr . warning (
'Purge your ChromaDB to remove it from there too. See the "Smart Context" tab in the Extensions menu for more information.' ,
'Message deleted from chat, but it still exists inside the ChromaDB database.' ,
{ timeOut : 0 , extendedTimeOut : 0 , preventDuplicates : true } ,
) ;
break ;
}
}
}
chatState . length = context . chat . length ;
for ( let i = 0 ; i < chatState . length ; i ++ ) {
if ( chatState [ i ] === undefined ) {
chatState [ i ] = 0 ;
}
}
2023-08-14 21:19:14 +02:00
await dbStore . setItem ( currentChatId , chatState ) ;
2023-07-20 19:32:15 +02:00
return chatState ;
}
async function loadSettings ( ) {
if ( Object . keys ( extension _settings . chromadb ) . length === 0 ) {
Object . assign ( extension _settings . chromadb , defaultSettings ) ;
}
console . debug ( ` loading chromadb strat: ${ extension _settings . chromadb . strategy } ` ) ;
$ ( "#chromadb_strategy option[value=" + extension _settings . chromadb . strategy + "]" ) . attr (
"selected" ,
"true"
) ;
$ ( "#chromadb_sort_strategy option[value=" + extension _settings . chromadb . sort _strategy + "]" ) . attr (
"selected" ,
"true"
) ;
$ ( '#chromadb_keep_context' ) . val ( extension _settings . chromadb . keep _context ) . trigger ( 'input' ) ;
$ ( '#chromadb_n_results' ) . val ( extension _settings . chromadb . n _results ) . trigger ( 'input' ) ;
$ ( '#chromadb_split_length' ) . val ( extension _settings . chromadb . split _length ) . trigger ( 'input' ) ;
$ ( '#chromadb_file_split_length' ) . val ( extension _settings . chromadb . file _split _length ) . trigger ( 'input' ) ;
$ ( '#chromadb_keep_context_proportion' ) . val ( extension _settings . chromadb . keep _context _proportion ) . trigger ( 'input' ) ;
$ ( '#chromadb_custom_depth' ) . val ( extension _settings . chromadb . chroma _depth ) . trigger ( 'input' ) ;
$ ( '#chromadb_custom_msg' ) . val ( extension _settings . chromadb . recall _msg ) . trigger ( 'input' ) ;
$ ( '#chromadb_hhaa_wrapperfmt' ) . val ( extension _settings . chromadb . hhaa _wrapper _msg ) . trigger ( 'input' ) ;
$ ( '#chromadb_hhaa_memoryfmt' ) . val ( extension _settings . chromadb . hhaa _memory _msg ) . trigger ( 'input' ) ;
$ ( '#chromadb_hhaa_token_limit' ) . val ( extension _settings . chromadb . hhaa _token _limit ) . trigger ( 'input' ) ;
$ ( '#chromadb_auto_adjust' ) . prop ( 'checked' , extension _settings . chromadb . auto _adjust ) ;
$ ( '#chromadb_freeze' ) . prop ( 'checked' , extension _settings . chromadb . freeze ) ;
2023-08-12 14:32:18 +02:00
$ ( '#chromadb_query_last_only' ) . prop ( 'checked' , extension _settings . chromadb . query _last _only ) ;
2023-07-20 19:32:15 +02:00
enableDisableSliders ( ) ;
onStrategyChange ( ) ;
}
function onStrategyChange ( ) {
console . debug ( 'changing chromadb strat' ) ;
extension _settings . chromadb . strategy = $ ( '#chromadb_strategy' ) . val ( ) ;
if ( extension _settings . chromadb . strategy === "custom" ) {
$ ( '#chromadb_custom_depth' ) . show ( ) ;
$ ( 'label[for="chromadb_custom_depth"]' ) . show ( ) ;
$ ( '#chromadb_custom_msg' ) . show ( ) ;
$ ( 'label[for="chromadb_custom_msg"]' ) . show ( ) ;
}
else if ( extension _settings . chromadb . strategy === "hh_aa" ) {
$ ( '#chromadb_hhaa_wrapperfmt' ) . show ( ) ;
$ ( 'label[for="chromadb_hhaa_wrapperfmt"]' ) . show ( ) ;
$ ( '#chromadb_hhaa_memoryfmt' ) . show ( ) ;
$ ( 'label[for="chromadb_hhaa_memoryfmt"]' ) . show ( ) ;
$ ( '#chromadb_hhaa_token_limit' ) . show ( ) ;
$ ( 'label[for="chromadb_hhaa_token_limit"]' ) . show ( ) ;
}
saveSettingsDebounced ( ) ;
}
function onRecallStrategyChange ( ) {
console . log ( 'changing chromadb recall strat' ) ;
extension _settings . chromadb . recall _strategy = $ ( '#chromadb_recall_strategy' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onSortStrategyChange ( ) {
console . log ( 'changing chromadb sort strat' ) ;
extension _settings . chromadb . sort _strategy = $ ( '#chromadb_sort_strategy' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onKeepContextInput ( ) {
extension _settings . chromadb . keep _context = Number ( $ ( '#chromadb_keep_context' ) . val ( ) ) ;
$ ( '#chromadb_keep_context_value' ) . text ( extension _settings . chromadb . keep _context ) ;
saveSettingsDebounced ( ) ;
}
function onNResultsInput ( ) {
extension _settings . chromadb . n _results = Number ( $ ( '#chromadb_n_results' ) . val ( ) ) ;
$ ( '#chromadb_n_results_value' ) . text ( extension _settings . chromadb . n _results ) ;
saveSettingsDebounced ( ) ;
}
function onChromaDepthInput ( ) {
extension _settings . chromadb . chroma _depth = Number ( $ ( '#chromadb_custom_depth' ) . val ( ) ) ;
$ ( '#chromadb_custom_depth_value' ) . text ( extension _settings . chromadb . chroma _depth ) ;
saveSettingsDebounced ( ) ;
}
function onChromaMsgInput ( ) {
extension _settings . chromadb . recall _msg = $ ( '#chromadb_custom_msg' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onChromaHHAAWrapper ( ) {
extension _settings . chromadb . hhaa _wrapper _msg = $ ( '#chromadb_hhaa_wrapperfmt' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onChromaHHAAMemory ( ) {
extension _settings . chromadb . hhaa _memory _msg = $ ( '#chromadb_hhaa_memoryfmt' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onChromaHHAATokens ( ) {
extension _settings . chromadb . hhaa _token _limit = Number ( $ ( '#chromadb_hhaa_token_limit' ) . val ( ) ) ;
$ ( '#chromadb_hhaa_token_limit_value' ) . text ( extension _settings . chromadb . hhaa _token _limit ) ;
saveSettingsDebounced ( ) ;
}
function onSplitLengthInput ( ) {
extension _settings . chromadb . split _length = Number ( $ ( '#chromadb_split_length' ) . val ( ) ) ;
$ ( '#chromadb_split_length_value' ) . text ( extension _settings . chromadb . split _length ) ;
saveSettingsDebounced ( ) ;
}
function onFileSplitLengthInput ( ) {
extension _settings . chromadb . file _split _length = Number ( $ ( '#chromadb_file_split_length' ) . val ( ) ) ;
$ ( '#chromadb_file_split_length_value' ) . text ( extension _settings . chromadb . file _split _length ) ;
saveSettingsDebounced ( ) ;
}
function onChunkNLInput ( ) {
let shouldSplit = $ ( '#onChunkNLInput' ) . is ( ':checked' ) ;
if ( shouldSplit ) {
extension _settings . chromadb . file _split _type = "newline" ;
} else {
extension _settings . chromadb . file _split _type = "length" ;
}
saveSettingsDebounced ( ) ;
}
function checkChatId ( chat _id ) {
if ( ! chat _id || chat _id . trim ( ) === '' ) {
toastr . error ( 'Please select a character and try again.' ) ;
return false ;
}
return true ;
}
async function addMessages ( chat _id , messages ) {
if ( extension _settings . chromadb . freeze ) {
return { count : 0 } ;
}
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb' ;
const messagesDeepCopy = JSON . parse ( JSON . stringify ( messages ) ) ;
let splitMessages = [ ] ;
let id = 0 ;
messagesDeepCopy . forEach ( ( m , index ) => {
const split = splitRecursive ( m . mes , extension _settings . chromadb . split _length ) ;
splitMessages . push ( ... split . map ( text => ( {
... m ,
mes : text ,
send _date : id ,
id : ` msg- ${ id ++ } ` ,
index : index ,
extra : undefined ,
} ) ) ) ;
} ) ;
splitMessages = await filterSyncedMessages ( splitMessages ) ;
// no messages to add
if ( splitMessages . length === 0 ) {
return { count : 0 } ;
}
const transformedMessages = splitMessages . map ( ( m ) => ( {
id : m . id ,
role : m . is _user ? 'user' : 'assistant' ,
content : m . mes ,
date : m . send _date ,
meta : JSON . stringify ( m ) ,
} ) ) ;
const addMessagesResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( { chat _id , messages : transformedMessages } ) ,
} ) ;
if ( addMessagesResult . ok ) {
const addMessagesData = await addMessagesResult . json ( ) ;
return addMessagesData ; // { count: 1 }
}
return { count : 0 } ;
}
async function filterSyncedMessages ( splitMessages ) {
const syncState = await getChatSyncState ( ) ;
const removeIndices = [ ] ;
const syncedIndices = [ ] ;
for ( let i = 0 ; i < splitMessages . length ; i ++ ) {
const index = splitMessages [ i ] . index ;
if ( syncState [ index ] ) {
removeIndices . push ( i ) ;
continue ;
}
syncedIndices . push ( index ) ;
}
for ( const index of syncedIndices ) {
syncState [ index ] = 1 ;
}
console . debug ( 'CHROMADB: sync state' , syncState . map ( ( v , i ) => ( { id : i , synced : v } ) ) ) ;
2023-08-14 21:19:14 +02:00
await dbStore . setItem ( getCurrentChatId ( ) , syncState ) ;
2023-07-20 19:32:15 +02:00
// remove messages that are already synced
return splitMessages . filter ( ( _ , i ) => ! removeIndices . includes ( i ) ) ;
}
async function onPurgeClick ( ) {
const chat _id = getCurrentChatId ( ) ;
if ( ! checkChatId ( chat _id ) ) {
return ;
}
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb/purge' ;
const purgeResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( { chat _id } ) ,
} ) ;
if ( purgeResult . ok ) {
2023-08-14 21:19:14 +02:00
await dbStore . removeItem ( chat _id ) ;
2023-07-20 19:32:15 +02:00
toastr . success ( 'ChromaDB context has been successfully cleared' ) ;
}
}
async function onExportClick ( ) {
const currentChatId = getCurrentChatId ( ) ;
if ( ! checkChatId ( currentChatId ) ) {
return ;
}
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb/export' ;
const exportResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( { chat _id : currentChatId } ) ,
} ) ;
if ( exportResult . ok ) {
const data = await exportResult . json ( ) ;
const blob = new Blob ( [ JSON . stringify ( data , null , 2 ) ] , { type : 'application/json' } ) ;
const href = URL . createObjectURL ( blob ) ;
const link = document . createElement ( 'a' ) ;
link . href = href ;
link . download = currentChatId + '.json' ;
document . body . appendChild ( link ) ;
link . click ( ) ;
document . body . removeChild ( link ) ;
} else {
//Show the error from the result without the html, only what's in the body paragraph
let parser = new DOMParser ( ) ;
let error = await exportResult . text ( ) ;
let doc = parser . parseFromString ( error , 'text/html' ) ;
let errorMessage = doc . querySelector ( 'p' ) . textContent ;
toastr . error ( ` An error occurred while attempting to download the data from ChromaDB: ${ errorMessage } ` ) ;
}
}
function tinyhash ( text ) {
let hash = 0 ;
for ( let i = 0 ; i < text . length ; ++ i ) {
hash = ( ( hash << 5 ) - hash ) + text . charCodeAt ( i ) ;
hash = hash & hash ; // Keeps it 32-bit allegedly.
}
return hash ;
}
async function onSelectImportFile ( e ) {
const file = e . target . files [ 0 ] ;
const currentChatId = getCurrentChatId ( ) ;
if ( ! checkChatId ( currentChatId ) ) {
return ;
}
if ( ! file ) {
return ;
}
try {
toastr . info ( 'This may take some time, depending on the file size' , 'Processing...' ) ;
const text = await getFileText ( file ) ;
const imported = JSON . parse ( text ) ;
const id _salt = "-" + tinyhash ( imported . chat _id ) . toString ( 36 ) ;
for ( let entry of imported . content ) {
entry . id = entry . id + id _salt ;
}
imported . chat _id = currentChatId ;
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb/import' ;
const importResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( imported ) ,
} ) ;
if ( importResult . ok ) {
const importResultData = await importResult . json ( ) ;
toastr . success ( ` Number of chunks: ${ importResultData . count } ` , 'Injected successfully!' ) ;
return importResultData ;
} else {
throw new Error ( ) ;
}
}
catch ( error ) {
console . log ( error ) ;
toastr . error ( 'Something went wrong while importing the data' ) ;
}
finally {
e . target . form . reset ( ) ;
}
}
async function queryMessages ( chat _id , query ) {
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb/query' ;
const queryMessagesResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( { chat _id , query , n _results : extension _settings . chromadb . n _results } ) ,
} ) ;
if ( queryMessagesResult . ok ) {
const queryMessagesData = await queryMessagesResult . json ( ) ;
return queryMessagesData ;
}
return [ ] ;
}
async function queryMultiMessages ( chat _id , query ) {
const context = getContext ( ) ;
const response = await fetch ( "/getallchatsofcharacter" , {
method : 'POST' ,
body : JSON . stringify ( { avatar _url : context . characters [ context . characterId ] . avatar } ) ,
headers : getRequestHeaders ( ) ,
} ) ;
if ( ! response . ok ) {
return ;
}
let data = await response . json ( ) ;
data = Object . values ( data ) ;
let chat _list = data . sort ( ( a , b ) => a [ "file_name" ] . localeCompare ( b [ "file_name" ] ) ) . reverse ( ) ;
// Extracting chat_ids from the chat_list
chat _list = chat _list . map ( chat => chat . file _name . replace ( /\.[^/.]+$/ , "" ) ) ;
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb/multiquery' ;
const queryMessagesResult = await fetch ( url , {
method : 'POST' ,
body : JSON . stringify ( { chat _list , query , n _results : extension _settings . chromadb . n _results } ) ,
headers : postHeaders ,
} ) ;
if ( queryMessagesResult . ok ) {
const queryMessagesData = await queryMessagesResult . json ( ) ;
return queryMessagesData ;
}
return [ ] ;
}
async function onSelectInjectFile ( e ) {
const file = e . target . files [ 0 ] ;
const currentChatId = getCurrentChatId ( ) ;
if ( ! checkChatId ( currentChatId ) ) {
return ;
}
if ( ! file ) {
return ;
}
try {
toastr . info ( 'This may take some time, depending on the file size' , 'Processing...' ) ;
const text = await getFileText ( file ) ;
extension _settings . chromadb . file _split _type = "newline" ;
//allow splitting on newlines or splitrecursively
let split = [ ] ;
if ( extension _settings . chromadb . file _split _type == "newline" ) {
split = text . split ( /\r?\n/ ) . filter ( onlyUnique ) ;
} else {
split = splitRecursive ( text , extension _settings . chromadb . file _split _length ) . filter ( onlyUnique ) ;
}
const baseDate = Date . now ( ) ;
const messages = split . map ( ( m , i ) => ( {
id : ` ${ file . name } - ${ split . indexOf ( m ) } ` ,
role : 'system' ,
content : m ,
date : baseDate + i ,
meta : JSON . stringify ( {
name : file . name ,
is _user : false ,
is _name : false ,
is _system : false ,
send _date : humanizedDateTime ( ) ,
mes : m ,
extra : {
type : system _message _types . NARRATOR ,
}
} ) ,
} ) ) ;
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/chromadb' ;
const addMessagesResult = await doExtrasFetch ( url , {
method : 'POST' ,
headers : postHeaders ,
body : JSON . stringify ( { chat _id : currentChatId , messages : messages } ) ,
} ) ;
if ( addMessagesResult . ok ) {
const addMessagesData = await addMessagesResult . json ( ) ;
toastr . success ( ` Number of chunks: ${ addMessagesData . count } ` , 'Injected successfully!' ) ;
return addMessagesData ;
} else {
throw new Error ( ) ;
}
}
catch ( error ) {
console . log ( error ) ;
toastr . error ( 'Something went wrong while injecting the data' ) ;
}
finally {
e . target . form . reset ( ) ;
}
}
2023-08-01 13:16:03 +02:00
// Gets the length of character description in the current context
function getCharacterDataLength ( ) {
const context = getContext ( ) ;
const character = context . characters [ context . characterId ] ;
if ( typeof character ? . data !== 'object' ) {
return 0 ;
}
let characterDataLength = 0 ;
for ( const [ key , value ] of Object . entries ( character . data ) ) {
if ( typeof value !== 'string' ) {
continue ;
}
if ( [ 'description' , 'personality' , 'scenario' ] . includes ( key ) ) {
characterDataLength += character . data [ key ] . length ;
}
}
return characterDataLength ;
}
2023-07-20 19:32:15 +02:00
/ *
* Automatically adjusts the extension settings for the optimal number of messages to keep and query based
* on the chat history and a specified maximum context length .
* /
function doAutoAdjust ( chat , maxContext ) {
2023-08-12 14:32:18 +02:00
// Only valid for chat injections strategy
if ( extension _settings . chromadb . recall _strategy !== 0 ) {
return ;
}
2023-07-20 19:32:15 +02:00
console . debug ( 'CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)' , chat . length , maxContext ) ;
// Get mean message length
2023-07-22 21:21:37 +02:00
const meanMessageLength = chat . reduce ( ( acc , cur ) => acc + ( cur ? . mes ? . length ? ? 0 ) , 0 ) / chat . length ;
2023-07-20 19:32:15 +02:00
2023-07-22 21:21:37 +02:00
if ( Number . isNaN ( meanMessageLength ) || meanMessageLength === 0 ) {
console . debug ( 'CHROMADB: Mean message length is zero or NaN, aborting auto-adjust' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
2023-08-01 13:16:03 +02:00
// Adjust max context for character defs length
maxContext = Math . floor ( maxContext - ( getCharacterDataLength ( ) / CHARACTERS _PER _TOKEN _RATIO ) ) ;
console . debug ( 'CHROMADB: Max context adjusted for character defs: %o' , maxContext ) ;
2023-07-20 19:32:15 +02:00
console . debug ( 'CHROMADB: Mean message length (characters): %o' , meanMessageLength ) ;
// Convert to number of "tokens"
const meanMessageLengthTokens = Math . ceil ( meanMessageLength / CHARACTERS _PER _TOKEN _RATIO ) ;
console . debug ( 'CHROMADB: Mean message length (tokens): %o' , meanMessageLengthTokens ) ;
// Get number of messages in context
const contextMessages = Math . max ( 1 , Math . ceil ( maxContext / meanMessageLengthTokens ) ) ;
2023-08-16 16:22:42 +02:00
// Round up to nearest 5
const contextMessagesRounded = Math . ceil ( contextMessages / 5 ) * 5 ;
2023-07-20 19:32:15 +02:00
console . debug ( 'CHROMADB: Estimated context messages (rounded): %o' , contextMessagesRounded ) ;
// Messages to keep (proportional, rounded to nearest 5, minimum 5, maximum 500)
const messagesToKeep = Math . min ( defaultSettings . keep _context _max , Math . max ( 5 , Math . floor ( contextMessagesRounded * extension _settings . chromadb . keep _context _proportion / 5 ) * 5 ) ) ;
console . debug ( 'CHROMADB: Estimated messages to keep: %o' , messagesToKeep ) ;
// Messages to query (rounded, maximum 500)
const messagesToQuery = Math . min ( defaultSettings . n _results _max , contextMessagesRounded - messagesToKeep ) ;
console . debug ( 'CHROMADB: Estimated messages to query: %o' , messagesToQuery ) ;
// Set extension settings
extension _settings . chromadb . keep _context = messagesToKeep ;
extension _settings . chromadb . n _results = messagesToQuery ;
// Update sliders
$ ( '#chromadb_keep_context' ) . val ( messagesToKeep ) ;
$ ( '#chromadb_n_results' ) . val ( messagesToQuery ) ;
// Update labels
$ ( '#chromadb_keep_context_value' ) . text ( extension _settings . chromadb . keep _context ) ;
$ ( '#chromadb_n_results_value' ) . text ( extension _settings . chromadb . n _results ) ;
}
window . chromadb _interceptGeneration = async ( chat , maxContext ) => {
if ( extension _settings . chromadb . auto _adjust ) {
doAutoAdjust ( chat , maxContext ) ;
}
const currentChatId = getCurrentChatId ( ) ;
if ( ! currentChatId )
return ;
//log the current settings
console . debug ( "CHROMADB: Current settings: %o" , extension _settings . chromadb ) ;
const selectedStrategy = extension _settings . chromadb . strategy ;
const recallStrategy = extension _settings . chromadb . recall _strategy ;
let recallMsg = extension _settings . chromadb . recall _msg || defaultSettings . chroma _default _msg ;
const chromaDepth = extension _settings . chromadb . chroma _depth ;
const chromaSortStrategy = extension _settings . chromadb . sort _strategy ;
const chromaQueryLastOnly = extension _settings . chromadb . query _last _only ;
const messagesToStore = chat . slice ( 0 , - extension _settings . chromadb . keep _context ) ;
if ( messagesToStore . length > 0 && ! extension _settings . chromadb . freeze ) {
//log the messages to store
console . debug ( "CHROMADB: Messages to store: %o" , messagesToStore ) ;
//log the messages to store length vs keep context
console . debug ( "CHROMADB: Messages to store length vs keep context: %o vs %o" , messagesToStore . length , extension _settings . chromadb . keep _context ) ;
await addMessages ( currentChatId , messagesToStore ) ;
}
2023-07-22 21:21:37 +02:00
2023-07-20 19:32:15 +02:00
const lastMessage = chat [ chat . length - 1 ] ;
let queriedMessages ;
if ( lastMessage ) {
let queryBlob = "" ;
if ( chromaQueryLastOnly ) {
queryBlob = lastMessage . mes ;
}
else {
for ( let msg of chat . slice ( - extension _settings . chromadb . keep _context ) ) {
queryBlob += ` ${ msg . mes } \n `
}
}
console . debug ( "CHROMADB: Query text:" , queryBlob ) ;
if ( recallStrategy === 'multichat' ) {
console . log ( "Utilizing multichat" )
queriedMessages = await queryMultiMessages ( currentChatId , queryBlob ) ;
}
else {
queriedMessages = await queryMessages ( currentChatId , queryBlob ) ;
}
if ( chromaSortStrategy === "date" ) {
queriedMessages . sort ( ( a , b ) => a . date - b . date ) ;
}
else {
queriedMessages . sort ( ( a , b ) => b . distance - a . distance ) ;
}
console . debug ( "CHROMADB: Query results: %o" , queriedMessages ) ;
let newChat = [ ] ;
if ( selectedStrategy === 'ross' ) {
//adds chroma to the end of chat and allows Generate() to cull old messages naturally.
const context = getContext ( ) ;
const charname = context . name2 ;
newChat . push (
{
is _name : false ,
is _user : false ,
mes : ` [Use these past chat exchanges to inform ${ charname } 's next response: ` ,
name : "system" ,
send _date : 0 ,
}
) ;
newChat . push ( ... queriedMessages . map ( m => m . meta ) . filter ( onlyUnique ) . map ( JSON . parse ) ) ;
newChat . push (
{
is _name : false ,
is _user : false ,
mes : ` ] \n ` ,
name : "system" ,
send _date : 0 ,
}
) ;
chat . splice ( chat . length , 0 , ... newChat ) ;
}
if ( selectedStrategy === 'hh_aa' ) {
// Insert chroma history messages as a list at the AFTER_SCENARIO anchor point
const context = getContext ( ) ;
const chromaTokenLimit = extension _settings . chromadb . hhaa _token _limit ;
let wrapperMsg = extension _settings . chromadb . hhaa _wrapper _msg || defaultSettings . chroma _default _hhaa _wrapper ;
wrapperMsg = substituteParams ( wrapperMsg , context . name1 , context . name2 ) ;
if ( ! wrapperMsg . includes ( "{{memories}}" ) ) {
wrapperMsg += " {{memories}}" ;
}
let memoryMsg = extension _settings . chromadb . hhaa _memory _msg || defaultSettings . chroma _default _hhaa _memory ;
memoryMsg = substituteParams ( memoryMsg , context . name1 , context . name2 ) ;
if ( ! memoryMsg . includes ( "{{message}}" ) ) {
memoryMsg += " {{message}}" ;
}
// Reversed because we want the most 'important' messages at the bottom.
let recalledMemories = queriedMessages . map ( m => m . meta ) . filter ( onlyUnique ) . map ( JSON . parse ) . reverse ( ) ;
let tokenApprox = 0 ;
let allMemoryBlob = "" ;
let seenMemories = new Set ( ) ; // Why are there even duplicates in chromadb anyway?
for ( const msg of recalledMemories ) {
const memoryBlob = memoryMsg . replace ( '{{name}}' , msg . name ) . replace ( '{{message}}' , msg . mes ) ;
const memoryTokens = ( memoryBlob . length / CHARACTERS _PER _TOKEN _RATIO ) ;
if ( ! seenMemories . has ( memoryBlob ) && tokenApprox + memoryTokens <= chromaTokenLimit ) {
allMemoryBlob += memoryBlob ;
tokenApprox += memoryTokens ;
seenMemories . add ( memoryBlob ) ;
}
}
// No memories? No prompt.
const promptBlob = ( tokenApprox == 0 ) ? "" : wrapperMsg . replace ( '{{memories}}' , allMemoryBlob ) ;
console . debug ( "CHROMADB: prompt blob: %o" , promptBlob ) ;
context . setExtensionPrompt ( MODULE _NAME , promptBlob , extension _prompt _types . AFTER _SCENARIO ) ;
}
if ( selectedStrategy === 'custom' ) {
const context = getContext ( ) ;
recallMsg = substituteParams ( recallMsg , context . name1 , context . name2 ) ;
if ( ! recallMsg . includes ( "{{memories}}" ) ) {
recallMsg += " {{memories}}" ;
}
let recallStart = recallMsg . split ( '{{memories}}' ) [ 0 ]
let recallEnd = recallMsg . split ( '{{memories}}' ) [ 1 ]
newChat . push (
{
is _name : false ,
is _user : false ,
mes : recallStart ,
name : "system" ,
send _date : 0 ,
}
) ;
newChat . push ( ... queriedMessages . map ( m => m . meta ) . filter ( onlyUnique ) . map ( JSON . parse ) ) ;
newChat . push (
{
is _name : false ,
is _user : false ,
mes : recallEnd + ` \n ` ,
name : "system" ,
send _date : 0 ,
}
) ;
//prototype chroma duplicate removal
let chatset = new Set ( chat . map ( obj => obj . mes ) ) ;
newChat = newChat . filter ( obj => ! chatset . has ( obj . mes ) ) ;
if ( chromaDepth === - 1 ) {
chat . splice ( chat . length , 0 , ... newChat ) ;
}
else {
chat . splice ( chromaDepth , 0 , ... newChat ) ;
}
}
if ( selectedStrategy === 'original' ) {
//removes .length # messages from the start of 'kept messages'
//replaces them with chromaDB results (with no separator)
newChat . push ( ... queriedMessages . map ( m => m . meta ) . filter ( onlyUnique ) . map ( JSON . parse ) ) ;
chat . splice ( 0 , messagesToStore . length , ... newChat ) ;
}
}
}
function onFreezeInput ( ) {
extension _settings . chromadb . freeze = $ ( '#chromadb_freeze' ) . is ( ':checked' ) ;
saveSettingsDebounced ( ) ;
}
function onAutoAdjustInput ( ) {
extension _settings . chromadb . auto _adjust = $ ( '#chromadb_auto_adjust' ) . is ( ':checked' ) ;
enableDisableSliders ( ) ;
saveSettingsDebounced ( ) ;
}
function onFullLogQuery ( ) {
extension _settings . chromadb . query _last _only = $ ( '#chromadb_query_last_only' ) . is ( ':checked' ) ;
saveSettingsDebounced ( ) ;
}
function enableDisableSliders ( ) {
const auto _adjust = extension _settings . chromadb . auto _adjust ;
$ ( 'label[for="chromadb_keep_context"]' ) . prop ( 'hidden' , auto _adjust ) ;
$ ( '#chromadb_keep_context' ) . prop ( 'hidden' , auto _adjust )
$ ( 'label[for="chromadb_n_results"]' ) . prop ( 'hidden' , auto _adjust ) ;
$ ( '#chromadb_n_results' ) . prop ( 'hidden' , auto _adjust )
$ ( 'label[for="chromadb_keep_context_proportion"]' ) . prop ( 'hidden' , ! auto _adjust ) ;
$ ( '#chromadb_keep_context_proportion' ) . prop ( 'hidden' , ! auto _adjust )
}
function onKeepContextProportionInput ( ) {
extension _settings . chromadb . keep _context _proportion = $ ( '#chromadb_keep_context_proportion' ) . val ( ) ;
$ ( '#chromadb_keep_context_proportion_value' ) . text ( Math . round ( extension _settings . chromadb . keep _context _proportion * 100 ) ) ;
saveSettingsDebounced ( ) ;
}
jQuery ( async ( ) => {
const settingsHtml = `
< div class = "chromadb_settings" >
< div class = "inline-drawer" >
< div class = "inline-drawer-toggle inline-drawer-header" >
< b > Smart Context < / b >
< div class = "inline-drawer-icon fa-solid fa-circle-chevron-down down" > < / d i v >
< / d i v >
< div class = "inline-drawer-content" >
< small > This extension rearranges the messages in the current chat to keep more relevant information in the context . Adjust the sliders below based on average amount of messages in your prompt ( refer to the chat cut - off line ) . < / s m a l l >
< span class = "wide100p marginTopBot5 displayBlock" > Memory Injection Strategy < / s p a n >
< hr >
< select id = "chromadb_strategy" >
< option value = "original" > Replace non - kept chat items with memories < / o p t i o n >
< option value = "ross" > Add memories after chat with a header tag < / o p t i o n >
< option value = "hh_aa" > Add memory list to character description < / o p t i o n >
< option value = "custom" > Add memories at custom depth with custom msg < / o p t i o n >
< / s e l e c t >
< label for = "chromadb_custom_msg" hidden > < small > Custom injection message : < / s m a l l > < / l a b e l >
< textarea id = "chromadb_custom_msg" hidden class = "text_pole textarea_compact" rows = "2" placeholder = "${defaultSettings.chroma_default_msg}" style = "height: 61px; display: none;" > < / t e x t a r e a >
< label for = "chromadb_custom_depth" hidden > < small > How deep should the memory messages be injected ? : ( < span id = "chromadb_custom_depth_value" > < / s p a n > ) < / s m a l l > < / l a b e l >
< input id = "chromadb_custom_depth" type = "range" min = "${defaultSettings.chroma_depth_min}" max = "${defaultSettings.chroma_depth_max}" step = "${defaultSettings.chroma_depth_step}" value = "${defaultSettings.chroma_depth}" hidden / >
2023-07-22 21:21:37 +02:00
2023-07-20 19:32:15 +02:00
< label for = "chromadb_hhaa_wrapperfmt" hidden > < small > Custom wrapper format : < / s m a l l > < / l a b e l >
< textarea id = "chromadb_hhaa_wrapperfmt" hidden class = "text_pole textarea_compact" rows = "2" placeholder = "${defaultSettings.chroma_default_hhaa_wrapper}" style = "height: 61px; display: none;" > < / t e x t a r e a >
< label for = "chromadb_hhaa_memoryfmt" hidden > < small > Custom memory format : < / s m a l l > < / l a b e l >
< textarea id = "chromadb_hhaa_memoryfmt" hidden class = "text_pole textarea_compact" rows = "2" placeholder = "${defaultSettings.chroma_default_hhaa_memory}" style = "height: 61px; display: none;" > < / t e x t a r e a >
< label for = "chromadb_hhaa_token_limit" hidden > < small > Maximum tokens allowed for memories : ( < span id = "chromadb_hhaa_token_limit_value" > < / s p a n > ) < / s m a l l > < / l a b e l >
< input id = "chromadb_hhaa_token_limit" type = "range" min = "0" max = "2048" step = "64" value = "${defaultSettings.hhaa_token_limit}" hidden / >
2023-07-22 21:21:37 +02:00
2023-07-20 19:32:15 +02:00
< span > Memory Recall Strategy < / s p a n >
< select id = "chromadb_recall_strategy" >
< option value = "original" > Recall only from this chat < / o p t i o n >
< option value = "multichat" > Recall from all character chats ( experimental ) < / o p t i o n >
< / s e l e c t >
< span > Memory Sort Strategy < / s p a n >
< select id = "chromadb_sort_strategy" >
< option value = "date" > Sort memories by date < / o p t i o n >
< option value = "distance" > Sort memories by relevance < / o p t i o n >
< / s e l e c t >
< label for = "chromadb_keep_context" > < small > How many original chat messages to keep : ( < span id = "chromadb_keep_context_value" > < / s p a n > ) m e s s a g e s < / s m a l l > < / l a b e l >
< input id = "chromadb_keep_context" type = "range" min = "${defaultSettings.keep_context_min}" max = "${defaultSettings.keep_context_max}" step = "${defaultSettings.keep_context_step}" value = "${defaultSettings.keep_context}" / >
< label for = "chromadb_n_results" > < small > Maximum number of ChromaDB 'memories' to inject : ( < span id = "chromadb_n_results_value" > < / s p a n > ) m e s s a g e s < / s m a l l > < / l a b e l >
< input id = "chromadb_n_results" type = "range" min = "${defaultSettings.n_results_min}" max = "${defaultSettings.n_results_max}" step = "${defaultSettings.n_results_step}" value = "${defaultSettings.n_results}" / >
2023-07-22 21:21:37 +02:00
2023-07-20 19:32:15 +02:00
< label for = "chromadb_keep_context_proportion" > < small > Keep ( < span id = "chromadb_keep_context_proportion_value" > < / s p a n > % ) o f i n - c o n t e x t c h a t m e s s a g e s ; r e p l a c e t h e r e s t w i t h m e m o r i e s < / s m a l l > < / l a b e l >
< input id = "chromadb_keep_context_proportion" type = "range" min = "${defaultSettings.keep_context_proportion_min}" max = "${defaultSettings.keep_context_proportion_max}" step = "${defaultSettings.keep_context_proportion_step}" value = "${defaultSettings.keep_context_proportion}" / >
< label for = "chromadb_split_length" > < small > Max length for each 'memory' pulled from the current chat history : ( < span id = "chromadb_split_length_value" > < / s p a n > ) c h a r a c t e r s < / s m a l l > < / l a b e l >
< input id = "chromadb_split_length" type = "range" min = "${defaultSettings.split_length_min}" max = "${defaultSettings.split_length_max}" step = "${defaultSettings.split_length_step}" value = "${defaultSettings.split_length}" / >
< label for = "chromadb_file_split_length" > < small > Max length for each 'memory' pulled from imported text files : ( < span id = "chromadb_file_split_length_value" > < / s p a n > ) c h a r a c t e r s < / s m a l l > < / l a b e l >
< input id = "chromadb_file_split_length" type = "range" min = "${defaultSettings.file_split_length_min}" max = "${defaultSettings.file_split_length_max}" step = "${defaultSettings.file_split_length_step}" value = "${defaultSettings.file_split_length}" / >
< label class = "checkbox_label" for = "chromadb_freeze" title = "Pauses the automatic synchronization of new messages with ChromaDB. Older messages and injections will still be pulled as usual." >
< input type = "checkbox" id = "chromadb_freeze" / >
< span > Freeze ChromaDB state < / s p a n >
< / l a b e l >
< label class = "checkbox_label for=" chromadb _auto _adjust " title=" Automatically adjusts the number of messages to keep based on the average number of messages in the current chat and the chosen proportion . " >
< input type = "checkbox" id = "chromadb_auto_adjust" / >
< span > Use % strategy < / s p a n >
< / l a b e l >
< label class = "checkbox_label" for = "chromadb_chunk_nl" title = "Chunk injected documents on newline instead of at set character size." >
< input type = "checkbox" id = "chromadb_chunk_nl" / >
< span > Chunk on Newlines < / s p a n >
< / l a b e l >
< label class = "checkbox_label for=" chromadb _query _last _only " title=" ChromaDB queries only use the most recent message . ( Instead of using all messages still in the context . ) " >
< input type = "checkbox" id = "chromadb_query_last_only" / >
< span > Query last message only < / s p a n >
< / l a b e l >
< div class = "flex-container spaceEvenly" >
< div id = "chromadb_inject" title = "Upload custom textual data to use in the context of the current chat" class = "menu_button" >
< i class = "fa-solid fa-file-arrow-up" > < / i >
< span > Inject Data ( TXT file ) < / s p a n >
< / d i v >
< div id = "chromadb_export" title = "Export all of the current chromadb data for this current chat" class = "menu_button" >
< i class = "fa-solid fa-file-export" > < / i >
< span > Export < / s p a n >
< / d i v >
< div id = "chromadb_import" title = "Import a full chromadb export for this current chat" class = "menu_button" >
< i class = "fa-solid fa-file-import" > < / i >
< span > Import < / s p a n >
< / d i v >
< div id = "chromadb_purge" title = "Force purge all the data related to the current chat from the database" class = "menu_button" >
< i class = "fa-solid fa-broom" > < / i >
< span > Purge Chat from the DB < / s p a n >
< / d i v >
< / d i v >
< small > < i > Local ChromaDB now persists to disk by default . The default folder is . chroma _db , and you can set a different folder with the -- chroma - folder argument . If you are using the Extras Colab notebook , you will need to inject the text data every time the Extras API server is restarted . < / i > < / s m a l l >
< / d i v >
< form > < input id = "chromadb_inject_file" type = "file" accept = "text/plain" hidden > < / f o r m >
< form > < input id = "chromadb_import_file" type = "file" accept = "application/json" hidden > < / f o r m >
< / d i v > ` ;
$ ( '#extensions_settings2' ) . append ( settingsHtml ) ;
$ ( '#chromadb_strategy' ) . on ( 'change' , onStrategyChange ) ;
$ ( '#chromadb_recall_strategy' ) . on ( 'change' , onRecallStrategyChange ) ;
$ ( '#chromadb_sort_strategy' ) . on ( 'change' , onSortStrategyChange ) ;
$ ( '#chromadb_keep_context' ) . on ( 'input' , onKeepContextInput ) ;
$ ( '#chromadb_n_results' ) . on ( 'input' , onNResultsInput ) ;
$ ( '#chromadb_custom_depth' ) . on ( 'input' , onChromaDepthInput ) ;
$ ( '#chromadb_custom_msg' ) . on ( 'input' , onChromaMsgInput ) ;
$ ( '#chromadb_hhaa_wrapperfmt' ) . on ( 'input' , onChromaHHAAWrapper ) ;
$ ( '#chromadb_hhaa_memoryfmt' ) . on ( 'input' , onChromaHHAAMemory ) ;
$ ( '#chromadb_hhaa_token_limit' ) . on ( 'input' , onChromaHHAATokens ) ;
$ ( '#chromadb_split_length' ) . on ( 'input' , onSplitLengthInput ) ;
$ ( '#chromadb_file_split_length' ) . on ( 'input' , onFileSplitLengthInput ) ;
$ ( '#chromadb_inject' ) . on ( 'click' , ( ) => $ ( '#chromadb_inject_file' ) . trigger ( 'click' ) ) ;
$ ( '#chromadb_import' ) . on ( 'click' , ( ) => $ ( '#chromadb_import_file' ) . trigger ( 'click' ) ) ;
$ ( '#chromadb_inject_file' ) . on ( 'change' , onSelectInjectFile ) ;
$ ( '#chromadb_import_file' ) . on ( 'change' , onSelectImportFile ) ;
$ ( '#chromadb_purge' ) . on ( 'click' , onPurgeClick ) ;
$ ( '#chromadb_export' ) . on ( 'click' , onExportClick ) ;
$ ( '#chromadb_freeze' ) . on ( 'input' , onFreezeInput ) ;
$ ( '#chromadb_chunk_nl' ) . on ( 'input' , onChunkNLInput ) ;
$ ( '#chromadb_auto_adjust' ) . on ( 'input' , onAutoAdjustInput ) ;
$ ( '#chromadb_query_last_only' ) . on ( 'input' , onFullLogQuery ) ;
$ ( '#chromadb_keep_context_proportion' ) . on ( 'input' , onKeepContextProportionInput ) ;
await loadSettings ( ) ;
// Not sure if this is needed, but it's here just in case
eventSource . on ( event _types . MESSAGE _DELETED , getChatSyncState ) ;
eventSource . on ( event _types . MESSAGE _RECEIVED , getChatSyncState ) ;
eventSource . on ( event _types . MESSAGE _SENT , getChatSyncState ) ;
// Will make the sync state update when a message is edited or swiped
eventSource . on ( event _types . MESSAGE _EDITED , invalidateMessageSyncState ) ;
eventSource . on ( event _types . MESSAGE _SWIPED , invalidateMessageSyncState ) ;
} ) ;