diff --git a/sdk-react/demo/TestPage.tsx b/sdk-react/demo/TestPage.tsx index 2cb1bd15..c841c219 100644 --- a/sdk-react/demo/TestPage.tsx +++ b/sdk-react/demo/TestPage.tsx @@ -3,7 +3,7 @@ import { useRun } from "../src"; import { useAgent } from "../src/hooks/useAgent"; export function TestPage(props: {}) { - const runConfig = useRun({ + const run = useRun({ clusterId: "01J7M4V93BBZP3YJYSKPDEGZ2T", baseUrl: "https://api.inferable.ai", authType: "custom", @@ -11,14 +11,13 @@ export function TestPage(props: {}) { }); const { Trigger, Pane } = useAgent({ - initialMessage: "System Status", - run: runConfig, - userInputs: ["Times to ping"], + prompt: "Ping the server, and return the system status at the time of the ping.", + run, }); return (
- Get the system status + Check system
); diff --git a/sdk-react/src/hooks/useAgent.css b/sdk-react/src/hooks/useAgent.css index dc572230..635c0385 100644 --- a/sdk-react/src/hooks/useAgent.css +++ b/sdk-react/src/hooks/useAgent.css @@ -51,92 +51,6 @@ background-color: #e4e4e7; } -.agent-form { - display: flex; - flex-direction: column; - gap: 0.75rem; - font-family: var(--font-sans); - width: 90%; - padding: 0.5rem; - flex-shrink: 0; -} - -.agent-form-group { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.agent-label { - font-size: 0.75rem; - font-weight: 500; - letter-spacing: -0.011em; - color: var(--color-text-secondary); -} - -.agent-form .agent-input { - padding: 0.375rem 0.75rem; - font-size: 0.75rem; - border-radius: 0.375rem; -} - -.agent-form .agent-button { - align-self: flex-start; - padding: 0.375rem 0.75rem; - font-size: 0.75rem; - border-radius: 0.375rem; -} - -.agent-input { - width: 100%; - padding: 0.5rem 0.75rem; - font-family: var(--font-sans); - font-size: 0.875rem; - letter-spacing: -0.011em; - color: var(--color-text-primary); - border: 1px solid var(--color-border); - border-radius: 0.375rem; - background: var(--color-background); - transition: all 0.2s ease; -} - -.agent-input:focus { - outline: none; - border-color: var(--color-text-tertiary); - box-shadow: 0 0 0 0.125rem rgba(24, 24, 27, 0.05); -} - -.agent-input::placeholder { - color: #9ca3af; -} - -.agent-button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5rem 1rem; - font-family: var(--font-sans); - font-size: 0.875rem; - font-weight: 500; - letter-spacing: -0.011em; - color: var(--color-background); - background-color: var(--color-primary); - border-radius: 0.375rem; - border: 1px solid var(--color-primary); - cursor: pointer; - transition: all 0.2s ease; -} - -.agent-button:hover { - background-color: var(--color-primary-hover); - border-color: var(--color-primary-hover); -} - -.agent-button:focus { - outline: none; - box-shadow: 0 0 0 0.125rem rgba(24, 24, 27, 0.05); -} - .agent-pane { position: fixed; top: 0; @@ -159,7 +73,6 @@ bottom: auto; right: auto; height: 31.25rem; - border-radius: 0.5rem; overflow: hidden; } @@ -169,7 +82,6 @@ flex-direction: column; background: transparent; backdrop-filter: blur(10px); - border-radius: 0.5rem; border: 1px solid var(--color-border); overflow: hidden; position: relative; @@ -188,10 +100,9 @@ display: flex; justify-content: flex-start; background: transparent; - position: sticky; - bottom: 0; width: 100%; - margin-top: 1rem; + margin-top: 0; + flex-shrink: 0; } .agent-message-composer form { @@ -229,7 +140,6 @@ display: flex; align-items: center; gap: 0.5rem; - width: calc(100% - 4rem); } .agent-message-input { @@ -283,10 +193,9 @@ } .agent-status { - font-size: 0.7rem; - color: var(--color-text-tertiary); - margin-top: 0.5rem; - margin-left: 0.25rem; + color: var(--color-text-secondary); + margin: 0; + font-size: 0.75rem; } .agent-bottom-bar { @@ -305,13 +214,3 @@ z-index: 10; flex-shrink: 0; } - -.agent-status { - color: var(--color-text-secondary); - margin: 0; -} - -.agent-message-composer { - margin-top: 0; - flex-shrink: 0; -} diff --git a/sdk-react/src/hooks/useAgent.tsx b/sdk-react/src/hooks/useAgent.tsx index 5d9c2c5e..a322681a 100644 --- a/sdk-react/src/hooks/useAgent.tsx +++ b/sdk-react/src/hooks/useAgent.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Message } from "../ui/message"; +import { Message, MessageLine } from "../ui/message"; import { useRun } from "./useRun"; import { z } from "zod"; import "./useAgent.css"; @@ -12,8 +12,7 @@ interface Message { } type UseAgentProps = { - initialMessage: string; - userInputs?: string[]; + prompt: string; run: ReturnType; }; @@ -22,11 +21,17 @@ type UseAgentReturn = { Pane: React.FC<{ floating?: boolean }>; }; -export function useAgent({ - initialMessage, - userInputs: userInputSchema, - run, -}: UseAgentProps): UseAgentReturn { +const humanStatus = (run: ReturnType["run"]) => { + if (!run) return "Processing request..."; + if (run.status === "done") return "Completed"; + if (run.status === "running") return "Running..."; + if (run.status === "paused") return "Paused"; + if (run.status === "pending") return "Pending..."; + if (run.status === "failed") return "An error occurred"; + return ""; +}; + +export function useAgent({ prompt: initialMessage, run }: UseAgentProps): UseAgentReturn { const [isPaneOpen, setIsPaneOpen] = useState(false); const triggerRef = React.useRef(null); const [panePosition, setPanePosition] = useState({ top: 0, left: 0 }); @@ -42,16 +47,33 @@ export function useAgent({ } }, [isPaneOpen]); + const initRunWithMessage = useCallback( + (message: string) => { + run + .init() + .then(() => + run.createMessage({ + message, + type: "human", + }) + ) + .catch(error => { + console.error(error); + }); + }, + [run] + ); + const Trigger: React.FC<{ children?: React.ReactNode }> = ({ children }) => { return ( { - if (isPaneOpen) { - setIsPaneOpen(false); - } else { - setIsPaneOpen(true); + setIsPaneOpen(true); + + if (!run.run?.id) { + initRunWithMessage(initialMessage); } }} > @@ -61,41 +83,6 @@ export function useAgent({ }; const Pane: React.FC<{ floating?: boolean }> = ({ floating }) => { - const hasForm = userInputSchema && Object.keys(userInputSchema).length > 0; - - const initRunWithMessage = useCallback( - (message: string) => { - run - .init() - .then(() => - run.createMessage({ - message, - type: "human", - }) - ) - .catch(error => { - console.error(error); - }); - }, - [run] - ); - - const handleFormSubmit = useCallback( - (formData: Record) => { - const messageWithData = JSON.stringify({ message: initialMessage, data: formData }); - initRunWithMessage(messageWithData); - }, - [initialMessage, initRunWithMessage] - ); - - useEffect(() => { - if (!isPaneOpen || !run.run?.id || run.messages.length > 0) return; - - if (!hasForm) { - initRunWithMessage(initialMessage); - } - }, [isPaneOpen, run.run?.id, run.messages.length, hasForm, initialMessage, initRunWithMessage]); - if (!isPaneOpen) return null; const paneStyle = floating @@ -107,107 +94,90 @@ export function useAgent({ width: "500px", boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", } - : undefined; + : { + position: "fixed" as const, + width: "60%", + right: "0", + bottom: "0", + top: "0", + height: "100%", + margin: "0", + }; return (
- {hasForm && run.messages.length === 0 && ( -
{ - e.preventDefault(); - const formData = new FormData(e.target as HTMLFormElement); - const formEntries = Object.fromEntries( - Array.from(formData.entries()).map(([key, value]) => [key, value.toString()]) - ) as Record; - handleFormSubmit(formEntries); - }} - className="agent-form" - > - {userInputSchema?.map(key => ( -
- - -
- ))} - - - )}
{run.messages .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) - .map((message, index) => ( - - ))} + .map((message, index) => + index === 0 ? ( + ${humanStatus(run.run)}`} + type={"human"} + /> + ) : ( + + ) + )}
-

- {run.run?.status === "done" - ? "Completed" - : run.run?.status === "running" - ? "Running..." - : run.run?.status === "paused" - ? "Paused" - : run.run?.status === "pending" - ? "Pending..." - : run.run?.status === "failed" - ? "An error occurred" - : ""} -

- {run.run?.status === "done" && ( -
- {!isComposing ? ( -
- + +
+ ) : ( +
{ + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const message = formData.get("message")?.toString(); + if (message) { + initRunWithMessage(message); + (e.target as HTMLFormElement).reset(); + } + setIsComposing(false); + }} + > +
+ + -
- ) : ( - { - e.preventDefault(); - const formData = new FormData(e.currentTarget); - const message = formData.get("message")?.toString(); - if (message) { - initRunWithMessage(message); - (e.target as HTMLFormElement).reset(); - } - }} - > -
- - -
-
- )} -
- )} + + )} +
diff --git a/sdk-react/src/hooks/useRun.ts b/sdk-react/src/hooks/useRun.ts index 0c7dc927..f835c714 100644 --- a/sdk-react/src/hooks/useRun.ts +++ b/sdk-react/src/hooks/useRun.ts @@ -102,8 +102,8 @@ export function useRun>(options: UseRunOptions): U const runId = useRef(); - useInterval(async () => { - if (!runId.current || !initialized) { + const poll = useCallback(async () => { + if (!runId.current) { return; } @@ -153,7 +153,19 @@ export function useRun>(options: UseRunOptions): U } catch (error) { options.onError?.(error instanceof Error ? error : new Error(String(error))); } - }, options.pollInterval || 1000); + + await new Promise(resolve => setTimeout(resolve, options.pollInterval || 1000)); + + return poll(); + }, [client]); + + useEffect(() => { + if (!runId.current || !initialized) { + return; + } + + poll(); + }, [poll, initialized, runId]); const createMessage = useCallback( async (input: CreateMessageInput) => { diff --git a/sdk-react/src/ui/message.tsx b/sdk-react/src/ui/message.tsx index 1f162774..6be5b8b7 100644 --- a/sdk-react/src/ui/message.tsx +++ b/sdk-react/src/ui/message.tsx @@ -27,9 +27,6 @@ const messageSchema = z.object({ type MessageProps = { message: unknown; - cellNumber?: number; - isLastMessage?: boolean; - className?: string; }; const msgs = (m: z.infer) => { @@ -93,7 +90,7 @@ export const MessageLine = ({ ); }; -export function Message({ message, cellNumber, isLastMessage = false, className }: MessageProps) { +export function Message({ message }: MessageProps) { const parsedMessage = messageSchema.safeParse(message); if (!parsedMessage.success) {