From ae464480aa890ccd7e1cb93cf5e419eeaa440d42 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 4 Mar 2026 22:57:41 +0100 Subject: [PATCH] latest changes --- .../Advisors/Agents/AgentPresets.tsx | 41 +- .../Advisors/Agents/AgentTasksList.tsx | 464 ++++++++++++++++++ .../interfaces/Advisors/Agents/AgentsList.tsx | 169 ++++--- .../Advisors/Agents/task-presets.ts | 73 +++ .../Advisors/Issues/IssueActions.tsx | 86 +++- .../Advisors/Issues/IssueDetail.tsx | 174 +++++-- .../Advisors/Issues/IssueDetail.utils.ts | 68 +++ .../Advisors/Overview/AdvisorsOverview.tsx | 37 +- .../Advisors/Rules/RuleTemplates.tsx | 59 ++- .../interfaces/Advisors/Rules/RulesList.tsx | 4 +- .../AdvisorsLayout/AdvisorsSidebarMenu.tsx | 4 +- apps/studio/data/advisors/agents-query.ts | 58 ++- apps/studio/data/advisors/keys.ts | 2 + .../20260304120004_enhanced_notifications.sql | 215 ++++++++ 14 files changed, 1273 insertions(+), 181 deletions(-) create mode 100644 apps/studio/components/interfaces/Advisors/Agents/AgentTasksList.tsx create mode 100644 apps/studio/components/interfaces/Advisors/Agents/task-presets.ts create mode 100644 apps/studio/components/interfaces/Advisors/Issues/IssueDetail.utils.ts create mode 100644 apps/studio/data/advisors/supabase/migrations/20260304120004_enhanced_notifications.sql diff --git a/apps/studio/components/interfaces/Advisors/Agents/AgentPresets.tsx b/apps/studio/components/interfaces/Advisors/Agents/AgentPresets.tsx index 2df5f612945..3e39eac9763 100644 --- a/apps/studio/components/interfaces/Advisors/Agents/AgentPresets.tsx +++ b/apps/studio/components/interfaces/Advisors/Agents/AgentPresets.tsx @@ -1,9 +1,10 @@ import { useParams } from 'common' -import { useCreateAgentMutation } from 'data/advisors/agents-query' +import { useCreateAgentMutation, useCreateAgentTaskMutation } from 'data/advisors/agents-query' import { Bot, ShieldCheck, Zap, AlertTriangle } from 'lucide-react' import { useState } from 'react' import { toast } from 'sonner' import { Badge, Button, Card, CardContent } from 'ui' +import { getTaskPresetsForAgent } from './task-presets' export interface AgentPreset { id: string @@ -69,6 +70,7 @@ Prioritize speed and clarity. Use bullet points and action items.`, export function AgentPresetsCards() { const { ref: projectRef } = useParams() const createMutation = useCreateAgentMutation(projectRef) + const createTaskMutation = useCreateAgentTaskMutation(projectRef) return (
@@ -77,7 +79,12 @@ export function AgentPresetsCards() {

{AGENT_PRESETS.map((preset) => ( - + ))}
@@ -87,9 +94,11 @@ export function AgentPresetsCards() { function PresetCard({ preset, createMutation, + createTaskMutation, }: { preset: AgentPreset createMutation: ReturnType + createTaskMutation: ReturnType }) { const [creating, setCreating] = useState(false) const Icon = preset.icon @@ -97,14 +106,33 @@ function PresetCard({ const handleCreate = async () => { setCreating(true) try { - await createMutation.mutateAsync({ + const agent = await createMutation.mutateAsync({ name: preset.name, summary: preset.summary, system_prompt: preset.system_prompt, tools: preset.tools, }) - toast.success(`"${preset.name}" agent created`) - } catch (err) { + + if (agent?.id) { + const taskPresets = getTaskPresetsForAgent(preset.id) + await Promise.all( + taskPresets.map((tp) => + createTaskMutation.mutateAsync({ + agent_id: agent.id, + name: tp.name, + description: tp.description, + schedule: tp.schedule, + is_unique: tp.is_unique, + enabled: tp.enabled, + }) + ) + ) + } + + const taskCount = getTaskPresetsForAgent(preset.id).length + const taskMsg = taskCount > 0 ? ` with ${taskCount} scheduled task${taskCount !== 1 ? 's' : ''}` : '' + toast.success(`"${preset.name}" agent created${taskMsg}`) + } catch { toast.error('Failed to create agent') } finally { setCreating(false) @@ -125,6 +153,9 @@ function PresetCard({
{preset.tools.length} tools + + {getTaskPresetsForAgent(preset.id).length} tasks + diff --git a/apps/studio/components/interfaces/Advisors/Agents/AgentTasksList.tsx b/apps/studio/components/interfaces/Advisors/Agents/AgentTasksList.tsx new file mode 100644 index 00000000000..0688ba12d7e --- /dev/null +++ b/apps/studio/components/interfaces/Advisors/Agents/AgentTasksList.tsx @@ -0,0 +1,464 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { + useAdvisorAgentTasksQuery, + useCreateAgentTaskMutation, + useDeleteAgentTaskMutation, + useExecuteAgentTaskMutation, + useTaskConversationsQuery, + useUpdateAgentTaskMutation, +} from 'data/advisors/agents-query' +import type { AdvisorAgentTask } from 'data/advisors/types' +import { + Calendar, + ChevronDown, + ChevronRight, + Clock, + MoreVertical, + Play, + Plus, + Trash, + Edit, +} from 'lucide-react' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { toast } from 'sonner' +import { + Badge, + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, + Form_Shadcn_, + FormControl_Shadcn_, + FormField_Shadcn_, + Input_Shadcn_, + Separator, + Sheet, + SheetContent, + SheetFooter, + SheetHeader, + SheetSection, + SheetTitle, + Switch, + Textarea, +} from 'ui' +import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import * as z from 'zod' + +const TaskFormSchema = z.object({ + name: z.string().min(1, 'Name is required'), + description: z.string().optional(), + schedule: z.string().min(1, 'Schedule (cron) is required'), + is_unique: z.boolean(), + enabled: z.boolean(), +}) + +interface AgentTasksListProps { + agentId: string + projectRef: string +} + +export function AgentTasksList({ agentId, projectRef }: AgentTasksListProps) { + const { data: allTasks } = useAdvisorAgentTasksQuery(projectRef) + const tasks = (allTasks ?? []).filter((t) => t.agent_id === agentId) + + const [showSheet, setShowSheet] = useState(false) + const [editingTask, setEditingTask] = useState(null) + const [deletingTask, setDeletingTask] = useState(null) + const [expandedTaskId, setExpandedTaskId] = useState(null) + + const updateMutation = useUpdateAgentTaskMutation(projectRef) + const deleteMutation = useDeleteAgentTaskMutation(projectRef) + const executeMutation = useExecuteAgentTaskMutation(projectRef) + + return ( +
+
+

+ Scheduled Tasks +

+ +
+ + {tasks.length === 0 ? ( +

+ No scheduled tasks yet. Add a task to automate this agent on a cron schedule. +

+ ) : ( +
+ {tasks.map((task) => ( +
+
+ +
+ {task.name} + {task.description && ( +

+ {task.description} +

+ )} +
+ + + {task.schedule} + + + updateMutation.mutate( + { taskId: task.id, enabled: checked }, + { + onSuccess: () => + toast.success( + `Task ${checked ? 'enabled' : 'disabled'}` + ), + } + ) + } + /> + + +
+ + {expandedTaskId === task.id && ( + + )} +
+ ))} +
+ )} + + { + setShowSheet(false) + setEditingTask(null) + }} + /> + + setDeletingTask(null)} + onConfirm={() => { + if (!deletingTask) return + deleteMutation.mutate(deletingTask.id, { + onSuccess: () => { + toast.success('Task deleted') + setDeletingTask(null) + }, + }) + }} + > +

+ Are you sure you want to delete this task? Any scheduled cron job will also be removed. +

+
+
+ ) +} + +function TaskRunHistory({ projectRef, taskId }: { projectRef: string; taskId: string }) { + const { data: conversations, isLoading } = useTaskConversationsQuery(projectRef, taskId) + + if (isLoading) { + return ( +
+

Loading run history…

+
+ ) + } + + if (!conversations || conversations.length === 0) { + return ( +
+

+ No runs yet. This task will produce results on its next scheduled run or when triggered + manually. +

+
+ ) + } + + return ( +
+
+

+ Run History +

+
+ {conversations.map((conv) => { + const messageCount = (conv as unknown as { message_count?: number }).message_count ?? 0 + return ( +
+ + + {new Date(conv.updated_at).toLocaleString()} + + + {messageCount} message{messageCount !== 1 ? 's' : ''} + + {conv.title && ( + {conv.title} + )} +
+ ) + })} +
+
+
+ ) +} + +function TaskEditorSheet({ + visible, + task, + agentId, + projectRef, + onClose, +}: { + visible: boolean + task: AdvisorAgentTask | null + agentId: string + projectRef: string + onClose: () => void +}) { + const createMutation = useCreateAgentTaskMutation(projectRef) + const updateMutation = useUpdateAgentTaskMutation(projectRef) + const isEditing = !!task + const formId = 'task-editor-form' + + const form = useForm>({ + resolver: zodResolver(TaskFormSchema), + defaultValues: { + name: '', + description: '', + schedule: '0 8 * * *', + is_unique: false, + enabled: true, + }, + }) + + const handleOpen = () => { + if (task) { + form.reset({ + name: task.name, + description: task.description ?? '', + schedule: task.schedule, + is_unique: task.is_unique, + enabled: task.enabled, + }) + } else { + form.reset() + } + } + + const onSubmit = async (values: z.infer) => { + const payload = { + agent_id: agentId, + name: values.name, + description: values.description, + schedule: values.schedule, + is_unique: values.is_unique, + enabled: values.enabled, + } + + if (isEditing && task) { + await updateMutation.mutateAsync({ taskId: task.id, ...payload }) + toast.success('Task updated') + } else { + await createMutation.mutateAsync(payload) + toast.success('Task created') + } + onClose() + } + + return ( + !open && onClose()}> + + + {isEditing ? 'Edit Task' : 'Add Task'} + + +
+ + ( + + + + + + )} + /> + + + + ( + + +