Fix assistant swap

This commit is contained in:
Weves 2024-07-21 13:39:02 -07:00
parent 3854ca11af
commit 70e3b8a4ef
12 changed files with 152 additions and 453 deletions

View File

@ -50,7 +50,7 @@ export default async function GalleryPage({
chatSessions,
availableSources,
availableDocumentSets: documentSets,
availablePersonas: assistants,
availableAssistants: assistants,
availableTags: tags,
llmProviders,
folders,

View File

@ -52,7 +52,7 @@ export default async function GalleryPage({
chatSessions,
availableSources,
availableDocumentSets: documentSets,
availablePersonas: assistants,
availableAssistants: assistants,
availableTags: tags,
llmProviders,
folders,

View File

@ -63,7 +63,6 @@ import { SettingsContext } from "@/components/settings/SettingsProvider";
import Dropzone from "react-dropzone";
import { checkLLMSupportsImageInput, getFinalLLM } from "@/lib/llm/utils";
import { ChatInputBar } from "./input/ChatInputBar";
import { ConfigurationModal } from "./modal/configuration/ConfigurationModal";
import { useChatContext } from "@/components/context/ChatContext";
import { v4 as uuidv4 } from "uuid";
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
@ -82,38 +81,29 @@ const SYSTEM_MESSAGE_ID = -3;
export function ChatPage({
toggle,
documentSidebarInitialWidth,
defaultSelectedPersonaId,
defaultSelectedAssistantId,
toggledSidebar,
}: {
toggle: () => void;
documentSidebarInitialWidth?: number;
defaultSelectedPersonaId?: number;
defaultSelectedAssistantId?: number;
toggledSidebar: boolean;
}) {
const [configModalActiveTab, setConfigModalActiveTab] = useState<
string | null
>(null);
const router = useRouter();
const searchParams = useSearchParams();
let {
user,
chatSessions,
availableSources,
availableDocumentSets,
availablePersonas,
availableAssistants,
llmProviders,
folders,
openedFolders,
} = useChatContext();
const filteredAssistants = orderAssistantsForUser(availablePersonas, user);
const [selectedAssistant, setSelectedAssistant] = useState<Persona | null>(
null
);
const [alternativeGeneratingAssistant, setAlternativeGeneratingAssistant] =
useState<Persona | null>(null);
const router = useRouter();
const searchParams = useSearchParams();
// chat session
const existingChatIdRaw = searchParams.get("chatId");
const existingChatSessionId = existingChatIdRaw
? parseInt(existingChatIdRaw)
@ -123,9 +113,51 @@ export function ChatPage({
);
const chatSessionIdRef = useRef<number | null>(existingChatSessionId);
// LLM
const llmOverrideManager = useLlmOverride(selectedChatSession);
const existingChatSessionPersonaId = selectedChatSession?.persona_id;
// Assistants
const filteredAssistants = orderAssistantsForUser(availableAssistants, user);
const existingChatSessionAssistantId = selectedChatSession?.persona_id;
const [selectedAssistant, setSelectedAssistant] = useState<
Persona | undefined
>(
// NOTE: look through available assistants here, so that even if the user
// has hidden this assistant it still shows the correct assistant when
// going back to an old chat session
existingChatSessionAssistantId !== undefined
? availableAssistants.find(
(assistant) => assistant.id === existingChatSessionAssistantId
)
: defaultSelectedAssistantId !== undefined
? availableAssistants.find(
(assistant) => assistant.id === defaultSelectedAssistantId
)
: undefined
);
const setSelectedAssistantFromId = (assistantId: number) => {
// NOTE: also intentionally look through available assistants here, so that
// even if the user has hidden an assistant they can still go back to it
// for old chats
setSelectedAssistant(
availableAssistants.find((assistant) => assistant.id === assistantId)
);
};
const liveAssistant =
selectedAssistant || filteredAssistants[0] || availableAssistants[0];
// this is for "@"ing assistants
const [alternativeAssistant, setAlternativeAssistant] =
useState<Persona | null>(null);
// this is used to track which assistant is being used to generate the current message
// for example, this would come into play when:
// 1. default assistant is `Danswer`
// 2. we "@"ed the `GPT` assistant and sent a message
// 3. while the `GPT` assistant message is generating, we "@" the `Paraphrase` assistant
const [alternativeGeneratingAssistant, setAlternativeGeneratingAssistant] =
useState<Persona | null>(null);
// used to track whether or not the initial "submit on load" has been performed
// this only applies if `?submit-on-load=true` or `?submit-on-load=1` is in the URL
@ -182,14 +214,10 @@ export function ChatPage({
async function initialSessionFetch() {
if (existingChatSessionId === null) {
setIsFetchingChatMessages(false);
if (defaultSelectedPersonaId !== undefined) {
setSelectedPersona(
filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId
)
);
if (defaultSelectedAssistantId !== undefined) {
setSelectedAssistantFromId(defaultSelectedAssistantId);
} else {
setSelectedPersona(undefined);
setSelectedAssistant(undefined);
}
setCompleteMessageDetail({
sessionId: null,
@ -214,12 +242,7 @@ export function ChatPage({
);
const chatSession = (await response.json()) as BackendChatSession;
setSelectedPersona(
filteredAssistants.find(
(persona) => persona.id === chatSession.persona_id
)
);
setSelectedAssistantFromId(chatSession.persona_id);
const newMessageMap = processRawChatHistory(chatSession.messages);
const newMessageHistory = buildLatestMessageChain(newMessageMap);
@ -373,32 +396,18 @@ export function ChatPage({
)
: { aiMessage: null };
const [selectedPersona, setSelectedPersona] = useState<Persona | undefined>(
existingChatSessionPersonaId !== undefined
? filteredAssistants.find(
(persona) => persona.id === existingChatSessionPersonaId
)
: defaultSelectedPersonaId !== undefined
? filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId
)
: undefined
);
const livePersona =
selectedPersona || filteredAssistants[0] || availablePersonas[0];
const [chatSessionSharedStatus, setChatSessionSharedStatus] =
useState<ChatSessionSharedStatus>(ChatSessionSharedStatus.Private);
useEffect(() => {
if (messageHistory.length === 0 && chatSessionIdRef.current === null) {
setSelectedPersona(
setSelectedAssistant(
filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId
(persona) => persona.id === defaultSelectedAssistantId
)
);
}
}, [defaultSelectedPersonaId]);
}, [defaultSelectedAssistantId]);
const [
selectedDocuments,
@ -414,7 +423,7 @@ export function ChatPage({
useEffect(() => {
async function fetchMaxTokens() {
const response = await fetch(
`/api/chat/max-selected-document-tokens?persona_id=${livePersona.id}`
`/api/chat/max-selected-document-tokens?persona_id=${liveAssistant.id}`
);
if (response.ok) {
const maxTokens = (await response.json()).max_tokens as number;
@ -423,12 +432,12 @@ export function ChatPage({
}
fetchMaxTokens();
}, [livePersona]);
}, [liveAssistant]);
const filterManager = useFilters();
const [finalAvailableSources, finalAvailableDocumentSets] =
computeAvailableFilters({
selectedPersona,
selectedPersona: selectedAssistant,
availableSources,
availableDocumentSets,
});
@ -624,16 +633,16 @@ export function ChatPage({
queryOverride,
forceSearch,
isSeededChat,
alternativeAssistant = null,
alternativeAssistantOverride = null,
}: {
messageIdToResend?: number;
messageOverride?: string;
queryOverride?: string;
forceSearch?: boolean;
isSeededChat?: boolean;
alternativeAssistant?: Persona | null;
alternativeAssistantOverride?: Persona | null;
} = {}) => {
setAlternativeGeneratingAssistant(alternativeAssistant);
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
clientScrollToBottom();
let currChatSessionId: number;
@ -643,7 +652,7 @@ export function ChatPage({
if (isNewSession) {
currChatSessionId = await createChatSession(
livePersona?.id || 0,
liveAssistant?.id || 0,
searchParamBasedChatSessionName
);
} else {
@ -721,9 +730,9 @@ export function ChatPage({
parentMessage = frozenMessageMap.get(SYSTEM_MESSAGE_ID) || null;
}
const currentAssistantId = alternativeAssistant
? alternativeAssistant.id
: selectedAssistant?.id;
const currentAssistantId = alternativeAssistantOverride
? alternativeAssistantOverride.id
: alternativeAssistant?.id || liveAssistant.id;
resetInputBar();
@ -751,7 +760,7 @@ export function ChatPage({
fileDescriptors: currentMessageFiles,
parentMessageId: lastSuccessfulMessageId,
chatSessionId: currChatSessionId,
promptId: livePersona?.prompts[0]?.id || 0,
promptId: liveAssistant?.prompts[0]?.id || 0,
filters: buildFilters(
filterManager.selectedSources,
filterManager.selectedDocumentSets,
@ -868,7 +877,7 @@ export function ChatPage({
files: finalMessage?.files || aiMessageImages || [],
toolCalls: finalMessage?.tool_calls || toolCalls,
parentMessageId: newUserMessageId,
alternateAssistantID: selectedAssistant?.id,
alternateAssistantID: alternativeAssistant?.id,
},
]);
}
@ -964,19 +973,23 @@ export function ChatPage({
}
};
const onPersonaChange = (persona: Persona | null) => {
if (persona && persona.id !== livePersona.id) {
const onAssistantChange = (assistant: Persona | null) => {
if (assistant && assistant.id !== liveAssistant.id) {
// remove uploaded files
setCurrentMessageFiles([]);
setSelectedPersona(persona);
setSelectedAssistant(assistant);
textAreaRef.current?.focus();
router.push(buildChatUrl(searchParams, null, persona.id));
router.push(buildChatUrl(searchParams, null, assistant.id));
}
};
const handleImageUpload = (acceptedFiles: File[]) => {
const llmAcceptsImages = checkLLMSupportsImageInput(
...getFinalLLM(llmProviders, livePersona, llmOverrideManager.llmOverride)
...getFinalLLM(
llmProviders,
liveAssistant,
llmOverrideManager.llmOverride
)
);
const imageFiles = acceptedFiles.filter((file) =>
file.type.startsWith("image/")
@ -1058,23 +1071,23 @@ export function ChatPage({
useEffect(() => {
const includes = checkAnyAssistantHasSearch(
messageHistory,
availablePersonas,
livePersona
availableAssistants,
liveAssistant
);
setRetrievalEnabled(includes);
}, [messageHistory, availablePersonas, livePersona]);
}, [messageHistory, availableAssistants, liveAssistant]);
const [retrievalEnabled, setRetrievalEnabled] = useState(() => {
return checkAnyAssistantHasSearch(
messageHistory,
availablePersonas,
livePersona
availableAssistants,
liveAssistant
);
});
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
const currentPersona = selectedAssistant || livePersona;
const currentPersona = alternativeAssistant || liveAssistant;
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@ -1176,21 +1189,8 @@ export function ChatPage({
/>
)}
<ConfigurationModal
chatSessionId={chatSessionIdRef.current!}
activeTab={configModalActiveTab}
setActiveTab={setConfigModalActiveTab}
onClose={() => setConfigModalActiveTab(null)}
filterManager={filterManager}
availableAssistants={filteredAssistants}
selectedAssistant={livePersona}
setSelectedAssistant={onPersonaChange}
llmProviders={llmProviders}
llmOverrideManager={llmOverrideManager}
/>
<div className="flex h-[calc(100dvh)] flex-col w-full">
{livePersona && (
{liveAssistant && (
<FunctionalHeader
page="chat"
setSharingModalVisible={
@ -1239,7 +1239,7 @@ export function ChatPage({
!isStreaming && (
<ChatIntro
availableSources={finalAvailableSources}
selectedPersona={livePersona}
selectedPersona={liveAssistant}
/>
)}
<div
@ -1319,7 +1319,7 @@ export function ChatPage({
const currentAlternativeAssistant =
message.alternateAssistantID != null
? availablePersonas.find(
? availableAssistants.find(
(persona) =>
persona.id ==
message.alternateAssistantID
@ -1342,7 +1342,7 @@ export function ChatPage({
toggleDocumentSelectionAspects
}
docs={message.documents}
currentPersona={livePersona}
currentPersona={liveAssistant}
alternativeAssistant={
currentAlternativeAssistant
}
@ -1352,7 +1352,7 @@ export function ChatPage({
query={
messageHistory[i]?.query || undefined
}
personaName={livePersona.name}
personaName={liveAssistant.name}
citedDocuments={getCitedDocumentsFromMessage(
message
)}
@ -1404,7 +1404,7 @@ export function ChatPage({
messageIdToResend:
previousMessage.messageId,
queryOverride: newQuery,
alternativeAssistant:
alternativeAssistantOverride:
currentAlternativeAssistant,
});
}
@ -1435,7 +1435,7 @@ export function ChatPage({
messageIdToResend:
previousMessage.messageId,
forceSearch: true,
alternativeAssistant:
alternativeAssistantOverride:
currentAlternativeAssistant,
});
} else {
@ -1460,9 +1460,9 @@ export function ChatPage({
return (
<div key={messageReactComponentKey}>
<AIMessage
currentPersona={livePersona}
currentPersona={liveAssistant}
messageId={message.messageId}
personaName={livePersona.name}
personaName={liveAssistant.name}
content={
<p className="text-red-700 text-sm my-auto">
{message.message}
@ -1481,13 +1481,13 @@ export function ChatPage({
key={`${messageHistory.length}-${chatSessionIdRef.current}`}
>
<AIMessage
currentPersona={livePersona}
currentPersona={liveAssistant}
alternativeAssistant={
alternativeGeneratingAssistant ??
selectedAssistant
alternativeAssistant
}
messageId={null}
personaName={livePersona.name}
personaName={liveAssistant.name}
content={
<div className="text-sm my-auto">
<ThreeDots
@ -1513,7 +1513,7 @@ export function ChatPage({
{currentPersona &&
currentPersona.starter_messages &&
currentPersona.starter_messages.length > 0 &&
selectedPersona &&
selectedAssistant &&
messageHistory.length === 0 &&
!isFetchingChatMessages && (
<div
@ -1570,32 +1570,25 @@ export function ChatPage({
<ChatInputBar
showDocs={() => setDocumentSelection(true)}
selectedDocuments={selectedDocuments}
setSelectedAssistant={onPersonaChange}
onSetSelectedAssistant={(
alternativeAssistant: Persona | null
) => {
setSelectedAssistant(alternativeAssistant);
}}
alternativeAssistant={selectedAssistant}
personas={filteredAssistants}
// assistant stuff
assistantOptions={filteredAssistants}
selectedAssistant={liveAssistant}
setSelectedAssistant={onAssistantChange}
setAlternativeAssistant={setAlternativeAssistant}
alternativeAssistant={alternativeAssistant}
// end assistant stuff
message={message}
setMessage={setMessage}
onSubmit={onSubmit}
isStreaming={isStreaming}
setIsCancelled={setIsCancelled}
retrievalDisabled={
!personaIncludesRetrieval(currentPersona)
}
filterManager={filterManager}
llmOverrideManager={llmOverrideManager}
selectedAssistant={livePersona}
files={currentMessageFiles}
setFiles={setCurrentMessageFiles}
handleFileUpload={handleImageUpload}
setConfigModalActiveTab={setConfigModalActiveTab}
textAreaRef={textAreaRef}
chatSessionId={chatSessionIdRef.current!}
availableAssistants={availablePersonas}
/>
</div>
</div>

View File

@ -5,10 +5,10 @@ import { ChatPage } from "./ChatPage";
import FunctionalWrapper from "./shared_chat_search/FunctionalWrapper";
export default function WrappedChat({
defaultPersonaId,
defaultAssistantId,
initiallyToggled,
}: {
defaultPersonaId?: number;
defaultAssistantId?: number;
initiallyToggled: boolean;
}) {
return (
@ -17,7 +17,7 @@ export default function WrappedChat({
content={(toggledSidebar, toggle) => (
<ChatPage
toggle={toggle}
defaultSelectedPersonaId={defaultPersonaId}
defaultSelectedAssistantId={defaultAssistantId}
toggledSidebar={toggledSidebar}
/>
)}

View File

@ -21,7 +21,6 @@ import { IconType } from "react-icons";
import Popup from "../../../components/popup/Popup";
import { LlmTab } from "../modal/configuration/LlmTab";
import { AssistantsTab } from "../modal/configuration/AssistantsTab";
import ChatInputAssistant from "./ChatInputAssistant";
import { DanswerDocument } from "@/lib/search/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { Tooltip } from "@/components/tooltip/Tooltip";
@ -29,7 +28,6 @@ import { Hoverable } from "@/components/Hoverable";
const MAX_INPUT_HEIGHT = 200;
export function ChatInputBar({
personas,
showDocs,
selectedDocuments,
message,
@ -37,34 +35,32 @@ export function ChatInputBar({
onSubmit,
isStreaming,
setIsCancelled,
retrievalDisabled,
filterManager,
llmOverrideManager,
onSetSelectedAssistant,
selectedAssistant,
files,
// assistants
selectedAssistant,
assistantOptions,
setSelectedAssistant,
setAlternativeAssistant,
files,
setFiles,
handleFileUpload,
setConfigModalActiveTab,
textAreaRef,
alternativeAssistant,
chatSessionId,
availableAssistants,
}: {
showDocs: () => void;
selectedDocuments: DanswerDocument[];
availableAssistants: Persona[];
onSetSelectedAssistant: (alternativeAssistant: Persona | null) => void;
assistantOptions: Persona[];
setAlternativeAssistant: (alternativeAssistant: Persona | null) => void;
setSelectedAssistant: (assistant: Persona) => void;
personas: Persona[];
message: string;
setMessage: (message: string) => void;
onSubmit: () => void;
isStreaming: boolean;
setIsCancelled: (value: boolean) => void;
retrievalDisabled: boolean;
filterManager: FilterManager;
llmOverrideManager: LlmOverrideManager;
selectedAssistant: Persona;
@ -72,7 +68,6 @@ export function ChatInputBar({
files: FileDescriptor[];
setFiles: (files: FileDescriptor[]) => void;
handleFileUpload: (files: File[]) => void;
setConfigModalActiveTab: (tab: string) => void;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
chatSessionId?: number;
}) {
@ -136,8 +131,10 @@ export function ChatInputBar({
};
// Update selected persona
const updateCurrentPersona = (persona: Persona) => {
onSetSelectedAssistant(persona.id == selectedAssistant.id ? null : persona);
const updatedTaggedAssistant = (assistant: Persona) => {
setAlternativeAssistant(
assistant.id == selectedAssistant.id ? null : assistant
);
hideSuggestions();
setMessage("");
};
@ -160,8 +157,8 @@ export function ChatInputBar({
}
};
const filteredPersonas = personas.filter((persona) =>
persona.name.toLowerCase().startsWith(
const assistantTagOptions = assistantOptions.filter((assistant) =>
assistant.name.toLowerCase().startsWith(
message
.slice(message.lastIndexOf("@") + 1)
.split(/\s/)[0]
@ -174,18 +171,18 @@ export function ChatInputBar({
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
showSuggestions &&
filteredPersonas.length > 0 &&
assistantTagOptions.length > 0 &&
(e.key === "Tab" || e.key == "Enter")
) {
e.preventDefault();
if (assistantIconIndex == filteredPersonas.length) {
if (assistantIconIndex == assistantTagOptions.length) {
window.open("/assistants/new", "_blank");
hideSuggestions();
setMessage("");
} else {
const option =
filteredPersonas[assistantIconIndex >= 0 ? assistantIconIndex : 0];
updateCurrentPersona(option);
assistantTagOptions[assistantIconIndex >= 0 ? assistantIconIndex : 0];
updatedTaggedAssistant(option);
}
}
if (!showSuggestions) {
@ -195,7 +192,7 @@ export function ChatInputBar({
if (e.key === "ArrowDown") {
e.preventDefault();
setAssistantIconIndex((assistantIconIndex) =>
Math.min(assistantIconIndex + 1, filteredPersonas.length)
Math.min(assistantIconIndex + 1, assistantTagOptions.length)
);
} else if (e.key === "ArrowUp") {
e.preventDefault();
@ -219,35 +216,36 @@ export function ChatInputBar({
mx-auto
"
>
{showSuggestions && filteredPersonas.length > 0 && (
{showSuggestions && assistantTagOptions.length > 0 && (
<div
ref={suggestionsRef}
className="text-sm absolute inset-x-0 top-0 w-full transform -translate-y-full"
>
<div className="rounded-lg py-1.5 bg-background border border-border-medium shadow-lg mx-2 px-1.5 mt-2 rounded z-10">
{filteredPersonas.map((currentPersona, index) => (
{assistantTagOptions.map((currentAssistant, index) => (
<button
key={index}
className={`px-2 ${
assistantIconIndex == index && "bg-hover-lightish"
} rounded rounded-lg content-start flex gap-x-1 py-2 w-full hover:bg-hover-lightish cursor-pointer`}
onClick={() => {
updateCurrentPersona(currentPersona);
updatedTaggedAssistant(currentAssistant);
}}
>
<p className="font-bold">{currentPersona.name}</p>
<p className="font-bold">{currentAssistant.name}</p>
<p className="line-clamp-1">
{currentPersona.id == selectedAssistant.id &&
{currentAssistant.id == selectedAssistant.id &&
"(default) "}
{currentPersona.description}
{currentAssistant.description}
</p>
</button>
))}
<a
key={filteredPersonas.length}
key={assistantTagOptions.length}
target="_blank"
className={`${
assistantIconIndex == filteredPersonas.length && "bg-hover"
assistantIconIndex == assistantTagOptions.length &&
"bg-hover"
} rounded rounded-lg px-3 flex gap-x-1 py-2 w-full items-center hover:bg-hover-lightish cursor-pointer"`}
href="/assistants/new"
>
@ -301,7 +299,7 @@ export function ChatInputBar({
<Hoverable
icon={FiX}
onClick={() => onSetSelectedAssistant(null)}
onClick={() => setAlternativeAssistant(null)}
/>
</div>
</div>
@ -409,7 +407,7 @@ export function ChatInputBar({
removePadding
content={(close) => (
<AssistantsTab
availableAssistants={availableAssistants}
availableAssistants={assistantOptions}
llmProviders={llmProviders}
selectedAssistant={selectedAssistant}
onSelect={(assistant) => {

View File

@ -505,7 +505,7 @@ export function removeMessage(
export function checkAnyAssistantHasSearch(
messageHistory: Message[],
availablePersonas: Persona[],
availableAssistants: Persona[],
livePersona: Persona
): boolean {
const response =
@ -516,8 +516,8 @@ export function checkAnyAssistantHasSearch(
) {
return false;
}
const alternateAssistant = availablePersonas.find(
(persona) => persona.id === message.alternateAssistantID
const alternateAssistant = availableAssistants.find(
(assistant) => assistant.id === message.alternateAssistantID
);
return alternateAssistant
? personaIncludesRetrieval(alternateAssistant)

View File

@ -1,180 +0,0 @@
"use client";
import React, { useEffect } from "react";
import { Modal } from "../../../../components/Modal";
import { FilterManager, LlmOverrideManager } from "@/lib/hooks";
import { FiltersTab } from "./FiltersTab";
import { FiCpu, FiFilter, FiX } from "react-icons/fi";
import { IconType } from "react-icons";
import { FaBrain } from "react-icons/fa";
import { AssistantsTab } from "./AssistantsTab";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LlmTab } from "./LlmTab";
import { LLMProviderDescriptor } from "@/app/admin/models/llm/interfaces";
import { AssistantsIcon, IconProps } from "@/components/icons/icons";
const TabButton = ({
label,
icon: Icon,
isActive,
onClick,
}: {
label: string;
icon: IconType;
isActive: boolean;
onClick: () => void;
}) => (
<button
onClick={onClick}
className={`
pb-4
pt-6
px-2
text-emphasis
font-bold
${isActive ? "border-b-2 border-accent" : ""}
hover:bg-hover-light
hover:text-strong
transition
duration-200
ease-in-out
flex
`}
>
<Icon className="inline-block mr-2 my-auto" size="16" />
<p className="my-auto">{label}</p>
</button>
);
export function ConfigurationModal({
activeTab,
setActiveTab,
onClose,
availableAssistants,
selectedAssistant,
setSelectedAssistant,
filterManager,
llmProviders,
llmOverrideManager,
chatSessionId,
}: {
activeTab: string | null;
setActiveTab: (tab: string | null) => void;
onClose: () => void;
availableAssistants: Persona[];
selectedAssistant: Persona;
setSelectedAssistant: (assistant: Persona) => void;
filterManager: FilterManager;
llmProviders: LLMProviderDescriptor[];
llmOverrideManager: LlmOverrideManager;
chatSessionId?: number;
}) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [onClose]);
if (!activeTab) return null;
return (
<Modal
onOutsideClick={onClose}
noPadding
className="
w-4/6
h-4/6
flex
flex-col
"
>
<div className="rounded flex flex-col overflow-hidden">
<div className="mb-4">
<div className="flex border-b border-border bg-background-emphasis">
<div className="flex px-6 gap-x-2">
<TabButton
label="Assistants"
icon={FaBrain}
isActive={activeTab === "assistants"}
onClick={() => setActiveTab("assistants")}
/>
<TabButton
label="Models"
icon={FiCpu}
isActive={activeTab === "llms"}
onClick={() => setActiveTab("llms")}
/>
<TabButton
label="Filters"
icon={FiFilter}
isActive={activeTab === "filters"}
onClick={() => setActiveTab("filters")}
/>
</div>
<button
className="
ml-auto
px-1
py-1
text-xs
font-medium
rounded
hover:bg-hover
focus:outline-none
focus:ring-2
focus:ring-offset-2
focus:ring-subtle
flex
items-center
h-fit
my-auto
mr-5
"
onClick={onClose}
>
<FiX size={24} />
</button>
</div>
</div>
<div className="flex flex-col overflow-y-auto">
<div className="px-8 pt-4">
{activeTab === "filters" && (
<FiltersTab filterManager={filterManager} />
)}
{activeTab === "llms" && (
<LlmTab
chatSessionId={chatSessionId}
llmOverrideManager={llmOverrideManager}
currentAssistant={selectedAssistant}
/>
)}
{activeTab === "assistants" && (
<div>
<AssistantsTab
availableAssistants={availableAssistants}
llmProviders={llmProviders}
selectedAssistant={selectedAssistant}
onSelect={(assistant) => {
setSelectedAssistant(assistant);
onClose();
}}
/>
</div>
)}
</div>
</div>
</div>
</Modal>
);
}

View File

@ -35,7 +35,7 @@ export default async function Page({
folders,
toggleSidebar,
openedFolders,
defaultPersonaId,
defaultAssistantId,
finalDocumentSidebarInitialWidth,
shouldShowWelcomeModal,
shouldDisplaySourcesIncompleteModal,
@ -58,7 +58,7 @@ export default async function Page({
chatSessions,
availableSources,
availableDocumentSets: documentSets,
availablePersonas: assistants,
availableAssistants: assistants,
availableTags: tags,
llmProviders,
folders,
@ -66,7 +66,7 @@ export default async function Page({
}}
>
<WrappedChat
defaultPersonaId={defaultPersonaId}
defaultAssistantId={defaultAssistantId}
initiallyToggled={toggleSidebar}
/>
</ChatProvider>

View File

@ -1,111 +0,0 @@
import { Persona } from "@/app/admin/assistants/interfaces";
import { BasicSelectable } from "@/components/BasicClickable";
import { AssistantsIcon } from "@/components/icons/icons";
import { User } from "@/lib/types";
import { Text } from "@tremor/react";
import Link from "next/link";
import { FaRobot } from "react-icons/fa";
import { FiEdit2 } from "react-icons/fi";
function AssistantDisplay({
persona,
onSelect,
user,
}: {
persona: Persona;
onSelect: (persona: Persona) => void;
user: User | null;
}) {
const isEditable =
(!user || user.id === persona.owner?.id) &&
!persona.default_persona &&
(!persona.is_public || !user || user.role === "admin");
return (
<div className="flex">
<div className="w-full" onClick={() => onSelect(persona)}>
<BasicSelectable selected={false} fullWidth>
<div className="flex">
<div className="truncate w-48 3xl:w-56 flex">
<AssistantsIcon className="mr-2 my-auto" size={16} />{" "}
{persona.name}
</div>
</div>
</BasicSelectable>
</div>
{isEditable && (
<div className="pl-2 my-auto">
<Link href={`/assistants/edit/${persona.id}`}>
<FiEdit2
className="my-auto ml-auto hover:bg-hover p-0.5"
size={20}
/>
</Link>
</div>
)}
</div>
);
}
export function AssistantsTab({
personas,
onPersonaChange,
user,
}: {
personas: Persona[];
onPersonaChange: (persona: Persona | null) => void;
user: User | null;
}) {
const globalAssistants = personas.filter((persona) => persona.is_public);
const personalAssistants = personas.filter(
(persona) =>
(!user || persona.users.some((u) => u.id === user.id)) &&
!persona.is_public
);
return (
<div className="mt-4 pb-1 overflow-y-auto h-full flex flex-col gap-y-1">
<Text className="mx-3 text-xs mb-4">
Select an Assistant below to begin a new chat with them!
</Text>
<div className="mx-3">
{globalAssistants.length > 0 && (
<>
<div className="text-xs text-subtle flex pb-0.5 ml-1 mb-1.5 font-bold">
Global
</div>
{globalAssistants.map((persona) => {
return (
<AssistantDisplay
key={persona.id}
persona={persona}
onSelect={onPersonaChange}
user={user}
/>
);
})}
</>
)}
{personalAssistants.length > 0 && (
<>
<div className="text-xs text-subtle flex pb-0.5 ml-1 mb-1.5 mt-5 font-bold">
Personal
</div>
{personalAssistants.map((persona) => {
return (
<AssistantDisplay
key={persona.id}
persona={persona}
onSelect={onPersonaChange}
user={user}
/>
);
})}
</>
)}
</div>
</div>
);
}

View File

@ -18,7 +18,7 @@ interface ChatContextProps {
chatSessions: ChatSession[];
availableSources: ValidSources[];
availableDocumentSets: DocumentSet[];
availablePersonas: Persona[];
availableAssistants: Persona[];
availableTags: Tag[];
llmProviders: LLMProviderDescriptor[];
folders: Folder[];

View File

@ -19,7 +19,7 @@ export function Citation({
return (
<CustomTooltip
citation
content={<p className="inline-block p-0 m-0 truncate">{link}</p>}
content={<div className="inline-block p-0 m-0 truncate">{link}</div>}
>
<a
onClick={() => (link ? window.open(link, "_blank") : undefined)}

View File

@ -30,7 +30,6 @@ import { fetchAssistantsSS } from "../assistants/fetchAssistantsSS";
interface FetchChatDataResult {
user: User | null;
chatSessions: ChatSession[];
ccPairs: CCPairBasicInfo[];
availableSources: ValidSources[];
documentSets: DocumentSet[];
@ -39,7 +38,7 @@ interface FetchChatDataResult {
llmProviders: LLMProviderDescriptor[];
folders: Folder[];
openedFolders: Record<string, boolean>;
defaultPersonaId?: number;
defaultAssistantId?: number;
toggleSidebar: boolean;
finalDocumentSidebarInitialWidth?: number;
shouldShowWelcomeModal: boolean;
@ -150,9 +149,9 @@ export async function fetchChatData(searchParams: {
console.log(`Failed to fetch tags - ${tagsResponse?.status}`);
}
const defaultPersonaIdRaw = searchParams["assistantId"];
const defaultPersonaId = defaultPersonaIdRaw
? parseInt(defaultPersonaIdRaw)
const defaultAssistantIdRaw = searchParams["assistantId"];
const defaultAssistantId = defaultAssistantIdRaw
? parseInt(defaultAssistantIdRaw)
: undefined;
const documentSidebarCookieInitialWidth = cookies().get(
@ -209,7 +208,7 @@ export async function fetchChatData(searchParams: {
llmProviders,
folders,
openedFolders,
defaultPersonaId,
defaultAssistantId,
finalDocumentSidebarInitialWidth,
toggleSidebar,
shouldShowWelcomeModal,