Fix assistant swap

This commit is contained in:
Weves
2024-07-21 13:39:02 -07:00
committed by Chris Weaver
parent 0e8ba111c8
commit c09c94bf32
12 changed files with 152 additions and 453 deletions

View File

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

View File

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

View File

@@ -63,7 +63,6 @@ import { SettingsContext } from "@/components/settings/SettingsProvider";
import Dropzone from "react-dropzone"; import Dropzone from "react-dropzone";
import { checkLLMSupportsImageInput, getFinalLLM } from "@/lib/llm/utils"; import { checkLLMSupportsImageInput, getFinalLLM } from "@/lib/llm/utils";
import { ChatInputBar } from "./input/ChatInputBar"; import { ChatInputBar } from "./input/ChatInputBar";
import { ConfigurationModal } from "./modal/configuration/ConfigurationModal";
import { useChatContext } from "@/components/context/ChatContext"; import { useChatContext } from "@/components/context/ChatContext";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants"; import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
@@ -82,38 +81,29 @@ const SYSTEM_MESSAGE_ID = -3;
export function ChatPage({ export function ChatPage({
toggle, toggle,
documentSidebarInitialWidth, documentSidebarInitialWidth,
defaultSelectedPersonaId, defaultSelectedAssistantId,
toggledSidebar, toggledSidebar,
}: { }: {
toggle: () => void; toggle: () => void;
documentSidebarInitialWidth?: number; documentSidebarInitialWidth?: number;
defaultSelectedPersonaId?: number; defaultSelectedAssistantId?: number;
toggledSidebar: boolean; toggledSidebar: boolean;
}) { }) {
const [configModalActiveTab, setConfigModalActiveTab] = useState< const router = useRouter();
string | null const searchParams = useSearchParams();
>(null);
let { let {
user, user,
chatSessions, chatSessions,
availableSources, availableSources,
availableDocumentSets, availableDocumentSets,
availablePersonas, availableAssistants,
llmProviders, llmProviders,
folders, folders,
openedFolders, openedFolders,
} = useChatContext(); } = useChatContext();
const filteredAssistants = orderAssistantsForUser(availablePersonas, user); // chat session
const [selectedAssistant, setSelectedAssistant] = useState<Persona | null>(
null
);
const [alternativeGeneratingAssistant, setAlternativeGeneratingAssistant] =
useState<Persona | null>(null);
const router = useRouter();
const searchParams = useSearchParams();
const existingChatIdRaw = searchParams.get("chatId"); const existingChatIdRaw = searchParams.get("chatId");
const existingChatSessionId = existingChatIdRaw const existingChatSessionId = existingChatIdRaw
? parseInt(existingChatIdRaw) ? parseInt(existingChatIdRaw)
@@ -123,9 +113,51 @@ export function ChatPage({
); );
const chatSessionIdRef = useRef<number | null>(existingChatSessionId); const chatSessionIdRef = useRef<number | null>(existingChatSessionId);
// LLM
const llmOverrideManager = useLlmOverride(selectedChatSession); 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 // 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 // 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() { async function initialSessionFetch() {
if (existingChatSessionId === null) { if (existingChatSessionId === null) {
setIsFetchingChatMessages(false); setIsFetchingChatMessages(false);
if (defaultSelectedPersonaId !== undefined) { if (defaultSelectedAssistantId !== undefined) {
setSelectedPersona( setSelectedAssistantFromId(defaultSelectedAssistantId);
filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId
)
);
} else { } else {
setSelectedPersona(undefined); setSelectedAssistant(undefined);
} }
setCompleteMessageDetail({ setCompleteMessageDetail({
sessionId: null, sessionId: null,
@@ -214,12 +242,7 @@ export function ChatPage({
); );
const chatSession = (await response.json()) as BackendChatSession; const chatSession = (await response.json()) as BackendChatSession;
setSelectedAssistantFromId(chatSession.persona_id);
setSelectedPersona(
filteredAssistants.find(
(persona) => persona.id === chatSession.persona_id
)
);
const newMessageMap = processRawChatHistory(chatSession.messages); const newMessageMap = processRawChatHistory(chatSession.messages);
const newMessageHistory = buildLatestMessageChain(newMessageMap); const newMessageHistory = buildLatestMessageChain(newMessageMap);
@@ -373,32 +396,18 @@ export function ChatPage({
) )
: { aiMessage: null }; : { 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] = const [chatSessionSharedStatus, setChatSessionSharedStatus] =
useState<ChatSessionSharedStatus>(ChatSessionSharedStatus.Private); useState<ChatSessionSharedStatus>(ChatSessionSharedStatus.Private);
useEffect(() => { useEffect(() => {
if (messageHistory.length === 0 && chatSessionIdRef.current === null) { if (messageHistory.length === 0 && chatSessionIdRef.current === null) {
setSelectedPersona( setSelectedAssistant(
filteredAssistants.find( filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId (persona) => persona.id === defaultSelectedAssistantId
) )
); );
} }
}, [defaultSelectedPersonaId]); }, [defaultSelectedAssistantId]);
const [ const [
selectedDocuments, selectedDocuments,
@@ -414,7 +423,7 @@ export function ChatPage({
useEffect(() => { useEffect(() => {
async function fetchMaxTokens() { async function fetchMaxTokens() {
const response = await fetch( 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) { if (response.ok) {
const maxTokens = (await response.json()).max_tokens as number; const maxTokens = (await response.json()).max_tokens as number;
@@ -423,12 +432,12 @@ export function ChatPage({
} }
fetchMaxTokens(); fetchMaxTokens();
}, [livePersona]); }, [liveAssistant]);
const filterManager = useFilters(); const filterManager = useFilters();
const [finalAvailableSources, finalAvailableDocumentSets] = const [finalAvailableSources, finalAvailableDocumentSets] =
computeAvailableFilters({ computeAvailableFilters({
selectedPersona, selectedPersona: selectedAssistant,
availableSources, availableSources,
availableDocumentSets, availableDocumentSets,
}); });
@@ -624,16 +633,16 @@ export function ChatPage({
queryOverride, queryOverride,
forceSearch, forceSearch,
isSeededChat, isSeededChat,
alternativeAssistant = null, alternativeAssistantOverride = null,
}: { }: {
messageIdToResend?: number; messageIdToResend?: number;
messageOverride?: string; messageOverride?: string;
queryOverride?: string; queryOverride?: string;
forceSearch?: boolean; forceSearch?: boolean;
isSeededChat?: boolean; isSeededChat?: boolean;
alternativeAssistant?: Persona | null; alternativeAssistantOverride?: Persona | null;
} = {}) => { } = {}) => {
setAlternativeGeneratingAssistant(alternativeAssistant); setAlternativeGeneratingAssistant(alternativeAssistantOverride);
clientScrollToBottom(); clientScrollToBottom();
let currChatSessionId: number; let currChatSessionId: number;
@@ -643,7 +652,7 @@ export function ChatPage({
if (isNewSession) { if (isNewSession) {
currChatSessionId = await createChatSession( currChatSessionId = await createChatSession(
livePersona?.id || 0, liveAssistant?.id || 0,
searchParamBasedChatSessionName searchParamBasedChatSessionName
); );
} else { } else {
@@ -721,9 +730,9 @@ export function ChatPage({
parentMessage = frozenMessageMap.get(SYSTEM_MESSAGE_ID) || null; parentMessage = frozenMessageMap.get(SYSTEM_MESSAGE_ID) || null;
} }
const currentAssistantId = alternativeAssistant const currentAssistantId = alternativeAssistantOverride
? alternativeAssistant.id ? alternativeAssistantOverride.id
: selectedAssistant?.id; : alternativeAssistant?.id || liveAssistant.id;
resetInputBar(); resetInputBar();
@@ -751,7 +760,7 @@ export function ChatPage({
fileDescriptors: currentMessageFiles, fileDescriptors: currentMessageFiles,
parentMessageId: lastSuccessfulMessageId, parentMessageId: lastSuccessfulMessageId,
chatSessionId: currChatSessionId, chatSessionId: currChatSessionId,
promptId: livePersona?.prompts[0]?.id || 0, promptId: liveAssistant?.prompts[0]?.id || 0,
filters: buildFilters( filters: buildFilters(
filterManager.selectedSources, filterManager.selectedSources,
filterManager.selectedDocumentSets, filterManager.selectedDocumentSets,
@@ -868,7 +877,7 @@ export function ChatPage({
files: finalMessage?.files || aiMessageImages || [], files: finalMessage?.files || aiMessageImages || [],
toolCalls: finalMessage?.tool_calls || toolCalls, toolCalls: finalMessage?.tool_calls || toolCalls,
parentMessageId: newUserMessageId, parentMessageId: newUserMessageId,
alternateAssistantID: selectedAssistant?.id, alternateAssistantID: alternativeAssistant?.id,
}, },
]); ]);
} }
@@ -964,19 +973,23 @@ export function ChatPage({
} }
}; };
const onPersonaChange = (persona: Persona | null) => { const onAssistantChange = (assistant: Persona | null) => {
if (persona && persona.id !== livePersona.id) { if (assistant && assistant.id !== liveAssistant.id) {
// remove uploaded files // remove uploaded files
setCurrentMessageFiles([]); setCurrentMessageFiles([]);
setSelectedPersona(persona); setSelectedAssistant(assistant);
textAreaRef.current?.focus(); textAreaRef.current?.focus();
router.push(buildChatUrl(searchParams, null, persona.id)); router.push(buildChatUrl(searchParams, null, assistant.id));
} }
}; };
const handleImageUpload = (acceptedFiles: File[]) => { const handleImageUpload = (acceptedFiles: File[]) => {
const llmAcceptsImages = checkLLMSupportsImageInput( const llmAcceptsImages = checkLLMSupportsImageInput(
...getFinalLLM(llmProviders, livePersona, llmOverrideManager.llmOverride) ...getFinalLLM(
llmProviders,
liveAssistant,
llmOverrideManager.llmOverride
)
); );
const imageFiles = acceptedFiles.filter((file) => const imageFiles = acceptedFiles.filter((file) =>
file.type.startsWith("image/") file.type.startsWith("image/")
@@ -1058,23 +1071,23 @@ export function ChatPage({
useEffect(() => { useEffect(() => {
const includes = checkAnyAssistantHasSearch( const includes = checkAnyAssistantHasSearch(
messageHistory, messageHistory,
availablePersonas, availableAssistants,
livePersona liveAssistant
); );
setRetrievalEnabled(includes); setRetrievalEnabled(includes);
}, [messageHistory, availablePersonas, livePersona]); }, [messageHistory, availableAssistants, liveAssistant]);
const [retrievalEnabled, setRetrievalEnabled] = useState(() => { const [retrievalEnabled, setRetrievalEnabled] = useState(() => {
return checkAnyAssistantHasSearch( return checkAnyAssistantHasSearch(
messageHistory, messageHistory,
availablePersonas, availableAssistants,
livePersona liveAssistant
); );
}); });
const innerSidebarElementRef = useRef<HTMLDivElement>(null); const innerSidebarElementRef = useRef<HTMLDivElement>(null);
const currentPersona = selectedAssistant || livePersona; const currentPersona = alternativeAssistant || liveAssistant;
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { 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"> <div className="flex h-[calc(100dvh)] flex-col w-full">
{livePersona && ( {liveAssistant && (
<FunctionalHeader <FunctionalHeader
page="chat" page="chat"
setSharingModalVisible={ setSharingModalVisible={
@@ -1239,7 +1239,7 @@ export function ChatPage({
!isStreaming && ( !isStreaming && (
<ChatIntro <ChatIntro
availableSources={finalAvailableSources} availableSources={finalAvailableSources}
selectedPersona={livePersona} selectedPersona={liveAssistant}
/> />
)} )}
<div <div
@@ -1319,7 +1319,7 @@ export function ChatPage({
const currentAlternativeAssistant = const currentAlternativeAssistant =
message.alternateAssistantID != null message.alternateAssistantID != null
? availablePersonas.find( ? availableAssistants.find(
(persona) => (persona) =>
persona.id == persona.id ==
message.alternateAssistantID message.alternateAssistantID
@@ -1342,7 +1342,7 @@ export function ChatPage({
toggleDocumentSelectionAspects toggleDocumentSelectionAspects
} }
docs={message.documents} docs={message.documents}
currentPersona={livePersona} currentPersona={liveAssistant}
alternativeAssistant={ alternativeAssistant={
currentAlternativeAssistant currentAlternativeAssistant
} }
@@ -1352,7 +1352,7 @@ export function ChatPage({
query={ query={
messageHistory[i]?.query || undefined messageHistory[i]?.query || undefined
} }
personaName={livePersona.name} personaName={liveAssistant.name}
citedDocuments={getCitedDocumentsFromMessage( citedDocuments={getCitedDocumentsFromMessage(
message message
)} )}
@@ -1404,7 +1404,7 @@ export function ChatPage({
messageIdToResend: messageIdToResend:
previousMessage.messageId, previousMessage.messageId,
queryOverride: newQuery, queryOverride: newQuery,
alternativeAssistant: alternativeAssistantOverride:
currentAlternativeAssistant, currentAlternativeAssistant,
}); });
} }
@@ -1435,7 +1435,7 @@ export function ChatPage({
messageIdToResend: messageIdToResend:
previousMessage.messageId, previousMessage.messageId,
forceSearch: true, forceSearch: true,
alternativeAssistant: alternativeAssistantOverride:
currentAlternativeAssistant, currentAlternativeAssistant,
}); });
} else { } else {
@@ -1460,9 +1460,9 @@ export function ChatPage({
return ( return (
<div key={messageReactComponentKey}> <div key={messageReactComponentKey}>
<AIMessage <AIMessage
currentPersona={livePersona} currentPersona={liveAssistant}
messageId={message.messageId} messageId={message.messageId}
personaName={livePersona.name} personaName={liveAssistant.name}
content={ content={
<p className="text-red-700 text-sm my-auto"> <p className="text-red-700 text-sm my-auto">
{message.message} {message.message}
@@ -1481,13 +1481,13 @@ export function ChatPage({
key={`${messageHistory.length}-${chatSessionIdRef.current}`} key={`${messageHistory.length}-${chatSessionIdRef.current}`}
> >
<AIMessage <AIMessage
currentPersona={livePersona} currentPersona={liveAssistant}
alternativeAssistant={ alternativeAssistant={
alternativeGeneratingAssistant ?? alternativeGeneratingAssistant ??
selectedAssistant alternativeAssistant
} }
messageId={null} messageId={null}
personaName={livePersona.name} personaName={liveAssistant.name}
content={ content={
<div className="text-sm my-auto"> <div className="text-sm my-auto">
<ThreeDots <ThreeDots
@@ -1513,7 +1513,7 @@ export function ChatPage({
{currentPersona && {currentPersona &&
currentPersona.starter_messages && currentPersona.starter_messages &&
currentPersona.starter_messages.length > 0 && currentPersona.starter_messages.length > 0 &&
selectedPersona && selectedAssistant &&
messageHistory.length === 0 && messageHistory.length === 0 &&
!isFetchingChatMessages && ( !isFetchingChatMessages && (
<div <div
@@ -1570,32 +1570,25 @@ export function ChatPage({
<ChatInputBar <ChatInputBar
showDocs={() => setDocumentSelection(true)} showDocs={() => setDocumentSelection(true)}
selectedDocuments={selectedDocuments} selectedDocuments={selectedDocuments}
setSelectedAssistant={onPersonaChange} // assistant stuff
onSetSelectedAssistant={( assistantOptions={filteredAssistants}
alternativeAssistant: Persona | null selectedAssistant={liveAssistant}
) => { setSelectedAssistant={onAssistantChange}
setSelectedAssistant(alternativeAssistant); setAlternativeAssistant={setAlternativeAssistant}
}} alternativeAssistant={alternativeAssistant}
alternativeAssistant={selectedAssistant} // end assistant stuff
personas={filteredAssistants}
message={message} message={message}
setMessage={setMessage} setMessage={setMessage}
onSubmit={onSubmit} onSubmit={onSubmit}
isStreaming={isStreaming} isStreaming={isStreaming}
setIsCancelled={setIsCancelled} setIsCancelled={setIsCancelled}
retrievalDisabled={
!personaIncludesRetrieval(currentPersona)
}
filterManager={filterManager} filterManager={filterManager}
llmOverrideManager={llmOverrideManager} llmOverrideManager={llmOverrideManager}
selectedAssistant={livePersona}
files={currentMessageFiles} files={currentMessageFiles}
setFiles={setCurrentMessageFiles} setFiles={setCurrentMessageFiles}
handleFileUpload={handleImageUpload} handleFileUpload={handleImageUpload}
setConfigModalActiveTab={setConfigModalActiveTab}
textAreaRef={textAreaRef} textAreaRef={textAreaRef}
chatSessionId={chatSessionIdRef.current!} chatSessionId={chatSessionIdRef.current!}
availableAssistants={availablePersonas}
/> />
</div> </div>
</div> </div>

View File

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

View File

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

View File

@@ -505,7 +505,7 @@ export function removeMessage(
export function checkAnyAssistantHasSearch( export function checkAnyAssistantHasSearch(
messageHistory: Message[], messageHistory: Message[],
availablePersonas: Persona[], availableAssistants: Persona[],
livePersona: Persona livePersona: Persona
): boolean { ): boolean {
const response = const response =
@@ -516,8 +516,8 @@ export function checkAnyAssistantHasSearch(
) { ) {
return false; return false;
} }
const alternateAssistant = availablePersonas.find( const alternateAssistant = availableAssistants.find(
(persona) => persona.id === message.alternateAssistantID (assistant) => assistant.id === message.alternateAssistantID
); );
return alternateAssistant return alternateAssistant
? personaIncludesRetrieval(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, folders,
toggleSidebar, toggleSidebar,
openedFolders, openedFolders,
defaultPersonaId, defaultAssistantId,
finalDocumentSidebarInitialWidth, finalDocumentSidebarInitialWidth,
shouldShowWelcomeModal, shouldShowWelcomeModal,
shouldDisplaySourcesIncompleteModal, shouldDisplaySourcesIncompleteModal,
@@ -58,7 +58,7 @@ export default async function Page({
chatSessions, chatSessions,
availableSources, availableSources,
availableDocumentSets: documentSets, availableDocumentSets: documentSets,
availablePersonas: assistants, availableAssistants: assistants,
availableTags: tags, availableTags: tags,
llmProviders, llmProviders,
folders, folders,
@@ -66,7 +66,7 @@ export default async function Page({
}} }}
> >
<WrappedChat <WrappedChat
defaultPersonaId={defaultPersonaId} defaultAssistantId={defaultAssistantId}
initiallyToggled={toggleSidebar} initiallyToggled={toggleSidebar}
/> />
</ChatProvider> </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[]; chatSessions: ChatSession[];
availableSources: ValidSources[]; availableSources: ValidSources[];
availableDocumentSets: DocumentSet[]; availableDocumentSets: DocumentSet[];
availablePersonas: Persona[]; availableAssistants: Persona[];
availableTags: Tag[]; availableTags: Tag[];
llmProviders: LLMProviderDescriptor[]; llmProviders: LLMProviderDescriptor[];
folders: Folder[]; folders: Folder[];

View File

@@ -19,7 +19,7 @@ export function Citation({
return ( return (
<CustomTooltip <CustomTooltip
citation 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 <a
onClick={() => (link ? window.open(link, "_blank") : undefined)} onClick={() => (link ? window.open(link, "_blank") : undefined)}

View File

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