2025-05-08 01:52:12 +00:00
|
|
|
import { _fs } from 'fs';
|
|
|
|
|
import { _path } from 'path';
|
|
|
|
|
|
|
|
|
|
import { _MessageFlags } from 'discord-api-types/v10';
|
2025-05-02 16:45:36 +00:00
|
|
|
import { SlashCommandBuilder, PermissionFlagsBits, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } from 'discord.js';
|
2025-05-06 19:21:55 +00:00
|
|
|
// Placeholder info for template variables
|
|
|
|
|
const TEMPLATE_KEYS_INFO = 'Available keys: userName, userId, locationName, locationId, date, time, datetime, clientId';
|
|
|
|
|
|
2025-05-02 16:45:36 +00:00
|
|
|
// Modal text input limits
|
|
|
|
|
const MAX_LEN = 4000;
|
|
|
|
|
const MAX_FIELDS = 5;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* responsesPrompt module
|
2025-05-06 19:21:55 +00:00
|
|
|
* Implements `/prompt [version]` to edit the current or historical prompt in a single PocketBase collection.
|
|
|
|
|
* responses_prompts collection holds all versions; newest record per client is the live prompt.
|
2025-05-02 16:45:36 +00:00
|
|
|
*/
|
|
|
|
|
export const commands = [
|
2025-05-08 01:52:12 +00:00
|
|
|
{
|
|
|
|
|
data: new SlashCommandBuilder()
|
|
|
|
|
.setName('prompt')
|
|
|
|
|
.setDescription('Edit the AI response prompt (current or past version)')
|
|
|
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
|
|
|
.setDMPermission(false)
|
|
|
|
|
.addStringOption(opt =>
|
|
|
|
|
opt.setName('version')
|
|
|
|
|
.setDescription('ID of a past prompt version to load')
|
|
|
|
|
.setRequired(false)
|
|
|
|
|
.setAutocomplete(true)
|
|
|
|
|
),
|
|
|
|
|
async execute(interaction, client) {
|
|
|
|
|
const _clientId = client.config.id;
|
|
|
|
|
const versionId = interaction.options.getString('version');
|
|
|
|
|
// Fetch prompt: live latest or selected historic
|
|
|
|
|
let promptText = client.responsesPrompt || '';
|
|
|
|
|
if (versionId) {
|
|
|
|
|
try {
|
|
|
|
|
const rec = await client.pb.getOne('responses_prompts', versionId);
|
|
|
|
|
if (rec?.prompt) promptText = rec.prompt;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
client.logger.error(`Failed to load prompt version ${versionId}: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Prepare modal fields: one SHORT help, then paragraph chunks
|
|
|
|
|
// Help field
|
|
|
|
|
const helpField = new TextInputBuilder()
|
|
|
|
|
.setCustomId('template_help')
|
|
|
|
|
.setLabel('Template variables (no edits)')
|
|
|
|
|
.setStyle(TextInputStyle.Short)
|
|
|
|
|
.setRequired(false)
|
|
|
|
|
// prefill with the list of usable keys
|
|
|
|
|
.setValue(TEMPLATE_KEYS_INFO);
|
|
|
|
|
const modal = new ModalBuilder()
|
|
|
|
|
.setCustomId(`promptModal-${versionId || 'current'}`)
|
|
|
|
|
.setTitle('Edit AI Prompt')
|
|
|
|
|
.addComponents(new ActionRowBuilder().addComponents(helpField));
|
|
|
|
|
// Prompt chunks
|
|
|
|
|
const chunks = [];
|
|
|
|
|
for (let off = 0; off < promptText.length && chunks.length < MAX_FIELDS - 1; off += MAX_LEN) {
|
|
|
|
|
chunks.push(promptText.slice(off, off + MAX_LEN));
|
|
|
|
|
}
|
|
|
|
|
chunks.forEach((text, idx) => {
|
|
|
|
|
const input = new TextInputBuilder()
|
|
|
|
|
.setCustomId(`prompt_${idx}`)
|
|
|
|
|
.setLabel(`Part ${idx + 1}`)
|
|
|
|
|
.setStyle(TextInputStyle.Paragraph)
|
|
|
|
|
.setRequired(idx === 0)
|
|
|
|
|
.setMaxLength(MAX_LEN)
|
|
|
|
|
.setValue(text);
|
|
|
|
|
modal.addComponents(new ActionRowBuilder().addComponents(input));
|
|
|
|
|
});
|
|
|
|
|
// Empty fields to fill out to MAX_FIELDS
|
|
|
|
|
for (let i = chunks.length; i < MAX_FIELDS - 1; i++) {
|
|
|
|
|
modal.addComponents(new ActionRowBuilder().addComponents(
|
|
|
|
|
new TextInputBuilder()
|
|
|
|
|
.setCustomId(`prompt_${i}`)
|
|
|
|
|
.setLabel(`Part ${i + 1}`)
|
|
|
|
|
.setStyle(TextInputStyle.Paragraph)
|
|
|
|
|
.setRequired(false)
|
|
|
|
|
.setMaxLength(MAX_LEN)
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
await interaction.showModal(modal);
|
2025-05-02 16:45:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
2025-05-06 19:21:55 +00:00
|
|
|
// Store clients for event hooks
|
2025-05-02 16:45:36 +00:00
|
|
|
const _clients = [];
|
|
|
|
|
|
|
|
|
|
export async function init(client, clientConfig) {
|
2025-05-08 01:52:12 +00:00
|
|
|
const _clientId = client.config.id;
|
|
|
|
|
client.logger.info('[module:responsesPrompt] initialized');
|
|
|
|
|
// Load live prompt (latest version)
|
2025-05-06 19:21:55 +00:00
|
|
|
try {
|
2025-05-08 01:52:12 +00:00
|
|
|
const { items } = await client.pb.collection('responses_prompts')
|
|
|
|
|
.getList(1, 1, { filter: `clientId="${_clientId}"`, sort: '-created' });
|
|
|
|
|
client.responsesPrompt = items[0]?.prompt || '';
|
2025-05-06 19:21:55 +00:00
|
|
|
} catch (err) {
|
2025-05-08 01:52:12 +00:00
|
|
|
client.logger.error(`Error loading current prompt: ${err.message}`);
|
|
|
|
|
client.responsesPrompt = '';
|
2025-05-02 16:45:36 +00:00
|
|
|
}
|
2025-05-08 01:52:12 +00:00
|
|
|
_clients.push({ client, clientConfig });
|
|
|
|
|
// Autocomplete versions
|
|
|
|
|
client.on('interactionCreate', async interaction => {
|
|
|
|
|
if (!interaction.isAutocomplete() || interaction.commandName !== 'prompt') return;
|
|
|
|
|
const focused = interaction.options.getFocused(true);
|
|
|
|
|
if (focused.name === 'version') {
|
|
|
|
|
try {
|
|
|
|
|
const { items } = await client.pb.collection('responses_prompts')
|
|
|
|
|
.getList(1, 25, { filter: `clientId="${_clientId}"`, sort: '-created' });
|
|
|
|
|
const choices = items.map(r => ({ name: new Date(r.created).toLocaleString(), value: r.id }));
|
|
|
|
|
await interaction.respond(choices);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
client.logger.error(`Prompt autocomplete error: ${err.message}`);
|
|
|
|
|
await interaction.respond([]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Modal submission: save new version & prune old
|
|
|
|
|
client.on('interactionCreate', async interaction => {
|
|
|
|
|
if (!interaction.isModalSubmit()) return;
|
|
|
|
|
const id = interaction.customId;
|
|
|
|
|
if (!id.startsWith('promptModal-')) return;
|
|
|
|
|
const parts = [];
|
|
|
|
|
for (let i = 0; i < MAX_FIELDS; i++) {
|
|
|
|
|
try {
|
|
|
|
|
const v = interaction.fields.getTextInputValue(`prompt_${i}`) || '';
|
|
|
|
|
if (v.trim()) parts.push(v);
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
const newPrompt = parts.join('\n');
|
|
|
|
|
// Persist new version
|
|
|
|
|
let _newRec;
|
|
|
|
|
try {
|
|
|
|
|
_newRec = await client.pb.createOne('responses_prompts', { clientId: _clientId, prompt: newPrompt, updatedBy: interaction.user.id });
|
|
|
|
|
client.responsesPrompt = newPrompt;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
client.logger.error(`Failed to save prompt: ${err.message}`);
|
|
|
|
|
return interaction.reply({ content: `Error saving prompt: ${err.message}`, ephemeral: true });
|
|
|
|
|
}
|
|
|
|
|
// Prune older versions beyond the 10 most recent
|
|
|
|
|
try {
|
|
|
|
|
const { items } = await client.pb.collection('responses_prompts')
|
|
|
|
|
.getList(1, 100, { filter: `clientId="${_clientId}"`, sort: '-created' });
|
|
|
|
|
const toDelete = items.map(r => r.id).slice(10);
|
|
|
|
|
for (const id of toDelete) {
|
|
|
|
|
await client.pb.deleteOne('responses_prompts', id);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
client.logger.error(`Failed to prune old prompts: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
await interaction.reply({ content: 'Prompt saved!', ephemeral: true });
|
|
|
|
|
});
|
|
|
|
|
}
|