Skip to content

Commit

Permalink
Merge pull request #48 from route06inc/run-1
Browse files Browse the repository at this point in the history
Prototype runner
  • Loading branch information
shige authored Oct 25, 2024
2 parents 930deb6 + ea2d031 commit 33646e1
Show file tree
Hide file tree
Showing 17 changed files with 2,240 additions and 29 deletions.
42 changes: 42 additions & 0 deletions app/(playground)/p/[agentId]/beta-proto/flow/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Flow, QueuedFlow, RunningFlow } from "./types";

const v2FlowActionTypes = {
setFlow: "v2.setFlow",
} as const;

type V2FlowActionType =
(typeof v2FlowActionTypes)[keyof typeof v2FlowActionTypes];

interface SetFlowAction {
type: Extract<V2FlowActionType, "v2.setFlow">;
input: SetFlowActionInput;
}
type SetFlowActionInput =
| Omit<QueuedFlow, "object">
| Omit<RunningFlow, "object">;

export type V2FlowAction = SetFlowAction;

export function isV2FlowAction(action: unknown): action is V2FlowAction {
return Object.values(v2FlowActionTypes).includes(
(action as V2FlowAction).type,
);
}

export function setFlow({ input }: { input: SetFlowActionInput }) {
return {
type: v2FlowActionTypes.setFlow,
input,
};
}

export function v2FlowReducer(
flow: Flow | null | undefined,
action: V2FlowAction,
): Flow | null | undefined {
switch (action.type) {
case v2FlowActionTypes.setFlow:
return { ...action.input, object: "flow" };
}
return flow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { forwardRef } from "react";
import { SparklesIcon } from "../../components/icons/sparkles";
import type { GiselleNode } from "../../giselle-node/types";
import { useGraph } from "../../graph/context";
import { runFlow } from "../composite-actions";

interface RunButtonInnerProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick?: () => void;
}
const RunButtonInner = forwardRef<HTMLButtonElement, RunButtonInnerProps>(
(props: RunButtonInnerProps, ref) => {
return (
<button
type="button"
className="px-[16px] py-[8px] rounded-[8px] flex items-center gap-[2px] bg-[hsla(207,19%,77%,0.3)] font-rosart"
style={{
boxShadow: "0px 0px 3px 0px hsla(0, 0%, 100%, 0.25) inset",
}}
ref={ref}
{...props}
>
<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>
);
},
);

export function RunButton() {
const { state, dispatch } = useGraph();
const finalNodes = state.graph.nodes.filter((node) => node.isFinal);
const handleClickRunButton = (node: GiselleNode) => {
dispatch(runFlow(node));
};
if (finalNodes.length === 1) {
return (
<RunButtonInner
onClick={() => {
handleClickRunButton(finalNodes[0]);
}}
/>
);
}
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<RunButtonInner />
</DropdownMenu.Trigger>

<DropdownMenu.Portal>
<DropdownMenu.Content
align="end"
className="z-50 bg-black-100 border-[0.5px] border-black-70 rounded-[16px] px-[16px] py-[8px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
>
{finalNodes.map((node) => (
<DropdownMenu.Item key={node.id} className="">
<button
type="button"
onClick={() => {
handleClickRunButton(node);
}}
>
{node.name}
</button>
</DropdownMenu.Item>
))}
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
}
54 changes: 54 additions & 0 deletions app/(playground)/p/[agentId]/beta-proto/flow/composite-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { GiselleNode } from "../giselle-node/types";
import type { ThunkAction } from "../graph/context";
import { playgroundModes } from "../graph/types";
import { updateMode } from "../graph/v2/mode";
import { setFlow } from "./action";
import { runAction } from "./server-action";
import { flowStatuses } from "./types";
import { createFlowActionId, createFlowId, resolveActionLayers } from "./utils";

export function runFlow(finalNode: GiselleNode): ThunkAction {
return async (dispatch, getState) => {
const state = getState();
dispatch(
setFlow({
input: {
id: createFlowId(),
status: flowStatuses.queued,
finalNodeId: finalNode.id,
},
}),
);
dispatch(
updateMode({
input: {
mode: playgroundModes.view,
},
}),
);
const actionLayers = resolveActionLayers(
state.graph.connectors,
finalNode.id,
);
dispatch(
setFlow({
input: {
id: createFlowId(),
status: flowStatuses.running,
finalNodeId: finalNode.id,
actionLayers,
},
}),
);
for (const actionLayer of actionLayers) {
await Promise.all(
actionLayer.actions.map(async (action) => {
await runAction({
nodeId: action.nodeId,
agentId: state.graph.agentId,
});
}),
);
}
};
}
159 changes: 159 additions & 0 deletions app/(playground)/p/[agentId]/beta-proto/flow/server-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use server";

