Performance Improvements (#2162)

This commit is contained in:
Chris Weaver
2024-08-19 11:07:00 -07:00
committed by GitHub
parent ea53977617
commit af647959f6
22 changed files with 426 additions and 403 deletions

View File

@@ -1,7 +1,4 @@
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { UserDropdown } from "@/components/UserDropdown";
import { ChatProvider } from "@/components/context/ChatContext";
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
import { fetchChatData } from "@/lib/chat/fetchChatData";
import { unstable_noStore as noStore } from "next/cache";
@@ -24,47 +21,27 @@ export default async function GalleryPage({
const {
user,
chatSessions,
availableSources,
documentSets,
assistants,
tags,
llmProviders,
folders,
openedFolders,
shouldShowWelcomeModal,
toggleSidebar,
userInputPrompts,
} = data;
return (
<>
<InstantSSRAutoRefresh />
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
<ChatProvider
value={{
user,
chatSessions,
availableSources,
availableDocumentSets: documentSets,
availableAssistants: assistants,
availableTags: tags,
llmProviders,
folders,
openedFolders,
userInputPrompts,
}}
>
<WrappedAssistantsGallery
initiallyToggled={toggleSidebar}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
user={user}
assistants={assistants}
/>
</ChatProvider>
<InstantSSRAutoRefresh />
<WrappedAssistantsGallery
initiallyToggled={toggleSidebar}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
user={user}
assistants={assistants}
/>
</>
);
}

View File

@@ -48,7 +48,6 @@ export default function WrappedPrompts({
content={(contentProps) => (
<div className="mx-auto w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
<AssistantsPageTitle>Prompt Gallery</AssistantsPageTitle>
<InstantSSRAutoRefresh />
<PromptSection
promptLibrary={promptLibrary || []}
isLoading={promptLibraryIsLoading}

View File

@@ -1,5 +1,4 @@
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { ChatProvider } from "@/components/context/ChatContext";
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
import { fetchChatData } from "@/lib/chat/fetchChatData";
import { unstable_noStore as noStore } from "next/cache";
@@ -22,47 +21,27 @@ export default async function GalleryPage({
const {
user,
chatSessions,
availableSources,
documentSets,
assistants,
tags,
llmProviders,
folders,
openedFolders,
shouldShowWelcomeModal,
toggleSidebar,
userInputPrompts,
} = data;
return (
<>
<InstantSSRAutoRefresh />
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
<ChatProvider
value={{
user,
chatSessions,
availableSources,
availableDocumentSets: documentSets,
availableAssistants: assistants,
availableTags: tags,
llmProviders,
folders,
openedFolders,
userInputPrompts,
}}
>
<WrappedAssistantsMine
initiallyToggled={toggleSidebar}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
user={user}
assistants={assistants}
/>
</ChatProvider>
<InstantSSRAutoRefresh />
<WrappedAssistantsMine
initiallyToggled={toggleSidebar}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
user={user}
assistants={assistants}
/>
</>
);
}

View File

@@ -1,7 +1,6 @@
"use client";
import ReactMarkdown from "react-markdown";
import { redirect, useRouter, useSearchParams } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import {
BackendChatSession,
BackendMessage,
@@ -23,7 +22,6 @@ import Cookies from "js-cookie";
import { HistorySidebar } from "./sessionSidebar/HistorySidebar";
import { Persona } from "../admin/assistants/interfaces";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import {
buildChatUrl,
buildLatestMessageChain,
@@ -84,7 +82,6 @@ import FixedLogo from "./shared_chat_search/FixedLogo";
import { getSecondsUntilExpiration } from "@/lib/time";
import { SetDefaultModelModal } from "./modal/SetDefaultModelModal";
import { DeleteChatModal } from "./modal/DeleteChatModal";
import remarkGfm from "remark-gfm";
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
@@ -1304,7 +1301,6 @@ export function ChatPage({
return (
<>
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
<InstantSSRAutoRefresh />
{/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit.
Only used in the EE version of the app. */}
{popup}

View File

@@ -1,281 +0,0 @@
import { useChatContext } from "@/components/context/ChatContext";
import { FilterManager } from "@/lib/hooks";
import { listSourceMetadata } from "@/lib/sources";
import { useEffect, useRef, useState } from "react";
import {
DateRangePicker,
DateRangePickerItem,
Divider,
Text,
} from "@tremor/react";
import { getXDaysAgo } from "@/lib/dateUtils";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import { Bubble } from "@/components/Bubble";
import { FiX } from "react-icons/fi";
import { getValidTags } from "@/lib/tags/tagUtils";
import debounce from "lodash/debounce";
import { Tag } from "@/lib/types";
export function FiltersTab({
filterManager,
}: {
filterManager: FilterManager;
}): JSX.Element {
const { availableSources, availableDocumentSets, availableTags } =
useChatContext();
const [filterValue, setFilterValue] = useState<string>("");
const [filteredTags, setFilteredTags] = useState<Tag[]>(availableTags);
const inputRef = useRef<HTMLInputElement>(null);
const allSources = listSourceMetadata();
const availableSourceMetadata = allSources.filter((source) =>
availableSources.includes(source.internalName)
);
const debouncedFetchTags = useRef(
debounce(async (value: string) => {
if (value) {
const fetchedTags = await getValidTags(value);
setFilteredTags(fetchedTags);
} else {
setFilteredTags(availableTags);
}
}, 50)
).current;
useEffect(() => {
debouncedFetchTags(filterValue);
return () => {
debouncedFetchTags.cancel();
};
}, [filterValue, availableTags, debouncedFetchTags]);
return (
<div className="overflow-hidden flex flex-col">
<div className="overflow-y-auto">
<div>
<div className="pb-4">
<h3 className="text-lg font-semibold">Time Range</h3>
<Text>
Choose the time range we should search over. If only one date is
selected, will only search after the specified date.
</Text>
<div className="mt-2">
<DateRangePicker
className="w-96"
value={{
from: filterManager.timeRange?.from,
to: filterManager.timeRange?.to,
selectValue: filterManager.timeRange?.selectValue,
}}
onValueChange={(value) =>
filterManager.setTimeRange({
from: value.from,
to: value.to,
selectValue: value.selectValue,
})
}
selectPlaceholder="Select range"
enableSelect
>
<DateRangePickerItem
key="Last 30 Days"
value="Last 30 Days"
from={getXDaysAgo(30)}
to={new Date()}
>
Last 30 Days
</DateRangePickerItem>
<DateRangePickerItem
key="Last 7 Days"
value="Last 7 Days"
from={getXDaysAgo(7)}
to={new Date()}
>
Last 7 Days
</DateRangePickerItem>
<DateRangePickerItem
key="Today"
value="Today"
from={getXDaysAgo(1)}
to={new Date()}
>
Today
</DateRangePickerItem>
</DateRangePicker>
</div>
</div>
<Divider />
<div className="mb-8">
<h3 className="text-lg font-semibold">Knowledge Sets</h3>
<Text>
Choose which knowledge sets we should search over. If multiple are
selected, we will search through all of them.
</Text>
<ul className="mt-3">
{availableDocumentSets.length > 0 ? (
availableDocumentSets.map((set) => {
const isSelected =
filterManager.selectedDocumentSets.includes(set.name);
return (
<DocumentSetSelectable
key={set.id}
documentSet={set}
isSelected={isSelected}
onSelect={() =>
filterManager.setSelectedDocumentSets((prev) =>
isSelected
? prev.filter((s) => s !== set.name)
: [...prev, set.name]
)
}
/>
);
})
) : (
<li>No knowledge sets available</li>
)}
</ul>
</div>
<Divider />
<div className="mb-4">
<h3 className="text-lg font-semibold">Sources</h3>
<Text>
Choose which sources we should search over. If multiple sources
are selected, we will search through all of them.
</Text>
<ul className="mt-3 flex gap-2">
{availableSourceMetadata.length > 0 ? (
availableSourceMetadata.map((sourceMetadata) => {
const isSelected = filterManager.selectedSources.some(
(selectedSource) =>
selectedSource.internalName ===
sourceMetadata.internalName
);
return (
<Bubble
key={sourceMetadata.internalName}
isSelected={isSelected}
onClick={() =>
filterManager.setSelectedSources((prev) =>
isSelected
? prev.filter(
(s) =>
s.internalName !== sourceMetadata.internalName
)
: [...prev, sourceMetadata]
)
}
showCheckbox={true}
>
<div className="flex items-center space-x-2">
{sourceMetadata?.icon({ size: 16 })}
<span>{sourceMetadata.displayName}</span>
</div>
</Bubble>
);
})
) : (
<li>No sources available</li>
)}
</ul>
</div>
<Divider />
<div className="mb-8">
<h3 className="text-lg font-semibold">Tags</h3>
<ul className="space-2 gap-2 flex flex-wrap mt-2">
{filterManager.selectedTags.length > 0 ? (
filterManager.selectedTags.map((tag) => (
<Bubble
key={tag.tag_key + tag.tag_value}
isSelected={true}
onClick={() =>
filterManager.setSelectedTags((prev) =>
prev.filter(
(t) =>
t.tag_key !== tag.tag_key ||
t.tag_value !== tag.tag_value
)
)
}
>
<div className="flex items-center space-x-2 text-sm">
<p>
{tag.tag_key}={tag.tag_value}
</p>{" "}
<FiX />
</div>
</Bubble>
))
) : (
<p className="text-xs italic">No selected tags</p>
)}
</ul>
<div className="w-96 mt-2">
<div>
<div className="mb-2 pt-2">
<input
ref={inputRef}
className="w-full border border-border py-0.5 px-2 rounded text-sm h-8"
placeholder="Find a tag"
value={filterValue}
onChange={(event) => setFilterValue(event.target.value)}
/>
</div>
<div className="max-h-48 flex flex-col gap-y-1 overflow-y-auto">
{filteredTags.length > 0 ? (
filteredTags
.filter(
(tag) =>
!filterManager.selectedTags.some(
(selectedTag) =>
selectedTag.tag_key === tag.tag_key &&
selectedTag.tag_value === tag.tag_value
)
)
.slice(0, 12)
.map((tag) => (
<Bubble
key={tag.tag_key + tag.tag_value}
isSelected={filterManager.selectedTags.includes(tag)}
onClick={() =>
filterManager.setSelectedTags((prev) =>
filterManager.selectedTags.includes(tag)
? prev.filter(
(t) =>
t.tag_key !== tag.tag_key ||
t.tag_value !== tag.tag_value
)
: [...prev, tag]
)
}
>
<>
{tag.tag_key}={tag.tag_value}
</>
</Bubble>
))
) : (
<div className="text-sm px-2 py-2">
No matching tags found
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -3,11 +3,8 @@ import { unstable_noStore as noStore } from "next/cache";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
import { NoCompleteSourcesModal } from "@/components/initialSetup/search/NoCompleteSourceModal";
import { ChatProvider } from "@/components/context/ChatContext";
import { fetchChatData } from "@/lib/chat/fetchChatData";
import FunctionalWrapper from "./shared_chat_search/FunctionalWrapper";
import { ChatPage } from "./ChatPage";
import WrappedChat from "./WrappedChat";
export default async function Page({

View File

@@ -69,11 +69,11 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
const currentChatId = currentChatSession?.id;
// prevent the NextJS Router cache from causing the chat sidebar to not
// update / show an outdated list of chats
useEffect(() => {
router.refresh();
}, [currentChatId]);
// NOTE: do not do something like the below - assume that the parent
// will handle properly refreshing the existingChats
// useEffect(() => {
// router.refresh();
// }, [currentChatId]);
const combinedSettings = useContext(SettingsContext);
if (!combinedSettings) {

View File

@@ -1,12 +1,19 @@
import "./globals.css";
import { getCombinedSettings } from "@/components/settings/lib";
import { CUSTOM_ANALYTICS_ENABLED } from "@/lib/constants";
import {
fetchEnterpriseSettingsSS,
getCombinedSettings,
} from "@/components/settings/lib";
import {
CUSTOM_ANALYTICS_ENABLED,
SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED,
} from "@/lib/constants";
import { SettingsProvider } from "@/components/settings/SettingsProvider";
import { Metadata } from "next";
import { buildClientUrl } from "@/lib/utilsSS";
import { Inter } from "next/font/google";
import Head from "next/head";
import { EnterpriseSettings } from "./admin/settings/interfaces";
const inter = Inter({
subsets: ["latin"],
@@ -15,15 +22,18 @@ const inter = Inter({
});
export async function generateMetadata(): Promise<Metadata> {
const dynamicSettings = await getCombinedSettings({ forceRetrieval: true });
const logoLocation =
dynamicSettings.enterpriseSettings &&
dynamicSettings.enterpriseSettings?.use_custom_logo
? "/api/enterprise-settings/logo"
: buildClientUrl("/danswer.ico");
let logoLocation = buildClientUrl("/danswer.ico");
let enterpriseSettings: EnterpriseSettings | null = null;
if (SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
enterpriseSettings = await (await fetchEnterpriseSettingsSS()).json();
logoLocation =
enterpriseSettings && enterpriseSettings.use_custom_logo
? "/api/enterprise-settings/logo"
: buildClientUrl("/danswer.ico");
}
return {
title: dynamicSettings.enterpriseSettings?.application_name ?? "Danswer",
title: enterpriseSettings?.application_name ?? "Danswer",
description: "Question answering for your documents",
icons: {
icon: logoLocation,

View File

@@ -185,6 +185,7 @@ export default async function Home() {
<>
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
<InstantSSRAutoRefresh />
{!shouldShowWelcomeModal &&
!shouldDisplayNoSourcesModal &&
@@ -200,7 +201,6 @@ export default async function Home() {
Only used in the EE version of the app. */}
<ChatPopup />
<InstantSSRAutoRefresh />
<WrappedSearch
disabledAgentic={DISABLE_LLM_DOC_RELEVANCE}
initiallyToggled={toggleSidebar}

View File

@@ -10,12 +10,24 @@ import {
import { fetchSS } from "@/lib/utilsSS";
import { getWebVersion } from "@/lib/version";
export async function fetchStandardSettingsSS() {
return fetchSS("/settings");
}
export async function fetchEnterpriseSettingsSS() {
return fetchSS("/enterprise-settings");
}
export async function fetchCustomAnalyticsScriptSS() {
return fetchSS("/enterprise-settings/custom-analytics-script");
}
export async function fetchSettingsSS() {
const tasks = [fetchSS("/settings")];
const tasks = [fetchStandardSettingsSS()];
if (SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
tasks.push(fetchSS("/enterprise-settings"));
tasks.push(fetchEnterpriseSettingsSS());
if (CUSTOM_ANALYTICS_ENABLED) {
tasks.push(fetchSS("/enterprise-settings/custom-analytics-script"));
tasks.push(fetchCustomAnalyticsScriptSS());
}
}

View File

@@ -0,0 +1,236 @@
import {
AuthTypeMetadata,
getAuthTypeMetadataSS,
getCurrentUserSS,
} from "@/lib/userSS";
import { fetchSS } from "@/lib/utilsSS";
import {
CCPairBasicInfo,
DocumentSet,
Tag,
User,
ValidSources,
} from "@/lib/types";
import { ChatSession } from "@/app/chat/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { FullEmbeddingModelResponse } from "@/components/embedding/interfaces";
import { Settings } from "@/app/admin/settings/interfaces";
import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
import { Folder } from "@/app/chat/folders/interfaces";
import { personaComparator } from "@/app/admin/assistants/lib";
import { cookies } from "next/headers";
import {
SIDEBAR_TOGGLED_COOKIE_NAME,
DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME,
} from "@/components/resizable/constants";
import { hasCompletedWelcomeFlowSS } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
import { fetchAssistantsSS } from "../assistants/fetchAssistantsSS";
import { NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN } from "../constants";
interface FetchChatDataResult {
user?: User | null;
chatSessions?: ChatSession[];
ccPairs?: CCPairBasicInfo[];
availableSources?: ValidSources[];
documentSets?: DocumentSet[];
assistants?: Persona[];
tags?: Tag[];
llmProviders?: LLMProviderDescriptor[];
folders?: Folder[];
openedFolders?: Record<string, boolean>;
defaultAssistantId?: number;
toggleSidebar?: boolean;
finalDocumentSidebarInitialWidth?: number;
shouldShowWelcomeModal?: boolean;
shouldDisplaySourcesIncompleteModal?: boolean;
userInputPrompts?: InputPrompt[];
}
type FetchOption =
| "user"
| "chatSessions"
| "ccPairs"
| "documentSets"
| "assistants"
| "tags"
| "llmProviders"
| "folders"
| "userInputPrompts";
/*
NOTE: currently unused, but leaving here for future use.
*/
export async function fetchSomeChatData(
searchParams: { [key: string]: string },
fetchOptions: FetchOption[] = []
): Promise<FetchChatDataResult | { redirect: string }> {
const tasks: Promise<any>[] = [];
const taskMap: Record<FetchOption, () => Promise<any>> = {
user: getCurrentUserSS,
chatSessions: () => fetchSS("/chat/get-user-chat-sessions"),
ccPairs: () => fetchSS("/manage/indexing-status"),
documentSets: () => fetchSS("/manage/document-set"),
assistants: fetchAssistantsSS,
tags: () => fetchSS("/query/valid-tags"),
llmProviders: fetchLLMProvidersSS,
folders: () => fetchSS("/folder"),
userInputPrompts: () => fetchSS("/input_prompt?include_public=true"),
};
// Always fetch auth type metadata
tasks.push(getAuthTypeMetadataSS());
// Add tasks based on fetchOptions
fetchOptions.forEach((option) => {
if (taskMap[option]) {
tasks.push(taskMap[option]());
}
});
let results: any[] = await Promise.all(tasks);
const authTypeMetadata = results.shift() as AuthTypeMetadata | null;
const authDisabled = authTypeMetadata?.authType === "disabled";
let user: User | null = null;
if (fetchOptions.includes("user")) {
user = results.shift();
if (!authDisabled && !user) {
return { redirect: "/auth/login" };
}
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
return { redirect: "/auth/waiting-on-verification" };
}
}
const result: FetchChatDataResult = {};
for (let i = 0; i < fetchOptions.length; i++) {
const option = fetchOptions[i];
const result = results[i];
switch (option) {
case "user":
result.user = user;
break;
case "chatSessions":
result.chatSessions = result?.ok
? ((await result.json()) as { sessions: ChatSession[] }).sessions
: [];
break;
case "ccPairs":
result.ccPairs = result?.ok
? ((await result.json()) as CCPairBasicInfo[])
: [];
break;
case "documentSets":
result.documentSets = result?.ok
? ((await result.json()) as DocumentSet[])
: [];
break;
case "assistants":
const [rawAssistantsList, assistantsFetchError] = result as [
Persona[],
string | null,
];
result.assistants = rawAssistantsList
.filter((assistant) => assistant.is_visible)
.sort(personaComparator);
break;
case "tags":
result.tags = result?.ok
? ((await result.json()) as { tags: Tag[] }).tags
: [];
break;
case "llmProviders":
result.llmProviders = result || [];
break;
case "folders":
result.folders = result?.ok
? ((await result.json()) as { folders: Folder[] }).folders
: [];
break;
case "userInputPrompts":
result.userInputPrompts = result?.ok
? ((await result.json()) as InputPrompt[])
: [];
break;
}
}
if (result.ccPairs) {
result.availableSources = Array.from(
new Set(result.ccPairs.map((ccPair) => ccPair.source))
);
}
if (result.chatSessions) {
result.chatSessions.sort((a, b) => (a.id > b.id ? -1 : 1));
}
if (fetchOptions.includes("assistants") && result.assistants) {
const hasAnyConnectors = result.ccPairs && result.ccPairs.length > 0;
if (!hasAnyConnectors) {
result.assistants = result.assistants.filter(
(assistant) => assistant.num_chunks === 0
);
}
const hasOpenAIProvider =
result.llmProviders &&
result.llmProviders.some((provider) => provider.provider === "openai");
if (!hasOpenAIProvider) {
result.assistants = result.assistants.filter(
(assistant) =>
!assistant.tools.some(
(tool) => tool.in_code_tool_id === "ImageGenerationTool"
)
);
}
}
if (fetchOptions.includes("folders")) {
const openedFoldersCookie = cookies().get("openedFolders");
result.openedFolders = openedFoldersCookie
? JSON.parse(openedFoldersCookie.value)
: {};
}
const defaultAssistantIdRaw = searchParams["assistantId"];
result.defaultAssistantId = defaultAssistantIdRaw
? parseInt(defaultAssistantIdRaw)
: undefined;
const documentSidebarCookieInitialWidth = cookies().get(
DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME
);
const sidebarToggled = cookies().get(SIDEBAR_TOGGLED_COOKIE_NAME);
result.toggleSidebar = sidebarToggled
? sidebarToggled.value.toLowerCase() === "true"
: NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN;
result.finalDocumentSidebarInitialWidth = documentSidebarCookieInitialWidth
? parseInt(documentSidebarCookieInitialWidth.value)
: undefined;
if (fetchOptions.includes("ccPairs") && result.ccPairs) {
const hasAnyConnectors = result.ccPairs.length > 0;
result.shouldShowWelcomeModal =
!hasCompletedWelcomeFlowSS() &&
!hasAnyConnectors &&
(!user || user.role === "admin");
result.shouldDisplaySourcesIncompleteModal =
hasAnyConnectors &&
!result.shouldShowWelcomeModal &&
!result.ccPairs.some(
(ccPair) => ccPair.has_successful_run && ccPair.docs_indexed > 0
) &&
(!user || user.role === "admin");
}
return result;
}