Changes for signed URL artifacts (#197)

This commit is contained in:
Salih Altun 2024-04-16 20:40:59 +03:00 committed by GitHub
parent b6f3bc2c41
commit eb668baba8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 70 deletions

View file

@ -31,6 +31,7 @@ export type ArtifactApiResponse = {
step_id: string; step_id: string;
artifact_type: ArtifactType; artifact_type: ArtifactType;
uri: string; uri: string;
signed_url?: string | null;
organization_id: string; organization_id: string;
}; };

View file

@ -1,25 +1,32 @@
import { artifactApiClient } from "@/api/AxiosClient"; import { artifactApiClient } from "@/api/AxiosClient";
import { ArtifactApiResponse } from "@/api/types";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios";
type Props = { type Props = {
uri: string; artifact: ArtifactApiResponse;
}; };
function JSONArtifact({ uri }: Props) { function JSONArtifact({ artifact }: Props) {
const { data, isFetching, isError, error } = useQuery< const { data, isFetching, isError, error } = useQuery<
Record<string, unknown> Record<string, unknown>
>({ >({
queryKey: ["artifact", uri], queryKey: ["artifact", artifact.artifact_id],
queryFn: async () => { queryFn: async () => {
return artifactApiClient if (artifact.uri.startsWith("file://")) {
.get(`/artifact/json`, { return artifactApiClient
params: { .get(`/artifact/json`, {
path: uri.slice(7), params: {
}, path: artifact.uri.slice(7),
}) },
.then((response) => response.data); })
.then((response) => response.data);
}
if (artifact.uri.startsWith("s3://") && artifact.signed_url) {
return axios.get(artifact.signed_url).then((response) => response.data);
}
}, },
}); });

View file

@ -9,11 +9,11 @@ import { Label } from "@/components/ui/label";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { artifactApiBaseUrl } from "@/util/env";
import { ZoomableImage } from "@/components/ZoomableImage"; import { ZoomableImage } from "@/components/ZoomableImage";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { JSONArtifact } from "./JSONArtifact"; import { JSONArtifact } from "./JSONArtifact";
import { TextArtifact } from "./TextArtifact"; import { TextArtifact } from "./TextArtifact";
import { getImageURL } from "./artifactUtils";
type Props = { type Props = {
id: string; id: string;
@ -40,46 +40,42 @@ function StepArtifacts({ id, stepProps }: Props) {
return <div>Error: {error?.message}</div>; return <div>Error: {error?.message}</div>;
} }
const llmScreenshotUris = artifacts const llmScreenshots = artifacts?.filter(
?.filter( (artifact) => artifact.artifact_type === ArtifactType.LLMScreenshot,
(artifact) => artifact.artifact_type === ArtifactType.LLMScreenshot, );
)
.map((artifact) => artifact.uri);
const actionScreenshotUris = artifacts const actionScreenshots = artifacts?.filter(
?.filter( (artifact) => artifact.artifact_type === ArtifactType.ActionScreenshot,
(artifact) => artifact.artifact_type === ArtifactType.ActionScreenshot, );
)
.map((artifact) => artifact.uri);
const visibleElementsTreeUri = artifacts?.find( const visibleElementsTree = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.VisibleElementsTree, (artifact) => artifact.artifact_type === ArtifactType.VisibleElementsTree,
)?.uri; );
const visibleElementsTreeTrimmedUri = artifacts?.find( const visibleElementsTreeTrimmed = artifacts?.find(
(artifact) => (artifact) =>
artifact.artifact_type === ArtifactType.VisibleElementsTreeTrimmed, artifact.artifact_type === ArtifactType.VisibleElementsTreeTrimmed,
)?.uri; );
const llmPromptUri = artifacts?.find( const llmPrompt = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.LLMPrompt, (artifact) => artifact.artifact_type === ArtifactType.LLMPrompt,
)?.uri; );
const llmRequestUri = artifacts?.find( const llmRequest = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.LLMRequest, (artifact) => artifact.artifact_type === ArtifactType.LLMRequest,
)?.uri; );
const llmResponseRawUri = artifacts?.find( const llmResponseRaw = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.LLMResponseRaw, (artifact) => artifact.artifact_type === ArtifactType.LLMResponseRaw,
)?.uri; );
const llmResponseParsedUri = artifacts?.find( const llmResponseParsed = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.LLMResponseParsed, (artifact) => artifact.artifact_type === ArtifactType.LLMResponseParsed,
)?.uri; );
const htmlRawUri = artifacts?.find( const htmlRaw = artifacts?.find(
(artifact) => artifact.artifact_type === ArtifactType.HTMLScrape, (artifact) => artifact.artifact_type === ArtifactType.HTMLScrape,
)?.uri; );
return ( return (
<Tabs defaultValue="info" className="w-full"> <Tabs defaultValue="info" className="w-full">
@ -128,12 +124,12 @@ function StepArtifacts({ id, stepProps }: Props) {
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="screenshot_llm"> <TabsContent value="screenshot_llm">
{llmScreenshotUris && llmScreenshotUris.length > 0 ? ( {llmScreenshots && llmScreenshots.length > 0 ? (
<div className="grid grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-3 gap-4 p-4">
{llmScreenshotUris.map((uri, index) => ( {llmScreenshots.map((artifact, index) => (
<ZoomableImage <ZoomableImage
key={index} key={index}
src={`${artifactApiBaseUrl}/artifact/image?path=${uri.slice(7)}`} src={getImageURL(artifact)}
className="object-cover w-full h-full" className="object-cover w-full h-full"
alt="action-screenshot" alt="action-screenshot"
/> />
@ -150,12 +146,12 @@ function StepArtifacts({ id, stepProps }: Props) {
)} )}
</TabsContent> </TabsContent>
<TabsContent value="screenshot_action"> <TabsContent value="screenshot_action">
{actionScreenshotUris && actionScreenshotUris.length > 0 ? ( {actionScreenshots && actionScreenshots.length > 0 ? (
<div className="grid grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-3 gap-4 p-4">
{actionScreenshotUris.map((uri, index) => ( {actionScreenshots.map((artifact, index) => (
<ZoomableImage <ZoomableImage
key={index} key={index}
src={`${artifactApiBaseUrl}/artifact/image?path=${uri.slice(7)}`} src={getImageURL(artifact)}
className="object-cover w-full h-full" className="object-cover w-full h-full"
alt="action-screenshot" alt="action-screenshot"
/> />
@ -172,31 +168,31 @@ function StepArtifacts({ id, stepProps }: Props) {
)} )}
</TabsContent> </TabsContent>
<TabsContent value="element_tree"> <TabsContent value="element_tree">
{visibleElementsTreeUri ? ( {visibleElementsTree ? (
<JSONArtifact uri={visibleElementsTreeUri} /> <JSONArtifact artifact={visibleElementsTree} />
) : null} ) : null}
</TabsContent> </TabsContent>
<TabsContent value="element_tree_trimmed"> <TabsContent value="element_tree_trimmed">
{visibleElementsTreeTrimmedUri ? ( {visibleElementsTreeTrimmed ? (
<JSONArtifact uri={visibleElementsTreeTrimmedUri} /> <JSONArtifact artifact={visibleElementsTreeTrimmed} />
) : null} ) : null}
</TabsContent> </TabsContent>
<TabsContent value="llm_prompt"> <TabsContent value="llm_prompt">
{llmPromptUri ? <TextArtifact uri={llmPromptUri} /> : null} {llmPrompt ? <TextArtifact artifact={llmPrompt} /> : null}
</TabsContent> </TabsContent>
<TabsContent value="llm_request"> <TabsContent value="llm_request">
{llmRequestUri ? <JSONArtifact uri={llmRequestUri} /> : null} {llmRequest ? <JSONArtifact artifact={llmRequest} /> : null}
</TabsContent> </TabsContent>
<TabsContent value="llm_response_raw"> <TabsContent value="llm_response_raw">
{llmResponseRawUri ? <JSONArtifact uri={llmResponseRawUri} /> : null} {llmResponseRaw ? <JSONArtifact artifact={llmResponseRaw} /> : null}
</TabsContent> </TabsContent>
<TabsContent value="llm_response_parsed"> <TabsContent value="llm_response_parsed">
{llmResponseParsedUri ? ( {llmResponseParsed ? (
<JSONArtifact uri={llmResponseParsedUri} /> <JSONArtifact artifact={llmResponseParsed} />
) : null} ) : null}
</TabsContent> </TabsContent>
<TabsContent value="html_raw"> <TabsContent value="html_raw">
{htmlRawUri ? <TextArtifact uri={htmlRawUri} /> : null} {htmlRaw ? <TextArtifact artifact={htmlRaw} /> : null}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
); );

View file

@ -11,13 +11,13 @@ import {
AccordionTrigger, AccordionTrigger,
} from "@/components/ui/accordion"; } from "@/components/ui/accordion";
import { StatusBadge } from "@/components/StatusBadge"; import { StatusBadge } from "@/components/StatusBadge";
import { artifactApiBaseUrl } from "@/util/env";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ReloadIcon } from "@radix-ui/react-icons"; import { ReloadIcon } from "@radix-ui/react-icons";
import { basicTimeFormat } from "@/util/timeFormat"; import { basicTimeFormat } from "@/util/timeFormat";
import { StepArtifactsLayout } from "./StepArtifactsLayout"; import { StepArtifactsLayout } from "./StepArtifactsLayout";
import Zoom from "react-medium-image-zoom"; import Zoom from "react-medium-image-zoom";
import { AspectRatio } from "@/components/ui/aspect-ratio"; import { AspectRatio } from "@/components/ui/aspect-ratio";
import { getRecordingURL, getScreenshotURL } from "./artifactUtils";
function TaskDetails() { function TaskDetails() {
const { taskId } = useParams(); const { taskId } = useParams();
@ -63,10 +63,7 @@ function TaskDetails() {
{task.recording_url ? ( {task.recording_url ? (
<div className="flex"> <div className="flex">
<Label className="w-32">Recording</Label> <Label className="w-32">Recording</Label>
<video <video src={getRecordingURL(task)} controls />
src={`${artifactApiBaseUrl}/artifact/recording?path=${task.recording_url.slice(7)}`}
controls
/>
</div> </div>
) : null} ) : null}
<div className="flex items-center"> <div className="flex items-center">
@ -142,10 +139,7 @@ function TaskDetails() {
{task.screenshot_url ? ( {task.screenshot_url ? (
<Zoom zoomMargin={16}> <Zoom zoomMargin={16}>
<AspectRatio ratio={16 / 9}> <AspectRatio ratio={16 / 9}>
<img <img src={getScreenshotURL(task)} alt="screenshot" />
src={`${artifactApiBaseUrl}/artifact/image?path=${task.screenshot_url.slice(7)}`}
alt="screenshot"
/>
</AspectRatio> </AspectRatio>
</Zoom> </Zoom>
) : ( ) : (

View file

@ -1,23 +1,30 @@
import { artifactApiClient } from "@/api/AxiosClient"; import { artifactApiClient } from "@/api/AxiosClient";
import { ArtifactApiResponse } from "@/api/types";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios";
type Props = { type Props = {
uri: string; artifact: ArtifactApiResponse;
}; };
function TextArtifact({ uri }: Props) { function TextArtifact({ artifact }: Props) {
const { data, isFetching, isError, error } = useQuery<string>({ const { data, isFetching, isError, error } = useQuery<string>({
queryKey: ["artifact", uri], queryKey: ["artifact", artifact.artifact_id],
queryFn: async () => { queryFn: async () => {
return artifactApiClient if (artifact.uri.startsWith("file://")) {
.get(`/artifact/text`, { return artifactApiClient
params: { .get(`/artifact/text`, {
path: uri.slice(7), params: {
}, path: artifact.uri.slice(7),
}) },
.then((response) => response.data); })
.then((response) => response.data);
}
if (artifact.uri.startsWith("s3://") && artifact.signed_url) {
return axios.get(artifact.signed_url).then((response) => response.data);
}
}, },
}); });

View file

@ -0,0 +1,31 @@
import { ArtifactApiResponse, TaskApiResponse } from "@/api/types";
import { artifactApiBaseUrl } from "@/util/env";
export function getImageURL(artifact: ArtifactApiResponse): string {
if (artifact.uri.startsWith("file://")) {
return `${artifactApiBaseUrl}/artifact/image?path=${artifact.uri.slice(7)}`;
} else if (artifact.uri.startsWith("s3://") && artifact.signed_url) {
return artifact.signed_url;
}
return artifact.uri;
}
export function getScreenshotURL(task: TaskApiResponse) {
if (!task.screenshot_url) {
return;
}
if (task.screenshot_url?.startsWith("file://")) {
return `${artifactApiBaseUrl}/artifact/image?path=${task.screenshot_url.slice(7)}`;
}
return task.screenshot_url;
}
export function getRecordingURL(task: TaskApiResponse) {
if (!task.recording_url) {
return;
}
if (task.recording_url?.startsWith("file://")) {
return `${artifactApiBaseUrl}/artifact/recording?path=${task.recording_url.slice(7)}`;
}
return task.recording_url;
}