Skip to content

Comments

feat: Compose User Tone#764

Closed
dakdevs wants to merge 27 commits intoMail-0:mainfrom
dakdevs:dakdevs/ai-compose
Closed

feat: Compose User Tone#764
dakdevs wants to merge 27 commits intoMail-0:mainfrom
dakdevs:dakdevs/ai-compose

Conversation

@dakdevs
Copy link
Contributor

@dakdevs dakdevs commented Apr 25, 2025

Description

This implementation uses AI-generated metrics to gauge a rolling style and tone analysis as the user sends emails through Zero. This replaces the current generateEmailBody logic.

Things to do: (Either in this PR or another)

  • Generate production migrations.
  • Create a rolling style and classification of slang/words summary that updates and re-summarized based on new emails.
  • Fix subject generation as well using the same style matrix.
  • Update reply logic to use the same style matrix per connection.
  • Investigate any unwanted side-effects that may have been induced.

Questions: (Also, for now or later PRs)

  • Shall we feature flag this?
  • Should we have a settings toggle for this?

Ideas for later:

  • Fine-tuning a model on the user's threads and replies. Then using Google/Outlook's search to find context in previous emails to use in the new email.

Holistic (out-of-scope) suggestions:

  • Use tRPC for improved security and modularity, tRPC makes it easier to have specialized functions for any situation. One of the times when wet code is great for velocity.
  • Keep folders for services (internal server-side services), lib (external apis), and utils (modular utility functions).
  • Keep code co-located more.
    • Ex. Using tRPC we can have a main function with broken out functions for easier readability, high velocity refactors, and easy hoisting for reusability. This also makes creating v2 easy to add then deprecate quickly.)
    • Ex. Keep UI components that are not reused at time of creation co-located in _modules folders. This also makes things easy to find, quickly refactor, and branch off of for new version without causing unwanted breaking changes. Move UI components to @/components when they be come widely reusable and hoisting up levels in app folder isn't enough.

How to test

  1. Run bun db:push to push the database changes.
  2. Compose and send a new email below are some suggestions.
    • For testing it's recommended to delete your mail0_writing_style_matrix record in the database when switching style and tone in your tests.
  3. Have the AI generate a new email from a prompt.
  4. See how much it matches your previous email's style and tone.

Suggested email tones:

Professional

Hi Jordan,

I hope you are well. I would like to brief you on the upcoming Q3 feature rollout and confirm the implementation timeline for your team. Could we schedule a 15-minute call this Thursday or Friday? Please let me know which slot suits you and I will send a calendar invite.

Thank you for your time.

Best regards,
Dak

Friendly

Hey Jordan,

Hope things are going great. Our new feature is almost ready and I’d love to run it by you, answer any questions, and make sure the launch feels smooth on your side. Got 15 minutes free later this week? Shoot me a time that works and I’ll pop an invite on the calendar.

Talk soon!
Dak

Concise & Direct

Jordan,

Need a brief call to align on the rollout schedule. Are you free Thu 3 PM or Fri 10 AM? Pick one and I’ll set it up.

Thanks,
Dak

Persuasive / Value-Driven

Hi Jordan,

Our latest release streamlines user onboarding. Early testers report a 25 % reduction in setup time and a smoother first-day experience for admins. I would love to walk you through the workflow and outline next steps for your team’s rollout. Do you have 15 minutes on Thursday or Friday? Your feedback will help us tune the final touches.

Looking forward to your thoughts.

Cheers,
Dak

Very Casual

hey jordan 👋

hope your week’s chill! the new rollout is basically cooked and i wanna make sure it slaps for your crew. got like 15 min thurs or fri to hop on a call? drop a time that works and i’ll toss it on the cal.

catch ya soon,
dak

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature with breaking changes)
  • 🎨 UI/UX improvement

Areas Affected

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • Authentication/Authorization
  • Data Storage/Management
  • API Endpoints
  • Documentation
  • Testing Infrastructure
  • Development Workflow
  • Deployment/Infrastructure

Testing Done

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Security Considerations

For changes involving data or authentication:

  • No sensitive data is exposed
  • Authentication checks are in place
  • Input validation is implemented
  • Rate limiting is considered (if applicable)

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

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

    • Enhanced AI email generation with style-aware prompting and refined post-processing for clarity and safety.
    • Added comprehensive extraction of writing style metrics from emails for deeper analysis.
    • Implemented writing style profile tracking and incremental updates per email connection to personalize composition.
    • Introduced new structured prompts to guide AI in mirroring user writing style and extracting style data.
    • Email sending now includes asynchronous updates to writing style profiles to continuously improve personalization.
  • Chores

    • Configured npm to save exact package versions.
    • Locked "posthog-js" dependency to a fixed version and added "dompurify" to dependencies.

