mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-02-16 23:35:46 +00:00
Compare commits
10 Commits
v2.12.0-cl
...
sidebar-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cc1bdd0df | ||
|
|
065e687c5f | ||
|
|
53cb92b1dd | ||
|
|
9a4d12249d | ||
|
|
6dcea4a894 | ||
|
|
350c72426a | ||
|
|
8bf1760b06 | ||
|
|
2f64f1c037 | ||
|
|
5fcc14ff34 | ||
|
|
a64215815d |
@@ -1,13 +1,13 @@
|
||||
import AgentsPage from "@/refresh-pages/AgentsPage";
|
||||
import * as Layouts from "@/refresh-components/layouts/layouts";
|
||||
import AppPageLayout from "@/layouts/AppPageLayout";
|
||||
import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS";
|
||||
|
||||
export default async function Page() {
|
||||
const headerData = await fetchHeaderDataSS();
|
||||
|
||||
return (
|
||||
<Layouts.AppPage {...headerData}>
|
||||
<AppPageLayout {...headerData}>
|
||||
<AgentsPage />
|
||||
</Layouts.AppPage>
|
||||
</AppPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { SEARCH_PARAM_NAMES } from "@/app/chat/services/searchParams";
|
||||
import { useFederatedConnectors, useFilters, useLlmManager } from "@/lib/hooks";
|
||||
import { OnyxInitializingLoader } from "@/components/OnyxInitializingLoader";
|
||||
import { FiArrowDown } from "react-icons/fi";
|
||||
import { OnyxDocument, MinimalOnyxDocument } from "@/lib/search/interfaces";
|
||||
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||
import Dropzone from "react-dropzone";
|
||||
@@ -74,16 +73,22 @@ import { Suggestions } from "@/sections/Suggestions";
|
||||
import OnboardingFlow from "@/refresh-components/onboarding/OnboardingFlow";
|
||||
import { useOnboardingState } from "@/refresh-components/onboarding/useOnboardingState";
|
||||
import { OnboardingStep } from "@/refresh-components/onboarding/types";
|
||||
import AppPageLayout from "@/layouts/AppPageLayout";
|
||||
import { HeaderData } from "@/lib/headers/fetchHeaderDataSS";
|
||||
import IconButton from "@/refresh-components/buttons/IconButton";
|
||||
import SvgChevronDown from "@/icons/chevron-down";
|
||||
|
||||
const DEFAULT_CONTEXT_TOKENS = 120_000;
|
||||
interface ChatPageProps {
|
||||
documentSidebarInitialWidth?: number;
|
||||
firstMessage?: string;
|
||||
headerData: HeaderData;
|
||||
}
|
||||
|
||||
export default function ChatPage({
|
||||
documentSidebarInitialWidth,
|
||||
firstMessage,
|
||||
headerData,
|
||||
}: ChatPageProps) {
|
||||
// Performance tracking
|
||||
// Keeping this here in case we need to track down slow renders in the future
|
||||
@@ -617,6 +622,29 @@ export default function ChatPage({
|
||||
setTimeout(() => updateCurrentDocumentSidebarVisible(false), 300);
|
||||
}, [updateCurrentDocumentSidebarVisible]);
|
||||
|
||||
const desktopDocumentSidebar =
|
||||
retrievalEnabled && !settings?.isMobile ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 overflow-hidden transition-all duration-300 ease-in-out",
|
||||
documentSidebarVisible ? "w-[25rem]" : "w-[0rem]"
|
||||
)}
|
||||
>
|
||||
<div className="h-full w-[25rem]">
|
||||
<DocumentResults
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
modal={false}
|
||||
closeSidebar={handleDesktopDocumentSidebarClose}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={() => setSelectedDocuments([])}
|
||||
selectedDocumentTokens={0}
|
||||
maxTokens={maxTokens}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// Only show the centered hero layout when there is NO project selected
|
||||
// and there are no messages yet. If a project is selected, prefer a top layout.
|
||||
const showCenteredHero = currentProjectId === null && showCenteredInput;
|
||||
@@ -765,193 +793,179 @@ export default function ChatPage({
|
||||
|
||||
<FederatedOAuthModal />
|
||||
|
||||
<div className="flex flex-row h-full w-full">
|
||||
<div
|
||||
ref={masterFlexboxRef}
|
||||
className="flex h-full w-full overflow-x-hidden"
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{desktopDocumentSidebar}
|
||||
<AppPageLayout
|
||||
settings={headerData.settings}
|
||||
chatSession={headerData.chatSession}
|
||||
className="flex flex-row h-full w-full"
|
||||
>
|
||||
{documentSidebarInitialWidth !== undefined && (
|
||||
<Dropzone
|
||||
key={chatSessionId}
|
||||
onDrop={(acceptedFiles) =>
|
||||
handleMessageSpecificFileUpload(acceptedFiles)
|
||||
}
|
||||
noClick
|
||||
<div className="flex flex-row h-full w-full">
|
||||
<div
|
||||
ref={masterFlexboxRef}
|
||||
className="flex h-full w-full overflow-x-hidden"
|
||||
>
|
||||
{({ getRootProps }) => (
|
||||
<div
|
||||
className="h-full w-full relative flex-auto min-w-0"
|
||||
{...getRootProps()}
|
||||
{documentSidebarInitialWidth !== undefined && (
|
||||
<Dropzone
|
||||
key={chatSessionId}
|
||||
onDrop={(acceptedFiles) =>
|
||||
handleMessageSpecificFileUpload(acceptedFiles)
|
||||
}
|
||||
noClick
|
||||
>
|
||||
<div
|
||||
onScroll={handleScroll}
|
||||
className="w-full h-[calc(100dvh-100px)] flex flex-col default-scrollbar overflow-y-auto overflow-x-hidden relative"
|
||||
ref={scrollableDivRef}
|
||||
>
|
||||
<MessagesDisplay
|
||||
messageHistory={messageHistory}
|
||||
completeMessageTree={completeMessageTree}
|
||||
liveAssistant={liveAssistant}
|
||||
llmManager={llmManager}
|
||||
deepResearchEnabled={deepResearchEnabled}
|
||||
currentMessageFiles={currentMessageFiles}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
onSubmit={onSubmit}
|
||||
onMessageSelection={onMessageSelection}
|
||||
stopGenerating={stopGenerating}
|
||||
uncaughtError={uncaughtError}
|
||||
loadingError={loadingError}
|
||||
handleResubmitLastMessage={handleResubmitLastMessage}
|
||||
autoScrollEnabled={autoScrollEnabled}
|
||||
getContainerHeight={getContainerHeight}
|
||||
lastMessageRef={lastMessageRef}
|
||||
endPaddingRef={endPaddingRef}
|
||||
endDivRef={endDivRef}
|
||||
hasPerformedInitialScroll={hasPerformedInitialScroll}
|
||||
chatSessionId={chatSessionId}
|
||||
enterpriseSettings={enterpriseSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={inputRef}
|
||||
className={cn(
|
||||
"absolute pointer-events-none z-10 w-full",
|
||||
showCenteredHero
|
||||
? "inset-0"
|
||||
: currentProjectId !== null && showCenteredInput
|
||||
? "top-0 left-0 right-0"
|
||||
: "bottom-0 left-0 right-0 translate-y-0"
|
||||
)}
|
||||
>
|
||||
{!showCenteredInput && aboveHorizon && (
|
||||
<div className="mx-auto w-fit !pointer-events-none flex sticky justify-center">
|
||||
<button
|
||||
onClick={() => clientScrollToBottom()}
|
||||
className="p-1 pointer-events-auto text-text-03 rounded-2xl bg-background-neutral-02 border border-border mx-auto"
|
||||
>
|
||||
<FiArrowDown size={18} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{({ getRootProps }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-auto w-[95%] mx-auto relative text-text-04 justify-center",
|
||||
showCenteredHero
|
||||
? "h-full grid grid-rows-[1fr_auto_1fr]"
|
||||
: "mb-8"
|
||||
)}
|
||||
className="h-full w-full relative flex-auto min-w-0"
|
||||
{...getRootProps()}
|
||||
>
|
||||
{currentProjectId == null && showCenteredInput && (
|
||||
<WelcomeMessage liveAssistant={liveAssistant} />
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center",
|
||||
showCenteredHero && "row-start-2"
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
className="w-full h-[calc(100dvh-100px)] flex flex-col default-scrollbar overflow-y-auto overflow-x-hidden relative"
|
||||
ref={scrollableDivRef}
|
||||
>
|
||||
{currentProjectId !== null && projectPanelVisible && (
|
||||
<ProjectContextPanel
|
||||
projectTokenCount={projectContextTokenCount}
|
||||
availableContextTokens={availableContextTokens}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(showOnboarding ||
|
||||
(user?.role !== UserRole.ADMIN &&
|
||||
!user?.personalization?.name)) &&
|
||||
currentProjectId === null && (
|
||||
<OnboardingFlow
|
||||
handleHideOnboarding={() =>
|
||||
setShowOnboarding(false)
|
||||
}
|
||||
state={onboardingState}
|
||||
actions={onboardingActions}
|
||||
llmDescriptors={llmDescriptors}
|
||||
/>
|
||||
)}
|
||||
<ChatInputBar
|
||||
deepResearchEnabled={deepResearchEnabled}
|
||||
toggleDeepResearch={toggleDeepResearch}
|
||||
toggleDocumentSidebar={toggleDocumentSidebar}
|
||||
filterManager={filterManager}
|
||||
<MessagesDisplay
|
||||
messageHistory={messageHistory}
|
||||
completeMessageTree={completeMessageTree}
|
||||
liveAssistant={liveAssistant}
|
||||
llmManager={llmManager}
|
||||
removeDocs={() => setSelectedDocuments([])}
|
||||
retrievalEnabled={retrievalEnabled}
|
||||
selectedDocuments={selectedDocuments}
|
||||
message={message}
|
||||
setMessage={setMessage}
|
||||
stopGenerating={stopGenerating}
|
||||
onSubmit={handleChatInputSubmit}
|
||||
chatState={currentChatState}
|
||||
currentSessionFileTokenCount={
|
||||
existingChatSessionId
|
||||
? currentSessionFileTokenCount
|
||||
: projectContextTokenCount
|
||||
}
|
||||
availableContextTokens={availableContextTokens}
|
||||
selectedAssistant={selectedAssistant || liveAssistant}
|
||||
handleFileUpload={handleMessageSpecificFileUpload}
|
||||
textAreaRef={textAreaRef}
|
||||
deepResearchEnabled={deepResearchEnabled}
|
||||
currentMessageFiles={currentMessageFiles}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
disabled={
|
||||
llmManager.hasAnyProvider === false ||
|
||||
onboardingState.currentStep !==
|
||||
OnboardingStep.Complete
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
onMessageSelection={onMessageSelection}
|
||||
stopGenerating={stopGenerating}
|
||||
uncaughtError={uncaughtError}
|
||||
loadingError={loadingError}
|
||||
handleResubmitLastMessage={handleResubmitLastMessage}
|
||||
autoScrollEnabled={autoScrollEnabled}
|
||||
getContainerHeight={getContainerHeight}
|
||||
lastMessageRef={lastMessageRef}
|
||||
endPaddingRef={endPaddingRef}
|
||||
endDivRef={endDivRef}
|
||||
hasPerformedInitialScroll={hasPerformedInitialScroll}
|
||||
chatSessionId={chatSessionId}
|
||||
enterpriseSettings={enterpriseSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentProjectId !== null && (
|
||||
<div className="transition-all duration-700 ease-out">
|
||||
<ProjectChatSessionList />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{liveAssistant.starter_messages &&
|
||||
liveAssistant.starter_messages.length > 0 &&
|
||||
messageHistory.length === 0 &&
|
||||
showCenteredHero && (
|
||||
<div className="mt-6 row-start-3 max-w-[50rem]">
|
||||
<Suggestions onSubmit={onSubmit} />
|
||||
<div
|
||||
ref={inputRef}
|
||||
className={cn(
|
||||
"absolute z-10 w-full",
|
||||
showCenteredHero
|
||||
? "inset-0"
|
||||
: currentProjectId !== null && showCenteredInput
|
||||
? "top-0 left-0 right-0"
|
||||
: "bottom-0 left-0 right-0 translate-y-0"
|
||||
)}
|
||||
>
|
||||
{!showCenteredInput && aboveHorizon && (
|
||||
<div className="mx-auto flex justify-center py-4">
|
||||
<IconButton
|
||||
icon={SvgChevronDown}
|
||||
onClick={() => clientScrollToBottom()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 overflow-hidden transition-all duration-300 ease-in-out",
|
||||
documentSidebarVisible && !settings?.isMobile
|
||||
? "w-[25rem]"
|
||||
: "w-[0rem]"
|
||||
)}
|
||||
>
|
||||
<div className="h-full w-[25rem]">
|
||||
{/* IMPORTANT: this is a memoized component, and it's very important
|
||||
for performance reasons that this stays true. MAKE SURE that all function
|
||||
props are wrapped in useCallback. */}
|
||||
<DocumentResults
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
modal={false}
|
||||
closeSidebar={handleDesktopDocumentSidebarClose}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={() => setSelectedDocuments([])}
|
||||
// TODO (chris): fix
|
||||
selectedDocumentTokens={0}
|
||||
maxTokens={maxTokens}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-auto w-[95%] mx-auto relative text-text-04 justify-center",
|
||||
showCenteredHero
|
||||
? "h-full grid grid-rows-[1fr_auto_1fr]"
|
||||
: "mb-8"
|
||||
)}
|
||||
>
|
||||
{currentProjectId == null && showCenteredInput && (
|
||||
<WelcomeMessage liveAssistant={liveAssistant} />
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center",
|
||||
showCenteredHero && "row-start-2"
|
||||
)}
|
||||
>
|
||||
{currentProjectId !== null &&
|
||||
projectPanelVisible && (
|
||||
<ProjectContextPanel
|
||||
projectTokenCount={projectContextTokenCount}
|
||||
availableContextTokens={
|
||||
availableContextTokens
|
||||
}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(showOnboarding ||
|
||||
(user?.role !== UserRole.ADMIN &&
|
||||
!user?.personalization?.name)) &&
|
||||
currentProjectId === null && (
|
||||
<OnboardingFlow
|
||||
handleHideOnboarding={() =>
|
||||
setShowOnboarding(false)
|
||||
}
|
||||
state={onboardingState}
|
||||
actions={onboardingActions}
|
||||
llmDescriptors={llmDescriptors}
|
||||
/>
|
||||
)}
|
||||
<ChatInputBar
|
||||
deepResearchEnabled={deepResearchEnabled}
|
||||
toggleDeepResearch={toggleDeepResearch}
|
||||
toggleDocumentSidebar={toggleDocumentSidebar}
|
||||
filterManager={filterManager}
|
||||
llmManager={llmManager}
|
||||
removeDocs={() => setSelectedDocuments([])}
|
||||
retrievalEnabled={retrievalEnabled}
|
||||
selectedDocuments={selectedDocuments}
|
||||
message={message}
|
||||
setMessage={setMessage}
|
||||
stopGenerating={stopGenerating}
|
||||
onSubmit={handleChatInputSubmit}
|
||||
chatState={currentChatState}
|
||||
currentSessionFileTokenCount={
|
||||
existingChatSessionId
|
||||
? currentSessionFileTokenCount
|
||||
: projectContextTokenCount
|
||||
}
|
||||
availableContextTokens={availableContextTokens}
|
||||
selectedAssistant={
|
||||
selectedAssistant || liveAssistant
|
||||
}
|
||||
handleFileUpload={handleMessageSpecificFileUpload}
|
||||
textAreaRef={textAreaRef}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
disabled={
|
||||
llmManager.hasAnyProvider === false ||
|
||||
onboardingState.currentStep !==
|
||||
OnboardingStep.Complete
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentProjectId !== null && (
|
||||
<div className="transition-all duration-700 ease-out">
|
||||
<ProjectChatSessionList />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{liveAssistant.starter_messages &&
|
||||
liveAssistant.starter_messages.length > 0 &&
|
||||
messageHistory.length === 0 &&
|
||||
showCenteredHero && (
|
||||
<div className="mt-6 row-start-3 max-w-[50rem]">
|
||||
<Suggestions onSubmit={onSubmit} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppPageLayout>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import InputPrompts from "@/app/chat/input-prompts/InputPrompts";
|
||||
import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS";
|
||||
import * as Layouts from "@/refresh-components/layouts/layouts";
|
||||
import AppPageLayout from "@/layouts/AppPageLayout";
|
||||
|
||||
export default async function InputPromptsPage() {
|
||||
const headerData = await fetchHeaderDataSS();
|
||||
|
||||
return (
|
||||
<Layouts.AppPage
|
||||
<AppPageLayout
|
||||
{...headerData}
|
||||
className="w-full px-32 py-16 mx-auto container"
|
||||
>
|
||||
<InputPrompts />
|
||||
</Layouts.AppPage>
|
||||
</AppPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { fetchChatData } from "@/lib/chat/fetchChatData";
|
||||
import { ChatProvider } from "@/refresh-components/contexts/ChatContext";
|
||||
import { ProjectsProvider } from "./projects/ProjectsContext";
|
||||
import AppSidebar from "@/sections/sidebar/AppSidebar";
|
||||
|
||||
export interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
@@ -58,9 +57,9 @@ export default async function Layout({ children }: LayoutProps) {
|
||||
defaultAssistantId={defaultAssistantId}
|
||||
>
|
||||
<ProjectsProvider initialProjects={projects}>
|
||||
<div className="flex flex-row w-full h-full">
|
||||
<div className="flex flex-row w-full h-full relative">
|
||||
<AppSidebar />
|
||||
{children}
|
||||
<div className="flex-1">{children}</div>
|
||||
</div>
|
||||
</ProjectsProvider>
|
||||
</ChatProvider>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
|
||||
import { cookies } from "next/headers";
|
||||
import NRFPage from "./NRFPage";
|
||||
import { NRFPreferencesProvider } from "../../../components/context/NRFPreferencesContext";
|
||||
import * as Layouts from "@/refresh-components/layouts/layouts";
|
||||
import AppPageLayout from "@/layouts/AppPageLayout";
|
||||
import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS";
|
||||
|
||||
export default async function Page() {
|
||||
@@ -12,11 +12,11 @@ export default async function Page() {
|
||||
const headerData = await fetchHeaderDataSS();
|
||||
|
||||
return (
|
||||
<Layouts.AppPage {...headerData} className="h-full w-full">
|
||||
<AppPageLayout {...headerData} className="h-full w-full">
|
||||
<InstantSSRAutoRefresh />
|
||||
<NRFPreferencesProvider>
|
||||
<NRFPage requestCookies={requestCookies} />
|
||||
</NRFPreferencesProvider>
|
||||
</Layouts.AppPage>
|
||||
</AppPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as Layouts from "@/refresh-components/layouts/layouts";
|
||||
import ChatPage from "@/app/chat/components/ChatPage";
|
||||
import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS";
|
||||
import { SEARCH_PARAM_NAMES } from "./services/searchParams";
|
||||
@@ -13,9 +12,7 @@ export default async function Page(props: PageProps) {
|
||||
const chatSessionId = searchParams[SEARCH_PARAM_NAMES.CHAT_ID];
|
||||
const headerData = await fetchHeaderDataSS(chatSessionId);
|
||||
|
||||
return (
|
||||
<Layouts.AppPage {...headerData}>
|
||||
<ChatPage firstMessage={firstMessage} />
|
||||
</Layouts.AppPage>
|
||||
);
|
||||
// Other pages in `web/src/app/chat` are wrapped with `<AppPageLayout>`.
|
||||
// `chat/page.tsx` is not because it also needs to handle rendering of the document-sidebar (`web/src/app/chat/components/documentSidebar/DocumentResults.tsx`).
|
||||
return <ChatPage firstMessage={firstMessage} headerData={headerData} />;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fetchSS } from "@/lib/utilsSS";
|
||||
import { redirect } from "next/navigation";
|
||||
import { requireAuth } from "@/lib/auth/requireAuth";
|
||||
import { SharedChatDisplay } from "@/app/chat/shared/[chatId]/SharedChatDisplay";
|
||||
import * as Layouts from "@/refresh-components/layouts/layouts";
|
||||
import AppPageLayout from "@/layouts/AppPageLayout";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { constructMiniFiedPersona } from "@/lib/assistantIconUtils";
|
||||
import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS";
|
||||
@@ -43,8 +43,8 @@ export default async function Page(props: PageProps) {
|
||||
const headerData = await fetchHeaderDataSS();
|
||||
|
||||
return (
|
||||
<Layouts.AppPage {...headerData}>
|
||||
<AppPageLayout {...headerData}>
|
||||
<SharedChatDisplay chatSession={chatSession} persona={persona} />
|
||||
</Layouts.AppPage>
|
||||
</AppPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function AppProvider({
|
||||
agents={assistants}
|
||||
pinnedAgentIds={user?.preferences.pinned_assistants || []}
|
||||
>
|
||||
<AppSidebarProvider folded={!!folded}>
|
||||
<AppSidebarProvider collapsed={!!folded}>
|
||||
{children}
|
||||
</AppSidebarProvider>
|
||||
</AgentsProvider>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Text from "@/refresh-components/texts/Text";
|
||||
import Button from "@/refresh-components/buttons/Button";
|
||||
import SvgShare from "@/icons/share";
|
||||
import { CombinedSettings } from "@/app/admin/settings/interfaces";
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import { useMemo, useState, useEffect, useCallback } from "react";
|
||||
import ShareChatSessionModal from "@/app/chat/components/modal/ShareChatSessionModal";
|
||||
import { useChatPageLayout } from "@/app/chat/stores/useChatSessionStore";
|
||||
import IconButton from "@/refresh-components/buttons/IconButton";
|
||||
@@ -31,18 +31,32 @@ import { PopoverMenu } from "@/components/ui/popover";
|
||||
import { PopoverSearchInput } from "@/sections/sidebar/ChatButton";
|
||||
import SimplePopover from "@/refresh-components/SimplePopover";
|
||||
import { FOLDED_SIZE } from "@/refresh-components/Logo";
|
||||
import { useScreenSize } from "@/hooks/useScreenSize";
|
||||
import { useAppSidebarContext } from "@/refresh-components/contexts/AppSidebarContext";
|
||||
import SvgSidebar from "@/icons/sidebar";
|
||||
|
||||
interface AppPageProps extends React.HtmlHTMLAttributes<HTMLDivElement> {
|
||||
interface AppPageLayoutProps extends React.HtmlHTMLAttributes<HTMLDivElement> {
|
||||
settings: CombinedSettings | null;
|
||||
chatSession: ChatSession | null;
|
||||
}
|
||||
|
||||
export function AppPage({
|
||||
// AppPageLayout wraps chat pages with the shared header/footer white-labelling chrome.
|
||||
// It also provides the "Share Chat" and kebab-menu on the right side of the header (for shareable chat pages).
|
||||
//
|
||||
// Since this is such a ubiquitous component, it's been moved to its own `layouts` directory.
|
||||
export default function AppPageLayout({
|
||||
settings,
|
||||
chatSession,
|
||||
className,
|
||||
...rest
|
||||
}: AppPageProps) {
|
||||
}: AppPageLayoutProps) {
|
||||
const { width } = useScreenSize();
|
||||
const { collapsed, setCollapsed } = useAppSidebarContext();
|
||||
const isCompactViewport = width !== undefined ? width < 640 : false; // Tailwind `sm` breakpoint
|
||||
const handleSidebarButtonClick = useCallback(() => {
|
||||
setCollapsed((prev) => !prev);
|
||||
}, [setCollapsed]);
|
||||
|
||||
const [showShareModal, setShowShareModal] = useState(false);
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const [showMoveCustomAgentModal, setShowMoveCustomAgentModal] =
|
||||
@@ -150,7 +164,7 @@ export function AppPage({
|
||||
|
||||
useEffect(() => {
|
||||
if (!showMoveOptions) {
|
||||
const popoverItems = [
|
||||
const items = [
|
||||
<MenuButton
|
||||
key="move"
|
||||
icon={SvgFolderIn}
|
||||
@@ -167,9 +181,9 @@ export function AppPage({
|
||||
Delete
|
||||
</MenuButton>,
|
||||
];
|
||||
setPopoverItems(popoverItems);
|
||||
setPopoverItems(items);
|
||||
} else {
|
||||
const popoverItems = [
|
||||
const items = [
|
||||
<PopoverSearchInput
|
||||
key="search"
|
||||
setShowMoveOptions={setShowMoveOptions}
|
||||
@@ -185,7 +199,7 @@ export function AppPage({
|
||||
</MenuButton>
|
||||
)),
|
||||
];
|
||||
setPopoverItems(popoverItems);
|
||||
setPopoverItems(items);
|
||||
}
|
||||
}, [showMoveOptions, filteredProjects]);
|
||||
|
||||
@@ -233,7 +247,23 @@ export function AppPage({
|
||||
<div className="flex flex-col h-full w-full">
|
||||
{(customHeaderContent || !showCenteredInput) && (
|
||||
<header className="w-full flex flex-row justify-center items-center py-3 px-4 h-16">
|
||||
<div className="flex-1" />
|
||||
<div className="flex-1 flex flex-row items-center">
|
||||
<IconButton
|
||||
icon={SvgSidebar}
|
||||
aria-label={
|
||||
isCompactViewport
|
||||
? collapsed
|
||||
? "Show sidebar"
|
||||
: "Hide sidebar"
|
||||
: collapsed
|
||||
? "Expand sidebar"
|
||||
: "Collapse sidebar"
|
||||
}
|
||||
onClick={handleSidebarButtonClick}
|
||||
internal
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col items-center">
|
||||
<Text text03>{customHeaderContent}</Text>
|
||||
</div>
|
||||
@@ -11,10 +11,12 @@ export interface HeaderData {
|
||||
export async function fetchHeaderDataSS(
|
||||
chatSessionId?: string
|
||||
): Promise<HeaderData> {
|
||||
const settings = await fetchSettingsSS();
|
||||
const backendChatSession = chatSessionId
|
||||
? await fetchBackendChatSessionSS(chatSessionId)
|
||||
: null;
|
||||
const [settings, backendChatSession] = await Promise.all([
|
||||
fetchSettingsSS(),
|
||||
chatSessionId
|
||||
? fetchBackendChatSessionSS(chatSessionId)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
const chatSession = backendChatSession
|
||||
? toChatSession(backendChatSession)
|
||||
: null;
|
||||
|
||||
@@ -12,29 +12,31 @@ import React, {
|
||||
import Cookies from "js-cookie";
|
||||
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
|
||||
|
||||
function setFoldedCookie(folded: boolean) {
|
||||
const foldedAsString = folded.toString();
|
||||
Cookies.set(SIDEBAR_TOGGLED_COOKIE_NAME, foldedAsString, { expires: 365 });
|
||||
function setCollapsedCookie(collapsed: boolean) {
|
||||
const collapsedAsString = collapsed.toString();
|
||||
Cookies.set(SIDEBAR_TOGGLED_COOKIE_NAME, collapsedAsString, {
|
||||
expires: 365,
|
||||
});
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem(SIDEBAR_TOGGLED_COOKIE_NAME, foldedAsString);
|
||||
localStorage.setItem(SIDEBAR_TOGGLED_COOKIE_NAME, collapsedAsString);
|
||||
}
|
||||
}
|
||||
|
||||
export interface AppSidebarProviderProps {
|
||||
folded: boolean;
|
||||
collapsed: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function AppSidebarProvider({
|
||||
folded: initiallyFolded,
|
||||
collapsed: initiallyCollapsed,
|
||||
children,
|
||||
}: AppSidebarProviderProps) {
|
||||
const [folded, setFoldedInternal] = useState(initiallyFolded);
|
||||
const [collapsed, setCollapsedInternal] = useState(initiallyCollapsed);
|
||||
|
||||
const setFolded: Dispatch<SetStateAction<boolean>> = (value) => {
|
||||
setFoldedInternal((prev) => {
|
||||
const setCollapsed: Dispatch<SetStateAction<boolean>> = (value) => {
|
||||
setCollapsedInternal((prev) => {
|
||||
const newState = typeof value === "function" ? value(prev) : value;
|
||||
setFoldedCookie(newState);
|
||||
setCollapsedCookie(newState);
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
@@ -46,7 +48,7 @@ export function AppSidebarProvider({
|
||||
if (!isModifierPressed || event.key !== "e") return;
|
||||
|
||||
event.preventDefault();
|
||||
setFolded((prev) => !prev);
|
||||
setCollapsed((prev) => !prev);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
@@ -58,8 +60,8 @@ export function AppSidebarProvider({
|
||||
return (
|
||||
<AppSidebarContext.Provider
|
||||
value={{
|
||||
folded,
|
||||
setFolded,
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -68,8 +70,8 @@ export function AppSidebarProvider({
|
||||
}
|
||||
|
||||
export interface AppSidebarContextType {
|
||||
folded: boolean;
|
||||
setFolded: Dispatch<SetStateAction<boolean>>;
|
||||
collapsed: boolean;
|
||||
setCollapsed: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const AppSidebarContext = createContext<AppSidebarContextType | undefined>(
|
||||
|
||||
@@ -60,6 +60,7 @@ import { useUser } from "@/components/user/UserProvider";
|
||||
import SvgSettings from "@/icons/settings";
|
||||
import { useAppFocus } from "@/lib/hooks";
|
||||
import { useCreateModal } from "@/refresh-components/contexts/ModalContext";
|
||||
import { useScreenSize } from "@/hooks/useScreenSize";
|
||||
|
||||
// Visible-agents = pinned-agents + current-agent (if current-agent not in pinned-agents)
|
||||
// OR Visible-agents = pinned-agents (if current-agent in pinned-agents)
|
||||
@@ -123,7 +124,7 @@ function RecentsSection({ chatSessions }: RecentsSectionProps) {
|
||||
function AppSidebarInner() {
|
||||
const route = useAppRouter();
|
||||
const { pinnedAgents, setPinnedAgents, currentAgent } = useAgentsContext();
|
||||
const { folded, setFolded } = useAppSidebarContext();
|
||||
const { collapsed, setCollapsed } = useAppSidebarContext();
|
||||
const { chatSessions, refreshChatSessions } = useChatContext();
|
||||
const combinedSettings = useSettingsContext();
|
||||
const { refreshCurrentProjectDetails, fetchProjects, currentProjectId } =
|
||||
@@ -317,7 +318,7 @@ function AppSidebarInner() {
|
||||
<div data-testid="AppSidebar/new-session">
|
||||
<SidebarTab
|
||||
leftIcon={SvgEditBig}
|
||||
folded={folded}
|
||||
folded={collapsed}
|
||||
onClick={() => {
|
||||
if (
|
||||
combinedSettings?.settings?.disable_default_assistant &&
|
||||
@@ -336,27 +337,27 @@ function AppSidebarInner() {
|
||||
</SidebarTab>
|
||||
</div>
|
||||
),
|
||||
[folded, route, activeSidebarTab, combinedSettings, currentAgent]
|
||||
[collapsed, route, activeSidebarTab, combinedSettings, currentAgent]
|
||||
);
|
||||
const moreAgentsButton = useMemo(
|
||||
() => (
|
||||
<div data-testid="AppSidebar/more-agents">
|
||||
<SidebarTab
|
||||
leftIcon={
|
||||
folded || visibleAgents.length === 0
|
||||
collapsed || visibleAgents.length === 0
|
||||
? SvgOnyxOctagon
|
||||
: SvgMoreHorizontal
|
||||
}
|
||||
href="/chat/agents"
|
||||
folded={folded}
|
||||
folded={collapsed}
|
||||
active={activeSidebarTab === "more-agents"}
|
||||
lowlight={!folded}
|
||||
lowlight={!collapsed}
|
||||
>
|
||||
{visibleAgents.length === 0 ? "Explore Agents" : "More Agents"}
|
||||
</SidebarTab>
|
||||
</div>
|
||||
),
|
||||
[folded, activeSidebarTab, visibleAgents]
|
||||
[collapsed, activeSidebarTab, visibleAgents]
|
||||
);
|
||||
const newProjectButton = useMemo(
|
||||
() => (
|
||||
@@ -364,13 +365,13 @@ function AppSidebarInner() {
|
||||
leftIcon={SvgFolderPlus}
|
||||
onClick={() => createProjectModal.toggle(true)}
|
||||
active={createProjectModal.isOpen}
|
||||
folded={folded}
|
||||
lowlight={!folded}
|
||||
folded={collapsed}
|
||||
lowlight={!collapsed}
|
||||
>
|
||||
New Project
|
||||
</SidebarTab>
|
||||
),
|
||||
[folded, createProjectModal.toggle, createProjectModal.isOpen]
|
||||
[collapsed, createProjectModal.toggle, createProjectModal.isOpen]
|
||||
);
|
||||
const settingsButton = useMemo(
|
||||
() => (
|
||||
@@ -379,15 +380,15 @@ function AppSidebarInner() {
|
||||
<SidebarTab
|
||||
href="/admin/indexing/status"
|
||||
leftIcon={SvgSettings}
|
||||
folded={folded}
|
||||
folded={collapsed}
|
||||
>
|
||||
{isAdmin ? "Admin Panel" : "Curator Panel"}
|
||||
</SidebarTab>
|
||||
)}
|
||||
<Settings folded={folded} />
|
||||
<Settings folded={collapsed} />
|
||||
</div>
|
||||
),
|
||||
[folded, isAdmin, isCurator]
|
||||
[collapsed, isAdmin, isCurator]
|
||||
);
|
||||
|
||||
if (!combinedSettings) {
|
||||
@@ -434,9 +435,9 @@ function AppSidebarInner() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<SidebarWrapper folded={folded} setFolded={setFolded}>
|
||||
<SidebarWrapper collapsed={collapsed} setCollapsed={setCollapsed}>
|
||||
<SidebarBody footer={settingsButton} actionButton={newSessionButton}>
|
||||
{folded ? (
|
||||
{collapsed ? (
|
||||
<>
|
||||
{moreAgentsButton}
|
||||
{newProjectButton}
|
||||
@@ -501,7 +502,42 @@ function AppSidebarInner() {
|
||||
);
|
||||
}
|
||||
|
||||
const AppSidebar = memo(AppSidebarInner);
|
||||
AppSidebar.displayName = "AppSidebar";
|
||||
const MemoizedAppSidebarInner = memo(AppSidebarInner);
|
||||
MemoizedAppSidebarInner.displayName = "AppSidebar";
|
||||
|
||||
export default AppSidebar;
|
||||
const MOBILE_SIDEBAR_BREAKPOINT_PX = 640;
|
||||
|
||||
export default function AppSidebar() {
|
||||
const { width } = useScreenSize();
|
||||
const { collapsed, setCollapsed } = useAppSidebarContext();
|
||||
const isCompact =
|
||||
width !== undefined ? width <= MOBILE_SIDEBAR_BREAKPOINT_PX : false;
|
||||
|
||||
if (!isCompact) return <MemoizedAppSidebarInner />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-y-0 left-0 z-50 w-[min(18rem,90vw)] max-w-full bg-background-neutral-00 shadow-03 transition-transform duration-300",
|
||||
collapsed ? "-translate-x-full" : "translate-x-0"
|
||||
)}
|
||||
>
|
||||
<div className="h-full overflow-y-auto">
|
||||
<MemoizedAppSidebarInner />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hitbox to close the sidebar if anything outside of it is touched */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-40 bg-black/40 transition-opacity duration-200",
|
||||
collapsed
|
||||
? "opacity-0 pointer-events-none"
|
||||
: "opacity-100 pointer-events-auto"
|
||||
)}
|
||||
onClick={() => setCollapsed(true)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import SvgSidebar from "@/icons/sidebar";
|
||||
import Logo from "@/refresh-components/Logo";
|
||||
|
||||
interface LogoSectionProps {
|
||||
folded?: boolean;
|
||||
setFolded?: Dispatch<SetStateAction<boolean>>;
|
||||
collapsed?: boolean;
|
||||
setCollapsed?: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function LogoSection({ folded, setFolded }: LogoSectionProps) {
|
||||
function LogoSection({ collapsed, setCollapsed }: LogoSectionProps) {
|
||||
const logo = useCallback(
|
||||
(className?: string) => <Logo folded={folded} className={className} />,
|
||||
[folded]
|
||||
(className?: string) => <Logo folded={collapsed} className={className} />,
|
||||
[collapsed]
|
||||
);
|
||||
const closeButton = useCallback(
|
||||
(shouldFold: boolean) => (
|
||||
@@ -20,10 +20,10 @@ function LogoSection({ folded, setFolded }: LogoSectionProps) {
|
||||
icon={SvgSidebar}
|
||||
tertiary
|
||||
tooltip="Close Sidebar"
|
||||
onClick={() => setFolded?.(shouldFold)}
|
||||
onClick={() => setCollapsed?.(shouldFold)}
|
||||
/>
|
||||
),
|
||||
[setFolded]
|
||||
[setCollapsed]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -36,12 +36,12 @@ function LogoSection({ folded, setFolded }: LogoSectionProps) {
|
||||
//
|
||||
// - @raunakab
|
||||
"flex flex-row items-center py-1 gap-1 min-h-[3.5rem] px-3.5",
|
||||
folded ? "justify-start" : "justify-between"
|
||||
collapsed ? "justify-start" : "justify-between"
|
||||
)}
|
||||
>
|
||||
{folded === undefined ? (
|
||||
{collapsed === undefined ? (
|
||||
logo()
|
||||
) : folded ? (
|
||||
) : collapsed ? (
|
||||
<>
|
||||
<div className="group-hover/SidebarWrapper:hidden">{logo()}</div>
|
||||
<div className="w-full justify-center hidden group-hover/SidebarWrapper:flex">
|
||||
@@ -59,14 +59,14 @@ function LogoSection({ folded, setFolded }: LogoSectionProps) {
|
||||
}
|
||||
|
||||
export interface SidebarWrapperProps {
|
||||
folded?: boolean;
|
||||
setFolded?: Dispatch<SetStateAction<boolean>>;
|
||||
collapsed?: boolean;
|
||||
setCollapsed?: Dispatch<SetStateAction<boolean>>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SidebarWrapper({
|
||||
folded,
|
||||
setFolded,
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
children,
|
||||
}: SidebarWrapperProps) {
|
||||
return (
|
||||
@@ -80,10 +80,10 @@ export default function SidebarWrapper({
|
||||
// @HERE (size of sidebar)
|
||||
//
|
||||
// - @raunakab
|
||||
folded ? "w-[3.25rem]" : "w-[15rem]"
|
||||
collapsed ? "w-[3.25rem]" : "w-[15rem]"
|
||||
)}
|
||||
>
|
||||
<LogoSection folded={folded} setFolded={setFolded} />
|
||||
<LogoSection collapsed={collapsed} setCollapsed={setCollapsed} />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user