diff --git a/pkg/api/handlers/threads.go b/pkg/api/handlers/threads.go index 0dcdc9cc5..a9d43eddf 100644 --- a/pkg/api/handlers/threads.go +++ b/pkg/api/handlers/threads.go @@ -230,7 +230,7 @@ func (a *ThreadHandler) Update(req api.Context) error { return err } for _, newTool := range newThread.Tools { - if !slices.Contains(agent.Spec.Manifest.AvailableThreadTools, newTool) { + if !slices.Contains(agent.Spec.Manifest.AvailableThreadTools, newTool) && !slices.Contains(agent.Spec.Manifest.DefaultThreadTools, newTool) { return types.NewErrBadRequest("tool %s is not available for agent %s", newTool, agent.Name) } } diff --git a/ui/admin/app/components/agent/ToolEntry.tsx b/ui/admin/app/components/agent/ToolEntry.tsx index 7e510dfa0..0e76b6184 100644 --- a/ui/admin/app/components/agent/ToolEntry.tsx +++ b/ui/admin/app/components/agent/ToolEntry.tsx @@ -14,7 +14,7 @@ export function ToolEntry({ actions, }: { tool: string; - onDelete: () => void; + onDelete?: () => void; actions?: React.ReactNode; }) { const { data: toolReference, isLoading } = useSWR( @@ -26,7 +26,7 @@ export function ToolEntry({ return (
-
+
{isLoading ? ( @@ -44,14 +44,16 @@ export function ToolEntry({
{actions} - + {onDelete && ( + + )}
diff --git a/ui/admin/app/components/agent/ToolForm.tsx b/ui/admin/app/components/agent/ToolForm.tsx index 9224fd3cd..0cf6c60ab 100644 --- a/ui/admin/app/components/agent/ToolForm.tsx +++ b/ui/admin/app/components/agent/ToolForm.tsx @@ -166,21 +166,23 @@ export function ToolForm({ onDelete={() => removeTools([field.tool])} actions={ - - - updateVariant( - field.tool, - checked - ? ToolVariant.DEFAULT - : ToolVariant.AVAILABLE - ) - } - /> + +
+ + updateVariant( + field.tool, + checked + ? ToolVariant.DEFAULT + : ToolVariant.AVAILABLE + ) + } + /> +
diff --git a/ui/admin/app/components/chat/Chat.tsx b/ui/admin/app/components/chat/Chat.tsx index f9d057a4f..8968f0963 100644 --- a/ui/admin/app/components/chat/Chat.tsx +++ b/ui/admin/app/components/chat/Chat.tsx @@ -31,7 +31,7 @@ export function Chat({ className }: ChatProps) { const showStartButtonPane = mode === "workflow" && !readOnly; return ( -
+
{showMessagePane && (
+
+ updateThread({ tools })} + agent={agent} + disabled={!thread} + /> +
+
+ ); +} diff --git a/ui/admin/app/components/chat/Chatbar.tsx b/ui/admin/app/components/chat/Chatbar.tsx index 46629d5b9..0c2ac75ed 100644 --- a/ui/admin/app/components/chat/Chatbar.tsx +++ b/ui/admin/app/components/chat/Chatbar.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { cn } from "~/lib/utils"; +import { ChatActions } from "~/components/chat/ChatActions"; import { useChat } from "~/components/chat/ChatContext"; import { LoadingSpinner } from "~/components/ui/LoadingSpinner"; import { Button } from "~/components/ui/button"; @@ -30,11 +31,12 @@ export function Chatbar({ className }: ChatbarProps) { return (
{ if (e.key === "Enter" && !e.shiftKey) { @@ -46,17 +48,27 @@ export function Chatbar({ className }: ChatbarProps) { minHeight={0} onChange={(e) => setInput(e.target.value)} placeholder="Type your message..." + bottomContent={ +
+ + + +
+ } />
- -
); } diff --git a/ui/admin/app/components/chat/chat-actions/FilesInfo.tsx b/ui/admin/app/components/chat/chat-actions/FilesInfo.tsx new file mode 100644 index 000000000..d18ffdca6 --- /dev/null +++ b/ui/admin/app/components/chat/chat-actions/FilesInfo.tsx @@ -0,0 +1,28 @@ +import { PaperclipIcon } from "lucide-react"; + +import { Button } from "~/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "~/components/ui/tooltip"; + +export function FilesInfo() { + return ( + + Files (Coming Soon) + + +
+
+
+
+ ); +} diff --git a/ui/admin/app/components/chat/chat-actions/KnowledgeInfo.tsx b/ui/admin/app/components/chat/chat-actions/KnowledgeInfo.tsx new file mode 100644 index 000000000..80a8c8b87 --- /dev/null +++ b/ui/admin/app/components/chat/chat-actions/KnowledgeInfo.tsx @@ -0,0 +1,63 @@ +import { LibraryIcon } from "lucide-react"; + +import { KnowledgeFile } from "~/lib/model/knowledge"; +import { cn } from "~/lib/utils"; + +import { TypographyMuted } from "~/components/Typography"; +import { Button } from "~/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "~/components/ui/tooltip"; + +export function KnowledgeInfo({ + knowledge, + className, + disabled, +}: { + knowledge: KnowledgeFile[]; + className?: string; + disabled?: boolean; +}) { + return ( + + Knowledge + + + + +
+ + + ); +} diff --git a/ui/admin/app/components/chat/chat-actions/ToolsInfo.tsx b/ui/admin/app/components/chat/chat-actions/ToolsInfo.tsx new file mode 100644 index 000000000..606035550 --- /dev/null +++ b/ui/admin/app/components/chat/chat-actions/ToolsInfo.tsx @@ -0,0 +1,128 @@ +import { WrenchIcon } from "lucide-react"; +import { useMemo } from "react"; + +import { Agent } from "~/lib/model/agents"; +import { cn } from "~/lib/utils"; + +import { TypographyMuted, TypographySmall } from "~/components/Typography"; +import { ToolEntry } from "~/components/agent/ToolEntry"; +import { Button } from "~/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; +import { Switch } from "~/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "~/components/ui/tooltip"; + +type ToolItem = { + tool: string; + isToggleable: boolean; + isEnabled: boolean; +}; + +export function ToolsInfo({ + tools, + className, + agent, + disabled, + onChange, +}: { + tools: string[]; + className?: string; + agent: Nullish; + disabled?: boolean; + onChange: (tools: string[]) => void; +}) { + const toolItems = useMemo(() => { + if (!agent) + return tools.map((tool) => ({ + tool, + isToggleable: false, + isEnabled: true, + })); + + const agentTools = (agent.tools ?? []).map((tool) => ({ + tool, + isToggleable: false, + isEnabled: true, + })); + + const { defaultThreadTools, availableThreadTools } = agent ?? {}; + + const toggleableTools = [ + ...(defaultThreadTools ?? []), + ...(availableThreadTools ?? []), + ].map((tool) => ({ + tool, + isToggleable: true, + isEnabled: tools.includes(tool), + })); + + return [...agentTools, ...toggleableTools]; + }, [tools, agent]); + + const handleToggleTool = (tool: string, checked: boolean) => { + onChange(checked ? [...tools, tool] : tools.filter((t) => t !== tool)); + }; + + return ( + + Tools + + + + +