Initial implementation of the new message storage

This commit is contained in:
powermaker450 2024-07-23 13:34:34 -04:00
parent 6257a0a704
commit d8e3b36d64
5 changed files with 143 additions and 29 deletions

View file

@ -34,3 +34,4 @@ ALLOWED_CHAT=
SAFE_WORD= SAFE_WORD=
# When this character/string is detected anywhere in a message, the bot won't respond to it. Defaults to "\". # When this character/string is detected anywhere in a message, the bot won't respond to it. Defaults to "\".

1
.gitignore vendored
View file

@ -65,3 +65,4 @@ TEST-results.xml
/lib/ /lib/
messages.json messages.json
dist/ dist/
.env

View file

@ -1,7 +1,7 @@
import { TailchatWsClient, stripMentionTag } from "tailchat-client-sdk"; import { TailchatWsClient } from "tailchat-client-sdk";
import { OpenAI } from "openai"; import { OpenAI } from "openai";
import * as fs from "fs"; import * as fs from "fs";
import { ImageRequestData, ImageSize, Messages } from "./types"; import { GuildData, ImageRequestData, ImageSize } from "./types";
import { import {
checkFile, checkFile,
getUsername, getUsername,
@ -20,7 +20,6 @@ import {
createImageModel, createImageModel,
temperature, temperature,
key, key,
system,
} from "./assistant"; } from "./assistant";
import { ImagesResponse } from "openai/resources"; import { ImagesResponse } from "openai/resources";
import chalk from "chalk"; import chalk from "chalk";
@ -28,9 +27,9 @@ import dotenv from "dotenv";
dotenv.config(); dotenv.config();
// Specific to Tailchat. The endpoint of my Tailchat server, the bot ID and Secret. // Specific to Tailchat. The endpoint of my Tailchat server, the bot ID and Secret.
const HOST = process.env.HOST; export const HOST = process.env.HOST;
const APPID = process.env.ID; export const APPID = process.env.ID;
const APPSECRET = process.env.SECRET; export const APPSECRET = process.env.SECRET;
const allVarsFilled = HOST && APPID && APPSECRET; const allVarsFilled = HOST && APPID && APPSECRET;
if (!allVarsFilled) { if (!allVarsFilled) {
@ -39,7 +38,7 @@ if (!allVarsFilled) {
} }
// Define the initial system message for the LLM. // 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); console.log("Our conversation is:", session);
const THINKING = "[md]`Thinking...`[/md]"; const THINKING = "[md]`Thinking...`[/md]";
@ -117,10 +116,13 @@ client.connect().then(async () => {
content: "[md]`Analyzing image...`[/md]", content: "[md]`Analyzing image...`[/md]",
}); });
session.push(formatImageMessage(username, imageData)); session.appendMessage(
formatImageMessage(username, imageData),
message.converseId,
);
const response = await assistant.chat.completions.create({ const response = await assistant.chat.completions.create({
messages: session, messages: session.getHistory(message.converseId),
model: imageModel, model: imageModel,
temperature: temperature, temperature: temperature,
}); });
@ -129,7 +131,7 @@ client.connect().then(async () => {
"", "",
); );
session.push(messageOf(response)); session.appendMessage(messageOf(response), message.converseId);
await client.sendMessage({ await client.sendMessage({
converseId: message.converseId, converseId: message.converseId,
@ -149,15 +151,18 @@ client.connect().then(async () => {
content: THINKING, content: THINKING,
}); });
session.push(formatUserMessage(username, message.content)); session.appendMessage(
formatUserMessage(username, message.content),
message.converseId,
);
const response = await assistant.chat.completions.create({ const response = await assistant.chat.completions.create({
messages: session, messages: session.getHistory(message.converseId),
model: textModel, model: textModel,
temperature: temperature, temperature: temperature,
}); });
session.push(messageOf(response)); session.appendMessage(messageOf(response), message.converseId);
await client.sendMessage({ await client.sendMessage({
converseId: message.converseId, converseId: message.converseId,
@ -187,15 +192,18 @@ client.connect().then(async () => {
content: THINKING, content: THINKING,
}); });
session.push(formatUserMessage(username, message.content)); session.appendMessage(
formatUserMessage(username, message.content),
message.converseId,
);
const response = await assistant.chat.completions.create({ const response = await assistant.chat.completions.create({
messages: session, messages: session.getHistory(message.converseId),
model: textModel, model: textModel,
temperature: temperature, temperature: temperature,
}); });
session.push(messageOf(response)); session.appendMessage(messageOf(response), message.converseId);
await client.sendMessage({ await client.sendMessage({
converseId: message.converseId, converseId: message.converseId,

View file

@ -5,6 +5,10 @@ import {
ChatCompletionSystemMessageParam, ChatCompletionSystemMessageParam,
} from "openai/resources"; } from "openai/resources";
import { TailchatWsClient } from "tailchat-client-sdk"; 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 { export interface ImageMessage {
role: "user"; role: "user";
@ -27,6 +31,8 @@ export type AnyChatCompletion =
| ChatCompletionUserMessageParam | ChatCompletionUserMessageParam
| ChatCompletionSystemMessageParam; | ChatCompletionSystemMessageParam;
export type AnyChatCompletionRole = "user" | "system" | "assistant";
export enum ImageSize { export enum ImageSize {
Small = "256x256", Small = "256x256",
Medium = "512x512", Medium = "512x512",
@ -82,4 +88,45 @@ export type Temperature =
| 1.9 | 1.9
| 2.0; | 2.0;
export type Messages = Array<AnyChatCompletion | ImageMessage>; 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.`,
);
}
}

View file

@ -1,28 +1,68 @@
import * as fs from "fs"; 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 { ChatCompletion, ChatCompletionMessage } from "openai/resources";
import { stripMentionTag } from "tailchat-client-sdk"; 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( export function checkFile(
file: string, file: string,
encoding: fs.EncodingOption, encoding: fs.EncodingOption,
defaultContent: string, ): ChatHistory {
): Messages { let final: ChatHistory;
let final: Messages;
const generic: Messages = [
{
role: "system",
content: defaultContent,
},
];
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
const data = fs.readFileSync(file, encoding); 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 { } else {
fs.createWriteStream(file); fs.createWriteStream(file);
final = generic; final = [];
} }
return 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 { export function messageOf(response: ChatCompletion): ChatCompletionMessage {
return response.choices.at(0)!.message; return response.choices.at(0)!.message;
} }