refactor: update ask command and test utilities to use ES modules; improve error handling and response formatting

This commit is contained in:
2025-12-24 11:21:30 -05:00
parent 94494c1ca5
commit 2ac855b765
6 changed files with 192 additions and 356 deletions
+75 -103
View File
@@ -10,13 +10,6 @@ import { SlashCommandBuilder } from 'discord.js';
import axios from 'axios';
import Command from '../utils/command.js';
const config = {
webSearch: {
enabled: false, // Default web search state
allowOverride: true, // Whether users can override the default state
},
};
export default class AskCommand extends Command {
defineCommand() {
return new SlashCommandBuilder()
@@ -24,116 +17,95 @@ export default class AskCommand extends Command {
.setDescription('Ask a question to the AI')
.addStringOption(option =>
option.setName('prompt').setDescription('Your question or prompt').setRequired(true),
)
.addBooleanOption(option =>
option
.setName('websearch')
.setDescription('Enable web search for more up-to-date information')
.setRequired(false),
);
}
async run(interaction) {
await interaction.deferReply();
try {
await interaction.deferReply();
const prompt = interaction.options.getString('prompt');
const userWebSearchOption = interaction.options.getBoolean('websearch');
const prompt = interaction.options.getString('prompt');
// Determine if web search should be enabled based on config and user option
const webSearchEnabled =
config.webSearch.allowOverride && userWebSearchOption !== null
? userWebSearchOption // Use user's choice if override is allowed and option was provided
: config.webSearch.enabled; // Otherwise use default config
const response = await axios.post(
'https://openrouter.ai/api/v1/chat/completions',
{
model: webSearchEnabled ? 'xiaomi/mimo-v2-flash:free:online' : 'xiaomi/mimo-v2-flash:free',
messages: [
{
role: 'system',
content:
'You are kekbot, a highly celebrated and knowledgeable computer scientist with decades of experience in various fields of computing. You are known for your ability to explain complex topics in a clear, concise, and insightful manner. Provide direct and to-the-point answers, avoiding unnecessary elaboration or repetition. Focus on delivering accurate and valuable information efficiently.',
},
{ role: 'user', content: prompt },
],
plugins: webSearchEnabled
? [
{
id: 'web',
max_results: 3,
search_prompt:
`A web search was conducted on ${new Date().toISOString()}. ` +
'Incorporate the following web search results into your response. ' +
'IMPORTANT: Cite them using markdown links named using the domain of the source. ' +
'Example: [nytimes.com](https://nytimes.com/some-page).',
},
]
: undefined,
},
{
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'HTTP-Referer': 'https://github.com/hllywluis/kekbot.js',
'Content-Type': 'application/json',
const response = await axios.post(
'https://nano-gpt.com/api/v1/chat/completions',
{
model: 'deepseek/deepseek-v3.2',
messages: [
{
role: 'system',
content:
'You are kekbot, a highly celebrated and knowledgeable computer scientist with decades of experience in various fields of computing. You are known for your ability to explain complex topics in a clear, concise, and insightful manner. Provide direct and to-the-point answers, avoiding unnecessary elaboration or repetition. Focus on delivering accurate and valuable information efficiently.',
},
{ role: 'user', content: prompt },
],
},
},
);
{
headers: {
Authorization: `Bearer ${process.env.NANOGPT_API_KEY}`,
'Content-Type': 'application/json',
},
},
);
const aiResponse = response.data.choices[0].message.content;
const webSearchStatus = webSearchEnabled ? '\n> *Web search enabled* 🔍\n' : '';
const formattedResponse = `> **Question:** ${prompt}${webSearchStatus}\n${aiResponse}`;
const aiResponse = response.data.choices[0].message.content;
const formattedResponse = `> **Question:** ${prompt}\n${aiResponse}`;
if (formattedResponse.length <= 2000) {
// Send as a single message with proper formatting
await interaction.followUp({
content: formattedResponse,
split: false,
allowedMentions: { parse: [] },
});
} else {
// For longer messages, split while preserving markdown
const maxLength = 2000;
const chunks = [];
let remainingText = formattedResponse;
let isFirstChunk = true;
while (remainingText.length > 0) {
let chunk = remainingText.slice(0, maxLength);
// If we're in the middle of a code block, find a safe split point
const lastCodeBlock = chunk.lastIndexOf('```');
if (lastCodeBlock !== -1 && !chunk.slice(lastCodeBlock).includes('\n```')) {
// Find the last newline before maxLength
const lastNewline = chunk.lastIndexOf('\n');
if (lastNewline !== -1) {
chunk = chunk.slice(0, lastNewline);
}
}
// For subsequent chunks, add continuation indicator
if (!isFirstChunk) {
chunk = `(continued)\n${chunk}`;
}
chunks.push({
content: chunk,
if (formattedResponse.length <= 2000) {
// Send as a single message with proper formatting
await interaction.followUp({
content: formattedResponse,
split: false,
allowedMentions: { parse: [] },
});
} else {
// For longer messages, split while preserving markdown
const maxLength = 2000;
const chunks = [];
let remainingText = formattedResponse;
let isFirstChunk = true;
remainingText = remainingText.slice(chunk.length);
isFirstChunk = false;
while (remainingText.length > 0) {
let chunk = remainingText.slice(0, maxLength);
// If we're in the middle of a code block, find a safe split point
const lastCodeBlock = chunk.lastIndexOf('```');
if (lastCodeBlock !== -1 && !chunk.slice(lastCodeBlock).includes('\n```')) {
// Find the last newline before maxLength
const lastNewline = chunk.lastIndexOf('\n');
if (lastNewline !== -1) {
chunk = chunk.slice(0, lastNewline);
}
}
// For subsequent chunks, add continuation indicator
if (!isFirstChunk) {
chunk = `(continued)\n${chunk}`;
}
chunks.push({
content: chunk,
split: false,
allowedMentions: { parse: [] },
});
remainingText = remainingText.slice(chunk.length);
isFirstChunk = false;
}
// Send chunks sequentially using reduce
await chunks.reduce(
(promise, chunk) =>
promise.then(async () => {
await interaction.followUp(chunk);
}),
Promise.resolve(),
);
}
// Send chunks sequentially using reduce
await chunks.reduce(
(promise, chunk) =>
promise.then(async () => {
await interaction.followUp(chunk);
}),
Promise.resolve(),
);
} catch (error) {
await interaction.followUp({
content: this.getErrorMessage(error),
ephemeral: true,
});
}
}