From e572dbb6f9837c818d6da322897c09f0005895ab Mon Sep 17 00:00:00 2001 From: Adam <13007539+MrgSub@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:42:55 -0700 Subject: [PATCH 01/20] improved error handling during attachment downloads. (#1751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Improved Attachment Handling and UI Refinements ## Description This PR improves email attachment handling by implementing on-demand attachment fetching and enhancing error handling. It also includes several UI refinements to the email composer and navigation components. Key changes: - Added a dedicated hook and API endpoint for fetching message attachments - Improved error handling with user-facing toast notifications for attachment operations - Updated the email composer to hide subject input when replying to messages - Enhanced styling for compose button, categories dropdown, and search bar - Fixed attachment loading issues by fetching data on demand instead of storing large attachment data ## Type of Change - [x] 🐛 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (non-breaking change which adds functionality) - [x] 🎨 UI/UX improvement - [x] ⚡ Performance improvement ## Areas Affected - [x] Email Integration (Gmail, IMAP, etc.) - [x] User Interface/Experience - [x] Data Storage/Management - [x] API Endpoints ## Testing Done - [x] Manual testing performed ## Security Considerations - [x] No sensitive data is exposed - [x] Input validation is implemented ## Checklist - [x] I have performed a self-review of my code - [x] My changes generate no new warnings ## Additional Notes The attachment handling improvements should resolve issues where large attachments were causing performance problems or failing to load properly. The UI refinements provide a more consistent experience across the application. --- .../mail/components/create/email-composer.tsx | 55 +- apps/mail/components/mail/mail-display.tsx | 55 +- apps/mail/components/mail/mail.tsx | 9 +- apps/mail/components/ui/app-sidebar.tsx | 8 +- apps/mail/components/ui/nav-main.tsx | 12 +- apps/mail/hooks/use-attachments.ts | 16 + apps/server/package.json | 4 +- apps/server/src/lib/driver/google.ts | 92 +- apps/server/src/lib/driver/types.ts | 12 + apps/server/src/lib/gmail-rate-limit.ts | 2 +- apps/server/src/lib/party.ts | 29 - apps/server/src/main.ts | 7 +- .../src/routes/{chat.ts => agent/index.ts} | 862 ++---------------- apps/server/src/routes/agent/mcp.ts | 475 ++++++++++ apps/server/src/routes/agent/rpc.ts | 223 +++++ apps/server/src/routes/agent/types.ts | 71 ++ apps/server/src/trpc/routes/mail.ts | 23 + apps/server/wrangler.jsonc | 14 +- pnpm-lock.yaml | 65 +- 19 files changed, 1107 insertions(+), 927 deletions(-) create mode 100644 apps/mail/hooks/use-attachments.ts delete mode 100644 apps/server/src/lib/party.ts rename apps/server/src/routes/{chat.ts => agent/index.ts} (58%) create mode 100644 apps/server/src/routes/agent/mcp.ts create mode 100644 apps/server/src/routes/agent/rpc.ts create mode 100644 apps/server/src/routes/agent/types.ts diff --git a/apps/mail/components/create/email-composer.tsx b/apps/mail/components/create/email-composer.tsx index 5a1f676476..75b37c99c8 100644 --- a/apps/mail/components/create/email-composer.tsx +++ b/apps/mail/components/create/email-composer.tsx @@ -149,6 +149,7 @@ export function EmailComposer({ const [imageQuality, setImageQuality] = useState( settings?.settings?.imageCompression || 'medium', ); + const [activeReplyId] = useQueryState('activeReplyId'); const [toggleToolbar, setToggleToolbar] = useState(false); const processAndSetAttachments = async ( filesToProcess: File[], @@ -1224,33 +1225,35 @@ export function EmailComposer({ {/* Subject */} -
-

Subject:

- { - const value = replaceEmojiShortcodes(e.target.value); - setValue('subject', value); - setHasUnsavedChanges(true); - }} - /> -
- - + + + ) : null} {/* From */} {aliases && aliases.length > 1 ? ( diff --git a/apps/mail/components/mail/mail-display.tsx b/apps/mail/components/mail/mail-display.tsx index 92bc57e340..df2413da16 100644 --- a/apps/mail/components/mail/mail-display.tsx +++ b/apps/mail/components/mail/mail-display.tsx @@ -37,6 +37,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import type { Sender, ParsedMessage, Attachment } from '@/types'; import { useActiveConnection } from '@/hooks/use-connections'; +import { useAttachments } from '@/hooks/use-attachments'; import { useBrainState } from '../../hooks/use-summary'; import { useTRPC } from '@/providers/query-provider'; import { useThreadLabels } from '@/hooks/use-labels'; @@ -378,9 +379,20 @@ const ActionButton = ({ onClick, icon, text, shortcut }: ActionButtonProps) => { ); }; -const downloadAttachment = (attachment: { body: string; mimeType: string; filename: string }) => { +const downloadAttachment = async (attachment: { + body: string; + mimeType: string; + filename: string; + attachmentId: string; +}) => { try { - const byteCharacters = atob(attachment.body); + let attachmentData = attachment.body; + + if (!attachmentData) { + throw new Error('Attachment data not found'); + } + + const byteCharacters = atob(attachmentData); const byteNumbers: number[] = Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); @@ -398,6 +410,7 @@ const downloadAttachment = (attachment: { body: string; mimeType: string; filena window.URL.revokeObjectURL(url); } catch (error) { console.error('Error downloading attachment:', error); + toast.error('Failed to download attachment'); } }; @@ -455,9 +468,20 @@ const handleDownloadAllAttachments = console.log('downloaded', subject, attachments); }; -const openAttachment = (attachment: { body: string; mimeType: string; filename: string }) => { +const openAttachment = async (attachment: { + body: string; + mimeType: string; + filename: string; + attachmentId: string; +}) => { try { - const byteCharacters = atob(attachment.body); + let attachmentData = attachment.body; + + if (!attachmentData) { + throw new Error('Attachment data not found'); + } + + const byteCharacters = atob(attachmentData); const byteNumbers: number[] = Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); @@ -484,6 +508,7 @@ const openAttachment = (attachment: { body: string; mimeType: string; filename: } } catch (error) { console.error('Error opening attachment:', error); + toast.error('Failed to open attachment'); } }; @@ -638,6 +663,7 @@ const MoreAboutQuery = ({ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: Props) => { const [isCollapsed, setIsCollapsed] = useState(false); const { data: threadData } = useThread(emailData.threadId ?? null); + const { data: messageAttachments } = useAttachments(emailData.id); // const [unsubscribed, setUnsubscribed] = useState(false); // const [isUnsubscribing, setIsUnsubscribing] = useState(false); const [preventCollapse, setPreventCollapse] = useState(false); @@ -660,6 +686,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: const { data: activeConnection } = useActiveConnection(); const [researchSender, setResearchSender] = useState(null); const [searchQuery, setSearchQuery] = useState(null); + // const trpc = useTRPC(); const isLastEmail = useMemo( () => emailData.id === threadData?.latest?.id, @@ -1077,11 +1104,11 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: ${ - emailData.attachments && emailData.attachments.length > 0 + messageAttachments && messageAttachments.length > 0 ? `
-

Attachments (${emailData.attachments.length})

- ${emailData.attachments +

Attachments (${messageAttachments.length})

+ ${messageAttachments .map( (attachment) => `
@@ -1491,11 +1518,11 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: {m['common.mailDisplay.print']()} - {(emailData.attachments?.length ?? 0) > 0 && ( + {(messageAttachments?.length ?? 0) > 0 && ( @@ -1642,9 +1669,9 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: /> ) : null} {/* mail attachments */} - {emailData?.attachments && emailData?.attachments.length > 0 ? ( + {messageAttachments && messageAttachments.length > 0 ? (
- {emailData?.attachments.map((attachment) => ( + {messageAttachments.map((attachment) => (
- {index < (emailData?.attachments?.length || 0) - 1 && ( + {index < (messageAttachments?.length || 0) - 1 && (
)}
diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index bf333cdd4e..e6666e4e2b 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -531,7 +531,7 @@ export function MailLayout() { diff --git a/apps/mail/components/ui/app-sidebar.tsx b/apps/mail/components/ui/app-sidebar.tsx index cdb68e79b3..4ed3a50f81 100644 --- a/apps/mail/components/ui/app-sidebar.tsx +++ b/apps/mail/components/ui/app-sidebar.tsx @@ -176,13 +176,13 @@ function ComposeButton() { - -
- - Configure the labels that Zero uses to automatically organize your emails. - - - - -
- {labels.map((label, index) => ( -
-
- - -
- ) => - handleUpdateLabel(label.id, 'name', e.target.value) - } - className="h-8" - placeholder="e.g., Important, Follow-up, Archive" - /> -
- -