2023-03-07 03:30:39 +00:00
#!/usr/bin/env node
//IRC bot that responds to !chat, queries the chatgpt api, and prints the response line by line
const irc = require ( 'irc' ) ;
const axios = require ( 'axios' ) ;
const config = require ( './config.json' ) ;
2023-03-08 02:41:08 +00:00
2023-03-09 05:03:43 +00:00
<< << << < HEAD
2023-03-08 02:41:08 +00:00
// context is a list of strings that are used to seed the chatgpt api and it's responses
class Context {
messages = [ ] ;
2023-03-08 04:04:18 +00:00
currentLine = '' ;
2023-03-08 02:41:08 +00:00
currentResponse = '' ;
2023-03-09 05:03:43 +00:00
=== === =
const seed _messages = [ { role : 'system' , content : 'You are a dungeon master, DMing a game for your friends. The setting is Left World, a dreamscape where not everything is as it seems. Let user introduce their characters and give them scenarios to interact with. Feel free to introduce NPCs, but do not make moves for the characters. If user introduces another character, keep the story going where you left off as if that new character just arrived.' } ] ;
// context is a list of strings that are used to seed the chatgpt api and it's responses
class Context {
currentResponse = '' ;
constructor ( messages = default _messages ) {
this . messages = messages ;
}
>>> >>> > 28 ab52a ( Refactor context out of chatgpt command )
2023-03-08 04:04:18 +00:00
add _user _prompt ( message ) {
2023-03-08 02:41:08 +00:00
this . messages . push ( { role : 'user' , content : message } ) ;
}
add _assistant _message ( message ) {
this . messages . push ( { role : 'assistant' , content : message } ) ;
}
2023-03-09 05:03:43 +00:00
end _line ( line ) {
this . currentResponse += ` ${ line } \n \n ` ;
return line ;
2023-03-08 02:41:08 +00:00
}
finish _current _response ( ) {
this . add _assistant _message ( this . currentResponse ) ;
2023-03-09 05:03:43 +00:00
const theLine = this . currentLine ;
2023-03-08 02:41:08 +00:00
this . currentResponse = '' ;
2023-03-09 05:03:43 +00:00
<< << << < HEAD
2023-03-08 04:04:18 +00:00
this . currentLine = '' ;
return theLine ;
2023-03-08 02:41:08 +00:00
}
2023-03-09 05:03:43 +00:00
=== === =
this . save _history ( ) ;
return theLine ;
2023-03-08 04:04:18 +00:00
}
2023-03-09 05:03:43 +00:00
save _history ( ) {
const prettyData = JSON . stringify ( this . messages , null , 2 ) ;
fs . writeFileSync ( './messages.json' , prettyData ) ;
}
>>> >>> > 28 ab52a ( Refactor context out of chatgpt command )
is _response _in _progress ( ) {
return this . currentResponse !== '' ;
2023-03-08 02:41:08 +00:00
}
clear ( ) {
this . messages = [ ] ;
this . currentResponse = '' ;
}
}
2023-03-09 05:03:43 +00:00
<< << << < HEAD
2023-03-08 02:41:08 +00:00
const context = new Context ( ) ;
2023-03-09 05:03:43 +00:00
=== === =
const savedMessages = require ( './messages.json' ) ;
>>> >>> > 28 ab52a ( Refactor context out of chatgpt command )
2023-03-08 02:41:08 +00:00
2023-03-07 03:30:39 +00:00
const client = new irc . Client ( config . server , config . nick , {
channels : config . channels ,
} ) ;
2023-03-09 05:03:43 +00:00
const context = new Context ( savedMessages ) ;
2023-03-07 03:30:39 +00:00
// listen for messages that start with !chat and call the chatgpt api with a callback that prints the response line by line
client . addListener ( 'message' , async ( from , to , message ) => {
2023-03-09 05:03:43 +00:00
<< << << < HEAD
2023-03-08 02:41:08 +00:00
is _chat _cmd = message . startsWith ( '!chat' ) ;
is _cont _cmd = message . startsWith ( '!cont' ) ;
if ( is _chat _cmd || is _cont _cmd ) {
if ( context . is _response _in _progress ( ) ) { return ; }
if ( is _chat _cmd ) {
context . clear ( ) ;
}
2023-03-09 05:03:43 +00:00
=== === =
let is _chat _cmd = message . startsWith ( '!chat' ) ;
let is _cont _cmd = message . startsWith ( '!cont' ) ;
if ( is _chat _cmd || is _cont _cmd ) {
if ( context . is _response _in _progress ( ) ) {
message ( ` (chat from ${ from } ignored, response in progress) ` )
return ;
}
context . add _user _prompt ( query ) ;
>>> >>> > 28 ab52a ( Refactor context out of chatgpt command )
2023-03-07 03:30:39 +00:00
const query = message . slice ( 6 ) ;
2023-03-09 05:03:43 +00:00
await chatgpt ( query , context . messages , handleChatGPTResponseLine ) ;
context . finish _current _response ( ) ;
}
function handleChatGPTResponseLine ( line ) {
context . end _line ( line ) ;
client . say ( to , line ) ;
2023-03-07 03:30:39 +00:00
}
} ) ;
// function that calls the chatgpt streaming api (with server send events) and calls the callback function for each line
2023-03-09 05:03:43 +00:00
function chatgpt ( query , messages , callback ) {
2023-03-07 03:30:39 +00:00
const apiUrl = 'https://api.openai.com/v1/chat/completions' ;
2023-03-09 05:03:43 +00:00
let currentLine = '' ;
return new Promise ( ( resolve , reject ) => {
axios . post ( apiUrl , {
messages : messages ,
model : 'gpt-3.5-turbo' ,
stream : true ,
} , {
headers : {
Authorization : ` Bearer ${ config . openaiApiKey } ` ,
'Content-Type' : 'application/json' ,
} ,
responseType : 'stream' ,
} )
. then ( response => {
response . data . on ( 'data' , ( event ) => {
let data = event . toString ( ) ;
let parts = data . split ( '\n' ) ;
// parse if starts with data:
for ( let part of parts ) {
console . log ( part ) ;
if ( part === 'data: [DONE]' ) {
callback ( currentLine ) ;
resolve ( ) ;
} else if ( part . startsWith ( 'data: ' ) ) {
let jsonString = part . slice ( part . indexOf ( '{' ) , part . lastIndexOf ( '}' ) + 1 ) ;
try {
let json = JSON . parse ( jsonString ) ;
let chunk = json . choices [ 0 ] . delta . content ;
if ( ! chunk ) {
continue ;
}
const lines = chunk . split ( /\r?\n/ ) ;
let hasStartNewline = chunk . startsWith ( "\n" ) ;
let hasEndNewline = chunk . endsWith ( "\n" ) ;
if ( hasStartNewline ) {
callback ( currentLine ) ;
currentLine = '' ;
}
for ( let i = 0 ; i < lines . length - 1 ; i ++ ) {
currentLine += lines [ i ] ;
callback ( currentLine ) ;
currentLine = '' ;
}
currentLine += lines [ lines . length - 1 ] ;
if ( hasEndNewline ) {
callback ( currentLine ) ;
currentLine = '' ;
}
if ( currentLine . length > 400 ) {
callback ( currentLine ) ;
currentLine = '' ;
}
} catch ( e ) {
console . log ( e ) ;
console . log ( part ) ;
}
}
2023-03-07 03:30:39 +00:00
}
2023-03-09 05:03:43 +00:00
} ) ;
} )
. catch ( error => {
if ( error . response ) {
console . log ( error . toJSON ( ) ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
2023-03-07 03:30:39 +00:00
}
2023-03-09 05:03:43 +00:00
reject ( error ) ;
} ) ;
2023-03-07 03:30:39 +00:00
} ) ;
2023-03-09 05:03:43 +00:00
}