2023-03-14 21:16:54 +01:00
|
|
|
import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
|
2023-05-08 08:50:59 +02:00
|
|
|
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
|
2023-03-18 02:06:49 +01:00
|
|
|
import FoldToAscii from "fold-to-ascii";
|
2023-03-14 21:16:54 +01:00
|
|
|
|
2023-03-24 15:44:22 +01:00
|
|
|
import config from "./config";
|
2023-03-19 04:15:08 +01:00
|
|
|
import countTokens from "./tokenCounter";
|
2023-05-08 08:50:59 +02:00
|
|
|
import { request } from "./execution";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* formats the request to use as a message contend in OpenAI api
|
|
|
|
* @param request the request to format
|
|
|
|
* @returns the formatted request
|
|
|
|
*/
|
|
|
|
export async function formatRequestOrResponse(request: request | InteractionResponse): Promise<string> {
|
|
|
|
if (request instanceof DiscordMessage) {
|
|
|
|
return formatMessage(request);
|
|
|
|
}
|
|
|
|
if (request instanceof InteractionResponse) {
|
|
|
|
return formatMessage(await request.fetch());
|
|
|
|
}
|
|
|
|
return formatMessage(await request.fetchReply());
|
|
|
|
}
|
2023-03-14 21:16:54 +01:00
|
|
|
|
2023-03-18 02:06:49 +01:00
|
|
|
/**
|
|
|
|
* Formats the message to use as a message content in OpenAI api
|
|
|
|
* @param message the message to format
|
|
|
|
* @returns the formatted message
|
|
|
|
*/
|
2023-03-14 23:44:43 +01:00
|
|
|
export function formatMessage(message: DiscordMessage): string {
|
2023-03-14 21:16:54 +01:00
|
|
|
let rvalue: string = message.cleanContent;
|
|
|
|
|
|
|
|
for (const attachment of message.attachments) {
|
|
|
|
rvalue += ` [Attachment: ${attachment[1].name}`;
|
|
|
|
rvalue += attachment[1].description ? ` - ${attachment[1].description}` : "";
|
|
|
|
rvalue += "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const embed of message.embeds) {
|
|
|
|
rvalue += ` [Embed:`;
|
|
|
|
rvalue += embed.title ? ` ${embed.title}` : "";
|
2023-03-17 19:59:46 +01:00
|
|
|
rvalue += embed.author ? ` by ${embed.author.name}` : "";
|
|
|
|
rvalue += (embed.title || embed.author) && embed.description ? " -": "";
|
2023-03-14 21:16:54 +01:00
|
|
|
rvalue += embed.description ? ` ${embed.description}` : "";
|
2023-03-18 03:38:29 +01:00
|
|
|
|
|
|
|
for (const field of embed.fields) {
|
|
|
|
rvalue += ` [${field.name} - ${field.value}]`;
|
|
|
|
}
|
2023-03-14 21:16:54 +01:00
|
|
|
rvalue += "]";
|
|
|
|
}
|
|
|
|
|
2023-03-18 04:55:37 +01:00
|
|
|
for (const sticker of message.stickers.values()) {
|
|
|
|
rvalue += ` [Sticker:`;
|
|
|
|
rvalue += sticker.name;
|
|
|
|
rvalue += sticker.description ? ` - ${sticker.description}` : "";
|
|
|
|
rvalue += "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
return rvalue.trim();
|
2023-03-14 21:16:54 +01:00
|
|
|
}
|
|
|
|
|
2023-03-18 02:06:49 +01:00
|
|
|
/**
|
|
|
|
* Formats name to be accepted in OpenAI api
|
|
|
|
* @param name the name to format
|
|
|
|
* @returns formatted name
|
|
|
|
*/
|
|
|
|
function formatName(name: string): string {
|
|
|
|
// replace all characters to ascii equivelant
|
|
|
|
return FoldToAscii.foldReplacing(name)
|
|
|
|
// White spaces are not allowed
|
|
|
|
.replace(" ", "_")
|
2023-03-18 03:27:44 +01:00
|
|
|
// replace characters that are not accepted by api with '_'
|
|
|
|
.replace(/[^a-zA-Z0-9_-]/g, "_")
|
2023-03-18 02:06:49 +01:00
|
|
|
// remove trailing '-_' characters
|
|
|
|
.replace(/^[-_]+|[-_]+$/g, "")
|
|
|
|
// remove duplicated '-_' characters
|
2023-03-18 03:27:44 +01:00
|
|
|
.replace(/([-_])*/g, "$1");
|
2023-03-18 02:06:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the name of the author of the message, formatted to be used in OpenAI api
|
|
|
|
* @param message the message to get the author name
|
|
|
|
* @returns the proper author name of the message
|
|
|
|
*/
|
2023-07-28 07:45:06 +02:00
|
|
|
function getAuthorUsername(message: DiscordMessage): string | undefined {
|
|
|
|
if (message.author.id === message.client.user.id ) return undefined;
|
2023-03-18 02:06:49 +01:00
|
|
|
if (message.member) {
|
|
|
|
const name = formatName(message.member.displayName);
|
|
|
|
if (name.length >= 3) return name;
|
|
|
|
}
|
|
|
|
const name = formatName(message.author.username);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-03-19 04:15:08 +01:00
|
|
|
* Converts the Collection of Discord Messages to array of OpenAI Messages to send
|
2023-03-18 02:06:49 +01:00
|
|
|
* @param messages the collection to convert
|
|
|
|
* @returns the converted messages
|
|
|
|
*/
|
2023-03-14 21:16:54 +01:00
|
|
|
export default function toOpenAIMessages(messages: Collection<string, DiscordMessage>): OpenAIMessage[] {
|
|
|
|
const rvalue: OpenAIMessage[] = [];
|
2023-03-19 04:15:08 +01:00
|
|
|
let tokenCount = 0;
|
2023-03-14 21:16:54 +01:00
|
|
|
|
2023-03-19 04:15:08 +01:00
|
|
|
messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp);
|
2023-03-14 21:16:54 +01:00
|
|
|
|
2023-03-19 04:15:08 +01:00
|
|
|
for (const message of messages.values()) {
|
|
|
|
const content = formatMessage(message);
|
|
|
|
// FIXME: tokens are not being counted properly (it's lower than it is) but it's enough for me for now.
|
|
|
|
tokenCount += countTokens(content);
|
2023-03-24 16:47:26 +01:00
|
|
|
if (tokenCount > config.limits.tokens) break;
|
2023-03-14 21:16:54 +01:00
|
|
|
rvalue.push({
|
|
|
|
role: message.author.id == message.client.user.id ? "assistant" : "user",
|
2023-03-19 04:15:08 +01:00
|
|
|
content: content,
|
2023-03-18 02:06:49 +01:00
|
|
|
name: getAuthorUsername(message),
|
2023-03-14 21:16:54 +01:00
|
|
|
});
|
2023-03-19 04:15:08 +01:00
|
|
|
}
|
|
|
|
|
2023-03-24 15:44:22 +01:00
|
|
|
rvalue.push(...config.systemPrompt().reverse());
|
2023-03-14 21:16:54 +01:00
|
|
|
|
2023-03-19 04:15:08 +01:00
|
|
|
return rvalue.reverse();
|
2023-03-14 21:16:54 +01:00
|
|
|
}
|