import { agents, db } from "@/drizzle";
import { eq } from "drizzle-orm";
import type { Artifact } from "../artifact/types";
import { type StructuredData, fileStatuses } from "../files/types";
import {
giselleNodeArchetypes,
textGeneratorParameterNames,
} from "../giselle-node/blueprints";
import {
type GiselleNode,
type GiselleNodeId,
giselleNodeCategories,
} from "../giselle-node/types";
import type { Graph } from "../graph/types";
import type { TextContent } from "../text-content/types";
import type { AgentId } from "../types";
import {
type WebSearchItem,
type WebSearchItemReference,
webSearchItemStatus,
webSearchStatus,
} from "../web-search/types";

type Source = Artifact | TextContent | StructuredData | WebSearchItem;
interface GatherInstructionSourcesInput {
node: GiselleNode;
graph: Graph;
}
async function gatherInstructionSources(input: GatherInstructionSourcesInput) {
if (!Array.isArray(input.node.properties.sources)) {
return [];
}
const instructionSources: Source[] = [];
for (const source of input.node.properties.sources) {
if (
typeof source !== "object" ||
source === null ||
typeof source.id !== "string" ||
typeof source.object !== "string"
) {
continue;
}
if (source.object === "textContent") {
instructionSources.push(source);
} else if (source.object === "artifact.reference") {
const artifact = input.graph.artifacts.find(
(artifact) => artifact.id === source.id,
);
if (artifact !== undefined) {
instructionSources.push(artifact);
}
} else if (source.object === "file") {
if (
typeof source.status === "string" &&
source.status === fileStatuses.processed &&
typeof source.structuredDataBlobUrl === "string" &&
typeof source.name === "string"
) {
const structuredData = await fetch(source.structuredDataBlobUrl).then(
(res) => res.text(),
);
instructionSources.push({
id: source.id,
object: "file",
title: source.name,
content: structuredData,
});
}
} else if (source.object === "webSearch") {
if (
typeof source.status === "string" &&
source.status === webSearchStatus.completed &&
Array.isArray(source.items)
) {
await Promise.all(
(source.items as WebSearchItemReference[]).map(async (item) => {
if (item.status === webSearchItemStatus.completed) {
const webSearchData = await fetch(item.contentBlobUrl).then(
(res) => res.text(),
);
instructionSources.push({
id: item.id,
object: "webSearch.item",
url: item.url,
title: item.title,
content: webSearchData,
relevance: item.relevance,
});
}
}),
);
}
}
}
return instructionSources;
}

interface RunActionInput {
agentId: AgentId;
nodeId: GiselleNodeId;
}
export async function runAction(input: RunActionInput) {
const agent = await db.query.agents.findFirst({
where: eq(agents.id, input.agentId),
});
if (agent === undefined) {
throw new Error(`Agent with id ${input.agentId} not found`);
}
const graph = agent.graphv2;

const instructionConnector = graph.connectors.find(
(connector) =>
connector.target === input.nodeId &&
connector.sourceNodeCategory === giselleNodeCategories.instruction,
);

if (instructionConnector === undefined) {
throw new Error(`No instruction connector found for node ${input.nodeId}`);
}

const instructionNode = graph.nodes.find(
(node) => node.id === instructionConnector.source,
);
const actionNode = graph.nodes.find(
(node) => node.id === instructionConnector.target,
);

if (instructionNode === undefined || actionNode === undefined) {
throw new Error(
`Instruction node ${instructionConnector.source} or action node ${instructionConnector.target} not found`,
);
}

const sources = await gatherInstructionSources({
node: instructionNode,
graph,
});

switch (instructionConnector.targetNodeArcheType) {
case giselleNodeArchetypes.textGenerator:
await generateText();
break;
case giselleNodeArchetypes.webSearch:
await webSearch();
break;
}
}

async function generateText() {
console.log(
"\x1b[33m\x1b[1mTODO:\x1b[0m Implement text generation functionality",
);
}

async function webSearch() {
console.log("\x1b[33m\x1b[1mTODO:\x1b[0m Implement websearch functionality");
}
Loading

0 comments on commit 33646e1

Please sign in to comment.