forked from giselles-ai/giselle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request giselles-ai#232 from toyamarinyon/copy-agent-for-d…
…eveloper-mode feat(playground): Add agent duplication feature with file handling
- Loading branch information
Showing
5 changed files
with
212 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
"use server"; | ||
|
||
import { agents, db } from "@/drizzle"; | ||
import { fetchCurrentUser } from "@/services/accounts"; | ||
import { fetchCurrentTeam } from "@/services/teams"; | ||
import { createId } from "@paralleldrive/cuid2"; | ||
import { copy, list } from "@vercel/blob"; | ||
import { putGraph } from "../../(playground)/p/[agentId]/canary/actions"; | ||
import { | ||
buildFileFolderPath, | ||
createFileId, | ||
pathJoin, | ||
} from "../../(playground)/p/[agentId]/canary/lib/utils"; | ||
import type { | ||
AgentId, | ||
Graph, | ||
Node, | ||
} from "../../(playground)/p/[agentId]/canary/types"; | ||
|
||
interface AgentDuplicationSuccess { | ||
result: "success"; | ||
agentId: AgentId; | ||
} | ||
interface AgentDuplicationError { | ||
result: "error"; | ||
message: string; | ||
} | ||
type AgentDuplicationResult = AgentDuplicationSuccess | AgentDuplicationError; | ||
|
||
export async function copyAgentAction( | ||
prev: AgentDuplicationResult | null, | ||
formData: FormData, | ||
): Promise<AgentDuplicationResult> { | ||
const agentId = formData.get("agentId"); | ||
if (typeof agentId !== "string" || agentId.length === 0) { | ||
return { result: "error", message: "Please fill in the agent id" }; | ||
} | ||
const agent = await db.query.agents.findFirst({ | ||
where: (agents, { eq }) => eq(agents.id, agentId as AgentId), | ||
}); | ||
if (agent === undefined || agent.graphUrl === null) { | ||
return { result: "error", message: `${agentId} is not found.` }; | ||
} | ||
const [user, team, graph] = await Promise.all([ | ||
fetchCurrentUser(), | ||
fetchCurrentTeam(), | ||
fetch(agent.graphUrl).then((res) => res.json() as unknown as Graph), | ||
]); | ||
const newNodes = await Promise.all( | ||
graph.nodes.map(async (node) => { | ||
if (node.content.type !== "files") { | ||
return node; | ||
} | ||
const newData = await Promise.all( | ||
node.content.data.map(async (fileData) => { | ||
if (fileData.status !== "completed") { | ||
return null; | ||
} | ||
const newFileId = createFileId(); | ||
const blobList = await list({ | ||
prefix: buildFileFolderPath(fileData.id), | ||
}); | ||
let newFileBlobUrl = ""; | ||
let newTextDataUrl = ""; | ||
await Promise.all( | ||
blobList.blobs.map(async (blob) => { | ||
const copyResult = await copy( | ||
blob.url, | ||
buildFileFolderPath(newFileId), | ||
{ | ||
access: "public", | ||
}, | ||
); | ||
if (blob.url === fileData.fileBlobUrl) { | ||
newFileBlobUrl = copyResult.url; | ||
} | ||
if (blob.url === fileData.textDataUrl) { | ||
newTextDataUrl = copyResult.url; | ||
} | ||
}), | ||
); | ||
return { | ||
...fileData, | ||
id: newFileId, | ||
fileBlobUrl: newFileBlobUrl, | ||
textDataUrl: newTextDataUrl, | ||
}; | ||
}), | ||
).then((data) => data.filter((d) => d !== null)); | ||
return { | ||
...node, | ||
content: { | ||
...node.content, | ||
data: newData, | ||
}, | ||
} as Node; | ||
}), | ||
); | ||
const { url } = await putGraph({ ...graph, nodes: newNodes }); | ||
const newAgentId = `agnt_${createId()}` as AgentId; | ||
const newAgent = await db.insert(agents).values({ | ||
id: newAgentId, | ||
name: `Copy of ${agent.name ?? agentId}`, | ||
teamDbId: team.dbId, | ||
creatorDbId: user.dbId, | ||
graphUrl: url, | ||
graphv2: { | ||
agentId: newAgentId, | ||
nodes: [], | ||
xyFlow: { | ||
nodes: [], | ||
edges: [], | ||
}, | ||
connectors: [], | ||
artifacts: [], | ||
webSearches: [], | ||
mode: "edit", | ||
flowIndexes: [], | ||
}, | ||
}); | ||
return { result: "success", agentId: newAgentId }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"use client"; | ||
|
||
import { CheckIcon, CopyCheckIcon } from "lucide-react"; | ||
import Link from "next/link"; | ||
import { useActionState } from "react"; | ||
import { Spinner } from "../tabui/components/spinner"; | ||
import { copyAgentAction } from "./action"; | ||
|
||
export default function AgentIdForm() { | ||
const [state, formAction, isPending] = useActionState(copyAgentAction, null); | ||
return ( | ||
<form action={formAction} data-pending={isPending}> | ||
<div className="text-black--30"> | ||
<div> | ||
<span>{">"} </span> | ||
<input | ||
type="text" | ||
name="agentId" | ||
className="outline-0" | ||
ref={(ref) => { | ||
ref?.focus(); | ||
function submitAgentId() { | ||
if (/agnt_[a-z0-9]{24}/.test(ref?.value ?? "")) { | ||
ref?.form?.requestSubmit(); | ||
} | ||
} | ||
ref?.addEventListener("input", submitAgentId); | ||
return () => { | ||
ref?.removeEventListener("input", submitAgentId); | ||
}; | ||
}} | ||
/> | ||
</div> | ||
{isPending && <Spinner />} | ||
{!isPending && state?.result === "error" ? ( | ||
<p>{state.message}</p> | ||
) : state?.result === "success" ? ( | ||
<div> | ||
<div className="flex items-center gap-[4px]"> | ||
<CheckIcon size={16} className="text-green" /> <p>Success</p> | ||
</div> | ||
<a | ||
className="underline" | ||
href={`/p/${state.agentId}`} | ||
rel="noopener noreferrer" | ||
target="_blank" | ||
> | ||
Open in a new tab | ||
</a> | ||
</div> | ||
) : null} | ||
</div> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { agents, db } from "@/drizzle"; | ||
import { developerFlag } from "@/flags"; | ||
import { fetchCurrentUser } from "@/services/accounts"; | ||
import { fetchCurrentTeam } from "@/services/teams"; | ||
import { createId } from "@paralleldrive/cuid2"; | ||
import { notFound } from "next/navigation"; | ||
import { putGraph } from "../../(playground)/p/[agentId]/canary/actions"; | ||
import type { | ||
AgentId, | ||
Graph, | ||
} from "../../(playground)/p/[agentId]/canary/types"; | ||
import AgentIdForm from "./agent-id-form"; | ||
|
||
export default async function CopyAgentPage() { | ||
const developerMode = await developerFlag(); | ||
if (!developerMode) { | ||
return notFound(); | ||
} | ||
|
||
return ( | ||
<div className="font-mono p-8 text-black-30"> | ||
<p> | ||
Please fill in the agent id, then copy it in your | ||
account.(agnt_[0-9a-zA-Z]+) | ||
</p> | ||
<AgentIdForm /> | ||
</div> | ||
); | ||
} |