mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7dd7546ba3 |
@@ -2,7 +2,10 @@ import * as React from 'react';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, useTheme } from '@mui/joy';
|
||||
import { Box, IconButton, useTheme } from '@mui/joy';
|
||||
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
|
||||
import { DEV_MODE_SETTINGS } from '../settings-modal/UxLabsSettings';
|
||||
|
||||
@@ -32,7 +35,7 @@ import { createErrorContentFragment, createTextContentFragment, DMessageAttachme
|
||||
import { gcChatImageAssets } from '~/common/stores/chat/chat.gc';
|
||||
import { getChatLLMId } from '~/common/stores/llms/store-llms';
|
||||
import { getConversation, getConversationSystemPurposeId, useConversation } from '~/common/stores/chat/store-chats';
|
||||
import { optimaActions, optimaOpenModels, optimaOpenPreferences } from '~/common/layout/optima/useOptima';
|
||||
import { optimaActions, optimaExitChromeless, optimaOpenDrawer, optimaOpenModels, optimaOpenPanel, optimaOpenPreferences, useOptimaChromeless } from '~/common/layout/optima/useOptima';
|
||||
import { useFolderStore } from '~/common/stores/folders/store-chat-folders';
|
||||
import { useIsMobile, useIsTallScreen } from '~/common/components/useMatchMedia';
|
||||
import { useLLM } from '~/common/stores/llms/llms.hooks';
|
||||
@@ -119,6 +122,26 @@ const composerOpenMobileSx: SxProps = {
|
||||
// };
|
||||
|
||||
|
||||
// Chromeless mode floating buttons
|
||||
const chromelessFloatingButtonsSx: SxProps = {
|
||||
position: 'fixed',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
zIndex: 25,
|
||||
display: 'flex',
|
||||
gap: 0.5,
|
||||
opacity: 0.55,
|
||||
transition: 'opacity 0.2s',
|
||||
'&:hover': { opacity: 1 },
|
||||
};
|
||||
|
||||
const chromelessButtonSx: SxProps = {
|
||||
backdropFilter: 'blur(6px)',
|
||||
backgroundColor: 'background.popup',
|
||||
boxShadow: 'xs',
|
||||
};
|
||||
|
||||
|
||||
// Lazy-loaded Modals
|
||||
const DiagramsModalLazy = React.lazy(() => import('~/modules/aifn/digrams/DiagramsModal').then(module => ({ default: module.DiagramsModal })));
|
||||
const FlattenerModalLazy = React.lazy(() => import('~/modules/aifn/flatten/FlattenerModal').then(module => ({ default: module.FlattenerModal })));
|
||||
@@ -214,8 +237,11 @@ export function AppChat() {
|
||||
return activeFolder?.id ?? null;
|
||||
});
|
||||
|
||||
// Chromeless mode
|
||||
const isChromeless = useOptimaChromeless();
|
||||
|
||||
// Composer Auto-hiding
|
||||
const forceComposerHide = !!beamOpenStoreInFocusedPane /* || !focusedPaneConversationId */; // auto-hide when no chat (the 'please select a conversation...' state) doesn't feel good
|
||||
const forceComposerHide = isChromeless || !!beamOpenStoreInFocusedPane /* || !focusedPaneConversationId */; // auto-hide when no chat (the 'please select a conversation...' state) doesn't feel good
|
||||
const composerAutoHide = useComposerAutoHide(forceComposerHide, composerHasContent);
|
||||
|
||||
// Window actions
|
||||
@@ -774,7 +800,22 @@ export function AppChat() {
|
||||
</Box>
|
||||
|
||||
{/* Hover zone for auto-hide */}
|
||||
{!forceComposerHide && composerAutoHide.isHidden && <Box {...composerAutoHide.detectorProps} />}
|
||||
{!isChromeless && !forceComposerHide && composerAutoHide.isHidden && <Box {...composerAutoHide.detectorProps} />}
|
||||
|
||||
{/* Chromeless mode floating buttons */}
|
||||
{isChromeless && (
|
||||
<Box sx={chromelessFloatingButtonsSx}>
|
||||
<IconButton size='sm' variant='soft' color='neutral' onClick={optimaOpenDrawer} sx={chromelessButtonSx} aria-label='Open Drawer'>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<IconButton size='sm' variant='soft' color='neutral' onClick={optimaOpenPanel} sx={chromelessButtonSx} aria-label='Open Menu'>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<IconButton size='sm' variant='soft' color='neutral' onClick={optimaExitChromeless} sx={chromelessButtonSx} aria-label='Exit Chrome-less'>
|
||||
<FullscreenExitIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Diagrams */}
|
||||
{!!diagramConfig && (
|
||||
|
||||
@@ -6,6 +6,8 @@ import AddIcon from '@mui/icons-material/Add';
|
||||
import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined';
|
||||
import CleaningServicesOutlinedIcon from '@mui/icons-material/CleaningServicesOutlined';
|
||||
import CompressIcon from '@mui/icons-material/Compress';
|
||||
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
@@ -20,7 +22,7 @@ import { CodiconSplitVertical } from '~/common/components/icons/CodiconSplitVert
|
||||
import { CodiconSplitVerticalRemove } from '~/common/components/icons/CodiconSplitVerticalRemove';
|
||||
import { FormLabelStart } from '~/common/components/forms/FormLabelStart';
|
||||
import { OptimaPanelGroupedList, OptimaPanelGroupGutter } from '~/common/layout/optima/panel/OptimaPanelGroupedList';
|
||||
import { optimaActions } from '~/common/layout/optima/useOptima';
|
||||
import { optimaActions, optimaToggleChromeless, useOptimaChromeless } from '~/common/layout/optima/useOptima';
|
||||
import { useChatStore } from '~/common/stores/chat/store-chats'; // may be replaced with a dedicated hook for the chat pane
|
||||
import { useLabsDevMode } from '~/common/stores/store-ux-labs';
|
||||
|
||||
@@ -56,6 +58,7 @@ export function ChatPane(props: {
|
||||
const { canAddPane, isMultiPane } = usePaneDuplicateOrClose();
|
||||
const [showSystemMessages, setShowSystemMessages] = useChatShowSystemMessages();
|
||||
const labsDevMode = useLabsDevMode();
|
||||
const isChromeless = useOptimaChromeless();
|
||||
|
||||
const { isArchived, setArchived } = useChatStore(useShallow((state) => {
|
||||
const conversation = state.conversations.find(_c => _c.id === props.conversationId);
|
||||
@@ -147,6 +150,11 @@ export function ChatPane(props: {
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
<ListItemButton onClick={optimaToggleChromeless}>
|
||||
<ListItemDecorator>{isChromeless ? <FullscreenExitIcon /> : <FullscreenIcon />}</ListItemDecorator>
|
||||
{isChromeless ? 'Exit Chrome-less' : 'Chrome-less'}
|
||||
</ListItemButton>
|
||||
|
||||
</OptimaPanelGroupedList>
|
||||
|
||||
{/* Chat Actions group */}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { NavItemApp } from '~/common/app.nav';
|
||||
// import { MobileNav } from './MobileNav';
|
||||
import { OptimaBar } from '~/common/layout/optima/bar/OptimaBar';
|
||||
import { optimaHasMOTD, OptimaMOTD } from '~/common/layout/optima/OptimaMOTD';
|
||||
import { useOptimaChromeless } from '~/common/layout/optima/useOptima';
|
||||
|
||||
|
||||
const pageCoreSx: SxProps = {
|
||||
@@ -33,45 +34,74 @@ const pageCoreBarSx: SxProps = {
|
||||
zIndex: themeZIndexPageBar,
|
||||
};
|
||||
|
||||
const barCollapsibleOpenSx: SxProps = {
|
||||
display: 'grid',
|
||||
gridTemplateRows: '1fr',
|
||||
transition: 'grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
};
|
||||
|
||||
const barCollapsibleClosedSx: SxProps = {
|
||||
display: 'grid',
|
||||
gridTemplateRows: '0fr',
|
||||
transition: 'grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
};
|
||||
|
||||
const barCollapsibleInnerStyle: React.CSSProperties = {
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
contain: 'paint',
|
||||
};
|
||||
|
||||
const pageCoreMobileNavSx: SxProps = {
|
||||
flex: 0,
|
||||
};
|
||||
|
||||
|
||||
export const PageCore = (props: {
|
||||
export function PageCore(props: {
|
||||
component: React.ElementType,
|
||||
currentApp?: NavItemApp,
|
||||
isFull: boolean,
|
||||
isMobile: boolean,
|
||||
children: React.ReactNode,
|
||||
}) =>
|
||||
<Box
|
||||
component={props.component}
|
||||
sx={props.currentApp?.pageBrighter ? pageCoreBrighterSx : props.isFull ? pageCoreFullSx : pageCoreSx}
|
||||
>
|
||||
}) {
|
||||
|
||||
{/* Optional deployment MOTD */}
|
||||
{optimaHasMOTD && <OptimaMOTD />}
|
||||
// external state
|
||||
const isChromeless = useOptimaChromeless();
|
||||
|
||||
{/* Responsive page bar (pluggable App Center Items and App Menu) */}
|
||||
<OptimaBar
|
||||
component='header'
|
||||
currentApp={props.currentApp}
|
||||
isMobile={props.isMobile}
|
||||
sx={pageCoreBarSx}
|
||||
/>
|
||||
return (
|
||||
<Box
|
||||
component={props.component}
|
||||
sx={props.currentApp?.pageBrighter ? pageCoreBrighterSx : props.isFull ? pageCoreFullSx : pageCoreSx}
|
||||
>
|
||||
|
||||
{/* Page (NextJS) must make the assumption they're in a flex-col layout */}
|
||||
{props.children}
|
||||
{/* Optional deployment MOTD */}
|
||||
{optimaHasMOTD && <OptimaMOTD />}
|
||||
|
||||
{/* [Mobile] Nav bar at the bottom */}
|
||||
{/*{!!props.isMobile && (*/}
|
||||
{/* <MobileNav*/}
|
||||
{/* component='nav'*/}
|
||||
{/* currentApp={props.currentApp}*/}
|
||||
{/* hideOnFocusMode*/}
|
||||
{/* sx={pageCoreMobileNavSx}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
{/* Responsive page bar (pluggable App Center Items and App Menu) - collapsible for chromeless mode */}
|
||||
<Box sx={isChromeless ? barCollapsibleClosedSx : barCollapsibleOpenSx}>
|
||||
<div style={barCollapsibleInnerStyle}>
|
||||
<OptimaBar
|
||||
component='header'
|
||||
currentApp={props.currentApp}
|
||||
isMobile={props.isMobile}
|
||||
sx={pageCoreBarSx}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
</Box>;
|
||||
{/* Page (NextJS) must make the assumption they're in a flex-col layout */}
|
||||
{props.children}
|
||||
|
||||
{/* [Mobile] Nav bar at the bottom */}
|
||||
{/*{!!props.isMobile && (*/}
|
||||
{/* <MobileNav*/}
|
||||
{/* component='nav'*/}
|
||||
{/* currentApp={props.currentApp}*/}
|
||||
{/* hideOnFocusMode*/}
|
||||
{/* sx={pageCoreMobileNavSx}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export type ModelOptionsContext = 'full' | 'parameters';
|
||||
interface OptimaState {
|
||||
|
||||
// modes
|
||||
// isFocusedMode: boolean; // when active, the Mobile App menu is not displayed
|
||||
isChromeless: boolean; // when active, the top bar and composer are hidden, with floating buttons
|
||||
|
||||
// panes
|
||||
drawerIsOpen: boolean;
|
||||
@@ -62,7 +62,7 @@ const modalsClosedState = {
|
||||
const initialState: OptimaState = {
|
||||
|
||||
// modes
|
||||
// isFocusedMode: false,
|
||||
isChromeless: false,
|
||||
|
||||
// panes
|
||||
drawerIsOpen: initialDrawerOpen(),
|
||||
@@ -81,7 +81,7 @@ const initialState: OptimaState = {
|
||||
|
||||
export interface OptimaActions {
|
||||
|
||||
// setIsFocusedMode: (isFocusedMode: boolean) => void;
|
||||
setChromeless: (isChromeless: boolean) => void;
|
||||
|
||||
closeDrawer: () => void;
|
||||
openDrawer: () => void;
|
||||
@@ -168,7 +168,7 @@ export const useLayoutOptimaStore = create<OptimaState & OptimaActions>((_set, _
|
||||
|
||||
...initialState,
|
||||
|
||||
// setIsFocusedMode: (isFocusedMode) => _set({ isFocusedMode }),
|
||||
setChromeless: (isChromeless) => _set({ isChromeless }),
|
||||
|
||||
closeDrawer: () => {
|
||||
// prevent accidental immediate close (e.g. double-click, animation protection)
|
||||
|
||||
@@ -6,6 +6,22 @@ import { NavItemApp } from '~/common/app.nav';
|
||||
import { useOptimaPortalHasInputs } from '~/common/layout/optima/portals/useOptimaPortalHasInputs';
|
||||
|
||||
|
||||
// Chromeless Mode
|
||||
|
||||
export function optimaToggleChromeless() {
|
||||
const state = useLayoutOptimaStore.getState();
|
||||
state.setChromeless(!state.isChromeless);
|
||||
}
|
||||
|
||||
export function optimaExitChromeless() {
|
||||
useLayoutOptimaStore.getState().setChromeless(false);
|
||||
}
|
||||
|
||||
export function useOptimaChromeless() {
|
||||
return useLayoutOptimaStore(({ isChromeless }) => isChromeless);
|
||||
}
|
||||
|
||||
|
||||
// Drawer
|
||||
|
||||
export function optimaCloseDrawer() {
|
||||
|
||||
Reference in New Issue
Block a user