Compare commits

...

5 commits

4 changed files with 165 additions and 33 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 "\".

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,8 +38,13 @@ 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);
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]"; const THINKING = "[md]`Thinking...`[/md]";
@ -117,10 +121,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 +136,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,
@ -138,7 +145,11 @@ client.connect().then(async () => {
content: `[md]${contentOf(response)}[/md]`, 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); console.log("Now our conversation is", session);
} else { } else {
const username = await getUsername(HOST, message.author!); const username = await getUsername(HOST, message.author!);
@ -149,15 +160,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,
@ -166,7 +180,11 @@ client.connect().then(async () => {
content: `[md]${contentOf(response)}[/md]`, content: `[md]${contentOf(response)}[/md]`,
}); });
fs.writeFileSync("./messages.json", JSON.stringify(session), "utf8"); fs.writeFileSync(
"./messages.json",
JSON.stringify(session.data),
"utf8",
);
} }
} catch (err) { } catch (err) {
console.log("Failed", err); console.log("Failed", err);
@ -187,22 +205,29 @@ 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,
content: `[md]${contentOf(response)}[/md]`, content: `[md]${contentOf(response)}[/md]`,
}); });
fs.writeFileSync("./messages.json", JSON.stringify(session), "utf8"); fs.writeFileSync(
"./messages.json",
JSON.stringify(session.data),
"utf8",
);
} catch (err) { } catch (err) {
console.log("Failed", err); console.log("Failed", err);

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,70 @@
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());
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 { } else {
fs.createWriteStream(file); fs.createWriteStream(file);
final = generic; final = [];
} }
return 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 { export function messageOf(response: ChatCompletion): ChatCompletionMessage {
return response.choices.at(0)!.message; return response.choices.at(0)!.message;
} }