From d8e3b36d64a5f87a01f7f7e17e231c91592ceace Mon Sep 17 00:00:00 2001 From: powermaker450 Date: Tue, 23 Jul 2024 13:34:34 -0400 Subject: [PATCH 1/3] Initial implementation of the new message storage --- .env.example | 1 + .gitignore | 1 + src/bot.ts | 40 +++++++++++++++----------- src/types.ts | 49 ++++++++++++++++++++++++++++++- src/utils.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 143 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 825067c..d4ccc7b 100644 --- a/.env.example +++ b/.env.example @@ -34,3 +34,4 @@ ALLOWED_CHAT= SAFE_WORD= # When this character/string is detected anywhere in a message, the bot won't respond to it. Defaults to "\". + diff --git a/.gitignore b/.gitignore index 2f3a028..7a3cb51 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ TEST-results.xml /lib/ messages.json dist/ +.env diff --git a/src/bot.ts b/src/bot.ts index 5557812..faba129 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,7 +1,7 @@ -import { TailchatWsClient, stripMentionTag } from "tailchat-client-sdk"; +import { TailchatWsClient } from "tailchat-client-sdk"; import { OpenAI } from "openai"; import * as fs from "fs"; -import { ImageRequestData, ImageSize, Messages } from "./types"; +import { GuildData, ImageRequestData, ImageSize } from "./types"; import { checkFile, getUsername, @@ -20,7 +20,6 @@ import { createImageModel, temperature, key, - system, } from "./assistant"; import { ImagesResponse } from "openai/resources"; import chalk from "chalk"; @@ -28,9 +27,9 @@ import dotenv from "dotenv"; dotenv.config(); // Specific to Tailchat. The endpoint of my Tailchat server, the bot ID and Secret. -const HOST = process.env.HOST; -const APPID = process.env.ID; -const APPSECRET = process.env.SECRET; +export const HOST = process.env.HOST; +export const APPID = process.env.ID; +export const APPSECRET = process.env.SECRET; const allVarsFilled = HOST && APPID && APPSECRET; if (!allVarsFilled) { @@ -39,7 +38,7 @@ if (!allVarsFilled) { } // Define the initial system message for the LLM. -const session: Messages = checkFile("./messages.json", "utf-8", system.normal); +const session = new GuildData(checkFile("./messages.json", "utf-8")); console.log("Our conversation is:", session); const THINKING = "[md]`Thinking...`[/md]"; @@ -117,10 +116,13 @@ client.connect().then(async () => { content: "[md]`Analyzing image...`[/md]", }); - session.push(formatImageMessage(username, imageData)); + session.appendMessage( + formatImageMessage(username, imageData), + message.converseId, + ); const response = await assistant.chat.completions.create({ - messages: session, + messages: session.getHistory(message.converseId), model: imageModel, temperature: temperature, }); @@ -129,7 +131,7 @@ client.connect().then(async () => { "", ); - session.push(messageOf(response)); + session.appendMessage(messageOf(response), message.converseId); await client.sendMessage({ converseId: message.converseId, @@ -149,15 +151,18 @@ client.connect().then(async () => { content: THINKING, }); - session.push(formatUserMessage(username, message.content)); + session.appendMessage( + formatUserMessage(username, message.content), + message.converseId, + ); const response = await assistant.chat.completions.create({ - messages: session, + messages: session.getHistory(message.converseId), model: textModel, temperature: temperature, }); - session.push(messageOf(response)); + session.appendMessage(messageOf(response), message.converseId); await client.sendMessage({ converseId: message.converseId, @@ -187,15 +192,18 @@ client.connect().then(async () => { content: THINKING, }); - session.push(formatUserMessage(username, message.content)); + session.appendMessage( + formatUserMessage(username, message.content), + message.converseId, + ); const response = await assistant.chat.completions.create({ - messages: session, + messages: session.getHistory(message.converseId), model: textModel, temperature: temperature, }); - session.push(messageOf(response)); + session.appendMessage(messageOf(response), message.converseId); await client.sendMessage({ converseId: message.converseId, diff --git a/src/types.ts b/src/types.ts index 6b15ac3..c5ac7f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,10 @@ import { ChatCompletionSystemMessageParam, } from "openai/resources"; import { TailchatWsClient } from "tailchat-client-sdk"; +import { ChatMessage } from "tailchat-types"; +import { formatNewHistory, formatUserMessage, getUsername } from "./utils"; +import { HOST } from "./bot"; +import chalk from "chalk"; export interface ImageMessage { role: "user"; @@ -27,6 +31,8 @@ export type AnyChatCompletion = | ChatCompletionUserMessageParam | ChatCompletionSystemMessageParam; +export type AnyChatCompletionRole = "user" | "system" | "assistant"; + export enum ImageSize { Small = "256x256", Medium = "512x512", @@ -82,4 +88,45 @@ export type Temperature = | 1.9 | 2.0; -export type Messages = Array; +export type Messages = (AnyChatCompletion | ImageMessage)[]; + +export interface ChatHistoryData { + id: string; + history: Messages; +} + +export type ChatHistory = ChatHistoryData[]; + +export class GuildData { + data: ChatHistory; + + constructor(existingData?: ChatHistory) { + this.data = existingData || []; + } + + appendMessage( + message: AnyChatCompletion | ImageMessage, + converseId: string, + ): void { + for (const converse of this.data) { + if (converseId === converse.id) { + converse.history.push(message); + return; + } + } + + this.data.push(formatNewHistory(message, converseId)); + } + + getHistory(converseId: string): Messages { + for (const converse of this.data) { + if (converseId === converse.id) { + return converse.history; + } + } + + throw new Error( + `No history was found with the given converse id: ${chalk.green(converseId)}. Something isn't right.`, + ); + } +} diff --git a/src/utils.ts b/src/utils.ts index 48acd4b..f6e25a5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,28 +1,68 @@ import * as fs from "fs"; -import { AnyChatCompletion, ImageMessage, Messages } from "./types"; +import { + AnyChatCompletion, + AnyChatCompletionRole, + ChatHistory, + ChatHistoryData, + ImageMessage, +} from "./types"; +import { system } from "./assistant"; import { ChatCompletion, ChatCompletionMessage } from "openai/resources"; import { stripMentionTag } from "tailchat-client-sdk"; +import { ChatMessage } from "tailchat-types"; +import { HOST } from "./bot"; +import chalk from "chalk"; + +//export function checkFile( +// file: string, +// encoding: fs.EncodingOption, +// defaultContent: string, +//): Messages { +// let final: Messages; +// const generic: Messages = [ +// { +// role: "system", +// content: defaultContent, +// }, +// ]; +// +// if (fs.existsSync(file)) { +// const data = fs.readFileSync(file, encoding); +// +// final = !data.toString().trim() ? generic : JSON.parse(data.toString()); +// } else { +// fs.createWriteStream(file); +// final = generic; +// } +// +// return final; +//} export function checkFile( file: string, encoding: fs.EncodingOption, - defaultContent: string, -): Messages { - let final: Messages; - const generic: Messages = [ - { - role: "system", - content: defaultContent, - }, - ]; +): ChatHistory { + let final: ChatHistory; if (fs.existsSync(file)) { const data = fs.readFileSync(file, encoding); - final = !data.toString().trim() ? generic : JSON.parse(data.toString()); + final = !data.toString().trim() ? [] : JSON.parse(data.toString()); + + // @ts-ignore + if (final.at(0).role) { + console.warn( + chalk.yellow( + "Your persistent storage uses the old data structure for persistent messages. These messages will be moved to a backup file and the existing file will be overwritten.", + ), + ); + fs.writeFileSync(`${file}.bak`, data); + + final = []; + } } else { fs.createWriteStream(file); - final = generic; + final = []; } return final; @@ -59,6 +99,23 @@ export function formatImageMessage( }; } +export function formatNewHistory( + message: AnyChatCompletion | ImageMessage, + converseId: string, + customSystemMessage?: string, +): ChatHistoryData { + return { + id: converseId, + history: [ + { + role: "system", + content: customSystemMessage || system.normal, + }, + message, + ], + }; +} + export function messageOf(response: ChatCompletion): ChatCompletionMessage { return response.choices.at(0)!.message; } -- 2.43.0 From b257da540efc462a4b2d41263c4949d5f889357e Mon Sep 17 00:00:00 2001 From: powermaker450 Date: Tue, 23 Jul 2024 18:10:02 -0400 Subject: [PATCH 2/3] Fix incorrect writing of messages to storage --- src/bot.ts | 18 +++++++++++++++--- src/utils.ts | 22 ++++++++++++---------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/bot.ts b/src/bot.ts index faba129..4ddced6 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -140,7 +140,11 @@ client.connect().then(async () => { content: `[md]${contentOf(response)}[/md]`, }); - fs.writeFileSync("./messages.json", JSON.stringify(session), "utf8"); + fs.writeFileSync( + "./messages.json", + JSON.stringify(session.data), + "utf8", + ); console.log("Now our conversation is", session); } else { const username = await getUsername(HOST, message.author!); @@ -171,7 +175,11 @@ client.connect().then(async () => { content: `[md]${contentOf(response)}[/md]`, }); - fs.writeFileSync("./messages.json", JSON.stringify(session), "utf8"); + fs.writeFileSync( + "./messages.json", + JSON.stringify(session.data), + "utf8", + ); } } catch (err) { console.log("Failed", err); @@ -210,7 +218,11 @@ client.connect().then(async () => { content: `[md]${contentOf(response)}[/md]`, }); - fs.writeFileSync("./messages.json", JSON.stringify(session), "utf8"); + fs.writeFileSync( + "./messages.json", + JSON.stringify(session.data), + "utf8", + ); } catch (err) { console.log("Failed", err); diff --git a/src/utils.ts b/src/utils.ts index f6e25a5..e98d3b9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -49,17 +49,19 @@ export function checkFile( final = !data.toString().trim() ? [] : JSON.parse(data.toString()); - // @ts-ignore - if (final.at(0).role) { - console.warn( - chalk.yellow( - "Your persistent storage uses the old data structure for persistent messages. These messages will be moved to a backup file and the existing file will be overwritten.", - ), - ); - fs.writeFileSync(`${file}.bak`, data); + try { + // @ts-ignore + if (final.at(0).role) { + console.warn( + chalk.yellow( + "Your persistent storage uses the old data structure for persistent messages. These messages will be moved to a backup file and the existing file will be overwritten.", + ), + ); + fs.writeFileSync(`${file}.bak`, data); - final = []; - } + final = []; + } + } catch {} } else { fs.createWriteStream(file); final = []; -- 2.43.0 From 53e358853ee03f28637741286518735ed145f6ea Mon Sep 17 00:00:00 2001 From: powermaker450 Date: Wed, 24 Jul 2024 16:54:32 -0400 Subject: [PATCH 3/3] Different console log if data is non existent --- src/bot.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bot.ts b/src/bot.ts index 4ddced6..0097818 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -39,7 +39,12 @@ if (!allVarsFilled) { // Define the initial system message for the LLM. const session = new GuildData(checkFile("./messages.json", "utf-8")); -console.log("Our conversation is:", session); + +session.data.toString() + ? console.log("Our conversation is:", session.data) + : console.log( + "Looks like we're starting fresh, no previous chat history was found.", + ); const THINKING = "[md]`Thinking...`[/md]"; -- 2.43.0