Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Small workflow updates #1018

Merged
merged 32 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1a67336
✨ Sort
asim-shrestha Jul 10, 2023
ae519d5
✨ Link
asim-shrestha Jul 10, 2023
d706d28
✨ Init
asim-shrestha Jul 10, 2023
f3910b6
✨ Update schema
asim-shrestha Jul 10, 2023
ebe5eea
✨ Status Check
asim-shrestha Jul 10, 2023
ad262e0
✨ Update
asim-shrestha Jul 10, 2023
454c9ce
✨ Hide rightside bar if not defined
asim-shrestha Jul 10, 2023
1cda3db
✨ Add workflow dashboard
asim-shrestha Jul 10, 2023
5d5ab7b
✨ Make right sidebar dynamic in dashboard
asim-shrestha Jul 10, 2023
0d17647
✨ Fix index dashboard
asim-shrestha Jul 10, 2023
6ec43fd
✨ Update AuthItem
asim-shrestha Jul 10, 2023
65f31c5
✨ Add Workflow side bar
asim-shrestha Jul 10, 2023
52bdf58
Merge branch 'main' into exec_3
asim-shrestha Jul 11, 2023
f5cef65
✨ Remove links
asim-shrestha Jul 11, 2023
6413d7a
✨ Fix mypy
asim-shrestha Jul 11, 2023
8d25667
✨ Fix mypy
asim-shrestha Jul 11, 2023
9e00b6c
✨ Update prisma schema
asim-shrestha Jul 11, 2023
5de604e
✨ FIX
asim-shrestha Jul 11, 2023
e6df2c7
✨ Update schemas
asim-shrestha Jul 11, 2023
8396efd
✨ Fix URL check types
asim-shrestha Jul 11, 2023
94eac7d
✨ Add workflow docs
asim-shrestha Jul 11, 2023
cc49ba7
✨ Remove super
asim-shrestha Jul 11, 2023
1a79225
✨ Update
asim-shrestha Jul 11, 2023
8fd4f26
✨ Update
asim-shrestha Jul 11, 2023
38f73c1
✨ Update useSqlite
asim-shrestha Jul 11, 2023
4fa7428
✨ Update definition
asim-shrestha Jul 11, 2023
4662530
✨ Curry createNode
asim-shrestha Jul 11, 2023
e34a717
✨ Front end display node block types
asim-shrestha Jul 11, 2023
ae3e9ef
✨ Front end display node block types
asim-shrestha Jul 11, 2023
8448c71
Merge branch 'main' into exec_3
asim-shrestha Jul 12, 2023
f579673
✨ Fix build issues
asim-shrestha Jul 12, 2023
e69ca28
✨ Fix sidebar
asim-shrestha Jul 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/development/workflows.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
asim-shrestha marked this conversation as resolved.
Show resolved Hide resolved
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".
15 changes: 14 additions & 1 deletion next/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion next/prisma/useSqlite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Binary file added next/public/tools/web.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions next/src/components/drawer/WorkflowSidebar.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<WorkflowSidebar show={show} setShow={setShow} createNode={createNode} />
);
WorkflowSidebarHOC.displayName = "WorkflowSidebarHOC";
return WorkflowSidebarHOC;
};

const WorkflowSidebar = ({ show, setShow, createNode }: WorkflowSidebarProps) => {
return (
<Sidebar show={show} setShow={setShow} side="right">
<div className="text-color-primary flex h-screen flex-col gap-2">
<div className="flex flex-row items-center gap-1">
<button
className="neutral-button-primary rounded-md border-none transition-all"
onClick={() => setShow(!show)}
>
<FaBars size="15" className="z-20 m-2" />
</button>
<div className="ml-5 font-bold">Block</div>
</div>
{getNodeBlockDefinitions().map((nodeBlockDefinition) => (
<NodeBlock
key={nodeBlockDefinition.type}
definition={nodeBlockDefinition}
createNode={createNode}
/>
))}
</div>
</Sidebar>
);
};

