Skip to content

Comments

Add prompt template selection to general settings page#1354

Merged
MrgSub merged 9 commits intoMail-0:stagingfrom
retrogtx:template
Jun 22, 2025
Merged

Add prompt template selection to general settings page#1354
MrgSub merged 9 commits intoMail-0:stagingfrom
retrogtx:template

Conversation

@retrogtx
Copy link
Contributor

@retrogtx retrogtx commented Jun 18, 2025

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.

  • ✨ New feature (non-breaking change which adds functionality)

Areas Affected

Please check all that apply:

  • User Interface/Experience
  • Natural Language

Testing Done

Describe the tests you've done:

  • Manual testing performed

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • All tests pass locally
  • Any dependent changes are merged and published

Screenshots/Recordings

image

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

    • Added the ability for users to edit and save AI system prompts for multiple categories (chat, compose, summarize, etc.) directly within the prompts dialog, with options to reset prompts to default values.
    • Support for two new prompt types: Chat and Compose, allowing further customization.
  • Bug Fixes

    • Improved handling of undefined or missing prompts, ensuring fallback to default values.
  • Refactor

    • Updated prompts dialog interface to use an editable form with validation and user feedback.
  • Chores

    • Expanded prompt type enum to include Chat and Compose.
    • Added backend support for updating and storing customized prompts per user connection.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 18, 2025

Walkthrough

This 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

File(s) Change Summary
apps/mail/app/(routes)/settings/general/page.tsx Added a single blank line inside the form element; no logic or structural changes.
apps/mail/components/ui/prompts-dialog.tsx Refactored from static prompt display to an editable, form-driven interface with save/reset and async updates.
apps/server/src/lib/brain.ts Added support for Chat and Compose prompt types, improved fallback handling, and removed legacy comments.
apps/server/src/routes/chat.ts Changed system prompt retrieval to use dynamic, per-connection prompt via getPrompt for chat streaming.
apps/server/src/trpc/routes/ai/compose.ts Updated to fetch system prompt asynchronously using getPrompt for compose functionality.
apps/server/src/trpc/routes/brain.ts Added updatePrompt mutation for storing/updating prompts by connection and type.
apps/server/src/types.ts Updated EPrompts enum: uncommented Chat and added Compose members.

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
Loading
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
Loading

Suggested reviewers

  • MrgSub

Poem

In the warren of code where prompts now reside,
A rabbit with forms let users decide—
To chat or compose, with words of their own,
Saving and resetting from fields newly grown.
With enums and storage, the backend’s in tune,
Hopping through prompts—improvements in bloom!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c28d7e and 0535091.

📒 Files selected for processing (1)
  • apps/mail/components/ui/prompts-dialog.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/components/ui/prompts-dialog.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/mail/config/promptTemplates.ts (1)

7-91: Make the template list immutable for stronger type-safety

If you annotate the array with as const, each object literal becomes readonly and every id, label, content literal is inferred as a string literal instead of plain string.
That prevents accidental mutation and enables exhaustive checks elsewhere.

-export const promptTemplates: PromptTemplate[] = [
+export const promptTemplates = [
   /* … */
-] ;
+] as const satisfies ReadonlyArray<PromptTemplate>;

(Using satisfies keeps the explicit PromptTemplate guarantee while preserving literal types.)
No runtime impact, compile-time only.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d049e9e and 3f6efc5.

📒 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 contains config/.
In this repo the file lives under apps/mail/config/. Unless the alias maps to the mono-repo root, this will 404 at build time.
Double-check your tsconfig.json/vite alias before merging.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 key chooseTemplate to selectTemplate and updating its value to “Select a template” for a consistent user experience.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6efc5 and 297f114.

📒 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 that promptTemplate, chooseTemplate (or if renamed, selectTemplate), and promptTemplateDescription are also added to all other locale files so non-English users aren’t left without translations.

@MrgSub
Copy link
Collaborator

MrgSub commented Jun 18, 2025

