From 27d966ec5c3a737c82e1c3903a0e1cd11c4f7f09 Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Thu, 14 Nov 2024 15:11:46 -0600 Subject: [PATCH 1/2] feat: admin ui - display information on agent when refName conflict --- apiclient/types/assitant.go | 1 + pkg/api/handlers/assistants.go | 1 + ui/admin/app/components/agent/Agent.tsx | 30 ++---- .../components/agent/AgentPublishStatus.tsx | 97 +++++++++++++++++++ ui/admin/app/components/agent/Publish.tsx | 21 ++-- ui/admin/app/components/agent/ToolForm.tsx | 2 +- ui/admin/app/components/agent/Unpublish.tsx | 18 +--- ui/admin/app/lib/model/agents.ts | 10 +- ui/admin/app/lib/model/assistants.ts | 10 ++ ui/admin/app/lib/routers/apiRoutes.ts | 30 ++++++ .../lib/service/api/assistantApiService.ts | 16 +++ ui/admin/app/routes/_auth.agents.$agent.tsx | 6 +- 12 files changed, 189 insertions(+), 53 deletions(-) create mode 100644 ui/admin/app/components/agent/AgentPublishStatus.tsx create mode 100644 ui/admin/app/lib/model/assistants.ts create mode 100644 ui/admin/app/lib/service/api/assistantApiService.ts diff --git a/apiclient/types/assitant.go b/apiclient/types/assitant.go index 35f01e7dc..8093dd86f 100644 --- a/apiclient/types/assitant.go +++ b/apiclient/types/assitant.go @@ -5,6 +5,7 @@ type Assistant struct { Name string `json:"name"` Description string `json:"description"` Icons AgentIcons `json:"icons"` + EntityId string `json:"entityId"` } type AssistantList List[Assistant] diff --git a/pkg/api/handlers/assistants.go b/pkg/api/handlers/assistants.go index 984526e3b..1319e48e8 100644 --- a/pkg/api/handlers/assistants.go +++ b/pkg/api/handlers/assistants.go @@ -120,6 +120,7 @@ func convertAssistant(agent v1.Agent) types.Assistant { Metadata: MetadataFrom(&agent), Name: agent.Spec.Manifest.Name, Description: agent.Spec.Manifest.Description, + EntityId: agent.ObjectMeta.Name, Icons: icons, } assistant.ID = agent.Spec.Manifest.RefName diff --git a/ui/admin/app/components/agent/Agent.tsx b/ui/admin/app/components/agent/Agent.tsx index b07249e93..70b8d9b3a 100644 --- a/ui/admin/app/components/agent/Agent.tsx +++ b/ui/admin/app/components/agent/Agent.tsx @@ -7,11 +7,9 @@ import { cn } from "~/lib/utils"; import { TypographyH4, TypographyP } from "~/components/Typography"; import { useAgent } from "~/components/agent/AgentContext"; import { AgentForm } from "~/components/agent/AgentForm"; +import { AgentPublishStatus } from "~/components/agent/AgentPublishStatus"; import { PastThreads } from "~/components/agent/PastThreads"; -import { Publish } from "~/components/agent/Publish"; import { ToolForm } from "~/components/agent/ToolForm"; -import { Unpublish } from "~/components/agent/Unpublish"; -import { CopyText } from "~/components/composed/CopyText"; import { AgentKnowledgePanel } from "~/components/knowledge"; import { Button } from "~/components/ui/button"; import { Card } from "~/components/ui/card"; @@ -52,26 +50,11 @@ export function Agent({ className, onRefresh }: AgentProps) { return (
-
- {agentUpdates.refName ? ( - - ) : ( -
- )} - - {agentUpdates.refName ? ( - - ) : ( - - )} -
+ + ); } + function convertTools( tools: { tool: string; variant: "fixed" | "default" | "available" }[] ) { diff --git a/ui/admin/app/components/agent/AgentPublishStatus.tsx b/ui/admin/app/components/agent/AgentPublishStatus.tsx new file mode 100644 index 000000000..cf6947911 --- /dev/null +++ b/ui/admin/app/components/agent/AgentPublishStatus.tsx @@ -0,0 +1,97 @@ +import { Link } from "@remix-run/react"; +import { useMemo } from "react"; +import { $path } from "remix-routes"; +import useSWR from "swr"; + +import { AgentBase } from "~/lib/model/agents"; +import { AssistantApiService } from "~/lib/service/api/assistantApiService"; + +import { TypographySmall } from "~/components/Typography"; +import { Publish } from "~/components/agent/Publish"; +import { Unpublish } from "~/components/agent/Unpublish"; +import { CopyText } from "~/components/composed/CopyText"; + +type AgentPublishStatusProps = { + agent: AgentBase; + onChange: (agent: Partial) => void; +}; + +export function AgentPublishStatus({ + agent, + onChange, +}: AgentPublishStatusProps) { + const getAssistants = useSWR( + () => + agent.refName && !agent.refNameAssigned + ? AssistantApiService.getAssistants.key() + : null, + () => AssistantApiService.getAssistants() + ); + + const refAgent = useMemo(() => { + if (!getAssistants.data) return null; + + return getAssistants.data.find(({ id }) => id === agent.refName); + }, [getAssistants.data, agent.refName]); + + return ( +
+ {renderAgentRef()} + + {agent.refName ? ( + onChange({ refName: "" })} /> + ) : ( + onChange({ refName })} + /> + )} +
+ ); + + function renderAgentRef() { + if (!agent.refName) return
; + + if (refAgent) { + const route = + refAgent.type === "agent" + ? $path("/agents/:agent", { + agent: refAgent.entityId, + }) + : $path("/workflows/:workflow", { + workflow: refAgent.entityId, + }); + + return ( +
+
+
+ Unavailable +
+ + + + Ref name {refAgent.id} used by{" "} + + + {refAgent.name} + + +
+ ); + } + + if (!agent.refNameAssigned) return
; + + return ( + + ); + } +} diff --git a/ui/admin/app/components/agent/Publish.tsx b/ui/admin/app/components/agent/Publish.tsx index d3bcf5152..ad1a1f482 100644 --- a/ui/admin/app/components/agent/Publish.tsx +++ b/ui/admin/app/components/agent/Publish.tsx @@ -1,8 +1,6 @@ import { Eye } from "lucide-react"; import { useState } from "react"; -import { Agent } from "~/lib/model/agents"; - import { TypographyMuted, TypographyMutedAccent, @@ -20,19 +18,18 @@ import { Input } from "~/components/ui/input"; type PublishProps = { className?: string; - agent: Agent; - onChange: (agent: Agent) => void; + refName: string; + onPublish: (refName: string) => void; }; -export function Publish({ className, agent, onChange }: PublishProps) { - const [refName, setRefName] = useState(agent.refName); +export function Publish({ + className, + refName: _refName, + onPublish, +}: PublishProps) { + const [refName, setRefName] = useState(_refName); - const handlePublish = () => { - onChange({ - ...agent, - refName, - }); - }; + const handlePublish = () => onPublish(refName); return ( diff --git a/ui/admin/app/components/agent/ToolForm.tsx b/ui/admin/app/components/agent/ToolForm.tsx index 9224fd3cd..32a95f021 100644 --- a/ui/admin/app/components/agent/ToolForm.tsx +++ b/ui/admin/app/components/agent/ToolForm.tsx @@ -166,7 +166,7 @@ export function ToolForm({ onDelete={() => removeTools([field.tool])} actions={ - + void; + onUnpublish: () => void; }; -export function Unpublish({ onChange }: PublishProps) { - const { agent } = useAgent(); - +export function Unpublish({ onUnpublish }: UnpublishProps) { return ( { - onChange({ - ...agent, - refName: "", - }); - }} + onConfirm={() => onUnpublish()} confirmProps={{ variant: "destructive", children: "Unpublish", diff --git a/ui/admin/app/lib/model/agents.ts b/ui/admin/app/lib/model/agents.ts index 7d987b2cd..a21555d87 100644 --- a/ui/admin/app/lib/model/agents.ts +++ b/ui/admin/app/lib/model/agents.ts @@ -8,6 +8,7 @@ export type AgentBase = { temperature?: number; cache?: boolean; refName: string; + refNameAssigned?: boolean; prompt: string; agents?: string[]; workflows?: string[]; @@ -28,10 +29,15 @@ export type AgentOAuthStatus = { export type Agent = EntityMeta & AgentBase & { - slugAssigned: boolean; - } & { authStatus?: Record; }; export type CreateAgent = AgentBase; export type UpdateAgent = AgentBase; + +export type AgentIcons = { + icon: string; + iconDark: string; + collapsed: string; + collapsedDark: string; +}; diff --git a/ui/admin/app/lib/model/assistants.ts b/ui/admin/app/lib/model/assistants.ts new file mode 100644 index 000000000..b73af7b6a --- /dev/null +++ b/ui/admin/app/lib/model/assistants.ts @@ -0,0 +1,10 @@ +import { AgentIcons } from "~/lib/model/agents"; +import { EntityMeta } from "~/lib/model/primitives"; + +export type Assistant = EntityMeta & { + name: string; + entityId: string; + description: string; + icons: AgentIcons; + type: "agent" | "workflow"; +}; diff --git a/ui/admin/app/lib/routers/apiRoutes.ts b/ui/admin/app/lib/routers/apiRoutes.ts index 84eb740da..fdbcce80f 100644 --- a/ui/admin/app/lib/routers/apiRoutes.ts +++ b/ui/admin/app/lib/routers/apiRoutes.ts @@ -30,6 +30,36 @@ const buildUrl = (path: string, params?: object) => { }; export const ApiRoutes = { + assistants: { + base: () => buildUrl("/assistants"), + getAssistants: () => buildUrl("/assistants"), + getCredentials: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/credentials`), + deleteCredential: (assistantId: string, credentialId: string) => + buildUrl(`/assistants/${assistantId}/credentials/${credentialId}`), + getEvents: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/events`), + invoke: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/invoke`), + getTools: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/tools`), + deleteTool: (assistantId: string, toolId: string) => + buildUrl(`/assistants/${assistantId}/tools/${toolId}`), + getFiles: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/files`), + getFileById: (assistantId: string, fileId: string) => + buildUrl(`/assistants/${assistantId}/files/${fileId}`), + uploadFile: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/files`), + deleteFile: (assistantId: string, fileId: string) => + buildUrl(`/assistants/${assistantId}/files/${fileId}`), + getKnowledge: (assistantId: string) => + buildUrl(`/assistants/${assistantId}/knowledge`), + addKnowledge: (assistantId: string, fileName: string) => + buildUrl(`/assistants/${assistantId}/knowledge/${fileName}`), + deleteKnowledge: (assistantId: string, fileName: string) => + buildUrl(`/assistants/${assistantId}/knowledge/${fileName}`), + }, agents: { base: () => buildUrl("/agents"), getById: (agentId: string) => buildUrl(`/agents/${agentId}`), diff --git a/ui/admin/app/lib/service/api/assistantApiService.ts b/ui/admin/app/lib/service/api/assistantApiService.ts new file mode 100644 index 000000000..1582a7403 --- /dev/null +++ b/ui/admin/app/lib/service/api/assistantApiService.ts @@ -0,0 +1,16 @@ +import { Assistant } from "~/lib/model/assistants"; +import { ApiRoutes } from "~/lib/routers/apiRoutes"; +import { request } from "~/lib/service/api/primitives"; + +async function getAssistants() { + const { data } = await request<{ items: Assistant[] }>({ + url: ApiRoutes.assistants.getAssistants().url, + }); + + return data.items ?? []; +} +getAssistants.key = () => ({ url: ApiRoutes.assistants.getAssistants().path }); + +export const AssistantApiService = { + getAssistants, +}; diff --git a/ui/admin/app/routes/_auth.agents.$agent.tsx b/ui/admin/app/routes/_auth.agents.$agent.tsx index b64801390..d738c14f0 100644 --- a/ui/admin/app/routes/_auth.agents.$agent.tsx +++ b/ui/admin/app/routes/_auth.agents.$agent.tsx @@ -82,7 +82,11 @@ export default function ChatAgent() { className="flex-auto" > - + From 9c093557a29beb0564cb90a9226a4ec2710fc57b Mon Sep 17 00:00:00 2001 From: Ryan Hopper-Lowe Date: Fri, 15 Nov 2024 08:38:30 -0600 Subject: [PATCH 2/2] fix: update assistant entity id to follow go conventions --- apiclient/types/assitant.go | 2 +- pkg/api/handlers/assistants.go | 2 +- pkg/storage/openapi/generated/openapi_generated.go | 9 ++++++++- ui/admin/app/components/agent/AgentPublishStatus.tsx | 4 ++-- ui/admin/app/lib/model/assistants.ts | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apiclient/types/assitant.go b/apiclient/types/assitant.go index 8093dd86f..c43e871fe 100644 --- a/apiclient/types/assitant.go +++ b/apiclient/types/assitant.go @@ -5,7 +5,7 @@ type Assistant struct { Name string `json:"name"` Description string `json:"description"` Icons AgentIcons `json:"icons"` - EntityId string `json:"entityId"` + EntityID string `json:"entityID"` } type AssistantList List[Assistant] diff --git a/pkg/api/handlers/assistants.go b/pkg/api/handlers/assistants.go index 1319e48e8..bd21d6319 100644 --- a/pkg/api/handlers/assistants.go +++ b/pkg/api/handlers/assistants.go @@ -120,7 +120,7 @@ func convertAssistant(agent v1.Agent) types.Assistant { Metadata: MetadataFrom(&agent), Name: agent.Spec.Manifest.Name, Description: agent.Spec.Manifest.Description, - EntityId: agent.ObjectMeta.Name, + EntityID: agent.ObjectMeta.Name, Icons: icons, } assistant.ID = agent.Spec.Manifest.RefName diff --git a/pkg/storage/openapi/generated/openapi_generated.go b/pkg/storage/openapi/generated/openapi_generated.go index d35416e9e..22b2faa44 100644 --- a/pkg/storage/openapi/generated/openapi_generated.go +++ b/pkg/storage/openapi/generated/openapi_generated.go @@ -574,8 +574,15 @@ func schema_otto8_ai_otto8_apiclient_types_Assistant(ref common.ReferenceCallbac Ref: ref("github.com/otto8-ai/otto8/apiclient/types.AgentIcons"), }, }, + "entityID": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, }, - Required: []string{"Metadata", "name", "description", "icons"}, + Required: []string{"Metadata", "name", "description", "icons", "entityID"}, }, }, Dependencies: []string{ diff --git a/ui/admin/app/components/agent/AgentPublishStatus.tsx b/ui/admin/app/components/agent/AgentPublishStatus.tsx index cf6947911..deb976d95 100644 --- a/ui/admin/app/components/agent/AgentPublishStatus.tsx +++ b/ui/admin/app/components/agent/AgentPublishStatus.tsx @@ -56,10 +56,10 @@ export function AgentPublishStatus({ const route = refAgent.type === "agent" ? $path("/agents/:agent", { - agent: refAgent.entityId, + agent: refAgent.entityID, }) : $path("/workflows/:workflow", { - workflow: refAgent.entityId, + workflow: refAgent.entityID, }); return ( diff --git a/ui/admin/app/lib/model/assistants.ts b/ui/admin/app/lib/model/assistants.ts index b73af7b6a..207940bfd 100644 --- a/ui/admin/app/lib/model/assistants.ts +++ b/ui/admin/app/lib/model/assistants.ts @@ -3,7 +3,7 @@ import { EntityMeta } from "~/lib/model/primitives"; export type Assistant = EntityMeta & { name: string; - entityId: string; + entityID: string; description: string; icons: AgentIcons; type: "agent" | "workflow";