Refactor context out of chatgpt command
parent
112e960135
commit
fbd27e01ce
229
index.js
229
index.js
|
@ -4,12 +4,26 @@ const irc = require('irc');
|
|||
const axios = require('axios');
|
||||
const config = require('./config.json');
|
||||
|
||||
<<<<<<< HEAD
|
||||
// context is a list of strings that are used to seed the chatgpt api and it's responses
|
||||
class Context {
|
||||
messages = [];
|
||||
currentLine = '';
|
||||
currentResponse = '';
|
||||
|
||||
=======
|
||||
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;
|
||||
}
|
||||
|
||||
>>>>>>> 28ab52a (Refactor context out of chatgpt command)
|
||||
add_user_prompt(message) {
|
||||
this.messages.push({ role: 'user', content: message });
|
||||
}
|
||||
|
@ -18,48 +32,55 @@ class Context {
|
|||
this.messages.push({ role: 'assistant', content: message });
|
||||
}
|
||||
|
||||
append_to_line(message) {
|
||||
this.currentLine += message;
|
||||
}
|
||||
|
||||
end_line() {
|
||||
const the_line = this.currentLine;
|
||||
this.currentResponse += `${the_line}\n\n`;
|
||||
this.currentLine = '';
|
||||
return the_line;
|
||||
end_line(line) {
|
||||
this.currentResponse += `${line}\n\n`;
|
||||
return line;
|
||||
}
|
||||
|
||||
finish_current_response() {
|
||||
this.add_assistant_message(this.currentResponse);
|
||||
const theLine = this.currentLine;
|
||||
const theLine = this.currentLine;
|
||||
this.currentResponse = '';
|
||||
<<<<<<< HEAD
|
||||
this.currentLine = '';
|
||||
return theLine;
|
||||
}
|
||||
|
||||
is_response_in_progress() {
|
||||
return this.currentResponse !== '' || this.currentLine !== '';
|
||||
=======
|
||||
this.save_history();
|
||||
return theLine;
|
||||
}
|
||||
|
||||
peek_line() {
|
||||
return this.currentLine;
|
||||
save_history() {
|
||||
const prettyData = JSON.stringify(this.messages, null, 2);
|
||||
fs.writeFileSync('./messages.json', prettyData);
|
||||
}
|
||||
|
||||
>>>>>>> 28ab52a (Refactor context out of chatgpt command)
|
||||
is_response_in_progress() {
|
||||
return this.currentResponse !== '';
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages = [];
|
||||
this.currentResponse = '';
|
||||
this.currentLine = '';
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
const context = new Context();
|
||||
=======
|
||||
const savedMessages = require('./messages.json');
|
||||
>>>>>>> 28ab52a (Refactor context out of chatgpt command)
|
||||
|
||||
const client = new irc.Client(config.server, config.nick, {
|
||||
channels: config.channels,
|
||||
});
|
||||
|
||||
const context = new Context(savedMessages);
|
||||
// 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) => {
|
||||
<<<<<<< HEAD
|
||||
is_chat_cmd = message.startsWith('!chat');
|
||||
is_cont_cmd = message.startsWith('!cont');
|
||||
if (is_chat_cmd || is_cont_cmd) {
|
||||
|
@ -67,94 +88,110 @@ client.addListener('message', async (from, to, message) => {
|
|||
if(is_chat_cmd) {
|
||||
context.clear();
|
||||
}
|
||||
=======
|
||||
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);
|
||||
>>>>>>> 28ab52a (Refactor context out of chatgpt command)
|
||||
const query = message.slice(6);
|
||||
chatgpt(query, (line) => {
|
||||
client.say(to, line);
|
||||
});
|
||||
await chatgpt(query, context.messages, handleChatGPTResponseLine);
|
||||
context.finish_current_response();
|
||||
}
|
||||
|
||||
function handleChatGPTResponseLine(line) {
|
||||
context.end_line(line);
|
||||
client.say(to, line);
|
||||
}
|
||||
});
|
||||
|
||||
// function that calls the chatgpt streaming api (with server send events) and calls the callback function for each line
|
||||
async function chatgpt(query, callback) {
|
||||
// a very primitive mutex to prevent multiple calls to the api at once
|
||||
if(context.is_response_in_progress()) { return; }
|
||||
context.add_user_prompt(query);
|
||||
function chatgpt(query, messages, callback) {
|
||||
const apiUrl = 'https://api.openai.com/v1/chat/completions';
|
||||
let response = null;
|
||||
try {
|
||||
response = await axios.post(apiUrl, {
|
||||
messages: context.messages,
|
||||
model: 'gpt-3.5-turbo',
|
||||
stream: true,
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.openaiApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseType: 'stream',
|
||||
});
|
||||
} catch(error) {
|
||||
if (error.response) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.log(error.toJSON());
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.log(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log('Error', error.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let currentLine = '';
|
||||
|
||||
response.data.on('data', (event) => {
|
||||
let data = event.toString();
|
||||
let parts = data.split('\n');
|
||||
// parse if starts with data:
|
||||
for(part of parts) {
|
||||
console.log(part);
|
||||
if(part === 'data: [DONE]') {
|
||||
callback(context.finish_current_response());
|
||||
} 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//split the chunk into lines leaving the delimiter in the array
|
||||
const lines = chunk.split(/\r?\n/); // split by new lines
|
||||
|
||||
let hasStartNewline = chunk.startsWith("\n");
|
||||
let hasEndNewline = chunk.endsWith("\n");
|
||||
|
||||
if(hasStartNewline) {
|
||||
callback(context.end_line())
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
context.append_to_line(lines[i]);
|
||||
callback(context.end_line());
|
||||
}
|
||||
|
||||
context.append_to_line(lines[lines.length - 1]);
|
||||
|
||||
if(hasEndNewline) {
|
||||
callback(context.end_line());
|
||||
}
|
||||
|
||||
if (context.peek_line().length > 400) {
|
||||
callback(context.end_line());
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log(part);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
console.log(error.toJSON());
|
||||
} else if (error.request) {
|
||||
console.log(error.request);
|
||||
} else {
|
||||
console.log('Error', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue