Use actions api for new tasks (#1027)
This commit is contained in:
@@ -276,3 +276,17 @@ export type TaskGenerationApiResponse = {
|
|||||||
navigation_payload: Record<string, unknown> | null;
|
navigation_payload: Record<string, unknown> | null;
|
||||||
extracted_information_schema: Record<string, unknown> | null;
|
extracted_information_schema: Record<string, unknown> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ActionsApiResponse = {
|
||||||
|
action_type: ActionType;
|
||||||
|
status: Status;
|
||||||
|
task_id: string | null;
|
||||||
|
step_id: string | null;
|
||||||
|
step_order: number | null;
|
||||||
|
action_order: number | null;
|
||||||
|
confidence_float: number | null;
|
||||||
|
description: string | null;
|
||||||
|
reasoning: string | null;
|
||||||
|
intention: string | null;
|
||||||
|
response: string | null;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { ActionScreenshot } from "./ActionScreenshot";
|
|
||||||
import { ScrollableActionList } from "./ScrollableActionList";
|
|
||||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
||||||
import {
|
|
||||||
ActionApiResponse,
|
|
||||||
ActionTypes,
|
|
||||||
Status,
|
|
||||||
StepApiResponse,
|
|
||||||
TaskApiResponse,
|
|
||||||
} from "@/api/types";
|
|
||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { Status, StepApiResponse, TaskApiResponse } from "@/api/types";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { envCredential } from "@/util/env";
|
|
||||||
import { statusIsNotFinalized, statusIsRunningOrQueued } from "../types";
|
|
||||||
import { ZoomableImage } from "@/components/ZoomableImage";
|
import { ZoomableImage } from "@/components/ZoomableImage";
|
||||||
import { useCostCalculator } from "@/hooks/useCostCalculator";
|
import { useCostCalculator } from "@/hooks/useCostCalculator";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { envCredential } from "@/util/env";
|
||||||
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { statusIsNotFinalized, statusIsRunningOrQueued } from "../types";
|
||||||
|
import { ActionScreenshot } from "./ActionScreenshot";
|
||||||
|
import { useActions } from "./hooks/useActions";
|
||||||
|
import { ScrollableActionList } from "./ScrollableActionList";
|
||||||
|
|
||||||
const formatter = Intl.NumberFormat("en-US", {
|
const formatter = Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
@@ -34,18 +29,6 @@ let socket: WebSocket | null = null;
|
|||||||
|
|
||||||
const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL;
|
const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL;
|
||||||
|
|
||||||
function getActionInput(action: ActionApiResponse) {
|
|
||||||
let input = "";
|
|
||||||
if (action.action_type === ActionTypes.InputText && action.text) {
|
|
||||||
input = action.text;
|
|
||||||
} else if (action.action_type === ActionTypes.Click) {
|
|
||||||
input = "Click";
|
|
||||||
} else if (action.action_type === ActionTypes.SelectOption && action.option) {
|
|
||||||
input = action.option.label;
|
|
||||||
}
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TaskActions() {
|
function TaskActions() {
|
||||||
const { taskId } = useParams();
|
const { taskId } = useParams();
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
@@ -165,31 +148,11 @@ function TaskActions() {
|
|||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = steps
|
const { data: actions, isLoading: actionsIsLoading } = useActions({
|
||||||
?.map((step) => {
|
id: taskId,
|
||||||
const actionsAndResults = step.output?.actions_and_results ?? [];
|
});
|
||||||
|
|
||||||
const actions = actionsAndResults.map((actionAndResult, index) => {
|
if (taskIsLoading || actionsIsLoading || stepsIsLoading) {
|
||||||
const action = actionAndResult[0];
|
|
||||||
const actionResult = actionAndResult[1];
|
|
||||||
if (actionResult.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
reasoning: action.reasoning,
|
|
||||||
confidence: action.confidence_float,
|
|
||||||
input: getActionInput(action),
|
|
||||||
type: action.action_type,
|
|
||||||
success: actionResult?.[0]?.success ?? false,
|
|
||||||
stepId: step.step_id,
|
|
||||||
index,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return actions;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
|
|
||||||
if (taskIsLoading || stepsIsLoading) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="h-[40rem] w-3/4">
|
<div className="h-[40rem] w-3/4">
|
||||||
|
|||||||
133
skyvern-frontend/src/routes/tasks/detail/hooks/useActions.ts
Normal file
133
skyvern-frontend/src/routes/tasks/detail/hooks/useActions.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
ActionApiResponse,
|
||||||
|
ActionsApiResponse,
|
||||||
|
ActionTypes,
|
||||||
|
Status,
|
||||||
|
StepApiResponse,
|
||||||
|
TaskApiResponse,
|
||||||
|
} from "@/api/types";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
|
import { statusIsNotFinalized } from "../../types";
|
||||||
|
|
||||||
|
function getActionInput(action: ActionApiResponse) {
|
||||||
|
let input = "";
|
||||||
|
if (action.action_type === ActionTypes.InputText && action.text) {
|
||||||
|
input = action.text;
|
||||||
|
} else if (action.action_type === ActionTypes.Click) {
|
||||||
|
input = "Click";
|
||||||
|
} else if (action.action_type === ActionTypes.SelectOption && action.option) {
|
||||||
|
input = action.option.label;
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isOld(task: TaskApiResponse) {
|
||||||
|
return new Date(task.created_at) < new Date(2024, 9, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useActions({ id }: Props): {
|
||||||
|
data: Array<Action | null>;
|
||||||
|
isLoading: boolean;
|
||||||
|
} {
|
||||||
|
const credentialGetter = useCredentialGetter();
|
||||||
|
|
||||||
|
const { data: task, isLoading: taskIsLoading } = useQuery<TaskApiResponse>({
|
||||||
|
queryKey: ["task", id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
return client.get(`/tasks/${id}`).then((response) => response.data);
|
||||||
|
},
|
||||||
|
refetchInterval: (query) => {
|
||||||
|
if (!query.state.data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (statusIsNotFinalized(query.state.data)) {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const taskIsNotFinalized = task && statusIsNotFinalized(task);
|
||||||
|
|
||||||
|
const { data: taskActions, isLoading: taskActionsIsLoading } = useQuery<
|
||||||
|
Array<ActionsApiResponse>
|
||||||
|
>({
|
||||||
|
queryKey: ["tasks", id, "actions"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
return client
|
||||||
|
.get(`/tasks/${id}/actions`)
|
||||||
|
.then((response) => response.data);
|
||||||
|
},
|
||||||
|
refetchInterval: taskIsNotFinalized ? 5000 : false,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
enabled: Boolean(task && !isOld(task)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: steps, isLoading: stepsIsLoading } = useQuery<
|
||||||
|
Array<StepApiResponse>
|
||||||
|
>({
|
||||||
|
queryKey: ["task", id, "steps"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
return client.get(`/tasks/${id}/steps`).then((response) => response.data);
|
||||||
|
},
|
||||||
|
enabled: Boolean(task && isOld(task)),
|
||||||
|
refetchOnWindowFocus: taskIsNotFinalized,
|
||||||
|
refetchInterval: taskIsNotFinalized ? 5000 : false,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const actions =
|
||||||
|
task && isOld(task)
|
||||||
|
? steps
|
||||||
|
?.map((step) => {
|
||||||
|
const actionsAndResults = step.output?.actions_and_results ?? [];
|
||||||
|
|
||||||
|
const actions = actionsAndResults.map((actionAndResult, index) => {
|
||||||
|
const action = actionAndResult[0];
|
||||||
|
const actionResult = actionAndResult[1];
|
||||||
|
if (actionResult.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
reasoning: action.reasoning,
|
||||||
|
confidence: action.confidence_float,
|
||||||
|
input: getActionInput(action),
|
||||||
|
type: action.action_type,
|
||||||
|
success: actionResult?.[0]?.success ?? false,
|
||||||
|
stepId: step.step_id,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return actions;
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
: taskActions?.map((action) => {
|
||||||
|
return {
|
||||||
|
reasoning: action.reasoning ?? "",
|
||||||
|
confidence: action.confidence_float ?? undefined,
|
||||||
|
input: action.response ?? "",
|
||||||
|
type: action.action_type,
|
||||||
|
success: action.status === Status.Completed,
|
||||||
|
stepId: action.step_id ?? "",
|
||||||
|
index: action.action_order ?? 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: actions ?? [],
|
||||||
|
isLoading: taskIsLoading || taskActionsIsLoading || stepsIsLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useActions };
|
||||||
Reference in New Issue
Block a user