Merge pull request 'new-message-storage' (#1) from new-message-storage into main

Reviewed-on: #1
This commit is contained in:
powermaker450 2024-07-25 00:37:59 -04:00
commit 0b8391a7e0
4 changed files with 165 additions and 33 deletions

View file

@ -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 "\".

View file

@ -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,8 +38,13 @@ if (!allVarsFilled) {
}
// Define the initial system message for the LLM.
const session: Messages = checkFile("./messages.json", "utf-8", system.normal);
console.log("Our conversation is:", session);
const session = new GuildData(checkFile("./messages.json", "utf-8"));
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]";
@ -117,10 +121,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 +136,7 @@ client.connect().then(async () => {
"",
);
session.push(messageOf(response));
session.appendMessage(messageOf(response), message.converseId);
await client.sendMessage({
converseId: message.converseId,
@ -138,7 +145,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!);
@ -149,15 +160,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,
@ -166,7 +180,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);
@ -187,22 +205,29 @@ 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,
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);

View file

@ -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<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,70 @@
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());
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 = [];
}
} catch {}
} else {
fs.createWriteStream(file);
final = generic;
final = [];
}
return final;
@ -59,6 +101,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;
}