feat(cron): create command payloads in UI (#1288)

This commit is contained in:
Zezae Oh authored and GitHub committed 2026-06-27 12:27:03 +07:00
1 parent 7a9210b765
commit a90127c3d6
7 files changed
+85 -21

No files matched your search

+2 -1
View File
@@ -43,7 +43,8 @@
"messagePlaceholder": "What should the agent do?",
"cancel": "Cancel",
"create": "Create",
"creating": "Creating..."
"creating": "Creating...",
"payloadType": "Payload type"
},
"enable": {
"title": "Enable Cron Job",
+2 -1
View File
@@ -43,7 +43,8 @@
"messagePlaceholder": "에이전트가 무엇을 해야 하나요?",
"cancel": "취소",
"create": "만들기",
"creating": "만드는 중..."
"creating": "만드는 중...",
"payloadType": "Payload 유형"
},
"enable": {
"title": "Cron 작업 활성화",
+2 -1
View File
@@ -43,7 +43,8 @@
"messagePlaceholder": "Agent cần làm gì?",
"cancel": "Hủy",
"create": "Tạo",
"creating": "Đang tạo..."
"creating": "Đang tạo...",
"payloadType": "Loại payload"
},
"enable": {
"title": "Bật tác vụ định kỳ",
+2 -1
View File
@@ -43,7 +43,8 @@
"messagePlaceholder": "Agent应该做什么?",
"cancel": "取消",
"create": "创建",
"creating": "创建中..."
"creating": "创建中...",
"payloadType": "Payload 类型"
},
"enable": {
"title": "启用定时任务",
+58 -15
View File
@@ -13,7 +13,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import type { CronSchedule } from "./hooks/use-cron";
import type { CronCommandSpec, CronSchedule } from "./hooks/use-cron";
import { slugify } from "@/lib/slug";
import { useAgents } from "@/pages/agents/hooks/use-agents";
import { cronCreateSchema, type CronCreateFormData } from "@/schemas/cron.schema";
@@ -24,7 +24,8 @@ interface CronFormDialogProps {
onSubmit: (data: {
name: string;
schedule: CronSchedule;
message: string;
message?: string;
command?: CronCommandSpec;
agentId?: string;
}) => Promise<void>;
}
@@ -38,7 +39,9 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
mode: "onChange",
defaultValues: {
name: "",
payloadKind: "agent_turn",
message: "",
commandJson: JSON.stringify({ argv: ["sh", "-c", "echo hello"] }, null, 2),
agentId: "",
scheduleKind: "every",
everyValue: "60",
@@ -47,6 +50,7 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
});
const scheduleKind = watch("scheduleKind");
const payloadKind = watch("payloadKind");
const onFormSubmit = async (data: CronCreateFormData) => {
let schedule: CronSchedule;
@@ -58,10 +62,15 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
schedule = { kind: "at", atMs: Date.now() + 60000 };
}
const command = data.payloadKind === "command"
? JSON.parse(data.commandJson || "{}") as CronCommandSpec
: undefined;
await onSubmit({
name: data.name,
schedule,
message: data.message,
message: data.payloadKind === "agent_turn" ? data.message : undefined,
command,
agentId: data.agentId || undefined,
});
onOpenChange(false);
@@ -115,6 +124,24 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
/>
</div>
<div className="space-y-2">
<Label>{t("create.payloadType")}</Label>
<div className="flex gap-2">
{(["agent_turn", "command"] as const).map((kind) => (
<Button
key={kind}
type="button"
variant={payloadKind === kind ? "default" : "outline"}
size="sm"
onClick={() => setValue("payloadKind", kind, { shouldValidate: true })}
>
{kind === "command" ? t("payload.command") : t("payload.agent")}
</Button>
))}
</div>
</div>
<div className="space-y-2">
<Label>{t("create.scheduleType")}</Label>
<div className="flex gap-2">
@@ -160,17 +187,33 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
</p>
)}
<div className="space-y-2">
<Label>{t("create.message")}</Label>
<Textarea
{...register("message")}
placeholder={t("create.messagePlaceholder")}
rows={3}
/>
{errors.message && (
<p className="text-xs text-destructive">{errors.message.message}</p>
)}
</div>
{payloadKind === "agent_turn" ? (
<div className="space-y-2">
<Label>{t("create.message")}</Label>
<Textarea
{...register("message")}
placeholder={t("create.messagePlaceholder")}
rows={3}
/>
{errors.message && (
<p className="text-xs text-destructive">{errors.message.message}</p>
)}
</div>
) : (
<div className="space-y-2">
<Label>{t("detail.commandJson")}</Label>
<Textarea
{...register("commandJson")}
rows={8}
className="font-mono text-base md:text-sm"
/>
{errors.commandJson ? (
<p className="text-xs text-destructive">{errors.commandJson.message}</p>
) : (
<p className="text-xs text-muted-foreground">{t("detail.commandJsonHelp")}</p>
)}
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
@@ -178,7 +221,7 @@ export function CronFormDialog({ open, onOpenChange, onSubmit }: CronFormDialogP
</Button>
<Button
onClick={handleSubmit(onFormSubmit)}
disabled={isSubmitting || !!errors.name || !!errors.message}
disabled={isSubmitting || !!errors.name || (payloadKind === "agent_turn" ? !!errors.message : !!errors.commandJson)}
>
{isSubmitting ? t("create.creating") : t("create.create")}
</Button>
+2 -1
View File
@@ -106,7 +106,8 @@ export function useCron() {
async (params: {
name: string;
schedule: CronSchedule;
message: string;
message?: string;
command?: CronCommandSpec;
agentId?: string;
deliver?: boolean;
channel?: string;
+17 -1
View File
@@ -6,11 +6,27 @@ export const cronCreateSchema = z.object({
.string()
.min(1, "Required")
.refine(isValidSlug, "Only lowercase letters, numbers, and hyphens"),
message: z.string().min(1, "Required"),
payloadKind: z.enum(["agent_turn", "command"]),
message: z.string().optional(),
commandJson: z.string().optional(),
agentId: z.string().optional(),
scheduleKind: z.enum(["every", "cron", "at"]),
everyValue: z.string().min(1, "Required"),
cronExpr: z.string().min(1, "Required"),
}).superRefine((data, ctx) => {
if (data.payloadKind === "agent_turn" && !data.message?.trim()) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["message"], message: "Required" });
}
if (data.payloadKind === "command") {
try {
const parsed = JSON.parse(data.commandJson || "");
if (!Array.isArray(parsed?.argv) || parsed.argv.length === 0 || !parsed.argv[0]) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandJson"], message: "argv must be a non-empty array" });
}
} catch {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["commandJson"], message: "Invalid JSON" });
}
}
});
export type CronCreateFormData = z.infer<typeof cronCreateSchema>;