2024-07-29 22:02:24 +00:00
// This requires axios
// In the html don't forget to import axios.min.js cdn
// <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// JavaScript equivalent of the ELF_GENERATION_FORMAT enum
const ELF _GENERATION _FORMAT = {
LOLLMS : 0 ,
OPENAI : 1 ,
OLLAMA : 2 ,
LITELLM : 2
} ;
// JavaScript equivalent of the ELF_COMPLETION_FORMAT enum
const ELF _COMPLETION _FORMAT = {
Instruct : 0 ,
Chat : 1
} ;
// Ensuring the objects are immutable
Object . freeze ( ELF _GENERATION _FORMAT ) ;
Object . freeze ( ELF _COMPLETION _FORMAT ) ;
class LollmsClient {
constructor (
host _address = null ,
model _name = null ,
ctx _size = 4096 ,
personality = - 1 ,
n _predict = 4096 ,
temperature = 0.1 ,
top _k = 50 ,
top _p = 0.95 ,
repeat _penalty = 0.8 ,
repeat _last _n = 40 ,
seed = null ,
n _threads = 8 ,
service _key = "" ,
default _generation _mode = ELF _GENERATION _FORMAT . LOLLMS
) {
// Handle the import or initialization of tiktoken equivalent in JavaScript
// this.tokenizer = new TikTokenJS('gpt-3.5-turbo-1106'); // This is hypothetical
this . host _address = host _address ;
this . model _name = model _name ;
this . ctx _size = ctx _size ;
2024-08-06 13:45:11 +00:00
this . n _predict = n _predict ? n _predict : 4096 ;
2024-07-29 22:02:24 +00:00
this . personality = personality ;
this . temperature = temperature ;
this . top _k = top _k ;
this . top _p = top _p ;
this . repeat _penalty = repeat _penalty ;
this . repeat _last _n = repeat _last _n ;
this . seed = seed ;
this . n _threads = n _threads ;
this . service _key = service _key ;
this . default _generation _mode = default _generation _mode ;
2024-08-29 22:57:14 +00:00
this . minNPredict = 10
2024-07-29 22:02:24 +00:00
this . template = {
start _header _id _template : "!@>" ,
end _header _id _template : ": " ,
separator _template : "\n" ,
start _user _header _id _template : "!@>" ,
end _user _header _id _template : ": " ,
end _user _message _id _template : "" ,
start _ai _header _id _template : "!@>" ,
end _ai _header _id _template : ": " ,
end _ai _message _id _template : "" ,
system _message _template : "system"
}
fetch ( '/template' )
. then ( ( response ) => {
if ( ! response . ok ) {
throw new Error ( 'Network response was not ok ' + response . statusText ) ;
}
return response . json ( ) ;
} )
. then ( ( data ) => {
console . log ( "data: " , data ) ;
this . template = data ;
} )
. catch ( ( error ) => {
console . error ( 'Error fetching template:' , error ) ;
} ) ;
}
2024-08-29 22:57:14 +00:00
updateSettings ( settings ) {
// Update each setting if it's provided in the settings object
if ( 'host_address' in settings ) this . host _address = settings . host _address ;
if ( 'model_name' in settings ) this . model _name = settings . model _name ;
if ( 'ctx_size' in settings ) this . ctx _size = settings . ctx _size ;
if ( 'n_predict' in settings ) this . n _predict = settings . n _predict ;
if ( 'personality' in settings ) this . personality = settings . personality ;
if ( 'temperature' in settings ) this . temperature = settings . temperature ;
if ( 'top_k' in settings ) this . top _k = settings . top _k ;
if ( 'top_p' in settings ) this . top _p = settings . top _p ;
if ( 'repeat_penalty' in settings ) this . repeat _penalty = settings . repeat _penalty ;
if ( 'repeat_last_n' in settings ) this . repeat _last _n = settings . repeat _last _n ;
if ( 'seed' in settings ) this . seed = settings . seed ;
if ( 'n_threads' in settings ) this . n _threads = settings . n _threads ;
if ( 'service_key' in settings ) this . service _key = settings . service _key ;
if ( 'default_generation_mode' in settings ) this . default _generation _mode = settings . default _generation _mode ;
// You might want to add some validation or type checking here
console . log ( 'Settings updated:' , settings ) ;
}
2024-07-29 22:02:24 +00:00
system _message ( ) {
return this . template . start _header _id _template + this . template . system _message _template + this . template . end _header _id _template
}
ai _message ( ai _name = "assistant" ) {
return this . template . start _ai _header _id _template + ai _name + this . template . end _ai _header _id _template
}
user _message ( user _name = "user" ) {
return this . template . start _user _header _id _template + user _name + this . template . end _user _header _id _template
}
2024-08-29 22:57:14 +00:00
custom _message ( message _name = "message" ) {
return this . template . start _ai _header _id _template + message _name + this . template . end _ai _header _id _template
}
2024-07-29 22:02:24 +00:00
updateServerAddress ( newAddress ) {
this . serverAddress = newAddress ;
}
async tokenize ( prompt ) {
/ * *
* Tokenizes the given prompt using the model ' s tokenizer .
*
* @ param { string } prompt - The input prompt to be tokenized .
* @ returns { Array } A list of tokens representing the tokenized prompt .
* /
const output = await axios . post ( "/lollms_tokenize" , { "prompt" : prompt } ) ;
return output . data . named _tokens
}
async detokenize ( tokensList ) {
/ * *
* Detokenizes the given list of tokens using the model ' s tokenizer .
*
* @ param { Array } tokensList - A list of tokens to be detokenized .
* @ returns { string } The detokenized text as a string .
* /
const output = await axios . post ( "/lollms_detokenize" , { "tokens" : tokensList } ) ;
console . log ( output . data . text )
return output . data . text
2024-08-06 13:45:11 +00:00
}
2024-07-29 22:02:24 +00:00
generate ( prompt , {
n _predict = null ,
stream = false ,
temperature = 0.1 ,
top _k = 50 ,
top _p = 0.95 ,
repeat _penalty = 0.8 ,
repeat _last _n = 40 ,
seed = null ,
n _threads = 8 ,
service _key = "" ,
streamingCallback = null
} = { } ) {
switch ( this . default _generation _mode ) {
case ELF _GENERATION _FORMAT . LOLLMS :
return this . lollms _generate ( prompt , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , service _key , streamingCallback ) ;
case ELF _GENERATION _FORMAT . OPENAI :
return this . openai _generate ( prompt , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , ELF _COMPLETION _FORMAT . INSTRUCT , service _key , streamingCallback ) ;
case ELF _GENERATION _FORMAT . OLLAMA :
return this . ollama _generate ( prompt , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , ELF _COMPLETION _FORMAT . INSTRUCT , service _key , streamingCallback ) ;
case ELF _GENERATION _FORMAT . LITELLM :
return this . litellm _generate ( prompt , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , ELF _COMPLETION _FORMAT . INSTRUCT , service _key , streamingCallback ) ;
default :
throw new Error ( 'Invalid generation mode' ) ;
}
}
2024-08-06 13:45:11 +00:00
generate _with _images ( prompt , images , {
n _predict = null ,
stream = false ,
temperature = 0.1 ,
top _k = 50 ,
top _p = 0.95 ,
repeat _penalty = 0.8 ,
repeat _last _n = 40 ,
seed = null ,
n _threads = 8 ,
service _key = "" ,
streamingCallback = null
} = { } ) {
switch ( this . default _generation _mode ) {
case ELF _GENERATION _FORMAT . LOLLMS :
return this . lollms _generate _with _images ( prompt , images , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , service _key , streamingCallback ) ;
2024-09-04 23:54:52 +00:00
case ELF _GENERATION _FORMAT . OPENAI :
return this . openai _generate _with _images ( prompt , images , this . host _address , this . model _name , - 1 , n _predict , stream , temperature , top _k , top _p , repeat _penalty , repeat _last _n , seed , n _threads , ELF _COMPLETION _FORMAT . INSTRUCT , service _key , streamingCallback ) ;
2024-08-06 13:45:11 +00:00
default :
throw new Error ( 'Invalid generation mode' ) ;
}
}
2024-07-29 22:02:24 +00:00
async generateText ( prompt , options = { } ) {
// Destructure with default values from `this` if not provided in `options`
const {
host _address = this . host _address ,
model _name = this . model _name ,
personality = this . personality ,
n _predict = this . n _predict ,
stream = false ,
temperature = this . temperature ,
top _k = this . top _k ,
top _p = this . top _p ,
repeat _penalty = this . repeat _penalty ,
repeat _last _n = this . repeat _last _n ,
seed = this . seed ,
n _threads = this . n _threads ,
service _key = this . service _key ,
streamingCallback = null
} = options ;
try {
const result = await this . lollms _generate (
prompt ,
host _address ,
model _name ,
personality ,
n _predict ,
stream ,
temperature ,
top _k ,
top _p ,
repeat _penalty ,
repeat _last _n ,
seed ,
n _threads ,
service _key ,
streamingCallback
) ;
return result ;
} catch ( error ) {
// Handle any errors that occur during generation
console . error ( 'An error occurred during text generation:' , error ) ;
throw error ; // Re-throw the error if you want to allow the caller to handle it as well
}
}
async lollms _generate ( prompt , host _address = this . host _address , model _name = this . model _name , personality = this . personality , n _predict = this . n _predict , stream = false , temperature = this . temperature , top _k = this . top _k , top _p = this . top _p , repeat _penalty = this . repeat _penalty , repeat _last _n = this . repeat _last _n , seed = this . seed , n _threads = this . n _threads , service _key = this . service _key , streamingCallback = null ) {
let url ;
if ( host _address != null ) {
url = ` ${ host _address } /lollms_generate ` ;
}
else {
url = ` /lollms_generate ` ;
}
const headers = service _key !== "" ? {
'Content-Type' : 'application/json; charset=utf-8' ,
'Authorization' : ` Bearer ${ service _key } ` ,
} : {
'Content-Type' : 'application/json' ,
} ;
2024-08-06 13:45:11 +00:00
console . log ( "n_predict:" , n _predict )
console . log ( "self.n_predict:" , this . n _predict )
2024-07-29 22:02:24 +00:00
const data = JSON . stringify ( {
prompt : prompt ,
model _name : model _name ,
personality : personality ,
2024-08-06 13:45:11 +00:00
n _predict : n _predict ? n _predict : this . n _predict ,
2024-07-29 22:02:24 +00:00
stream : stream ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
seed : seed ,
n _threads : n _threads
} ) ;
try {
const response = await fetch ( url , {
method : 'POST' ,
headers : headers ,
body : data
} ) ;
// Check if the response is okay
if ( ! response . ok ) {
throw new Error ( 'Network response was not ok ' + response . statusText ) ;
}
// Read the response as plaintext
const responseBody = await response . text ( ) ;
console . log ( responseBody )
return responseBody ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
2024-08-06 13:45:11 +00:00
async lollms _generate _with _images ( prompt , images , host _address = this . host _address , model _name = this . model _name , personality = this . personality , n _predict = this . n _predict , stream = false , temperature = this . temperature , top _k = this . top _k , top _p = this . top _p , repeat _penalty = this . repeat _penalty , repeat _last _n = this . repeat _last _n , seed = this . seed , n _threads = this . n _threads , service _key = this . service _key , streamingCallback = null ) {
let url ;
if ( host _address != null ) {
url = ` ${ host _address } /lollms_generate_with_images ` ;
}
else {
url = ` /lollms_generate_with_images ` ;
}
const headers = service _key !== "" ? {
'Content-Type' : 'application/json; charset=utf-8' ,
'Authorization' : ` Bearer ${ service _key } ` ,
} : {
'Content-Type' : 'application/json' ,
} ;
console . log ( "n_predict:" , n _predict )
console . log ( "self.n_predict:" , this . n _predict )
const data = JSON . stringify ( {
prompt : prompt ,
images : images ,
model _name : model _name ,
personality : personality ,
n _predict : n _predict ? n _predict : this . n _predict ,
stream : stream ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
seed : seed ,
n _threads : n _threads
} ) ;
try {
const response = await fetch ( url , {
method : 'POST' ,
headers : headers ,
body : data
} ) ;
2024-07-29 22:02:24 +00:00
2024-08-06 13:45:11 +00:00
// Check if the response is okay
if ( ! response . ok ) {
throw new Error ( 'Network response was not ok ' + response . statusText ) ;
}
// Read the response as plaintext
const responseBody = await response . text ( ) ;
console . log ( responseBody )
return responseBody ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
2024-07-29 22:02:24 +00:00
async openai _generate ( prompt , host _address = this . host _address , model _name = this . model _name , personality = this . personality , n _predict = this . n _predict , stream = false , temperature = this . temperature , top _k = this . top _k , top _p = this . top _p , repeat _penalty = this . repeat _penalty , repeat _last _n = this . repeat _last _n , seed = this . seed , n _threads = this . n _threads , ELF _COMPLETION _FORMAT = "vllm instruct" , service _key = this . service _key , streamingCallback = null ) {
const url = ` ${ host _address } /generate_completion ` ;
const headers = service _key !== "" ? {
'Content-Type' : 'application/json; charset=utf-8' ,
'Authorization' : ` Bearer ${ service _key } ` ,
} : {
'Content-Type' : 'application/json' ,
} ;
const data = JSON . stringify ( {
prompt : prompt ,
model _name : model _name ,
personality : personality ,
n _predict : n _predict ,
stream : stream ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
seed : seed ,
n _threads : n _threads ,
completion _format : ELF _COMPLETION _FORMAT
} ) ;
try {
const response = await fetch ( url , {
method : 'POST' ,
headers : headers ,
body : data
} ) ;
if ( stream && streamingCallback ) {
// Note: Streaming with Fetch API in the browser might not work as expected because Fetch API does not support HTTP/2 server push.
// You would need a different approach for real-time streaming.
streamingCallback ( await response . json ( ) , 'MSG_TYPE_CHUNK' ) ;
} else {
return await response . json ( ) ;
}
} catch ( error ) {
console . error ( "Error generating completion:" , error ) ;
return null ;
}
}
2024-09-04 23:54:52 +00:00
async openai _generate _with _images ( prompt , images , options = { } ) {
const {
host _address = this . host _address ,
model _name = this . model _name ,
personality = this . personality ,
n _predict = this . n _predict ,
stream = false ,
temperature = this . temperature ,
top _k = this . top _k ,
top _p = this . top _p ,
repeat _penalty = this . repeat _penalty ,
repeat _last _n = this . repeat _last _n ,
seed = this . seed ,
n _threads = this . n _threads ,
max _image _width = - 1 ,
service _key = this . service _key ,
streamingCallback = null ,
} = options ;
const headers = {
'Content-Type' : 'application/json' ,
... ( service _key ? { 'Authorization' : ` Bearer ${ service _key } ` } : { } )
} ;
const data = {
model : model _name ,
messages : [
{
role : "user" ,
content : [
{
type : "text" ,
text : prompt
} ,
... images . map ( image _path => ( {
type : "image_url" ,
image _url : {
url : ` data:image/jpeg;base64, ${ this . encode _image ( image _path , max _image _width ) } `
}
} ) )
]
}
] ,
stream : true ,
temperature : parseFloat ( temperature ) ,
max _tokens : n _predict
} ;
const completion _format _path = "/v1/chat/completions" ;
const url = ` ${ host _address . endsWith ( "/" ) ? host _address . slice ( 0 , - 1 ) : host _address } ${ completion _format _path } ` ;
try {
const response = await fetch ( url , {
method : 'POST' ,
headers : headers ,
body : JSON . stringify ( data )
} ) ;
if ( ! response . ok ) {
const content = await response . json ( ) ;
if ( response . status === 400 ) {
this . error ( content . error ? . message || content . message ) ;
} else if ( response . status === 404 ) {
console . error ( await response . text ( ) ) ;
}
return ;
}
const reader = response . body . getReader ( ) ;
const decoder = new TextDecoder ( ) ;
let text = "" ;
while ( true ) {
const { done , value } = await reader . read ( ) ;
if ( done ) break ;
const chunk = decoder . decode ( value ) ;
const lines = chunk . split ( '\n' ) ;
for ( const line of lines ) {
if ( line . startsWith ( "data: " ) ) {
try {
const jsonData = JSON . parse ( line . slice ( 5 ) . trim ( ) ) ;
const content = jsonData . choices [ 0 ] ? . delta ? . content || "" ;
text += content ;
if ( streamingCallback ) {
if ( ! streamingCallback ( content , "MSG_TYPE_CHUNK" ) ) {
return text ;
}
}
} catch ( error ) {
// Handle JSON parsing error
}
} else if ( line . startsWith ( "{" ) ) {
try {
const jsonData = JSON . parse ( line ) ;
if ( jsonData . object === "error" ) {
this . error ( jsonData . message ) ;
return text ;
}
} catch ( error ) {
this . error ( "Couldn't generate text, verify your key or model name" ) ;
}
} else {
text += line ;
if ( streamingCallback ) {
if ( ! streamingCallback ( line , "MSG_TYPE_CHUNK" ) ) {
return text ;
}
}
}
}
}
return text ;
} catch ( error ) {
console . error ( "Error in openai_generate_with_images:" , error ) ;
throw error ;
}
}
async encode _image ( image _path , max _image _width = - 1 ) {
// In a browser environment, we'll use the File API and canvas
// For Node.js, you'd need to use libraries like 'sharp' or 'jimp'
return new Promise ( ( resolve , reject ) => {
const img = new Image ( ) ;
img . onload = ( ) => {
let width = img . width ;
let height = img . height ;
// Resize if necessary
if ( max _image _width !== - 1 && width > max _image _width ) {
const ratio = max _image _width / width ;
width = max _image _width ;
height = Math . round ( height * ratio ) ;
}
// Create a canvas to draw the image
const canvas = document . createElement ( 'canvas' ) ;
canvas . width = width ;
canvas . height = height ;
const ctx = canvas . getContext ( '2d' ) ;
// Draw the image on the canvas
ctx . drawImage ( img , 0 , 0 , width , height ) ;
// Convert to base64
const base64Image = canvas . toDataURL ( 'image/jpeg' ) . split ( ',' ) [ 1 ] ;
resolve ( base64Image ) ;
} ;
img . onerror = ( error ) => reject ( error ) ;
// Load the image from the provided path
img . src = image _path ;
} ) ;
}
async generateCode ( prompt , images = [ ] , {
n _predict = null ,
stream = false ,
temperature = 0.1 ,
top _k = 50 ,
top _p = 0.95 ,
repeat _penalty = 0.8 ,
repeat _last _n = 40 ,
seed = null ,
n _threads = 8 ,
service _key = "" ,
streamingCallback = null
} = { } ) {
let response ;
const systemHeader = this . custom _message ( "Generation infos" ) ;
const codeInstructions = "Generated code must be put inside the adequate markdown code tag. Use this template:\n```language name\nCode\n```\nMake sure only a single code tag is generated at each dialogue turn." ;
const fullPrompt = systemHeader + codeInstructions + this . separatorTemplate + prompt ;
if ( images . length > 0 ) {
response = await this . generate _with _images ( fullPrompt , images , {
n _predict : n _predict ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
callback : streamingCallback
} ) ;
} else {
response = await this . generate ( fullPrompt , {
n _predict : n _predict ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
callback : streamingCallback
} ) ;
}
const codes = this . extractCodeBlocks ( response ) ;
if ( codes . length > 0 ) {
let code = '' ;
if ( ! codes [ 0 ] . is _complete ) {
code = codes [ 0 ] . content . split ( '\n' ) . slice ( 0 , - 1 ) . join ( '\n' ) ;
while ( ! codes [ 0 ] . is _complete ) {
console . warn ( "The AI did not finish the code, let's ask it to continue" )
const continuePrompt = prompt + code + this . userFullHeader + "continue the code. Rewrite last line and continue the code." + this . separatorTemplate + this . aiFullHeader ;
response = await this . generate ( fullPrompt , {
n _predict : n _predict ,
temperature : temperature ,
top _k : top _k ,
top _p : top _p ,
repeat _penalty : repeat _penalty ,
repeat _last _n : repeat _last _n ,
callback : streamingCallback
} ) ;
const newCodes = this . extractCodeBlocks ( response ) ;
if ( newCodes . length === 0 ) break ;
if ( ! newCodes [ 0 ] . is _complete ) {
code += '\n' + newCodes [ 0 ] . content . split ( '\n' ) . slice ( 0 , - 1 ) . join ( '\n' ) ;
} else {
code += '\n' + newCodes [ 0 ] . content ;
}
}
} else {
code = codes [ 0 ] . content ;
}
return code ;
} else {
return null ;
}
}
2024-09-08 00:58:54 +00:00
async generateCodes ( prompt , images = [ ] , {
n _predict = null ,
stream = false ,
temperature = 0.1 ,
top _k = 50 ,
top _p = 0.95 ,
repeat _penalty = 0.8 ,
repeat _last _n = 40 ,
seed = null ,
n _threads = 8 ,
service _key = "" ,
streamingCallback = null
} = { } ) {
let response ;
const systemHeader = this . custom _message ( "Generation infos" ) ;
const codeInstructions = "Generated code must be put inside the adequate markdown code tag. Use this template:\n```language name\nCode\n```\n" ;
const fullPrompt = systemHeader + codeInstructions + this . separatorTemplate + prompt ;
if ( images . length > 0 ) {
response = await this . generate _with _images ( fullPrompt , images , {
n _predict ,
temperature ,
top _k ,
top _p ,
repeat _penalty ,
repeat _last _n ,
callback : streamingCallback
} ) ;
} else {
response = await this . generate ( fullPrompt , {
n _predict ,
temperature ,
top _k ,
top _p ,
repeat _penalty ,
repeat _last _n ,
callback : streamingCallback
} ) ;
}
let codes = this . extractCodeBlocks ( response ) ;
let completeCodes = [ ] ;
while ( codes . length > 0 ) {
let currentCode = codes . shift ( ) ;
let codeContent = currentCode . content ;
while ( ! currentCode . is _complete ) {
console . warn ( "The AI did not finish the code, let's ask it to continue" ) ;
const continuePrompt = prompt + codeContent + this . userFullHeader + "continue the code. Rewrite last line and continue the code." + this . separatorTemplate + this . aiFullHeader ;
response = await this . generate ( continuePrompt , {
n _predict ,
temperature ,
top _k ,
top _p ,
repeat _penalty ,
repeat _last _n ,
callback : streamingCallback
} ) ;
const newCodes = this . extractCodeBlocks ( response ) ;
if ( newCodes . length === 0 ) break ;
// Append the content of the first new code block
codeContent += '\n' + newCodes [ 0 ] . content ;
currentCode = newCodes [ 0 ] ;
// If there are more code blocks, add them to the codes array
if ( newCodes . length > 1 ) {
codes = [ ... newCodes . slice ( 1 ) , ... codes ] ;
}
}
completeCodes . push ( {
language : currentCode . language ,
content : codeContent
} ) ;
}
return completeCodes ;
}
2024-09-04 23:54:52 +00:00
extractCodeBlocks ( text ) {
const codeBlocks = [ ] ;
let remaining = text ;
let blocIndex = 0 ;
let firstIndex = 0 ;
const indices = [ ] ;
// Find all code block delimiters
while ( remaining . length > 0 ) {
const index = remaining . indexOf ( "```" ) ;
if ( index === - 1 ) {
if ( blocIndex % 2 === 1 ) {
indices . push ( remaining . length + firstIndex ) ;
}
break ;
}
indices . push ( index + firstIndex ) ;
remaining = remaining . slice ( index + 3 ) ;
firstIndex += index + 3 ;
blocIndex ++ ;
}
let isStart = true ;
for ( let i = 0 ; i < indices . length ; i ++ ) {
if ( isStart ) {
const blockInfo = {
index : i ,
file _name : "" ,
section : "" ,
content : "" ,
type : "" ,
is _complete : false
} ;
// Check for file name in preceding line
const precedingText = text . slice ( 0 , indices [ i ] ) . trim ( ) . split ( '\n' ) ;
if ( precedingText . length > 0 ) {
const lastLine = precedingText [ precedingText . length - 1 ] . trim ( ) ;
if ( lastLine . startsWith ( "<file_name>" ) && lastLine . endsWith ( "</file_name>" ) ) {
blockInfo . file _name = lastLine . slice ( "<file_name>" . length , - "</file_name>" . length ) . trim ( ) ;
} else if ( lastLine . startsWith ( "## filename:" ) ) {
blockInfo . file _name = lastLine . slice ( "## filename:" . length ) . trim ( ) ;
}
if ( lastLine . startsWith ( "<section>" ) && lastLine . endsWith ( "</section>" ) ) {
blockInfo . section = lastLine . slice ( "<section>" . length , - "</section>" . length ) . trim ( ) ;
}
}
const subText = text . slice ( indices [ i ] + 3 ) ;
if ( subText . length > 0 ) {
const findSpace = subText . indexOf ( " " ) ;
const findReturn = subText . indexOf ( "\n" ) ;
let nextIndex = Math . min ( findSpace === - 1 ? Infinity : findSpace , findReturn === - 1 ? Infinity : findReturn ) ;
if ( subText . slice ( 0 , nextIndex ) . includes ( '{' ) ) {
nextIndex = 0 ;
}
const startPos = nextIndex ;
if ( text [ indices [ i ] + 3 ] === "\n" || text [ indices [ i ] + 3 ] === " " || text [ indices [ i ] + 3 ] === "\t" ) {
blockInfo . type = 'language-specific' ;
} else {
blockInfo . type = subText . slice ( 0 , nextIndex ) ;
}
if ( i + 1 < indices . length ) {
const nextPos = indices [ i + 1 ] - indices [ i ] ;
if ( nextPos - 3 < subText . length && subText [ nextPos - 3 ] === "`" ) {
blockInfo . content = subText . slice ( startPos , nextPos - 3 ) . trim ( ) ;
blockInfo . is _complete = true ;
} else {
blockInfo . content = subText . slice ( startPos , nextPos ) . trim ( ) ;
blockInfo . is _complete = false ;
}
} else {
blockInfo . content = subText . slice ( startPos ) . trim ( ) ;
blockInfo . is _complete = false ;
}
codeBlocks . push ( blockInfo ) ;
}
isStart = false ;
} else {
isStart = true ;
}
}
return codeBlocks ;
}
2024-07-29 22:02:24 +00:00
async listMountedPersonalities ( host _address = this . host _address ) {
const url = ` ${ host _address } /list_mounted_personalities ` ;
try {
const response = await fetch ( url ) ;
return await response . json ( ) ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
async listModels ( host _address = this . host _address ) {
const url = ` ${ host _address } /list_models ` ;
try {
const response = await fetch ( url ) ;
return await response . json ( ) ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
}
2024-09-12 08:57:10 +00:00
class TextChunker {
constructor ( chunkSize = 512 , overlap = 0 , tokenizer = null , model = null ) {
this . chunkSize = chunkSize ;
this . overlap = overlap ;
this . tokenizer = tokenizer || new TikTokenTokenizer ( ) ;
this . model = model ;
}
getTextChunks ( text , doc , cleanChunk = true , minNbTokensInChunk = 10 ) {
const paragraphs = text . split ( '\n\n' ) ;
const chunks = [ ] ;
let currentChunk = [ ] ;
let currentTokens = 0 ;
let chunkId = 0 ;
for ( const paragraph of paragraphs ) {
const cleanedParagraph = cleanChunk ? paragraph . trim ( ) : paragraph ;
const paragraphTokens = this . tokenizer . tokenize ( cleanedParagraph ) . length ;
if ( currentTokens + paragraphTokens > this . chunkSize ) {
if ( currentTokens > minNbTokensInChunk ) {
let chunkText = currentChunk . join ( '\n\n' ) ;
if ( cleanChunk ) {
chunkText = TextChunker . removeUnnecessaryReturns ( chunkText ) ;
}
const chunk = new Chunk ( doc , '' , chunkText , currentTokens , chunkId ) ;
chunkId ++ ;
chunks . push ( chunk ) ;
}
if ( this . overlap > 0 ) {
currentChunk = [ ... currentChunk . slice ( - this . overlap ) , cleanedParagraph ] ;
} else {
currentChunk = [ cleanedParagraph ] ;
}
currentTokens = currentChunk . reduce ( ( sum , p ) => sum + this . tokenizer . tokenize ( p ) . length , 0 ) ;
} else {
currentChunk . push ( cleanedParagraph ) ;
currentTokens += paragraphTokens ;
}
}
if ( currentChunk . length > 0 && currentTokens > minNbTokensInChunk ) {
let chunkText = currentChunk . join ( '\n\n' ) ;
if ( cleanChunk ) {
chunkText = TextChunker . removeUnnecessaryReturns ( chunkText ) ;
}
const chunk = new Chunk ( doc , '' , chunkText , currentTokens , chunkId ) ;
chunks . push ( chunk ) ;
}
return chunks ;
}
static removeUnnecessaryReturns ( paragraph ) {
const lines = paragraph . split ( '\n' ) ;
return lines . filter ( line => line . trim ( ) ) . join ( '\n' ) ;
}
static chunkText ( text , tokenizer , chunkSize = 512 , overlap = 0 , cleanChunk = true , minNbTokensInChunk = 10 ) {
const paragraphs = text . split ( '\n\n' ) ;
const chunks = [ ] ;
let currentChunk = [ ] ;
let currentTokens = 0 ;
for ( const paragraph of paragraphs ) {
const cleanedParagraph = cleanChunk ? paragraph . trim ( ) : paragraph ;
const paragraphTokens = tokenizer . tokenize ( cleanedParagraph ) . length ;
if ( currentTokens + paragraphTokens > chunkSize ) {
if ( currentTokens > minNbTokensInChunk ) {
let chunkText = currentChunk . join ( '\n\n' ) ;
if ( cleanChunk ) {
chunkText = TextChunker . removeUnnecessaryReturns ( chunkText ) ;
}
chunks . push ( chunkText ) ;
}
if ( overlap > 0 ) {
currentChunk = [ ... currentChunk . slice ( - overlap ) , cleanedParagraph ] ;
} else {
currentChunk = [ cleanedParagraph ] ;
}
currentTokens = currentChunk . reduce ( ( sum , p ) => sum + tokenizer . tokenize ( p ) . length , 0 ) ;
} else {
currentChunk . push ( cleanedParagraph ) ;
currentTokens += paragraphTokens ;
}
}
if ( currentChunk . length > 0 && currentTokens > minNbTokensInChunk ) {
let chunkText = currentChunk . join ( '\n\n' ) ;
if ( cleanChunk ) {
chunkText = TextChunker . removeUnnecessaryReturns ( chunkText ) ;
}
chunks . push ( chunkText ) ;
}
return chunks ;
}
}
2024-07-29 22:02:24 +00:00
class TasksLibrary {
constructor ( lollms ) {
this . lollms = lollms ;
}
async translateTextChunk ( textChunk , outputLanguage = "french" , host _address = null , model _name = null , temperature = 0.1 , maxGenerationSize = 3000 ) {
const translationPrompt = [
` !@>system: ` ,
` Translate the following text to ${ outputLanguage } . ` ,
` Be faithful to the original text and do not add or remove any information. ` ,
` Respond only with the translated text. ` ,
` Do not add comments or explanations. ` ,
` !@>text to translate: ` ,
` ${ textChunk } ` ,
` !@>translation: ` ,
] . join ( "\n" ) ;
const translated = await this . lollms . generateText (
translationPrompt ,
host _address ,
model _name ,
- 1 , // personality
maxGenerationSize , // n_predict
false , // stream
temperature , // temperature
undefined , // top_k, using undefined to fallback on LollmsClient's default
undefined , // top_p, using undefined to fallback on LollmsClient's default
undefined , // repeat_penalty, using undefined to fallback on LollmsClient's default
undefined , // repeat_last_n, using undefined to fallback on LollmsClient's default
undefined , // seed, using undefined to fallback on LollmsClient's default
undefined , // n_threads, using undefined to fallback on LollmsClient's default
undefined // service_key, using undefined to fallback on LollmsClient's default
) ;
return translated ;
}
2024-08-29 22:57:14 +00:00
async tokenize ( text ) {
// Assuming the LollmsClient has a method to tokenize text
return await this . lollms . tokenize ( text ) ;
}
2024-09-04 12:16:41 +00:00
async summarizeText (
2024-08-29 22:57:14 +00:00
text ,
summaryInstruction = "summarize" ,
docName = "chunk" ,
answerStart = "" ,
maxGenerationSize = 3000 ,
maxSummarySize = 512 ,
callback = null ,
chunkSummaryPostProcessing = null ,
summaryMode = "SEQUENTIAL"
2024-09-04 12:16:41 +00:00
) {
console . log ( "Tokenizing:" )
console . log ( text )
2024-08-29 22:57:14 +00:00
let tk = await this . tokenize ( text ) ;
let prevLen = tk . length ;
let documentChunks = null ;
while ( tk . length > maxSummarySize && ( documentChunks === null || documentChunks . length > 1 ) ) {
this . stepStart ( ` Compressing ${ docName } ... ` ) ;
let chunkSize = Math . floor ( this . lollms . ctxSize * 0.6 ) ;
documentChunks = TextChunker . chunkText ( text , this . lollms , chunkSize , 0 , true ) ;
text = await this . summarizeChunks ( {
chunks : documentChunks ,
summaryInstruction ,
docName ,
answerStart ,
maxGenerationSize ,
callback ,
chunkSummaryPostProcessing ,
summaryMode
} ) ;
tk = await this . tokenize ( text ) ;
let dtkLn = prevLen - tk . length ;
prevLen = tk . length ;
this . step ( ` Current text size: ${ prevLen } , max summary size: ${ maxSummarySize } ` ) ;
this . stepEnd ( ` Compressing ${ docName } ... ` ) ;
if ( dtkLn <= 10 ) break ; // it is not summarizing
}
return text ;
}
async smartDataExtraction ( {
text ,
dataExtractionInstruction = "summarize the current chunk." ,
finalTaskInstruction = "reformulate with better wording" ,
docName = "chunk" ,
answerStart = "" ,
maxGenerationSize = 3000 ,
maxSummarySize = 512 ,
callback = null ,
chunkSummaryPostProcessing = null ,
summaryMode = "SEQUENTIAL"
} ) {
let tk = await this . tokenize ( text ) ;
let prevLen = tk . length ;
while ( tk . length > maxSummarySize ) {
let chunkSize = Math . floor ( this . lollms . ctxSize * 0.6 ) ;
let documentChunks = TextChunker . chunkText ( text , this . lollms , chunkSize , 0 , true ) ;
text = await this . summarizeChunks ( {
chunks : documentChunks ,
summaryInstruction : dataExtractionInstruction ,
docName ,
answerStart ,
maxGenerationSize ,
callback ,
chunkSummaryPostProcessing ,
summaryMode
} ) ;
tk = await this . tokenize ( text ) ;
let dtkLn = prevLen - tk . length ;
prevLen = tk . length ;
this . step ( ` Current text size: ${ prevLen } , max summary size: ${ maxSummarySize } ` ) ;
if ( dtkLn <= 10 ) break ; // it is not summarizing
}
this . stepStart ( "Rewriting ..." ) ;
text = await this . summarizeChunks ( {
chunks : [ text ] ,
summaryInstruction : finalTaskInstruction ,
docName ,
answerStart ,
maxGenerationSize ,
callback ,
chunkSummaryPostProcessing
} ) ;
this . stepEnd ( "Rewriting ..." ) ;
return text ;
}
async summarizeChunks ( {
chunks ,
summaryInstruction = "summarize the current chunk." ,
docName = "chunk" ,
answerStart = "" ,
maxGenerationSize = 3000 ,
callback = null ,
chunkSummaryPostProcessing = null ,
summaryMode = "SEQUENTIAL"
} ) {
if ( summaryMode === "SEQUENTIAL" ) {
let summary = "" ;
for ( let i = 0 ; i < chunks . length ; i ++ ) {
this . stepStart ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
summary = ` ${ answerStart } ` + await this . fastGen (
[
this . lollms . system _message ( ) ,
` ${ summary } ` ,
this . lollms . system _message ( ) ,
` ${ chunks [ i ] } ` ,
this . lollms . system _message ( ) ,
summaryInstruction ,
"Keep only information relevant to the context" ,
"The output must keep information from the previous chunk analysis and add the current chunk extracted information." ,
"Be precise and do not invent information that does not exist in the previous chunks analysis or the current chunk." ,
"Do not add any extra comments." ,
this . lollms . system _message ( ) + answerStart
] . join ( "\n" ) ,
maxGenerationSize ,
callback
) ;
if ( chunkSummaryPostProcessing ) {
summary = chunkSummaryPostProcessing ( summary ) ;
}
this . stepEnd ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
}
return summary ;
} else {
let summaries = [ ] ;
for ( let i = 0 ; i < chunks . length ; i ++ ) {
this . stepStart ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
let summary = ` ${ answerStart } ` + await this . fastGen (
[
` ${ this . lollms . system _message ( ) } Document_chunk [ ${ docName } ] ${ this . lollms . system _message ( ) } ` ,
` ${ chunks [ i ] } ` ,
` ${ this . lollms . system _message ( ) } ${ summaryInstruction } ` ,
"Answer directly with the summary with no extra comments." ,
` ${ this . lollms . system _message ( ) } summary ${ this . lollms . system _message ( ) } ` ,
` ${ answerStart } `
] . join ( "\n" ) ,
maxGenerationSize ,
callback
) ;
if ( chunkSummaryPostProcessing ) {
summary = chunkSummaryPostProcessing ( summary ) ;
}
summaries . push ( summary ) ;
this . stepEnd ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
}
return summaries . join ( "\n" ) ;
}
}
async sequentialChunksSummary ( {
chunks ,
summaryInstruction = "summarize" ,
docName = "chunk" ,
answerStart = "" ,
maxGenerationSize = 3000 ,
callback = null ,
chunkSummaryPostProcessing = null
} ) {
let summaries = [ ] ;
for ( let i = 0 ; i < chunks . length ; i ++ ) {
let chunk1 = i < chunks . length - 1 ? chunks [ i + 1 ] : "" ;
let chunk = i > 0 ? summaries [ summaries . length - 1 ] : chunks [ i ] ;
this . stepStart ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
let summary = ` ${ answerStart } ` + await this . fastGen (
[
` ${ this . lollms . system _message ( ) } Document_chunk: ${ docName } ${ this . lollms . system _message ( ) } ` ,
"Block1:" ,
` ${ chunk } ` ,
"Block2:" ,
` ${ chunk1 } ` ,
` ${ this . lollms . system _message ( ) } ${ summaryInstruction } ` ,
"Answer directly with the summary with no extra comments." ,
` ${ this . lollms . system _message ( ) } summary ${ this . lollms . system _message ( ) } ` ,
` ${ answerStart } `
] . join ( "\n" ) ,
maxGenerationSize ,
callback
) ;
if ( chunkSummaryPostProcessing ) {
summary = chunkSummaryPostProcessing ( summary ) ;
}
summaries . push ( summary ) ;
this . stepEnd ( ` Summary of ${ docName } - Processing chunk: ${ i + 1 } / ${ chunks . length } ` ) ;
}
return summaries . join ( "\n" ) ;
}
2024-09-12 09:47:39 +00:00
// Placeholder methods for step stepStart, stepEnd, fastGen
step ( message ) {
console . log ( message ) ;
}
2024-08-29 22:57:14 +00:00
stepStart ( message ) {
console . log ( message ) ;
}
stepEnd ( message ) {
console . log ( message ) ;
}
2024-07-29 22:02:24 +00:00
2024-08-29 22:57:14 +00:00
async fastGen ( prompt , maxGenerationSize , callback ) {
// Use the LollmsClient to generate text
const response = await this . lollms . generateText ( prompt ) ;
if ( callback ) callback ( response ) ;
return response ;
2024-08-03 22:07:41 +00:00
}
2024-08-29 22:57:14 +00:00
async yesNo ( question , context = "" , maxAnswerLength = 50 , conditioning = "" ) {
2024-08-03 22:07:41 +00:00
/ * *
* Analyzes the user prompt and answers whether it is asking to generate an image .
*
* @ param { string } question - The user ' s message .
* @ param { string } context - The context for the question .
* @ param { number } maxAnswerLength - The maximum length of the generated answer .
* @ param { string } conditioning - An optional system message to put at the beginning of the prompt .
* @ returns { boolean } True if the user prompt is asking to generate an image , False otherwise .
* /
return this . multichoiceQuestion ( question , [ "no" , "yes" ] , context , maxAnswerLength , conditioning ) > 0 ;
}
2024-08-29 22:57:14 +00:00
printPrompt ( prompt ) {
console . log ( prompt ) ;
}
async multichoiceQuestion ( question , possibleAnswers , context = "" , maxAnswerLength = 50 , conditioning = "" ) {
2024-08-03 22:07:41 +00:00
/ * *
* Interprets a multi - choice question from a user ' s response . This function expects only one choice as true .
* All other choices are considered false . If none are correct , returns - 1.
*
* @ param { string } question - The multi - choice question posed by the user .
* @ param { Array } possibleAnswers - A list containing all valid options for the chosen value .
* @ param { string } context - The context for the question .
* @ param { number } maxAnswerLength - Maximum string length allowed while interpreting the users ' responses .
* @ param { string } conditioning - An optional system message to put at the beginning of the prompt .
* @ returns { number } Index of the selected option within the possibleAnswers list . Or - 1 if there was no match found among any of them .
* /
const choices = possibleAnswers . map ( ( answer , index ) => ` ${ index } . ${ answer } ` ) . join ( "\n" ) ;
const elements = conditioning ? [ conditioning ] : [ ] ;
elements . push (
2024-08-29 22:57:14 +00:00
this . lollms . system _message ( ) ,
2024-08-03 22:07:41 +00:00
"Answer this multi choices question." ,
"Answer with an id from the possible answers." ,
"Do not answer with an id outside this possible answers."
) ;
if ( context ) {
elements . push (
2024-08-29 22:57:14 +00:00
this . lollms . custom _message ( "context" ) ,
2024-08-03 22:07:41 +00:00
context
) ;
}
elements . push (
2024-08-29 22:57:14 +00:00
this . lollms . custom _message ( ` question ` ) + ` $ ${ question } ` ,
this . lollms . custom _message ( ` possible answers ` ) ,
2024-08-03 22:07:41 +00:00
choices ,
2024-08-29 22:57:14 +00:00
this . lollms . custom _message ( "answer" )
2024-08-03 22:07:41 +00:00
) ;
const prompt = this . buildPrompt ( elements ) ;
2024-08-29 22:57:14 +00:00
let gen = await this . lollms . generate ( prompt , {
2024-08-03 22:07:41 +00:00
n _predict : maxAnswerLength ,
temperature : 0.1 ,
top _k : 50 ,
top _p : 0.9 ,
repeat _penalty : 1.0 ,
repeat _last _n : 50 ,
callback : this . sink
2024-08-29 22:57:14 +00:00
} )
gen = gen . trim ( ) . replace ( "" , "" ) . replace ( "" , "" ) ;
2024-08-03 22:07:41 +00:00
const selection = gen . trim ( ) . split ( " " ) [ 0 ] . replace ( "," , "" ) . replace ( "." , "" ) ;
this . printPrompt ( "Multi choice selection" , prompt + gen ) ;
try {
return parseInt ( selection , 10 ) ;
} catch ( error ) {
console . log ( "Model failed to answer the question" ) ;
return - 1 ;
}
}
buildPrompt ( promptParts , sacrificeId = - 1 , contextSize = null , minimumSpareContextSize = null ) {
/ * *
* Builds the prompt for code generation .
*
* @ param { Array < string > } promptParts - A list of strings representing the parts of the prompt .
* @ param { number } sacrificeId - The ID of the part to sacrifice .
* @ param { number } contextSize - The size of the context .
* @ param { number } minimumSpareContextSize - The minimum spare context size .
* @ returns { string } - The built prompt .
* /
if ( contextSize === null ) {
2024-08-29 22:57:14 +00:00
contextSize = this . lollms . ctxSize ;
2024-08-03 22:07:41 +00:00
}
if ( minimumSpareContextSize === null ) {
2024-08-29 22:57:14 +00:00
minimumSpareContextSize = this . lollms . minNPredict ;
2024-08-03 22:07:41 +00:00
}
if ( sacrificeId === - 1 || promptParts [ sacrificeId ] . length < 50 ) {
return promptParts . filter ( s => s !== "" ) . join ( "\n" ) ;
} else {
const partTokens = [ ] ;
let nbTokens = 0 ;
for ( let i = 0 ; i < promptParts . length ; i ++ ) {
const part = promptParts [ i ] ;
const tk = this . lollms . tokenize ( part ) ;
partTokens . push ( tk ) ;
if ( i !== sacrificeId ) {
nbTokens += tk . length ;
}
}
let sacrificeText = "" ;
if ( partTokens [ sacrificeId ] . length > 0 ) {
const sacrificeTk = partTokens [ sacrificeId ] ;
const tokensToKeep = sacrificeTk . slice ( - ( contextSize - nbTokens - minimumSpareContextSize ) ) ;
sacrificeText = this . lollms . detokenize ( tokensToKeep ) ;
}
promptParts [ sacrificeId ] = sacrificeText ;
return promptParts . filter ( s => s !== "" ) . join ( "\n" ) ;
}
}
extractCodeBlocks ( text ) {
2024-08-21 23:17:29 +00:00
const codeBlocks = [ ] ;
2024-09-04 23:54:52 +00:00
let remaining = text ;
let blocIndex = 0 ;
let firstIndex = 0 ;
const indices = [ ] ;
// Find all code block delimiters
while ( remaining . length > 0 ) {
const index = remaining . indexOf ( "```" ) ;
if ( index === - 1 ) {
if ( blocIndex % 2 === 1 ) {
indices . push ( remaining . length + firstIndex ) ;
}
break ;
2024-08-21 23:17:29 +00:00
}
2024-09-04 23:54:52 +00:00
indices . push ( index + firstIndex ) ;
remaining = remaining . slice ( index + 3 ) ;
firstIndex += index + 3 ;
blocIndex ++ ;
}
2024-08-03 22:07:41 +00:00
2024-09-04 23:54:52 +00:00
let isStart = true ;
for ( let i = 0 ; i < indices . length ; i ++ ) {
if ( isStart ) {
const blockInfo = {
index : i ,
file _name : "" ,
section : "" ,
content : "" ,
type : "" ,
is _complete : false
} ;
// Check for file name in preceding line
const precedingText = text . slice ( 0 , indices [ i ] ) . trim ( ) . split ( '\n' ) ;
if ( precedingText . length > 0 ) {
const lastLine = precedingText [ precedingText . length - 1 ] . trim ( ) ;
if ( lastLine . startsWith ( "<file_name>" ) && lastLine . endsWith ( "</file_name>" ) ) {
blockInfo . file _name = lastLine . slice ( "<file_name>" . length , - "</file_name>" . length ) . trim ( ) ;
} else if ( lastLine . startsWith ( "## filename:" ) ) {
blockInfo . file _name = lastLine . slice ( "## filename:" . length ) . trim ( ) ;
}
if ( lastLine . startsWith ( "<section>" ) && lastLine . endsWith ( "</section>" ) ) {
blockInfo . section = lastLine . slice ( "<section>" . length , - "</section>" . length ) . trim ( ) ;
}
}
const subText = text . slice ( indices [ i ] + 3 ) ;
if ( subText . length > 0 ) {
const findSpace = subText . indexOf ( " " ) ;
const findReturn = subText . indexOf ( "\n" ) ;
let nextIndex = Math . min ( findSpace === - 1 ? Infinity : findSpace , findReturn === - 1 ? Infinity : findReturn ) ;
if ( subText . slice ( 0 , nextIndex ) . includes ( '{' ) ) {
nextIndex = 0 ;
}
const startPos = nextIndex ;
if ( text [ indices [ i ] + 3 ] === "\n" || text [ indices [ i ] + 3 ] === " " || text [ indices [ i ] + 3 ] === "\t" ) {
blockInfo . type = 'language-specific' ;
} else {
blockInfo . type = subText . slice ( 0 , nextIndex ) ;
}
if ( i + 1 < indices . length ) {
const nextPos = indices [ i + 1 ] - indices [ i ] ;
if ( nextPos - 3 < subText . length && subText [ nextPos - 3 ] === "`" ) {
blockInfo . content = subText . slice ( startPos , nextPos - 3 ) . trim ( ) ;
blockInfo . is _complete = true ;
} else {
blockInfo . content = subText . slice ( startPos , nextPos ) . trim ( ) ;
blockInfo . is _complete = false ;
}
} else {
blockInfo . content = subText . slice ( startPos ) . trim ( ) ;
blockInfo . is _complete = false ;
}
codeBlocks . push ( blockInfo ) ;
}
isStart = false ;
} else {
isStart = true ;
}
2024-08-03 22:07:41 +00:00
}
return codeBlocks ;
}
2024-08-21 23:17:29 +00:00
2024-09-04 23:54:52 +00:00
2024-08-08 19:17:24 +00:00
/ * *
* Updates the given code based on the provided query string .
* The query string can contain two types of modifications :
* 1. FULL _REWRITE : Completely replaces the original code with the new code .
* 2. REPLACE : Replaces specific code snippets within the original code .
*
* @ param { string } originalCode - The original code to be updated .
* @ param { string } queryString - The string containing the update instructions .
* @ returns { object } - An object with the following properties :
* - updatedCode : The updated code .
* - modifications : An array of objects representing the changes made , each with properties 'oldCode' and 'newCode' .
* - hasQuery : A boolean indicating whether the queryString contained any valid queries .
* /
updateCode ( originalCode , queryString ) {
const queries = queryString . split ( '# REPLACE\n' ) ;
let updatedCode = originalCode ;
const modifications = [ ] ;
// Check if there's a FULL_REWRITE first
const fullRewriteStart = queryString . indexOf ( '# FULL_REWRITE' ) ;
if ( fullRewriteStart !== - 1 ) {
const newCode = queryString . slice ( fullRewriteStart + 14 ) . trim ( ) ;
updatedCode = newCode ;
modifications . push ( {
oldCode : originalCode ,
newCode
} ) ;
return {
updatedCode ,
modifications ,
hasQuery : true
} ;
}
2024-08-03 22:07:41 +00:00
2024-08-08 19:17:24 +00:00
if ( queries . length === 1 && queries [ 0 ] . trim ( ) === '' ) {
console . log ( "No queries detected" ) ;
return {
updatedCode ,
modifications : [ ] ,
hasQuery : false
} ;
}
for ( const query of queries ) {
if ( query . trim ( ) === '' ) continue ;
const originalCodeStart = query . indexOf ( '# ORIGINAL\n' ) + 11 ;
const originalCodeEnd = query . indexOf ( '\n# SET\n' ) ;
2024-08-09 12:21:42 +00:00
let oldCode = query . slice ( originalCodeStart , originalCodeEnd ) ;
2024-08-06 09:47:49 +00:00
2024-08-08 19:17:24 +00:00
const newCodeStart = query . indexOf ( '# SET\n' ) + 6 ;
const newCode = query . slice ( newCodeStart ) ;
const modification = {
oldCode : oldCode . trim ( ) ,
newCode : newCode . trim ( )
} ;
2024-08-09 12:21:42 +00:00
if ( oldCode == "<old_code>" ) {
oldCode = originalCode
}
console . log ( "oldCode:" )
console . log ( oldCode )
console . log ( "newCode:" )
console . log ( newCode )
console . log ( "Before update" , updatedCode ) ;
if ( oldCode === updatedCode ) {
console . log ( "Changing the whole content" )
updatedCode = newCode
}
else {
updatedCode = updatedCode . replace ( oldCode , newCode . trim ( ) ) ;
}
console . log ( "After update" , updatedCode ) ;
2024-08-08 19:17:24 +00:00
modifications . push ( modification ) ;
}
return {
updatedCode ,
modifications ,
hasQuery : true
} ;
}
}
2024-08-06 09:47:49 +00:00
class LOLLMSRAGClient {
2024-08-29 22:57:14 +00:00
constructor ( lc ) {
this . lc = lc ;
this . key = lc . service _key || this . generateRandomKey ( ) ;
console . log ( "Connecting to server with key:" , this . key ) ;
2024-08-06 09:47:49 +00:00
}
2024-08-29 22:57:14 +00:00
generateRandomKey ( ) {
// Generate a random key (UUID v4)
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' . replace ( /[xy]/g , function ( c ) {
const r = Math . random ( ) * 16 | 0 , v = c === 'x' ? r : ( r & 0x3 | 0x8 ) ;
return v . toString ( 16 ) ;
} ) ;
}
2024-08-06 09:47:49 +00:00
2024-08-29 22:57:14 +00:00
async request ( endpoint , method = 'GET' , body = null ) {
try {
2024-08-06 09:47:49 +00:00
const options = {
2024-08-29 22:57:14 +00:00
method ,
headers : {
'Content-Type' : 'application/json' ,
} ,
body : body ? JSON . stringify ( body ) : null ,
2024-08-06 09:47:49 +00:00
} ;
2024-08-29 22:57:14 +00:00
const response = await fetch ( ` ${ this . lc . host _address } ${ endpoint } ` , options ) ;
const data = await response . json ( ) ;
2024-08-06 09:47:49 +00:00
if ( ! response . ok ) {
2024-08-29 22:57:14 +00:00
throw new Error ( ` Error: ${ data . detail || response . statusText } ` ) ;
2024-08-06 09:47:49 +00:00
}
2024-08-29 22:57:14 +00:00
return data ;
} catch ( error ) {
console . error ( "Request failed:" , error ) ;
throw new Error ( ` Error: ${ error . message } ` ) ;
}
2024-08-06 09:47:49 +00:00
}
async addDocument ( title , content , path = "unknown" ) {
2024-08-29 22:57:14 +00:00
const document = { title , content , path , key : this . key } ;
return this . request ( '/add_document' , 'POST' , document ) ;
2024-08-06 09:47:49 +00:00
}
async removeDocument ( documentId ) {
2024-08-29 22:57:14 +00:00
const body = { key : this . key } ;
return this . request ( ` /remove_document/ ${ documentId } ` , 'POST' , body ) ;
2024-08-06 09:47:49 +00:00
}
async indexDatabase ( ) {
2024-08-29 22:57:14 +00:00
const body = { key : this . key } ;
console . log ( "Sending request to index database with body:" , body ) ;
2024-08-06 09:47:49 +00:00
2024-08-29 22:57:14 +00:00
try {
const response = await this . request ( '/index_database' , 'POST' , body , {
'Content-Type' : 'application/json'
} ) ;
console . log ( "Index database response:" , response ) ;
return response ;
} catch ( error ) {
console . error ( "Error indexing database:" , error ) ;
throw error ;
}
}
2024-08-06 09:47:49 +00:00
async search ( query ) {
2024-08-29 22:57:14 +00:00
const searchQuery = { query , key : this . key } ;
return this . request ( '/search' , 'POST' , searchQuery ) ;
2024-08-06 09:47:49 +00:00
}
async wipeDatabase ( ) {
2024-08-29 22:57:14 +00:00
const body = { key : this . key } ;
return this . request ( '/wipe_database' , 'DELETE' , body ) ;
2024-08-06 09:47:49 +00:00
}
}
2024-08-29 22:57:14 +00:00
2024-08-06 09:47:49 +00:00
// Example usage:
// const ragClient = new RAGClient('http://localhost:8000', 'your_bearer_token');
// ragClient.addDocument('My Title', 'This is the content of the document.')
// .then(response => console.log(response))
// .catch(error => console.error(error));