diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 7507e24d..0831891f 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -181,7 +181,6 @@ function ChatBodyData(props: ChatBodyDataProps) { conversationId={conversationId} isOpen={props.isChatSideBarOpen} onOpenChange={props.onChatSideBarOpenChange} - preexistingAgent={agentMetadata} isMobileWidth={props.isMobileWidth} /> ); diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index f0b46e1e..ec4fe062 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -17,7 +17,7 @@ import { Lightbulb, ArrowDown, XCircle } from "@phosphor-icons/react"; import AgentProfileCard from "../profileCard/profileCard"; import { getIconFromIconName } from "@/app/common/iconUtils"; -import { AgentData } from "@/app/agents/page"; +import { AgentData } from "../agentCard/agentCard"; import React from "react"; import { useIsMobileWidth } from "@/app/common/utils"; import { Button } from "@/components/ui/button"; @@ -281,12 +281,20 @@ export default function ChatHistory(props: ChatHistoryProps) { function constructAgentName() { if (!data || !data.agent || !data.agent?.name) return `Agent`; + if (data.agent.is_hidden) return 'Khoj'; + console.log(data.agent); return data.agent?.name; } function constructAgentPersona() { - if (!data || !data.agent || !data.agent?.persona) + if (!data || !data.agent) { return `Your agent is no longer available. You will be reset to the default agent.`; + } + + if (!data.agent?.persona) { + return `You can set a persona for your agent in the Chat Options side panel.`; + } + return data.agent?.persona; } diff --git a/src/interface/web/app/components/chatSidebar/chatSidebar.tsx b/src/interface/web/app/components/chatSidebar/chatSidebar.tsx index 8047a97b..8c2ca249 100644 --- a/src/interface/web/app/components/chatSidebar/chatSidebar.tsx +++ b/src/interface/web/app/components/chatSidebar/chatSidebar.tsx @@ -1,19 +1,19 @@ "use client" -import { ArrowsDownUp, Bell, CaretCircleDown, Sparkle } from "@phosphor-icons/react"; +import { ArrowsDownUp, CaretCircleDown, CircleNotch, Sparkle } from "@phosphor-icons/react"; import { Button } from "@/components/ui/button"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; -import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Textarea } from "@/components/ui/textarea"; import { ModelSelector } from "@/app/common/modelSelector"; import { FilesMenu } from "../allConversations/allConversations"; import { AgentConfigurationOptions } from "@/app/agents/page"; import useSWR from "swr"; +import { mutate } from "swr"; import { Sheet, SheetContent } from "@/components/ui/sheet"; import { AgentData } from "../agentCard/agentCard"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { getIconForSlashCommand, getIconFromIconName } from "@/app/common/iconUtils"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; @@ -22,7 +22,6 @@ import { TooltipContent } from "@radix-ui/react-tooltip"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; interface ChatSideBarProps { - preexistingAgent?: AgentData | null; conversationId: string; isOpen: boolean; isMobileWidth?: boolean; @@ -48,47 +47,162 @@ export function ChatSidebar({ ...props }: ChatSideBarProps) { } return ( - +
+ +
); } function ChatSidebarInternal({ ...props }: ChatSideBarProps) { - const isEditable = props.preexistingAgent?.name.toLowerCase() === "khoj" || props.preexistingAgent?.is_hidden === true; - const isDefaultAgent = props.preexistingAgent?.name.toLowerCase() === "khoj"; + const [isEditable, setIsEditable] = useState(false); + const [isDefaultAgent, setIsDefaultAgent] = useState(false); const { data: agentConfigurationOptions, error: agentConfigurationOptionsError } = useSWR("/api/agents/options", fetcher); - const [customPrompt, setCustomPrompt] = useState(!isDefaultAgent && props.preexistingAgent ? props.preexistingAgent.persona : "always respond in spanish"); - const [selectedModel, setSelectedModel] = useState(props.preexistingAgent?.chat_model); - const [inputTools, setInputTools] = useState(props.preexistingAgent?.input_tools); + const { data: agentData, error: agentDataError } = useSWR(`/api/agents/conversation?conversation_id=${props.conversationId}`, fetcher); + const [customPrompt, setCustomPrompt] = useState(""); + const [selectedModel, setSelectedModel] = useState(); + const [inputTools, setInputTools] = useState(); + const [outputModes, setOutputModes] = useState(); + const [hasModified, setHasModified] = useState(false); - function isValueChecked(value: string, existingSelections: string[] | undefined): boolean { - if (existingSelections === undefined || existingSelections === null || existingSelections.length === 0) { - return true; + const [isSaving, setIsSaving] = useState(false); + + function setupAgentData() { + if (agentData) { + setSelectedModel(agentData.chat_model); + setInputTools(agentData.input_tools); + if (agentData.input_tools === undefined || agentData.input_tools.length === 0) { + setInputTools(agentConfigurationOptions?.input_tools ? Object.keys(agentConfigurationOptions.input_tools) : []); + } + setOutputModes(agentData.output_modes); + if (agentData.output_modes === undefined || agentData.output_modes.length === 0) { + setOutputModes(agentConfigurationOptions?.output_modes ? Object.keys(agentConfigurationOptions.output_modes) : []); + } + + if (agentData.name.toLowerCase() === "khoj" || agentData.is_hidden === true) { + setIsEditable(true); + } + + if (agentData.slug.toLowerCase() === "khoj") { + setIsDefaultAgent(true); + } else { + setCustomPrompt(agentData.persona); + } } + } + + useEffect(() => { + setupAgentData(); + }, [agentData]); + + + function isValueChecked(value: string, existingSelections: string[]): boolean { + console.log("isValueChecked", value, existingSelections); return existingSelections.includes(value); } + function handleCheckToggle(value: string, existingSelections: string[]): string[] { + console.log("handleCheckToggle", value, existingSelections); + + setHasModified(true); + + if (existingSelections.includes(value)) { + return existingSelections.filter((v) => v !== value); + } + + return [...existingSelections, value]; + } + + function handleCustomPromptChange(value: string) { + setCustomPrompt(value); + setHasModified(true); + } + + function handleSave() { + if (hasModified) { + + if (agentData?.is_hidden === false) { + alert("This agent is not a hidden agent. It cannot be modified from this interface."); + return; + } + + let mode = "PATCH"; + + if (isDefaultAgent) { + mode = "POST"; + } + + const data = { + persona: customPrompt, + chat_model: selectedModel, + input_tools: inputTools, + output_modes: outputModes, + ...(isDefaultAgent ? {} : { slug: agentData?.slug }) + }; + + console.log("outgoing data payload", data); + setIsSaving(true); + + const url = !isDefaultAgent ? `/api/agents/hidden` : `/api/agents/hidden?conversation_id=${props.conversationId}`; + + // There are four scenarios here. + // 1. If the agent is a default agent, then we need to create a new agent just to associate with this conversation. + // 2. If the agent is not a default agent, then we need to update the existing hidden agent. This will be associated using the `slug` field. + // 3. If the agent is a "proper" agent and not a hidden agent, then it cannot be updated from this API. + // 4. The API is being called before the new conversation has been provisioned. If this happens, then create the agent and associate it with the conversation. Reroute the user to the new conversation page. + fetch(url, { + method: mode, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) + }) + .then((res) => { + setIsSaving(false); + res.json() + }) + .then((data) => { + mutate(`/api/agents/conversation?conversation_id=${props.conversationId}`); + setHasModified(false); + }) + .catch((error) => { + console.error("Error:", error); + setIsSaving(false); + }); + } + } + + function handleReset() { + setupAgentData(); + setHasModified(false); + } + + function handleModelSelect(model: string) { + setSelectedModel(model); + setHasModified(true); + } + return ( { - props.preexistingAgent && !isEditable ? ( + agentData && !isEditable ? ( ) : ( @@ -103,7 +217,7 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) { { - props.preexistingAgent && props.preexistingAgent.has_files ? ( + agentData && agentData.has_files ? (
@@ -123,7 +237,7 @@ function ChatSidebarInternal({ ...props }: ChatSideBarProps) {