-
Notifications
You must be signed in to change notification settings - Fork 1.1k
wip deep clean v2 #852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
wip deep clean v2 #852
Changes from all commits
30da521
d0d4645
d7e3464
9d5dca9
560d17b
3c3dca1
f926360
9f182e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||
| "use client"; | ||||||||||||||
|
|
||||||||||||||
| import { useEffect, useState } from "react"; | ||||||||||||||
| import { ProgressPanel } from "@/components/ProgressPanel"; | ||||||||||||||
| import { useBulkOperationProgress } from "@/hooks/useDeepClean"; | ||||||||||||||
|
|
||||||||||||||
| export function BulkOperationProgress() { | ||||||||||||||
| const [hasActiveOperations, setHasActiveOperations] = useState(false); | ||||||||||||||
|
|
||||||||||||||
| const { data } = useBulkOperationProgress( | ||||||||||||||
| hasActiveOperations ? 2000 : 10_000, // Poll more frequently when operations are active | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| const operations = data?.operations || []; | ||||||||||||||
| const activeOperations = operations.filter( | ||||||||||||||
| (op) => op.status === "processing" || op.status === "pending", | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| useEffect(() => { | ||||||||||||||
| setHasActiveOperations(activeOperations.length > 0); | ||||||||||||||
| }, [activeOperations.length]); | ||||||||||||||
|
|
||||||||||||||
| // Show progress for each active operation | ||||||||||||||
| return ( | ||||||||||||||
| <> | ||||||||||||||
| {operations.map((operation) => { | ||||||||||||||
| const isActive = | ||||||||||||||
| operation.status === "processing" || operation.status === "pending"; | ||||||||||||||
| const isCompleted = operation.status === "completed"; | ||||||||||||||
|
|
||||||||||||||
| if (!isActive && !isCompleted) return null; | ||||||||||||||
|
|
||||||||||||||
| // Hide completed operations after 5 seconds | ||||||||||||||
| if (isCompleted) { | ||||||||||||||
| setTimeout(() => { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling setTimeout during render enqueues a new timer every render for each completed operation, leaking timers and hurting performance. Move this timer logic into a useEffect tied to completed operations or remove it. Prompt for AI agentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this setTimeout out of render; invoking it here queues a new timeout on every re-render for completed operations, leaking timers and doing unnecessary work. Use a useEffect tied to completion state instead. Prompt for AI agents |
||||||||||||||
| // This will be handled by React's re-render when the operation is removed from Redis | ||||||||||||||
| }, 5000); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+33
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove render-time Calling Apply this diff to eliminate the render-side effect: - // Hide completed operations after 5 seconds
- if (isCompleted) {
- setTimeout(() => {
- // This will be handled by React's re-render when the operation is removed from Redis
- }, 5000);
- }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const displayName = | ||||||||||||||
| operation.operationType === "archive" | ||||||||||||||
| ? `Archiving ${operation.categoryOrSender}` | ||||||||||||||
| : `Marking ${operation.categoryOrSender} as read`; | ||||||||||||||
|
|
||||||||||||||
| const completedText = | ||||||||||||||
| operation.operationType === "archive" | ||||||||||||||
| ? `Archived ${operation.completedItems} emails!` | ||||||||||||||
| : `Marked ${operation.completedItems} emails as read!`; | ||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <ProgressPanel | ||||||||||||||
| key={operation.operationId} | ||||||||||||||
| totalItems={operation.totalItems} | ||||||||||||||
| remainingItems={operation.totalItems - operation.completedItems} | ||||||||||||||
| inProgressText={displayName} | ||||||||||||||
| completedText={completedText} | ||||||||||||||
| itemLabel="emails" | ||||||||||||||
| /> | ||||||||||||||
| ); | ||||||||||||||
| })} | ||||||||||||||
| </> | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import { atom, useAtom } from "jotai"; | ||
| import useSWR from "swr"; | ||
| import { ProgressPanel } from "@/components/ProgressPanel"; | ||
| import type { CategorizeProgress } from "@/app/api/user/categorize/senders/progress/route"; | ||
| import { useInterval } from "@/hooks/useInterval"; | ||
|
|
||
| const isCategorizeInProgressAtom = atom(false); | ||
|
|
||
| export function useCategorizeProgress() { | ||
| const [isBulkCategorizing, setIsBulkCategorizing] = useAtom( | ||
| isCategorizeInProgressAtom, | ||
| ); | ||
| return { isBulkCategorizing, setIsBulkCategorizing }; | ||
| } | ||
|
|
||
| export function CategorizeSendersProgress({ | ||
| refresh = false, | ||
| }: { | ||
| refresh: boolean; | ||
| }) { | ||
| const { isBulkCategorizing } = useCategorizeProgress(); | ||
| const [fakeProgress, setFakeProgress] = useState(0); | ||
|
|
||
| const { data } = useSWR<CategorizeProgress>( | ||
| "/api/user/categorize/senders/progress", | ||
| { | ||
| refreshInterval: refresh || isBulkCategorizing ? 1000 : undefined, | ||
| }, | ||
| ); | ||
|
|
||
| useInterval( | ||
| () => { | ||
| if (!data?.totalItems) return; | ||
|
|
||
| setFakeProgress((prev) => { | ||
| const realCompleted = data.completedItems || 0; | ||
| if (realCompleted > prev) return realCompleted; | ||
|
|
||
| const maxProgress = Math.min( | ||
| Math.floor(data.totalItems * 0.9), | ||
| realCompleted + 30, | ||
| ); | ||
| return prev < maxProgress ? prev + 1 : prev; | ||
| }); | ||
| }, | ||
| isBulkCategorizing ? 1500 : null, | ||
| ); | ||
|
|
||
| const { setIsBulkCategorizing } = useCategorizeProgress(); | ||
| useEffect(() => { | ||
| let timeoutId: NodeJS.Timeout | undefined; | ||
| if (data?.completedItems === data?.totalItems) { | ||
| timeoutId = setTimeout(() => { | ||
| setIsBulkCategorizing(false); | ||
| setFakeProgress(0); | ||
| }, 3000); | ||
| } | ||
| return () => { | ||
| if (timeoutId) clearTimeout(timeoutId); | ||
| }; | ||
| }, [data?.completedItems, data?.totalItems, setIsBulkCategorizing]); | ||
|
|
||
| if (!data) return null; | ||
|
|
||
| const totalItems = data.totalItems || 0; | ||
| const displayedProgress = Math.max(data.completedItems || 0, fakeProgress); | ||
|
|
||
| return ( | ||
| <ProgressPanel | ||
| totalItems={totalItems} | ||
| remainingItems={totalItems - displayedProgress} | ||
| inProgressText="Categorizing senders..." | ||
| completedText={`Categorization complete! ${displayedProgress} categorized!`} | ||
| itemLabel="senders" | ||
| /> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,105 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SparklesIcon } from "lucide-react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "sonner"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { bulkCategorizeSendersAction } from "@/utils/actions/categorize"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PremiumTooltip, usePremium } from "@/components/PremiumAlert"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { usePremiumModal } from "@/app/(app)/premium/PremiumModal"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ButtonProps } from "@/components/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useCategorizeProgress } from "@/app/(app)/[emailAccountId]/smart-categories/CategorizeProgress"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Tooltip } from "@/components/Tooltip"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useAccount } from "@/providers/EmailAccountProvider"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function CategorizeWithAiButton({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonProps, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonProps?: ButtonProps; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { emailAccountId } = useAccount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isCategorizing, setIsCategorizing] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { hasAiAccess } = usePremium(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { PremiumModal, openModal: openPremiumModal } = usePremiumModal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { setIsBulkCategorizing } = useCategorizeProgress(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <CategorizeWithAiButtonTooltip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasAiAccess={hasAiAccess} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openPremiumModal={openPremiumModal} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loading={isCategorizing} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={!hasAiAccess} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isCategorizing) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.promise( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsCategorizing(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsBulkCategorizing(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await bulkCategorizeSendersAction(emailAccountId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (result?.serverError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsCategorizing(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the categorize action fails we keep isBulkCategorizing stuck at true, so the Deep Clean progress UI never stops. Reset the bulk categorizing flag before throwing to avoid a permanently spinning progress indicator. Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(result.serverError); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsCategorizing(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result?.data?.totalUncategorizedSenders || 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loading: "Categorizing senders... This might take a while.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| success: (totalUncategorizedSenders) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return totalUncategorizedSenders | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? `Categorizing ${totalUncategorizedSenders} senders...` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : "There are no more senders to categorize."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: (err) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `Error categorizing senders: ${err.message}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset categorization flags when the action fails We set - toast.promise(
- async () => {
- setIsCategorizing(true);
- setIsBulkCategorizing(true);
- const result =
- await bulkCategorizeSendersAction(emailAccountId);
-
- if (result?.serverError) {
- setIsCategorizing(false);
- throw new Error(result.serverError);
- }
-
- setIsCategorizing(false);
-
- return result?.data?.totalUncategorizedSenders || 0;
- },
+ toast.promise(
+ async () => {
+ setIsCategorizing(true);
+ setIsBulkCategorizing(true);
+ try {
+ const result =
+ await bulkCategorizeSendersAction(emailAccountId);
+
+ if (result?.serverError) {
+ throw new Error(result.serverError);
+ }
+
+ return result?.data?.totalUncategorizedSenders || 0;
+ } catch (error) {
+ setIsBulkCategorizing(false);
+ throw error;
+ } finally {
+ setIsCategorizing(false);
+ }
+ },📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...buttonProps} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {buttonProps?.children || ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SparklesIcon className="mr-2 size-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Categorize Senders with AI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </CategorizeWithAiButtonTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PremiumModal /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function CategorizeWithAiButtonTooltip({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasAiAccess, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openPremiumModal, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactElement<any>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasAiAccess: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openPremiumModal: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (hasAiAccess) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tooltip content="Categorize thousands of senders. This will take a few minutes."> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PremiumTooltip showTooltip={!hasAiAccess} openModal={openPremiumModal}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </PremiumTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid calling setTimeout within the render path; move this side effect into a useEffect tied to completed operations or remove it if no action is needed.
Prompt for AI agents