From 87b173fa2c50f6d4e121fe239a15809f7372c2c6 Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Tue, 12 Nov 2024 16:31:58 -0600 Subject: [PATCH 1/5] feat: implement workflow chat interface --- ui/admin/app/components/chat/Chat.tsx | 30 ++++++-- ui/admin/app/components/chat/ChatContext.tsx | 35 ++-------- ui/admin/app/components/chat/Chatbar.tsx | 2 +- ui/admin/app/components/chat/MessagePane.tsx | 4 +- ui/admin/app/components/chat/NoMessages.tsx | 10 +-- ui/admin/app/lib/service/api/primitives.ts | 4 +- .../app/routes/_auth.workflows.$workflow.tsx | 70 +++++++++++++++++-- 7 files changed, 102 insertions(+), 53 deletions(-) diff --git a/ui/admin/app/components/chat/Chat.tsx b/ui/admin/app/components/chat/Chat.tsx index fe88f94b..3fa5bcbc 100644 --- a/ui/admin/app/components/chat/Chat.tsx +++ b/ui/admin/app/components/chat/Chat.tsx @@ -1,5 +1,7 @@ import { useState } from "react"; +import { cn } from "~/lib/utils"; + import { useChat } from "~/components/chat/ChatContext"; import { Chatbar } from "~/components/chat/Chatbar"; import { MessagePane } from "~/components/chat/MessagePane"; @@ -9,16 +11,23 @@ type ChatProps = React.HTMLAttributes & { showStartButton?: boolean; }; -export function Chat({ className, showStartButton = false }: ChatProps) { - const { messages, threadId, mode, invoke, readOnly } = useChat(); +export function Chat({ className }: ChatProps) { + const { + messages, + threadId, + mode, + invoke, + readOnly, + isInvoking, + isRunning, + } = useChat(); const [runTriggered, setRunTriggered] = useState(false); const showMessagePane = mode === "agent" || - (mode === "workflow" && (threadId || runTriggered || !showStartButton)); + (mode === "workflow" && (threadId || runTriggered || !readOnly)); - const showStartButtonPane = - mode === "workflow" && showStartButton && !(threadId || runTriggered); + const showStartButtonPane = mode === "workflow" && !readOnly; return (
@@ -34,13 +43,22 @@ export function Chat({ className, showStartButton = false }: ChatProps) { {mode === "agent" && !readOnly && } {showStartButtonPane && ( -
+
diff --git a/ui/admin/app/components/chat/ChatContext.tsx b/ui/admin/app/components/chat/ChatContext.tsx index 71a1d948..8f469e0c 100644 --- a/ui/admin/app/components/chat/ChatContext.tsx +++ b/ui/admin/app/components/chat/ChatContext.tsx @@ -20,7 +20,7 @@ type Mode = "agent" | "workflow"; interface ChatContextType { messages: Message[]; mode: Mode; - processUserMessage: (text: string, sender: "user" | "agent") => void; + processUserMessage: (text: string) => void; id: string; threadId: Nullish; invoke: (prompt?: string) => void; @@ -46,38 +46,17 @@ export function ChatProvider({ onCreateThreadId?: (threadId: string) => void; readOnly?: boolean; }) { - /** - * processUserMessage is responsible for adding the user's message to the chat and - * triggering the agent to respond to it. - */ - const processUserMessage = (text: string, sender: "user" | "agent") => { - if (mode === "workflow" || readOnly) return; - const newMessage: Message = { text, sender }; - - // insertMessage(newMessage); - handlePrompt(newMessage.text); - }; - const invoke = (prompt?: string) => { - if (prompt && mode === "agent" && !readOnly) { - handlePrompt(prompt); - } - }; + if (readOnly) return; - const handlePrompt = (prompt: string) => { - if (prompt && mode === "agent" && !readOnly) { - invokeAgent.execute({ - slug: id, - prompt: prompt, - thread: threadId, - }); - } - // do nothing if the mode is workflow + if (mode === "workflow") invokeAgent.execute({ slug: id }); + else if (mode === "agent") + invokeAgent.execute({ slug: id, prompt: prompt, thread: threadId }); }; const invokeAgent = useAsync(InvokeService.invokeAgentWithStream, { onSuccess: ({ threadId: responseThreadId }) => { - if (responseThreadId && !threadId) { + if (responseThreadId && responseThreadId !== threadId) { // persist the threadId onCreateThreadId?.(responseThreadId); @@ -93,7 +72,7 @@ export function ChatProvider({ diff --git a/ui/admin/app/components/chat/NoMessages.tsx b/ui/admin/app/components/chat/NoMessages.tsx index 956693dc..c80c9737 100644 --- a/ui/admin/app/components/chat/NoMessages.tsx +++ b/ui/admin/app/components/chat/NoMessages.tsx @@ -6,10 +6,6 @@ import { Button } from "~/components/ui/button"; export function NoMessages() { const { processUserMessage, isInvoking } = useChat(); - const handleAddMessage = (content: string) => { - processUserMessage(content, "user"); - }; - return (

Start the conversation!

@@ -22,7 +18,7 @@ export function NoMessages() { shape="pill" disabled={isInvoking} onClick={() => - handleAddMessage( + processUserMessage( "Tell me who you are and what your objectives are." ) } @@ -35,7 +31,7 @@ export function NoMessages() { shape="pill" disabled={isInvoking} onClick={() => - handleAddMessage( + processUserMessage( "Tell me what tools you have available." ) } @@ -48,7 +44,7 @@ export function NoMessages() { shape="pill" disabled={isInvoking} onClick={() => - handleAddMessage( + processUserMessage( "Using your knowledge tools, tell me about your knowledge set." ) } diff --git a/ui/admin/app/lib/service/api/primitives.ts b/ui/admin/app/lib/service/api/primitives.ts index 2e5d4f31..549e0152 100644 --- a/ui/admin/app/lib/service/api/primitives.ts +++ b/ui/admin/app/lib/service/api/primitives.ts @@ -24,7 +24,7 @@ interface ExtendedAxiosRequestConfig } export async function request, D = unknown>({ - errorMessage = "Request failed", + errorMessage: _, disableTokenRefresh, ...config }: ExtendedAxiosRequestConfig): Promise { @@ -34,8 +34,6 @@ export async function request, D = unknown>({ ...config, }); } catch (error) { - console.error(errorMessage); - if (isAxiosError(error) && error.response?.status === 400) { throw new BadRequestError(error.response.data); } diff --git a/ui/admin/app/routes/_auth.workflows.$workflow.tsx b/ui/admin/app/routes/_auth.workflows.$workflow.tsx index 23dc138d..eb4d4258 100644 --- a/ui/admin/app/routes/_auth.workflows.$workflow.tsx +++ b/ui/admin/app/routes/_auth.workflows.$workflow.tsx @@ -2,16 +2,36 @@ import { ClientLoaderFunctionArgs, redirect, useLoaderData, + useNavigate, } from "@remix-run/react"; -import { $params } from "remix-routes"; +import { $path } from "remix-routes"; +import { z } from "zod"; import { WorkflowService } from "~/lib/service/api/workflowService"; +import { RouteService } from "~/lib/service/routeService"; import { noop } from "~/lib/utils"; +import { Chat } from "~/components/chat"; +import { ChatProvider } from "~/components/chat/ChatContext"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "~/components/ui/resizable"; import { Workflow } from "~/components/workflow"; -export const clientLoader = async ({ params }: ClientLoaderFunctionArgs) => { - const { workflow: id } = $params("/workflows/:workflow", params); +export type SearchParams = z.infer< + (typeof RouteService.schemas)["/workflows/:workflow"] +>; + +export const clientLoader = async ({ + params, + request, +}: ClientLoaderFunctionArgs) => { + const { workflow: id } = RouteService.getPathParams( + "/workflows/:workflow", + params + ); if (!id) { throw redirect("/threads"); @@ -20,11 +40,49 @@ export const clientLoader = async ({ params }: ClientLoaderFunctionArgs) => { const workflow = await WorkflowService.getWorkflowById(id).catch(noop); if (!workflow) throw redirect("/agents"); - return { workflow }; + const { threadId } = + RouteService.getQueryParams( + "/workflows/:workflow", + new URL(request.url).search + ) || {}; + + return { workflow, threadId }; }; export default function ChatAgent() { - const { workflow } = useLoaderData(); + const { workflow, threadId } = useLoaderData(); + + const navigate = useNavigate(); - return ; + return ( +
+ + navigate( + $path( + "/workflows/:workflow", + { workflow: workflow.id }, + { threadId } + ) + ) + } + > + + + + + + + + + + +
+ ); } From 61bf96f4d8a6612479053640c1e2b59ea87822a7 Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Mon, 18 Nov 2024 12:50:04 -0600 Subject: [PATCH 2/5] chore: rework params section in workflow form --- .../app/components/form/controlledInputs.tsx | 8 +- .../app/components/workflow/ParamsForm.tsx | 204 ++++++++---------- ui/admin/app/lib/service/routeService.ts | 5 +- .../app/routes/_auth.workflows.$workflow.tsx | 27 ++- 4 files changed, 114 insertions(+), 130 deletions(-) diff --git a/ui/admin/app/components/form/controlledInputs.tsx b/ui/admin/app/components/form/controlledInputs.tsx index 6ae25f18..1c79d5f3 100644 --- a/ui/admin/app/components/form/controlledInputs.tsx +++ b/ui/admin/app/components/form/controlledInputs.tsx @@ -43,8 +43,14 @@ export type ControlledInputProps< TName extends FieldPath, > = InputProps & BaseProps & { - classNames?: { wrapper?: string }; onChangeConversion?: (value: string) => string; + classNames?: { + wrapper?: string; + label?: string; + input?: string; + description?: string; + message?: string; + }; }; export function ControlledInput< diff --git a/ui/admin/app/components/workflow/ParamsForm.tsx b/ui/admin/app/components/workflow/ParamsForm.tsx index 3e362ea0..4af84668 100644 --- a/ui/admin/app/components/workflow/ParamsForm.tsx +++ b/ui/admin/app/components/workflow/ParamsForm.tsx @@ -1,47 +1,81 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { Plus, TrashIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; +import { PlusIcon, TrashIcon } from "lucide-react"; +import { useEffect, useMemo } from "react"; +import { useFieldArray, useForm } from "react-hook-form"; import { z } from "zod"; import { Workflow } from "~/lib/model/workflows"; -import { noop } from "~/lib/utils"; +import { ControlledInput } from "~/components/form/controlledInputs"; import { Button } from "~/components/ui/button"; -import { Form, FormField, FormItem, FormMessage } from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; +import { Form } from "~/components/ui/form"; const formSchema = z.object({ - params: z.record(z.string(), z.string()).optional(), + params: z.array( + z.object({ + name: z.string(), + description: z.string(), + }) + ), }); export type ParamFormValues = z.infer; +type ParamValues = Workflow["params"]; + +const convertFrom = (params: ParamValues) => { + const converted = Object.entries(params || {}).map( + ([name, description]) => ({ + name, + description, + }) + ); + + return { + params: converted.length ? converted : [{ name: "", description: "" }], + }; +}; + +const convertTo = (params: ParamFormValues["params"]) => { + if (!params?.length) return undefined; + + return params.reduce((acc, param) => { + if (!param.name) return acc; + + acc[param.name] = param.description; + return acc; + }, {} as NonNullable); +}; + export function ParamsForm({ workflow, - onSubmit, onChange, }: { workflow: Workflow; - onSubmit?: (values: ParamFormValues) => void; - onChange?: (values: ParamFormValues) => void; + onChange?: (values: { params?: ParamValues }) => void; }) { + const defaultValues = useMemo( + () => convertFrom(workflow.params), + [workflow.params] + ); + const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: { params: workflow.params || {} }, + defaultValues, }); - const handleSubmit = form.handleSubmit(onSubmit || noop); - - const [newParamKey, setNewParamKey] = useState(""); - const [newParamValue, setNewParamValue] = useState(""); + const paramFields = useFieldArray({ + control: form.control, + name: "params", + }); useEffect(() => { const subscription = form.watch((value, { name, type }) => { if (name === "params" || type === "change") { const { data, success } = formSchema.safeParse(value); + if (success) { - onChange?.(data); + onChange?.({ params: convertTo(data.params) }); } } }); @@ -50,103 +84,47 @@ export function ParamsForm({ return (
- - ( - -
- - setNewParamKey(e.target.value) - } - className="flex-grow" - /> - - setNewParamValue(e.target.value) - } - className="flex-grow" - /> - -
- -
- {Object.entries(field.value || {}).map( - ([key, value], index) => ( -
- - { - const updatedParams = { - ...field.value, - }; - updatedParams[key] = - e.target.value; - field.onChange( - updatedParams - ); - }} - className="flex-grow" - /> - -
- ) - )} -
- -
- )} - /> - +
+ {paramFields.fields.map((field, i) => ( +
+ + + + + +
+ ))} + + +
); } diff --git a/ui/admin/app/lib/service/routeService.ts b/ui/admin/app/lib/service/routeService.ts index f3a5d993..c484f7b9 100644 --- a/ui/admin/app/lib/service/routeService.ts +++ b/ui/admin/app/lib/service/routeService.ts @@ -13,6 +13,9 @@ const QuerySchemas = { workflowId: z.string().nullish(), from: z.enum(["workflows", "agents", "users"]).nullish().catch(null), }), + workflowSchema: z.object({ + threadId: z.string().nullish(), + }), } as const; function parseQuery(search: string, schema: T) { @@ -121,7 +124,7 @@ export const RouteHelperMap = { "/workflows/:workflow": { regex: exactRegex($path("/workflows/:workflow", { workflow: "(.+)" })), path: "/workflows/:workflow", - schema: z.null(), + schema: QuerySchemas.workflowSchema, }, } satisfies Record; diff --git a/ui/admin/app/routes/_auth.workflows.$workflow.tsx b/ui/admin/app/routes/_auth.workflows.$workflow.tsx index eb4d4258..fe2d1e94 100644 --- a/ui/admin/app/routes/_auth.workflows.$workflow.tsx +++ b/ui/admin/app/routes/_auth.workflows.$workflow.tsx @@ -5,10 +5,9 @@ import { useNavigate, } from "@remix-run/react"; import { $path } from "remix-routes"; -import { z } from "zod"; import { WorkflowService } from "~/lib/service/api/workflowService"; -import { RouteService } from "~/lib/service/routeService"; +import { RouteQueryParams, RouteService } from "~/lib/service/routeService"; import { noop } from "~/lib/utils"; import { Chat } from "~/components/chat"; @@ -20,31 +19,29 @@ import { } from "~/components/ui/resizable"; import { Workflow } from "~/components/workflow"; -export type SearchParams = z.infer< - (typeof RouteService.schemas)["/workflows/:workflow"] ->; +export type SearchParams = RouteQueryParams<"workflowSchema">; export const clientLoader = async ({ params, request, }: ClientLoaderFunctionArgs) => { - const { workflow: id } = RouteService.getPathParams( + const { pathParams, query } = RouteService.getRouteInfo( "/workflows/:workflow", + new URL(request.url), params ); - if (!id) { - throw redirect("/threads"); + if (!pathParams.workflow) { + throw redirect($path("/workflows")); } - const workflow = await WorkflowService.getWorkflowById(id).catch(noop); - if (!workflow) throw redirect("/agents"); + const workflow = await WorkflowService.getWorkflowById( + pathParams.workflow + ).catch(noop); - const { threadId } = - RouteService.getQueryParams( - "/workflows/:workflow", - new URL(request.url).search - ) || {}; + if (!workflow) throw redirect($path("/workflows")); + + const { threadId } = query ?? {}; return { workflow, threadId }; }; From 5705c08a1e77c1e55b0931f8830bfc0ce67234ed Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Thu, 21 Nov 2024 17:08:00 -0600 Subject: [PATCH 3/5] feat: add params form to workflow invoke button --- ui/admin/app/components/chat/Chat.tsx | 13 ++--- ui/admin/app/components/chat/ChatContext.tsx | 8 ++- ui/admin/app/components/chat/RunWorkflow.tsx | 54 +++++++++++++++++++ .../app/components/chat/RunWorkflowForm.tsx | 44 +++++++++++++++ .../app/routes/_auth.workflows.$workflow.tsx | 1 + 5 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 ui/admin/app/components/chat/RunWorkflow.tsx create mode 100644 ui/admin/app/components/chat/RunWorkflowForm.tsx diff --git a/ui/admin/app/components/chat/Chat.tsx b/ui/admin/app/components/chat/Chat.tsx index 3fa5bcbc..4dd409da 100644 --- a/ui/admin/app/components/chat/Chat.tsx +++ b/ui/admin/app/components/chat/Chat.tsx @@ -5,7 +5,7 @@ import { cn } from "~/lib/utils"; import { useChat } from "~/components/chat/ChatContext"; import { Chatbar } from "~/components/chat/Chatbar"; import { MessagePane } from "~/components/chat/MessagePane"; -import { Button } from "~/components/ui/button"; +import { RunWorkflow } from "~/components/chat/RunWorkflow"; type ChatProps = React.HTMLAttributes & { showStartButton?: boolean; @@ -20,6 +20,7 @@ export function Chat({ className }: ChatProps) { readOnly, isInvoking, isRunning, + params, } = useChat(); const [runTriggered, setRunTriggered] = useState(false); @@ -48,11 +49,11 @@ export function Chat({ className }: ChatProps) { "flex justify-center items-center h-full": !threadId, })} > - +
)}
diff --git a/ui/admin/app/components/chat/ChatContext.tsx b/ui/admin/app/components/chat/ChatContext.tsx index 8f469e0c..83346cf3 100644 --- a/ui/admin/app/components/chat/ChatContext.tsx +++ b/ui/admin/app/components/chat/ChatContext.tsx @@ -27,6 +27,7 @@ interface ChatContextType { readOnly?: boolean; isRunning: boolean; isInvoking: boolean; + params?: Record; } const ChatContext = createContext(undefined); @@ -38,6 +39,7 @@ export function ChatProvider({ threadId, onCreateThreadId, readOnly, + params, }: { children: ReactNode; mode?: Mode; @@ -45,13 +47,14 @@ export function ChatProvider({ threadId?: Nullish; onCreateThreadId?: (threadId: string) => void; readOnly?: boolean; + params?: Record; }) { const invoke = (prompt?: string) => { if (readOnly) return; - if (mode === "workflow") invokeAgent.execute({ slug: id }); + if (mode === "workflow") invokeAgent.execute({ slug: id, prompt }); else if (mode === "agent") - invokeAgent.execute({ slug: id, prompt: prompt, thread: threadId }); + invokeAgent.execute({ slug: id, prompt, thread: threadId }); }; const invokeAgent = useAsync(InvokeService.invokeAgentWithStream, { @@ -80,6 +83,7 @@ export function ChatProvider({ isRunning, isInvoking: invokeAgent.isLoading, readOnly, + params, }} > {children} diff --git a/ui/admin/app/components/chat/RunWorkflow.tsx b/ui/admin/app/components/chat/RunWorkflow.tsx new file mode 100644 index 00000000..fe4fc04d --- /dev/null +++ b/ui/admin/app/components/chat/RunWorkflow.tsx @@ -0,0 +1,54 @@ +import { useState } from "react"; + +import { RunWorkflowForm } from "~/components/chat/RunWorkflowForm"; +import { Button, ButtonProps } from "~/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; + +type RunWorkflowProps = { + params?: Record; + onSubmit: (params?: Record) => void; +}; + +export function RunWorkflow({ + params, + onSubmit, + disabled, + ...props +}: RunWorkflowProps & ButtonProps) { + const [open, setOpen] = useState(false); + + if (!params) + return ( + + ); + + return ( + + + + + + + { + setOpen(false); + onSubmit(params); + }} + /> + + + ); +} diff --git a/ui/admin/app/components/chat/RunWorkflowForm.tsx b/ui/admin/app/components/chat/RunWorkflowForm.tsx new file mode 100644 index 00000000..3b3af8e7 --- /dev/null +++ b/ui/admin/app/components/chat/RunWorkflowForm.tsx @@ -0,0 +1,44 @@ +import { useMemo } from "react"; +import { useForm } from "react-hook-form"; + +import { ControlledInput } from "~/components/form/controlledInputs"; +import { Button } from "~/components/ui/button"; +import { Form } from "~/components/ui/form"; + +type RunWorkflowFormProps = { + params: Record; + onSubmit: (params: Record) => void; +}; + +export function RunWorkflowForm({ params, onSubmit }: RunWorkflowFormProps) { + const defaultValues = useMemo(() => { + return Object.keys(params).reduce( + (acc, key) => { + acc[key] = ""; + return acc; + }, + {} as Record + ); + }, [params]); + + const form = useForm({ defaultValues }); + const handleSubmit = form.handleSubmit(onSubmit); + + return ( +
+ + {Object.entries(params).map(([name, description]) => ( + + ))} + + + + + ); +} diff --git a/ui/admin/app/routes/_auth.workflows.$workflow.tsx b/ui/admin/app/routes/_auth.workflows.$workflow.tsx index fe2d1e94..1510c347 100644 --- a/ui/admin/app/routes/_auth.workflows.$workflow.tsx +++ b/ui/admin/app/routes/_auth.workflows.$workflow.tsx @@ -57,6 +57,7 @@ export default function ChatAgent() { id={workflow.id} mode="workflow" threadId={threadId} + params={workflow.params} onCreateThreadId={(threadId) => navigate( $path( From 0711a7960fdfd13aa02b31318c8a2bb5f366509b Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Thu, 21 Nov 2024 17:36:50 -0600 Subject: [PATCH 4/5] fix: load workflow directly when invoking --- ui/admin/app/components/chat/Chat.tsx | 4 +-- ui/admin/app/components/chat/ChatContext.tsx | 4 --- ui/admin/app/components/chat/RunWorkflow.tsx | 26 ++++++++++++++----- .../app/routes/_auth.workflows.$workflow.tsx | 18 +++++-------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/ui/admin/app/components/chat/Chat.tsx b/ui/admin/app/components/chat/Chat.tsx index 4dd409da..3d00ca85 100644 --- a/ui/admin/app/components/chat/Chat.tsx +++ b/ui/admin/app/components/chat/Chat.tsx @@ -13,6 +13,7 @@ type ChatProps = React.HTMLAttributes & { export function Chat({ className }: ChatProps) { const { + id, messages, threadId, mode, @@ -20,7 +21,6 @@ export function Chat({ className }: ChatProps) { readOnly, isInvoking, isRunning, - params, } = useChat(); const [runTriggered, setRunTriggered] = useState(false); @@ -50,7 +50,7 @@ export function Chat({ className }: ChatProps) { })} > { setRunTriggered(true); invoke(params && JSON.stringify(params)); diff --git a/ui/admin/app/components/chat/ChatContext.tsx b/ui/admin/app/components/chat/ChatContext.tsx index 83346cf3..d5d1661f 100644 --- a/ui/admin/app/components/chat/ChatContext.tsx +++ b/ui/admin/app/components/chat/ChatContext.tsx @@ -27,7 +27,6 @@ interface ChatContextType { readOnly?: boolean; isRunning: boolean; isInvoking: boolean; - params?: Record; } const ChatContext = createContext(undefined); @@ -39,7 +38,6 @@ export function ChatProvider({ threadId, onCreateThreadId, readOnly, - params, }: { children: ReactNode; mode?: Mode; @@ -47,7 +45,6 @@ export function ChatProvider({ threadId?: Nullish; onCreateThreadId?: (threadId: string) => void; readOnly?: boolean; - params?: Record; }) { const invoke = (prompt?: string) => { if (readOnly) return; @@ -83,7 +80,6 @@ export function ChatProvider({ isRunning, isInvoking: invokeAgent.isLoading, readOnly, - params, }} > {children} diff --git a/ui/admin/app/components/chat/RunWorkflow.tsx b/ui/admin/app/components/chat/RunWorkflow.tsx index fe4fc04d..2736df60 100644 --- a/ui/admin/app/components/chat/RunWorkflow.tsx +++ b/ui/admin/app/components/chat/RunWorkflow.tsx @@ -1,4 +1,7 @@ import { useState } from "react"; +import useSWR from "swr"; + +import { WorkflowService } from "~/lib/service/api/workflowService"; import { RunWorkflowForm } from "~/components/chat/RunWorkflowForm"; import { Button, ButtonProps } from "~/components/ui/button"; @@ -9,24 +12,31 @@ import { } from "~/components/ui/popover"; type RunWorkflowProps = { - params?: Record; onSubmit: (params?: Record) => void; + workflowId: string; }; export function RunWorkflow({ - params, + workflowId, onSubmit, - disabled, ...props }: RunWorkflowProps & ButtonProps) { const [open, setOpen] = useState(false); - if (!params) + const { data: workflow, isLoading } = useSWR( + WorkflowService.getWorkflowById.key(workflowId), + ({ workflowId }) => WorkflowService.getWorkflowById(workflowId) + ); + + const params = workflow?.params; + + if (!params || isLoading) return ( @@ -35,7 +45,11 @@ export function RunWorkflow({ return ( - diff --git a/ui/admin/app/routes/_auth.workflows.$workflow.tsx b/ui/admin/app/routes/_auth.workflows.$workflow.tsx index 1510c347..654ecd6e 100644 --- a/ui/admin/app/routes/_auth.workflows.$workflow.tsx +++ b/ui/admin/app/routes/_auth.workflows.$workflow.tsx @@ -5,10 +5,10 @@ import { useNavigate, } from "@remix-run/react"; import { $path } from "remix-routes"; +import { preload } from "swr"; import { WorkflowService } from "~/lib/service/api/workflowService"; import { RouteQueryParams, RouteService } from "~/lib/service/routeService"; -import { noop } from "~/lib/utils"; import { Chat } from "~/components/chat"; import { ChatProvider } from "~/components/chat/ChatContext"; @@ -31,19 +31,16 @@ export const clientLoader = async ({ params ); - if (!pathParams.workflow) { - throw redirect($path("/workflows")); - } + if (!pathParams.workflow) throw redirect($path("/workflows")); - const workflow = await WorkflowService.getWorkflowById( - pathParams.workflow - ).catch(noop); + const workflow = await preload( + WorkflowService.getWorkflowById.key(pathParams.workflow), + () => WorkflowService.getWorkflowById(pathParams.workflow) + ); if (!workflow) throw redirect($path("/workflows")); - const { threadId } = query ?? {}; - - return { workflow, threadId }; + return { workflow, threadId: query?.threadId }; }; export default function ChatAgent() { @@ -57,7 +54,6 @@ export default function ChatAgent() { id={workflow.id} mode="workflow" threadId={threadId} - params={workflow.params} onCreateThreadId={(threadId) => navigate( $path( From c07279345f4786e698379c2047ff12e33a2584b7 Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Thu, 21 Nov 2024 18:02:50 -0600 Subject: [PATCH 5/5] feat: move workflow param form to invoke button on inital trigger --- ui/admin/app/components/chat/Chat.tsx | 3 +++ ui/admin/app/components/chat/RunWorkflow.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/admin/app/components/chat/Chat.tsx b/ui/admin/app/components/chat/Chat.tsx index 3d00ca85..07d190c6 100644 --- a/ui/admin/app/components/chat/Chat.tsx +++ b/ui/admin/app/components/chat/Chat.tsx @@ -58,6 +58,9 @@ export function Chat({ className }: ChatProps) { className={cn({ "w-full": threadId, })} + popoverContentProps={{ + sideOffset: !threadId ? -150 : undefined, + }} loading={isInvoking || isRunning} disabled={isInvoking || isRunning} > diff --git a/ui/admin/app/components/chat/RunWorkflow.tsx b/ui/admin/app/components/chat/RunWorkflow.tsx index 2736df60..f27a668b 100644 --- a/ui/admin/app/components/chat/RunWorkflow.tsx +++ b/ui/admin/app/components/chat/RunWorkflow.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { ComponentProps, useState } from "react"; import useSWR from "swr"; import { WorkflowService } from "~/lib/service/api/workflowService"; @@ -14,6 +14,7 @@ import { type RunWorkflowProps = { onSubmit: (params?: Record) => void; workflowId: string; + popoverContentProps?: ComponentProps; }; export function RunWorkflow({ @@ -49,12 +50,16 @@ export function RunWorkflow({ {...props} disabled={props.disabled || open || isLoading} loading={props.loading || isLoading} + onClick={() => setOpen((prev) => !prev)} > Run Workflow - + {