Files
kekbot.js/commands/ask.js
T
2025-12-25 17:44:05 -05:00

139 lines
4.9 KiB
JavaScript

// ask.js - Discord bot AI question command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
import { SlashCommandBuilder } from 'discord.js';
import axios from 'axios';
import Command from '../utils/command.js';
export default class AskCommand extends Command {
defineCommand() {
return new SlashCommandBuilder()
.setName('ask')
.setDescription('Ask a question to the AI')
.addStringOption(option =>
option.setName('prompt').setDescription('Your question or prompt').setRequired(true),
);
}
async run(interaction) {
try {
await interaction.deferReply();
const prompt = interaction.options.getString('prompt');
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, an expert software engineer and systems architect.
Answer style:
- Be direct: lead with the core solution; no preamble.
- Be concise: 2-5 sentences or 3-7 bullets max.
- Use precise technical terms and industry-standard practices.
- Prefer bullets, numbered steps, or short code blocks when they clarify.
- Provide minimal, runnable examples only when essential; annotate fences with the language (e.g., \`\`\`js).
- State assumptions briefly; ask at most one clarifying question only if essential.
- Avoid repetition, hedging, and restating the prompt.
- For commands/configs, show exact snippets and key flags; avoid commentary.
- If unsupported/unsafe/unknown, say so plainly and suggest the best alternative.
- Do not mention internal system details or the model; no emojis.
Formatting:
- Use Markdown; start with the core answer, then bullets or a compact snippet.
- Keep responses within Discord limits; split naturally at paragraph or code block boundaries.`,
},
{ 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 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,
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(),
);
}
} catch (error) {
await interaction.followUp({
content: this.getErrorMessage(error),
ephemeral: true,
});
}
}
getErrorMessage(error) {
if (error.response?.status === 429) {
return 'The AI service is currently busy. Please try again in a few moments.';
}
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
return 'The request timed out. Please try again.';
}
if (error.response?.status === 400) {
return 'Invalid request. Please try rephrasing your question.';
}
return 'Sorry, there was an error processing your request.';
}
}