diff --git a/apps/mail/actions/ai-reply.ts b/apps/mail/actions/ai-reply.ts index 5f762c8bd9..17272c9a6e 100644 --- a/apps/mail/actions/ai-reply.ts +++ b/apps/mail/actions/ai-reply.ts @@ -1,5 +1,6 @@ 'use server'; +import { getUserSettings } from '@/actions/settings'; import { headers } from 'next/headers'; import { auth } from '@/lib/auth'; @@ -7,31 +8,34 @@ import { auth } from '@/lib/auth'; function truncateThreadContent(threadContent: string, maxTokens: number = 12000): string { // Split the thread into individual emails const emails = threadContent.split('\n---\n'); - + // Start with the most recent email (last in the array) let truncatedContent = emails[emails.length - 1]; - + // Add previous emails until we reach the token limit for (let i = emails.length - 2; i >= 0; i--) { const newContent = `${emails[i]}\n---\n${truncatedContent}`; - + // Rough estimation of tokens (1 token ≈ 4 characters) const estimatedTokens = newContent.length / 4; - + if (estimatedTokens > maxTokens) { break; } - + truncatedContent = newContent; } - + return truncatedContent; } -export async function generateAIResponse(threadContent: string, originalSender: string): Promise { +export async function generateAIResponse( + threadContent: string, + originalSender: string, +): Promise { const headersList = await headers(); const session = await auth.api.getSession({ headers: headersList }); - + if (!session?.user) { throw new Error('Unauthorized'); } @@ -40,12 +44,18 @@ export async function generateAIResponse(threadContent: string, originalSender: throw new Error('OpenAI API key is not configured'); } + // Get user settings to check for custom prompt + const userSettings = await getUserSettings(); + const customPrompt = userSettings?.customPrompt || ''; + // Truncate the thread content to fit within token limits const truncatedThreadContent = truncateThreadContent(threadContent); // Create the prompt for OpenAI const prompt = ` - You are ${session.user.name}, writing an email reply. + ${process.env.AI_SYSTEM_PROMPT} + + You should write as if your name is ${session.user.name}, who is the user writing an email reply. Here's the context of the email thread: ${truncatedThreadContent} @@ -54,7 +64,7 @@ export async function generateAIResponse(threadContent: string, originalSender: Requirements: - Be concise but thorough (2-3 paragraphs maximum) - - Maintain a professional and friendly tone + - Base your reply on the context provided. sometimes there will be an email that needs to be replied in an orderly manner while other times you will want a casual reply. - Address the key points from the original email - Close with an appropriate sign-off - Don't use placeholder text or mention that you're an AI @@ -62,6 +72,10 @@ export async function generateAIResponse(threadContent: string, originalSender: - Don't include the subject line in the reply - Double space paragraphs (2 newlines) - Add two spaces bellow the sign-off + +Here is some additional information about the user: +${customPrompt} + `; try { @@ -70,14 +84,15 @@ export async function generateAIResponse(threadContent: string, originalSender: method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, + Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: 'gpt-3.5-turbo', messages: [ { role: 'system', - content: 'You are a helpful email assistant that generates concise, professional replies.', + content: + 'You are a helpful email assistant that generates concise, professional replies.', }, { role: 'user', content: prompt }, ], @@ -97,4 +112,4 @@ export async function generateAIResponse(threadContent: string, originalSender: console.error('OpenAI API Error:', error); throw new Error(`OpenAI API Error: ${error.message || 'Unknown error'}`); } -} \ No newline at end of file +} diff --git a/apps/mail/app/(routes)/settings/general/page.tsx b/apps/mail/app/(routes)/settings/general/page.tsx index a6d31c2819..4cc226c715 100644 --- a/apps/mail/app/(routes)/settings/general/page.tsx +++ b/apps/mail/app/(routes)/settings/general/page.tsx @@ -30,12 +30,14 @@ import * as z from 'zod'; import { useSettings } from '@/hooks/use-settings'; import { getBrowserTimezone } from '@/lib/timezones'; import { saveUserSettings } from '@/actions/settings'; +import { Textarea } from '@/components/ui/textarea'; const formSchema = z.object({ language: z.enum(locales as [string, ...string[]]), timezone: z.string(), dynamicContent: z.boolean(), externalImages: z.boolean(), + customPrompt: z.string(), }); export default function GeneralPage() { @@ -51,6 +53,7 @@ export default function GeneralPage() { timezone: getBrowserTimezone(), dynamicContent: false, externalImages: true, + customPrompt: "", }, }); @@ -189,6 +192,25 @@ export default function GeneralPage() { )} /> + ( + + {t('pages.settings.general.customPrompt')} + +