diff --git a/src/interface/web/app/agents/layout.tsx b/src/interface/web/app/agents/layout.tsx index a0ae26d6..728ad4bb 100644 --- a/src/interface/web/app/agents/layout.tsx +++ b/src/interface/web/app/agents/layout.tsx @@ -8,18 +8,19 @@ export const metadata: Metadata = { title: "Khoj AI - Agents", description: "Find a specialized agent that can help you address more specific needs.", icons: { - icon: '/static/favicon.ico', + icon: "/static/favicon.ico", }, }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { return ( - - - {children} - + object-src 'none';" + > + {children} ); } diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index f9c3a36b..c9e8a1bd 100644 --- a/src/interface/web/app/agents/page.tsx +++ b/src/interface/web/app/agents/page.tsx @@ -1,37 +1,50 @@ -'use client' +"use client"; -import styles from './agents.module.css'; +import styles from "./agents.module.css"; -import Image from 'next/image'; -import useSWR from 'swr'; +import Image from "next/image"; +import useSWR from "swr"; -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; -import { useAuthenticatedData, UserProfile } from '../common/auth'; -import { Button } from '@/components/ui/button'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip" +import { useAuthenticatedData, UserProfile } from "../common/auth"; +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; + +import { PaperPlaneTilt, Lightning, Plus } from "@phosphor-icons/react"; import { - PaperPlaneTilt, - Lightning, - Plus, -} from "@phosphor-icons/react"; - -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTrigger } from '@/components/ui/dialog'; -import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; -import LoginPrompt from '../components/loginPrompt/loginPrompt'; -import { InlineLoading } from '../components/loading/loading'; -import SidePanel from '../components/sidePanel/chatHistorySidePanel'; -import NavMenu from '../components/navMenu/navMenu'; -import { getIconFromIconName } from '../common/iconUtils'; -import { convertColorToTextClass } from '../common/colorUtils'; -import { Alert, AlertDescription } from '@/components/ui/alert'; + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import LoginPrompt from "../components/loginPrompt/loginPrompt"; +import { InlineLoading } from "../components/loading/loading"; +import SidePanel from "../components/sidePanel/chatHistorySidePanel"; +import NavMenu from "../components/navMenu/navMenu"; +import { getIconFromIconName } from "../common/iconUtils"; +import { convertColorToTextClass } from "../common/colorUtils"; +import { Alert, AlertDescription } from "@/components/ui/alert"; export interface AgentData { slug: string; @@ -43,7 +56,6 @@ export interface AgentData { } async function openChat(slug: string, userData: UserProfile | null) { - const unauthenticatedRedirectUrl = `/login?next=/agents?agent=${slug}`; if (!userData) { window.location.href = unauthenticatedRedirectUrl; @@ -61,7 +73,11 @@ async function openChat(slug: string, userData: UserProfile | null) { } } -const agentsFetcher = () => window.fetch('/api/agents').then(res => res.json()).catch(err => console.log(err)); +const agentsFetcher = () => + window + .fetch("/api/agents") + .then((res) => res.json()) + .catch((err) => console.log(err)); interface AgentCardProps { data: AgentData; @@ -69,171 +85,195 @@ interface AgentCardProps { isMobileWidth: boolean; } - function AgentCard(props: AgentCardProps) { const searchParams = new URLSearchParams(window.location.search); - const agentSlug = searchParams.get('agent'); + const agentSlug = searchParams.get("agent"); const [showModal, setShowModal] = useState(agentSlug === props.data.slug); const [showLoginPrompt, setShowLoginPrompt] = useState(false); const userData = props.userProfile; if (showModal) { - window.history.pushState({}, `Khoj AI - Agent ${props.data.slug}`, `/agents?agent=${props.data.slug}`); + window.history.pushState( + {}, + `Khoj AI - Agent ${props.data.slug}`, + `/agents?agent=${props.data.slug}`, + ); } const stylingString = convertColorToTextClass(props.data.color); return ( - - { - showLoginPrompt && + + {showLoginPrompt && ( - } + onOpenChange={setShowLoginPrompt} + /> + )} - { - !props.isMobileWidth ? - { - setShowModal(!showModal); - window.history.pushState({}, `Khoj AI - Agents`, `/agents`); - }}> - -
- { - getIconFromIconName(props.data.icon, props.data.color) || { + setShowModal(!showModal); + window.history.pushState({}, `Khoj AI - Agents`, `/agents`); + }} + > + +
+ {getIconFromIconName(props.data.icon, props.data.color) || ( + {props.data.name} + )} + {props.data.name} +
+
+
+ {props.userProfile ? ( + + ) : ( + + )} +
+ + +
+ {getIconFromIconName(props.data.icon, props.data.color) || ( + {props.data.name} - } - {props.data.name} + )} +

{props.data.name}

- -
- {props.userProfile ? ( - - ) : ( - - )} -
- - -
- { - getIconFromIconName(props.data.icon, props.data.color) || {props.data.name} - } -

{props.data.name}

-
-
-
- {props.data.persona} -
- - - -
-
- : - { - setShowModal(open); - window.history.pushState({}, `Khoj AI - Agents`, `/agents`); - }}> - -
- { - getIconFromIconName(props.data.icon, props.data.color) || {props.data.name} - } - {props.data.name} -
-
-
- {props.userProfile ? ( - - ) : ( - - )} -
- - - {props.data.name} - Full Prompt - + +
{props.data.persona} - - - Done - - - - - } +
+ + + + + + ) : ( + { + setShowModal(open); + window.history.pushState({}, `Khoj AI - Agents`, `/agents`); + }} + > + +
+ {getIconFromIconName(props.data.icon, props.data.color) || ( + {props.data.name} + )} + {props.data.name} +
+
+
+ {props.userProfile ? ( + + ) : ( + + )} +
+ + + {props.data.name} + Full Prompt + + {props.data.persona} + + Done + + +
+ )}
-
- ) + ); } export default function Agents() { - const { data, error } = useSWR('agents', agentsFetcher, { revalidateOnFocus: false }); + const { data, error } = useSWR("agents", agentsFetcher, { + revalidateOnFocus: false, + }); const authenticatedData = useAuthenticatedData(); const [isMobileWidth, setIsMobileWidth] = useState(false); const [showLoginPrompt, setShowLoginPrompt] = useState(false); useEffect(() => { - if (typeof window !== 'undefined') { + if (typeof window !== "undefined") { setIsMobileWidth(window.innerWidth < 768); } - window.addEventListener('resize', () => { + window.addEventListener("resize", () => { setIsMobileWidth(window.innerWidth < 768); }); }, []); @@ -241,12 +281,8 @@ export default function Agents() { if (error) { return (
-
- Agents -
-
- Error loading agents -
+
Agents
+
Error loading agents
); } @@ -279,7 +315,7 @@ export default function Agents() {
- +

Create Agent

@@ -290,21 +326,27 @@ export default function Agents() { - { - showLoginPrompt && + {showLoginPrompt && ( - } - + onOpenChange={setShowLoginPrompt} + /> + )} + - - How it works Use any of these specialized personas to tune your conversation to your needs. + + How it works Use any of these + specialized personas to tune your conversation to your needs.
- {data.map(agent => ( - + {data.map((agent) => ( + ))}
diff --git a/src/interface/web/app/automations/layout.tsx b/src/interface/web/app/automations/layout.tsx index ac0fd376..285b200f 100644 --- a/src/interface/web/app/automations/layout.tsx +++ b/src/interface/web/app/automations/layout.tsx @@ -3,12 +3,11 @@ import { Toaster } from "@/components/ui/toaster"; import "../globals.css"; - export const metadata: Metadata = { title: "Khoj AI - Automations", description: "Use Autoomations with Khoj to simplify the process of running repetitive tasks.", icons: { - icon: '/static/favicon.ico', + icon: "/static/favicon.ico", }, }; diff --git a/src/interface/web/app/automations/page.tsx b/src/interface/web/app/automations/page.tsx index fe4e2858..7db98e77 100644 --- a/src/interface/web/app/automations/page.tsx +++ b/src/interface/web/app/automations/page.tsx @@ -1,16 +1,9 @@ -'use client' +"use client"; -import useSWR from 'swr'; -import { InlineLoading } from '../components/loading/loading'; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle - -} from '@/components/ui/card'; -import { Button, buttonVariants } from '@/components/ui/button'; +import useSWR from "swr"; +import { InlineLoading } from "../components/loading/loading"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Select, @@ -30,61 +23,89 @@ interface AutomationsData { next: string; } -import cronstrue from 'cronstrue'; -import { zodResolver } from "@hookform/resolvers/zod" -import { UseFormReturn, useForm } from "react-hook-form" -import { z } from "zod" -import { Suspense, useEffect, useState } from 'react'; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; -import { DialogTitle } from '@radix-ui/react-dialog'; -import { Textarea } from '@/components/ui/textarea'; -import { LocationData, useIPLocationData } from '../common/utils'; +import cronstrue from "cronstrue"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { UseFormReturn, useForm } from "react-hook-form"; +import { z } from "zod"; +import { Suspense, useEffect, useState } from "react"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { DialogTitle } from "@radix-ui/react-dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { LocationData, useIPLocationData } from "../common/utils"; -import styles from './automations.module.css'; -import ShareLink from '../components/shareLink/shareLink'; -import { useSearchParams } from 'next/navigation'; -import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; -import { CalendarCheck, CalendarDot, CalendarDots, Clock, ClockAfternoon, ClockCounterClockwise, DotsThreeVertical, Envelope, Info, Lightning, MapPinSimple, Pencil, Play, Plus, Trash } from '@phosphor-icons/react'; -import { useAuthenticatedData, UserProfile } from '../common/auth'; -import LoginPrompt from '../components/loginPrompt/loginPrompt'; -import { useToast } from '@/components/ui/use-toast'; -import { ToastAction } from '@/components/ui/toast'; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" -import SidePanel from '../components/sidePanel/chatHistorySidePanel'; -import { Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; +import styles from "./automations.module.css"; +import ShareLink from "../components/shareLink/shareLink"; +import { useSearchParams } from "next/navigation"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; +import { + CalendarCheck, + CalendarDot, + CalendarDots, + Clock, + ClockAfternoon, + ClockCounterClockwise, + DotsThreeVertical, + Envelope, + Info, + Lightning, + MapPinSimple, + Pencil, + Play, + Plus, + Trash, +} from "@phosphor-icons/react"; +import { useAuthenticatedData, UserProfile } from "../common/auth"; +import LoginPrompt from "../components/loginPrompt/loginPrompt"; +import { useToast } from "@/components/ui/use-toast"; +import { ToastAction } from "@/components/ui/toast"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import SidePanel from "../components/sidePanel/chatHistorySidePanel"; +import { Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; -const automationsFetcher = () => window.fetch('/api/automations').then(res => res.json()).catch(err => console.log(err)); +const automationsFetcher = () => + window + .fetch("/api/automations") + .then((res) => res.json()) + .catch((err) => console.log(err)); // Standard cron format: minute hour dayOfMonth month dayOfWeek function getEveryBlahFromCron(cron: string) { - const cronParts = cron.split(' '); + const cronParts = cron.split(" "); const dayOfMonth = cronParts[2]; const dayOfWeek = cronParts[4]; // If both dayOfMonth and dayOfWeek are '*', it runs every day - if (dayOfMonth === '*' && dayOfWeek === '*') { - return 'Day'; + if (dayOfMonth === "*" && dayOfWeek === "*") { + return "Day"; } // If dayOfWeek is not '*', it suggests a specific day of the week, implying a weekly schedule - else if (dayOfWeek !== '*') { - return 'Week'; + else if (dayOfWeek !== "*") { + return "Week"; } // If dayOfMonth is not '*', it suggests a specific day of the month, implying a monthly schedule - else if (dayOfMonth !== '*') { - return 'Month'; + else if (dayOfMonth !== "*") { + return "Month"; } // Default to 'Day' if none of the above conditions are met else { - return 'Day'; + return "Day"; } } function getDayOfWeekFromCron(cron: string) { - const cronParts = cron.split(' '); - if (cronParts[3] === '*' && cronParts[4] !== '*') { + const cronParts = cron.split(" "); + if (cronParts[3] === "*" && cronParts[4] !== "*") { return Number(cronParts[4]); } @@ -92,25 +113,25 @@ function getDayOfWeekFromCron(cron: string) { } function getTimeRecurrenceFromCron(cron: string) { - const cronParts = cron.split(' '); + const cronParts = cron.split(" "); const hour = cronParts[1]; const minute = cronParts[0]; - const period = Number(hour) >= 12 ? 'PM' : 'AM'; + const period = Number(hour) >= 12 ? "PM" : "AM"; let friendlyHour = Number(hour) > 12 ? Number(hour) - 12 : hour; - if (friendlyHour === '00') { - friendlyHour = '12'; + if (friendlyHour === "00") { + friendlyHour = "12"; } let friendlyMinute = minute; - if (Number(friendlyMinute) < 10 && friendlyMinute !== '00') { + if (Number(friendlyMinute) < 10 && friendlyMinute !== "00") { friendlyMinute = `0${friendlyMinute}`; } return `${friendlyHour}:${friendlyMinute} ${period}`; } function getDayOfMonthFromCron(cron: string) { - const cronParts = cron.split(' '); + const cronParts = cron.split(" "); return String(cronParts[2]); } @@ -119,22 +140,22 @@ function cronToHumanReadableString(cron: string) { return cronstrue.toString(cron); } -const frequencies = ['Day', 'Week', 'Month']; +const frequencies = ["Day", "Week", "Month"]; const daysOfMonth = Array.from({ length: 31 }, (_, i) => String(i + 1)); -const weekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; +const weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; const timeOptions: string[] = []; -const timePeriods = ['AM', 'PM']; +const timePeriods = ["AM", "PM"]; // Populate the time selector with options for each hour of the day for (var i = 0; i < timePeriods.length; i++) { for (var hour = 0; hour < 12; hour++) { for (var minute = 0; minute < 60; minute += 15) { // Ensure all minutes are two digits - const paddedMinute = String(minute).padStart(2, '0'); + const paddedMinute = String(minute).padStart(2, "0"); const friendlyHour = hour === 0 ? 12 : hour; timeOptions.push(`${friendlyHour}:${paddedMinute} ${timePeriods[i]}`); } @@ -145,41 +166,45 @@ const timestamp = Date.now(); const suggestedAutomationsMetadata: AutomationsData[] = [ { - "subject": "Weekly Newsletter", - "query_to_run": "Compile a message including: 1. A recap of news from last week 2. An at-home workout I can do before work 3. A quote to inspire me for the week ahead", - "schedule": "9AM every Monday", - "next": "Next run at 9AM on Monday", - "crontime": "0 9 * * 1", - "id": timestamp, - "scheduling_request": "", + subject: "Weekly Newsletter", + query_to_run: + "Compile a message including: 1. A recap of news from last week 2. An at-home workout I can do before work 3. A quote to inspire me for the week ahead", + schedule: "9AM every Monday", + next: "Next run at 9AM on Monday", + crontime: "0 9 * * 1", + id: timestamp, + scheduling_request: "", }, { - "subject": "Daily Bedtime Story", - "query_to_run": "Compose a bedtime story that a five-year-old might enjoy. It should not exceed five paragraphs. Appeal to the imagination, but weave in learnings.", - "schedule": "9PM every night", - "next": "Next run at 9PM today", - "crontime": "0 21 * * *", - "id": timestamp + 1, - "scheduling_request": "", + subject: "Daily Bedtime Story", + query_to_run: + "Compose a bedtime story that a five-year-old might enjoy. It should not exceed five paragraphs. Appeal to the imagination, but weave in learnings.", + schedule: "9PM every night", + next: "Next run at 9PM today", + crontime: "0 21 * * *", + id: timestamp + 1, + scheduling_request: "", }, { - "subject": "Front Page of Hacker News", - "query_to_run": "Summarize the top 5 posts from https://news.ycombinator.com/best and share them with me, including links", - "schedule": "9PM on every Wednesday", - "next": "Next run at 9PM on Wednesday", - "crontime": "0 21 * * 3", - "id": timestamp + 2, - "scheduling_request": "", + subject: "Front Page of Hacker News", + query_to_run: + "Summarize the top 5 posts from https://news.ycombinator.com/best and share them with me, including links", + schedule: "9PM on every Wednesday", + next: "Next run at 9PM on Wednesday", + crontime: "0 21 * * 3", + id: timestamp + 2, + scheduling_request: "", }, { - "subject": "Market Summary", - "query_to_run": "Get the market summary for today and share it with me. Focus on tech stocks and the S&P 500.", - "schedule": "9AM on every weekday", - "next": "Next run at 9AM on Monday", - "crontime": "0 9 * * *", - "id": timestamp + 3, - "scheduling_request": "", - } + subject: "Market Summary", + query_to_run: + "Get the market summary for today and share it with me. Focus on tech stocks and the S&P 500.", + schedule: "9AM on every weekday", + next: "Next run at 9AM on Monday", + crontime: "0 9 * * *", + id: timestamp + 3, + scheduling_request: "", + }, ]; function createShareLink(automation: AutomationsData) { @@ -193,27 +218,27 @@ function createShareLink(automation: AutomationsData) { } function deleteAutomation(automationId: string, setIsDeleted: (isDeleted: boolean) => void) { - fetch(`/api/automation?automation_id=${automationId}`, { method: 'DELETE' } - ).then(response => response.json()) - .then(data => { + fetch(`/api/automation?automation_id=${automationId}`, { method: "DELETE" }) + .then((response) => response.json()) + .then((data) => { setIsDeleted(true); }); } function sendAPreview(automationId: string, setToastMessage: (toastMessage: string) => void) { - fetch(`/api/trigger/automation?automation_id=${automationId}`, { method: 'POST' }) - .then(response => { + fetch(`/api/trigger/automation?automation_id=${automationId}`, { method: "POST" }) + .then((response) => { if (!response.ok) { - throw new Error('Network response was not ok'); + throw new Error("Network response was not ok"); } return response; }) - .then(automations => { + .then((automations) => { setToastMessage("Automation triggered. Check your inbox in a few minutes!"); }) - .catch(error => { + .catch((error) => { setToastMessage("Sorry, something went wrong. Try again later."); - }) + }); } interface AutomationsCardProps { @@ -227,19 +252,20 @@ interface AutomationsCardProps { authenticatedData: UserProfile | null; } - function AutomationsCard(props: AutomationsCardProps) { const [isEditing, setIsEditing] = useState(false); - const [updatedAutomationData, setUpdatedAutomationData] = useState(null); + const [updatedAutomationData, setUpdatedAutomationData] = useState( + null, + ); const [isDeleted, setIsDeleted] = useState(false); - const [toastMessage, setToastMessage] = useState(''); + const [toastMessage, setToastMessage] = useState(""); const { toast } = useToast(); const automation = props.automation; - const [timeRecurrence, setTimeRecurrence] = useState(''); + const [timeRecurrence, setTimeRecurrence] = useState(""); - const [intervalString, setIntervalString] = useState(''); + const [intervalString, setIntervalString] = useState(""); useEffect(() => { // The updated automation data, if present, takes priority over the original automation data @@ -247,33 +273,30 @@ function AutomationsCard(props: AutomationsCardProps) { setTimeRecurrence(getTimeRecurrenceFromCron(automationData.crontime)); const frequency = getEveryBlahFromCron(automationData.crontime); - if (frequency === 'Day') { - setIntervalString('Daily'); - } else if (frequency === 'Week') { + if (frequency === "Day") { + setIntervalString("Daily"); + } else if (frequency === "Week") { const dayOfWeek = getDayOfWeekFromCron(automationData.crontime); if (dayOfWeek === undefined) { - setIntervalString('Weekly'); + setIntervalString("Weekly"); } else { setIntervalString(`${weekDays[dayOfWeek]}`); } - } else if (frequency === 'Month') { + } else if (frequency === "Month") { const dayOfMonth = getDayOfMonthFromCron(automationData.crontime); setIntervalString(`Monthly on the ${dayOfMonth}`); } }, [updatedAutomationData, automation]); - useEffect(() => { const toastTitle = `Automation: ${updatedAutomationData?.subject || automation.subject}`; if (toastMessage) { toast({ title: toastTitle, description: toastMessage, - action: ( - Ok - ), - }) - setToastMessage(''); + action: Ok, + }); + setToastMessage(""); } }, [toastMessage, updatedAutomationData, automation, toast]); @@ -282,34 +305,37 @@ function AutomationsCard(props: AutomationsCardProps) { } return ( - + - + {updatedAutomationData?.subject || automation.subject} - + - - { - (!props.suggestedCard && props.locationData) && ( - - ) - } + + {!props.suggestedCard && props.locationData && ( + + )} - { - !props.suggestedCard && ( - - ) - } - + )} + - + {updatedAutomationData?.query_to_run || automation.query_to_run} -
-
- -
+
+
+ +
{timeRecurrence}
-
- -
+
+ +
{intervalString}
- { - props.suggestedCard && props.setNewAutomationData && ( - - ) - } + {props.suggestedCard && props.setNewAutomationData && ( + + )} - ) + ); } interface SharedAutomationCardProps { @@ -394,9 +423,9 @@ function SharedAutomationCard(props: SharedAutomationCardProps) { const searchParams = useSearchParams(); const [isCreating, setIsCreating] = useState(true); - const subject = searchParams.get('subject'); - const query = searchParams.get('query'); - const crontime = searchParams.get('crontime'); + const subject = searchParams.get("subject"); + const query = searchParams.get("query"); + const crontime = searchParams.get("crontime"); if (!subject || !query || !crontime) { return null; @@ -406,27 +435,26 @@ function SharedAutomationCard(props: SharedAutomationCardProps) { id: 0, subject: decodeURIComponent(subject), query_to_run: decodeURIComponent(query), - scheduling_request: '', + scheduling_request: "", schedule: cronToHumanReadableString(decodeURIComponent(crontime)), crontime: decodeURIComponent(crontime), - next: '', - } + next: "", + }; - return ( - isCreating ? - - : null - ) + return isCreating ? ( + + ) : null; } const EditAutomationSchema = z.object({ @@ -456,16 +484,23 @@ function EditCard(props: EditCardProps) { resolver: zodResolver(EditAutomationSchema), defaultValues: { subject: automation?.subject, - everyBlah: (automation?.crontime ? getEveryBlahFromCron(automation.crontime) : 'Day'), - dayOfWeek: (automation?.crontime ? getDayOfWeekFromCron(automation.crontime) : undefined), - timeRecurrence: (automation?.crontime ? getTimeRecurrenceFromCron(automation.crontime) : '12:00 PM'), - dayOfMonth: (automation?.crontime ? getDayOfMonthFromCron(automation.crontime) : "1"), + everyBlah: automation?.crontime ? getEveryBlahFromCron(automation.crontime) : "Day", + dayOfWeek: automation?.crontime ? getDayOfWeekFromCron(automation.crontime) : undefined, + timeRecurrence: automation?.crontime + ? getTimeRecurrenceFromCron(automation.crontime) + : "12:00 PM", + dayOfMonth: automation?.crontime ? getDayOfMonthFromCron(automation.crontime) : "1", queryToRun: automation?.query_to_run, }, - }) + }); const onSubmit = (values: z.infer) => { - const cronFrequency = convertFrequencyToCron(values.everyBlah, values.timeRecurrence, values.dayOfWeek, values.dayOfMonth); + const cronFrequency = convertFrequencyToCron( + values.everyBlah, + values.timeRecurrence, + values.dayOfWeek, + values.dayOfMonth, + ); let updateQueryUrl = `/api/automation?`; @@ -488,16 +523,15 @@ function EditCard(props: EditCardProps) { updateQueryUrl += `&timezone=${props.locationData.timezone}`; } - let method = props.createNew ? 'POST' : 'PUT'; + let method = props.createNew ? "POST" : "PUT"; fetch(updateQueryUrl, { method: method }) - .then(response => response.json()) - .then - ((data: AutomationsData) => { + .then((response) => response.json()) + .then((data: AutomationsData) => { props.setIsEditing(false); props.setUpdatedAutomationData({ id: data.id, - subject: data.subject || '', + subject: data.subject || "", query_to_run: data.query_to_run, scheduling_request: data.scheduling_request, schedule: cronToHumanReadableString(data.crontime), @@ -505,26 +539,34 @@ function EditCard(props: EditCardProps) { next: data.next, }); }); - } + }; - function convertFrequencyToCron(frequency: string, timeRecurrence: string, dayOfWeek?: number, dayOfMonth?: string) { - let cronString = ''; + function convertFrequencyToCron( + frequency: string, + timeRecurrence: string, + dayOfWeek?: number, + dayOfMonth?: string, + ) { + let cronString = ""; - const minutes = timeRecurrence.split(':')[1].split(' ')[0]; - const period = timeRecurrence.split(':')[1].split(' ')[1]; - const rawHourAsNumber = Number(timeRecurrence.split(':')[0]); - const hours = period === 'PM' && (rawHourAsNumber < 12) ? String(rawHourAsNumber + 12) : rawHourAsNumber; + const minutes = timeRecurrence.split(":")[1].split(" ")[0]; + const period = timeRecurrence.split(":")[1].split(" ")[1]; + const rawHourAsNumber = Number(timeRecurrence.split(":")[0]); + const hours = + period === "PM" && rawHourAsNumber < 12 + ? String(rawHourAsNumber + 12) + : rawHourAsNumber; - const dayOfWeekNumber = dayOfWeek ? dayOfWeek : '*'; + const dayOfWeekNumber = dayOfWeek ? dayOfWeek : "*"; switch (frequency) { - case 'Day': + case "Day": cronString = `${minutes} ${hours} * * *`; break; - case 'Week': + case "Week": cronString = `${minutes} ${hours} * * ${dayOfWeekNumber}`; break; - case 'Month': + case "Month": cronString = `${minutes} ${hours} ${dayOfMonth} * *`; break; } @@ -540,9 +582,9 @@ function EditCard(props: EditCardProps) { onSubmit={onSubmit} create={props.createNew} isLoggedIn={props.isLoggedIn} - setShowLoginPrompt={props.setShowLoginPrompt} /> - ) - + setShowLoginPrompt={props.setShowLoginPrompt} + /> + ); } interface AutomationModificationFormProps { @@ -556,87 +598,90 @@ interface AutomationModificationFormProps { } function AutomationModificationForm(props: AutomationModificationFormProps) { - const [isSaving, setIsSaving] = useState(false); const { errors } = props.form.formState; - function recommendationPill(recommendationText: string, onChange: (value: any, event: React.MouseEvent) => void) { + function recommendationPill( + recommendationText: string, + onChange: (value: any, event: React.MouseEvent) => void, + ) { return ( - ) + ); } const recommendationPills = [ "Make a picture of", "Generate a summary of", "Create a newsletter of", - "Notify me when" + "Notify me when", ]; return (
- { - props.onSubmit(values); - setIsSaving(true); - })} className="space-y-8"> + { + props.onSubmit(values); + setIsSaving(true); + })} + className="space-y-8" + > Setup - Emails will be sent to this address. Timezone and location data will be used to schedule automations. - { - props.locationData && - metadataMap(props.locationData, props.authenticatedData) - } + Emails will be sent to this address. Timezone and location data will be used + to schedule automations. + {props.locationData && + metadataMap(props.locationData, props.authenticatedData)} - { - !props.create && ( - ( - - Subject - - This is the subject of the email you will receive. - - - - - - {errors.subject && {errors.subject?.message}} - - )} - />) - } + {!props.create && ( + ( + + Subject + + This is the subject of the email you will receive. + + + + + + {errors.subject && ( + {errors.subject?.message} + )} + + )} + /> + )} ( - - - Frequency - - - How often should this automation run? - + + Frequency + How often should this automation run? - {errors.subject && {errors.everyBlah?.message}} + {errors.subject && ( + {errors.everyBlah?.message} + )} )} /> - { - props.form.watch('everyBlah') === 'Week' && ( - ( - - - Every week, on which day should this automation run? - - - - {errors.subject && {errors.dayOfWeek?.message}} - - )} - /> - ) - } - { - props.form.watch('everyBlah') === 'Month' && ( - ( - - Every month, on which day should the automation run? - - - {errors.subject && {errors.dayOfMonth?.message}} - - )} - /> - ) - } - { - ( - props.form.watch('everyBlah') === 'Day' || - props.form.watch('everyBlah') == 'Week' || - props.form.watch('everyBlah') == 'Month') && ( - ( - - Time - - On the days this automation runs, at what time should it run? - - - - {errors.subject && {errors.timeRecurrence?.message}} - - )} - /> - ) - } + {props.form.watch("everyBlah") === "Week" && ( + ( + + + Every week, on which day should this automation run? + + + + {errors.subject && ( + {errors.dayOfWeek?.message} + )} + + )} + /> + )} + {props.form.watch("everyBlah") === "Month" && ( + ( + + + Every month, on which day should the automation run? + + + + {errors.subject && ( + {errors.dayOfMonth?.message} + )} + + )} + /> + )} + {(props.form.watch("everyBlah") === "Day" || + props.form.watch("everyBlah") == "Week" || + props.form.watch("everyBlah") == "Month") && ( + ( + + Time + + On the days this automation runs, at what time should it run? + + + + {errors.subject && ( + {errors.timeRecurrence?.message} + )} + + )} + /> + )} ( Instructions - - What do you want Khoj to do? - - { - props.create && ( -
- { - recommendationPills.map((recommendation) => recommendationPill(recommendation, field.onChange)) - } -
- ) - } + What do you want Khoj to do? + {props.create && ( +
+ {recommendationPills.map((recommendation) => + recommendationPill(recommendation, field.onChange), + )} +
+ )} -