diff --git a/apps/rowboat/app/actions/recurring-job-rules.actions.ts b/apps/rowboat/app/actions/recurring-job-rules.actions.ts new file mode 100644 index 00000000..a72d87c9 --- /dev/null +++ b/apps/rowboat/app/actions/recurring-job-rules.actions.ts @@ -0,0 +1,91 @@ +"use server"; + +import { container } from "@/di/container"; +import { ICreateRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/create-recurring-job-rule.controller"; +import { IListRecurringJobRulesController } from "@/src/interface-adapters/controllers/recurring-job-rules/list-recurring-job-rules.controller"; +import { IFetchRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/fetch-recurring-job-rule.controller"; +import { IToggleRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/toggle-recurring-job-rule.controller"; +import { IDeleteRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/delete-recurring-job-rule.controller"; +import { authCheck } from "./auth_actions"; +import { z } from "zod"; +import { Message } from "@/app/lib/types/types"; + +const createRecurringJobRuleController = container.resolve('createRecurringJobRuleController'); +const listRecurringJobRulesController = container.resolve('listRecurringJobRulesController'); +const fetchRecurringJobRuleController = container.resolve('fetchRecurringJobRuleController'); +const toggleRecurringJobRuleController = container.resolve('toggleRecurringJobRuleController'); +const deleteRecurringJobRuleController = container.resolve('deleteRecurringJobRuleController'); + +export async function createRecurringJobRule(request: { + projectId: string, + input: { + messages: z.infer[], + }, + cron: string, +}) { + const user = await authCheck(); + + return await createRecurringJobRuleController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + input: request.input, + cron: request.cron, + }); +} + +export async function listRecurringJobRules(request: { + projectId: string, + cursor?: string, + limit?: number, +}) { + const user = await authCheck(); + + return await listRecurringJobRulesController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + cursor: request.cursor, + limit: request.limit, + }); +} + +export async function fetchRecurringJobRule(request: { + ruleId: string, +}) { + const user = await authCheck(); + + return await fetchRecurringJobRuleController.execute({ + caller: 'user', + userId: user._id, + ruleId: request.ruleId, + }); +} + +export async function toggleRecurringJobRule(request: { + ruleId: string, + disabled: boolean, +}) { + const user = await authCheck(); + + return await toggleRecurringJobRuleController.execute({ + caller: 'user', + userId: user._id, + ruleId: request.ruleId, + disabled: request.disabled, + }); +} + +export async function deleteRecurringJobRule(request: { + projectId: string, + ruleId: string, +}) { + const user = await authCheck(); + + return await deleteRecurringJobRuleController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + ruleId: request.ruleId, + }); +} diff --git a/apps/rowboat/app/actions/scheduled-job-rules.actions.ts b/apps/rowboat/app/actions/scheduled-job-rules.actions.ts new file mode 100644 index 00000000..cb15bdec --- /dev/null +++ b/apps/rowboat/app/actions/scheduled-job-rules.actions.ts @@ -0,0 +1,75 @@ +"use server"; + +import { container } from "@/di/container"; +import { ICreateScheduledJobRuleController } from "@/src/interface-adapters/controllers/scheduled-job-rules/create-scheduled-job-rule.controller"; +import { IListScheduledJobRulesController } from "@/src/interface-adapters/controllers/scheduled-job-rules/list-scheduled-job-rules.controller"; +import { IFetchScheduledJobRuleController } from "@/src/interface-adapters/controllers/scheduled-job-rules/fetch-scheduled-job-rule.controller"; +import { IDeleteScheduledJobRuleController } from "@/src/interface-adapters/controllers/scheduled-job-rules/delete-scheduled-job-rule.controller"; +import { authCheck } from "./auth_actions"; +import { z } from "zod"; +import { Message } from "@/app/lib/types/types"; + +const createScheduledJobRuleController = container.resolve('createScheduledJobRuleController'); +const listScheduledJobRulesController = container.resolve('listScheduledJobRulesController'); +const fetchScheduledJobRuleController = container.resolve('fetchScheduledJobRuleController'); +const deleteScheduledJobRuleController = container.resolve('deleteScheduledJobRuleController'); + +export async function createScheduledJobRule(request: { + projectId: string, + input: { + messages: z.infer[], + }, + scheduledTime: string, // ISO datetime string +}) { + const user = await authCheck(); + + return await createScheduledJobRuleController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + input: request.input, + scheduledTime: request.scheduledTime, + }); +} + +export async function listScheduledJobRules(request: { + projectId: string, + cursor?: string, + limit?: number, +}) { + const user = await authCheck(); + + return await listScheduledJobRulesController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + cursor: request.cursor, + limit: request.limit, + }); +} + +export async function fetchScheduledJobRule(request: { + ruleId: string, +}) { + const user = await authCheck(); + + return await fetchScheduledJobRuleController.execute({ + caller: 'user', + userId: user._id, + ruleId: request.ruleId, + }); +} + +export async function deleteScheduledJobRule(request: { + projectId: string, + ruleId: string, +}) { + const user = await authCheck(); + + return await deleteScheduledJobRuleController.execute({ + caller: 'user', + userId: user._id, + projectId: request.projectId, + ruleId: request.ruleId, + }); +} \ No newline at end of file diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/create-recurring-job-rule-form.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/create-recurring-job-rule-form.tsx new file mode 100644 index 00000000..465507c3 --- /dev/null +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/create-recurring-job-rule-form.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Panel } from "@/components/common/panel-common"; +import { createRecurringJobRule } from "@/app/actions/recurring-job-rules.actions"; +import { ArrowLeftIcon, PlusIcon, TrashIcon, InfoIcon } from "lucide-react"; +import Link from "next/link"; + +// Define a simpler message type for the form that only includes the fields we need +type FormMessage = { + role: "system" | "user" | "assistant"; + content: string; +}; + +const commonCronExamples = [ + { label: "Every minute", value: "* * * * *" }, + { label: "Every 5 minutes", value: "*/5 * * * *" }, + { label: "Every hour", value: "0 * * * *" }, + { label: "Every 2 hours", value: "0 */2 * * *" }, + { label: "Daily at midnight", value: "0 0 * * *" }, + { label: "Daily at 9 AM", value: "0 9 * * *" }, + { label: "Weekly on Sunday at midnight", value: "0 0 * * 0" }, + { label: "Monthly on the 1st at midnight", value: "0 0 1 * *" }, +]; + +export function CreateRecurringJobRuleForm({ projectId }: { projectId: string }) { + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [messages, setMessages] = useState([ + { role: "user", content: "" } + ]); + const [cronExpression, setCronExpression] = useState("* * * * *"); + const [showCronHelp, setShowCronHelp] = useState(false); + + const addMessage = () => { + setMessages([...messages, { role: "user", content: "" }]); + }; + + const removeMessage = (index: number) => { + if (messages.length > 1) { + setMessages(messages.filter((_, i) => i !== index)); + } + }; + + const updateMessage = (index: number, field: keyof FormMessage, value: string) => { + const newMessages = [...messages]; + newMessages[index] = { ...newMessages[index], [field]: value }; + setMessages(newMessages); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validate required fields + if (!cronExpression.trim()) { + alert("Please enter a cron expression"); + return; + } + + if (messages.some(msg => !msg.content?.trim())) { + alert("Please fill in all message content"); + return; + } + + setLoading(true); + try { + // Convert FormMessage to the expected Message type + const convertedMessages = messages.map(msg => { + if (msg.role === "assistant") { + return { + role: msg.role, + content: msg.content, + agentName: null, + responseType: "internal" as const, + timestamp: undefined + }; + } + return { + role: msg.role, + content: msg.content, + timestamp: undefined + }; + }); + + await createRecurringJobRule({ + projectId, + input: { messages: convertedMessages }, + cron: cronExpression, + }); + router.push(`/projects/${projectId}/job-rules`); + } catch (error) { + console.error("Failed to create recurring job rule:", error); + alert("Failed to create recurring job rule"); + } finally { + setLoading(false); + } + }; + + return ( + + + + +
+ CREATE RECURRING JOB RULE +
+ + } + > +
+
+
+ {/* Cron Expression */} +
+
+ + +
+ + setCronExpression(e.target.value)} + placeholder="* * * * *" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white font-mono" + required + /> + + {showCronHelp && ( +
+
+ Format: minute hour day month dayOfWeek +
+
+ Examples: +
+
+ {commonCronExamples.map((example, index) => ( +
+ + {example.value} + + + {example.label} + +
+ ))} +
+
+ Note: All times are in UTC timezone +
+
+ )} +
+ + {/* Messages */} +
+
+ + +
+ +
+ {messages.map((message, index) => ( +
+
+ + {messages.length > 1 && ( + + )} +
+