Skip to content

Commit

Permalink
feat: cron jobs activity
Browse files Browse the repository at this point in the history
  • Loading branch information
paulclindo committed Dec 20, 2024
1 parent 9626f7e commit f556068
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 49 deletions.
103 changes: 56 additions & 47 deletions apps/shinkai-desktop/src/pages/task-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
CheckCircle2,
Clock,
Edit,
RefreshCwIcon,
Sparkles,
TrashIcon,
XCircle,
Expand Down Expand Up @@ -59,7 +60,9 @@ export const TaskLogs = () => {
const {
data: logs,
isPending,
isRefetching,
isSuccess,
refetch,
} = useGetRecurringTaskLogs({
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
Expand All @@ -77,6 +80,7 @@ export const TaskLogs = () => {
<TaskCard
cronExpression={task.cron}
description={task.description}
isRunning={!task.paused}
key={task.task_id}
llmProvider={
'CreateJobWithConfigAndMessage' in task.action
Expand Down Expand Up @@ -114,19 +118,19 @@ export const TaskLogs = () => {
</div>
))}

<div className="flex items-center justify-between p-2">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">Logs</h2>
{/*<Button*/}
{/* size="sm"*/}
{/* variant="outline"*/}
{/*>*/}
{/* {isRunning ? (*/}
{/* <Pause className="mr-2 h-4 w-4" />*/}
{/* ) : (*/}
{/* <Play className="mr-2 h-4 w-4" />*/}
{/* )}*/}
{/* {isRunning ? 'Pause Task' : 'Resume Task'}*/}
{/*</Button>*/}
<Button
className="h-8 w-auto gap-2 rounded-lg p-1 px-2 text-xs"
disabled={isRefetching}
isLoading={isRefetching}
onClick={() => refetch()}
size="auto"
variant="outline"
>
<RefreshCwIcon className="h-4 w-4" />
Refresh logs
</Button>
</div>

<Card className="border-0 p-0">
Expand All @@ -139,15 +143,15 @@ export const TaskLogs = () => {
</div>
)}
{isSuccess && logs.length > 0 && (
<div className="divide-y divide-gray-300 p-2">
<div className="grid grid-cols-[360px_100px_1fr] items-center gap-6 py-1.5 text-xs text-gray-50">
<div className="divide-gray-375 divide-y py-2">
<div className="grid grid-cols-[360px_100px_1fr] items-center gap-6 py-1.5 font-mono text-xs text-gray-50">
<span>Execution Time</span>
<span>Status</span>
<span>Message</span>
</div>
{logs.map((log) => (
<div
className="grid grid-cols-[360px_100px_1fr] items-center gap-6 py-1.5 text-sm"
className="grid grid-cols-[360px_100px_1fr] items-center gap-6 py-3 text-xs"
key={log.execution_time}
>
<div className="text-muted-foreground shrink-0 font-mono">
Expand All @@ -164,6 +168,9 @@ export const TaskLogs = () => {
{log.success ? 'Success' : 'Failed'}
</div>
</div>
<div className="text-gray-80 font-mono">
{log.error_message || '-'}
</div>
</div>
))}
</div>
Expand All @@ -181,15 +188,15 @@ const TaskCard = ({
cronExpression,
prompt,
llmProvider,
isRunning = true,
isRunning,
}: {
taskId: number;
name: string;
description?: string;
cronExpression: string;
prompt: string;
llmProvider: string;
isRunning?: boolean;
isRunning: boolean;
}) => {
const navigate = useNavigate();
const { t } = useTranslation();
Expand All @@ -201,17 +208,21 @@ const TaskCard = ({
});

return (
<Card className="mb-8 border">
<CardHeader>
<Card className="mb-4 border-none p-0 py-2 shadow-none">
<CardHeader className="px-0 py-0">
<div className="flex items-start justify-between">
<div className="space-y-1">
<CardTitle className="flex items-center gap-2 capitalize">
{name}
<Badge
className="rounded-md border border-gray-300"
className={cn(
'rounded-md border border-gray-300',
isRunning &&
'border-cyan-600 bg-cyan-900/20 font-normal text-cyan-400',
)}
variant={isRunning ? 'default' : 'secondary'}
>
{isRunning ? 'Active' : 'Paused'}
{isRunning ? 'Active' : 'Inactive'}
</Badge>
</CardTitle>
<CardDescription>{description}</CardDescription>
Expand Down Expand Up @@ -282,34 +293,32 @@ const TaskCard = ({
/>
</div>
</CardHeader>
<CardContent>
<div className="grid gap-6 sm:grid-cols-2">
<div className="space-y-2">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Sparkles className="h-4 w-4" />
Prompt
</div>
<div className="rounded-md text-sm">{prompt}</div>
<CardContent className="flex flex-col gap-4 px-0 py-6">
<div className="flex items-center gap-4">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Sparkles className="h-4 w-4" />
Prompt:
</div>
<div className="grid gap-4">
<div className="space-y-2">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Clock className="h-4 w-4" />
Schedule
</div>
<div className="text-sm">
{readableCron}
<span className="text-gray-80 ml-2">({cronExpression})</span>
</div>
</div>
<div className="space-y-2">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Bot className="h-4 w-4" />
Agent/AI Model
</div>
<div className="text-sm">{llmProvider}</div>
</div>
<div className="rounded-md text-sm">{prompt}</div>
</div>
<div className="flex items-center gap-4">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Clock className="h-4 w-4" />
Schedule:
</div>
<div className="text-sm">
{readableCron}
<span className="text-gray-80 ml-2 rounded-lg bg-gray-300 px-2 py-1 font-mono">
{cronExpression}
</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-muted-foreground flex items-center gap-2 text-sm font-medium">
<Bot className="h-4 w-4" />
Agent/AI Model:
</div>
<div className="text-sm">{llmProvider}</div>
</div>
</CardContent>
</Card>
Expand Down
89 changes: 87 additions & 2 deletions apps/shinkai-desktop/src/pages/tasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from '@shinkai_network/shinkai-i18n';
import { JobConfig } from '@shinkai_network/shinkai-message-ts/api/jobs/types';
import { useRemoveRecurringTask } from '@shinkai_network/shinkai-node-state/v2/mutations/removeRecurringTask/useRemoveRecurringTask';
import { useUpdateRecurringTask } from '@shinkai_network/shinkai-node-state/v2/mutations/updateRecurringTask/useUpdateRecurringTask';
import { useGetRecurringTaskNextExecutionTime } from '@shinkai_network/shinkai-node-state/v2/queries/getRecurringTaskNextExecutionTime/useGetRecurringTaskNextExecutionTime';
import { useGetRecurringTasks } from '@shinkai_network/shinkai-node-state/v2/queries/getRecurringTasks/useGetRecurringTasks';
import {
Button,
Expand All @@ -18,11 +19,19 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Popover,
PopoverContent,
PopoverTrigger,
Switch,
} from '@shinkai_network/shinkai-ui';
import {
ScheduledTasksComingSoonIcon,
ScheduledTasksIcon,
} from '@shinkai_network/shinkai-ui/assets';
import { cn } from '@shinkai_network/shinkai-ui/utils';
import cronstrue from 'cronstrue';
import { Edit, PlusIcon, TrashIcon } from 'lucide-react';
import { formatDistance } from 'date-fns';
import { Edit, PlusIcon, RefreshCwIcon, TrashIcon } from 'lucide-react';
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
Expand All @@ -42,6 +51,16 @@ export const Tasks = () => {
token: auth?.api_v2_key ?? '',
});

const {
data: cronTasksNextExecutionTime,
isSuccess: isCronTasksNextExecutionTimeSuccess,
refetch,
isRefetching,
} = useGetRecurringTaskNextExecutionTime({
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
});

const { mutateAsync: updateRecurringTask } = useUpdateRecurringTask({
onError: (error) => {
toast.error('Failed to updated task', {
Expand All @@ -53,7 +72,73 @@ export const Tasks = () => {
return (
<SimpleLayout
headerRightElement={
<div className="flex items-center gap-2">
<div className="flex items-center gap-3">
{isCronTasksNextExecutionTimeSuccess &&
cronTasksNextExecutionTime.length > 0 && (
<Popover>
<PopoverTrigger asChild>
<Button
className="h-[30px] gap-2 rounded-lg px-3 text-xs"
size="auto"
type="button"
variant="outline"
>
<ScheduledTasksComingSoonIcon className="size-3.5" />
<span className="text-xs">Activity</span>
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
alignOffset={-4}
className="flex w-[400px] flex-col gap-2 bg-gray-300 px-3.5 py-4 text-xs"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<div className="flex items-center justify-between gap-2">
<h1 className="text-sm">Scheduled Cron Tasks </h1>
<Button
className="h-8 w-auto gap-2 rounded-lg p-1 px-2 text-xs"
disabled={isRefetching}
isLoading={isRefetching}
onClick={() => refetch()}
size="auto"
variant="outline"
>
{!isRefetching && (
<RefreshCwIcon className="h-3.5 w-3.5" />
)}
</Button>
</div>
{cronTasksNextExecutionTime?.map(([task, date]) => (
<div
className="flex items-start gap-2 py-1"
key={task.task_id}
>
<ScheduledTasksIcon className="text-gray-80 mt-1 size-4" />
<div className="flex flex-col gap-1 text-left">
<span className="text-sm text-gray-50">
{task.name}
<span className="text-gray-80 mx-1 rounded-lg border border-gray-200 px-1.5 py-1 text-xs">
{cronstrue.toString(task.cron, {
throwExceptionOnParseError: false,
})}
</span>
</span>
<span className="text-gray-80">
{' '}
Next execution in{' '}
<span className="text-gray-80 font-semibold">
{formatDistance(new Date(date), new Date(), {
addSuffix: true,
})}
</span>
</span>
</div>
</div>
))}
</PopoverContent>
</Popover>
)}

<Link
className={cn(
buttonVariants({
Expand Down
15 changes: 15 additions & 0 deletions libs/shinkai-message-ts/src/api/recurring-tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GetRecurringTaskLogsResponse,
GetRecurringTaskRequest,
GetRecurringTaskResponse,
GetRecurringTasksNextExecutionTimeResponse,
GetRecurringTasksResponse,
RemoveRecurringTaskRequest,
RemoveRecurringTaskResponse,
Expand Down Expand Up @@ -60,6 +61,20 @@ export const getRecurringTask = async (
return response.data as GetRecurringTaskResponse;
};

export const getRecurringTasksExecutionTime = async (
nodeAddress: string,
bearerToken: string,
) => {
const response = await httpClient.get(
urlJoin(nodeAddress, '/v2/get_cron_schedule'),
{
headers: { Authorization: `Bearer ${bearerToken}` },
responseType: 'json',
},
);
return response.data as GetRecurringTasksNextExecutionTimeResponse;
};

export const setRecurringTask = async (
nodeAddress: string,
bearerToken: string,
Expand Down
4 changes: 4 additions & 0 deletions libs/shinkai-message-ts/src/api/recurring-tasks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export type GetRecurringTaskRequest = {
cron_task_id: string;
};
export type GetRecurringTaskResponse = RecurringTask;
export type GetRecurringTasksNextExecutionTimeResponse = [
RecurringTask,
string,
][];
export type SetRecurringTaskRequest = {
cron_task_id: string;
} & Omit<RecurringTask, 'task_id' | 'created_at' | 'last_modified'>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { getRecurringTasksExecutionTime as getRecurringTaskExecutionTimeApi } from '@shinkai_network/shinkai-message-ts/api/recurring-tasks/index';

import type { GetRecurringTasksNextExecutionTimeInput } from './types';

export const getRecurringTasksExecutionTime = async ({
nodeAddress,
token,
}: GetRecurringTasksNextExecutionTimeInput) => {
const result = await getRecurringTaskExecutionTimeApi(nodeAddress, token);
return result;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Token } from '@shinkai_network/shinkai-message-ts/api/general/types';
import { GetRecurringTaskResponse } from '@shinkai_network/shinkai-message-ts/api/recurring-tasks/types';

export type GetRecurringTasksNextExecutionTimeInput = Token & {
nodeAddress: string;
};

export type GetRecurringTasksNextExecutionTimeOutput = GetRecurringTaskResponse;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query';

import { FunctionKeyV2 } from '../../constants';
import { getRecurringTasksExecutionTime } from './index';
import { GetRecurringTasksNextExecutionTimeInput } from './types';

export const useGetRecurringTaskNextExecutionTime = (
input: GetRecurringTasksNextExecutionTimeInput,
) => {
const response = useQuery({
queryKey: [FunctionKeyV2.GET_RECURRING_TASK, input],
queryFn: () => getRecurringTasksExecutionTime(input),
});
return response;
};
Loading

0 comments on commit f556068

Please sign in to comment.