import React, { useState, useRef, useCallback, useEffect } from "react"; import { useAssistants } from "@/components/context/AssistantsContext"; import { useChatContext } from "@/components/context/ChatContext"; import { useUser } from "@/components/user/UserProvider"; import { Persona } from "@/app/admin/assistants/interfaces"; import { FiChevronDown } from "react-icons/fi"; import { destructureValue, getFinalLLM } from "@/lib/llm/utils"; import { updateModelOverrideForChatSession } from "@/app/chat/lib"; import { debounce } from "lodash"; import { LlmList } from "@/components/llm/LLMList"; import { checkPersonaRequiresImageGeneration } from "@/app/admin/assistants/lib"; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { DraggableAssistantCard } from "@/components/assistants/AssistantCards"; import { updateUserAssistantList } from "@/lib/assistants/updateAssistantPreferences"; import Text from "@/components/ui/text"; import { getDisplayNameForModel, LlmOverrideManager } from "@/lib/hooks"; import { Tab } from "@headlessui/react"; import { AssistantIcon } from "../assistants/AssistantIcon"; import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; import { restrictToParentElement } from "@dnd-kit/modifiers"; import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "../ui/drawer"; import { truncateString } from "@/lib/utils"; const AssistantSelector = ({ liveAssistant, onAssistantChange, chatSessionId, llmOverrideManager, isMobile, }: { liveAssistant: Persona; onAssistantChange: (assistant: Persona) => void; chatSessionId?: string; llmOverrideManager?: LlmOverrideManager; isMobile: boolean; }) => { const { finalAssistants } = useAssistants(); const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); const { llmProviders } = useChatContext(); const { user } = useUser(); const [assistants, setAssistants] = useState(finalAssistants); const [isTemperatureExpanded, setIsTemperatureExpanded] = useState(false); const [localTemperature, setLocalTemperature] = useState( llmOverrideManager?.temperature || 0 ); // Initialize selectedTab from localStorage const [selectedTab, setSelectedTab] = useState(() => { const storedTab = localStorage.getItem("assistantSelectorSelectedTab"); return storedTab !== null ? Number(storedTab) : 0; }); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); const handleDragEnd = async (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { const oldIndex = assistants.findIndex( (item) => item.id.toString() === active.id ); const newIndex = assistants.findIndex( (item) => item.id.toString() === over.id ); const updatedAssistants = arrayMove(assistants, oldIndex, newIndex); setAssistants(updatedAssistants); await updateUserAssistantList(updatedAssistants.map((a) => a.id)); } }; const debouncedSetTemperature = useCallback( (value: number) => { const debouncedFunction = debounce((value: number) => { llmOverrideManager?.setTemperature(value); }, 300); return debouncedFunction(value); }, [llmOverrideManager] ); const handleTemperatureChange = (value: number) => { setLocalTemperature(value); debouncedSetTemperature(value); }; // Handle tab change and update localStorage const handleTabChange = (index: number) => { setSelectedTab(index); localStorage.setItem("assistantSelectorSelectedTab", index.toString()); }; // Get the user's default model const userDefaultModel = user?.preferences.default_model; const [_, currentLlm] = getFinalLLM( llmProviders, liveAssistant, llmOverrideManager?.llmOverride ?? null ); const requiresImageGeneration = checkPersonaRequiresImageGeneration(liveAssistant); const content = ( <> `w-full py-2.5 text-sm leading-5 font-medium rounded-md ${ selected ? "bg-white text-gray-700 shadow" : "text-gray-500 hover:bg-white/[0.12] hover:text-gray-700" }` } > Assistant `w-full py-2.5 text-sm leading-5 font-medium rounded-md ${ selected ? "bg-white text-gray-700 shadow" : "text-gray-500 hover:bg-white/[0.12] hover:text-gray-700" }` } > Model

Choose an Assistant

a.id.toString())} strategy={verticalListSortingStrategy} >
{assistants.map((assistant) => ( { onAssistantChange(assistant); setIsOpen(false); }} llmName={ assistant.llm_model_version_override ?? userDefaultModel ?? currentLlm } /> ))}

Choose a Model

{ if (value == null) return; const { modelName, name, provider } = destructureValue(value); llmOverrideManager?.setLlmOverride({ name, provider, modelName, }); if (chatSessionId) { updateModelOverrideForChatSession(chatSessionId, value); } setIsOpen(false); }} />
{isTemperatureExpanded && ( <> Adjust the temperature of the LLM. Higher temperatures will make the LLM generate more creative and diverse responses, while lower temperature will make the LLM generate more conservative and focused responses.
handleTemperatureChange(parseFloat(e.target.value)) } className="w-full p-2 border border-border rounded-md" min="0" max="2" step="0.01" value={localTemperature} />
{localTemperature}
)}
); useEffect(() => { if (!isMobile) { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; } }, [isMobile]); return (
{ setIsOpen(!isOpen); // Get selectedTab from localStorage when opening const storedTab = localStorage.getItem( "assistantSelectorSelectedTab" ); setSelectedTab(storedTab !== null ? Number(storedTab) : 0); }} className="flex items-center gap-x-2 justify-between px-6 py-3 text-sm font-medium text-white bg-black rounded-full shadow-lg hover:shadow-xl transition-all duration-300 cursor-pointer" >
{liveAssistant.name}
{truncateString(getDisplayNameForModel(currentLlm), 30)}
{isMobile ? ( Assistant Selector {content} ) : ( isOpen && (
{content}
) )}
); }; export default AssistantSelector;