mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Use react-resizable-panels instead of the flexbox
Also Fix #255 due to the large layout restructuring.
This commit is contained in:
Generated
+10
@@ -38,6 +38,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-katex": "^3.0.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-resizable-panels": "^1.0.5",
|
||||
"react-timeago": "^7.2.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"superjson": "^2.2.1",
|
||||
@@ -5874,6 +5875,15 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-resizable-panels": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-1.0.5.tgz",
|
||||
"integrity": "sha512-OP0whNQCko+f4BgoptGaeIc7StBRyeMeJ+8r/7rXACBDf9W5EcMWuM32hfqPDMenS2HFy/eZVi/r8XqK+ZIEag==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-ssr-prepass": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-ssr-prepass/-/react-ssr-prepass-1.5.0.tgz",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-katex": "^3.0.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-resizable-panels": "^1.0.5",
|
||||
"react-timeago": "^7.2.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"superjson": "^2.2.1",
|
||||
|
||||
+48
-32
@@ -1,7 +1,8 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Box } from '@mui/joy';
|
||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import { Box, useTheme } from '@mui/joy';
|
||||
|
||||
import { CmdRunBrowse } from '~/modules/browse/browse.client';
|
||||
import { CmdRunReact } from '~/modules/aifn/react/react';
|
||||
@@ -63,6 +64,8 @@ export function AppChat() {
|
||||
const composerTextAreaRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// external state
|
||||
const theme = useTheme();
|
||||
|
||||
const { openLlmOptions } = useOptimaLayout();
|
||||
|
||||
const { chatLLM } = useChatLLM();
|
||||
@@ -74,6 +77,7 @@ export function AppChat() {
|
||||
openConversationInFocusedPane,
|
||||
openConversationInSplitPane,
|
||||
setFocusedPaneIndex,
|
||||
removePaneIndex,
|
||||
} = usePanesManager();
|
||||
|
||||
const {
|
||||
@@ -97,10 +101,6 @@ export function AppChat() {
|
||||
|
||||
const chatPaneIDs = chatPanes.length > 0 ? chatPanes.map(pane => pane.conversationId) : [null];
|
||||
|
||||
const setActivePaneIndex = React.useCallback((idx: number) => {
|
||||
setFocusedPaneIndex(idx);
|
||||
}, [setFocusedPaneIndex]);
|
||||
|
||||
const setFocusedConversationId = React.useCallback((conversationId: DConversationId | null) => {
|
||||
conversationId && openConversationInFocusedPane(conversationId);
|
||||
}, [openConversationInFocusedPane]);
|
||||
@@ -382,18 +382,28 @@ export function AppChat() {
|
||||
|
||||
return <>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex', flexDirection: { xs: 'column', md: 'row' },
|
||||
overflow: 'clip',
|
||||
}}>
|
||||
<PanelGroup direction='horizontal'>
|
||||
|
||||
{chatPaneIDs.map((_conversationId, idx) => (
|
||||
<Box key={'chat-pane-' + idx} onClick={() => setActivePaneIndex(idx)} sx={{
|
||||
flexGrow: 1, flexBasis: 1,
|
||||
display: 'flex', flexDirection: 'column',
|
||||
overflow: 'clip',
|
||||
}}>
|
||||
{chatPaneIDs.map((_conversationId, idx, panels) => <React.Fragment key={'chat-pane-' + _conversationId + '-' + panels.length}>
|
||||
|
||||
<Panel
|
||||
id={'chat-pane-' + _conversationId}
|
||||
order={idx}
|
||||
collapsible
|
||||
defaultSize={panels.length > 0 ? Math.round(100 / panels.length) : undefined}
|
||||
minSize={20}
|
||||
onClick={() => setFocusedPaneIndex(idx)}
|
||||
onCollapse={() => setTimeout(() => removePaneIndex(idx), 50)}
|
||||
style={{
|
||||
// allows the content to be scrolled (all browsers)
|
||||
overflowY: 'auto',
|
||||
// border only for active pane (if two or more panes)
|
||||
...(chatPaneIDs.length < 2 ? {}
|
||||
: (_conversationId === focusedConversationId)
|
||||
? { border: `2px solid ${theme.palette.primary.solidBg}` }
|
||||
: { border: `2px solid ${theme.palette.background.level1}` }),
|
||||
}}
|
||||
>
|
||||
|
||||
<ChatMessageList
|
||||
conversationId={_conversationId}
|
||||
@@ -407,34 +417,40 @@ export function AppChat() {
|
||||
onTextImagine={handleTextImagine}
|
||||
onTextSpeak={handleTextSpeak}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
backgroundColor: themeBgApp,
|
||||
overflowY: 'auto',
|
||||
minHeight: 96,
|
||||
// outline the current focused pane
|
||||
...(chatPaneIDs.length < 2 ? {}
|
||||
: (_conversationId === focusedConversationId)
|
||||
? {
|
||||
border: '2px solid',
|
||||
borderColor: 'primary.solidBg',
|
||||
} : {
|
||||
padding: '2px',
|
||||
}),
|
||||
minHeight: '100%', // ensures filling of the blank space on newer chats
|
||||
}}
|
||||
/>
|
||||
|
||||
<Ephemerals
|
||||
conversationId={_conversationId}
|
||||
sx={{
|
||||
// TODO: Fixme post panels?
|
||||
// flexGrow: 0.1,
|
||||
flexShrink: 0.5,
|
||||
overflowY: 'auto',
|
||||
minHeight: 64,
|
||||
}} />
|
||||
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Panel>
|
||||
|
||||
{/* Panel Separators & Resizers */}
|
||||
{idx < panels.length - 1 && (
|
||||
<PanelResizeHandle>
|
||||
<Box sx={{
|
||||
backgroundColor: themeBgApp,
|
||||
height: '100%',
|
||||
width: '4px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.softActiveBg',
|
||||
},
|
||||
}} />
|
||||
</PanelResizeHandle>
|
||||
)}
|
||||
|
||||
</React.Fragment>)}
|
||||
|
||||
</PanelGroup>
|
||||
|
||||
<Composer
|
||||
chatLLM={chatLLM}
|
||||
|
||||
@@ -34,8 +34,7 @@ interface AppChatPanesStore {
|
||||
openConversationInSplitPane: (conversationId: DConversationId) => void;
|
||||
navigateHistoryInFocusedPane: (direction: 'back' | 'forward') => boolean;
|
||||
setFocusedPaneIndex: (paneIndex: number) => void;
|
||||
splitChatPane: (numberOfPanes: number) => void;
|
||||
unsplitChatPane: (paneIndexToKeep: number) => void;
|
||||
removePaneIndex: (paneIndex: number) => void;
|
||||
onConversationsChanged: (conversationIds: DConversationId[]) => void;
|
||||
|
||||
}
|
||||
@@ -169,22 +168,20 @@ const useAppChatPanesStore = create<AppChatPanesStore>()(persist(
|
||||
};
|
||||
}),
|
||||
|
||||
splitChatPane: (numberOfPanes: number) => {
|
||||
const { chatPanes, chatPaneFocusIndex } = _get();
|
||||
const focusedPane = (chatPaneFocusIndex !== null ? chatPanes[chatPaneFocusIndex] : null) ?? createPane();
|
||||
removePaneIndex: (paneIndex: number) =>
|
||||
_set(state => {
|
||||
const { chatPanes, chatPaneFocusIndex } = state;
|
||||
if (paneIndex < 0 || paneIndex >= chatPanes.length)
|
||||
return state;
|
||||
|
||||
_set({
|
||||
chatPanes: Array.from({ length: numberOfPanes }, () => ({ ...focusedPane })),
|
||||
chatPaneFocusIndex: 0,
|
||||
});
|
||||
},
|
||||
|
||||
unsplitChatPane: (paneIndexToKeep: number) =>
|
||||
_set(state => ({
|
||||
chatPanes: [state.chatPanes[paneIndexToKeep] || createPane()],
|
||||
chatPaneFocusIndex: 0,
|
||||
})),
|
||||
const newPanes = chatPanes.toSpliced(paneIndex, 1);
|
||||
|
||||
// when a pane is removed, focus the pane 0, or null if no panes remain
|
||||
return {
|
||||
chatPanes: newPanes,
|
||||
chatPaneFocusIndex: newPanes.length ? 0 : null,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* This function is vital, as is invoked when the conversationId[] changes in the global chats store.
|
||||
@@ -258,6 +255,7 @@ export function usePanesManager() {
|
||||
onConversationsChanged,
|
||||
openConversationInFocusedPane,
|
||||
openConversationInSplitPane,
|
||||
removePaneIndex,
|
||||
setFocusedPaneIndex,
|
||||
} = state;
|
||||
const focusedConversationId = chatPaneFocusIndex !== null ? chatPanes[chatPaneFocusIndex]?.conversationId ?? null : null;
|
||||
@@ -268,6 +266,7 @@ export function usePanesManager() {
|
||||
onConversationsChanged,
|
||||
openConversationInFocusedPane,
|
||||
openConversationInSplitPane,
|
||||
removePaneIndex,
|
||||
setFocusedPaneIndex,
|
||||
};
|
||||
}, shallow);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SettingsModal } from '../../../apps/settings-modal/SettingsModal';
|
||||
import { ShortcutsModal } from '../../../apps/settings-modal/ShortcutsModal';
|
||||
|
||||
import { isPwa } from '~/common/util/pwaUtils';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
import { useUIPreferencesStore } from '~/common/state/store-ui';
|
||||
|
||||
import { AppBar } from './AppBar';
|
||||
@@ -14,6 +15,31 @@ import { NextRouterProgress } from './NextLoadProgress';
|
||||
import { useOptimaLayout } from './useOptimaLayout';
|
||||
|
||||
|
||||
/*function ResponsiveNavigation() {
|
||||
return <>
|
||||
<Drawer
|
||||
open={false}
|
||||
variant='solid'
|
||||
anchor='left'
|
||||
onClose={() => {
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiDrawer-paper': {
|
||||
width: 256,
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: 256, height: '100%' }}>
|
||||
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>;
|
||||
}*/
|
||||
|
||||
|
||||
/**
|
||||
* Core layout of big-AGI, used by all the Primary applications therein.
|
||||
*
|
||||
@@ -28,22 +54,34 @@ import { useOptimaLayout } from './useOptimaLayout';
|
||||
export function OptimaLayout(props: { suspendAutoModelsSetup?: boolean, children: React.ReactNode, }) {
|
||||
|
||||
// external state
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
let centerMode = useUIPreferencesStore(state => (isPwa() || isMobile) ? 'full' : state.centerMode);
|
||||
|
||||
const {
|
||||
closePreferences, closeShortcuts,
|
||||
openShortcuts,
|
||||
showPreferencesTab, showShortcuts,
|
||||
} = useOptimaLayout();
|
||||
|
||||
const centerMode = useUIPreferencesStore(state => isPwa() ? 'full' : state.centerMode);
|
||||
|
||||
|
||||
return <>
|
||||
|
||||
{/*<Box sx={{*/}
|
||||
{/* display: 'flex', flexDirection: 'row',*/}
|
||||
{/* maxWidth: '100%', flexWrap: 'nowrap',*/}
|
||||
{/* // overflowX: 'hidden',*/}
|
||||
{/* background: 'lime',*/}
|
||||
{/*}}>*/}
|
||||
|
||||
{/*<Box sx={{ background: 'rgba(100 0 0 / 0.5)' }}>a</Box>*/}
|
||||
|
||||
{/*<ResponsiveNavigation />*/}
|
||||
|
||||
<Container
|
||||
disableGutters
|
||||
maxWidth={centerMode === 'full' ? false : centerMode === 'narrow' ? 'md' : 'xl'}
|
||||
sx={{
|
||||
// minWidth: 0,
|
||||
boxShadow: {
|
||||
xs: 'none',
|
||||
md: centerMode === 'narrow' ? 'md' : 'none',
|
||||
@@ -60,12 +98,17 @@ export function OptimaLayout(props: { suspendAutoModelsSetup?: boolean, children
|
||||
zIndex: 20,
|
||||
}} />
|
||||
|
||||
{/* Children must make the assumption they're in a flex-col layout */}
|
||||
{props.children}
|
||||
|
||||
</Box>
|
||||
|
||||
</Container>
|
||||
|
||||
{/*<Box sx={{ background: 'rgba(100 0 0 / 0.5)' }}>bb</Box>*/}
|
||||
|
||||
{/*</Box>*/}
|
||||
|
||||
|
||||
{/* Overlay Settings */}
|
||||
<SettingsModal open={!!showPreferencesTab} tabIndex={showPreferencesTab} onClose={closePreferences} onOpenShortcuts={openShortcuts} />
|
||||
|
||||
Reference in New Issue
Block a user