fix(builder/monitor): Fix Graph export (#7556)

- fix(builder/monitor): Export `Graph` rather than `GraphMeta`
  - Fixes #7557

- refactor(builder): Split up `lib/autogpt_server_api` into multi-file module
  - Resolves #7555
  - Rename `lib/autogpt_server_api` to `lib/autogpt-server-api`
  - Split up `lib/autogpt-server-api` into `/client`, `/types`
  - Move `ObjectSchema` from `lib/types` to `lib/autogpt-server-api/types`
  - Make definition of `Node['metadata']['position']` independent of `reactflow.XYPosition`

- fix(builder/monitor): Strip secrets from graph on export
  - Resolves #7492
  - Add `safeCopyGraph` function in `lib/autogpt-server-api/utils`
  - Use `safeCopyGraph` to strip secrets from graph on export in `/monitor` > `FlowInfo`
This commit is contained in:
Reinier van der Leer
2024-07-23 09:28:06 +02:00
committed by GitHub
parent d407fd101e
commit ab0df04bfe
8 changed files with 149 additions and 106 deletions

View File

@@ -23,8 +23,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import AutoGPTServerAPI, {
Graph,
GraphMeta,
NodeExecutionResult,
safeCopyGraph,
} from '@/lib/autogpt-server-api';
import { ChevronDownIcon, ClockIcon, EnterIcon, ExitIcon, Pencil2Icon } from '@radix-ui/react-icons';
import AutoGPTServerAPI, { GraphMeta, NodeExecutionResult } from '@/lib/autogpt_server_api';
import { cn, exportAsJSONFile, hashString } from '@/lib/utils';
import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
@@ -400,8 +405,11 @@ const FlowInfo: React.FC<React.HTMLAttributes<HTMLDivElement> & {
}> = ({ flow, flowRuns, flowVersion, ...props }) => {
const api = new AutoGPTServerAPI();
const [flowVersions, setFlowVersions] = useState<GraphMeta[] | null>(null);
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
const [selectedVersion, setSelectedFlowVersion] = useState(flowVersion ?? "all");
const selectedFlowVersion: Graph | undefined = flowVersions?.find(v => (
v.version == (selectedVersion == "all" ? flow.version : selectedVersion)
));
useEffect(() => {
api.getGraphAllVersions(flow.id).then(result => setFlowVersions(result));
@@ -449,7 +457,13 @@ const FlowInfo: React.FC<React.HTMLAttributes<HTMLDivElement> & {
variant="outline"
className="px-2.5"
title="Export to a JSON-file"
onClick={() => exportAsJSONFile(flow, `${flow.name}_v${flow.version}.json`)}
onClick={async () => exportAsJSONFile(
safeCopyGraph(
flowVersions!.find(v => v.version == selectedFlowVersion!.version)!,
await api.getBlocks(),
),
`${flow.name}_v${selectedFlowVersion!.version}.json`
)}
>
<ExitIcon />
</Button>
@@ -457,11 +471,7 @@ const FlowInfo: React.FC<React.HTMLAttributes<HTMLDivElement> & {
</CardHeader>
<CardContent>
<FlowRunsStats
flows={[
selectedVersion != "all"
? flowVersions?.find(v => v.version == selectedVersion)!
: flow
]}
flows={[selectedFlowVersion ?? flow]}
flowRuns={flowRuns.filter(r =>
r.graphID == flow.id
&& (selectedVersion == "all" || r.graphVersion == selectedVersion)

View File

@@ -15,8 +15,7 @@ import ReactFlow, {
import 'reactflow/dist/style.css';
import CustomNode from './CustomNode';
import './flow.css';
import AutoGPTServerAPI, { Block, Graph } from '@/lib/autogpt_server_api';
import { ObjectSchema } from '@/lib/types';
import AutoGPTServerAPI, { Block, Graph, ObjectSchema } from '@/lib/autogpt-server-api';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { ChevronRight, ChevronLeft } from "lucide-react";

View File

@@ -14,7 +14,7 @@ import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import AutoGPTServerAPI, { Graph, GraphCreatable } from "@/lib/autogpt_server_api"
import AutoGPTServerAPI, { Graph, GraphCreatable } from "@/lib/autogpt-server-api"
import { cn } from "@/lib/utils"
import { EnterIcon } from "@radix-ui/react-icons"

View File

@@ -1,5 +1,12 @@
import { XYPosition } from "reactflow";
import { ObjectSchema } from "./types";
import {
Block,
Graph,
GraphCreatable,
GraphUpdateable,
GraphMeta,
GraphExecuteResponse,
NodeExecutionResult,
} from "./types"
export default class AutoGPTServerAPI {
private baseUrl: string;
@@ -196,96 +203,12 @@ export default class AutoGPTServerAPI {
}
}
/* Mirror of autogpt_server/data/block.py:Block */
export type Block = {
id: string;
name: string;
description: string;
inputSchema: ObjectSchema;
outputSchema: ObjectSchema;
};
/* Mirror of autogpt_server/data/graph.py:Node */
export type Node = {
id: string;
block_id: string;
input_default: { [key: string]: any };
input_nodes: Array<{ name: string, node_id: string }>;
output_nodes: Array<{ name: string, node_id: string }>;
metadata: {
position: XYPosition;
[key: string]: any;
};
};
/* *** UTILITY TYPES *** */
/* Mirror of autogpt_server/data/graph.py:Link */
export type Link = {
id: string;
source_id: string;
sink_id: string;
source_name: string;
sink_name: string;
}
export type LinkCreatable = Omit<Link, "id"> & {
id?: string;
}
/* Mirror of autogpt_server/data/graph.py:GraphMeta */
export type GraphMeta = {
id: string;
version: number;
is_active: boolean;
is_template: boolean;
name: string;
description: string;
}
/* Mirror of autogpt_server/data/graph.py:Graph */
export type Graph = GraphMeta & {
nodes: Array<Node>;
links: Array<Link>;
};
export type GraphUpdateable = Omit<
Graph,
"version" | "is_active" | "is_template" | "links"
> & {
version?: number;
is_active?: boolean;
is_template?: boolean;
links: Array<LinkCreatable>;
}
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string }
export type GraphCreateRequestBody = {
type GraphCreateRequestBody = {
template_id: string;
template_version: number;
} | {
graph: GraphCreatable;
}
/* Derived from autogpt_server/executor/manager.py:ExecutionManager.add_execution */
export type GraphExecuteResponse = {
/* ID of the initiated run */
id: string;
/* List of node executions */
executions: Array<{ id: string, node_id: string }>;
};
/* Mirror of autogpt_server/data/execution.py:ExecutionResult */
export type NodeExecutionResult = {
graph_exec_id: string;
node_exec_id: string;
graph_id: string;
graph_version: number;
node_id: string;
status: 'INCOMPLETE' | 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
input_data: { [key: string]: any };
output_data: { [key: string]: Array<any> };
add_time: Date;
queue_time?: Date;
start_time?: Date;
end_time?: Date;
};

View File

@@ -0,0 +1,5 @@
import AutoGPTServerAPI from "./client";
export default AutoGPTServerAPI;
export * from "./types";
export * from "./utils";

View File

@@ -0,0 +1,93 @@
/* Mirror of autogpt_server/data/block.py:Block */
export type Block = {
id: string;
name: string;
description: string;
inputSchema: ObjectSchema;
outputSchema: ObjectSchema;
};
export type ObjectSchema = {
type: string;
properties: { [key: string]: any };
additionalProperties?: { type: string };
required?: string[];
};
/* Mirror of autogpt_server/data/graph.py:Node */
export type Node = {
id: string;
block_id: string;
input_default: { [key: string]: any };
input_nodes: Array<{ name: string, node_id: string }>;
output_nodes: Array<{ name: string, node_id: string }>;
metadata: {
position: { x: number; y: number; };
[key: string]: any;
};
};
/* Mirror of autogpt_server/data/graph.py:Link */
export type Link = {
id: string;
source_id: string;
sink_id: string;
source_name: string;
sink_name: string;
}
export type LinkCreatable = Omit<Link, "id"> & {
id?: string;
}
/* Mirror of autogpt_server/data/graph.py:GraphMeta */
export type GraphMeta = {
id: string;
version: number;
is_active: boolean;
is_template: boolean;
name: string;
description: string;
}
/* Mirror of autogpt_server/data/graph.py:Graph */
export type Graph = GraphMeta & {
nodes: Array<Node>;
links: Array<Link>;
};
export type GraphUpdateable = Omit<
Graph,
"version" | "is_active" | "is_template" | "links"
> & {
version?: number;
is_active?: boolean;
is_template?: boolean;
links: Array<LinkCreatable>;
}
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string }
/* Derived from autogpt_server/executor/manager.py:ExecutionManager.add_execution */
export type GraphExecuteResponse = {
/** ID of the initiated run */
id: string;
/** List of node executions */
executions: Array<{ id: string, node_id: string }>;
};
/* Mirror of autogpt_server/data/execution.py:ExecutionResult */
export type NodeExecutionResult = {
graph_exec_id: string;
node_exec_id: string;
graph_id: string;
graph_version: number;
node_id: string;
status: 'INCOMPLETE' | 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
input_data: { [key: string]: any };
output_data: { [key: string]: Array<any> };
add_time: Date;
queue_time?: Date;
start_time?: Date;
end_time?: Date;
};

View File

@@ -0,0 +1,20 @@
import { Graph, Block, Node } from "./types";
/** Creates a copy of the graph with all secrets removed */
export function safeCopyGraph(graph: Graph, block_defs: Block[]): Graph {
return {
...graph,
nodes: graph.nodes.map(node => {
const block = block_defs.find(b => b.id == node.block_id)!;
return {
...node,
input_default: Object.keys(node.input_default)
.filter(k => !block.inputSchema.properties[k].secret)
.reduce((obj: Node['input_default'], key) => {
obj[key] = node.input_default[key];
return obj;
}, {}),
}
}),
}
}

View File

@@ -1,10 +1,3 @@
export type ObjectSchema = {
type: string;
properties: { [key: string]: any };
additionalProperties?: { type: string };
required?: string[];
};
export type BlockSchema = {
type: string;
properties: { [key: string]: any };