mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-14 08:03:58 +00:00
feat: scheduled tasks ux improvement (#300)
* feat: show scheduled tasks tab if job runs are empty * chore: switch tabs after creating new tasks * feat: provide option to cancel and retry scheduled tasks * feat: provide option to retry and cancel jobs on the popups * chore: fix minor race condition between window cleanup and job status update
This commit is contained in:
@@ -6,6 +6,8 @@ import {
|
||||
CheckCircle2,
|
||||
Copy,
|
||||
Loader2,
|
||||
RotateCcw,
|
||||
Square,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import { type FC, useState } from 'react'
|
||||
@@ -27,6 +29,8 @@ interface RunResultDialogProps {
|
||||
run: ScheduledJobRun | null
|
||||
jobName?: string
|
||||
onOpenChange: (open: boolean) => void
|
||||
onCancelRun?: (runId: string) => void
|
||||
onRetryRun?: (jobId: string) => void
|
||||
}
|
||||
|
||||
const formatDateTime = (dateStr: string) =>
|
||||
@@ -46,6 +50,8 @@ export const RunResultDialog: FC<RunResultDialogProps> = ({
|
||||
run,
|
||||
jobName,
|
||||
onOpenChange,
|
||||
onCancelRun,
|
||||
onRetryRun,
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
@@ -99,6 +105,24 @@ export const RunResultDialog: FC<RunResultDialogProps> = ({
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter>
|
||||
{run.status === 'running' && onCancelRun && (
|
||||
<Button variant="destructive" onClick={() => onCancelRun(run.id)}>
|
||||
<Square className="h-4 w-4" />
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{run.status === 'failed' && onRetryRun && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onRetryRun(run.jobId)
|
||||
onOpenChange(false)
|
||||
}}
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
{run.result && (
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
Loader2,
|
||||
Pencil,
|
||||
Play,
|
||||
RotateCcw,
|
||||
Square,
|
||||
Trash2,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
@@ -31,6 +33,8 @@ interface ScheduledTaskCardProps {
|
||||
onToggle: (enabled: boolean) => void
|
||||
onRun: () => void
|
||||
onViewRun: (run: ScheduledJobRun) => void
|
||||
onCancelRun: (runId: string) => void
|
||||
onRetryRun: (jobId: string) => void
|
||||
}
|
||||
|
||||
function formatSchedule(job: ScheduledJob): string {
|
||||
@@ -72,6 +76,8 @@ export const ScheduledTaskCard: FC<ScheduledTaskCardProps> = ({
|
||||
onToggle,
|
||||
onRun,
|
||||
onViewRun,
|
||||
onCancelRun,
|
||||
onRetryRun,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
@@ -180,14 +186,44 @@ export const ScheduledTaskCard: FC<ScheduledTaskCardProps> = ({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onViewRun(run)}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{run.status === 'running' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onCancelRun(run.id)
|
||||
}}
|
||||
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Cancel run"
|
||||
>
|
||||
<Square className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
{run.status === 'failed' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRetryRun(run.jobId)
|
||||
}}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
aria-label="Retry run"
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onViewRun(run)}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { Calendar, CheckCircle2, Clock, Loader2, XCircle } from 'lucide-react'
|
||||
import {
|
||||
Calendar,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Loader2,
|
||||
RotateCcw,
|
||||
Square,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -21,6 +29,8 @@ interface JobRunWithDetails extends ScheduledJobRun {
|
||||
|
||||
interface ScheduledTaskResultsProps {
|
||||
onViewRun: (run: ScheduledJobRun) => void
|
||||
onCancelRun: (runId: string) => void
|
||||
onRetryRun: (jobId: string) => void
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: JobRunWithDetails['status']) => {
|
||||
@@ -38,6 +48,8 @@ const formatTimestamp = (dateString: string) => dayjs(dateString).fromNow()
|
||||
|
||||
export const ScheduledTaskResults: FC<ScheduledTaskResultsProps> = ({
|
||||
onViewRun,
|
||||
onCancelRun,
|
||||
onRetryRun,
|
||||
}) => {
|
||||
const { jobRuns } = useScheduledJobRuns()
|
||||
const { jobs } = useScheduledJobs()
|
||||
@@ -99,6 +111,34 @@ export const ScheduledTaskResults: FC<ScheduledTaskResultsProps> = ({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{run.status === 'running' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onCancelRun(run.id)
|
||||
}}
|
||||
className="shrink-0 text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Cancel run"
|
||||
>
|
||||
<Square className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
{run.status === 'failed' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRetryRun(run.jobId)
|
||||
}}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
aria-label="Retry run"
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -9,6 +9,8 @@ interface ScheduledTasksListProps {
|
||||
onToggle: (jobId: string, enabled: boolean) => void
|
||||
onRun: (jobId: string) => void
|
||||
onViewRun: (run: ScheduledJobRun) => void
|
||||
onCancelRun: (runId: string) => void
|
||||
onRetryRun: (jobId: string) => void
|
||||
}
|
||||
|
||||
export const ScheduledTasksList: FC<ScheduledTasksListProps> = ({
|
||||
@@ -18,6 +20,8 @@ export const ScheduledTasksList: FC<ScheduledTasksListProps> = ({
|
||||
onToggle,
|
||||
onRun,
|
||||
onViewRun,
|
||||
onCancelRun,
|
||||
onRetryRun,
|
||||
}) => {
|
||||
if (jobs.length === 0) {
|
||||
return (
|
||||
@@ -42,6 +46,8 @@ export const ScheduledTasksList: FC<ScheduledTasksListProps> = ({
|
||||
onToggle={(enabled) => onToggle(job.id, enabled)}
|
||||
onRun={() => onRun(job.id)}
|
||||
onViewRun={onViewRun}
|
||||
onCancelRun={onCancelRun}
|
||||
onRetryRun={onRetryRun}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import { RunResultDialog } from '@/components/ai-elements/run-result-dialog'
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -13,8 +13,10 @@ import {
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import {
|
||||
NEW_SCHEDULED_TASK_CREATED_EVENT,
|
||||
SCHEDULED_TASK_CANCELLED_EVENT,
|
||||
SCHEDULED_TASK_DELETED_EVENT,
|
||||
SCHEDULED_TASK_EDITED_EVENT,
|
||||
SCHEDULED_TASK_RETRIED_EVENT,
|
||||
SCHEDULED_TASK_TESTED_EVENT,
|
||||
SCHEDULED_TASK_TOGGLED_EVENT,
|
||||
SCHEDULED_TASK_VIEW_RESULTS_EVENT,
|
||||
@@ -22,7 +24,11 @@ import {
|
||||
import { useGraphqlMutation } from '@/lib/graphql/useGraphqlMutation'
|
||||
import { track } from '@/lib/metrics/track'
|
||||
import { DeleteScheduledJobDocument } from '@/lib/schedules/graphql/syncSchedulesDocument'
|
||||
import { useScheduledJobs } from '@/lib/schedules/scheduleStorage'
|
||||
import {
|
||||
scheduledJobRunStorage,
|
||||
useScheduledJobRuns,
|
||||
useScheduledJobs,
|
||||
} from '@/lib/schedules/scheduleStorage'
|
||||
import type { ScheduledJobRun } from '@/lib/schedules/scheduleTypes'
|
||||
import { NewScheduledTaskDialog } from './NewScheduledTaskDialog'
|
||||
import { ScheduledTaskResults } from './ScheduledTaskResults'
|
||||
@@ -37,9 +43,11 @@ import type { ScheduledJob } from './types'
|
||||
export const ScheduledTasksPage: FC = () => {
|
||||
const { jobs, addJob, editJob, toggleJob, removeJob, runJob } =
|
||||
useScheduledJobs()
|
||||
const { cancelJobRun } = useScheduledJobRuns()
|
||||
|
||||
const deleteRemoteJobMutation = useGraphqlMutation(DeleteScheduledJobDocument)
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string | null>(null)
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [editingJob, setEditingJob] = useState<ScheduledJob | null>(null)
|
||||
const [deleteJobId, setDeleteJobId] = useState<string | null>(null)
|
||||
@@ -80,6 +88,7 @@ export const ScheduledTasksPage: FC = () => {
|
||||
})
|
||||
} else {
|
||||
await addJob(data)
|
||||
setActiveTab('tasks')
|
||||
track(NEW_SCHEDULED_TASK_CREATED_EVENT, {
|
||||
scheduleType: data.scheduleType,
|
||||
interval: data.scheduleInterval,
|
||||
@@ -98,11 +107,27 @@ export const ScheduledTasksPage: FC = () => {
|
||||
track(SCHEDULED_TASK_TESTED_EVENT)
|
||||
}
|
||||
|
||||
const handleCancelRun = async (runId: string) => {
|
||||
await cancelJobRun(runId)
|
||||
track(SCHEDULED_TASK_CANCELLED_EVENT)
|
||||
}
|
||||
|
||||
const handleRetryRun = async (jobId: string) => {
|
||||
await runJob(jobId)
|
||||
track(SCHEDULED_TASK_RETRIED_EVENT)
|
||||
}
|
||||
|
||||
const handleViewRun = (run: ScheduledJobRun) => {
|
||||
setViewingRun(run)
|
||||
track(SCHEDULED_TASK_VIEW_RESULTS_EVENT)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
scheduledJobRunStorage.getValue().then((runs) => {
|
||||
setActiveTab(runs && runs.length > 0 ? 'results' : 'tasks')
|
||||
})
|
||||
}, [])
|
||||
|
||||
const jobToDelete = deleteJobId
|
||||
? jobs.find((j) => j.id === deleteJobId)
|
||||
: null
|
||||
@@ -111,27 +136,35 @@ export const ScheduledTasksPage: FC = () => {
|
||||
<div className="fade-in slide-in-from-bottom-5 animate-in space-y-6 duration-500">
|
||||
<ScheduledTasksHeader onAddClick={handleAdd} />
|
||||
|
||||
<Tabs defaultValue="results">
|
||||
<TabsList>
|
||||
<TabsTrigger value="results">Results</TabsTrigger>
|
||||
<TabsTrigger value="tasks">Scheduled Tasks</TabsTrigger>
|
||||
</TabsList>
|
||||
{activeTab && (
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="results">Results</TabsTrigger>
|
||||
<TabsTrigger value="tasks">Scheduled Tasks</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="results">
|
||||
<ScheduledTaskResults onViewRun={handleViewRun} />
|
||||
</TabsContent>
|
||||
<TabsContent value="results">
|
||||
<ScheduledTaskResults
|
||||
onViewRun={handleViewRun}
|
||||
onCancelRun={handleCancelRun}
|
||||
onRetryRun={handleRetryRun}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tasks">
|
||||
<ScheduledTasksList
|
||||
jobs={jobs}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onToggle={handleToggle}
|
||||
onRun={handleRun}
|
||||
onViewRun={handleViewRun}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<TabsContent value="tasks">
|
||||
<ScheduledTasksList
|
||||
jobs={jobs}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onToggle={handleToggle}
|
||||
onRun={handleRun}
|
||||
onViewRun={handleViewRun}
|
||||
onCancelRun={handleCancelRun}
|
||||
onRetryRun={handleRetryRun}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<NewScheduledTaskDialog
|
||||
open={isDialogOpen}
|
||||
@@ -148,6 +181,11 @@ export const ScheduledTasksPage: FC = () => {
|
||||
: undefined
|
||||
}
|
||||
onOpenChange={(open) => !open && setViewingRun(null)}
|
||||
onCancelRun={handleCancelRun}
|
||||
onRetryRun={(jobId) => {
|
||||
handleRetryRun(jobId)
|
||||
setViewingRun(null)
|
||||
}}
|
||||
/>
|
||||
|
||||
<AlertDialog
|
||||
|
||||
@@ -11,6 +11,8 @@ const MAX_RUNS_PER_JOB = 15
|
||||
const STALE_TIMEOUT_MS = 10 * 60 * 1000 // 10 minutes
|
||||
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
const runAbortControllers = new Map<string, AbortController>()
|
||||
|
||||
export const scheduledJobRuns = async () => {
|
||||
const cleanupStaleJobRuns = async () => {
|
||||
const current = (await scheduledJobRunStorage.getValue()) ?? []
|
||||
@@ -127,12 +129,15 @@ export const scheduledJobRuns = async () => {
|
||||
}
|
||||
|
||||
const jobRun = await createJobRun(jobId, 'running')
|
||||
const abortController = new AbortController()
|
||||
runAbortControllers.set(jobRun.id, abortController)
|
||||
|
||||
try {
|
||||
const response = await getChatServerResponse({
|
||||
message: job.query,
|
||||
activeTab: backgroundTab,
|
||||
windowId: backgroundWindow.id,
|
||||
signal: abortController.signal,
|
||||
})
|
||||
|
||||
await updateJobRun(jobRun.id, {
|
||||
@@ -144,7 +149,12 @@ export const scheduledJobRuns = async () => {
|
||||
toolCalls: response.toolCalls,
|
||||
})
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e)
|
||||
const isCancelled = abortController.signal.aborted
|
||||
const errorMessage = isCancelled
|
||||
? 'Cancelled by user'
|
||||
: e instanceof Error
|
||||
? e.message
|
||||
: String(e)
|
||||
await updateJobRun(jobRun.id, {
|
||||
status: 'failed',
|
||||
completedAt: new Date().toISOString(),
|
||||
@@ -152,10 +162,15 @@ export const scheduledJobRuns = async () => {
|
||||
error: errorMessage,
|
||||
})
|
||||
} finally {
|
||||
await updateJobLastRunAt(jobId)
|
||||
runAbortControllers.delete(jobRun.id)
|
||||
if (backgroundWindow.id) {
|
||||
await chrome.windows.remove(backgroundWindow.id)
|
||||
try {
|
||||
await chrome.windows.remove(backgroundWindow.id)
|
||||
} catch {
|
||||
// Window may already be closed
|
||||
}
|
||||
}
|
||||
await updateJobLastRunAt(jobId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +243,15 @@ export const scheduledJobRuns = async () => {
|
||||
}
|
||||
})
|
||||
|
||||
onScheduleMessage('cancelScheduledJobRun', async ({ data }) => {
|
||||
const controller = runAbortControllers.get(data.runId)
|
||||
if (!controller) {
|
||||
return { success: false, error: 'Run not found or already completed' }
|
||||
}
|
||||
controller.abort()
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
chrome.runtime.onStartup.addListener(async () => {
|
||||
await cleanupStaleJobRuns()
|
||||
await syncAlarmState()
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
ChevronDown,
|
||||
Clock,
|
||||
Loader2,
|
||||
RotateCcw,
|
||||
Square,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
@@ -19,6 +21,8 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible'
|
||||
import {
|
||||
SCHEDULED_TASK_CANCELLED_EVENT,
|
||||
SCHEDULED_TASK_RETRIED_EVENT,
|
||||
SCHEDULED_TASK_VIEW_MORE_IN_NEWTAB_EVENT,
|
||||
SCHEDULED_TASK_VIEW_RESULTS_IN_NEWTAB_EVENT,
|
||||
} from '@/lib/constants/analyticsEvents'
|
||||
@@ -66,8 +70,8 @@ export const ScheduleResults: FC = () => {
|
||||
localStorage.setItem(SCHEDULE_RESULTS_COLLAPSED_KEY, (!open).toString())
|
||||
}
|
||||
|
||||
const { jobRuns } = useScheduledJobRuns()
|
||||
const { jobs } = useScheduledJobs()
|
||||
const { jobRuns, cancelJobRun } = useScheduledJobRuns()
|
||||
const { jobs, runJob } = useScheduledJobs()
|
||||
|
||||
const runningCount = jobRuns.filter((r) => r.status === 'running').length
|
||||
|
||||
@@ -102,6 +106,17 @@ export const ScheduleResults: FC = () => {
|
||||
setViewingRun(run)
|
||||
}
|
||||
|
||||
const handleCancelRun = async (runId: string) => {
|
||||
await cancelJobRun(runId)
|
||||
track(SCHEDULED_TASK_CANCELLED_EVENT)
|
||||
}
|
||||
|
||||
const handleRetryRun = async (jobId: string) => {
|
||||
await runJob(jobId)
|
||||
setViewingRun(null)
|
||||
track(SCHEDULED_TASK_RETRIED_EVENT)
|
||||
}
|
||||
|
||||
if (!displayedRuns.length) return null
|
||||
|
||||
return (
|
||||
@@ -158,6 +173,34 @@ export const ScheduleResults: FC = () => {
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{run.status === 'running' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleCancelRun(run.id)
|
||||
}}
|
||||
className="shrink-0 text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Cancel run"
|
||||
>
|
||||
<Square className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
{run.status === 'failed' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleRetryRun(run.jobId)
|
||||
}}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
aria-label="Retry run"
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
@@ -176,6 +219,8 @@ export const ScheduleResults: FC = () => {
|
||||
run={viewingRun}
|
||||
jobName={viewingRun?.job?.name}
|
||||
onOpenChange={(open) => !open && setViewingRun(null)}
|
||||
onCancelRun={handleCancelRun}
|
||||
onRetryRun={handleRetryRun}
|
||||
/>
|
||||
</Collapsible>
|
||||
)
|
||||
|
||||
@@ -145,5 +145,12 @@ export const JTBD_POPUP_SHOWN_EVENT = 'ui.jtbd_popup.shown'
|
||||
/** @public */
|
||||
export const JTBD_POPUP_CLICKED_EVENT = 'ui.jtbd_popup.clicked'
|
||||
|
||||
/** @public */
|
||||
export const SCHEDULED_TASK_CANCELLED_EVENT =
|
||||
'settings.scheduled_task.cancelled'
|
||||
|
||||
/** @public */
|
||||
export const SCHEDULED_TASK_RETRIED_EVENT = 'settings.scheduled_task.retried'
|
||||
|
||||
/** @public */
|
||||
export const JTBD_POPUP_DISMISSED_EVENT = 'ui.jtbd_popup.dismissed'
|
||||
|
||||
@@ -4,6 +4,10 @@ interface RunScheduledJobData {
|
||||
jobId: string
|
||||
}
|
||||
|
||||
interface CancelScheduledJobRunData {
|
||||
runId: string
|
||||
}
|
||||
|
||||
interface RunScheduledJobResponse {
|
||||
success: boolean
|
||||
error?: string
|
||||
@@ -11,6 +15,9 @@ interface RunScheduledJobResponse {
|
||||
|
||||
type ScheduleMessagesProtocol = {
|
||||
runScheduledJob(data: RunScheduledJobData): RunScheduledJobResponse
|
||||
cancelScheduledJobRun(
|
||||
data: CancelScheduledJobRunData,
|
||||
): RunScheduledJobResponse
|
||||
}
|
||||
|
||||
const { sendMessage, onMessage } =
|
||||
|
||||
@@ -23,6 +23,7 @@ interface ChatServerRequest {
|
||||
conversationId?: string
|
||||
windowId?: number
|
||||
activeTab?: ActiveTab
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
interface ChatServerResponse {
|
||||
@@ -93,6 +94,7 @@ export async function getChatServerResponse(
|
||||
|
||||
const response = await fetch(`${agentServerUrl}/chat`, {
|
||||
method: 'POST',
|
||||
signal: request.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
||||
@@ -142,7 +142,11 @@ export function useScheduledJobRuns() {
|
||||
)
|
||||
}
|
||||
|
||||
return { jobRuns, addJobRun, removeJobRun, editJobRun }
|
||||
const cancelJobRun = async (runId: string) => {
|
||||
return sendScheduleMessage('cancelScheduledJobRun', { runId })
|
||||
}
|
||||
|
||||
return { jobRuns, addJobRun, removeJobRun, editJobRun, cancelJobRun }
|
||||
}
|
||||
|
||||
export async function syncScheduledJobs(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user