type NodeBlockProps = {
definition: NodeBlockDefinition;
createNode: createNodeType;
};
const NodeBlock = ({ definition, createNode }: NodeBlockProps) => {
return (
<div
className="flex cursor-pointer flex-row gap-2 rounded-md border border-white/20 p-2 hover:bg-white/10"
onClick={() => createNode(definition)}
>
<div className="h-[30px] w-[30px]">
<img src={definition.image_url} alt={definition.type} width={30} />
</div>
<div>
<h3 className="font-medium">{definition.type}</h3>
<p className="text-sm font-thin">{definition.description}</p>
</div>
</div>
);
};
3 changes: 2 additions & 1 deletion next/src/components/workflow/BasicNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ function BasicNode({ data }: NodeProps<WorkflowNode>) {
>
<div className="flex items-center">
<div className="ml-2">
<div className="text-lg font-bold dark:text-gray-100">{data.ref.substr(0, 4)}</div>
<div className="text-lg font-bold dark:text-gray-100">{data.block.type}</div>
<div className="text-md font-thin">{data.block.description}</div>
</div>
</div>

Expand Down
7 changes: 6 additions & 1 deletion next/src/hooks/useWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -77,7 +78,7 @@ export const useWorkflow = (workflowId: string) => {
);
});

const createNode = () => {
const createNode: createNodeType = (block: NodeBlockDefinition) => {
const ref = nanoid(11);

setNodes((nodes) => [
Expand All @@ -91,6 +92,7 @@ export const useWorkflow = (workflowId: string) => {
ref: ref,
pos_x: 0,
pos_y: 0,
block: block,
},
},
]);
Expand All @@ -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,
Expand All @@ -122,3 +125,5 @@ export const useWorkflow = (workflowId: string) => {
createNode,
};
};

export type createNodeType = (block: NodeBlockDefinition) => void;
67 changes: 38 additions & 29 deletions next/src/layout/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -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 });

