diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx
index 4213711447..c58bdac642 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx
@@ -1,6 +1,6 @@
import { beautifyString } from "@/lib/utils";
import { Clipboard, Maximize2 } from "lucide-react";
-import React, { useState } from "react";
+import React, { useMemo, useState } from "react";
import { Button } from "../../../../../components/__legacy__/ui/button";
import { ContentRenderer } from "../../../../../components/__legacy__/ui/render";
import {
@@ -11,6 +11,12 @@ import {
TableHeader,
TableRow,
} from "../../../../../components/__legacy__/ui/table";
+import type { OutputMetadata } from "@/components/contextual/OutputRenderers";
+import {
+ globalRegistry,
+ OutputItem,
+} from "@/components/contextual/OutputRenderers";
+import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { useToast } from "../../../../../components/molecules/Toast/use-toast";
import ExpandableOutputDialog from "./ExpandableOutputDialog";
@@ -26,6 +32,9 @@ export default function DataTable({
data,
}: DataTableProps) {
const { toast } = useToast();
+ const enableEnhancedOutputHandling = useGetFlag(
+ Flag.ENABLE_ENHANCED_OUTPUT_HANDLING,
+ );
const [expandedDialog, setExpandedDialog] = useState<{
isOpen: boolean;
execId: string;
@@ -33,6 +42,15 @@ export default function DataTable({
data: any[];
} | null>(null);
+ // Prepare renderers for each item when enhanced mode is enabled
+ const getItemRenderer = useMemo(() => {
+ if (!enableEnhancedOutputHandling) return null;
+ return (item: unknown) => {
+ const metadata: OutputMetadata = {};
+ return globalRegistry.getRenderer(item, metadata);
+ };
+ }, [enableEnhancedOutputHandling]);
+
const copyData = (pin: string, data: string) => {
navigator.clipboard.writeText(data).then(() => {
toast({
@@ -102,15 +120,31 @@ export default function DataTable({
- {value.map((item, index) => (
-
-
- {index < value.length - 1 && ", "}
-
- ))}
+ {value.map((item, index) => {
+ const renderer = getItemRenderer?.(item);
+ if (enableEnhancedOutputHandling && renderer) {
+ const metadata: OutputMetadata = {};
+ return (
+
+
+ {index < value.length - 1 && ", "}
+
+ );
+ }
+ return (
+
+
+ {index < value.length - 1 && ", "}
+
+ );
+ })}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeOutputs.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeOutputs.tsx
index d90b7d6a4c..2111db7d99 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeOutputs.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/NodeOutputs.tsx
@@ -1,8 +1,14 @@
-import React, { useContext, useState } from "react";
+import React, { useContext, useMemo, useState } from "react";
import { Button } from "@/components/__legacy__/ui/button";
import { Maximize2 } from "lucide-react";
import * as Separator from "@radix-ui/react-separator";
import { ContentRenderer } from "@/components/__legacy__/ui/render";
+import type { OutputMetadata } from "@/components/contextual/OutputRenderers";
+import {
+ globalRegistry,
+ OutputItem,
+} from "@/components/contextual/OutputRenderers";
+import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { beautifyString } from "@/lib/utils";
@@ -21,6 +27,9 @@ export default function NodeOutputs({
data,
}: NodeOutputsProps) {
const builderContext = useContext(BuilderContext);
+ const enableEnhancedOutputHandling = useGetFlag(
+ Flag.ENABLE_ENHANCED_OUTPUT_HANDLING,
+ );
const [expandedDialog, setExpandedDialog] = useState<{
isOpen: boolean;
@@ -37,6 +46,15 @@ export default function NodeOutputs({
const { getNodeTitle } = builderContext;
+ // Prepare renderers for each item when enhanced mode is enabled
+ const getItemRenderer = useMemo(() => {
+ if (!enableEnhancedOutputHandling) return null;
+ return (item: unknown) => {
+ const metadata: OutputMetadata = {};
+ return globalRegistry.getRenderer(item, metadata);
+ };
+ }, [enableEnhancedOutputHandling]);
+
const getBeautifiedPinName = (pin: string) => {
if (!pin.startsWith("tools_^_")) {
return beautifyString(pin);
@@ -87,15 +105,31 @@ export default function NodeOutputs({
Data:
- {dataArray.slice(0, 10).map((item, index) => (
-
-
- {index < Math.min(dataArray.length, 10) - 1 && ", "}
-
- ))}
+ {dataArray.slice(0, 10).map((item, index) => {
+ const renderer = getItemRenderer?.(item);
+ if (enableEnhancedOutputHandling && renderer) {
+ const metadata: OutputMetadata = {};
+ return (
+
+
+ {index < Math.min(dataArray.length, 10) - 1 && ", "}
+
+ );
+ }
+ return (
+
+
+ {index < Math.min(dataArray.length, 10) - 1 && ", "}
+
+ );
+ })}
{dataArray.length > 10 && (
diff --git a/autogpt_platform/frontend/src/components/__legacy__/ui/render.tsx b/autogpt_platform/frontend/src/components/__legacy__/ui/render.tsx
index 5173326f23..b290c51809 100644
--- a/autogpt_platform/frontend/src/components/__legacy__/ui/render.tsx
+++ b/autogpt_platform/frontend/src/components/__legacy__/ui/render.tsx
@@ -22,7 +22,7 @@ const isValidVideoUrl = (url: string): boolean => {
if (url.startsWith("data:video")) {
return true;
}
- const videoExtensions = /\.(mp4|webm|ogg)$/i;
+ const videoExtensions = /\.(mp4|webm|ogg|mov|avi|mkv|m4v)$/i;
const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
const cleanedUrl = url.split("?")[0];
return (
@@ -44,11 +44,29 @@ const isValidAudioUrl = (url: string): boolean => {
if (url.startsWith("data:audio")) {
return true;
}
- const audioExtensions = /\.(mp3|wav)$/i;
+ const audioExtensions = /\.(mp3|wav|ogg|m4a|aac|flac)$/i;
const cleanedUrl = url.split("?")[0];
return isValidMediaUri(url) && audioExtensions.test(cleanedUrl);
};
+const getVideoMimeType = (url: string): string => {
+ if (url.startsWith("data:video/")) {
+ const match = url.match(/^data:(video\/[^;]+)/);
+ return match?.[1] || "video/mp4";
+ }
+ const extension = url.split("?")[0].split(".").pop()?.toLowerCase();
+ const mimeMap: Record = {
+ mp4: "video/mp4",
+ webm: "video/webm",
+ ogg: "video/ogg",
+ mov: "video/quicktime",
+ avi: "video/x-msvideo",
+ mkv: "video/x-matroska",
+ m4v: "video/mp4",
+ };
+ return mimeMap[extension || ""] || "video/mp4";
+};
+
const VideoRenderer: React.FC<{ videoUrl: string }> = ({ videoUrl }) => {
const videoId = getYouTubeVideoId(videoUrl);
return (
@@ -63,7 +81,7 @@ const VideoRenderer: React.FC<{ videoUrl: string }> = ({ videoUrl }) => {
>
) : (
)}
diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/MarkdownContent/MarkdownContent.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/MarkdownContent/MarkdownContent.tsx
index 3dd5eca692..ecadbe938b 100644
--- a/autogpt_platform/frontend/src/components/contextual/Chat/components/MarkdownContent/MarkdownContent.tsx
+++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/MarkdownContent/MarkdownContent.tsx
@@ -3,7 +3,7 @@
import { getGetWorkspaceDownloadFileByIdUrl } from "@/app/api/__generated__/endpoints/workspace/workspace";
import { cn } from "@/lib/utils";
import { EyeSlash } from "@phosphor-icons/react";
-import React from "react";
+import React, { useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
@@ -48,7 +48,9 @@ interface InputProps extends React.InputHTMLAttributes {
*/
function resolveWorkspaceUrl(src: string): string {
if (src.startsWith("workspace://")) {
- const fileId = src.replace("workspace://", "");
+ // Strip MIME type fragment if present (e.g., workspace://abc123#video/mp4 → abc123)
+ const withoutPrefix = src.replace("workspace://", "");
+ const fileId = withoutPrefix.split("#")[0];
// Use the generated API URL helper to get the correct path
const apiPath = getGetWorkspaceDownloadFileByIdUrl(fileId);
// Route through the Next.js proxy (same pattern as customMutator for client-side)
@@ -65,13 +67,49 @@ function isWorkspaceImage(src: string | undefined): boolean {
return src?.includes("/workspace/files/") ?? false;
}
+/**
+ * Renders a workspace video with controls and an optional "AI cannot see" badge.
+ */
+function WorkspaceVideo({
+ src,
+ aiCannotSee,
+}: {
+ src: string;
+ aiCannotSee: boolean;
+}) {
+ return (
+
+
+ {aiCannotSee && (
+
+
+ AI cannot see this video
+
+ )}
+
+ );
+}
+
/**
* Custom image component that shows an indicator when the AI cannot see the image.
+ * Also handles the "video:" alt-text prefix convention to render