diff --git a/docs/development/workflows.mdx b/docs/development/workflows.mdx new file mode 100644 index 0000000000..75de6e9562 --- /dev/null +++ b/docs/development/workflows.mdx @@ -0,0 +1,20 @@ +--- +title: "Workflows" +description: The workflow automation system within AgentGPT. +icon: "wave-sine" +--- + +A core function of Reworkd is AI powered workflow automation. This documentation covers key concepts within our workflow platform. + +## Frontend models +The workflow hierarchy follows a graph-like structure. The frontend models only prescribe the front-end view of the workflow. + +- A workflow is the graph itself. It represents the workflow in its entirety +- A node is a single element within a workflow. It has a position +- An edge represents a connection between two nodes of a workflow + +## Backend models +The backend models represent the mechanisms to actually perform work for a given node. +Each frontend `Node` will have an associated `Block`. +`Node` represents the frontend view / position while the `Block` represents what will actually happen when that `Node` is run. +For example, a "SlackMessageBlock" is a `Block` that, when executed, would send a user a message on "Slack". \ No newline at end of file diff --git a/next/prisma/schema.prisma b/next/prisma/schema.prisma index d24c78f6bc..5f35fadfb8 100644 --- a/next/prisma/schema.prisma +++ b/next/prisma/schema.prisma @@ -170,8 +170,21 @@ model WorkflowNode { update_date DateTime? @updatedAt delete_date DateTime? - workflow Workflow @relation(fields: [workflow_id], references: [id], onDelete: Cascade) + workflow Workflow @relation(fields: [workflow_id], references: [id], onDelete: Cascade) + NodeBlock NodeBlock[] @@unique([workflow_id, ref]) @@map("workflow_node") } + +model NodeBlock { + id String @id @default(cuid()) + workflow_node_id String + + type String + input Json + + workflow_node WorkflowNode @relation(fields: [workflow_node_id], references: [id], onDelete: Cascade) + + @@map("node_block") +} diff --git a/next/prisma/useSqlite.sh b/next/prisma/useSqlite.sh index 6c36e97953..a6c8fec0a3 100755 --- a/next/prisma/useSqlite.sh +++ b/next/prisma/useSqlite.sh @@ -5,4 +5,4 @@ cp schema.prisma schema.prisma.mysql sed -ie 's/mysql/sqlite/g' schema.prisma sed -ie 's/@db.Text//' schema.prisma sed -ie 's/@db.VarChar([0-9]\{1,\})//' schema.prisma - +sed -ie 's/Json/String/g' schema.prisma diff --git a/next/public/tools/web.png b/next/public/tools/web.png new file mode 100644 index 0000000000..4082aa20c9 Binary files /dev/null and b/next/public/tools/web.png differ diff --git a/next/src/components/drawer/WorkflowSidebar.tsx b/next/src/components/drawer/WorkflowSidebar.tsx new file mode 100644 index 0000000000..d308b21f71 --- /dev/null +++ b/next/src/components/drawer/WorkflowSidebar.tsx @@ -0,0 +1,68 @@ +import type { DisplayProps } from "./Sidebar"; +import Sidebar from "./Sidebar"; +import React from "react"; +import { FaBars } from "react-icons/fa"; +import type { NodeBlockDefinition } from "../../services/workflow/node-block-definitions"; +import { getNodeBlockDefinitions } from "../../services/workflow/node-block-definitions"; +import type { createNodeType } from "../../hooks/useWorkflow"; + +type WorkflowControls = { + createNode: createNodeType; +}; + +type WorkflowSidebarProps = DisplayProps & WorkflowControls; + +// Wrapper HOC to curry the createNode function +export const getWorkflowSidebar = (createNode: createNodeType) => { + const WorkflowSidebarHOC = ({ show, setShow }: DisplayProps) => ( + + ); + WorkflowSidebarHOC.displayName = "WorkflowSidebarHOC"; + return WorkflowSidebarHOC; +}; + +const WorkflowSidebar = ({ show, setShow, createNode }: WorkflowSidebarProps) => { + return ( + +
+
+ +
Block
+
+ {getNodeBlockDefinitions().map((nodeBlockDefinition) => ( + + ))} +
+
+ ); +}; + +type NodeBlockProps = { + definition: NodeBlockDefinition; + createNode: createNodeType; +}; +const NodeBlock = ({ definition, createNode }: NodeBlockProps) => { + return ( +
createNode(definition)} + > +
+ {definition.type} +
+
+

{definition.type}

+

{definition.description}

+
+
+ ); +}; diff --git a/next/src/components/workflow/BasicNode.tsx b/next/src/components/workflow/BasicNode.tsx index 9c580738c7..965aa5842e 100644 --- a/next/src/components/workflow/BasicNode.tsx +++ b/next/src/components/workflow/BasicNode.tsx @@ -17,7 +17,8 @@ function BasicNode({ data }: NodeProps) { >
-
{data.ref.substr(0, 4)}
+
{data.block.type}
+
{data.block.description}
diff --git a/next/src/hooks/useWorkflow.ts b/next/src/hooks/useWorkflow.ts index acf2ac7247..634bb4ba17 100644 --- a/next/src/hooks/useWorkflow.ts +++ b/next/src/hooks/useWorkflow.ts @@ -8,6 +8,7 @@ import { toReactFlowEdge, toReactFlowNode } from "../types/workflow"; import WorkflowApi from "../services/workflow/workflowApi"; import useSocket from "./useSocket"; import { z } from "zod"; +import type { NodeBlockDefinition } from "../services/workflow/node-block-definitions"; const eventSchema = z.object({ nodeId: z.string(), @@ -77,7 +78,7 @@ export const useWorkflow = (workflowId: string) => { ); }); - const createNode = () => { + const createNode: createNodeType = (block: NodeBlockDefinition) => { const ref = nanoid(11); setNodes((nodes) => [ @@ -91,6 +92,7 @@ export const useWorkflow = (workflowId: string) => { ref: ref, pos_x: 0, pos_y: 0, + block: block, }, }, ]); @@ -103,6 +105,7 @@ export const useWorkflow = (workflowId: string) => { ref: n.data.ref, pos_x: n.position.x, pos_y: n.position.y, + block: n.data.block, })), edges: edges.map((e) => ({ id: e.id, @@ -122,3 +125,5 @@ export const useWorkflow = (workflowId: string) => { createNode, }; }; + +export type createNodeType = (block: NodeBlockDefinition) => void; diff --git a/next/src/layout/dashboard.tsx b/next/src/layout/dashboard.tsx index 7babf57516..57575fbf59 100644 --- a/next/src/layout/dashboard.tsx +++ b/next/src/layout/dashboard.tsx @@ -1,18 +1,23 @@ -import type { PropsWithChildren } from "react"; +import type { ReactNode } from "react"; import { useState } from "react"; import clsx from "clsx"; import DottedGridBackground from "../components/DottedGridBackground"; import AppHead from "../components/AppHead"; import { useTheme } from "../hooks/useTheme"; import LeftSidebar from "../components/drawer/LeftSidebar"; +import type { DisplayProps } from "../components/drawer/Sidebar"; import { SidebarControlButton } from "../components/drawer/Sidebar"; -import TaskSidebar from "../components/drawer/TaskSidebar"; type SidebarSettings = { mobile: boolean; desktop: boolean; }; -const DashboardLayout = (props: PropsWithChildren) => { + +type DashboardLayoutProps = { + children: ReactNode; + rightSidebar?: ({ show, setShow }: DisplayProps) => JSX.Element; +}; +const DashboardLayout = (props: DashboardLayoutProps) => { const [leftSettings, setLeftSettings] = useState({ mobile: false, desktop: true }); const [rightSettings, setRightSettings] = useState({ mobile: false, desktop: true }); @@ -64,37 +69,41 @@ const DashboardLayout = (props: PropsWithChildren) => { {/* Right sidebar */} {/* Mobile */} - -
- -
- {/* Desktop sidebar */} -
- -
-
- -
+ {props.rightSidebar && ( + <> + +
+ +
+ {/* Desktop sidebar */} +
+ +
+
+ +
+ + )}
diff --git a/next/src/pages/index.tsx b/next/src/pages/index.tsx index c93a9483a9..bcca8dabf3 100644 --- a/next/src/pages/index.tsx +++ b/next/src/pages/index.tsx @@ -40,6 +40,7 @@ import Summarize from "../components/console/SummarizeButton"; import AgentControls from "../components/console/AgentControls"; import { ChatMessage } from "../components/console/ChatMessage"; import clsx from "clsx"; +import TaskSidebar from "../components/drawer/TaskSidebar"; const Home: NextPage = () => { const { t } = useTranslation("indexPage"); @@ -158,7 +159,7 @@ const Home: NextPage = () => { }; return ( - + setShowToolsDialog(false)} /> diff --git a/next/src/pages/workflow/[workflow].tsx b/next/src/pages/workflow/[workflow].tsx index 28edaf502b..c4957e4547 100644 --- a/next/src/pages/workflow/[workflow].tsx +++ b/next/src/pages/workflow/[workflow].tsx @@ -9,6 +9,7 @@ import Button from "../../ui/button"; import { languages } from "../../utils/languages"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import nextI18NextConfig from "../../../next-i18next.config"; +import { getWorkflowSidebar } from "../../components/drawer/WorkflowSidebar"; const WorkflowPage: NextPage = () => { const router = useRouter(); @@ -18,7 +19,7 @@ const WorkflowPage: NextPage = () => { ); return ( - + { />
-