From d053421f575accd9442ab98a101751de46b47216 Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:01:35 +0200 Subject: [PATCH] Refactor components to process tab (instead of test tab) --- apps/web/app/(app)/automation/Process.tsx | 4 +- .../(app)/automation/ProcessResultDisplay.tsx | 123 +++++++++++ .../{TestRules.tsx => ProcessRules.tsx} | 208 +----------------- .../app/(app)/automation/ReportMistake.tsx | 10 +- .../(app)/automation/TestCustomEmailForm.tsx | 69 ++++++ apps/web/utils/actions/ai-rule.ts | 9 +- apps/web/utils/actions/validation.ts | 5 + 7 files changed, 220 insertions(+), 208 deletions(-) create mode 100644 apps/web/app/(app)/automation/ProcessResultDisplay.tsx rename apps/web/app/(app)/automation/{TestRules.tsx => ProcessRules.tsx} (58%) create mode 100644 apps/web/app/(app)/automation/TestCustomEmailForm.tsx diff --git a/apps/web/app/(app)/automation/Process.tsx b/apps/web/app/(app)/automation/Process.tsx index 64b141a4..8ef3d268 100644 --- a/apps/web/app/(app)/automation/Process.tsx +++ b/apps/web/app/(app)/automation/Process.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { TestRulesContent } from "@/app/(app)/automation/TestRules"; +import { ProcessRulesContent } from "@/app/(app)/automation/ProcessRules"; import { Toggle } from "@/components/Toggle"; import { Card, @@ -34,7 +34,7 @@ export function Process() { /> - + ); } diff --git a/apps/web/app/(app)/automation/ProcessResultDisplay.tsx b/apps/web/app/(app)/automation/ProcessResultDisplay.tsx new file mode 100644 index 00000000..f3451adc --- /dev/null +++ b/apps/web/app/(app)/automation/ProcessResultDisplay.tsx @@ -0,0 +1,123 @@ +"use client"; + +import Link from "next/link"; +import { capitalCase } from "capital-case"; +import { CheckCircle2Icon, EyeIcon, ExternalLinkIcon } from "lucide-react"; +import type { RunRulesResult } from "@/utils/ai/choose-rule/run-rules"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { HoverCard } from "@/components/HoverCard"; +import { Badge } from "@/components/Badge"; +import { isAIRule } from "@/utils/condition"; + +export function ProcessResultDisplay({ + result, + prefix, +}: { + result: RunRulesResult; + prefix?: string; +}) { + if (!result) return null; + + if (!result.rule) { + return ( + + No rule matched + +
+ This email does not match any of the rules you have set. +
+
+ Reason: {result.reason || "No reason provided"} +
+
+ + } + > + + {prefix ? prefix : ""}No rule matched + + +
+ ); + } + + const MAX_LENGTH = 280; + + const aiGeneratedContent = result.actionItems?.map((action, i) => ( +
+
+ {capitalCase(action.type)} +
+ {Object.entries(action) + .filter( + ([key, value]) => + value && + ["label", "subject", "content", "to", "cc", "bcc", "url"].includes( + key, + ), + ) + .map(([key, value]) => ( +
+ + {capitalCase(key)}: + + + {value} + +
+ ))} +
+ )); + + return ( + + + + Matched rule "{result.rule.name}" + + View rule + + + + + {isAIRule(result.rule) && ( +
+ AI Instructions: + {result.rule.instructions.substring(0, MAX_LENGTH)} + {result.rule.instructions.length >= MAX_LENGTH && "..."} +
+ )} + {!!aiGeneratedContent?.length && ( +
{aiGeneratedContent}
+ )} + {!!result.reason && ( +
+ Reason: + {result.reason} +
+ )} +
+ + } + > + + {prefix ? prefix : ""} + {result.rule.name} + + +
+ ); +} diff --git a/apps/web/app/(app)/automation/TestRules.tsx b/apps/web/app/(app)/automation/ProcessRules.tsx similarity index 58% rename from apps/web/app/(app)/automation/TestRules.tsx rename to apps/web/app/(app)/automation/ProcessRules.tsx index 5aa9607e..69518939 100644 --- a/apps/web/app/(app)/automation/TestRules.tsx +++ b/apps/web/app/(app)/automation/ProcessRules.tsx @@ -1,45 +1,32 @@ "use client"; import { useCallback, useState, useRef, useMemo } from "react"; -import { type SubmitHandler, useForm } from "react-hook-form"; import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; -import Link from "next/link"; import { useSession } from "next-auth/react"; import { parseAsBoolean, useQueryState } from "nuqs"; -import { capitalCase } from "capital-case"; import { BookOpenCheckIcon, - CheckCircle2Icon, SparklesIcon, PenSquareIcon, PauseIcon, - EyeIcon, - ExternalLinkIcon, ChevronsDownIcon, RefreshCcwIcon, } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/Input"; import { toastError } from "@/components/Toast"; import { LoadingContent } from "@/components/LoadingContent"; -import { SlideOverSheet } from "@/components/SlideOverSheet"; import type { MessagesResponse } from "@/app/api/google/messages/route"; import { Separator } from "@/components/ui/separator"; import { TestRulesMessage } from "@/app/(app)/cold-email-blocker/TestRulesMessage"; -import { - runRulesAction, - testAiCustomContentAction, -} from "@/utils/actions/ai-rule"; +import { runRulesAction } from "@/utils/actions/ai-rule"; import type { RulesResponse } from "@/app/api/user/rules/route"; import { Table, TableBody, TableRow, TableCell } from "@/components/ui/table"; import { CardContent } from "@/components/ui/card"; import { isActionError } from "@/utils/error"; import type { RunRulesResult } from "@/utils/ai/choose-rule/run-rules"; import { SearchForm } from "@/components/SearchForm"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { ReportMistake } from "@/app/(app)/automation/ReportMistake"; -import { HoverCard } from "@/components/HoverCard"; import { Badge } from "@/components/Badge"; import { isAIRule, @@ -49,29 +36,12 @@ import { } from "@/utils/condition"; import { BulkRunRules } from "@/app/(app)/automation/BulkRunRules"; import { cn } from "@/utils"; +import { TestCustomEmailForm } from "@/app/(app)/automation/TestCustomEmailForm"; +import { ProcessResultDisplay } from "@/app/(app)/automation/ProcessResultDisplay"; type Message = MessagesResponse["messages"][number]; -export function TestRules(props: { disabled?: boolean }) { - return ( - - - - } - > - - - ); -} - -export function TestRulesContent({ testMode }: { testMode: boolean }) { +export function ProcessRulesContent({ testMode }: { testMode: boolean }) { const [searchQuery, setSearchQuery] = useQueryState("search"); const [showCustomForm, setShowCustomForm] = useQueryState( "custom", @@ -198,7 +168,7 @@ export function TestRulesContent({ testMode }: { testMode: boolean }) { {hasAiRules && showCustomForm && testMode && (
- +
@@ -214,7 +184,7 @@ export function TestRulesContent({ testMode }: { testMode: boolean }) { {messages.map((message) => ( - { - const [testResult, setTestResult] = useState(); - - const { - register, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm(); - - const onSubmit: SubmitHandler = useCallback(async (data) => { - const result = await testAiCustomContentAction({ content: data.message }); - if (isActionError(result)) { - toastError({ - title: "Error testing email", - description: result.error, - }); - } else { - setTestResult(result); - } - }, []); - - return ( -
-
- - - - {testResult && ( -
- -
- )} -
- ); -}; - -function TestRulesContentRow({ +function ProcessRulesRow({ message, userEmail, isRunning, @@ -331,7 +252,7 @@ function TestRulesContentRow({ {result.existing && ( Already processed )} - + + + {testResult && ( +
+ +
+ )} + + ); +}; diff --git a/apps/web/utils/actions/ai-rule.ts b/apps/web/utils/actions/ai-rule.ts index 34a17cfb..b356e84a 100644 --- a/apps/web/utils/actions/ai-rule.ts +++ b/apps/web/utils/actions/ai-rule.ts @@ -33,6 +33,8 @@ import { type SaveRulesPromptBody, runRulesBody, type RunRulesBody, + testAiCustomContentBody, + type TestAiCustomContentBody, } from "@/utils/actions/validation"; import { aiPromptToRules } from "@/utils/ai/rule/prompt-to-rules"; import { aiDiffRules } from "@/utils/ai/rule/diff-rules"; @@ -131,7 +133,12 @@ export const runRulesAction = withActionInstrumentation( export const testAiCustomContentAction = withActionInstrumentation( "testAiCustomContent", - async ({ content }: { content: string }) => { + async (unsafeBody: TestAiCustomContentBody) => { + const { data, error } = testAiCustomContentBody.safeParse(unsafeBody); + if (error) return { error: error.message }; + + const { content } = data; + const session = await auth(); if (!session?.user.id) return { error: "Not logged in" }; const gmail = getGmailClient(session); diff --git a/apps/web/utils/actions/validation.ts b/apps/web/utils/actions/validation.ts index 4de83903..d72898dd 100644 --- a/apps/web/utils/actions/validation.ts +++ b/apps/web/utils/actions/validation.ts @@ -159,6 +159,11 @@ export type RulesExamplesBody = z.infer; export const testAiBody = z.object({ messageId: z.string() }); export type TestAiBody = z.infer; +export const testAiCustomContentBody = z.object({ + content: z.string().min(1, "Please enter a message"), +}); +export type TestAiCustomContentBody = z.infer; + export const runRulesBody = z.object({ messageId: z.string(), threadId: z.string(),