@retrogtx we have a dialog on the AI Chat sidebar that hosts the prompts, each prompt is unique to the user using these:

  return `${connectionId}-${prompt}`;
};

export const getPrompt = async (promptName: string, fallback: string) => {
  try {
    const existingPrompt = await env.prompts_storage.get(promptName);
    if (!existingPrompt) {
      await env.prompts_storage.put(promptName, fallback);
      return fallback;
    }
    return existingPrompt;
  } catch (error) {
    log('[GET_PROMPT] Failed to get prompt:', {
      promptName,
      error: error instanceof Error ? error.message : String(error),
    });
    return fallback;
  }
};```

Can you upload the dialog to fetch the user's unique prompts and allow them to update? it's not in the settings page

@retrogtx
Copy link
Contributor Author

@retrogtx we have a dialog on the AI Chat sidebar that hosts the prompts, each prompt is unique to the user using these:

  return `${connectionId}-${prompt}`;
};

export const getPrompt = async (promptName: string, fallback: string) => {
  try {
    const existingPrompt = await env.prompts_storage.get(promptName);
    if (!existingPrompt) {
      await env.prompts_storage.put(promptName, fallback);
      return fallback;
    }
    return existingPrompt;
  } catch (error) {
    log('[GET_PROMPT] Failed to get prompt:', {
      promptName,
      error: error instanceof Error ? error.message : String(error),
    });
    return fallback;
  }
};```

Can you upload the dialog to fetch the user's unique prompts and allow them to update? it's not in the settings page

i thought that we wanted the user itself to add a custom prompt

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 updatePrompt procedure 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 activeConnectionProcedure for authentication

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between 297f114 and fe527d3.

📒 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 Chat and newly added Compose enum 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 dynamic getPrompt() 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 EPrompts to 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 updatePrompt mutation correctly handles success/error states with toast notifications and query invalidation. The loading state management with isSavingPrompt provides 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 Chat and Compose prompt types are correctly added to the prompts record, maintaining consistency with the existing structure.


33-33: Let’s pull in the context around existingPrompt and 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PromptTabContent component:

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

📥 Commits

Reviewing files that changed from the base of the PR and between bb04816 and edae605.

📒 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 isPromptValid helper 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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace with

const fallbackPrompts = {
  [EPrompts.Chat]: AiChatPrompt('', '', ''),
  [EPrompts.Compose]: StyledEmailAssistantSystemPrompt(),
  [EPrompts.SummarizeThread]: SummarizeThread,
  [EPrompts.ReSummarizeThread]: ReSummarizeThread,
  [EPrompts.SummarizeMessage]: SummarizeMessage,
};

summarizeMessage: string;
};

const initialValues: PromptFormValues = {
Copy link
Collaborator

@MrgSub MrgSub Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const initialValues: Record<EPrompts, string> = {
  [EPrompts.Chat]: '',
  [EPrompts.Compose]: '',
  [EPrompts.SummarizeThread]: '',
  [EPrompts.ReSummarizeThread]: '',
  [EPrompts.SummarizeMessage]: '',
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also static objects should be outside of the component

summarizeMessage: '',
};

const mappedValues = useMemo<PromptFormValues>(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  const resetToDefault = (promptType: EPrompts) => {
    setValue(promptType, fallbackPrompts[promptType]);
  };

className="min-h-60"
{...register('chatPrompt')}
/>
{renderPromptButtons('chatPrompt', EPrompts.Chat)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<Textarea className="min-h-60" {...register(EPrompts.Chat)} />
                {renderPromptButtons(EPrompts.Chat, EPrompts.Chat)}

Copy link
Collaborator

@MrgSub MrgSub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaner code, use enums instead of hardcoded strings

@MrgSub MrgSub merged commit b56b6f8 into Mail-0:staging Jun 22, 2025
3 checks passed
krakenftw pushed a commit to krakenftw/Zero that referenced this pull request Jun 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants