import { useEffect, useMemo, useRef, useState } from "react" import { Link, useNavigate } from "@tanstack/react-router" import { CaretDownIcon, CheckIcon, ClockIcon, TrashIcon, } from "@phosphor-icons/react" import type { ModelOption } from "@/lib/api" import type { AgentSchedule } from "@/lib/agents/types" import type { ModelSelection } from "@/lib/agents/provider/useModelOptions" import { RepoSelector } from "@/components/agents/RepoSelector" import { ScheduleTriggerPicker } from "@/components/agents/ScheduleTriggerPicker" import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { describeCron, presetForCron } from "@/lib/agents/cron" import { useCreateAgentSchedule, useDeleteAgentSchedule, useUpdateAgentSchedule, } from "@/lib/agents/queries" import { formatModelSelection, useModelOptions, } from "@/lib/agents/provider/useModelOptions" import { useRepos } from "@/lib/profile" import { cn } from "@/lib/utils" interface AutomationEditorProps { mode: "create" | "edit" schedule?: AgentSchedule } function scheduleToSelection( models: Array, schedule?: AgentSchedule ): ModelSelection | null { if (!schedule?.model || !schedule.effort) return null const supported = models.some( (model) => model.id === schedule.model && model.efforts.includes(schedule.effort!) ) return supported ? { modelId: schedule.model, effort: schedule.effort } : null } export function AutomationEditor({ mode, schedule }: AutomationEditorProps) { const navigate = useNavigate() const reposQuery = useRepos() const { models, defaultSelection } = useModelOptions() const createSchedule = useCreateAgentSchedule() const updateSchedule = useUpdateAgentSchedule() const deleteSchedule = useDeleteAgentSchedule() const [name, setName] = useState(schedule?.name ?? "") const [prompt, setPrompt] = useState(schedule?.prompt ?? "") const [cron, setCron] = useState(schedule?.schedule ?? null) const [customMode, setCustomMode] = useState( schedule ? presetForCron(schedule.schedule) === "custom" : false ) const [repo, setRepo] = useState(schedule?.repo ?? null) const [enabled, setEnabled] = useState(schedule?.enabled ?? true) // undefined = untouched (derive from the schedule / default as models load). const [selectionOverride, setSelectionOverride] = useState< ModelSelection | null | undefined >(undefined) const activeSelection = selectionOverride !== undefined ? selectionOverride : (scheduleToSelection(models, schedule) ?? defaultSelection) const error = createSchedule.error || updateSchedule.error || deleteSchedule.error const errorMessage = error instanceof Error ? error.message : null const isSaving = createSchedule.isPending || updateSchedule.isPending const canSave = name.trim().length > 0 && prompt.trim().length > 0 && !!cron const onPickTrigger = (value: string | null) => { if (value === null) { setCustomMode(true) setCron((current) => current ?? "0 9 * * *") } else { setCustomMode(false) setCron(value) } } const handleSave = () => { if (!canSave || !cron) return const modelIsReal = models.some((m) => m.id === activeSelection?.modelId) const modelId = modelIsReal ? (activeSelection?.modelId ?? null) : null const effort = modelIsReal ? (activeSelection?.effort ?? null) : null if (mode === "create") { createSchedule.mutate( { name: name.trim(), prompt: prompt.trim(), schedule: cron.trim(), repo, model_id: modelId, effort, }, { onSuccess: () => navigate({ to: "/agents/automations" }) } ) return } if (!schedule) return updateSchedule.mutate( { scheduleId: schedule.id, body: { name: name.trim(), prompt: prompt.trim(), schedule: cron.trim(), repo: repo ?? "", model_id: modelId, effort, enabled, }, }, { onSuccess: () => navigate({ to: "/agents/automations" }) } ) } const handleDelete = () => { if (!schedule) return if (!window.confirm(`Delete "${schedule.name}"?`)) return deleteSchedule.mutate(schedule.id, { onSuccess: () => navigate({ to: "/agents/automations" }), }) } return (
Automations / {name.trim() || "New automation"}
{mode === "edit" && ( )}
setName(e.target.value)} placeholder="Untitled automation" className="w-full bg-transparent text-2xl font-semibold text-[var(--ui-text)] outline-none placeholder:text-[var(--ui-text-dim)]" />
{enabled ? "Active" : "Paused"}
|
Triggers
{cron && (
{customMode ? ( setCron(e.target.value)} placeholder="0 9 * * 1-5" className="flex-1 bg-transparent font-mono text-sm text-[var(--ui-text)] outline-none placeholder:text-[var(--ui-text-dim)]" /> ) : ( {describeCron(cron)} )}
)} {cron &&
}
Agent Instructions