diff --git a/src/slash-commands/ask-llm/command.test.ts b/src/slash-commands/ask-llm/command.test.ts index 89b4135..457bf3b 100644 --- a/src/slash-commands/ask-llm/command.test.ts +++ b/src/slash-commands/ask-llm/command.test.ts @@ -39,11 +39,11 @@ describe('ask command', () => { throw new Error('Invalid'); } }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Invalid model'); }); @@ -61,11 +61,11 @@ describe('ask command', () => { } }); mockChatCompletions.mockRejectedValueOnce(new Error('Synthetic Error.')); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Error in asking the LLM'); }); @@ -98,11 +98,11 @@ describe('ask command', () => { }, ] as ChatCompletion.Choice[], }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('No response'); }); @@ -136,11 +136,11 @@ describe('ask command', () => { }, ] as ChatCompletion.Choice[], }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain(`Q: asdf1234\nA: ${mockAnswer}`); }); }); diff --git a/src/slash-commands/ask-llm/command.ts b/src/slash-commands/ask-llm/command.ts index e8996da..66e0b20 100644 --- a/src/slash-commands/ask-llm/command.ts +++ b/src/slash-commands/ask-llm/command.ts @@ -12,6 +12,7 @@ const data = new SlashCommandBuilder() .addStringOption((option) => option.setName('question').setDescription('Enter your prompt').setRequired(true).setMinLength(10)); export const execute: SlashCommandHandler = async (interaction) => { + await interaction.deferReply(); const model = interaction.options.getString('model', true).trim().toLowerCase(); const question = interaction.options.getString('question', true).trim(); logger.info(`[ask]: Asking ${model} model with prompt: ${question}`); @@ -19,18 +20,20 @@ export const execute: SlashCommandHandler = async (interaction) => { const findModelOp = Result.safe(() => findModel(model)); if (findModelOp.isErr()) { logger.info(`[ask]: Invalid model ${model}`); - interaction.reply('Invalid model. Please choose from the available models.'); + interaction.editReply('Invalid model. Please choose from the available models.'); return; } const supportedModel = findModelOp.unwrap(); + logger.info(`[ask]: Asking LLM with prompt: ${question}`); const answers = await askQuestion(supportedModel, question); - logger.info('[ask]: Got response from LLM', data); - await answers.reduce(async (accum, chunk) => { - await accum; - await interaction.reply(chunk); - return undefined; - }, Promise.resolve(undefined)); + + logger.info('[ask]: Got response from LLM. Sending to client.', answers); + const [firstChunk, ...chunks] = answers; + await interaction.editReply(firstChunk); + for await (const chunk of chunks) { + interaction.followUp(chunk); + } }; const command: SlashCommand = { diff --git a/src/slash-commands/review-resume/command.test.ts b/src/slash-commands/review-resume/command.test.ts index 8b13389..64edce8 100644 --- a/src/slash-commands/review-resume/command.test.ts +++ b/src/slash-commands/review-resume/command.test.ts @@ -42,11 +42,11 @@ describe('review resume command', () => { throw new Error('Invalid'); } }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Invalid model'); }); @@ -63,11 +63,11 @@ describe('review resume command', () => { throw new Error('Invalid'); } }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Invalid URL'); }); @@ -84,11 +84,11 @@ describe('review resume command', () => { throw new Error('Invalid'); } }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Error downloading resume'); }); @@ -105,11 +105,11 @@ describe('review resume command', () => { throw new Error('Invalid'); } }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Error downloading resume'); }); @@ -127,11 +127,11 @@ describe('review resume command', () => { } }); readerSpy.mockRejectedValueOnce(new Error('Synthetic Error.')); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Error reading resume'); }); @@ -149,11 +149,11 @@ describe('review resume command', () => { } }); mockChatCompletions.mockRejectedValueOnce(new Error('Synthetic Error.')); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('Error in asking the LLM'); }); @@ -186,11 +186,11 @@ describe('review resume command', () => { }, ] as ChatCompletion.Choice[], }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain('No response'); }); @@ -224,11 +224,11 @@ describe('review resume command', () => { }, ] as ChatCompletion.Choice[], }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain(`${mockAnswer}`); }); @@ -262,11 +262,11 @@ describe('review resume command', () => { }, ] as ChatCompletion.Choice[], }); - const respondInput = captor['0']>(); + const respondInput = captor['0']>(); await execute(mockChatInputInteraction); - expect(mockChatInputInteraction.reply).toBeCalledWith(respondInput); + expect(mockChatInputInteraction.editReply).toBeCalledWith(respondInput); expect(respondInput.value).toContain(`${mockAnswer}`); }); }); diff --git a/src/slash-commands/review-resume/command.ts b/src/slash-commands/review-resume/command.ts index d499fd4..5840019 100644 --- a/src/slash-commands/review-resume/command.ts +++ b/src/slash-commands/review-resume/command.ts @@ -15,6 +15,7 @@ const data = new SlashCommandBuilder() .addStringOption((option) => option.setName('url').setDescription('PDF or Google Drive URL').setRequired(true)); export const execute: SlashCommandHandler = async (interaction) => { + await interaction.deferReply(); const model = interaction.options.getString('model', true).trim().toLowerCase(); const url = interaction.options.getString('url', true); logger.info(`[review-resume]: Reviewing resume from URL: ${url}`); @@ -22,7 +23,7 @@ export const execute: SlashCommandHandler = async (interaction) => { const findModelOp = Result.safe(() => findModel(model)); if (findModelOp.isErr()) { logger.info(`[ask]: Invalid model ${model}`); - interaction.reply('Invalid model. Please choose from the available models.'); + interaction.editReply('Invalid model. Please choose from the available models.'); return; } const supportedModel = findModelOp.unwrap(); @@ -30,7 +31,7 @@ export const execute: SlashCommandHandler = async (interaction) => { const validURL = PDFURL.safeParse(url); if (!validURL.success) { logger.info(`[review-resume]: Invalid URL ${url}`); - await interaction.reply('Invalid URL. The URL must end with `.pdf` or must be valid Google Drive URL.'); + await interaction.editReply('Invalid URL. The URL must end with `.pdf` or must be valid Google Drive URL.'); return; } @@ -39,7 +40,7 @@ export const execute: SlashCommandHandler = async (interaction) => { if (downloadOp.isErr()) { logger.error(`[review-resume]: Error downloading file: ${downloadOp.unwrapErr().message}`); cleanup(filename); - await interaction.reply('Error downloading resume. Please try again later.'); + await interaction.editReply('Error downloading resume. Please try again later.'); return; } @@ -47,7 +48,7 @@ export const execute: SlashCommandHandler = async (interaction) => { if (readOp.isErr()) { logger.error(`[review-resume]: Error reading file: ${readOp.unwrapErr().message}`); cleanup(filename); - await interaction.reply('Error reading resume. Please try again later.'); + await interaction.editReply('Error reading resume. Please try again later.'); return; } @@ -60,13 +61,15 @@ export const execute: SlashCommandHandler = async (interaction) => { ${doc} `; + logger.info('[review-resume]: Sending review resume request to LLM.'); const answers = await askQuestion(supportedModel, question, false); - logger.info('[review-resume]: Got response from LLM', data); - await answers.reduce(async (accum, chunk) => { - await accum; - await interaction.reply(chunk); - return undefined; - }, Promise.resolve(undefined)); + + logger.info('[review-resume]: Got response from LLM. Sending to client', answers); + const [firstChunk, ...chunks] = answers; + await interaction.editReply(firstChunk); + for await (const chunk of chunks) { + interaction.followUp(chunk); + } }; const command: SlashCommand = {