Skip to content

Commit

Permalink
Merge pull request giselles-ai#228 from toyamarinyon/subgraph2
Browse files Browse the repository at this point in the history
[feat] Implement graph execution engine with real-time tracking and viewer UI
  • Loading branch information
toyamarinyon authored Dec 13, 2024
2 parents d2bdbe6 + ff47164 commit 1030098
Show file tree
Hide file tree
Showing 25 changed files with 1,869 additions and 220 deletions.
2 changes: 1 addition & 1 deletion app/(main)/agents-v2/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createId } from "@paralleldrive/cuid2";
import { redirect } from "next/navigation";
import type { ReactNode } from "react";
import { putGraph } from "../../(playground)/p/[agentId]/canary/actions";
import { initGraph } from "../../(playground)/p/[agentId]/canary/utils";
import { initGraph } from "../../(playground)/p/[agentId]/canary/lib/utils";
import { CreateAgentButton } from "./components";

export default function Layout({
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/agents-v2/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fetchCurrentTeam } from "@/services/teams";
import { and, eq, isNotNull } from "drizzle-orm";
import Link from "next/link";
import { type ReactNode, Suspense } from "react";
import { formatTimestamp } from "../../(playground)/p/[agentId]/canary/utils";
import { formatTimestamp } from "../../(playground)/p/[agentId]/canary/lib/utils";

function DataList({ label, children }: { label: string; children: ReactNode }) {
return (
Expand Down
16 changes: 8 additions & 8 deletions app/(playground)/p/[agentId]/canary/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import { UnstructuredClient } from "unstructured-client";
import { Strategy } from "unstructured-client/sdk/models/shared";
import * as v from "valibot";
import { vercelBlobFileFolder, vercelBlobGraphFolder } from "./constants";
import { textGenerationPrompt } from "./prompts";
import { textGenerationPrompt } from "./lib/prompts";
import {
buildGraphPath,
elementsToMarkdown,
langfuseModel,
pathJoin,
toErrorWithMessage,
} from "./lib/utils";
import type {
AgentId,
ArtifactId,
Expand All @@ -29,13 +36,6 @@ import type {
TextArtifactObject,
TextGenerateActionContent,
} from "./types";
import {
buildGraphPath,
elementsToMarkdown,
langfuseModel,
pathJoin,
toErrorWithMessage,
} from "./utils";

function resolveLanguageModel(
llm: TextGenerateActionContent["llm"],
Expand Down
7 changes: 6 additions & 1 deletion app/(playground)/p/[agentId]/canary/components/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import { useMousePosition } from "../contexts/mouse-position";
import { usePropertiesPanel } from "../contexts/properties-panel";
import { useToast } from "../contexts/toast";
import { useToolbar } from "../contexts/toolbar";
import { createNodeId, isTextGeneration } from "../lib/utils";
import type { NodeId, Tool } from "../types";
import { createNodeId, isTextGeneration } from "../utils";
import { Edge } from "./edge";
import { Header } from "./header";
import { KeyboardShortcut } from "./keyboard-shortcut";
import { NavigationPanel } from "./navigation-panel";
import { Node, PreviewNode } from "./node";
Expand Down Expand Up @@ -297,6 +298,10 @@ export function Editor() {
backgroundSize: "cover",
}}
/>

<Panel position="top-left" className="!top-0 !left-0 !right-0 !m-0">
<Header />
</Panel>
<Panel position="top-right" className="!top-0 !bottom-0 !right-0 !m-0">
<PropertiesPanel />
</Panel>
Expand Down
85 changes: 85 additions & 0 deletions app/(playground)/p/[agentId]/canary/components/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { GiselleLogo } from "@/components/giselle-logo";
import Link from "next/link";
import type { ReactNode } from "react";
import { useExecution } from "../contexts/execution";
import { useGraph } from "../contexts/graph";
import {
type PlaygroundMode,
usePlaygroundMode,
} from "../contexts/playground-mode";
// import { RunButton } from "./flow/components/run-button";
import { SparklesIcon } from "./icons/sparkles";
import { Button } from "./ui/button";

function SelectionIndicator() {
return (
<svg
width="70"
height="32"
viewBox="0 0 70 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<title>Selection Indicator</title>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M67.1402 12.1274C66.9421 12.4761 66.7105 12.8341 66.4438 13.2011C65.1133 15.0319 62.9944 16.9585 60.173 18.8432C54.5383 22.6072 46.3748 26.0225 37.0232 28.1281C36.7764 28.1837 36.53 28.238 36.2841 28.2912C35.9438 28.2992 35.602 28.3053 35.259 28.3094C25.7364 28.4229 17.1525 26.9771 10.9859 24.6019C7.89837 23.4127 5.49151 22.019 3.87301 20.5357C3.55344 20.2428 3.26861 19.9504 3.01705 19.6591C3.23284 19.2627 3.49226 18.8542 3.79778 18.4338C5.12826 16.603 7.2472 14.6764 10.0685 12.7917C15.7032 9.02772 23.8668 5.6124 33.2183 3.50685C33.5357 3.4354 33.8522 3.36595 34.1678 3.29849C34.3585 3.29497 34.5495 3.29207 34.741 3.28979C44.2635 3.17628 52.8475 4.62209 59.0141 6.9973C62.1016 8.18652 64.5085 9.58017 66.127 11.0635C66.5151 11.4192 66.852 11.7741 67.1402 12.1274ZM68.0138 13.4742C64.5805 18.7398 55.682 24.1517 44.2965 27.7203C50.1314 27.0137 55.3006 25.7153 59.3667 24.0252C62.4034 22.763 64.7514 21.3127 66.3077 19.7915C67.8608 18.2733 68.5276 16.7899 68.4989 15.4003C68.4859 14.7721 68.33 14.1281 68.0138 13.4742ZM36.4479 29.7878C55.2361 29.3192 70.1557 22.9646 69.9988 15.3824C69.9741 14.1916 69.579 13.0398 68.8575 11.9442C69.4501 10.6239 69.6744 9.3254 69.482 8.08082C68.3259 0.599797 52.5477 -2.14952 34.0078 1.80124C15.0058 2.17748 -0.157012 8.57345 0.00122734 16.2168C0.0276145 17.4913 0.478388 18.7211 1.29942 19.8851C0.770428 21.1384 0.576644 22.3707 0.75953 23.5541C1.92009 31.0639 17.8155 33.8055 36.4479 29.7878ZM28.3757 29.6186C16.4759 28.8081 6.69877 25.564 2.38267 21.1723C2.17159 21.9076 2.12519 22.5923 2.2229 23.2246C2.43309 24.5848 3.33787 25.8811 5.12281 27.0079C6.91063 28.1365 9.46253 29.0115 12.6571 29.5417C17.0099 30.2641 22.4122 30.3228 28.3757 29.6186ZM2.10372 18.3548C1.70844 17.6219 1.51565 16.9006 1.50112 16.1989C1.47235 14.8093 2.13917 13.3259 3.69231 11.8077C5.24861 10.2865 7.5966 8.8362 10.6333 7.57399C14.8317 5.82891 20.2063 4.50135 26.2755 3.81179C14.5914 7.42214 5.47462 12.9776 2.10372 18.3548ZM41.9658 2.00462C47.8897 1.31276 53.256 1.37486 57.5845 2.09323C60.779 2.62339 63.3309 3.49837 65.1188 4.62703C66.9037 5.75386 67.8085 7.05015 68.0187 8.41031C68.1236 9.0894 68.0623 9.82899 67.8093 10.6269C63.6361 6.17621 53.8903 2.86638 41.9658 2.00462Z"
fill="#BAC6D0"
/>
</svg>
);
}

interface ModeButtonProps {
children: ReactNode;
mode: PlaygroundMode;
selected?: boolean;
}
function ModeButton(props: ModeButtonProps) {
const { playgroundMode, setPlaygroundMode } = usePlaygroundMode();
return (
<button
type="button"
className="px-[16px] uppercase font-bold text-[14px] relative"
onClick={() => setPlaygroundMode(props.mode)}
>
{props.children}
{playgroundMode === props.mode && (
<div className="absolute left-0 -top-[6px]">
<SelectionIndicator />
</div>
)}
</button>
);
}

export function Header() {
const { graph } = useGraph();
const { executeFlow } = useExecution();
return (
<div className="h-[60px] flex items-center justify-between mx-[20px]">
<div className="flex gap-[8px] items-center flex-1">
<Link href="/">
<GiselleLogo className="fill-white w-[70px] h-auto mt-[6px]" />
</Link>
<div className="font-rosart text-[18px] text-black--30">Playground</div>
</div>
<div className="flex items-center gap-[10px] flex-1 justify-center">
<ModeButton mode="editor">edit</ModeButton>
<ModeButton mode="viewer">view</ModeButton>
</div>
<div className="flex-1 flex justify-end">
<Button
type="button"
onClick={() => {
executeFlow(graph.flows[0].id);
}}
>
<SparklesIcon className="w-[18px] h-[18px] fill-white drop-shadow-[0.66px_1.32px_2.64px_hsla(0,0%,100%,0.25)]" />
<span>Run</span>
</Button>
</div>
</div>
);
}
26 changes: 26 additions & 0 deletions app/(playground)/p/[agentId]/canary/components/icons/sparkles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { FC, SVGProps } from "react";

export const SparklesIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>Sparkles Icon</title>
<g clipPath="url(#clip0_6588_4373)">
<g filter="url(#filter0_d_6588_4373)">
<path d="M14.7272 7.77291C8.63104 8.24793 8.24769 8.63129 7.77266 14.7275C7.29764 8.63129 6.91428 8.24793 0.818115 7.77291C6.91428 7.29788 7.29764 6.91453 7.77266 0.818359C8.24769 6.91453 8.63104 7.29788 14.7272 7.77291Z" />
</g>
<g filter="url(#filter1_d_6588_4373)">
<path d="M17.1818 14.7273C13.9541 14.9507 13.7513 15.131 13.4999 18C13.2486 15.131 13.0458 14.9507 9.81812 14.7273C13.0458 14.5039 13.2486 14.3236 13.4999 11.4546C13.7513 14.3236 13.9541 14.5039 17.1818 14.7273Z" />
</g>
</g>
<defs>
<clipPath id="clip0_6588_4373">
<rect width="18" height="18" />
</clipPath>
</defs>
</svg>
);
136 changes: 135 additions & 1 deletion app/(playground)/p/[agentId]/canary/components/navigation-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import * as Tabs from "@radix-ui/react-tabs";
import { getDownloadUrl, head } from "@vercel/blob";
import { DownloadIcon, GithubIcon, HammerIcon, XIcon } from "lucide-react";
import clsx from "clsx/lite";
import {
DownloadIcon,
FrameIcon,
GithubIcon,
HammerIcon,
ListTreeIcon,
XIcon,
} from "lucide-react";
import {
type ComponentProps,
type ReactNode,
createContext,
useContext,
useMemo,
useState,
} from "react";
import { LayersIcon } from "../../beta-proto/components/icons/layers";
import { WilliIcon } from "../../beta-proto/components/icons/willi";
import { useAgentName } from "../contexts/agent-name";
import { useDeveloperMode } from "../contexts/developer-mode";
import { useGraph } from "../contexts/graph";
import type { Node, Step } from "../types";
import { ContentTypeIcon } from "./content-type-icon";

function TabsTrigger(
props: Omit<ComponentProps<typeof Tabs.Trigger>, "className">,
Expand Down Expand Up @@ -68,6 +80,9 @@ export function NavigationPanel() {
{/* <TabsTrigger value="github">
<GithubIcon className="w-[18px] h-[18px] stroke-black-30" />
</TabsTrigger> */}
<TabsTrigger value="structure">
<ListTreeIcon className="w-[18px] h-[18px] stroke-black-30" />
</TabsTrigger>

{developerMode && (
<TabsTrigger value="developer">
Expand All @@ -81,6 +96,9 @@ export function NavigationPanel() {
{/* <TabsContent value="github">
<GitHubIntegration />
</TabsContent> */}
<TabsContent value="structure">
<Structure />
</TabsContent>
<TabsContent value="developer">
<Developer />
</TabsContent>
Expand Down Expand Up @@ -186,3 +204,119 @@ function Developer() {
</ContentPanel>
);
}

function StructureNodeItem({
node,
className,
}: { node: Node; className?: string }) {
return (
<div
className={clsx(
"hover:bg-white/10 flex items-center gap-[14px] px-[4px] py-[1px] cursor-default",
className,
)}
>
<ContentTypeIcon
contentType={node.content.type}
className="w-[16px] h-[16px] fill-current"
/>
<p>{node.name}</p>
</div>
);
}

function StructureStepItem({
step,
stepClassName,
variableNodesClassName,
}: {
step: Step & { node: Node; variableNodes: Node[] };
stepClassName?: string;
variableNodesClassName?: string;
}) {
return (
<div>
<StructureNodeItem node={step.node} className={stepClassName} />
{step.variableNodes.map((node) => (
<StructureNodeItem
key={node.id}
node={node}
className={variableNodesClassName}
/>
))}
</div>
);
}
export function Structure() {
const { graph } = useGraph();
const flows = useMemo(
() =>
graph.flows.map((flow) => ({
...flow,
jobs: flow.jobs.map((job) => ({
...job,
steps: job.steps
.map((step) => {
const node = graph.nodes.find((node) => node.id === step.nodeId);
const variableNodes = step.variableNodeIds
.map((nodeId) => graph.nodes.find((node) => node.id === nodeId))
.filter((node) => node !== undefined);
if (node === undefined) {
return null;
}
return {
...step,
node,
variableNodes,
};
})
.filter((step) => step !== null),
})),
})),
[graph],
);
return (
<ContentPanel>
<ContentPanelHeader>Structure</ContentPanelHeader>
<div className="flex flex-col gap-[8px]">
{flows.map((flow) => (
<div key={flow.id}>
<div className="flex items-center gap-[14px] hover:bg-white/10 px-[4px] py-[1px] rounded-[2px]">
<WilliIcon className="w-[16px] h-[16px] fill-current" />
<p className="text-[14px]">{flow.name}</p>
</div>
<div className="pt-[4px] flex flex-col gap-[4px] text-[14px]">
{flow.jobs.map((job) => (
<div key={job.id}>
{job.steps.length === 1 ? (
<StructureStepItem
step={job.steps[0]}
stepClassName="pl-[34px]"
variableNodesClassName="pl-[64px]"
/>
) : (
<div>
<div className="pl-[34px] hover:bg-white/10 px-[4px] py-[1px] gap-[14px] flex items-center">
<FrameIcon className="w-[16px] h-[16px] fill-current" />
<p>Subflow</p>
</div>

{job.steps.map((step) => (
<StructureStepItem
step={step}
key={step.id}
stepClassName="pl-[64px]"
variableNodesClassName="pl-[94px]"
/>
))}
</div>
)}
</div>
))}
</div>
</div>
))}
</div>
</ContentPanel>
);
}
16 changes: 16 additions & 0 deletions app/(playground)/p/[agentId]/canary/components/playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import { usePlaygroundMode } from "../contexts/playground-mode";
import { Editor } from "./editor";
import { Viewer } from "./viewer";

export function Playground() {
const { playgroundMode } = usePlaygroundMode();
if (playgroundMode === "editor") {
return <Editor />;
}
if (playgroundMode === "viewer") {
return <Viewer />;
}
throw new Error("Invalid playground mode");
}
Loading

0 comments on commit 1030098

Please sign in to comment.