dakdevs added 3 commits April 24, 2025 22:39
The `bun.lock` file was deleted to declutter the repository and avoid potential dependency conflicts or outdated lock versions. This change ensures that dependency resolution relies on package definitions instead of pre-locked constraints.
Replace `generateEmailBody` with the updated and more robust `generateEmailBodyV2` in the actions layer. Adjust type annotations and streamline parameters to enhance clarity and maintainability.
@vercel
Copy link

vercel bot commented Apr 25, 2025

@dakdevs is attempting to deploy a commit to the Zero Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 25, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This update introduces a comprehensive writing style analysis and adaptation system for email generation. A new database table and service layer are added to store and update a "writing style matrix" for each email connection, capturing detailed stylistic metrics from sent emails. New prompt templates and AI utility functions enable generating email bodies that mimic the user's style, using advanced models and style-aware system prompts. The email sending workflow is extended to asynchronously update the style matrix after each sent message. Supporting changes include new dependencies, prompt functions, and minor code cleanups.

Changes

File(s) Change Summary
apps/mail/lib/ai.ts Refactored generateEmailBody to use OpenAI SDK generateText with GPT-4o and style-aware system prompts; added extractStyleMatrix to extract detailed writing style metrics from email bodies; introduced postProcessMessage for AI response cleanup and classification; minor formatting improvements.
apps/mail/lib/prompts.ts Added EmailAssistantPrompt for constructing email assistant prompts with context; added StyledEmailAssistantSystemPrompt for style-adaptive system prompts using writing style metrics; added StyleMatrixExtractorPrompt for extracting style metrics from email text; included XML escaping utilities.
apps/mail/services/writing-style-service.ts New module managing writing style matrices per connection; supports fetching and incremental updating of style metrics using running statistics; tracks greeting and sign-off frequencies with coverage and top-K limits; includes helper functions and type definitions.
packages/db/src/schema.ts Added new writing_style_matrix table schema with columns for connection ID, message count, JSONB style matrix, and update timestamp; includes primary key and foreign key constraints.
apps/mail/actions/ai.ts Removed conversationId parameter from generateAIEmailBody; updated call to generateEmailBody to use named parameters and renamed to to recipients; minor whitespace cleanup.
apps/mail/actions/send.ts Added post-send hook using after to asynchronously call updateWritingStyleMatrix after sending an email; includes logging before and after update and error handling; no signature changes.
apps/mail/components/create/ai-assistant.tsx Removed conversationId from call to generateAIEmailBody in handleSubmit; trimmed trailing whitespace; no logic changes.
apps/mail/package.json Locked "posthog-js" dependency to exact version "1.236.6"; added new dependency "dompurify" version "3.2.5".
.npmrc Added save-exact=true to enforce saving exact dependency versions in package.json.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MailApp
    participant AIService
    participant StyleService
    participant DB

    User->>MailApp: Compose & Send Email
    MailApp->>AIService: generateEmailBody(prompt, context, recipients, subject, userContext)
    AIService->>StyleService: getWritingStyleMatrix(connectionId)
    StyleService->>DB: Fetch style matrix
    DB-->>StyleService: Return style matrix
    StyleService-->>AIService: Style matrix
    AIService->>AIService: Generate email body using style-aware prompt
    AIService-->>MailApp: AI-generated email body
    MailApp->>MailApp: Send email via driver
    MailApp-->>User: Email sent confirmation
    MailApp->>StyleService: updateWritingStyleMatrix(connectionId, emailBody) (async)
    StyleService->>DB: Update style matrix with new metrics
    DB-->>StyleService: Confirm update
Loading

Possibly related PRs

  • Ai generate security #706: Related refactoring of generateAIEmailBody and generateEmailBody functions in apps/mail/actions/ai.ts with signature changes and improved error handling, overlapping with this PR’s modifications to AI email body generation.

Poem

In the warren, we study your style,
Each email, each word, we compile.
With metrics and prompts,
Our AI now romps—
Your voice, in each message, beguile!

🐇✨


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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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.

Deleted unused files in the mail app to clean up the repository. Updated the root `.npmrc` to enforce exact version saving for better dependency management. These changes improve clarity and maintainability.
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: 8

🔭 Outside diff range comments (1)
apps/mail/.node-version (1)

1-2: 💡 Verification agent

🧩 Analysis chain

Verify compatibility with Node.js 22

Node.js 22 is a very recent version (released in April 2024). Ensure that:

  1. Your critical dependencies are compatible with this version
  2. Your deployment environments support Node.js 22
  3. The team is aware of any breaking changes from previous Node.js versions

🏁 Script executed:

#!/bin/bash
# Check if there are any package.json files that might be affected
echo "Checking for package.json files in the mail app:"
find apps/mail -name "package.json" -type f | grep -v "node_modules"

# Check for engines field in package.json files
echo -e "\nChecking for Node.js engine requirements in package.json:"
find apps/mail -name "package.json" -type f -exec grep -l "\"engines\"" {} \; | 
  xargs -I{} bash -c 'echo "File: {}"; cat {} | jq ".engines"'

