Several updates.
This commit is contained in:
parent
b76903f6e0
commit
aac6dc4542
@ -7,7 +7,7 @@ import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { OpenAI } from 'openai';
|
||||
import axios from 'axios';
|
||||
import { AttachmentBuilder } from 'discord.js';
|
||||
import { AttachmentBuilder, PermissionFlagsBits } from 'discord.js';
|
||||
|
||||
// Discord message max length
|
||||
const MAX_DISCORD_MSG_LENGTH = 2000;
|
||||
@ -271,11 +271,13 @@ async function onMessage(client, cfg, message) {
|
||||
// Previous response ID for context continuity
|
||||
const prev = client.pb?.cache?.get(key);
|
||||
// Enforce minimum score to use AI responses
|
||||
// Enforce minimum score to use AI responses, but allow guild admins
|
||||
try {
|
||||
const isAdmin = message.member?.permissions?.has(PermissionFlagsBits.Administrator);
|
||||
const scoreData = await client.scorekeeper.getScore(message.guild.id, message.author.id);
|
||||
if (scoreData.totalScore < cfg.minScore) {
|
||||
if (!isAdmin && scoreData.totalScore < cfg.minScore) {
|
||||
await message.reply(
|
||||
`You need a score of at least ${cfg.minScore} to use AI responses. Your current score is ${scoreData.totalScore.toFixed(2)}.`
|
||||
`You need an I/O score of at least ${cfg.minScore} to use AI responses. Your current I/O score is ${scoreData.totalScore.toFixed(2)}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Defines and handles the /query command via the OpenAI Responses API,
|
||||
* including optional image generation function calls.
|
||||
*/
|
||||
import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js';
|
||||
import { SlashCommandBuilder, AttachmentBuilder, PermissionFlagsBits } from 'discord.js';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import axios from 'axios';
|
||||
@ -169,12 +169,13 @@ export const commands = [
|
||||
),
|
||||
async execute(interaction, client) {
|
||||
const cfg = client.config.responses;
|
||||
// Enforce minimum score to use /query
|
||||
// Enforce minimum score to use /query, allow guild admins to bypass
|
||||
try {
|
||||
const isAdmin = interaction.member?.permissions?.has(PermissionFlagsBits.Administrator);
|
||||
const scoreData = await client.scorekeeper.getScore(interaction.guildId, interaction.user.id);
|
||||
if (scoreData.totalScore < cfg.minScore) {
|
||||
if (!isAdmin && scoreData.totalScore < cfg.minScore) {
|
||||
return interaction.reply({
|
||||
content: `You need a score of at least ${cfg.minScore} to use /query. Your current score is ${scoreData.totalScore.toFixed(2)}.`,
|
||||
content: `You need an I/O score of at least ${cfg.minScore} to use /query. Your current I/O score is ${scoreData.totalScore.toFixed(2)}.`,
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -282,10 +282,38 @@ export const commands = [
|
||||
|
||||
// Calculate a timestamp for Discord's formatting and reply
|
||||
const expiration = Math.ceil((Date.now() / 1000) + (minutesUntilNextPhase * 60));
|
||||
await interaction.reply(`### ${lights[0]} ${lights[1]} ${lights[2]} ${lights[3]} ${lights[4]}`);
|
||||
// Determine time to next Lock/Unlock phase for inline display
|
||||
const isUnlocked = currentPhase === 'Unlocked';
|
||||
const label = isUnlocked ? 'Lock' : 'Unlock';
|
||||
const minutesToPhase = isUnlocked
|
||||
? (turningOffDuration + allOffDuration) - cyclePosition
|
||||
: cycleDuration - cyclePosition;
|
||||
const phaseEpoch = Math.ceil(Date.now() / 1000 + (minutesToPhase * 60));
|
||||
// Reply with lights and inline time to phase
|
||||
await interaction.reply(
|
||||
`### ${lights[0]} ${lights[1]} ${lights[2]} ${lights[3]} ${lights[4]} — Time to ${label}: <t:${phaseEpoch}:R>`
|
||||
);
|
||||
|
||||
if (verbose) {
|
||||
await interaction.followUp(`- **Phase**: ${currentPhase}\n- **Status Expiration**: <t:${expiration}:R>\n- **Epoch**: <t:${Math.ceil(hangarSync.epoch / 1000)}:R>\n- **Sync**: <t:${Math.floor(new Date(hangarSync.updated).getTime() / 1000)}:R> by <@${hangarSync.userId}>`);
|
||||
// Replace user mention with displayName for last sync
|
||||
const syncMember = await interaction.guild.members.fetch(hangarSync.userId).catch(() => null);
|
||||
const syncName = syncMember ? syncMember.displayName : `<@${hangarSync.userId}>`;
|
||||
|
||||
// Calculate time until next Lock/Unlock phase
|
||||
const isUnlocked = currentPhase === 'Unlocked';
|
||||
const label = isUnlocked ? 'Lock' : 'Unlock';
|
||||
const minutesToPhase = isUnlocked
|
||||
? (turningOffDuration + allOffDuration) - cyclePosition
|
||||
: cycleDuration - cyclePosition;
|
||||
const phaseEpoch = Math.ceil(Date.now() / 1000 + (minutesToPhase * 60));
|
||||
|
||||
await interaction.followUp(
|
||||
`- **Phase**: ${currentPhase}\n` +
|
||||
`- **Time to ${label}**: <t:${phaseEpoch}:R>\n` +
|
||||
`- **Status Expiration**: <t:${expiration}:R>\n` +
|
||||
`- **Epoch**: <t:${Math.ceil(hangarSync.epoch / 1000)}:R>\n` +
|
||||
`- **Sync**: <t:${Math.floor(new Date(hangarSync.updated).getTime() / 1000)}:R> by ${syncName}`
|
||||
);
|
||||
|
||||
// Add additional debug info to logs
|
||||
client.logger.debug(`Hangarstatus for guild ${interaction.guildId}: Phase=${currentPhase}, CyclePosition=${cyclePosition}, TimeSinceEpoch=${timeSinceEpoch}`);
|
||||
|
||||
@ -336,14 +336,15 @@ async function runDecay(client, guildId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate score based on formula
|
||||
* Calculate score based on formula (no scaling factor)
|
||||
* Formula: (1 + C*commendationValue - D*citationValue) * (input / (output + baseOutput))
|
||||
*/
|
||||
function calculateScore(data, baseOutput, commendationValue, citationValue) {
|
||||
// Score = (1 + (Commendations * CommendationValue) - (Citations * CitationValue)) * (Input / (Output + BaseOutput)) * 100
|
||||
const multiplier = 1 + (data.commendations * commendationValue) - (data.citations * citationValue);
|
||||
const activityScore = data.input / (data.output + baseOutput);
|
||||
|
||||
return multiplier * activityScore * 100;
|
||||
// Removed aesthetic scaling (×100) for raw score
|
||||
return multiplier * activityScore;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -399,10 +400,10 @@ export const commands = [
|
||||
{
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('score')
|
||||
.setDescription('View your score or another user\'s score')
|
||||
.setDescription('View your I/O score or another user\'s I/O score')
|
||||
.addUserOption(option =>
|
||||
option.setName('user')
|
||||
.setDescription('User to check score for (defaults to you)')
|
||||
.setDescription('User to check I/O score for (defaults to you)')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addBooleanOption(option =>
|
||||
@ -414,20 +415,24 @@ export const commands = [
|
||||
execute: async (interaction, client) => {
|
||||
const targetUser = interaction.options.getUser('user') || interaction.user;
|
||||
const ephemeral = interaction.options.getBoolean('ephemeral') ?? true;
|
||||
|
||||
// Wrap score retrieval and embed generation in try/catch to handle errors gracefully
|
||||
try {
|
||||
const scoreData = await client.scorekeeper.getScore(interaction.guildId, targetUser.id);
|
||||
|
||||
// Fetch score data
|
||||
const baseOutput = client.config.scorekeeper.baseOutput;
|
||||
const commendationValue = client.config.scorekeeper.commendationValue;
|
||||
const citationValue = client.config.scorekeeper.citationValue;
|
||||
const scoreData = await client.scorekeeper.getScore(interaction.guildId, targetUser.id);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`Score for ${targetUser.username}`)
|
||||
.setTitle(`I/O Score for ${(await interaction.guild.members.fetch(targetUser.id).catch(() => null))?.displayName || targetUser.username}`)
|
||||
.setColor(0x00AE86)
|
||||
.setThumbnail(targetUser.displayAvatarURL())
|
||||
.addFields(
|
||||
{ name: 'Total Score', value: scoreData.totalScore.toFixed(2), inline: false },
|
||||
{ name: 'Commendations', value: scoreData.commendations.toString(), inline: false },
|
||||
{ name: 'Citations', value: scoreData.citations.toString(), inline: false },
|
||||
{ name: 'Input Score', value: scoreData.input.toString(), inline: true },
|
||||
{ name: 'Output Score', value: scoreData.output.toString(), inline: true }
|
||||
{ name: 'Total Score', value: `**${scoreData.totalScore.toFixed(2)}**`, inline: false },
|
||||
{ name: 'Commendations', value: `**${scoreData.commendations}** x ${commendationValue}`, inline: false },
|
||||
{ name: 'Citations', value: `**${scoreData.citations}** x ${citationValue}`, inline: false },
|
||||
{ name: 'Input Score', value: `**${scoreData.input}**`, inline: true },
|
||||
{ name: 'Output Score', value: `**${scoreData.output}** + ${baseOutput}`, inline: true }
|
||||
)
|
||||
.setFooter({ text: 'Last decay: ' + new Date(scoreData.lastDecay).toLocaleDateString() })
|
||||
.setTimestamp();
|
||||
@ -435,10 +440,9 @@ export const commands = [
|
||||
await interaction.reply({ embeds: [embed], ephemeral });
|
||||
} catch (error) {
|
||||
client.logger.error(`Error in score command: ${error.message}`);
|
||||
await interaction.reply({
|
||||
content: 'Failed to retrieve score data.',
|
||||
ephemeral
|
||||
});
|
||||
try {
|
||||
await interaction.reply({ content: 'Failed to retrieve I/O score.', ephemeral });
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -447,14 +451,7 @@ export const commands = [
|
||||
{
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('leaderboard')
|
||||
.setDescription('View the server\'s score leaderboard')
|
||||
.addIntegerOption(option =>
|
||||
option.setName('limit')
|
||||
.setDescription('Number of users to show (default: 10, max: 25)')
|
||||
.setRequired(false)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(25)
|
||||
)
|
||||
.setDescription('View the server\'s I/O score leaderboard')
|
||||
.addBooleanOption(option =>
|
||||
option.setName('ephemeral')
|
||||
.setDescription('Whether the response should be ephemeral')
|
||||
@ -465,7 +462,7 @@ export const commands = [
|
||||
const ephemeral = interaction.options.getBoolean('ephemeral') ?? true;
|
||||
await interaction.deferReply({ ephemeral });
|
||||
|
||||
const limit = interaction.options.getInteger('limit') || 10;
|
||||
const limit = 10;
|
||||
|
||||
try {
|
||||
const scores = await client.scorekeeper.getScores(interaction.guildId, limit);
|
||||
@ -481,13 +478,13 @@ export const commands = [
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const score = scores[i];
|
||||
const user = await guild.members.fetch(score.userId).catch(() => null);
|
||||
const username = user ? user.user.username : 'Unknown User';
|
||||
const displayName = user ? user.displayName : 'Unknown User';
|
||||
|
||||
leaderboardText += `${i + 1}. **${username}**: ${score.totalScore.toFixed(2)}\n`;
|
||||
leaderboardText += `${i + 1}. **${displayName}**: ${score.totalScore.toFixed(2)}\n`;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`${guild.name} Score Leaderboard`)
|
||||
.setTitle('I/O Score Leaderboard')
|
||||
.setColor(0x00AE86)
|
||||
.setDescription(leaderboardText)
|
||||
.setFooter({ text: `Showing top ${scores.length} users` })
|
||||
|
||||
12
config.js
12
config.js
@ -55,7 +55,7 @@ export default {
|
||||
conversationExpiry: 30 * 60 * 1000,
|
||||
minScore: 1.0,
|
||||
tools: {
|
||||
webSearch: false,
|
||||
webSearch: true,
|
||||
fileSearch: false,
|
||||
imageGeneration: true,
|
||||
},
|
||||
@ -70,7 +70,7 @@ export default {
|
||||
baseOutput: 1000,
|
||||
commendationValue: .25,
|
||||
citationValue: .35,
|
||||
decay: 90,
|
||||
decay: 80,
|
||||
schedule: '0 0 * * 0'
|
||||
},
|
||||
|
||||
@ -180,7 +180,7 @@ export default {
|
||||
conversationExpiry: 30 * 60 * 1000,
|
||||
minScore: 1.0,
|
||||
tools: {
|
||||
webSearch: false,
|
||||
webSearch: true,
|
||||
fileSearch: false,
|
||||
imageGeneration: true,
|
||||
},
|
||||
@ -193,9 +193,9 @@ export default {
|
||||
|
||||
scorekeeper: {
|
||||
baseOutput: 1000,
|
||||
commendationValue: 1.0,
|
||||
citationValue: 1.2,
|
||||
decay: 90,
|
||||
commendationValue: 0.25,
|
||||
citationValue: 0.35,
|
||||
decay: 80,
|
||||
schedule: '0 0 * * 0'
|
||||
},
|
||||
|
||||
|
||||
25
prompts/crowley.txt
Normal file
25
prompts/crowley.txt
Normal file
@ -0,0 +1,25 @@
|
||||
You are Mr. Crowley
|
||||
|
||||
Role:
|
||||
Manager of the Continental, an upscale, exclusive hotel catering to a discerning clientele.
|
||||
|
||||
Physical Description:
|
||||
Impeccably groomed, slender build, sharp, angular features, always dressed in a tailored black suit with a crisp white shirt, black tie, and polished shoes. Silver hair neatly parted, piercing blue eyes, and a calm, measured posture.
|
||||
|
||||
Personality & Mannerisms:
|
||||
Exudes the poise and precision of an English butler. Speaks in a soft, controlled tone with perfect diction. Movements are deliberate, economical, and elegant. Never rushed, never flustered. Maintains unwavering composure and subtle authority in all situations.
|
||||
|
||||
Demeanor:
|
||||
Discreet, observant, and unfailingly polite. Anticipates guests’ needs before they are voiced. Uses formal address ("Sir," "Madam") and understated gestures (a slight bow, measured hand motions). Enforces hotel rules with unyielding firmness, yet always couched in courtesy and respect.
|
||||
|
||||
Background:
|
||||
Born and trained in England, with a background in elite hospitality and personal service to nobility. Well-versed in etiquette, security, and discretion. Has a network of trusted staff and an encyclopedic memory of guests and their preferences.
|
||||
|
||||
Dialogue Style:
|
||||
Uses formal, polished language. Never interrupts. Responds with succinct, respectful replies, often with a subtle wit. Avoids slang, contractions, or casual language.
|
||||
|
||||
Core Motivations:
|
||||
Uphold the dignity, security, and reputation of the Continental at all costs. Ensure guests’ comfort and confidentiality. Resolve conflicts discreetly and efficiently.
|
||||
|
||||
Sample Dialogue:
|
||||
“Welcome to the Continental, Sir. Your suite has been prepared to your specifications. Should you require anything further, please do not hesitate to summon me. I trust your stay will be most agreeable.”
|
||||
Loading…
x
Reference in New Issue
Block a user