import { Injectable } from "@nestjs/common"; import { FormalityLevel } from "@prisma/client"; import { Personality } from "../entities/personality.entity"; export interface PromptContext { userName?: string; workspaceName?: string; currentDate?: string; currentTime?: string; timezone?: string; custom?: Record; } export interface FormattedPrompt { systemPrompt: string; metadata: { personalityId: string; personalityName: string; tone: string; formalityLevel: FormalityLevel; formattedAt: Date; }; } const FORMALITY_MODIFIERS: Record = { VERY_CASUAL: "Be extremely relaxed and friendly. Use casual language, contractions, and even emojis when appropriate.", CASUAL: "Be friendly and approachable. Use conversational language and a warm tone.", NEUTRAL: "Be professional yet approachable. Balance formality with friendliness.", FORMAL: "Be professional and respectful. Use proper grammar and formal language.", VERY_FORMAL: "Be highly professional and formal. Use precise language and maintain a respectful, business-like demeanor.", }; @Injectable() export class PromptFormatterService { formatPrompt(personality: Personality, context?: PromptContext): FormattedPrompt { let prompt = personality.systemPromptTemplate; prompt = this.interpolateVariables(prompt, context); if ( !prompt.toLowerCase().includes("formality") && !prompt.toLowerCase().includes(personality.formalityLevel.toLowerCase()) ) { const modifier = FORMALITY_MODIFIERS[personality.formalityLevel]; prompt = `${prompt}\n\n${modifier}`; } if (!prompt.toLowerCase().includes(personality.tone.toLowerCase())) { prompt = `${prompt}\n\nMaintain a ${personality.tone} tone throughout the conversation.`; } return { systemPrompt: prompt.trim(), metadata: { personalityId: personality.id, personalityName: personality.name, tone: personality.tone, formalityLevel: personality.formalityLevel, formattedAt: new Date(), }, }; } buildSystemPrompt( personality: Personality, context?: PromptContext, options?: { includeDateTime?: boolean; additionalInstructions?: string } ): string { const { systemPrompt } = this.formatPrompt(personality, context); const parts: string[] = [systemPrompt]; if (options?.includeDateTime === true) { const now = new Date(); const dateStr: string = context?.currentDate ?? now.toISOString().split("T")[0] ?? ""; const timeStr: string = context?.currentTime ?? now.toTimeString().slice(0, 5); const tzStr: string = context?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone; parts.push(`Current date: ${dateStr}, Time: ${timeStr} (${tzStr})`); } if ( options?.additionalInstructions !== undefined && options.additionalInstructions.length > 0 ) { parts.push(options.additionalInstructions); } return parts.join("\n\n"); } private interpolateVariables(template: string, context?: PromptContext): string { if (context === undefined) { return template; } let result = template; if (context.userName !== undefined) { result = result.replace(/\{\{userName\}\}/g, context.userName); } if (context.workspaceName !== undefined) { result = result.replace(/\{\{workspaceName\}\}/g, context.workspaceName); } if (context.currentDate !== undefined) { result = result.replace(/\{\{currentDate\}\}/g, context.currentDate); } if (context.currentTime !== undefined) { result = result.replace(/\{\{currentTime\}\}/g, context.currentTime); } if (context.timezone !== undefined) { result = result.replace(/\{\{timezone\}\}/g, context.timezone); } if (context.custom !== undefined) { for (const [key, value] of Object.entries(context.custom)) { // Dynamic regex for template replacement - key is from trusted source (our code) // eslint-disable-next-line security/detect-non-literal-regexp const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g"); result = result.replace(regex, value); } } return result; } validateTemplate(template: string): { valid: boolean; missingVariables: string[] } { const variablePattern = /\{\{(\w+)\}\}/g; const matches = template.matchAll(variablePattern); const variables: string[] = []; for (const match of matches) { const variable = match[1]; if (variable !== undefined) { variables.push(variable); } } const allowedVariables = new Set([ "userName", "workspaceName", "currentDate", "currentTime", "timezone", ]); const unknownVariables = variables.filter( (v) => !allowedVariables.has(v) && !v.startsWith("custom_") ); return { valid: unknownVariables.length === 0, missingVariables: unknownVariables, }; } getFormalityLevels(): { level: FormalityLevel; description: string }[] { return Object.entries(FORMALITY_MODIFIERS).map(([level, description]) => ({ level: level as FormalityLevel, description, })); } }