Expand Down Expand Up @@ -64,37 +69,41 @@ const DashboardLayout = (props: PropsWithChildren) => {

{/* Right sidebar */}
{/* Mobile */}
<TaskSidebar
show={rightSettings.mobile}
setShow={setMobile(rightSettings, setRightSettings)}
/>
<div className={rightSettings.mobile ? "hidden" : "lg:hidden"}>
<SidebarControlButton
side="right"
show={rightSettings.mobile}
setShow={setMobile(rightSettings, setRightSettings)}
/>
</div>
{/* Desktop sidebar */}
<div className="hidden lg:visible lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
<TaskSidebar
show={rightSettings.desktop}
setShow={setDesktop(rightSettings, setRightSettings)}
/>
</div>
<div className={rightSettings.desktop ? "hidden" : "hidden lg:block"}>
<SidebarControlButton
side="right"
show={rightSettings.desktop}
setShow={setDesktop(rightSettings, setRightSettings)}
/>
</div>
{props.rightSidebar && (
<>
<props.rightSidebar
show={rightSettings.mobile}
setShow={setMobile(rightSettings, setRightSettings)}
/>
<div className={rightSettings.mobile ? "hidden" : "lg:hidden"}>
<SidebarControlButton
side="right"
show={rightSettings.mobile}
setShow={setMobile(rightSettings, setRightSettings)}
/>
</div>
{/* Desktop sidebar */}
<div className="hidden lg:visible lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
<props.rightSidebar
show={rightSettings.desktop}
setShow={setDesktop(rightSettings, setRightSettings)}
/>
</div>
<div className={rightSettings.desktop ? "hidden" : "hidden lg:block"}>
<SidebarControlButton
side="right"
show={rightSettings.desktop}
setShow={setDesktop(rightSettings, setRightSettings)}
/>
</div>
</>
)}

<main
className={clsx(
"bg-gradient-to-b from-[#2B2B2B] to-[#1F1F1F] duration-300",
leftSettings.desktop && "lg:pl-64",
rightSettings.desktop && "lg:pr-64"
props.rightSidebar && rightSettings.desktop && "lg:pr-64"
)}
>
<DottedGridBackground className="min-w-screen min-h-screen">
Expand Down
3 changes: 2 additions & 1 deletion next/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -158,7 +159,7 @@ const Home: NextPage = () => {
};

return (
<DashboardLayout>
<DashboardLayout rightSidebar={TaskSidebar}>
<HelpDialog />
<ToolsDialog show={showToolsDialog} close={() => setShowToolsDialog(false)} />

Expand Down
11 changes: 2 additions & 9 deletions next/src/pages/workflow/[workflow].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -18,7 +19,7 @@ const WorkflowPage: NextPage = () => {
);

return (
<DashboardLayout>
<DashboardLayout rightSidebar={getWorkflowSidebar(createNode)}>
<FlowChart
controls={true}
nodesModel={nodesModel}
Expand All @@ -27,14 +28,6 @@ const WorkflowPage: NextPage = () => {
/>
<div className="relative h-full w-full">
<div className="absolute bottom-4 right-4 flex flex-row items-center justify-center gap-2">
<Button
className="rounded-md bg-purple-600 px-4 py-2 font-medium text-white transition-colors duration-150 hover:bg-purple-700"
onClick={() => {
createNode();
}}
>
New
</Button>
<Button
className="rounded-md bg-purple-600 px-4 py-2 font-medium text-white transition-colors duration-150 hover:bg-purple-700"
onClick={async () => {
Expand Down
19 changes: 19 additions & 0 deletions next/src/services/workflow/node-block-definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

export const NodeBlockDefinitionSchema = z.object({
type: z.string(),
description: z.string(),
image_url: z.string(),
});

export type NodeBlockDefinition = z.infer<typeof NodeBlockDefinitionSchema>;

const UrlStatusCheckBlockDefinition: NodeBlockDefinition = {
type: "UrlStatusCheck",
description: "Check the status of a URL",
image_url: "/tools/web.png",
};

export const getNodeBlockDefinitions = () => {
return [UrlStatusCheckBlockDefinition];
};
5 changes: 5 additions & 0 deletions next/src/types/workflow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { z } from "zod";
import type { Edge, Node } from "reactflow";
import type { Dispatch, SetStateAction } from "react";
import {
NodeBlockDefinition,
NodeBlockDefinitionSchema,
} from "../services/workflow/node-block-definitions";

type Model<T> = [T, Dispatch<SetStateAction<T>>];

Expand All @@ -10,6 +14,7 @@ const WorkflowNodeSchema = z.object({
pos_x: z.number(),
pos_y: z.number(),
status: z.enum(["running", "success", "failure"]).optional(),
block: NodeBlockDefinitionSchema,
});

const WorkflowEdgeSchema = z.object({
Expand Down
8 changes: 4 additions & 4 deletions platform/reworkd_platform/db/models/workflow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from sqlalchemy import String, Float, ForeignKey
from sqlalchemy.orm import relationship, mapped_column
from sqlalchemy import Float, ForeignKey, String
from sqlalchemy.orm import mapped_column, relationship

from reworkd_platform.db.base import TrackedModel, UserMixin, Base
from reworkd_platform.web.api.workflow.schemas import Workflow, Node, Edge
from reworkd_platform.db.base import Base, TrackedModel, UserMixin
from reworkd_platform.web.api.workflow.schemas import Edge, Node, Workflow


class WorkflowModel(TrackedModel, UserMixin):
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Optional

import requests
from requests import RequestException

from reworkd_platform.web.api.workflow.schemas import Block, BlockIOBase


class UrlStatusCheckBlockInput(BlockIOBase):
url: str


class UrlStatusCheckBlockOutput(BlockIOBase):
code: Optional[int]


class UrlStatusCheckBlock(Block):
type = "UrlStatusCheckNode"
description = "Outputs the status code of a GET request to a URL"
image_url = ""
input_config: UrlStatusCheckBlockInput

def run(self) -> BlockIOBase:
try:
response = requests.get(self.input_config.url)
code = response.status_code
except RequestException:
code = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to log something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we need better logging throughout


output = UrlStatusCheckBlockOutput(code=code)
return output
Loading