Conversation
- added cc and bcc when saving drafts - save drafts less aggresively
chore: simplify and fix the dev env
* Create prompts with XML formatting * Include XML formatted prompts in generate func * remove unused regex and add helper functions/warnings * error handling * Update apps/mail/lib/prompts.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * lint issues * Update prompts.ts * #706 (comment) Coderabbit fix 1 * erabbitai bot 3 days ago⚠️ Potential issue errorOccurred state is stale inside finally React state setters (setErrorOccurred) are asynchronous; the errorOccurred value captured at render time will not yet reflect changes made earlier in the same event loop. Consequently, the logic deciding whether to collapse/expand may run with an outdated flag. - } finally { - setIsLoading(false); - if (!errorOccurred || isAskingQuestion) { - setIsExpanded(true); - } else { - setIsExpanded(false); // Collapse on errors - } - } + } finally { + setIsLoading(false); + // Use a local flag to track errors deterministically + const hadError = isAskingQuestion ? false : !!errorFlagRef.current; + setIsExpanded(!hadError); + } You can create const errorFlagRef = useRef(false); and update errorFlagRef.current = true every time an error is detected, ensuring reliable behaviour irrespective of React batching. Committable suggestion skipped: line range outside the PR's diff. * #706 (comment) * #706 (comment) * #706 (comment) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…users (#726) * feat(i18n): add Vietnamese language support Add Vietnamese ('vi') to the list of supported languages in the i18n configuration and JSON file to expand language options. * Add a new Vietnamese translation file to support Vietnamese language users. * Clear Vietnamese translation strings
Co-authored-by: needle <122770437+needleXO@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update implements a major refactor and feature expansion across the mail application. Environment variable management is consolidated into a single root Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI
participant useEmailAliases Hook
participant EmailAlias API
participant Compose/Reply Handler
participant SendEmail Action
participant Mail Driver
User->>UI: Open compose/reply email
UI->>useEmailAliases Hook: Fetch aliases
useEmailAliases Hook->>EmailAlias API: GET /api/driver/email-aliases
EmailAlias API->>useEmailAliases Hook: Return aliases
useEmailAliases Hook->>UI: Provide aliases
User->>UI: Select "From" email
User->>Compose/Reply Handler: Compose and send email
Compose/Reply Handler->>SendEmail Action: sendEmail({ ..., fromEmail })
SendEmail Action->>Mail Driver: create({ ..., fromEmail })
Mail Driver->>SendEmail Action: Send email with selected alias
SendEmail Action->>Compose/Reply Handler: Return result
Compose/Reply Handler->>UI: Show status
sequenceDiagram
participant User
participant UI
participant AI Assistant
participant AI Backend
User->>UI: Request AI email draft
UI->>AI Assistant: Submit prompt
AI Assistant->>AI Backend: generateAIEmailBody(prompt, ...)
AI Backend-->>AI Assistant: Return body or question
AI Assistant->>AI Backend: (If body) generateAISubject(body)
AI Backend-->>AI Assistant: Return subject
AI Assistant->>UI: Show generated subject and body
User->>UI: Accept or edit draft
Possibly related PRs
Suggested reviewers
Poem
✨ 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:
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: 13
🔭 Outside diff range comments (2)
apps/mail/app/api/driver/google.ts (1)
396-401:⚠️ Potential issueUse URL‑safe base‑64 when calling Gmail APIs
gmail.users.messages.send(anddrafts.create/update) expects the URL‑safe variant of base‑64 (see Google API docs).
Buffer.from(...).toString('base64')produces the standard form containing+ / =, which frequently yields400 invalidArgumentor silently drops attachments.-const encodedMessage = Buffer.from(emailContent).toString('base64'); +const encodedMessage = Buffer.from(emailContent) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, '');(You already do this in
createDraft; bringing the two paths in sync also avoids future regressions.)apps/mail/components/create/ai-assistant.tsx (1)
194-210:⚠️ Potential issue
conversationIdshould be stable across renders
generateConversationId()is executed on every render, so follow‑up messages will not share the same context on the server side.-const conversationId = generateConversationId(); +const conversationIdRef = useRef(generateConversationId()); +const conversationId = conversationIdRef.current;
🧹 Nitpick comments (10)
apps/mail/lib/ai.ts (2)
134-142: Over‑aggressive HTML / code‑block filter may block legitimate email bodiesThe current regex rejects any
<tag>or fenced code, yet many real emails legitimately contain
HTML (ordered lists, links,<strong>, etc.).Recommend:
- Allow a whitelist of harmless tags (
<br>,<a>,<strong>…)- Only strip or escape instead of rejecting outright
- Keep code‑block detection (```/~~~) if you need plain‑text only
This prevents false positives that surface as the generic “Unable to fulfil your request.” message.
160-163: Cap the stored history length to avoid runaway memoryYou already slice the last four messages for embeddings, but the full history keeps growing.
conversationHistories[userId][convId].push({ role: 'assistant', content: generatedBody }); +// Keep only the most recent N entries (e.g. 50) +const MAX_HISTORY = 50; +const history = conversationHistories[userId][convId]; +if (history.length > MAX_HISTORY) history.splice(0, history.length - MAX_HISTORY);apps/mail/hooks/use-email-aliases.ts (1)
9-12: Consider adding retry options for better resilienceWhile the implementation is solid, fetching email aliases is likely critical for the user experience. Consider adding retry options to handle temporary network issues.
const { data, error, isLoading, mutate } = useSWRImmutable<EmailAlias[]>( '/api/driver/email-aliases', fetcher, + { + errorRetryCount: 3, + errorRetryInterval: 2000 + } );apps/mail/actions/email-aliases.ts (1)
13-36: Robust implementation with proper error handlingThe
getEmailAliasesfunction is well-implemented with proper error handling and fallbacks. It correctly:
- Checks for valid authentication tokens
- Creates a driver for the specific provider
- Attempts to fetch email aliases
- Falls back to using just the primary email if fetching fails
A small improvement could be to add more specific error typing for better error handling.
Consider enhancing error handling with more specific error types:
- } catch (error) { + } catch (error: unknown) { console.error('Error fetching email aliases:', error); + // Consider logging different error types differently + if (error instanceof Error) { + console.error(`Error name: ${error.name}, message: ${error.message}`); + } return [{ email: connection.email, primary: true }]; }apps/mail/components/draft/drafts-list.tsx (2)
32-33: Delete or wire‑up thebodyTextstateThe state pair is declared but never written to or read from. Keeping dead state:
- Increases bundle size ever so slightly.
- Confuses future readers who will look for its purpose.
Either remove it or actually set it (e.g. in an
useEffectthat callsextractTextFromHTML(message.processedHtml)).- const [bodyText, setBodyText] = React.useState('');
99-107: Show a real body preview instead of the (often empty)titleYou already imported
extractTextFromHTML; leverage it so that users see an actual snippet of the draft’s body.- {highlightText(message.title || 'No content', searchValue.highlight)} + {highlightText( + extractTextFromHTML(message.processedHtml ?? message.body ?? '') + .slice(0, 140) || // limit to first ~140 chars + 'No content', + searchValue.highlight, + )}This re‑uses the removed import and gives meaning to the list’s second line.
package.json (1)
19-23: Duplicated ‘dotenv’ prefix in nested Turbo pipelines
turbo run db:generatewill itself execute thedb:generatescript inside packages; those inner scripts often also expect envs.
Runningdotenvat the root and inside each package doubles the work and may load different.envfiles.Consider limiting
dotenvto either the root or the leaf scripts to avoid confusion.apps/mail/app/api/driver/google.ts (1)
488-520: Filter out unverified or disabled aliases
sendAs.listcan return items withverificationStatus!=="accepted"orisPrimary === false && isSuspended === true.
Attempting to send from such aliases raises a 400/403. Recommend filtering:if (alias.verificationStatus === 'accepted' && !alias.isSuspended) { … }This prevents the UI from showing unusable addresses.
apps/mail/components/create/create-email.tsx (1)
610-647: Dropdown loses selection when aliases reloadWhen
aliasesrefetch (e.g., after token refresh)selectedFromEmailstaysnull, so the UI flips back to the first alias instead of the user choice.A quick fix:
useEffect(() => { if (!selectedFromEmail && aliases?.length) { setSelectedFromEmail(aliases[0].email); } }, [aliases]);This preserves UX consistency across re‑renders.
apps/mail/lib/prompts.ts (1)
8-15: Export the XML‑escaping helper for reuse & testing
escapeXmlis currently file‑private. Exporting it (or moving to a shared util) lets other prompt files sanitise user names consistently and makes unit testing trivial.-const escapeXml = (s: string) => +export const escapeXml = (s: string) =>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
apps/mail/public/icons-pwa/icon-180.pngis excluded by!**/*.pngapps/mail/public/icons-pwa/icon-192.pngis excluded by!**/*.pngapps/mail/public/icons-pwa/icon-512.pngis excluded by!**/*.pngbun.lockis excluded by!**/*.lock
📒 Files selected for processing (40)
.env.example(0 hunks).github/CONTRIBUTING.md(4 hunks)README.md(4 hunks)apps/mail/.nvmrc(0 hunks)apps/mail/actions/ai.ts(2 hunks)apps/mail/actions/email-aliases.ts(1 hunks)apps/mail/actions/send.ts(3 hunks)apps/mail/app/api/auth/early-access/count/route.ts(2 hunks)apps/mail/app/api/auth/early-access/route.ts(3 hunks)apps/mail/app/api/driver/google.ts(6 hunks)apps/mail/app/api/driver/types.ts(1 hunks)apps/mail/app/api/utils.ts(1 hunks)apps/mail/app/api/v1/mail/auth/[providerId]/callback/route.ts(0 hunks)apps/mail/app/api/v1/mail/auth/[providerId]/init/route.ts(0 hunks)apps/mail/app/manifest.ts(1 hunks)apps/mail/components/connection/add.tsx(3 hunks)apps/mail/components/create/ai-assistant.tsx(7 hunks)apps/mail/components/create/create-email.tsx(10 hunks)apps/mail/components/draft/drafts-list.tsx(4 hunks)apps/mail/components/mail/reply-composer.tsx(11 hunks)apps/mail/drizzle.config.ts(0 hunks)apps/mail/hooks/use-email-aliases.ts(1 hunks)apps/mail/i18n/config.ts(1 hunks)apps/mail/lib/ai.ts(1 hunks)apps/mail/lib/auth-providers.ts(1 hunks)apps/mail/lib/auth.ts(3 hunks)apps/mail/lib/constants.ts(1 hunks)apps/mail/lib/prompts.ts(1 hunks)apps/mail/locales/en.json(2 hunks)apps/mail/locales/es.json(2 hunks)apps/mail/locales/vi.json(1 hunks)apps/mail/middleware-eg.ts(0 hunks)apps/mail/package.json(2 hunks)apps/mail/types/index.ts(1 hunks)docker-compose.yaml(1 hunks)i18n.json(1 hunks)middleware-draft.ts(0 hunks)package.json(2 hunks)packages/db/.env.example(0 hunks)turbo.json(2 hunks)
💤 Files with no reviewable changes (8)
- .env.example
- apps/mail/.nvmrc
- apps/mail/drizzle.config.ts
- apps/mail/app/api/v1/mail/auth/[providerId]/init/route.ts
- middleware-draft.ts
- apps/mail/middleware-eg.ts
- apps/mail/app/api/v1/mail/auth/[providerId]/callback/route.ts
- packages/db/.env.example
🧰 Additional context used
🧬 Code Graph Analysis (5)
apps/mail/app/api/auth/early-access/count/route.ts (1)
apps/mail/app/api/utils.ts (1)
processIP(64-73)
apps/mail/app/api/utils.ts (1)
apps/mail/app/error.tsx (1)
Error(8-39)
apps/mail/app/api/auth/early-access/route.ts (1)
apps/mail/app/api/utils.ts (1)
processIP(64-73)
apps/mail/hooks/use-email-aliases.ts (1)
apps/mail/actions/email-aliases.ts (1)
EmailAlias(7-11)
apps/mail/components/draft/drafts-list.tsx (5)
apps/mail/types/index.ts (2)
ThreadProps(90-102)ParsedMessage(37-62)apps/mail/components/mail/use-mail.ts (1)
useMail(23-25)apps/mail/hooks/use-search-value.ts (1)
useSearchValue(19-21)apps/mail/lib/email-utils.client.tsx (1)
highlightText(59-77)apps/mail/lib/utils.ts (1)
cn(52-52)
🔇 Additional comments (49)
i18n.json (1)
22-23: Language support expansion looks good.Adding Persian ("fa") and Vietnamese ("vi") to the target locales properly extends the application's internationalization capabilities.
apps/mail/package.json (2)
10-10: Database scripts removal aligns with centralization strategy.The removal of database-related scripts from the mail app's package.json is consistent with the PR's objective to consolidate database configuration.
59-59: Dependency version update.Updating
better-authfrom 1.2.1 to 1.2.7 appears to be a standard maintenance update.docker-compose.yaml (1)
33-33: Minor quoting style change.Changing from double quotes to single quotes for the Redis connection string is a stylistic change with no functional impact.
apps/mail/app/manifest.ts (2)
8-9: Improved PWA configuration.Setting the scope to "/" and updating the start URL to "/mail/inbox" enhances the Progressive Web App experience by directing users directly to the inbox view.
13-31: Enhanced icon support for better device compatibility.Replacing the single favicon with multiple PNG icons at different resolutions (512x512, 192x192, 180x180) significantly improves the app's appearance across various devices and platforms. The "maskable" purpose attribute ensures proper display on devices that use icon masks.
apps/mail/lib/ai.ts (1)
252-260:checkIfQuestionmisses many interrogatives & returns false positivesEdge cases:
- “Any thoughts on …” (no
?)- “Let me know if …?” (ends with
?but is not a clarification request)Consider NLP‑based detection (e.g. simple classification prompt) or enrich the heuristic (regex for “\bany\b.*\bquestions?\b”, etc.).
Marking a body as a question incorrectly will break the composing UI.apps/mail/types/index.ts (1)
119-120: Propagate the newfromEmailfield through all send / draft flows
fromEmailis optional here, but callers likesendEmail, draft creation, and alias pickers
should treat absence as:
- Default to the primary account address, or
- Block send with a validation error
Ensure unit tests cover:
- alias chosen → correct “From” header set
- no alias → default address
apps/mail/i18n/config.ts (1)
18-19: Nice addition – Vietnamese locale wired correctly
viis added to both the constant and derived arrays; no further action needed. 👍apps/mail/app/api/driver/types.ts (1)
39-39: LGTM: Clean implementation of email alias support.The new
getEmailAliases()method is well-defined with appropriate return type structure to support the email alias selection feature. The interface extension follows the existing pattern in the MailManager interface.apps/mail/actions/send.ts (1)
17-17: LGTM: Consistent implementation of sender alias support.The addition of the optional
fromEmailparameter and its proper propagation to the driver allows users to specify sender addresses when composing emails. This correctly implements the email alias selection feature.Also applies to: 27-27, 56-56
apps/mail/locales/es.json (1)
281-281: Improved Spanish translations for mail folders.The changes improve grammatical accuracy in Spanish:
- "Enviado" → "Enviados" (singular to plural form for "sent")
- "Archivar" → "Archivados" (verb form to noun form for "archive")
These changes better align with standard email folder naming conventions in Spanish.
Also applies to: 283-283, 443-443
apps/mail/app/api/utils.ts (1)
28-28: Ensure consistent error propagation after sign-out.This change ensures that the function always throws an error after sign-out, maintaining consistent error flow. This makes the function behavior match its name and ensures callers can reliably catch the unauthorized condition.
apps/mail/lib/constants.ts (1)
100-106: Good type safety enhancement withas constAdding the
as constassertion to theemailProvidersarray converts it from a regular array to a readonly tuple with literal types. This improves type safety when working with provider IDs throughout the application, making it easier to catch errors at compile time rather than runtime.apps/mail/app/api/auth/early-access/count/route.ts (1)
4-4: Good refactoring to use centralized IP processingReplacing manual IP extraction with the centralized
processIPutility improves code maintainability and consistency. The utility function provides more robust handling of different IP sources (CF-Connecting-IP, x-forwarded-for) and better fallback mechanisms.Also applies to: 18-18
apps/mail/locales/en.json (2)
90-90: Added localization support for new sender selection featureThe addition of the "from" key supports the new UI components for selecting sender email addresses, aligning with the email alias feature introduced in this PR.
140-140: Minor formatting improvement for consistencyFixed spacing in the "bcc" translation value for better consistency across the localization file.
apps/mail/hooks/use-email-aliases.ts (1)
1-20: Well-implemented hook for email alias managementThis hook follows React best practices by using SWR for efficient data fetching and providing appropriate loading/error states. Using
useSWRImmutableis a good choice since email aliases don't change frequently during a session.apps/mail/components/connection/add.tsx (3)
10-10: New import for auth client added.The addition of
authClientimport corresponds to the shift from link-based navigation to programmatic authentication.
56-56: Element type changed from anchor to div.Changed from
<motion.a>to<motion.div>to match the new authentication flow that no longer relies on direct URL navigation.Also applies to: 78-78
67-71: Authentication flow changed to use programmatic method.The implementation now correctly uses the
authClient.linkSocialmethod to handle OAuth authentication, replacing the previous direct URL navigation. This provides better control over the authentication process and aligns with the centralized authentication approach.apps/mail/lib/auth-providers.ts (2)
35-35: Removal of GOOGLE_REDIRECT_URI from required environment variables.Simplified the OAuth setup by removing the GOOGLE_REDIRECT_URI requirement. This aligns with the broader efforts to streamline the authentication configuration.
39-39: Removed the corresponding environment variable information.Properly updated the
envVarInfoarray to match the changes torequiredEnvVars, maintaining consistency in the configuration..github/CONTRIBUTING.md (5)
34-34: Simplified environment setup instructions.Updated to reference a single
.envfile in the project root instead of multiple.envfiles in different locations, simplifying the developer setup process.
45-45: Removed unnecessary instruction.The step to install database dependencies (
bun db:dependencies) was removed as it's no longer required in the updated workflow.
116-117: Simplified database command instructions.Removed database dependency instructions between commands, cleaning up the documentation while maintaining the essential database operation instructions.
Also applies to: 119-120, 122-123
130-132: Updated database connection instructions.Correctly updated to reference a single
.envfile for database configuration, consistent with the project's new environment variable management approach.
179-180: Improved formatting for better readability.Added blank lines for better visual separation, improving the documentation's readability without changing its content.
Also applies to: 186-187
turbo.json (3)
3-3: Added loose environment mode.The
"envMode": "loose"setting allows tasks to access all environment variables without explicit declaration, supporting the simplified environment configuration approach of the project.
11-11: Made dev task dependent on database studio.Added dependency on
db:studiofor thedevtask, which means runningbun devwill automatically start the database studio. This improves the developer experience by requiring fewer manual steps.
21-32: Added database management tasks.Centralized database management by adding dedicated tasks for schema generation, migration, pushing changes, and running the database studio. Setting
"cache": falseensures these tasks always execute when called, which is appropriate for database operations that need to run reliably.apps/mail/app/api/auth/early-access/route.ts (4)
4-4: Good addition of centralized IP processingImporting the
processIPutility function enhances code maintainability by centralizing IP extraction logic.
7-7: Import reordering looks goodReordering imports to maintain a logical grouping structure.
35-35: Refactored IP extraction improves reliabilityReplacing direct header access with the
processIPutility function improves error handling and cross-platform compatibility. The utility properly handles multiple IP header formats and provides appropriate fallbacks.
90-90: Simplified date calculationThe date calculation for scheduling emails is more concise and readable now.
apps/mail/actions/email-aliases.ts (1)
7-11: Well-defined EmailAlias typeGood use of TypeScript to define a clear interface for email aliases with appropriate optional properties.
README.md (6)
78-81: Simplified environment setup instructionsGood simplification of the environment setup process by consolidating to a single
.envfile in the project root.
83-83: Clearer database setup instructionsImproved clarity in database setup steps by removing unnecessary dependencies installation.
167-168: Updated OAuth redirect URI guidanceThe warning message has been updated to reference the correct environment file location.
172-173: Streamlined environment variables sectionInstructions for environment variable configuration have been simplified to reference a single
.envfile.
213-213: Clarified database connection configurationUpdated instructions to ensure the database connection string is properly configured in the single
.envfile.
245-245: Helpful note about database studio automationGood addition explaining that running
bun devautomatically runs the database studio, which improves the developer experience.apps/mail/components/draft/drafts-list.tsx (1)
62-75: Recipient fallback is case‑sensitive and may throw
includes('no-sender')fails for values like"No‑Sender"and will blow up if eithernameornull/undefined(possible when Gmail omits the display name).- message.to.some( - (to: { name: string; email: string }) => - to.name.includes('no-sender') || to.email.includes('no-sender'), - ) + message.to.some( + (to: { name?: string | null; email?: string | null }) => { + const n = (to.name ?? '').toLowerCase(); + const e = (to.email ?? '').toLowerCase(); + return n.includes('no-sender') || e.includes('no-sender'); + }, + )Use safe‑access and
toLowerCase()for robustness.package.json (1)
7-12: Scripts implicitly depend ondotenv-cli– ensure it’s available in all environments
dotenv -- turbo …relies on thedotenvbin fromdotenv-cli.
Because it’s a devDependency, production environments (e.g., Docker image, CI build step) that runnpm ci --only=productionwill miss it and the scripts will fail.Options:
- Move
dotenv-clitodependencies.- Replace the script with
"node -r dotenv/config $(npm bin)/turbo run …"which doesn’t need the CLI.apps/mail/components/mail/reply-composer.tsx (2)
777-813: DRY: duplicate “From” dropdown appears twiceThe markup that renders the alias dropdown is copied almost verbatim for the editing and compact header modes.
Consider extracting a smallFromEmailSelectorcomponent to keep the JSX readable and to avoid bugs when future changes touch only one copy.Benefits:
• Easier maintenance
• Consistent behaviour & styling
• Single place for future enhancements (e.g. search/filter)[ suggest_optional_refactor ]
Also applies to: 871-909
341-349: Graceful fallback when aliases are still loading
fromEmailfalls back toaliases?.[0]?.emailoruserEmailif no alias is selected.
When the user clicks Send before the alias query has finished,aliasesis stillundefined, so we will send from the primary account. That’s fine, but there is no visual hint that the pending alias list might change the actual “From”.
A tiny UX improvement would be to disable the send button whileisLoadingAliasesis true or show a spinner inside the dropdown.[ suggest_nitpick ]
apps/mail/actions/ai.ts (2)
4-8: Unnecessaryawaitaroundheaders()
next/headersexports a synchronous function; awaiting a non‑promise has no effect but does add lint noise.-const headersList = await headers(); +const headersList = headers();[ suggest_nitpick ]
138-152: Utility duplication
createJsonContentFromBodyis re‑implemented here while a very similar helper already exists in several files. Moving this to a shared utility module prevents divergence (e.g. differing paragraph wrapping).[ suggest_optional_refactor ]
apps/mail/components/create/ai-assistant.tsx (1)
468-476: Disable Accept button when an error occurred
hasContent={!!generatedBody && !errorOccurred}is correct, but the Accept handler still fires if the user somehow triggers it while an error is flagged. Guard inside the handler:if (errorOccurred || !generatedBody) return;[ suggest_nitpick ]
| const headersList = await headers(); | ||
| const session = await auth.api.getSession({ headers: headersList }); | ||
| const userName = session?.user.name || 'User'; |
There was a problem hiding this comment.
Remove the unnecessary await to avoid a compile‑time error
headers() is synchronous and returns a ReadonlyHeaders.
Using await on a non‑Promise value will cause a TS error (TS2536) and break the build.
- const headersList = await headers();
+ const headersList = headers();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const headersList = await headers(); | |
| const session = await auth.api.getSession({ headers: headersList }); | |
| const userName = session?.user.name || 'User'; | |
| const headersList = headers(); | |
| const session = await auth.api.getSession({ headers: headersList }); | |
| const userName = session?.user.name || 'User'; |
| // Keyed by user to prevent cross‑tenant bleed‑through and allow GC per‑user | ||
| const conversationHistories: Record< | ||
| string, | ||
| { role: 'user' | 'assistant' | 'system'; content: string }[] | ||
| string, // userId | ||
| Record< | ||
| string, // conversationId | ||
| { role: 'user' | 'assistant' | 'system'; content: string }[] | ||
| > | ||
| > = {}; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add eviction / per‑request scoping to prevent memory & data‑leak issues
conversationHistories is a long‑lived in‑memory singleton:
- It grows unbounded – a busy server process will eventually OOM.
- Un‑authenticated users are bucketed under the static key
"anonymous", so different guests share the same history and may read each other’s prompts / completions.
Consider:
-const conversationHistories: Record<...> = {};
+const conversationHistories: Record<...> = new Map();
/* 1️⃣ TTL / max‑length eviction
2️⃣ Use crypto.randomUUID() for guests
3️⃣ Optionally persist to a per‑user KV store (Redis) instead of process memory */Committable suggestion skipped: line range outside the PR's diff.
| let dynamicContext = '\n\n<dynamic_context>\n'; | ||
| if (subject) { | ||
| dynamicContext += ` <current_subject>${subject}</current_subject>\n`; | ||
| } | ||
|
|
||
| // Add context about current email if it exists | ||
| if (currentContent) { | ||
| systemPrompt += `\n\nThe user's current email draft is:\n\n${currentContent}`; | ||
| dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`; | ||
| } | ||
|
|
||
| // Add context about recipients | ||
| if (recipients && recipients.length > 0) { | ||
| systemPrompt += `\n\nThe email is addressed to: ${recipients.join(', ')}`; | ||
| dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`; | ||
| } | ||
| dynamicContext += '</dynamic_context>\n'; | ||
| const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : ''); | ||
|
|
There was a problem hiding this comment.
Dynamic‑context shouldn’t be gated by an arbitrary length check
dynamicContext.length > 30 will silently drop valid context (e.g. a short subject line only).
Drop the length heuristic or switch to an explicit “has‑any‑field” check:
-const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : '');
+const hasDynamic = !!(subject || currentContent || (recipients?.length));
+const fullSystemPrompt = baseSystemPrompt + (hasDynamic ? dynamicContext : '');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let dynamicContext = '\n\n<dynamic_context>\n'; | |
| if (subject) { | |
| dynamicContext += ` <current_subject>${subject}</current_subject>\n`; | |
| } | |
| // Add context about current email if it exists | |
| if (currentContent) { | |
| systemPrompt += `\n\nThe user's current email draft is:\n\n${currentContent}`; | |
| dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`; | |
| } | |
| // Add context about recipients | |
| if (recipients && recipients.length > 0) { | |
| systemPrompt += `\n\nThe email is addressed to: ${recipients.join(', ')}`; | |
| dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`; | |
| } | |
| dynamicContext += '</dynamic_context>\n'; | |
| const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : ''); | |
| let dynamicContext = '\n\n<dynamic_context>\n'; | |
| if (subject) { | |
| dynamicContext += ` <current_subject>${subject}</current_subject>\n`; | |
| } | |
| if (currentContent) { | |
| dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`; | |
| } | |
| if (recipients && recipients.length > 0) { | |
| dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`; | |
| } | |
| dynamicContext += '</dynamic_context>\n'; | |
| const hasDynamic = !!(subject || currentContent || recipients?.length); | |
| const fullSystemPrompt = baseSystemPrompt + (hasDynamic ? dynamicContext : ''); |
| { | ||
| "common": { | ||
| "actions": { | ||
| "logout": "", | ||
| "back": "", | ||
| "create": "", | ||
| "saveChanges": "", | ||
| "saving": "", | ||
| "resetToDefaults": "", | ||
| "close": "", | ||
| "signingOut": "", | ||
| "signedOutSuccess": "", | ||
| "signOutError": "", | ||
| "refresh": "", | ||
| "loading": "", | ||
| "featureNotImplemented": "", | ||
| "moving": "", | ||
| "movedToInbox": "", | ||
| "movingToInbox": "", | ||
| "movedToSpam": "", | ||
| "movingToSpam": "", | ||
| "archiving": "", | ||
| "archived": "", | ||
| "failedToMove": "", | ||
| "addingToFavorites": "", | ||
| "removingFromFavorites": "", | ||
| "addedToFavorites": "", | ||
| "removedFromFavorites": "", | ||
| "failedToAddToFavorites": "", | ||
| "failedToRemoveFromFavorites": "", | ||
| "failedToModifyFavorites": "", | ||
| "movingToBin": "", | ||
| "movedToBin": "", | ||
| "failedToMoveToBin": "", | ||
| "markingAsRead": "", | ||
| "markingAsUnread": "", | ||
| "hiddenImagesWarning": "", | ||
| "showImages": "", | ||
| "disableImages": "", | ||
| "trustSender": "", | ||
| "cancel": "", | ||
| "save": "", | ||
| "remove": "", | ||
| "settings": "" | ||
| }, | ||
| "themes": { | ||
| "dark": "", | ||
| "light": "", | ||
| "system": "" | ||
| }, | ||
| "commandPalette": { | ||
| "title": "", | ||
| "description": "", | ||
| "placeholder": "", | ||
| "noResults": "", | ||
| "groups": { | ||
| "mail": "", | ||
| "settings": "", | ||
| "actions": "", | ||
| "help": "", | ||
| "navigation": "" | ||
| }, | ||
| "commands": { | ||
| "goToInbox": "", | ||
| "goToDrafts": "", | ||
| "goToSent": "", | ||
| "goToSpam": "", | ||
| "goToArchive": "", | ||
| "goToBin": "", | ||
| "goToSettings": "", | ||
| "newEmail": "", | ||
| "composeMessage": "", | ||
| "searchEmails": "", | ||
| "toggleTheme": "", | ||
| "backToMail": "", | ||
| "goToDocs": "", | ||
| "helpWithShortcuts": "" | ||
| } | ||
| }, | ||
| "searchBar": { | ||
| "pickDateRange": "", | ||
| "search": "", | ||
| "clearSearch": "", | ||
| "advancedSearch": "", | ||
| "quickFilters": "", | ||
| "searchIn": "", | ||
| "recipient": "", | ||
| "sender": "", | ||
| "subject": "", | ||
| "dateRange": "", | ||
| "category": "", | ||
| "folder": "", | ||
| "allMail": "", | ||
| "unread": "", | ||
| "hasAttachment": "", | ||
| "starred": "", | ||
| "applyFilters": "", | ||
| "reset": "", | ||
| "searching": "", | ||
| "aiSuggestions": "", | ||
| "aiSearching": "", | ||
| "aiSearchError": "", | ||
| "aiNoResults": "", | ||
| "aiEnhancedQuery": "" | ||
| }, | ||
| "navUser": { | ||
| "customerSupport": "", | ||
| "documentation": "", | ||
| "appTheme": "", | ||
| "accounts": "", | ||
| "signIn": "" | ||
| }, | ||
| "mailCategories": { | ||
| "primary": "", | ||
| "allMail": "", | ||
| "important": "", | ||
| "personal": "", | ||
| "updates": "", | ||
| "promotions": "", | ||
| "social": "", | ||
| "unread": "" | ||
| }, | ||
| "replyCompose": { | ||
| "replyTo": "", | ||
| "thisEmail": "", | ||
| "dropFiles": "", | ||
| "attachments": "", | ||
| "attachmentCount": "", | ||
| "fileCount": "", | ||
| "saveDraft": "", | ||
| "send": "", | ||
| "forward": "" | ||
| }, | ||
| "mailDisplay": { | ||
| "details": "", | ||
| "from": "", | ||
| "to": "", | ||
| "cc": "", | ||
| "bcc": "", | ||
| "date": "", | ||
| "mailedBy": "", | ||
| "signedBy": "", | ||
| "security": "", | ||
| "standardEncryption": "", | ||
| "loadingMailContent": "", | ||
| "unsubscribe": "", | ||
| "unsubscribed": "", | ||
| "unsubscribeDescription": "", | ||
| "unsubscribeOpenSiteDescription": "", | ||
| "cancel": "", | ||
| "goToWebsite": "", | ||
| "failedToUnsubscribe": "" | ||
| }, | ||
| "threadDisplay": { | ||
| "exitFullscreen": "", | ||
| "enterFullscreen": "", | ||
| "archive": "", | ||
| "reply": "", | ||
| "moreOptions": "", | ||
| "moveToSpam": "", | ||
| "replyAll": "", | ||
| "forward": "", | ||
| "markAsUnread": "", | ||
| "markAsRead": "", | ||
| "addLabel": "", | ||
| "muteThread": "", | ||
| "favourites": "", | ||
| "disableImages": "", | ||
| "enableImages": "" | ||
| }, | ||
| "notes": { | ||
| "title": "", | ||
| "empty": "", | ||
| "emptyDescription": "", | ||
| "addNote": "", | ||
| "addYourNote": "", | ||
| "editNote": "", | ||
| "deleteNote": "", | ||
| "deleteConfirm": "", | ||
| "deleteConfirmDescription": "", | ||
| "cancel": "", | ||
| "delete": "", | ||
| "save": "", | ||
| "toSave": "", | ||
| "label": "", | ||
| "search": "", | ||
| "noteCount": "", | ||
| "notePinned": "", | ||
| "noteUnpinned": "", | ||
| "colorChanged": "", | ||
| "noteUpdated": "", | ||
| "noteDeleted": "", | ||
| "noteCopied": "", | ||
| "noteAdded": "", | ||
| "notesReordered": "", | ||
| "noMatchingNotes": "", | ||
| "clearSearch": "", | ||
| "pinnedNotes": "", | ||
| "otherNotes": "", | ||
| "created": "", | ||
| "updated": "", | ||
| "errors": { | ||
| "failedToLoadNotes": "", | ||
| "failedToLoadThreadNotes": "", | ||
| "failedToAddNote": "", | ||
| "failedToUpdateNote": "", | ||
| "failedToDeleteNote": "", | ||
| "failedToUpdateNoteColor": "", | ||
| "noValidNotesToReorder": "", | ||
| "failedToReorderNotes": "" | ||
| }, | ||
| "colors": { | ||
| "default": "", | ||
| "red": "", | ||
| "orange": "", | ||
| "yellow": "", | ||
| "green": "", | ||
| "blue": "", | ||
| "purple": "", | ||
| "pink": "" | ||
| }, | ||
| "actions": { | ||
| "pin": "", | ||
| "unpin": "", | ||
| "edit": "", | ||
| "delete": "", | ||
| "copy": "", | ||
| "changeColor": "" | ||
| } | ||
| }, | ||
| "settings": { | ||
| "notFound": "", | ||
| "saved": "", | ||
| "failedToSave": "", | ||
| "languageChanged": "" | ||
| }, | ||
| "mail": { | ||
| "replies": "", | ||
| "deselectAll": "", | ||
| "selectedEmails": "", | ||
| "noEmailsToSelect": "", | ||
| "markedAsRead": "", | ||
| "markedAsUnread": "", | ||
| "failedToMarkAsRead": "", | ||
| "failedToMarkAsUnread": "", | ||
| "selected": "", | ||
| "clearSelection": "", | ||
| "mute": "", | ||
| "moveToSpam": "", | ||
| "moveToInbox": "", | ||
| "unarchive": "", | ||
| "archive": "", | ||
| "moveToBin": "", | ||
| "restoreFromBin": "", | ||
| "markAsUnread": "", | ||
| "markAsRead": "", | ||
| "addFavorite": "", | ||
| "removeFavorite": "", | ||
| "muteThread": "", | ||
| "moving": "", | ||
| "moved": "", | ||
| "errorMoving": "", | ||
| "reply": "", | ||
| "replyAll": "", | ||
| "forward": "", | ||
| "labels": "", | ||
| "createNewLabel": "", | ||
| "noLabelsAvailable": "", | ||
| "loadMore": "", | ||
| "imagesHidden": "", | ||
| "showImages": "", | ||
| "noEmails": "", | ||
| "noSearchResults": "", | ||
| "clearSearch": "" | ||
| }, | ||
| "units": { | ||
| "mb": "" | ||
| } | ||
| }, | ||
| "navigation": { | ||
| "sidebar": { | ||
| "inbox": "", | ||
| "drafts": "", | ||
| "sent": "", | ||
| "spam": "", | ||
| "archive": "", | ||
| "bin": "", | ||
| "feedback": "", | ||
| "settings": "" | ||
| }, | ||
| "settings": { | ||
| "general": "", | ||
| "connections": "", | ||
| "security": "", | ||
| "appearance": "", | ||
| "signatures": "", | ||
| "shortcuts": "" | ||
| } | ||
| }, | ||
| "pages": { | ||
| "error": { | ||
| "notFound": { | ||
| "title": "", | ||
| "description": "", | ||
| "goBack": "" | ||
| }, | ||
| "settingsNotFound": "" | ||
| }, | ||
| "settings": { | ||
| "general": { | ||
| "title": "", | ||
| "description": "", | ||
| "language": "", | ||
| "selectLanguage": "", | ||
| "timezone": "", | ||
| "selectTimezone": "", | ||
| "dynamicContent": "", | ||
| "dynamicContentDescription": "", | ||
| "externalImages": "", | ||
| "externalImagesDescription": "", | ||
| "trustedSenders": "", | ||
| "trustedSendersDescription": "", | ||
| "languageChangedTo": "", | ||
| "customPrompt": "", | ||
| "customPromptPlaceholder": "", | ||
| "customPromptDescription": "", | ||
| "noResultsFound": "" | ||
| }, | ||
| "connections": { | ||
| "title": "", | ||
| "description": "", | ||
| "disconnectTitle": "", | ||
| "disconnectDescription": "", | ||
| "cancel": "", | ||
| "remove": "", | ||
| "disconnectSuccess": "", | ||
| "disconnectError": "", | ||
| "addEmail": "", | ||
| "connectEmail": "", | ||
| "connectEmailDescription": "", | ||
| "moreComingSoon": "" | ||
| }, | ||
| "security": { | ||
| "title": "", | ||
| "description": "", | ||
| "twoFactorAuth": "", | ||
| "twoFactorAuthDescription": "", | ||
| "loginNotifications": "", | ||
| "loginNotificationsDescription": "", | ||
| "deleteAccount": "", | ||
| "loadImagesDefault": "", | ||
| "loadImagesDefaultDescription": "" | ||
| }, | ||
| "appearance": { | ||
| "title": "", | ||
| "description": "", | ||
| "theme": "", | ||
| "inboxType": "" | ||
| }, | ||
| "signatures": { | ||
| "title": "", | ||
| "description": "", | ||
| "enableSignature": "", | ||
| "enableSignatureDescription": "", | ||
| "includeByDefault": "", | ||
| "includeByDefaultDescription": "", | ||
| "signatureContent": "", | ||
| "signatureContentPlaceholder": "", | ||
| "signaturePreview": "", | ||
| "signatureSaved": "", | ||
| "signaturePreviewDescription": "", | ||
| "editorType": "", | ||
| "editorTypeDescription": "", | ||
| "plainText": "", | ||
| "richText": "", | ||
| "richTextDescription": "", | ||
| "richTextPlaceholder": "", | ||
| "signatureContentHelp": "" | ||
| }, | ||
| "shortcuts": { | ||
| "title": "", | ||
| "description": "", | ||
| "actions": { | ||
| "newEmail": "", | ||
| "sendEmail": "", | ||
| "reply": "", | ||
| "replyAll": "", | ||
| "forward": "", | ||
| "drafts": "", | ||
| "inbox": "", | ||
| "sentMail": "", | ||
| "delete": "", | ||
| "search": "", | ||
| "markAsUnread": "", | ||
| "muteThread": "", | ||
| "printEmail": "", | ||
| "archiveEmail": "", | ||
| "markAsSpam": "", | ||
| "moveToFolder": "", | ||
| "undoLastAction": "", | ||
| "viewEmailDetails": "", | ||
| "goToDrafts": "", | ||
| "expandEmailView": "", | ||
| "helpWithShortcuts": "", | ||
| "recordHotkey": "", | ||
| "pressKeys": "", | ||
| "releaseKeys": "", | ||
| "selectAll": "" | ||
| } | ||
| } | ||
| }, | ||
| "createEmail": { | ||
| "invalidEmail": "", | ||
| "body": "", | ||
| "example": "", | ||
| "attachments": "", | ||
| "addMore": "", | ||
| "dropFilesToAttach": "", | ||
| "writeYourMessageHere": "", | ||
| "emailSentSuccessfully": "", | ||
| "failedToSendEmail": "", | ||
| "signature": { | ||
| "title": "", | ||
| "include": "", | ||
| "remove": "", | ||
| "enable": "", | ||
| "disable": "", | ||
| "add": "" | ||
| }, | ||
| "addLink": "", | ||
| "addUrlToCreateALink": "", | ||
| "editor": { | ||
| "menuBar": { | ||
| "heading1": "", | ||
| "heading2": "", | ||
| "heading3": "", | ||
| "bold": "", | ||
| "italic": "", | ||
| "strikethrough": "", | ||
| "underline": "", | ||
| "link": "", | ||
| "bulletList": "", | ||
| "orderedList": "", | ||
| "viewAttachments": "", | ||
| "attachFiles": "" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Empty translation values need to be populated
The Vietnamese localization file structure looks comprehensive, but all translation values are empty strings. This file appears to be a template ready for translation.
Before deploying this file to production, ensure all translation keys have proper Vietnamese translations. Empty translations would result in blank text throughout the UI for Vietnamese users.
#!/bin/bash
# Check if any translations are missing in the Vietnamese locale file
grep -o '": ""' apps/mail/locales/vi.json | wc -l| import { extractTextFromHTML } from '@/actions/extractText'; | ||
| import { useSearchValue } from '@/hooks/use-search-value'; |
There was a problem hiding this comment.
Remove unused import to keep the build green
extractTextFromHTML is imported but never referenced.
With noUnusedLocals / noUnusedParameters (or the ESLint rule @typescript-eslint/no-unused-vars) enabled, this will break CI.
-import { extractTextFromHTML } from '@/actions/extractText';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { extractTextFromHTML } from '@/actions/extractText'; | |
| import { useSearchValue } from '@/hooks/use-search-value'; | |
| import { useSearchValue } from '@/hooks/use-search-value'; |
| const msg = createMimeMessage(); | ||
| msg.setSender('me'); | ||
| msg.setTo(data.to); |
There was a problem hiding this comment.
Hard‑coded "me" breaks alias support and violates RFC 5322
msg.setSender('me') inserts a literal address me, which is not a valid mailbox and overrides the alias the user just selected in the UI.
Pass the same fromEmail that parseOutgoing supports (falling back to config.auth?.email) so drafts accurately reflect the chosen identity.
-msg.setSender('me');
+msg.setSender({ addr: data.fromEmail ?? config.auth?.email ?? 'noreply@example.com' });Make sure the calling code (createDraft(...) in the action layer) forwards fromEmail just like the send flow.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const msg = createMimeMessage(); | |
| msg.setSender('me'); | |
| msg.setTo(data.to); | |
| const msg = createMimeMessage(); | |
| msg.setSender({ addr: data.fromEmail ?? config.auth?.email ?? 'noreply@example.com' }); | |
| msg.setTo(data.to); |
|
|
||
| try { | ||
| setIsLoading(true); | ||
| const draftData = { | ||
| to: toEmails.join(', '), | ||
| cc: ccEmails.join(', '), | ||
| bcc: bccEmails.join(', '), | ||
| subject: subjectInput, | ||
| message: messageContent || '', | ||
| attachments: attachments, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Draft save omits the selected “From” address
createDraft presently receives to, cc, bcc, etc., but not the user‑chosen fromEmail.
As a result, reopening a draft defaults back to the primary address, which is confusing and may cause the message to be sent from the wrong identity.
Extend both the payload and the backend handler:
const draftData = {
to: toEmails.join(', '),
cc: ccEmails.join(', '),
bcc: bccEmails.join(', '),
+ fromEmail: selectedFromEmail ?? userEmail,
subject: subjectInput,
message: messageContent || '',
attachments,
id: draftId,
};(Be sure to propagate the field through the createDraft action and driver.)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| setIsLoading(true); | |
| const draftData = { | |
| to: toEmails.join(', '), | |
| cc: ccEmails.join(', '), | |
| bcc: bccEmails.join(', '), | |
| subject: subjectInput, | |
| message: messageContent || '', | |
| attachments: attachments, | |
| try { | |
| setIsLoading(true); | |
| const draftData = { | |
| to: toEmails.join(', '), | |
| cc: ccEmails.join(', '), | |
| bcc: bccEmails.join(', '), | |
| fromEmail: selectedFromEmail ?? userEmail, | |
| subject: subjectInput, | |
| message: messageContent || '', | |
| attachments: attachments, | |
| id: draftId, | |
| }; | |
| // …rest of createDraft call… |
| const [selectedFromEmail, setSelectedFromEmail] = useState<string | null>(null); | ||
| const { aliases, isLoading: isLoadingAliases } = useEmailAliases(); | ||
| const ccInputRef = useRef<HTMLInputElement | null>(null); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Reset selectedFromEmail when composer closes
selectedFromEmail is never reset in useEffect that cleans up the composer (see lines 914–925).
If the user opens a fresh composer after having chosen a different alias, the previous alias silently carries over, which is surprising and can even reference a stale alias list for another account.
useEffect(() => {
if (!composerIsOpen) {
...
+ setSelectedFromEmail(null); // reset alias
}
}, [composerIsOpen, reset, mode]);Committable suggestion skipped: line range outside the PR's diff.
| const responses = await generateEmailBody( | ||
| prompt, | ||
| currentContent, | ||
| to, | ||
| subject, | ||
| conversationId, | ||
| userContext, | ||
| ); | ||
|
|
||
| const questionResponse = responses.find((r) => r.type === 'question'); | ||
| if (questionResponse) { | ||
| return { | ||
| content: questionResponse.content, | ||
| jsonContent: createJsonContent([questionResponse.content]), | ||
| type: 'question', | ||
| }; | ||
| const response = responses[0]; | ||
| if (!response) { | ||
| console.error('AI Action Error (Body): Received no response array item from generateEmailBody'); |
There was a problem hiding this comment.
generateEmailBody return type is assumed to be an array
const responses = await generateEmailBody(...); const response = responses[0];
Unless the helper is guaranteed to return an array, this will explode at runtime.
Either:
- Update the helper’s signature to
Promise<AIGeneratedEmail[]>and annotate it properly, or - Remove the array access.
-const responses = await generateEmailBody(...);
-const response = responses[0];
+const response = await generateEmailBody(...); // if single objectCommittable suggestion skipped: line range outside the PR's diff.
| // Handle submit | ||
| const handleSubmit = async (e?: React.MouseEvent) => { | ||
| const handleSubmit = async (e?: React.MouseEvent, overridePrompt?: string): Promise<void> => { | ||
| e?.stopPropagation(); | ||
| if (!prompt.trim()) return; | ||
| const promptToUse = overridePrompt || prompt; | ||
| if (!promptToUse.trim() || isLoading) return; | ||
|
|
||
| try { | ||
| setIsLoading(true); | ||
| setErrorOccurred(false); | ||
| errorFlagRef.current = false; | ||
|
|
||
| // Track AI assistant usage | ||
| posthog.capture('Create Email AI Assistant Submit'); | ||
| addMessage('user', promptToUse, 'question'); | ||
|
|
||
| // Add user message | ||
| addMessage('user', prompt, 'question'); | ||
|
|
||
| // Reset states | ||
| setIsAskingQuestion(false); | ||
| setShowActions(false); | ||
|
|
||
| // Call the server action | ||
| const result = await generateAIEmailContent({ | ||
| prompt, | ||
| currentContent: generatedContent?.content || currentContent, | ||
| setGeneratedBody(null); | ||
| setGeneratedSubject(undefined); | ||
|
|
There was a problem hiding this comment.
Resetting generatedBody before using its value
Immediately after setGeneratedBody(null) the code tries to read generatedBody?.content, which is now undefined, defeating incremental prompt building.
- setGeneratedBody(null);
...
- currentContent: generatedBody?.content || currentContent,
+ const prevBody = generatedBody?.content;
+ setGeneratedBody(null);
...
+ currentContent: prevBody || currentContent,Committable suggestion skipped: line range outside the PR's diff.
Summary by CodeRabbit
New Features
Improvements
.envfile.Bug Fixes
Chores
Style