/** * @copyright NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { ids } from "../config/ids.js"; import { logger } from "../utils/logger.js"; import { makeAiRequest } from "../utils/makeAiRequest.js"; interface LeantimeResponse { error?: { message: string }; result?: number; } const taskSystemPrompt = "Create well-structured task descriptions." + " Be concise and actionable." + " Return only the description text with no extra formatting or headers."; /** * Generates an AI-augmented task description. * @param description - Optional additional context for the task. * @param title - The subject of the Leantime task. * @returns The generated task description text, or the original description as fallback. */ const generateTaskDescription = async( description: string, title: string, ): Promise => { const result = await makeAiRequest({ maxTokens: 500, systemPrompt: taskSystemPrompt, userMessage: `Create a clear, concise task description for a personal productivity board.\n\nTask title: ${title}${description === "" ? "" : `\nAdditional context: ${description}`}`, }); return result ?? description; }; /** * Posts a task to the Leantime board via JSON-RPC. * @param description - Body copy for the Leantime task. * @param priority - The task priority level. * @param title - The headline for the Leantime task. * @returns The Leantime API response. */ const postLeantimeTask = async( description: string, priority: number, title: string, ): Promise => { const response = await fetch("https://board.nhcarrigan.com/api/jsonrpc", { body: JSON.stringify({ id: `amari-task-${Date.now().toString()}`, jsonrpc: "2.0", method: "leantime.rpc.tickets.addTicket", params: { values: { description: description, editorId: "1", headline: title, priority: priority.toString(), projectId: "1", type: "task", }, }, }), headers: { // eslint-disable-next-line @typescript-eslint/naming-convention -- HTTP header name. "Content-Type": "application/json", // eslint-disable-next-line @typescript-eslint/naming-convention -- HTTP header name. "x-api-key": process.env.LEANTIME_KEY ?? "", }, method: "POST", }); // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Required to type Response.json() output. const data = await response.json() as LeantimeResponse; return data; }; /** * Creates a Leantime task using AI-augmented description content. * @param interaction - The Discord slash command interaction. */ export const createTask = async( interaction: ChatInputCommandInteraction, ): Promise => { if (interaction.user.id !== ids.users.naomi) { await interaction.reply({ content: "This command is restricted to Naomi.", flags: [ MessageFlags.Ephemeral ], }); return; } const title = interaction.options.getString("title", true); const description = interaction.options.getString("description") ?? ""; const priority = interaction.options.getInteger("priority") ?? 3; await interaction.deferReply({ ephemeral: true }); try { const augmentedDesc = await generateTaskDescription(description, title); const data = await postLeantimeTask(augmentedDesc, priority, title); if (data.error !== undefined) { await interaction.editReply({ content: `❌ Failed to create task: ${data.error.message}`, }); return; } const taskId = data.result; const taskUrl = taskId === undefined ? "https://board.nhcarrigan.com" : `https://board.nhcarrigan.com/dashboard/home#/tickets/showTicket/${taskId.toString()}`; await logger.metric("created_task", 1, { title }); await interaction.editReply({ content: `✅ Task created: **${title}**\n${taskUrl}`, }); } catch (error) { if (error instanceof Error) { await logger.error("createTask command", error); } await interaction.editReply({ content: "❌ An unexpected error occurred while creating the task.", }); } };