diff --git a/apiclient/types/assitant.go b/apiclient/types/assitant.go index 35f01e7d..c43e871f 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 984526e3..bd21d631 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/pkg/storage/openapi/generated/openapi_generated.go b/pkg/storage/openapi/generated/openapi_generated.go index d35416e9..22b2faa4 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/Agent.tsx b/ui/admin/app/components/agent/Agent.tsx index b07249e9..70b8d9b3 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 00000000..deb976d9 --- /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 d3bcf515..ad1a1f48 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 9224fd3c..32a95f02 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 7d987b2c..a21555d8 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 00000000..207940bf --- /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 84eb740d..fdbcce80 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 00000000..1582a740 --- /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 b6480139..d738c14f 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" > - +