Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughA new AI-powered feature for generating email subject lines based on message content and writing style has been implemented. This includes frontend UI changes, new state management, API hooks, and backend procedures for subject generation using a GPT-4o model. Additional UI and code structure improvements are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant EmailComposer (Frontend)
participant TRPC API (Backend)
participant AI Model (GPT-4o)
User->>EmailComposer (Frontend): Click "Generate Subject" button
EmailComposer (Frontend)->>TRPC API (Backend): generateEmailSubject(message)
TRPC API (Backend)->>AI Model (GPT-4o): Generate subject with style profile
AI Model (GPT-4o)-->>TRPC API (Backend): Subject line
TRPC API (Backend)-->>EmailComposer (Frontend): Subject line
EmailComposer (Frontend)-->>User: Autofill subject input
Suggested reviewers
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
apps/server/src/main.ts (1)
79-87: Improved type safety and context handling.The changes to the class definition and
fetchmethod improve type safety with proper generic typing and ensure the environment and context are correctly passed to the application.However, the type casting on line 82 could be improved:
- return routePartykitRequest(request, env as unknown as Record<string, unknown>, { + return routePartykitRequest(request, env as Record<string, unknown>, {apps/mail/components/create/email-composer.tsx (1)
886-888: Add visual feedback for automatic subject generation.When auto-generating a subject, there's no visual indication to the user that this is happening alongside the message generation. Consider showing a toast notification or other feedback.
if (!subjectInput.trim()) { + toast.info('Generating subject and message...'); await handleGenerateSubject(); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/mail/components/create/email-composer.tsx(6 hunks)apps/mail/components/mail/mail-list.tsx(5 hunks)apps/server/src/main.ts(3 hunks)apps/server/src/trpc/routes/ai/compose.ts(2 hunks)apps/server/src/trpc/routes/ai/index.ts(1 hunks)
🔇 Additional comments (12)
apps/server/src/trpc/routes/ai/index.ts (1)
1-9: LGTM! Export changes are correctly integrated.The new
generateEmailSubjectprocedure has been properly imported and registered in theaiRouter.apps/server/src/main.ts (2)
8-8: Good cleanup of unused imports.Removing the unused
HonoContexttype import helps keep the codebase clean.
21-22: Simplification of CORS callback.The CORS middleware origin callback has been simplified by removing the unused context parameter.
apps/mail/components/mail/mail-list.tsx (4)
175-176: Improved hook usage with proper destructuring.Good addition of
threadsfrom theuseThreads()hook for the thread navigation feature.
184-186: Added state hooks for thread navigation.The new state hooks are necessary for the thread navigation feature implementation.
236-236: Improved UX with automatic thread navigation after moving.Adding the call to
handleNextafter moving a thread provides a better user experience by automatically navigating to the next thread.
611-611: Minor CSS class reordering.The reordering of CSS classes in the className string doesn't affect functionality.
apps/mail/components/create/email-composer.tsx (5)
107-107: LGTM: New state for tracking subject generation progress.This looks good, following the same pattern as other loading state management in the component.
112-114: LGTM: New mutation hook for email subject generation.Well implemented following the existing pattern for TRPC mutation hooks.
693-697: LGTM: Blur effect when showing AI-generated content.Good implementation using conditional classes with the
cnutility function.
926-926: LGTM: Updated button visibility class.Removed the 'hidden' class to make the button always visible but still responsive with 'md:flex'.
1002-1002: LGTM: Added dark mode styling for content preview.Added dark mode background support with
dark:bg-subtleBlack.
| const generateSubject = async (message: string, styleProfile?: WritingStyleMatrix | null) => { | ||
| const parts: string[] = []; | ||
|
|
||
| parts.push('# Email Subject Generation Task'); | ||
| if (styleProfile) { | ||
| parts.push('## Style Profile'); | ||
| parts.push(`\`\`\`json | ||
| ${JSON.stringify(styleProfile, null, 2)} | ||
| \`\`\``); | ||
| } | ||
|
|
||
| parts.push('## Email Content'); | ||
| parts.push(escapeXml(message)); | ||
| parts.push(''); | ||
| parts.push( | ||
| 'Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.', | ||
| ); | ||
|
|
||
| const { text } = await generateText({ | ||
| model: openai('gpt-4o'), | ||
| messages: [ | ||
| { | ||
| role: 'system', | ||
| content: | ||
| 'You are an email subject line generator. Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.', | ||
| }, | ||
| { | ||
| role: 'user', | ||
| content: parts.join('\n\n'), | ||
| }, | ||
| ], | ||
| maxTokens: 50, | ||
| temperature: 0.3, | ||
| frequencyPenalty: 0.1, | ||
| presencePenalty: 0.1, | ||
| maxRetries: 1, | ||
| }); | ||
|
|
||
| return text.trim(); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve subject generation function with cleaner structure and error handling.
The generateSubject function has several areas for improvement:
- Duplicate instructions in both system and user message
- Missing error handling
- Hardcoded parameters that should be constants
Apply these improvements:
+const EMAIL_SUBJECT_MAX_LENGTH = 100;
+const SUBJECT_GENERATION_PARAMS = {
+ maxTokens: 50,
+ temperature: 0.3,
+ frequencyPenalty: 0.1,
+ presencePenalty: 0.1,
+ maxRetries: 1,
+};
const generateSubject = async (message: string, styleProfile?: WritingStyleMatrix | null) => {
const parts: string[] = [];
parts.push('# Email Subject Generation Task');
if (styleProfile) {
parts.push('## Style Profile');
parts.push(`\`\`\`json
${JSON.stringify(styleProfile, null, 2)}
\`\`\``);
}
parts.push('## Email Content');
parts.push(escapeXml(message));
parts.push('');
parts.push(
- 'Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.',
+ `Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under ${EMAIL_SUBJECT_MAX_LENGTH} characters.`,
);
+ try {
const { text } = await generateText({
model: openai('gpt-4o'),
messages: [
{
role: 'system',
content:
- 'You are an email subject line generator. Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.',
+ `You are an email subject line generator. Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under ${EMAIL_SUBJECT_MAX_LENGTH} characters.`,
},
{
role: 'user',
content: parts.join('\n\n'),
},
],
- maxTokens: 50,
- temperature: 0.3,
- frequencyPenalty: 0.1,
- presencePenalty: 0.1,
- maxRetries: 1,
+ ...SUBJECT_GENERATION_PARAMS,
});
return text.trim();
+ } catch (error) {
+ console.error('Failed to generate subject:', error);
+ throw error;
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const generateSubject = async (message: string, styleProfile?: WritingStyleMatrix | null) => { | |
| const parts: string[] = []; | |
| parts.push('# Email Subject Generation Task'); | |
| if (styleProfile) { | |
| parts.push('## Style Profile'); | |
| parts.push(`\`\`\`json | |
| ${JSON.stringify(styleProfile, null, 2)} | |
| \`\`\``); | |
| } | |
| parts.push('## Email Content'); | |
| parts.push(escapeXml(message)); | |
| parts.push(''); | |
| parts.push( | |
| 'Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.', | |
| ); | |
| const { text } = await generateText({ | |
| model: openai('gpt-4o'), | |
| messages: [ | |
| { | |
| role: 'system', | |
| content: | |
| 'You are an email subject line generator. Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under 100 characters.', | |
| }, | |
| { | |
| role: 'user', | |
| content: parts.join('\n\n'), | |
| }, | |
| ], | |
| maxTokens: 50, | |
| temperature: 0.3, | |
| frequencyPenalty: 0.1, | |
| presencePenalty: 0.1, | |
| maxRetries: 1, | |
| }); | |
| return text.trim(); | |
| }; | |
| const EMAIL_SUBJECT_MAX_LENGTH = 100; | |
| const SUBJECT_GENERATION_PARAMS = { | |
| maxTokens: 50, | |
| temperature: 0.3, | |
| frequencyPenalty: 0.1, | |
| presencePenalty: 0.1, | |
| maxRetries: 1, | |
| }; | |
| const generateSubject = async (message: string, styleProfile?: WritingStyleMatrix | null) => { | |
| const parts: string[] = []; | |
| parts.push('# Email Subject Generation Task'); | |
| if (styleProfile) { | |
| parts.push('## Style Profile'); | |
| parts.push(`\`\`\`json | |
| ${JSON.stringify(styleProfile, null, 2)} | |
| \`\`\``); | |
| } | |
| parts.push('## Email Content'); | |
| parts.push(escapeXml(message)); | |
| parts.push(''); | |
| parts.push( | |
| `Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under ${EMAIL_SUBJECT_MAX_LENGTH} characters.`, | |
| ); | |
| try { | |
| const { text } = await generateText({ | |
| model: openai('gpt-4o'), | |
| messages: [ | |
| { | |
| role: 'system', | |
| content: `You are an email subject line generator. Generate a concise, clear subject line that summarizes the main point of the email. The subject should be professional and under ${EMAIL_SUBJECT_MAX_LENGTH} characters.`, | |
| }, | |
| { | |
| role: 'user', | |
| content: parts.join('\n\n'), | |
| }, | |
| ], | |
| ...SUBJECT_GENERATION_PARAMS, | |
| }); | |
| return text.trim(); | |
| } catch (error) { | |
| console.error('Failed to generate subject:', error); | |
| throw error; | |
| } | |
| }; |
| export const generateEmailSubject = activeConnectionProcedure | ||
| .input( | ||
| z.object({ | ||
| message: z.string(), | ||
| }), | ||
| ) | ||
| .mutation(async ({ ctx, input }) => { | ||
| const { activeConnection } = ctx; | ||
| const { message } = input; | ||
|
|
||
| const writingStyleMatrix = await getWritingStyleMatrixForConnectionId({ | ||
| connectionId: activeConnection.id, | ||
| c: ctx.c, | ||
| }); | ||
|
|
||
| const subject = await generateSubject(message, writingStyleMatrix?.style as WritingStyleMatrix); | ||
|
|
||
| return { | ||
| subject, | ||
| }; | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Missing error handling for AI service failures.
The new generateEmailSubject procedure doesn't include any error handling if the AI model fails to generate a subject or if the service times out.
Add a try/catch block to gracefully handle errors:
export const generateEmailSubject = activeConnectionProcedure
.input(
z.object({
message: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
const { activeConnection } = ctx;
const { message } = input;
const writingStyleMatrix = await getWritingStyleMatrixForConnectionId({
connectionId: activeConnection.id,
c: ctx.c,
});
+ try {
const subject = await generateSubject(message, writingStyleMatrix?.style as WritingStyleMatrix);
return {
subject,
};
+ } catch (error) {
+ console.error('Error generating email subject:', error);
+ throw new Error('Failed to generate email subject. Please try again later.');
+ }
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const generateEmailSubject = activeConnectionProcedure | |
| .input( | |
| z.object({ | |
| message: z.string(), | |
| }), | |
| ) | |
| .mutation(async ({ ctx, input }) => { | |
| const { activeConnection } = ctx; | |
| const { message } = input; | |
| const writingStyleMatrix = await getWritingStyleMatrixForConnectionId({ | |
| connectionId: activeConnection.id, | |
| c: ctx.c, | |
| }); | |
| const subject = await generateSubject(message, writingStyleMatrix?.style as WritingStyleMatrix); | |
| return { | |
| subject, | |
| }; | |
| }); | |
| export const generateEmailSubject = activeConnectionProcedure | |
| .input( | |
| z.object({ | |
| message: z.string(), | |
| }), | |
| ) | |
| .mutation(async ({ ctx, input }) => { | |
| const { activeConnection } = ctx; | |
| const { message } = input; | |
| const writingStyleMatrix = await getWritingStyleMatrixForConnectionId({ | |
| connectionId: activeConnection.id, | |
| c: ctx.c, | |
| }); | |
| try { | |
| const subject = await generateSubject( | |
| message, | |
| writingStyleMatrix?.style as WritingStyleMatrix, | |
| ); | |
| return { | |
| subject, | |
| }; | |
| } catch (error) { | |
| console.error('Error generating email subject:', error); | |
| throw new Error('Failed to generate email subject. Please try again later.'); | |
| } | |
| }); |
| const handleNext = useCallback( | ||
| (id: string) => { | ||
| if (!id || !threads.length || focusedIndex === null) return setThreadId(null); | ||
| if (focusedIndex < threads.length - 1) { | ||
| const nextThread = threads[focusedIndex]; | ||
| if (nextThread) { | ||
| setThreadId(nextThread.id); | ||
| setActiveReplyId(null); | ||
| setFocusedIndex(focusedIndex); | ||
| } | ||
| } | ||
| }, | ||
| [threads, id, focusedIndex], | ||
| ); |
There was a problem hiding this comment.
Fix the thread navigation logic in handleNext function.
The handleNext function has a logic issue - it's not actually navigating to the next thread, but instead using the current thread position.
Apply this fix:
const handleNext = useCallback(
(id: string) => {
if (!id || !threads.length || focusedIndex === null) return setThreadId(null);
if (focusedIndex < threads.length - 1) {
- const nextThread = threads[focusedIndex];
+ const nextThread = threads[focusedIndex + 1];
if (nextThread) {
setThreadId(nextThread.id);
setActiveReplyId(null);
- setFocusedIndex(focusedIndex);
+ setFocusedIndex(focusedIndex + 1);
}
}
},
- [threads, id, focusedIndex],
+ [threads, focusedIndex, setThreadId, setActiveReplyId, setFocusedIndex],
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleNext = useCallback( | |
| (id: string) => { | |
| if (!id || !threads.length || focusedIndex === null) return setThreadId(null); | |
| if (focusedIndex < threads.length - 1) { | |
| const nextThread = threads[focusedIndex]; | |
| if (nextThread) { | |
| setThreadId(nextThread.id); | |
| setActiveReplyId(null); | |
| setFocusedIndex(focusedIndex); | |
| } | |
| } | |
| }, | |
| [threads, id, focusedIndex], | |
| ); | |
| const handleNext = useCallback( | |
| (id: string) => { | |
| if (!id || !threads.length || focusedIndex === null) return setThreadId(null); | |
| if (focusedIndex < threads.length - 1) { | |
| const nextThread = threads[focusedIndex + 1]; | |
| if (nextThread) { | |
| setThreadId(nextThread.id); | |
| setActiveReplyId(null); | |
| setFocusedIndex(focusedIndex + 1); | |
| } | |
| } | |
| }, | |
| [threads, focusedIndex, setThreadId, setActiveReplyId, setFocusedIndex], | |
| ); |
| <button | ||
| className="" | ||
| onClick={handleGenerateSubject} | ||
| disabled={isLoading || isGeneratingSubject} | ||
| > | ||
| <div className="flex items-center justify-center gap-2.5 pl-0.5"> | ||
| <div className="flex h-5 items-center justify-center gap-1 rounded-sm"> | ||
| {isGeneratingSubject ? ( | ||
| <Loader className="h-3.5 w-3.5 animate-spin fill-black dark:fill-white" /> | ||
| ) : ( | ||
| <Sparkles className="h-3.5 w-3.5 fill-black dark:fill-white" /> | ||
| )} | ||
| </div> | ||
| </div> | ||
| </button> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve button accessibility and styling.
The button has an empty className prop and lacks accessibility attributes. Add an aria-label and tooltip to explain the button's purpose for better user experience.
<button
- className=""
+ className="focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#8B5CF6] rounded-md"
onClick={handleGenerateSubject}
disabled={isLoading || isGeneratingSubject}
+ aria-label="Generate email subject using AI"
>
<div className="flex items-center justify-center gap-2.5 pl-0.5">
<div className="flex h-5 items-center justify-center gap-1 rounded-sm">
{isGeneratingSubject ? (
<Loader className="h-3.5 w-3.5 animate-spin fill-black dark:fill-white" />
) : (
<Sparkles className="h-3.5 w-3.5 fill-black dark:fill-white" />
)}
</div>
</div>
</button>Consider wrapping this button in a Tooltip component like other action buttons in this component:
<Tooltip>
<TooltipTrigger asChild>
<button>
{/* button content */}
</button>
</TooltipTrigger>
<TooltipContent>
<p>Generate AI subject</p>
</TooltipContent>
</Tooltip>| const handleGenerateSubject = async () => { | ||
| setIsGeneratingSubject(true); | ||
| const { subject } = await generateEmailSubject({ message: editor.getText() }); | ||
| setValue('subject', subject); | ||
| setIsGeneratingSubject(false); | ||
| }; |
There was a problem hiding this comment.
Add error handling to prevent UI from getting stuck.
The function lacks error handling. If the API call fails, isGeneratingSubject will remain true, leaving the UI in a perpetual loading state.
const handleGenerateSubject = async () => {
setIsGeneratingSubject(true);
- const { subject } = await generateEmailSubject({ message: editor.getText() });
- setValue('subject', subject);
- setIsGeneratingSubject(false);
+ try {
+ const { subject } = await generateEmailSubject({ message: editor.getText() });
+ setValue('subject', subject);
+ toast.success('Subject generated successfully');
+ } catch (error) {
+ console.error('Error generating subject:', error);
+ toast.error('Failed to generate subject');
+ } finally {
+ setIsGeneratingSubject(false);
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleGenerateSubject = async () => { | |
| setIsGeneratingSubject(true); | |
| const { subject } = await generateEmailSubject({ message: editor.getText() }); | |
| setValue('subject', subject); | |
| setIsGeneratingSubject(false); | |
| }; | |
| const handleGenerateSubject = async () => { | |
| setIsGeneratingSubject(true); | |
| try { | |
| const { subject } = await generateEmailSubject({ message: editor.getText() }); | |
| setValue('subject', subject); | |
| toast.success('Subject generated successfully'); | |
| } catch (error) { | |
| console.error('Error generating subject:', error); | |
| toast.error('Failed to generate subject'); | |
| } finally { | |
| setIsGeneratingSubject(false); | |
| } | |
| }; |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
README.md (2)
83-83: Improve phrasing and grammar for Cloudflare setup instruction.The current line reads:
- Setup cloudflare with `bun run cf-install`, you will need to run this everytime there is a `.env` changeSuggested rewrite for clarity, correctness, and consistency (capitalize “Cloudflare”, split “every time”, and reference “the
.envfile”):- - Setup cloudflare with `bun run cf-install`, you will need to run this everytime there is a `.env` change + - Set up Cloudflare integration by running `bun run cf-install` after any change to the `.env` file to sync environment variables.
214-214: Fix grammar in environment sync instruction.The current sentence has a missing article and an incorrect verb form:
Make sure your database connection string is in `.env` file. And you have ran `bun run cf-install` to sync the latest env.Please apply this diff for grammatical accuracy and clarity:
- Make sure your database connection string is in `.env` file. And you have ran `bun run cf-install` to sync the latest env. + Make sure your database connection string is in the `.env` file, and you have run `bun run cf-install` to sync the latest environment variables.🧰 Tools
🪛 LanguageTool
[uncategorized] ~214-~214: You might be missing the article “the” here.
Context: ...e your database connection string is in.envfile. And you have ran `bun run cf...(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[grammar] ~214-~214: It appears that the past participle should be used here.
Context: ... string is in.envfile. And you have ranbun run cf-installto sync the latest...(HAVE_PART_AGREEMENT)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.env.example(1 hunks).github/CONTRIBUTING.md(1 hunks)README.md(3 hunks)
✅ Files skipped from review due to trivial changes (2)
- .env.example
- .github/CONTRIBUTING.md
🧰 Additional context used
🪛 LanguageTool
README.md
[uncategorized] ~214-~214: You might be missing the article “the” here.
Context: ...e your database connection string is in .env file. And you have ran `bun run cf...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[grammar] ~214-~214: It appears that the past participle should be used here.
Context: ... string is in .env file. And you have ran bun run cf-install to sync the latest...
(HAVE_PART_AGREEMENT)
🔇 Additional comments (1)
README.md (1)
152-152: Confirm OAuth redirect URI update across all configurations.You’ve updated the development callback URI to:
- `http://localhost:8787/api/auth/callback/google`Please verify that this change is reflected consistently in:
.env.example- NextAuth (or Better Auth) configuration files
- Any CI/CD or deployment workflows (e.g., GitHub Actions secrets)
READ CAREFULLY THEN REMOVE
Remove bullet points that are not relevant.
PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI.
Description
Please provide a clear description of your changes.
Type of Change
Please delete options that are not relevant.
Areas Affected
Please check all that apply:
Testing Done
Describe the tests you've done:
Security Considerations
For changes involving data or authentication:
Checklist
Additional Notes
Add any other context about the pull request here.
Screenshots/Recordings
Add screenshots or recordings here if applicable.
By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.
Summary by CodeRabbit
New Features
Improvements
Documentation