Add prompt template selection to general settings page#1354
Add prompt template selection to general settings page#1354MrgSub merged 9 commits intoMail-0:stagingfrom
Conversation
WalkthroughThis set of changes implements customizable AI system prompts for chat and email composition features. It introduces editable prompt management in the UI, adds backend support for storing and updating prompts per connection and type, and updates server logic to fetch and use these prompts dynamically. Minor formatting and enum adjustments are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PromptsDialog (UI)
participant Server API
participant Storage
User->>PromptsDialog (UI): Edit prompt in form
User->>PromptsDialog (UI): Click "Save"
PromptsDialog (UI)->>Server API: updatePrompt(promptType, content)
Server API->>Storage: Store prompt content by connection and type
Storage-->>Server API: Confirmation
Server API-->>PromptsDialog (UI): Success response
PromptsDialog (UI)-->>User: Show success toast
User->>PromptsDialog (UI): Click "Reset to Default"
PromptsDialog (UI)-->>User: Reset field to default value
sequenceDiagram
participant Chat/Compose Feature
participant Server API
participant Storage
Chat/Compose Feature->>Server API: Request system prompt for connection/type
Server API->>Storage: Fetch prompt by key
alt Prompt exists and valid
Storage-->>Server API: Custom prompt
else Prompt missing/undefined
Server API-->>Storage: Store fallback prompt
Storage-->>Server API: Fallback prompt
end
Server API-->>Chat/Compose Feature: Return prompt
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
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: 1
🧹 Nitpick comments (1)
apps/mail/config/promptTemplates.ts (1)
7-91: Make the template list immutable for stronger type-safetyIf you annotate the array with
as const, each object literal becomes readonly and everyid,label,contentliteral is inferred as a string literal instead of plainstring.
That prevents accidental mutation and enables exhaustive checks elsewhere.-export const promptTemplates: PromptTemplate[] = [ +export const promptTemplates = [ /* … */ -] ; +] as const satisfies ReadonlyArray<PromptTemplate>;(Using
satisfieskeeps the explicitPromptTemplateguarantee while preserving literal types.)
No runtime impact, compile-time only.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/mail/app/(routes)/settings/general/page.tsx(2 hunks)apps/mail/config/promptTemplates.ts(1 hunks)
🔇 Additional comments (1)
apps/mail/app/(routes)/settings/general/page.tsx (1)
37-38: Potential wrong import alias – verify path
'@/config/promptTemplates'assumes the alias root containsconfig/.
In this repo the file lives underapps/mail/config/. Unless the alias maps to the mono-repo root, this will 404 at build time.
Double-check yourtsconfig.json/vitealias before merging.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/mail/locales/en.json (1)
388-391: Align key naming with existing conventions
The new dropdown labels use “Prompt Template”/“Choose a template” but other settings use “Select” (e.g.,selectLanguage,selectTimezone). Consider renaming the keychooseTemplatetoselectTemplateand updating its value to “Select a template” for a consistent user experience.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/mail/app/(routes)/settings/general/page.tsx(3 hunks)apps/mail/locales/en.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/mail/app/(routes)/settings/general/page.tsx
🔇 Additional comments (1)
apps/mail/locales/en.json (1)
388-391: Add missing translations for new keys
Ensure thatpromptTemplate,chooseTemplate(or if renamed,selectTemplate), andpromptTemplateDescriptionare also added to all other locale files so non-English users aren’t left without translations.
|
@retrogtx we have a dialog on the AI Chat sidebar that hosts the prompts, each prompt is unique to the user using these: |
i thought that we wanted the user itself to add a custom prompt |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/server/src/trpc/routes/brain.ts (1)
83-98: Well-implemented prompt storage with proper validation.The
updatePromptprocedure correctly:
- Uses
z.nativeEnum(EPrompts)for type-safe prompt type validation- Follows the consistent key pattern
${connectionId}-${promptType}- Provides appropriate success response
- Leverages the existing
activeConnectionProcedurefor authenticationConsider adding rate limiting for this mutation in a future enhancement to prevent abuse.
apps/mail/components/ui/prompts-dialog.tsx (1)
48-52: Consider consolidating state management.Having separate state variables for each prompt type works but could be simplified with a single state object:
-const [chatPrompt, setChatPrompt] = useState(''); -const [composePrompt, setComposePrompt] = useState(''); -const [summarizeThread, setSummarizeThread] = useState(''); -const [reSummarizeThread, setReSummarizeThread] = useState(''); -const [summarizeMessage, setSummarizeMessage] = useState(''); +const [prompts, setPrompts] = useState({ + chat: '', + compose: '', + summarizeThread: '', + reSummarizeThread: '', + summarizeMessage: '', +});
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/mail/components/ui/prompts-dialog.tsx(2 hunks)apps/server/src/lib/brain.ts(3 hunks)apps/server/src/routes/chat.ts(2 hunks)apps/server/src/trpc/routes/ai/compose.ts(2 hunks)apps/server/src/trpc/routes/brain.ts(2 hunks)apps/server/src/types.ts(1 hunks)
🔇 Additional comments (13)
apps/server/src/types.ts (1)
217-218: LGTM! Enum additions support the new prompt management feature.The uncommented
Chatand newly addedComposeenum members properly extend the prompt types to support the dynamic prompt management system being introduced.apps/server/src/trpc/routes/ai/compose.ts (2)
6-7: LGTM! Clean imports for the new prompt management functionality.The added imports support the transition from static to dynamic prompt retrieval.
38-41: Well-implemented dynamic prompt retrieval with proper fallback.The change from static
StyledEmailAssistantSystemPrompt()to dynamicgetPrompt()with fallback maintains backward compatibility while enabling per-connection prompt customization. The key construction using${connectionId}-${EPrompts.Compose}follows a consistent pattern.apps/server/src/routes/chat.ts (2)
15-16: LGTM! Consistent imports with the compose route.The imports align with the dynamic prompt management pattern established across the codebase.
149-149: Excellent consistency with the compose route implementation.The dynamic prompt retrieval follows the same pattern as in
compose.ts, using connection-specific keys and proper fallbacks. This ensures consistent behavior across chat and compose functionalities.apps/server/src/trpc/routes/brain.ts (1)
2-2: LGTM! Clean import addition.Adding
EPromptsto the existing import statement maintains organization.apps/mail/components/ui/prompts-dialog.tsx (3)
21-28: Clean import additions supporting the new functionality.The imports properly support the transition to editable prompts with state management, mutations, and default prompt handling.
36-46: Well-implemented mutation with proper error handling.The
updatePromptmutation correctly handles success/error states with toast notifications and query invalidation. The loading state management withisSavingPromptprovides good UX feedback.
118-140: Excellent transformation to editable interface.The chat tab implementation correctly:
- Provides clear context about the prompt's usage
- Uses controlled textarea with proper event handling
- Implements save functionality with proper loading state
- Maintains consistent UI patterns
The same quality is maintained across all prompt type tabs.
apps/server/src/lib/brain.ts (4)
2-2: LGTM: Import statement correctly adds new prompt functions.The import statement properly adds the two new prompt functions that are used as fallbacks for the Chat and Compose prompt types.
45-46: LGTM: New prompt types properly added to the prompts record.The new
ChatandComposeprompt types are correctly added to the prompts record, maintaining consistency with the existing structure.
33-33: Let’s pull in the context aroundexistingPromptand see how prompts are being read/written:#!/bin/bash # Show the beginning of brain.ts where existingPrompt is used sed -n '1,200p' apps/server/src/lib/brain.ts # Find how prompts get persisted or retrieved rg -nE "db\.|prisma|save|create|update|persist|store" -g "apps/server/src/lib/brain.ts"
53-54: Let’s check whether and how those parameters (threadId,currentFolder,currentFilter) actually get interpolated into the prompt:#!/bin/bash # Search for parameter usage in the server’s AiChatPrompt template rg -n "threadId" apps/server/src/lib/prompts.ts rg -n "currentFolder" apps/server/src/lib/prompts.ts rg -n "currentFilter" apps/server/src/lib/prompts.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/mail/components/ui/prompts-dialog.tsx (2)
41-51: Consider more granular loading states.The current implementation disables all save buttons when any prompt is being saved (
isSavingPrompt). This creates a poor user experience as users cannot save other prompts while one is being saved.Consider tracking loading state per prompt type for better UX.
-const { mutateAsync: updatePrompt, isPending: isSavingPrompt } = useMutation( +const { mutateAsync: updatePrompt, isPending: isSavingPrompt, variables: savingPromptType } = useMutation( trpc.brain.updatePrompt.mutationOptions({ onSuccess: () => { toast.success('Prompt updated'); queryClient.invalidateQueries({ queryKey: trpc.brain.getPrompts.queryKey() }); }, onError: (error) => { toast.error(error.message ?? 'Failed to update prompt'); }, }), ); // Then in the button disabled prop: -disabled={isSavingPrompt} +disabled={isSavingPrompt && savingPromptType?.promptType === EPrompts.Chat}
138-161: Consider extracting repetitive tab content into a reusable component.The tab content implementation is highly repetitive across all five prompt types. This violates the DRY principle and makes maintenance more difficult.
Create a reusable
PromptTabContentcomponent:interface PromptTabContentProps { promptType: EPrompts; fieldName: keyof PromptFormValues; description: string; register: UseFormRegister<PromptFormValues>; getValues: UseFormGetValues<PromptFormValues>; updatePrompt: (data: { promptType: EPrompts; content: string }) => void; isSaving: boolean; } const PromptTabContent: React.FC<PromptTabContentProps> = ({ promptType, fieldName, description, register, getValues, updatePrompt, isSaving }) => ( <div className="space-y-2"> <span className="text-muted-foreground mb-2 flex gap-2 text-sm"> {description} </span> <Textarea className="min-h-60" {...register(fieldName)} /> <Button size="sm" onClick={() => updatePrompt({ promptType, content: getValues(fieldName), })} disabled={isSaving} > Save </Button> </div> );Also applies to: 162-184, 185-207, 210-231, 234-255
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/mail/app/(routes)/settings/general/page.tsx(3 hunks)apps/mail/components/ui/prompts-dialog.tsx(3 hunks)apps/mail/config/promptTemplates.ts(1 hunks)apps/mail/locales/en.json(1 hunks)apps/server/src/lib/brain.ts(3 hunks)apps/server/src/routes/chat.ts(2 hunks)apps/server/src/trpc/routes/ai/compose.ts(2 hunks)apps/server/src/trpc/routes/brain.ts(2 hunks)apps/server/src/types.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/mail/locales/en.json
- apps/server/src/types.ts
- apps/server/src/trpc/routes/ai/compose.ts
- apps/server/src/trpc/routes/brain.ts
- apps/server/src/routes/chat.ts
- apps/mail/app/(routes)/settings/general/page.tsx
- apps/server/src/lib/brain.ts
- apps/mail/config/promptTemplates.ts
🔇 Additional comments (2)
apps/mail/components/ui/prompts-dialog.tsx (2)
31-34: Good refactoring of validation logic.The
isPromptValidhelper function successfully addresses the previous review comment about reducing code duplication. The implementation is clean and reusable.
117-117: Minor improvement to dialog description.The shortened description is more concise and reads better than the previous version.
| }), | ||
| ); | ||
|
|
||
| type PromptFormValues = { |
There was a problem hiding this comment.
replace with
const fallbackPrompts = {
[EPrompts.Chat]: AiChatPrompt('', '', ''),
[EPrompts.Compose]: StyledEmailAssistantSystemPrompt(),
[EPrompts.SummarizeThread]: SummarizeThread,
[EPrompts.ReSummarizeThread]: ReSummarizeThread,
[EPrompts.SummarizeMessage]: SummarizeMessage,
};
| summarizeMessage: string; | ||
| }; | ||
|
|
||
| const initialValues: PromptFormValues = { |
There was a problem hiding this comment.
const initialValues: Record<EPrompts, string> = {
[EPrompts.Chat]: '',
[EPrompts.Compose]: '',
[EPrompts.SummarizeThread]: '',
[EPrompts.ReSummarizeThread]: '',
[EPrompts.SummarizeMessage]: '',
};
There was a problem hiding this comment.
also static objects should be outside of the component
| summarizeMessage: '', | ||
| }; | ||
|
|
||
| const mappedValues = useMemo<PromptFormValues>(() => { |
There was a problem hiding this comment.
const mappedValues = useMemo(() => {
if (!prompts) return initialValues;
return Object.fromEntries(
Object.entries(initialValues).map(([key]) => [
key,
isPromptValid(prompts[key as EPrompts] ?? '')
? prompts[key as EPrompts]
: fallbackPrompts[key as EPrompts],
]),
) as Record<EPrompts, string>;
}, [prompts]);
| values: mappedValues, | ||
| }); | ||
|
|
||
| const resetToDefault = (promptType: keyof PromptFormValues) => { |
There was a problem hiding this comment.
const resetToDefault = (promptType: EPrompts) => {
setValue(promptType, fallbackPrompts[promptType]);
};
| className="min-h-60" | ||
| {...register('chatPrompt')} | ||
| /> | ||
| {renderPromptButtons('chatPrompt', EPrompts.Chat)} |
There was a problem hiding this comment.
<Textarea className="min-h-60" {...register(EPrompts.Chat)} />
{renderPromptButtons(EPrompts.Chat, EPrompts.Chat)}
MrgSub
left a comment
There was a problem hiding this comment.
Cleaner code, use enums instead of hardcoded strings
Description
Solves Linear ZERO-142
Added templates for the system prompt, just like mentioned in the article linked in the linear post
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:
Checklist
Screenshots/Recordings
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
Bug Fixes
Refactor
Chores