mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 06:14:22 +00:00
feat(cron): create command payloads in UI (#1288)
This commit is contained in:
1 parent
7a9210b765
commit
a90127c3d6
7 files changed
+85
-21
No files matched your search
@@ -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",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"messagePlaceholder": "에이전트가 무엇을 해야 하나요?",
|
||||
"cancel": "취소",
|
||||
"create": "만들기",
|
||||
"creating": "만드는 중..."
|
||||
"creating": "만드는 중...",
|
||||
"payloadType": "Payload 유형"
|
||||
},
|
||||
"enable": {
|
||||
"title": "Cron 작업 활성화",
|
||||
|
||||
@@ -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ỳ",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"messagePlaceholder": "Agent应该做什么?",
|
||||
"cancel": "取消",
|
||||
"create": "创建",
|
||||
"creating": "创建中..."
|
||||
"creating": "创建中...",
|
||||
"payloadType": "Payload 类型"
|
||||
},
|
||||
"enable": {
|
||||
"title": "启用定时任务",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
Reference in new issue
Block a user