Skip to content

Commit

Permalink
feat: add temporary rendering for workflows and their threads
Browse files Browse the repository at this point in the history
Signed-off-by: tylerslaton <[email protected]>
  • Loading branch information
tylerslaton committed Nov 7, 2024
1 parent 3d78282 commit 1d39c32
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 42 deletions.
29 changes: 29 additions & 0 deletions ui/admin/app/components/header/HeaderNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useSWR from "swr";

import { AgentService } from "~/lib/service/api/agentService";
import { ThreadsService } from "~/lib/service/api/threadsService";
import { WorkflowService } from "~/lib/service/api/workflowService";
import { cn } from "~/lib/utils";

import { DarkModeToggle } from "~/components/DarkModeToggle";
Expand Down Expand Up @@ -103,6 +104,25 @@ function getBreadcrumbs(route: string, params: Readonly<Params<string>>) {
</BreadcrumbItem>
</>
)}
{new RegExp(
$path("/workflows/:workflow", { workflow: "(.*)" })
).test(route) && (
<>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to={$path("/workflows")}>Workflows</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>
<WorkflowName
workflowId={params.workflow || ""}
/>
</BreadcrumbPage>
</BreadcrumbItem>
</>
)}
{new RegExp($path("/tools")).test(route) && (
<BreadcrumbItem>
<BreadcrumbPage>Tools</BreadcrumbPage>
Expand Down Expand Up @@ -132,6 +152,15 @@ const AgentName = ({ agentId }: { agentId: string }) => {
return <>{agent?.name || "New Agent"}</>;
};

const WorkflowName = ({ workflowId }: { workflowId: string }) => {
const { data: workflow } = useSWR(
WorkflowService.getWorkflowById.key(workflowId),
({ workflowId }) => WorkflowService.getWorkflowById(workflowId)
);

return <>{workflow?.name || "New Workflow"}</>;
};

const ThreadName = ({ threadId }: { threadId: string }) => {
const { data: thread } = useSWR(
ThreadsService.getThreadById.key(threadId),
Expand Down
63 changes: 35 additions & 28 deletions ui/admin/app/components/thread/ThreadMeta.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Link } from "@remix-run/react";
import { EditIcon, FileIcon, FilesIcon } from "lucide-react";
import { EditIcon, ExternalLink, FileIcon, FilesIcon } from "lucide-react";
import { $path } from "remix-routes";

import { Agent } from "~/lib/model/agents";
import { KnowledgeFile } from "~/lib/model/knowledge";
import { runStateToBadgeColor } from "~/lib/model/runs";
import { Thread } from "~/lib/model/threads";
import { Workflow } from "~/lib/model/workflows";
import { WorkspaceFile } from "~/lib/model/workspace";
import { cn } from "~/lib/utils";

Expand All @@ -20,20 +21,22 @@ import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card";

interface ThreadMetaProps {
for: Agent | Workflow;
thread: Thread;
agent: Agent;
files: WorkspaceFile[];
knowledge: KnowledgeFile[];
className?: string;
}

export function ThreadMeta({
for: entity,
thread,
agent,
files,
className,
}: ThreadMetaProps) {
const from = $path("/thread/:id", { id: thread.id });
const isAgent = entity.id.startsWith("a");

return (
<Card className={cn("h-full bg-0", className)}>
<CardContent className="space-y-4 pt-6">
Expand All @@ -48,33 +51,37 @@ export function ThreadMeta({
{new Date(thread.created).toLocaleString()}
</td>
</tr>
{agent.name && (
<tr className="border-foreground/25">
<td className="font-medium py-2 pr-4">
Agent
</td>
<td className="text-right">
<div className="flex items-center justify-end gap-2">
<span>{agent.name}</span>
<Button
variant="ghost"
size="icon"
asChild
<tr className="border-foreground/25">
<td className="font-medium py-2 pr-4">
{isAgent ? "Agent" : "Workflow"}
</td>
<td className="text-right">
<div className="flex items-center justify-end gap-2">
<span>{entity.name}</span>
<Button
variant="ghost"
size="icon"
asChild
>
<Link
to={$path(
isAgent
? "/agents/:agent"
: "/workflows/:workflow",
{ workflow: entity.id },
{ from }
)}
>
<Link
to={$path(
"/agents/:agent",
{ agent: agent.id },
{ from }
)}
>
{isAgent ? (
<EditIcon className="w-4 h-4" />
</Link>
</Button>
</div>
</td>
</tr>
)}
) : (
<ExternalLink className="w-4 h-4" />
)}
</Link>
</Button>
</div>
</td>
</tr>
<tr className="border-foreground/25">
<td className="font-medium py-2 pr-4">State</td>
<td className="text-right">
Expand Down
13 changes: 13 additions & 0 deletions ui/admin/app/lib/service/routeQueryParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const QueryParamSchemaMap = {
agentId: z.string().optional(),
workflowId: z.string().optional(),
}),
"/workflows": z.undefined(),
"/workflows/:workflow": z.undefined(),
"/tools": z.undefined(),
"/users": z.undefined(),
} satisfies Record<keyof Routes, ZodSchema | null>;
Expand Down Expand Up @@ -57,6 +59,17 @@ function getUnknownQueryParams(pathname: string, search: string) {
} satisfies QueryParamInfo<"/threads">;
}

if (
new RegExp($path("/workflows/:workflow", { workflow: "(.*)" })).test(
pathname
)
) {
return {
path: "/workflows/:workflow",
query: parseSearchParams("/workflows/:workflow", search),
} satisfies QueryParamInfo<"/workflows/:workflow">;
}

return {};
}

Expand Down
2 changes: 1 addition & 1 deletion ui/admin/app/routes/_auth.agents._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function clientLoader() {
return null;
}

export default function Threads() {
export default function Agents() {
const navigate = useNavigate();
const getThreads = useSWR(ThreadsService.getThreads.key(), () =>
ThreadsService.getThreads()
Expand Down
19 changes: 15 additions & 4 deletions ui/admin/app/routes/_auth.thread.$id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
} from "@remix-run/react";
import { ArrowLeftIcon } from "lucide-react";

import { Agent } from "~/lib/model/agents";
import { AgentService } from "~/lib/service/api/agentService";
import { ThreadsService } from "~/lib/service/api/threadsService";
import { WorkflowService } from "~/lib/service/api/workflowService";
import { RouteService } from "~/lib/service/routeQueryParams";
import { noop } from "~/lib/utils";

Expand Down Expand Up @@ -42,16 +42,27 @@ export const clientLoader = async ({ params }: ClientLoaderFunctionArgs) => {
const agent = thread.agentID
? await AgentService.getAgentById(thread.agentID).catch(noop)
: null;

const workflow = thread.workflowID
? await WorkflowService.getWorkflowById(thread.workflowID).catch(noop)
: null;

const files = await ThreadsService.getFiles(id);
const knowledge = await ThreadsService.getKnowledge(id);

return { thread, agent, files, knowledge };
return { thread, agent, workflow, files, knowledge };
};

export default function ChatAgent() {
const { thread, agent, files, knowledge } =
const { thread, agent, workflow, files, knowledge } =
useLoaderData<typeof clientLoader>();

const getEntity = () => {
if (agent) return agent;
if (workflow) return workflow;
throw new Error("Trying to view a thread with an unsupported parent.");
};

return (
<ChatProvider
id={agent?.id || ""}
Expand Down Expand Up @@ -90,7 +101,7 @@ export default function ChatAgent() {
<ThreadMeta
className="rounded-none border-none"
thread={thread}
agent={agent ?? ({} as Agent)}
for={getEntity()}
files={files}
knowledge={knowledge}
/>
Expand Down
62 changes: 56 additions & 6 deletions ui/admin/app/routes/_auth.threads.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { ReaderIcon } from "@radix-ui/react-icons";
import { PersonIcon, ReaderIcon } from "@radix-ui/react-icons";
import {
ClientLoaderFunctionArgs,
Link,
useLoaderData,
useNavigate,
} from "@remix-run/react";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { Trash } from "lucide-react";
import { PuzzleIcon, Trash } from "lucide-react";
import { useMemo } from "react";
import { $path } from "remix-routes";
import useSWR from "swr";
import useSWR, { preload } from "swr";
import { z } from "zod";

import { Agent } from "~/lib/model/agents";
import { Thread } from "~/lib/model/threads";
import { Workflow } from "~/lib/model/workflows";
import { AgentService } from "~/lib/service/api/agentService";
import { ThreadsService } from "~/lib/service/api/threadsService";
import { WorkflowService } from "~/lib/service/api/workflowService";
import { RouteService } from "~/lib/service/routeQueryParams";
import { timeSince } from "~/lib/utils";

Expand All @@ -32,9 +34,18 @@ import { useAsync } from "~/hooks/useAsync";

export type SearchParams = z.infer<(typeof RouteService.schemas)["/threads"]>;

export function clientLoader({ request }: ClientLoaderFunctionArgs) {
export async function clientLoader({ request }: ClientLoaderFunctionArgs) {
const search = new URL(request.url).search;

await Promise.all([
preload(AgentService.getAgents.key(), AgentService.getAgents),
preload(
WorkflowService.getWorkflows.key(),
WorkflowService.getWorkflows
),
preload(ThreadsService.getThreads.key(), ThreadsService.getThreads),
]);

return RouteService.getQueryParams("/threads", search) ?? {};
}

Expand All @@ -52,6 +63,11 @@ export default function Threads() {
AgentService.getAgents
);

const getWorkflows = useSWR(
WorkflowService.getWorkflows.key(),
WorkflowService.getWorkflows
);

const agentMap = useMemo(() => {
// note(tylerslaton): the or condition here is because the getAgents.data can
// be an object containing a url only when switching to the agent page from the
Expand All @@ -66,6 +82,17 @@ export default function Threads() {
);
}, [getAgents.data]);

const workflowMap = useMemo(() => {
if (!getWorkflows.data || !Array.isArray(getWorkflows.data)) return {};
return getWorkflows.data.reduce(
(acc, workflow) => {
acc[workflow.id] = workflow;
return acc;
},
{} as Record<string, Workflow>
);
}, [getWorkflows.data]);

const threads = useMemo(() => {
console.log(agentId);
if (!getThreads.data) return [];
Expand Down Expand Up @@ -96,7 +123,9 @@ export default function Threads() {
<TypographyH2 className="mb-8">Threads</TypographyH2>
<DataTable
columns={getColumns()}
data={threads.filter((thread) => thread.agentID)}
data={threads.filter(
(thread) => thread.agentID || thread.workflowID
)}
sort={[{ id: "created", desc: true }]}
classNames={{
row: "!max-h-[200px] grow-0 height-[200px]",
Expand All @@ -119,10 +148,31 @@ export default function Threads() {
(thread) => {
if (thread.agentID)
return agentMap[thread.agentID]?.name ?? thread.agentID;
else if (thread.workflowID)
return (
workflowMap[thread.workflowID]?.name ??
thread.workflowID
);
return "Unnamed";
},
{ header: "Agent" }
{ header: "Name" }
),
columnHelper.display({
id: "type",
header: "Type",
cell: ({ row }) => {
return (
<TypographyP className="flex items-center gap-2">
{row.original.agentID ? (
<PersonIcon className="w-4 h-4" />
) : (
<PuzzleIcon className="w-4 h-4" />
)}
{row.original.agentID ? "Agent" : "Workflow"}
</TypographyP>
);
},
}),
columnHelper.accessor("created", {
id: "created",
header: "Created",
Expand Down
Loading

0 comments on commit 1d39c32

Please sign in to comment.