# Look for CI/CD configuration files to check deployment environments
echo -e "\nChecking CI/CD configurations:"
find . -name "*.yml" -o -name "*.yaml" | grep -E "github|gitlab|azure|jenkins|circleci|travis" | 
  xargs grep -l "node" 2>/dev/null

Length of output: 722


🏁 Script executed:

#!/bin/bash
echo "Scanning CI workflows for Node.js version configuration…"

echo -e "\n1. Checking for actions/setup-node usage:"
grep -R "actions/setup-node" -n .github/workflows/*.yml || echo "→ No setup-node action found."

echo -e "\n2. Checking for explicit node-version fields:"
grep -R "node-version" -n .github/workflows/*.yml || echo "→ No node-version field found."

echo -e "\n3. Checking for matrix definitions with node:"  
grep -R "matrix" -n .github/workflows/*.yml | grep -C3 "node" || echo "→ No node matrix found."

Length of output: 992


Ensure Node.js 22 Compatibility and Pin Version

Please update your configuration to explicitly target and verify Node.js 22:

• In apps/mail/package.json, add an “engines” field to lock to Node 22 (e.g.

"engines": {  
  "node": ">=22 <23"  
}

)
• In your GitHub workflows (.github/workflows/ci.yml & main.yml), replace node-version: latest with node-version: 22.x to guarantee CI runs on Node 22
• Confirm all critical dependencies have been tested on Node 22 and that your deployment environments support it
• Document any known breaking changes for the team’s awareness

🧹 Nitpick comments (6)
apps/mail/actions/ai.ts (2)

4-4: Remove unused import to avoid TS --noUnusedLocals breakage

generateEmailBody is no longer referenced after the switch to generateEmailBodyV2.

-import { generateEmailBody, generateEmailBodyV2, generateSubjectForEmail } from '@/lib/ai';
+import { generateEmailBodyV2, generateSubjectForEmail } from '@/lib/ai';

74-85: Early guard duplicates work – consider returning early

You test for an empty responseBody directly after assigning a default '', which will always be falsy. The guard therefore always triggers. Either drop the default or invert the check.

-const responseBody = response.body ?? '';
-if (!responseBody) {
+const responseBody = response.content?.trim() ?? '';
+if (responseBody === '') {
apps/mail/services/writing-style-service.ts (2)

165-174: updateStat lacks sample-count tracking → cannot derive variance later

You keep running m2 but never record n, so computing variance elsewhere as m2 / (n-1) won’t be possible.
Either:

  1. Store count alongside mean & m2, or
  2. Infer it from numMessages when reading, but document that contract.
export type RunningStat = {
  mean: number;
  m2: number;
  n: number; // <-- add
}

129-153: Coverage algorithm may drop the last needed element

takeWhile stops before the predicate fails, so the last element that pushes coverage ≥ 0.95 is excluded.
Switch to reduce or include the current element before evaluating coverage.

-    takeWhile(([_, count]) => {
-      running += count
-      return running / total < coverage
-    }),
+    takeWhile(([_, count]) => {
+      running += count
+      return running / total <= coverage   // include boundary
+    }),
apps/mail/.npmrc (1)

1-6: Risky npm flags – please confirm necessity

legacy-peer-deps=true and the two package-manager-strict* flags disable critical safety nets.
This can mask incompatible versions at install-time and make deterministic builds harder.

Recommend enabling strictness in CI (at least) or pinning peer ranges explicitly.

apps/mail/lib/ai.ts (1)

106-166: Duplicate post-processing logic – extract to shared util

postProcessMessage duplicates almost line-for-line the cleaning logic present further down in the legacy generateEmailBody. Maintaining two copies increases bug-fix surface and drift risk.

Refactor into a single helper (e.g., sanitizeGeneratedBody(text): AIBodyResponse[]) and invoke it from both functions.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c99d5ad and fa67c73.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/mail/.node-version (1 hunks)
  • apps/mail/.npmrc (1 hunks)
  • apps/mail/.tool-versions (1 hunks)
  • apps/mail/actions/ai.ts (5 hunks)
  • apps/mail/actions/send.ts (2 hunks)
  • apps/mail/lib/ai.ts (11 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/services/writing-style-service.ts (1 hunks)
  • package.json (1 hunks)
  • packages/db/src/schema.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/db/src/schema.ts (1)
apps/mail/services/writing-style-service.ts (1)
  • WritingStyleMatrix (221-229)
apps/mail/actions/ai.ts (1)
apps/mail/lib/ai.ts (1)
  • generateEmailBodyV2 (41-104)
apps/mail/lib/prompts.ts (1)
apps/mail/services/writing-style-service.ts (1)
  • WritingStyleMatrix (221-229)
🔇 Additional comments (4)
apps/mail/actions/ai.ts (1)

49-57:

Details

❌ Incorrect review comment

Type mismatch: AIBodyResponse defines content, but the code expects body

generateEmailBodyV2 returns AIBodyResponse[] where each element has content; yet lines 69-76 access response.body.
This will compile only if you’ve disabled strict type-checking or if postProcessMessage returns a different shape than declared. In either case one of them is wrong.

-const responseBody = response.body ?? '';
+const responseBody = (response.content ?? '').toString();

Update either the interface or the post-processing helper so that shapes align, then adjust every consumer accordingly.


🏁 Script executed:

#!/bin/bash
set -e

echo "=== Searching for generateEmailBodyV2 definition and usage ==="
rg "generateEmailBodyV2" -n

echo
echo "=== Searching for AIBodyResponse interface or type declaration ==="
rg "AIBodyResponse" -n

echo
echo "=== Searching for postProcessMessage definition ==="
rg "postProcessMessage" -n

Length of output: 1298


🏁 Script executed:

#!/bin/bash
set -e

echo "=== apps/mail/lib/ai.ts: Definitions and generateEmailBodyV2 ==="
sed -n '1,200p' apps/mail/lib/ai.ts

echo
echo "=== apps/mail/actions/ai.ts: Imported types, local interface, and usage ==="
sed -n '1,100p' apps/mail/actions/ai.ts

Length of output: 9693


Ignore the reported type mismatch – the interfaces are distinct per module

The AIBodyResponse in apps/mail/lib/ai.ts defines a body field, which is what generateEmailBodyV2 returns and what the action layer correctly consumes via response.body. The action file deliberately declares its own AIBodyResponse (with content and jsonContent) as the return type of generateAIEmailBody. There’s no misalignment in the shapes that would break strict mode on either side.

No changes are required to generateEmailBodyV2, postProcessMessage, or the consuming code.

Likely an incorrect or invalid review comment.

apps/mail/.tool-versions (1)

1-2: Consistent with .node-version file

This file properly aligns with the Node.js version (22) specified in the .node-version file. The .tool-versions file is used by the asdf version manager to specify tool versions, while .node-version is used by other version managers like nvm.

packages/db/src/schema.ts (2)

1-4: LGTM - Import for the WritingStyleMatrix type

The import for the WritingStyleMatrix type is correctly added to support the new database table schema.


134-147:

Details

✅ Verification successful

Well-structured database schema for the writing style matrix

The table schema is well-defined with proper references, data types, and constraints. The primary key is correctly set up on the connectionId column.

A few observations:

  1. The schema properly references the connection.id as a foreign key
  2. The style column uses jsonb type with appropriate type safety via $type
  3. The updatedAt column has an automatic update trigger

🏁 Script executed:

#!/bin/bash
# Verify that the WritingStyleMatrix type is correctly defined
echo "Checking WritingStyleMatrix type definition:"
grep -A 15 "export type WritingStyleMatrix" apps/mail/services/writing-style-service.ts

# Check for code that will update this table
echo -e "\nChecking for code that updates the writing style matrix:"
rg "writingStyleMatrix" --type ts

Length of output: 1597


✅ Approve WritingStyleMatrix schema and usage

  • Verified that export type WritingStyleMatrix in apps/mail/services/writing-style-service.ts matches the JSONB style column fields.
  • Confirmed insert and update operations on writingStyleMatrix in apps/mail/services/writing-style-service.ts.
  • Foreign key reference to connection.id, JSONB $type, and updatedAt auto-update trigger are all correctly configured.

No changes required.

dakdevs added 8 commits April 24, 2025 23:07
Removed redundant dependencies and imports to streamline the codebase. Updated versions and formatting in package files while improving readability and maintainability of the AI-related functionality.
Wrap the writing style matrix update in a try-catch block to ensure failures are logged without crashing the process. This improves robustness and helps in debugging issues when the update fails.
Updated the environment variable check to validate `OPENAI_API_KEY` instead of `GROQ_API_KEY`. This ensures compatibility with the updated API configuration and avoids potential misconfigurations.
Replaces `generateEmailBodyV2` with `generateEmailBody`, consolidating logic and improving error handling. Simplifies code for better maintainability while ensuring consistent functionality during AI-driven email generation.
Refactored AI prompt generation to exclude conversation history logic. Simplifies the code by directly appending the user prompt without wrapping in conversation history tags or processing previous messages. This improves maintainability and focuses solely on the current prompt context.
Replaced the raw user prompt with an escaped version to prevent potential issues with invalid or unsafe XML characters. This ensures message formatting remains correct and improves overall robustness.
Previously, totals were always initialized to 1 regardless of whether values existed. This update ensures totals are only set to 1 when corresponding values are present, improving accuracy in metrics calculation.
This refactor eliminates the unused `conversationId` and `signatureHash` parameters and related logic across multiple files. Additionally, it streamlines the prompt definitions and improves the initialization of metrics calculations for writing style analysis.
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: 3

🔭 Outside diff range comments (1)
apps/mail/lib/ai.ts (1)

32-40: 🛠️ Refactor suggestion

conversationHistories is never mutated – feature is inert

The cache is allocated but nothing writes to it, so conversation context is lost and memory is wasted.
Either remove the structure or push the user/assistant messages into it after each generation.

♻️ Duplicate comments (1)
apps/mail/lib/prompts.ts (1)

162-165: ⚠️ Potential issue

Escape untrusted dynamic context to prevent prompt-injection

currentSubject, currentDraft, and the recipients list are injected verbatim into the pseudo-XML envelope.
If any of these contain <, >, &, quotes, or XML-like substrings, the prompt becomes malformed and opens the door to prompt-injection.

-const currentSubjectContent = currentSubject ? `\n\n<current_subject>${currentSubject}</current_subject>\n\n` : '';
-const currentDraftContent   = currentDraft   ? `\n\n<current_draft>${currentDraft}</current_draft>\n\n`   : '';
-const recipientsContent     = recipients     ? `\n\n<recipients>${recipients.join(', ')}</recipients>\n\n` : '';
+const currentSubjectContent = currentSubject
+  ? `\n\n<current_subject>${escapeXml(currentSubject)}</current_subject>\n\n`
+  : '';
+const currentDraftContent   = currentDraft
+  ? `\n\n<current_draft>${escapeXml(currentDraft)}</current_draft>\n\n`
+  : '';
+const recipientsContent = recipients && recipients.length
+  ? `\n\n<recipients>${recipients.map(escapeXml).join(', ')}</recipients>\n\n`
+  : '';
🧹 Nitpick comments (4)
apps/mail/lib/prompts.ts (1)

185-195: Escape serialized JSON style profile before embedding

styleProfileJSON may contain >, <, or & via user greetings/sign-offs, breaking the surrounding XML.
Escape it just like userName:

-const styleProfileJSON = JSON.stringify(styleProfile, null, 2);
+const styleProfileJSON = escapeXml(JSON.stringify(styleProfile, null, 2));
apps/mail/services/writing-style-service.ts (1)

201-204: RunningStat lacks the email count – variance cannot be recovered

You store only mean and m2. To compute variance later you’ll need n (numMessages).
Either embed count in RunningStat or always pass numMessages alongside the stat.

apps/mail/lib/ai.ts (2)

42-53: userContext declared in the type but not destructured

The parameter list omits userContext; callers can still pass it silently, causing confusion.
Add it to the destructuring list or drop it from the signature.


134-140: Over-zealous unsafe pattern may block valid addresses

The regex flags any <…> which matches common email address formats (<john@example.com>).
Consider a narrower pattern that targets HTML tags (/<\s*(script|style|[a-z]+[^>]*)>/i) instead of any angle-bracket pair.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd5a10 and 7a76316.

📒 Files selected for processing (6)
  • apps/mail/actions/ai.ts (4 hunks)
  • apps/mail/actions/send.ts (2 hunks)
  • apps/mail/components/create/ai-assistant.tsx (2 hunks)
  • apps/mail/lib/ai.ts (7 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/services/writing-style-service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/mail/components/create/ai-assistant.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/actions/send.ts
  • apps/mail/actions/ai.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/mail/lib/prompts.ts (1)
apps/mail/services/writing-style-service.ts (1)
  • WritingStyleMatrix (213-221)
apps/mail/services/writing-style-service.ts (3)
packages/db/src/index.ts (1)
  • db (17-17)
apps/mail/lib/ai.ts (1)
  • extractStyleMatrix (246-302)
packages/db/src/schema.ts (1)
  • writingStyleMatrix (134-147)

Added metrics for slang usage, contractions, subject casualness, and more to provide deeper insights into writing style. Updated prompts and service logic to incorporate these metrics while maintaining clear documentation and examples. Improved consistency and expanded guidelines for style extraction.
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

♻️ Duplicate comments (4)
apps/mail/services/writing-style-service.ts (3)

152-157: Fix the takeTopCoverage function to include the threshold element.

The current implementation terminates too early and never reaches the requested coverage. Since the predicate runs after running is incremented and uses a strict <, the loop exits while coverage is still below the threshold.

-takeWhile(([_, count]) => {
-  running += count
-  return running / total < coverage
-}),
+reduce((acc, [key, count]) => {
+  if (running / total >= coverage) return acc;
+  running += count;
+  acc[key] = count;
+  return acc;
+}, {} as Record<string, number>),

113-118: Recalculate total after trimming counts.

After applying takeTopCoverage/takeTopK, you're dropping low-frequency entries but still using the old greetingTotal to compute probabilities. This makes pGreet inconsistent with the sum of greetingCounts.

-newStyle.greetingCounts = TAKE_TYPE === 'coverage'
-  ? takeTopCoverage(newStyle.greetingCounts)
-  : takeTopK(newStyle.greetingCounts)
-
-// Record the total number of greetings
-newStyle.greetingTotal = newStyle.greetingTotal + 1
-newStyle.pGreet = newStyle.greetingTotal / newNumMessages
+newStyle.greetingCounts = TAKE_TYPE === 'coverage'
+  ? takeTopCoverage(newStyle.greetingCounts)
+  : takeTopK(newStyle.greetingCounts)
+
+// Recompute total and probability from the trimmed counts
+const updatedGreetingTotal = Object.values(newStyle.greetingCounts)
+  .reduce((sum, count) => sum + count, 0)
+newStyle.greetingTotal = updatedGreetingTotal
+newStyle.pGreet = updatedGreetingTotal / newNumMessages

125-130: 🛠️ Refactor suggestion

Apply the same fix for signOff counts.

The same issue applies to signOffCounts. After trimming, you need to recalculate the total.

-newStyle.signOffCounts = TAKE_TYPE === 'coverage' 
-  ? takeTopCoverage(newStyle.signOffCounts) 
-  : takeTopK(newStyle.signOffCounts)
-
-// Record the total number of sign offs
-newStyle.signOffTotal = newStyle.signOffTotal + 1
-newStyle.pSign = newStyle.signOffTotal / newNumMessages
+newStyle.signOffCounts = TAKE_TYPE === 'coverage' 
+  ? takeTopCoverage(newStyle.signOffCounts) 
+  : takeTopK(newStyle.signOffCounts)
+
+// Recompute total and probability from the trimmed counts
+const updatedSignOffTotal = Object.values(newStyle.signOffCounts)
+  .reduce((sum, count) => sum + count, 0)
+newStyle.signOffTotal = updatedSignOffTotal
+newStyle.pSign = updatedSignOffTotal / newNumMessages
apps/mail/lib/ai.ts (1)

86-98: Missing conversation history update.

Unlike the legacy implementation, this code doesn't update conversationHistories, so any context from previous interactions is lost.

Add code to initialize and update conversation histories:

  const {
    text,
  } = await generateText({
    model: openai('gpt-4o'),
    system: systemPrompt,
    prompt: finalPrompt,
    maxTokens: 600,
    temperature: 0.35, // controlled creativity
    frequencyPenalty: 0.2, // dampen phrase repetition
    presencePenalty: 0.1, // nudge the model to add fresh info
    maxRetries: 1,
  })
+  
+  // Record conversation history
+  if (!conversationHistories[userId]) {
+    conversationHistories[userId] = {}
+  }
+  
+  const conversationId = generateConversationId(prompt) // Or however you want to track conversations
+  if (!conversationHistories[userId][conversationId]) {
+    conversationHistories[userId][conversationId] = []
+  }
+  
+  conversationHistories[userId][conversationId].push({ role: 'user', content: prompt })
+  conversationHistories[userId][conversationId].push({ role: 'assistant', content: text })
🧹 Nitpick comments (3)
apps/mail/services/writing-style-service.ts (1)

57-89: Add error handling for the service operation.

The updateWritingStyleMatrix function lacks specific error handling. If extractStyleMatrix or database operations fail, errors will propagate to the caller, potentially causing unhandled exceptions.

Consider adding explicit error handling:

 export const updateWritingStyleMatrix = async (connectionId: string, emailBody: string) => {
+  try {
     const emailStyleMatrix = await extractStyleMatrix(emailBody)
 
     await db.transaction(async (tx) => {
       // ... existing implementation
     })
+  } catch (error) {
+    console.error('Failed to update writing style matrix:', error)
+    // Either rethrow or handle gracefully depending on requirements
+    throw error
+  }
 }
apps/mail/lib/ai.ts (2)

143-157: Consider expanding refusal detection patterns.

The current list of refusal patterns is good but could be expanded to catch more variations.

Consider adding more common refusal patterns:

const isRefusal =
  lowerBody.includes("i cannot") ||
  lowerBody.includes("i'm unable to") ||
  lowerBody.includes("i am unable to") ||
  lowerBody.includes("as an ai") ||
  lowerBody.includes("my purpose is to assist") ||
  lowerBody.includes("violates my safety guidelines") ||
+ lowerBody.includes("i don't have the ability") ||
+ lowerBody.includes("i'm not able to") ||
+ lowerBody.includes("i'm not designed to") ||
+ lowerBody.includes("against my programming") ||
+ lowerBody.includes("ethical concerns") ||
  lowerBody.includes("sorry, i can only assist with email body");

196-197: Consider more deterministic subject generation.

For subject generation, you're using a relatively high temperature (0.5) compared to the email body generation (0.35) and style extraction (0). For consistency in subject lines, you might consider lowering this.

const { completion: generatedSubjectRaw } = await generateCompletions({
    model: 'gpt-4', // Using the more capable model
    systemPrompt: systemPrompt,
    prompt: subjectPrompt,
-   temperature: 0.5,
+   temperature: 0.35, // Same temperature as email body for consistency
});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7a76316 and f7df688.

📒 Files selected for processing (3)
  • apps/mail/lib/ai.ts (7 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/services/writing-style-service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/lib/prompts.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/mail/services/writing-style-service.ts (3)
packages/db/src/index.ts (1)
  • db (17-17)
apps/mail/lib/ai.ts (1)
  • extractStyleMatrix (246-314)
packages/db/src/schema.ts (1)
  • writingStyleMatrix (134-147)
🔇 Additional comments (7)
apps/mail/services/writing-style-service.ts (3)

189-204: LGTM: Properly handling nullable greetings and sign-offs.

The initialization logic correctly sets greetingTotal and signOffTotal to 0 when their respective fields are null, addressing a previously identified issue.


13-43: Thorough metric key documentation.

The extensive comments for each metric key provide excellent context for understanding what the writing style service is tracking, which improves maintainability.


171-180: LGTM: Efficient running statistics implementation.

The updateStat function correctly implements Welford's online algorithm for computing running mean and variance, which is numerically stable and memory-efficient for tracking statistics across many emails.

apps/mail/lib/ai.ts (4)

55-57: Fix for API key check: using OpenAI instead of Groq.

You've correctly fixed the API key check to use OPENAI_API_KEY instead of GROQ_API_KEY, which aligns with the OpenAI model being used (openai('gpt-4o')).


134-140: Robust safety checks for generated content.

Great implementation of safety checks to detect HTML, code blocks, or other potentially unsafe content in the generated response.


246-314: Excellent implementation of style extraction.

The extractStyleMatrix function is well-implemented with:

  • Comprehensive schema validation using Zod
  • Appropriate error handling for empty inputs
  • Good model selection with mini variant for efficiency
  • Normalization of greeting and sign-off values
  • Clear typing and structure

73-77:

Details

✅ Verification successful

Ensure writing style matrix property access is correct.

The service returns an object with numMessages and style fields, where style contains the WritingStyleMatrix. The current code assumes the matrix is directly accessible via writingStyleMatrix.style.


🏁 Script executed:

#!/bin/bash
# Verify the structure of the WritingStyleMatrix return
echo "=== getWritingStyleMatrixForConnectionId signature ==="
fd --type file "writing-style-service.ts" | xargs rg -n "export const getWritingStyleMatrixForConnectionId" -A 10

Length of output: 569


No action needed – property access is correct

The getWritingStyleMatrixForConnectionId function returns an object with a style field (alongside numMessages), so accessing writingStyleMatrix.style is exactly right.

Replaced the existing metrics calculation with Welford's algorithm for improved numerical accuracy and efficiency. Split metrics into mean-based, sum-based, and count-based categories, and adjusted initialization and update methods accordingly. Deprecated `subjectEmojiCount` and `subjectInformalityScore` while introducing metrics for greeting/sign-off presence.
# don't show warnings if package versions don't match
strict-peer-dependencies=false
auto-install-peers=true
save-exact=true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this to auto-pin dependencies.

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: 4

🔭 Outside diff range comments (1)
apps/mail/lib/ai.ts (1)

32-39: 🛠️ Refactor suggestion

conversationHistories is never mutated – feature is inert

You define an in-memory store but never push messages into it, so every call starts with an empty history.
Either:

  1. Remove the variable, or
  2. Persist user/assistant turns after generateText completes (see earlier review on generateEmailBodyV2).

Neglecting this defeats multi-turn context and wastes memory.

♻️ Duplicate comments (1)
apps/mail/services/writing-style-service.ts (1)

170-174: takeTopCoverage still stops one element short of the requested coverage
This was reported in an earlier review and remains unfixed.
See prior diff suggestion for the correct reducer-based implementation.

🧹 Nitpick comments (2)
apps/mail/services/writing-style-service.ts (1)

118-121: Empty for-loop is dead code

The loop over TOP_COUNTS_KEYS performs no body work and can be removed.
Keeping it around is confusing and may hint at an unfinished implementation.

apps/mail/lib/ai.ts (1)

47-52: Unused userContext parameter

userContext is accepted but ignored entirely. Delete the param or make use of it (e.g., pass the sender’s email/name into prompts).
Leaving dead interface surface invites bugs down the line.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f7df688 and feab9c4.

📒 Files selected for processing (3)
  • apps/mail/lib/ai.ts (7 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/services/writing-style-service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/lib/prompts.ts
🔇 Additional comments (1)
apps/mail/services/writing-style-service.ts (1)

189-198: updateStat is unused – remove or integrate

There are no callers for updateStat. Keeping unused code increases maintenance burden and confuses readers.
[ suggest_optional_refactor ]

dakdevs added 4 commits April 25, 2025 00:46
Unify handling of greeting and signOff keys by leveraging TOP_COUNTS_KEYS. Simplify logic for updating and initializing style metrics, reducing redundancy. These changes improve maintainability and clarity of the service code.
The conversationHistory parameter was no longer being utilized and has been removed to clean up the code. This improves maintainability and reduces potential confusion for future updates.
The conversationHistories object was no longer being used and has been removed to clean up the code. This simplifies code maintenance and avoids potential confusion.
Replaced angle brackets with HTML entities to ensure proper rendering and standardized formatting of prompt instructions. Enhanced readability by aligning tone, sentence, and paragraph length guidelines for consistency.
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

♻️ Duplicate comments (2)
apps/mail/lib/ai.ts (2)

50-51: Remove unnecessary await from headers() call.

The headers() function from next/headers is synchronous and doesn't return a Promise - awaiting it can cause the request to hang in Edge runtimes.

-    const headersList = await headers()
+    const headersList = headers()

64-68: ⚠️ Potential issue

Fix the writingStyleMatrix property access.

The writingStyleMatrix.style property access appears incorrect - based on the definition in the writing style service, the whole object IS the style profile.

-    const systemPrompt = writingStyleMatrix ?
-      StyledEmailAssistantSystemPrompt(userName, writingStyleMatrix.style) :
+    const systemPrompt = writingStyleMatrix ?
+      StyledEmailAssistantSystemPrompt(userName, writingStyleMatrix) :
       EmailAssistantSystemPrompt(userName);
🧹 Nitpick comments (3)
apps/mail/lib/ai.ts (3)

125-131: Consider adding test for HTML sanitization.

The safety check pattern is well-implemented, but consider adding a unit test to verify it catches all unsafe patterns you're concerned about.


237-307: Strong implementation of writing style extraction.

The extractStyleMatrix function is well designed with:

  • Comprehensive style metrics extraction
  • Proper schema validation using zod
  • Appropriate error handling for empty content
  • Good normalization of greeting and sign-off strings

Consider adding a caching mechanism if this function is called frequently, as it could be expensive in terms of API usage and latency.


242-296: Consider handling potential AI model errors.

The generateObject call could fail if the model struggles to extract certain metrics from unusual email content. Consider adding more specific error handling for this case.

 const {
   object,
- } = await generateObject({
+ } = await generateObject({
   // ...existing configuration
 })
+} catch (error) {
+  console.error('Failed to extract style matrix:', error);
+  // Return default/empty style matrix or rethrow with more context
+  throw new Error(`Style extraction failed: ${error.message}`);
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between feab9c4 and beafd43.

📒 Files selected for processing (3)
  • apps/mail/lib/ai.ts (7 hunks)
  • apps/mail/lib/prompts.ts (2 hunks)
  • apps/mail/services/writing-style-service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/lib/prompts.ts
  • apps/mail/services/writing-style-service.ts
🔇 Additional comments (1)
apps/mail/lib/ai.ts (1)

46-47: LGTM: Environmental API key validation corrected.

The code now properly checks for the OpenAI API key instead of Groq, which aligns with using the openai model in the implementation.

dakdevs added 2 commits April 25, 2025 01:13
Enhanced formatting, structure, and style adaptation rules for better alignment with user metrics. Improved descriptions and examples to ensure consistent tone, readability, and engagement cues.
@dakdevs dakdevs marked this pull request as draft April 25, 2025 08:27
Comment on lines +60 to +68
after(async () => {
try {
console.warn('Saving writing style matrix...')
await updateWritingStyleMatrix(connection.id, message)
console.warn('Saved writing style matrix.')
} catch (error) {
console.error('Failed to save writing style matrix', error)
}
})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This way once the email sends, the user gets feedback immediately while the style matrix gets update in the background.

Comment on lines -26 to -32
const conversationHistories: Record<
string, // userId
Record<
string, // conversationId
{ role: 'user' | 'assistant' | 'system'; content: string }[]
>
> = {};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to the server-less nature of the runtime, this wouldn't actually properly persist.

});
console.log(`AI Assistant (Body): Received completion for convId ${convId}:`, generatedBodyRaw);

// --- Post-processing: Remove common conversational prefixes ---
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved a lot of this to it's own function for now.

dakdevs added 7 commits April 25, 2025 21:03
Introduces a new seeder framework to manage style matrices, including commands for seeding and resetting data. Enhances email summary updates with improved prompts and transaction handling for better performance and reliability.
# Conflicts:
#	packages/db/src/schema.ts
Switched to the "llama-3.1-8b-instant" model for text generation and increased max retries from 3 to 5 for improved reliability.
Deleted an extraneous blank line to improve code readability and consistency. This is a minor cleanup with no functional impact.
Refactored AI body generation by integrating LangChain's ChatGroq and updated JSON parsing logic for consistent schema handling. Enhanced the style-matrix seeding script with retry logic to improve resilience. Added stricter guidelines in prompts and updated dependencies for compatibility.
Renamed the seed-style-matrix command and related files to seed-style for consistency and simplicity. Updated `run.ts` to reflect the changes and added a comprehensive README for better script system documentation. Adjusted file paths accordingly for better organization.
@dakdevs dakdevs closed this Apr 27, 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.

1 participant