feat: add ability to clear or "reset" conversation
Some checks failed
Node.js CI / Lint and Test (pull_request) Failing after 40s

This commit is contained in:
Naomi Carrigan 2025-05-22 15:18:58 -07:00
parent 1eed3afa99
commit cc3aade2c4
Signed by: naomi
SSH Key Fingerprint: SHA256:rca1iUI2OhAM6n4FIUaFcZcicmri0jgocqKiTTAfrt8
4 changed files with 104 additions and 11 deletions

24
src/commands/clear.ts Normal file
View File

@ -0,0 +1,24 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
ApplicationIntegrationType,
SlashCommandBuilder,
InteractionContextType,
} from "discord.js";
const command = new SlashCommandBuilder().
setContexts(
InteractionContextType.BotDM,
InteractionContextType.Guild,
InteractionContextType.PrivateChannel,
).
setIntegrationTypes(ApplicationIntegrationType.UserInstall).
setName("clear").
setDescription("Clear your current conversation so you can start a new one!");
// eslint-disable-next-line no-console -- We don't need our logger here as this never runs in production.
console.log(JSON.stringify(command.toJSON()));

View File

@ -14,13 +14,14 @@ import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js"; import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribedMessage } from "../utils/isSubscribed.js"; import { isSubscribedMessage } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import type { MessageParam } from "@anthropic-ai/sdk/resources/index.js"; import type { MessageParam } from "@anthropic-ai/sdk/resources/index.js";
/** /**
* Handles the Discord message event. * Handles the Discord message event.
* @param message - The message payload from Discord. * @param message - The message payload from Discord.
*/ */
// eslint-disable-next-line max-lines-per-function -- We're off by one bloody line. // eslint-disable-next-line max-lines-per-function, max-statements -- We're off by one bloody line.
export const onMessage = async(message: Message): Promise<void> => { export const onMessage = async(message: Message): Promise<void> => {
try { try {
if (message.channel.type !== ChannelType.DM) { if (message.channel.type !== ChannelType.DM) {
@ -33,9 +34,21 @@ export const onMessage = async(message: Message): Promise<void> => {
if (!subbed) { if (!subbed) {
return; return;
} }
const history = await message.channel.messages.fetch({ limit: 6 }); const historyRequest = await message.channel.messages.fetch({ limit: 20 });
const context: Array<MessageParam> const history = [ ...historyRequest.values() ];
= history.reverse().map((messageInner) => { const clearMessageIndex = history.findIndex((messageInner) => {
return (
messageInner.content === "<Clear History>"
&& messageInner.author.id === message.client.user.id
);
});
if (clearMessageIndex !== -1) {
// Remove the clear message and everything sent before it, which means everything after in the array because the array is backwards
history.splice(clearMessageIndex, history.length - clearMessageIndex);
}
const context: Array<MessageParam> = history.
reverse().
map((messageInner) => {
return { return {
content: messageInner.content, content: messageInner.content,
role: role:
@ -61,8 +74,13 @@ export const onMessage = async(message: Message): Promise<void> => {
response?.text ?? "There was an error. Please try again later.", response?.text ?? "There was an error. Please try again later.",
); );
if (!response) {
await logger.log("info", `No response from AI, here's the payload: ${JSON.stringify(messages)}`);
}
await calculateCost(messages.usage, message.author.username); await calculateCost(messages.usage, message.author.username);
} catch (error) { } catch (error) {
await logger.error("message event", error as Error);
const button = new ButtonBuilder(). const button = new ButtonBuilder().
setLabel("Need help?"). setLabel("Need help?").
setStyle(ButtonStyle.Link). setStyle(ButtonStyle.Link).

View File

@ -6,6 +6,7 @@
import { Client, Events, GatewayIntentBits, Partials } from "discord.js"; import { Client, Events, GatewayIntentBits, Partials } from "discord.js";
import { onMessage } from "./events/message.js"; import { onMessage } from "./events/message.js";
import { about } from "./modules/about.js"; import { about } from "./modules/about.js";
import { clear } from "./modules/clear.js";
import { instantiateServer } from "./server/serve.js"; import { instantiateServer } from "./server/serve.js";
import { logger } from "./utils/logger.js"; import { logger } from "./utils/logger.js";
@ -45,6 +46,9 @@ client.on(Events.InteractionCreate, (interaction) => {
ephemeral: true, ephemeral: true,
}); });
break; break;
case "clear":
void clear(interaction);
break;
default: default:
void interaction.reply({ void interaction.reply({
content: `I'm sorry, I don't know the ${interaction.commandName} command.`, content: `I'm sorry, I don't know the ${interaction.commandName} command.`,

47
src/modules/clear.ts Normal file
View File

@ -0,0 +1,47 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
MessageFlags,
type ChatInputCommandInteraction,
} from "discord.js";
import { isSubscribedInteraction } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/**
* Sends a clear message in the DMs.
* @param interaction -- The interaction payload from Discord.
*/
export const clear = async(
interaction: ChatInputCommandInteraction,
): Promise<void> => {
try {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
const subbed = await isSubscribedInteraction(interaction);
if (!subbed) {
return;
}
const sent = await interaction.user.send({
content: "<Clear History>",
}).catch(() => {
return null;
});
await interaction.editReply({
content: sent
? "I have added a clear history marker to your DMs."
// eslint-disable-next-line stylistic/max-len -- This is a long string.
: "I was unable to send you a DM. Please ensure your privacy settings allow direct messages.",
});
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("about command", error);
}
}
};