diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/RunBlock.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/RunBlock.tsx index fefd18a2a2..3d7d2cef8d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/RunBlock.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/RunBlock.tsx @@ -3,13 +3,8 @@ import type { ToolUIPart } from "ai"; import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation"; import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion"; -import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { - ChatCredentialsSetup, - type CredentialInfo, -} from "@/components/contextual/Chat/components/ChatCredentialsSetup/ChatCredentialsSetup"; -import { - formatMaybeJson, + getAccordionMeta, getAnimationText, getRunBlockToolOutput, isRunBlockBlockOutput, @@ -18,6 +13,9 @@ import { ToolIcon, type RunBlockToolOutput, } from "./helpers"; +import { BlockOutputCard } from "./components/BlockOutputCard/BlockOutputCard"; +import { SetupRequirementsCard } from "./components/SetupRequirementsCard/SetupRequirementsCard"; +import { ErrorCard } from "./components/ErrorCard/ErrorCard"; export interface RunBlockToolPart { type: string; @@ -31,165 +29,8 @@ interface Props { part: RunBlockToolPart; } -function getAccordionMeta(output: RunBlockToolOutput): { - badgeText: string; - title: string; - description?: string; -} { - if (isRunBlockBlockOutput(output)) { - const keys = Object.keys(output.outputs ?? {}); - return { - badgeText: "Run block", - title: output.block_name, - description: - keys.length > 0 - ? `${keys.length} output key${keys.length === 1 ? "" : "s"}` - : output.message, - }; - } - - if (isRunBlockSetupRequirementsOutput(output)) { - const missingCredsCount = Object.keys( - (output.setup_info.user_readiness?.missing_credentials ?? {}) as Record< - string, - unknown - >, - ).length; - return { - badgeText: "Run block", - title: output.setup_info.agent_name, - description: - missingCredsCount > 0 - ? `Missing ${missingCredsCount} credential${missingCredsCount === 1 ? "" : "s"}` - : output.message, - }; - } - - return { badgeText: "Run block", title: "Error" }; -} - -function coerceMissingCredentials( - rawMissingCredentials: unknown, -): CredentialInfo[] { - const missing = - rawMissingCredentials && typeof rawMissingCredentials === "object" - ? (rawMissingCredentials as Record) - : {}; - - const validTypes = new Set([ - "api_key", - "oauth2", - "user_password", - "host_scoped", - ]); - - const results: CredentialInfo[] = []; - - Object.values(missing).forEach((value) => { - if (!value || typeof value !== "object") return; - const cred = value as Record; - - const provider = - typeof cred.provider === "string" ? cred.provider.trim() : ""; - if (!provider) return; - - const providerName = - typeof cred.provider_name === "string" && cred.provider_name.trim() - ? cred.provider_name.trim() - : provider.replace(/_/g, " "); - - const title = - typeof cred.title === "string" && cred.title.trim() - ? cred.title.trim() - : providerName; - - const types = - Array.isArray(cred.types) && cred.types.length > 0 - ? cred.types - : typeof cred.type === "string" - ? [cred.type] - : []; - - const credentialTypes = types - .map((t) => (typeof t === "string" ? t.trim() : "")) - .filter( - (t): t is "api_key" | "oauth2" | "user_password" | "host_scoped" => - validTypes.has(t), - ); - - if (credentialTypes.length === 0) return; - - const scopes = Array.isArray(cred.scopes) - ? cred.scopes.filter((s): s is string => typeof s === "string") - : undefined; - - const item: CredentialInfo = { - provider, - providerName, - credentialTypes, - title, - }; - if (scopes && scopes.length > 0) { - item.scopes = scopes; - } - results.push(item); - }); - - return results; -} - -function coerceExpectedInputs(rawInputs: unknown): Array<{ - name: string; - title: string; - type: string; - description?: string; - required: boolean; -}> { - if (!Array.isArray(rawInputs)) return []; - const results: Array<{ - name: string; - title: string; - type: string; - description?: string; - required: boolean; - }> = []; - - rawInputs.forEach((value, index) => { - if (!value || typeof value !== "object") return; - const input = value as Record; - - const name = - typeof input.name === "string" && input.name.trim() - ? input.name.trim() - : `input-${index}`; - const title = - typeof input.title === "string" && input.title.trim() - ? input.title.trim() - : name; - const type = typeof input.type === "string" ? input.type : "unknown"; - const description = - typeof input.description === "string" && input.description.trim() - ? input.description.trim() - : undefined; - const required = Boolean(input.required); - - const item: { - name: string; - title: string; - type: string; - description?: string; - required: boolean; - } = { name, title, type, required }; - if (description) item.description = description; - results.push(item); - }); - - return results; -} - export function RunBlockTool({ part }: Props) { const text = getAnimationText(part); - const { onSend } = useCopilotChatActions(); const isStreaming = part.state === "input-streaming" || part.state === "input-available"; @@ -204,12 +45,6 @@ export function RunBlockTool({ part }: Props) { isRunBlockSetupRequirementsOutput(output) || isRunBlockErrorOutput(output)); - function handleAllCredentialsComplete() { - onSend( - "I've configured the required credentials. Please re-run the block now.", - ); - } - return (
@@ -225,99 +60,13 @@ export function RunBlockTool({ part }: Props) { {...getAccordionMeta(output)} defaultExpanded={isRunBlockSetupRequirementsOutput(output)} > - {isRunBlockBlockOutput(output) && ( -
-

{output.message}

- - {Object.entries(output.outputs ?? {}).map(([key, items]) => ( -
-
-

- {key} -

- - {items.length} item{items.length === 1 ? "" : "s"} - -
-
-                    {formatMaybeJson(items.slice(0, 3))}
-                  
-
- ))} -
- )} + {isRunBlockBlockOutput(output) && } {isRunBlockSetupRequirementsOutput(output) && ( -
-

{output.message}

- - {coerceMissingCredentials( - output.setup_info.user_readiness?.missing_credentials, - ).length > 0 && ( - {}} - /> - )} - - {coerceExpectedInputs( - (output.setup_info.requirements as Record) - ?.inputs, - ).length > 0 && ( -
-

- Expected inputs -

-
- {coerceExpectedInputs( - ( - output.setup_info.requirements as Record< - string, - unknown - > - )?.inputs, - ).map((input) => ( -
-
-

- {input.title} -

- - {input.required ? "Required" : "Optional"} - -
-

- {input.name} • {input.type} - {input.description ? ` • ${input.description}` : ""} -

-
- ))} -
-
- )} -
+ )} - {isRunBlockErrorOutput(output) && ( -
-

{output.message}

- {output.error && ( -
-                  {formatMaybeJson(output.error)}
-                
- )} - {output.details && ( -
-                  {formatMaybeJson(output.details)}
-                
- )} -
- )} + {isRunBlockErrorOutput(output) && } )}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/components/BlockOutputCard/BlockOutputCard.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/components/BlockOutputCard/BlockOutputCard.tsx new file mode 100644 index 0000000000..3e5e97fc27 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/copilot-2/tools/RunBlock/components/BlockOutputCard/BlockOutputCard.tsx @@ -0,0 +1,144 @@ +"use client"; + +import React, { useState } from "react"; +import { getGetWorkspaceDownloadFileByIdUrl } from "@/app/api/__generated__/endpoints/workspace/workspace"; +import { Button } from "@/components/atoms/Button/Button"; +import type { BlockOutputResponse } from "@/app/api/__generated__/models/blockOutputResponse"; +import { formatMaybeJson } from "../../helpers"; + +interface Props { + output: BlockOutputResponse; +} + +const COLLAPSED_LIMIT = 3; + +function resolveWorkspaceUrl(src: string): string { + const withoutPrefix = src.replace("workspace://", ""); + const fileId = withoutPrefix.split("#")[0]; + const apiPath = getGetWorkspaceDownloadFileByIdUrl(fileId); + return `/api/proxy${apiPath}`; +} + +function getWorkspaceMimeHint(src: string): string | undefined { + const hashIndex = src.indexOf("#"); + if (hashIndex === -1) return undefined; + return src.slice(hashIndex + 1) || undefined; +} + +function isWorkspaceRef(value: unknown): value is string { + return typeof value === "string" && value.startsWith("workspace://"); +} + +function WorkspaceMedia({ value }: { value: string }) { + const [imgFailed, setImgFailed] = useState(false); + const resolvedUrl = resolveWorkspaceUrl(value); + const mime = getWorkspaceMimeHint(value); + + if (mime?.startsWith("video/") || imgFailed) { + return ( + + ); + } + + if (mime?.startsWith("audio/")) { + return