diff --git a/rnd/autogpt_builder/.gitignore b/rnd/autogpt_builder/.gitignore index fd3dbb571a12..1dd45b202245 100644 --- a/rnd/autogpt_builder/.gitignore +++ b/rnd/autogpt_builder/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# Sentry Config File +.env.sentry-build-plugin diff --git a/rnd/autogpt_builder/next.config.mjs b/rnd/autogpt_builder/next.config.mjs index 9b18200a153c..2204c43a805d 100644 --- a/rnd/autogpt_builder/next.config.mjs +++ b/rnd/autogpt_builder/next.config.mjs @@ -1,3 +1,4 @@ +import { withSentryConfig } from "@sentry/nextjs"; import dotenv from "dotenv"; // Load environment variables @@ -28,4 +29,56 @@ const nextConfig = { }, }; -export default nextConfig; +export default withSentryConfig(nextConfig, { + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options + + org: "significant-gravitas", + project: "builder", + + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + + // Automatically annotate React components to show their full name in breadcrumbs and session replay + reactComponentAnnotation: { + enabled: true, + }, + + // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // This can increase your server load as well as your hosting bill. + // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- + // side errors will fail. + tunnelRoute: "/monitoring", + + // Hides source maps from generated client bundles + hideSourceMaps: true, + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + + // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) + // See the following for more information: + // https://docs.sentry.io/product/crons/ + // https://vercel.com/docs/cron-jobs + automaticVercelMonitors: true, + + async headers() { + return [ + { + source: "/:path*", + headers: [ + { + key: "Document-Policy", + value: "js-profiling", + }, + ], + }, + ]; + }, +}); diff --git a/rnd/autogpt_builder/package.json b/rnd/autogpt_builder/package.json index 77b24c096379..0b312655e59f 100644 --- a/rnd/autogpt_builder/package.json +++ b/rnd/autogpt_builder/package.json @@ -27,6 +27,7 @@ "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.2", + "@sentry/nextjs": "^8", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.45.0", "@tanstack/react-table": "^8.20.5", diff --git a/rnd/autogpt_builder/sentry.client.config.ts b/rnd/autogpt_builder/sentry.client.config.ts new file mode 100644 index 000000000000..aad53ebbb71b --- /dev/null +++ b/rnd/autogpt_builder/sentry.client.config.ts @@ -0,0 +1,57 @@ +// This file configures the initialization of Sentry on the client. +// The config you add here will be used whenever a users loads a page in their browser. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288", + + // Add optional integrations for additional features + integrations: [ + Sentry.replayIntegration(), + Sentry.httpClientIntegration(), + Sentry.replayCanvasIntegration(), + Sentry.reportingObserverIntegration(), + Sentry.browserProfilingIntegration(), + // Sentry.feedbackIntegration({ + // // Additional SDK configuration goes in here, for example: + // colorScheme: "system", + // }), + ], + + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, + + // Set `tracePropagationTargets` to control for which URLs trace propagation should be enabled + tracePropagationTargets: [ + "localhost", + /^https:\/\/dev\-builder\.agpt\.co\/api/, + ], + + beforeSend(event, hint) { + // Check if it is an exception, and if so, show the report dialog + if (event.exception && event.event_id) { + Sentry.showReportDialog({ eventId: event.event_id }); + } + return event; + }, + + // Define how likely Replay events are sampled. + // This sets the sample rate to be 10%. You may want this to be 100% while + // in development and sample at a lower rate in production + replaysSessionSampleRate: 0.1, + + // Define how likely Replay events are sampled when an error occurs. + replaysOnErrorSampleRate: 1.0, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, + + // Set profilesSampleRate to 1.0 to profile every transaction. + // Since profilesSampleRate is relative to tracesSampleRate, + // the final profiling rate can be computed as tracesSampleRate * profilesSampleRate + // For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would + // result in 25% of transactions being profiled (0.5*0.5=0.25) + profilesSampleRate: 1.0, +}); diff --git a/rnd/autogpt_builder/sentry.edge.config.ts b/rnd/autogpt_builder/sentry.edge.config.ts new file mode 100644 index 000000000000..09903cfad599 --- /dev/null +++ b/rnd/autogpt_builder/sentry.edge.config.ts @@ -0,0 +1,16 @@ +// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). +// The config you add here will be used whenever one of the edge features is loaded. +// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288", + + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, +}); diff --git a/rnd/autogpt_builder/sentry.server.config.ts b/rnd/autogpt_builder/sentry.server.config.ts new file mode 100644 index 000000000000..db0cf30751fc --- /dev/null +++ b/rnd/autogpt_builder/sentry.server.config.ts @@ -0,0 +1,23 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; +// import { NodeProfilingIntegration } from "@sentry/profiling-node"; + +Sentry.init({ + dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288", + + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, + + // Integrations + integrations: [ + Sentry.anrIntegration(), + // NodeProfilingIntegration, + // Sentry.fsIntegration(), + ], +}); diff --git a/rnd/autogpt_builder/src/app/global-error.tsx b/rnd/autogpt_builder/src/app/global-error.tsx new file mode 100644 index 000000000000..9388e06e02bd --- /dev/null +++ b/rnd/autogpt_builder/src/app/global-error.tsx @@ -0,0 +1,27 @@ +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; +import { useEffect } from "react"; + +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} diff --git a/rnd/autogpt_builder/src/app/login/actions.ts b/rnd/autogpt_builder/src/app/login/actions.ts index dadd1de912c8..08ac37d02576 100644 --- a/rnd/autogpt_builder/src/app/login/actions.ts +++ b/rnd/autogpt_builder/src/app/login/actions.ts @@ -3,6 +3,7 @@ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { createServerClient } from "@/lib/supabase/server"; import { z } from "zod"; +import * as Sentry from "@sentry/nextjs"; const loginFormSchema = z.object({ email: z.string().email().min(2).max(64), @@ -10,45 +11,53 @@ const loginFormSchema = z.object({ }); export async function login(values: z.infer) { - const supabase = createServerClient(); + return await Sentry.withServerActionInstrumentation("login", {}, async () => { + const supabase = createServerClient(); - if (!supabase) { - redirect("/error"); - } + if (!supabase) { + redirect("/error"); + } - // We are sure that the values are of the correct type because zod validates the form - const { data, error } = await supabase.auth.signInWithPassword(values); + // We are sure that the values are of the correct type because zod validates the form + const { data, error } = await supabase.auth.signInWithPassword(values); - if (error) { - return error.message; - } + if (error) { + return error.message; + } - if (data.session) { - await supabase.auth.setSession(data.session); - } + if (data.session) { + await supabase.auth.setSession(data.session); + } - revalidatePath("/", "layout"); - redirect("/profile"); + revalidatePath("/", "layout"); + redirect("/profile"); + }); } export async function signup(values: z.infer) { - const supabase = createServerClient(); - - if (!supabase) { - redirect("/error"); - } - - // We are sure that the values are of the correct type because zod validates the form - const { data, error } = await supabase.auth.signUp(values); - - if (error) { - return error.message; - } - - if (data.session) { - await supabase.auth.setSession(data.session); - } - - revalidatePath("/", "layout"); - redirect("/profile"); + return await Sentry.withServerActionInstrumentation( + "signup", + {}, + async () => { + const supabase = createServerClient(); + + if (!supabase) { + redirect("/error"); + } + + // We are sure that the values are of the correct type because zod validates the form + const { data, error } = await supabase.auth.signUp(values); + + if (error) { + return error.message; + } + + if (data.session) { + await supabase.auth.setSession(data.session); + } + + revalidatePath("/", "layout"); + redirect("/profile"); + }, + ); } diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index a794cb0164ca..b0c112a5aa95 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -27,7 +27,7 @@ import "@xyflow/react/dist/style.css"; import { CustomNode } from "./CustomNode"; import "./flow.css"; import { Link } from "@/lib/autogpt-server-api"; -import { getTypeColor } from "@/lib/utils"; +import { getTypeColor, filterBlocksByType } from "@/lib/utils"; import { history } from "./history"; import { CustomEdge } from "./CustomEdge"; import ConnectionLine from "./ConnectionLine"; @@ -36,14 +36,19 @@ import { SaveControl } from "@/components/edit/control/SaveControl"; import { BlocksControl } from "@/components/edit/control/BlocksControl"; import { IconPlay, + IconUndo2, IconRedo2, IconSquare, - IconUndo2, + IconOutput, } from "@/components/ui/icons"; import { startTutorial } from "./tutorial"; import useAgentGraph from "@/hooks/useAgentGraph"; import { v4 as uuidv4 } from "uuid"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; +import { LogOut } from "lucide-react"; +import RunnerUIWrapper, { + RunnerUIWrapperRef, +} from "@/components/RunnerUIWrapper"; // This is for the history, this is the minimum distance a block must move before it is logged // It helps to prevent spamming the history with small movements especially when pressing on a input in a block @@ -101,6 +106,8 @@ const FlowEditor: React.FC<{ // State to control if blocks menu should be pinned open const [pinBlocksPopover, setPinBlocksPopover] = useState(false); + const runnerUIRef = useRef(null); + useEffect(() => { const params = new URLSearchParams(window.location.search); @@ -550,9 +557,21 @@ const FlowEditor: React.FC<{ onClick: handleRedo, }, { - label: !isRunning ? "Run" : "Stop", + label: !savedAgent + ? "Please save the agent to run" + : !isRunning + ? "Run" + : "Stop", icon: !isRunning ? : , - onClick: !isRunning ? requestSaveAndRun : requestStopRun, + onClick: !isRunning + ? () => runnerUIRef.current?.runOrOpenInput() + : requestStopRun, + disabled: !savedAgent, + }, + { + label: "Runner Output", + icon: , + onClick: () => runnerUIRef.current?.openRunnerOutput(), }, ]; @@ -596,6 +615,13 @@ const FlowEditor: React.FC<{ + ); }; diff --git a/rnd/autogpt_builder/src/components/RunnerUIWrapper.tsx b/rnd/autogpt_builder/src/components/RunnerUIWrapper.tsx new file mode 100644 index 000000000000..bd9b6f29dbe2 --- /dev/null +++ b/rnd/autogpt_builder/src/components/RunnerUIWrapper.tsx @@ -0,0 +1,141 @@ +import React, { + useState, + useCallback, + forwardRef, + useImperativeHandle, +} from "react"; +import RunnerInputUI from "./runner-ui/RunnerInputUI"; +import RunnerOutputUI from "./runner-ui/RunnerOutputUI"; +import { Node } from "@xyflow/react"; +import { filterBlocksByType } from "@/lib/utils"; +import { BlockIORootSchema } from "@/lib/autogpt-server-api/types"; + +interface RunnerUIWrapperProps { + nodes: Node[]; + setNodes: React.Dispatch>; + isRunning: boolean; + requestSaveAndRun: () => void; +} + +export interface RunnerUIWrapperRef { + openRunnerInput: () => void; + openRunnerOutput: () => void; + runOrOpenInput: () => void; +} + +const RunnerUIWrapper = forwardRef( + ({ nodes, setNodes, isRunning, requestSaveAndRun }, ref) => { + const [isRunnerInputOpen, setIsRunnerInputOpen] = useState(false); + const [isRunnerOutputOpen, setIsRunnerOutputOpen] = useState(false); + + const getBlockInputsAndOutputs = useCallback(() => { + const inputBlocks = filterBlocksByType( + nodes, + (node) => node.data.block_id === "c0a8e994-ebf1-4a9c-a4d8-89d09c86741b", + ); + + const outputBlocks = filterBlocksByType( + nodes, + (node) => node.data.block_id === "363ae599-353e-4804-937e-b2ee3cef3da4", + ); + + const inputs = inputBlocks.map((node) => ({ + id: node.id, + type: "input" as const, + inputSchema: node.data.inputSchema as BlockIORootSchema, + hardcodedValues: { + name: (node.data.hardcodedValues as any).name || "", + description: (node.data.hardcodedValues as any).description || "", + value: (node.data.hardcodedValues as any).value, + placeholder_values: + (node.data.hardcodedValues as any).placeholder_values || [], + limit_to_placeholder_values: + (node.data.hardcodedValues as any).limit_to_placeholder_values || + false, + }, + })); + + const outputs = outputBlocks.map((node) => ({ + id: node.id, + type: "output" as const, + outputSchema: node.data.outputSchema as BlockIORootSchema, + hardcodedValues: { + name: (node.data.hardcodedValues as any).name || "Output", + description: + (node.data.hardcodedValues as any).description || + "Output from the agent", + value: (node.data.hardcodedValues as any).value, + }, + result: (node.data.executionResults as any)?.at(-1)?.data?.output, + })); + + return { inputs, outputs }; + }, [nodes]); + + const handleInputChange = useCallback( + (nodeId: string, field: string, value: string) => { + setNodes((nds) => + nds.map((node) => { + if (node.id === nodeId) { + return { + ...node, + data: { + ...node.data, + hardcodedValues: { + ...(node.data.hardcodedValues as any), + [field]: value, + }, + }, + }; + } + return node; + }), + ); + }, + [setNodes], + ); + + const openRunnerInput = () => setIsRunnerInputOpen(true); + const openRunnerOutput = () => setIsRunnerOutputOpen(true); + + const runOrOpenInput = () => { + const { inputs } = getBlockInputsAndOutputs(); + if (inputs.length > 0) { + openRunnerInput(); + } else { + requestSaveAndRun(); + } + }; + + useImperativeHandle(ref, () => ({ + openRunnerInput, + openRunnerOutput, + runOrOpenInput, + })); + + return ( + <> + setIsRunnerInputOpen(false)} + blockInputs={getBlockInputsAndOutputs().inputs} + onInputChange={handleInputChange} + onRun={() => { + setIsRunnerInputOpen(false); + requestSaveAndRun(); + }} + isRunning={isRunning} + /> + setIsRunnerOutputOpen(false)} + blockOutputs={getBlockInputsAndOutputs().outputs} + /> + + ); + }, +); + +RunnerUIWrapper.displayName = "RunnerUIWrapper"; + +export default RunnerUIWrapper; diff --git a/rnd/autogpt_builder/src/components/admin/marketplace/AdminFeaturedAgentsControl.tsx b/rnd/autogpt_builder/src/components/admin/marketplace/AdminFeaturedAgentsControl.tsx index 06331cd9fa52..e0a9f1d6ec98 100644 --- a/rnd/autogpt_builder/src/components/admin/marketplace/AdminFeaturedAgentsControl.tsx +++ b/rnd/autogpt_builder/src/components/admin/marketplace/AdminFeaturedAgentsControl.tsx @@ -9,6 +9,7 @@ import { import FeaturedAgentsTable from "./FeaturedAgentsTable"; import { AdminAddFeaturedAgentDialog } from "./AdminAddFeaturedAgentDialog"; import { revalidatePath } from "next/cache"; +import * as Sentry from "@sentry/nextjs"; export default async function AdminFeaturedAgentsControl({ className, @@ -55,9 +56,15 @@ export default async function AdminFeaturedAgentsControl({ component: , action: async (rows) => { "use server"; - const all = rows.map((row) => removeFeaturedAgent(row.id)); - await Promise.all(all); - revalidatePath("/marketplace"); + return await Sentry.withServerActionInstrumentation( + "removeFeaturedAgent", + {}, + async () => { + const all = rows.map((row) => removeFeaturedAgent(row.id)); + await Promise.all(all); + revalidatePath("/marketplace"); + }, + ); }, }, ]} diff --git a/rnd/autogpt_builder/src/components/admin/marketplace/actions.ts b/rnd/autogpt_builder/src/components/admin/marketplace/actions.ts index 88c3c2a88ad5..69ab10404480 100644 --- a/rnd/autogpt_builder/src/components/admin/marketplace/actions.ts +++ b/rnd/autogpt_builder/src/components/admin/marketplace/actions.ts @@ -2,16 +2,23 @@ import AutoGPTServerAPI from "@/lib/autogpt-server-api"; import MarketplaceAPI from "@/lib/marketplace-api"; import { revalidatePath } from "next/cache"; +import * as Sentry from "@sentry/nextjs"; export async function approveAgent( agentId: string, version: number, comment: string, ) { - const api = new MarketplaceAPI(); - await api.approveAgentSubmission(agentId, version, comment); - console.debug(`Approving agent ${agentId}`); - revalidatePath("/marketplace"); + return await Sentry.withServerActionInstrumentation( + "approveAgent", + {}, + async () => { + const api = new MarketplaceAPI(); + await api.approveAgentSubmission(agentId, version, comment); + console.debug(`Approving agent ${agentId}`); + revalidatePath("/marketplace"); + }, + ); } export async function rejectAgent( @@ -19,67 +26,117 @@ export async function rejectAgent( version: number, comment: string, ) { - const api = new MarketplaceAPI(); - await api.rejectAgentSubmission(agentId, version, comment); - console.debug(`Rejecting agent ${agentId}`); - revalidatePath("/marketplace"); + return await Sentry.withServerActionInstrumentation( + "rejectAgent", + {}, + async () => { + const api = new MarketplaceAPI(); + await api.rejectAgentSubmission(agentId, version, comment); + console.debug(`Rejecting agent ${agentId}`); + revalidatePath("/marketplace"); + }, + ); } export async function getReviewableAgents() { - const api = new MarketplaceAPI(); - return api.getAgentSubmissions(); + return await Sentry.withServerActionInstrumentation( + "getReviewableAgents", + {}, + async () => { + const api = new MarketplaceAPI(); + return api.getAgentSubmissions(); + }, + ); } export async function getFeaturedAgents( page: number = 1, pageSize: number = 10, ) { - const api = new MarketplaceAPI(); - const featured = await api.getFeaturedAgents(page, pageSize); - console.debug(`Getting featured agents ${featured.agents.length}`); - return featured; + return await Sentry.withServerActionInstrumentation( + "getFeaturedAgents", + {}, + async () => { + const api = new MarketplaceAPI(); + const featured = await api.getFeaturedAgents(page, pageSize); + console.debug(`Getting featured agents ${featured.agents.length}`); + return featured; + }, + ); } export async function getFeaturedAgent(agentId: string) { - const api = new MarketplaceAPI(); - const featured = await api.getFeaturedAgent(agentId); - console.debug(`Getting featured agent ${featured.agentId}`); - return featured; + return await Sentry.withServerActionInstrumentation( + "getFeaturedAgent", + {}, + async () => { + const api = new MarketplaceAPI(); + const featured = await api.getFeaturedAgent(agentId); + console.debug(`Getting featured agent ${featured.agentId}`); + return featured; + }, + ); } export async function addFeaturedAgent( agentId: string, categories: string[] = ["featured"], ) { - const api = new MarketplaceAPI(); - await api.addFeaturedAgent(agentId, categories); - console.debug(`Adding featured agent ${agentId}`); - revalidatePath("/marketplace"); + return await Sentry.withServerActionInstrumentation( + "addFeaturedAgent", + {}, + async () => { + const api = new MarketplaceAPI(); + await api.addFeaturedAgent(agentId, categories); + console.debug(`Adding featured agent ${agentId}`); + revalidatePath("/marketplace"); + }, + ); } export async function removeFeaturedAgent( agentId: string, categories: string[] = ["featured"], ) { - const api = new MarketplaceAPI(); - await api.removeFeaturedAgent(agentId, categories); - console.debug(`Removing featured agent ${agentId}`); - revalidatePath("/marketplace"); + return await Sentry.withServerActionInstrumentation( + "removeFeaturedAgent", + {}, + async () => { + const api = new MarketplaceAPI(); + await api.removeFeaturedAgent(agentId, categories); + console.debug(`Removing featured agent ${agentId}`); + revalidatePath("/marketplace"); + }, + ); } export async function getCategories() { - const api = new MarketplaceAPI(); - const categories = await api.getCategories(); - console.debug(`Getting categories ${categories.unique_categories.length}`); - return categories; + return await Sentry.withServerActionInstrumentation( + "getCategories", + {}, + async () => { + const api = new MarketplaceAPI(); + const categories = await api.getCategories(); + console.debug( + `Getting categories ${categories.unique_categories.length}`, + ); + return categories; + }, + ); } export async function getNotFeaturedAgents( page: number = 1, pageSize: number = 100, ) { - const api = new MarketplaceAPI(); - const agents = await api.getNotFeaturedAgents(page, pageSize); - console.debug(`Getting not featured agents ${agents.agents.length}`); - return agents; + return await Sentry.withServerActionInstrumentation( + "getNotFeaturedAgents", + {}, + async () => { + const api = new MarketplaceAPI(); + const agents = await api.getNotFeaturedAgents(page, pageSize); + console.debug(`Getting not featured agents ${agents.agents.length}`); + return agents; + }, + ); } diff --git a/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx b/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx index 5abbab30b33b..a74bb302f9c1 100644 --- a/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx +++ b/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx @@ -19,6 +19,7 @@ import React from "react"; export type Control = { icon: React.ReactNode; label: string; + disabled?: boolean; onClick: () => void; }; @@ -50,15 +51,18 @@ export const ControlPanel = ({ {controls.map((control, index) => ( - +
+ +
{control.label}
diff --git a/rnd/autogpt_builder/src/components/marketplace/actions.ts b/rnd/autogpt_builder/src/components/marketplace/actions.ts index 2fc158d4a6b4..bee7e74173ab 100644 --- a/rnd/autogpt_builder/src/components/marketplace/actions.ts +++ b/rnd/autogpt_builder/src/components/marketplace/actions.ts @@ -1,9 +1,16 @@ "use server"; +import * as Sentry from "@sentry/nextjs"; import MarketplaceAPI, { AnalyticsEvent } from "@/lib/marketplace-api"; export async function makeAnalyticsEvent(event: AnalyticsEvent) { - const apiUrl = process.env.AGPT_SERVER_API_URL; - const api = new MarketplaceAPI(); - await api.makeAnalyticsEvent(event); + return await Sentry.withServerActionInstrumentation( + "makeAnalyticsEvent", + {}, + async () => { + const apiUrl = process.env.AGPT_SERVER_API_URL; + const api = new MarketplaceAPI(); + await api.makeAnalyticsEvent(event); + }, + ); } diff --git a/rnd/autogpt_builder/src/components/node-input-components.tsx b/rnd/autogpt_builder/src/components/node-input-components.tsx index f0a2bf85f48e..9799876756cf 100644 --- a/rnd/autogpt_builder/src/components/node-input-components.tsx +++ b/rnd/autogpt_builder/src/components/node-input-components.tsx @@ -380,7 +380,7 @@ const NodeKeyValueInput: FC<{ updateKeyValuePairs( keyValuePairs.toSpliced(index, 1, { @@ -563,7 +563,7 @@ const NodeStringInput: FC<{ handleInputChange(selfKey, parseFloat(e.target.value))} placeholder={ schema.placeholder || `Enter ${beautifyString(displayName)}` diff --git a/rnd/autogpt_builder/src/components/runner-ui/RunnerInputBlock.tsx b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputBlock.tsx new file mode 100644 index 000000000000..67d08bada87f --- /dev/null +++ b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputBlock.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +interface InputBlockProps { + id: string; + name: string; + description?: string; + value: string; + placeholder_values?: any[]; + onInputChange: (id: string, field: string, value: string) => void; +} + +export function InputBlock({ + id, + name, + description, + value, + placeholder_values, + onInputChange, +}: InputBlockProps) { + return ( +
+

{name || "Unnamed Input"}

+ {description &&

{description}

} +
+ {placeholder_values && placeholder_values.length > 1 ? ( + + ) : ( + onInputChange(id, "value", e.target.value)} + placeholder={placeholder_values?.[0]?.toString() || "Enter value"} + className="w-full" + /> + )} +
+
+ ); +} diff --git a/rnd/autogpt_builder/src/components/runner-ui/RunnerInputList.tsx b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputList.tsx new file mode 100644 index 000000000000..abf052397931 --- /dev/null +++ b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputList.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { InputBlock } from "./RunnerInputBlock"; +import { BlockInput } from "./RunnerInputUI"; + +interface InputListProps { + blockInputs: BlockInput[]; + onInputChange: (nodeId: string, field: string, value: string) => void; +} + +export function InputList({ blockInputs, onInputChange }: InputListProps) { + return ( + +
+ {blockInputs && blockInputs.length > 0 ? ( + blockInputs.map((block) => ( + + )) + ) : ( +

No input blocks available.

+ )} +
+
+ ); +} diff --git a/rnd/autogpt_builder/src/components/runner-ui/RunnerInputUI.tsx b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputUI.tsx new file mode 100644 index 000000000000..4e0077c8f6d9 --- /dev/null +++ b/rnd/autogpt_builder/src/components/runner-ui/RunnerInputUI.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { BlockIORootSchema } from "@/lib/autogpt-server-api/types"; +import { InputList } from "./RunnerInputList"; + +export interface BlockInput { + id: string; + inputSchema: BlockIORootSchema; + hardcodedValues: { + name: string; + description: string; + value: any; + placeholder_values?: any[]; + limit_to_placeholder_values?: boolean; + }; +} + +interface RunSettingsUiProps { + isOpen: boolean; + onClose: () => void; + blockInputs: BlockInput[]; + onInputChange: (nodeId: string, field: string, value: string) => void; + onRun: () => void; + isRunning: boolean; +} + +export function RunnerInputUI({ + isOpen, + onClose, + blockInputs, + onInputChange, + onRun, + isRunning, +}: RunSettingsUiProps) { + const handleRun = () => { + onRun(); + onClose(); + }; + + return ( + + + + Run Settings + + Configure settings for running your agent. + + +
+ +
+ + + +
+
+ ); +} + +export default RunnerInputUI; diff --git a/rnd/autogpt_builder/src/components/runner-ui/RunnerOutputUI.tsx b/rnd/autogpt_builder/src/components/runner-ui/RunnerOutputUI.tsx new file mode 100644 index 000000000000..8e39eb3d4863 --- /dev/null +++ b/rnd/autogpt_builder/src/components/runner-ui/RunnerOutputUI.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, +} from "@/components/ui/sheet"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { BlockIORootSchema } from "@/lib/autogpt-server-api/types"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; + +interface BlockOutput { + id: string; + outputSchema: BlockIORootSchema; + hardcodedValues: { + name: string; + description: string; + }; + result?: any; +} + +interface OutputModalProps { + isOpen: boolean; + onClose: () => void; + blockOutputs: BlockOutput[]; +} + +const formatOutput = (output: any): string => { + if (typeof output === "object") { + try { + return JSON.stringify(output, null, 2); + } catch (error) { + return `Error formatting output: ${(error as Error).message}`; + } + } + return String(output); +}; + +export function RunnerOutputUI({ + isOpen, + onClose, + blockOutputs, +}: OutputModalProps) { + return ( + + + + Run Outputs + + View the outputs from your agent run. + + +
+ +
+ {blockOutputs && blockOutputs.length > 0 ? ( + blockOutputs.map((block) => ( +
+ + + {block.hardcodedValues.description && ( + + )} + +
+