diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx index 6c447377..a372835e 100644 --- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx +++ b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx @@ -2,7 +2,8 @@ import styles from "./sidePanel.module.css"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { useRef } from "react"; import { mutate } from "swr"; @@ -57,12 +58,16 @@ import { UserCirclePlus, Sidebar, NotePencil, + FunnelSimple, + MagnifyingGlass, } from "@phosphor-icons/react"; interface ChatHistory { conversation_id: string; slug: string; agent_name: string; + agent_icon: string; + agent_color: string; compressed: boolean; created: string; updated: string; @@ -71,8 +76,11 @@ interface ChatHistory { import { DropdownMenu, + DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; @@ -96,6 +104,8 @@ import { modifyFileFilterForConversation } from "@/app/common/chatFunctions"; import { ScrollAreaScrollbar } from "@radix-ui/react-scroll-area"; import { KhojLogoType } from "@/app/components/logo/khojLogo"; import NavMenu from "@/app/components/navMenu/navMenu"; +import { getIconFromIconName } from "@/app/common/iconUtils"; +import AgentProfileCard from "../profileCard/profileCard"; // Define a fetcher function const fetcher = (url: string) => @@ -407,6 +417,12 @@ function SessionsAndFiles(props: SessionsAndFilesProps) { return ( <>
+ {props.data && props.data.length > 5 && ( + + )} ), @@ -444,12 +462,6 @@ function SessionsAndFiles(props: SessionsAndFilesProps) { ))}
- {props.data && props.data.length > 5 && ( - - )} void; } +interface AgentStyle { + color: string; + icon: string; +} + function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { + const [agentsFilter, setAgentsFilter] = useState([]); + const [agentOptions, setAgentOptions] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + + const [agentNameToStyleMap, setAgentNameToStyleMap] = useState>({}); + + useEffect(() => { + if (data) { + const agents: string[] = []; + let agentNameToStyleMapLocal: Record = {}; + Object.keys(data).forEach((timeGrouping) => { + data[timeGrouping].forEach((chatHistory) => { + if (!agents.includes(chatHistory.agent_name) && chatHistory.agent_name) { + agents.push(chatHistory.agent_name); + + agentNameToStyleMapLocal = { + ...agentNameToStyleMapLocal, + [chatHistory.agent_name]: { + color: chatHistory.agent_color, + icon: chatHistory.agent_icon, + }, + }; + } + }); + }); + console.log(agentNameToStyleMapLocal); + setAgentNameToStyleMap(agentNameToStyleMapLocal); + setAgentOptions(agents); + } + }, [data]); + + // Memoize the filtered results + const filteredData = useMemo(() => { + if (!data) return null; + + // Early return if no filters active + if (agentsFilter.length === 0 && searchQuery.length === 0) { + return data; + } + + const filtered: GroupedChatHistory = {}; + const agentSet = new Set(agentsFilter); + const searchLower = searchQuery.toLowerCase(); + + for (const timeGrouping in data) { + const matches = data[timeGrouping].filter((chatHistory) => { + // Early return for agent filter + if (agentsFilter.length > 0 && !agentSet.has(chatHistory.agent_name)) { + return false; + } + + // Early return for search query + if (searchQuery && !chatHistory.slug?.toLowerCase().includes(searchLower)) { + return false; + } + + return true; + }); + + if (matches.length > 0) { + filtered[timeGrouping] = matches; + } + } + + return filtered; + }, [data, agentsFilter, searchQuery]); + return ( - - - See All + + + Find + Conversation All Conversations +
+ { + setSearchQuery(e.target.value); + }} + placeholder="Search conversations" + /> + + + + + + {/* */} + Agents + + {agentOptions.map((agent) => ( + e.preventDefault()} + checked={agentsFilter.includes(agent)} + onCheckedChange={(checked) => { + if (checked) { + setAgentsFilter([...agentsFilter, agent]); + } else { + setAgentsFilter( + agentsFilter.filter((a) => a !== agent), + ); + } + }} + > +
+ {getIconFromIconName( + agentNameToStyleMap[agent]?.icon, + agentNameToStyleMap[agent]?.color, + )} +
{agent}
+
+
+ ))} + {/*
*/} +
+
+
- {data && - Object.keys(data).map((timeGrouping) => ( + {filteredData && + Object.keys(filteredData).map((timeGrouping) => (
{timeGrouping}
- {data[timeGrouping].map((chatHistory) => ( + {filteredData[timeGrouping].map((chatHistory) => ( ))} diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 8c8bf765..5639a01e 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -432,7 +432,15 @@ def chat_sessions( conversations = conversations[:8] sessions = conversations.values_list( - "id", "slug", "title", "agent__slug", "agent__name", "created_at", "updated_at" + "id", + "slug", + "title", + "agent__slug", + "agent__name", + "created_at", + "updated_at", + "agent__style_icon", + "agent__style_color", ) session_values = [ @@ -442,6 +450,8 @@ def chat_sessions( "agent_name": session[4], "created": session[5].strftime("%Y-%m-%d %H:%M:%S"), "updated": session[6].strftime("%Y-%m-%d %H:%M:%S"), + "agent_icon": session[7], + "agent_color": session[8], } for session in sessions ]