u
This commit is contained in:
@@ -26,10 +26,13 @@ import ElderlyRoundedIcon from '@mui/icons-material/ElderlyRounded';
|
||||
import DirectionsWalkRoundedIcon from '@mui/icons-material/DirectionsWalkRounded';
|
||||
import DirectionsRunRoundedIcon from '@mui/icons-material/DirectionsRunRounded';
|
||||
import PsychologyRoundedIcon from '@mui/icons-material/PsychologyRounded';
|
||||
import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded';
|
||||
|
||||
import { setting as settingApi } from '../../api';
|
||||
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
|
||||
import userSettingsAutomat from '../../automats/userSettingsAutomat';
|
||||
import vfsAutomat from '../../automats/vfsAutomat';
|
||||
import { VfsEditorPanel } from '../vfs';
|
||||
import ChatFlowList from './ChatFlowList';
|
||||
import AskUserPrompt from './AskUserPrompt';
|
||||
import { ACCEPT_STRING, readFileAttachment } from './chatAttachments';
|
||||
@@ -43,12 +46,16 @@ const DEFAULT_DRAWER_WIDTH = 460;
|
||||
const MIN_DRAWER_WIDTH = 320;
|
||||
|
||||
export default class ChatDrawer extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const s = chatAutomat.state;
|
||||
const settings = userSettingsAutomat.state;
|
||||
const vfs = vfsAutomat.state;
|
||||
this.state = {
|
||||
isOpen: s.isOpen,
|
||||
isVfsOpen: vfs.isOpen,
|
||||
vfsWidth: vfs.vfsWidth || 480,
|
||||
conversations: s.conversations,
|
||||
selectedConversationId: s.selectedConversationId,
|
||||
draftConversation: s.draftConversation,
|
||||
@@ -123,12 +130,18 @@ export default class ChatDrawer extends PureComponent {
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
this.unsubVfs = vfsAutomat.subscribe(this, (state) => ({
|
||||
isVfsOpen: state.isOpen,
|
||||
vfsWidth: state.vfsWidth,
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
||||
this.unsub?.();
|
||||
this.unsubSettings?.();
|
||||
this.unsubVfs?.();
|
||||
this.cleanupResizeListeners();
|
||||
}
|
||||
|
||||
@@ -136,7 +149,14 @@ export default class ChatDrawer extends PureComponent {
|
||||
chatAutomat.actions.closeChat();
|
||||
};
|
||||
|
||||
handleResizeStart = (e) => {
|
||||
handleInsertToChat = (text) => {
|
||||
this.setState((prev) => ({
|
||||
inputText: prev.inputText ? `${prev.inputText}\n${text}` : text,
|
||||
}));
|
||||
this.focusInput(50);
|
||||
};
|
||||
|
||||
handleVfsResizerStart = (e) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -144,25 +164,36 @@ export default class ChatDrawer extends PureComponent {
|
||||
const startX = e.clientX ?? (e.touches && e.touches[0]?.clientX);
|
||||
if (startX === undefined) return;
|
||||
|
||||
const startWidth = this.state.drawerWidth;
|
||||
this.setState({ isResizing: true });
|
||||
const startVfsWidth = this.state.vfsWidth;
|
||||
const startChatWidth = this.state.drawerWidth;
|
||||
const totalPairWidth = startVfsWidth + startChatWidth;
|
||||
|
||||
this.setState({ isResizing: true });
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
let currentWidth = startWidth;
|
||||
let currentChatWidth = startChatWidth;
|
||||
let currentVfsWidth = startVfsWidth;
|
||||
|
||||
const onMove = (moveEvt) => {
|
||||
moveEvt.preventDefault();
|
||||
const clientX = moveEvt.clientX ?? (moveEvt.touches && moveEvt.touches[0]?.clientX);
|
||||
if (clientX === undefined) return;
|
||||
|
||||
const deltaX = startX - clientX;
|
||||
const maxWidth = Math.min(window.innerWidth - 40, 1400);
|
||||
const newWidth = Math.max(MIN_DRAWER_WIDTH, Math.min(maxWidth, Math.round(startWidth + deltaX)));
|
||||
const deltaX = clientX - startX;
|
||||
const minVfs = 260;
|
||||
const minChat = MIN_DRAWER_WIDTH; // 320
|
||||
|
||||
currentWidth = newWidth;
|
||||
this.setState({ drawerWidth: newWidth });
|
||||
// Drag right (deltaX > 0): VFS increases, Chat decreases
|
||||
// Drag left (deltaX < 0): VFS decreases, Chat increases
|
||||
const clampedVfs = Math.max(minVfs, Math.min(totalPairWidth - minChat, Math.round(startVfsWidth + deltaX)));
|
||||
const clampedChat = totalPairWidth - clampedVfs;
|
||||
|
||||
currentVfsWidth = clampedVfs;
|
||||
currentChatWidth = clampedChat;
|
||||
|
||||
vfsAutomat.actions.setVfsWidth(clampedVfs);
|
||||
this.setState({ drawerWidth: clampedChat });
|
||||
};
|
||||
|
||||
const onEnd = () => {
|
||||
@@ -177,7 +208,75 @@ export default class ChatDrawer extends PureComponent {
|
||||
window.removeEventListener('touchend', onEnd);
|
||||
|
||||
this.setState({ isResizing: false });
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentWidth);
|
||||
vfsAutomat.actions.setVfsWidth(currentVfsWidth);
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onMove, { passive: false });
|
||||
window.addEventListener('pointerup', onEnd);
|
||||
window.addEventListener('pointercancel', onEnd);
|
||||
window.addEventListener('mousemove', onMove, { passive: false });
|
||||
window.addEventListener('mouseup', onEnd);
|
||||
window.addEventListener('touchmove', onMove, { passive: false });
|
||||
window.addEventListener('touchend', onEnd);
|
||||
};
|
||||
|
||||
handleResizeStart = (e) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const startX = e.clientX ?? (e.touches && e.touches[0]?.clientX);
|
||||
if (startX === undefined) return;
|
||||
|
||||
const startWidth = this.state.drawerWidth;
|
||||
const startVfsWidth = this.state.vfsWidth;
|
||||
const isVfs = this.state.isVfsOpen;
|
||||
|
||||
this.setState({ isResizing: true });
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
let currentChatWidth = startWidth;
|
||||
let currentVfsWidth = startVfsWidth;
|
||||
|
||||
const onMove = (moveEvt) => {
|
||||
moveEvt.preventDefault();
|
||||
const clientX = moveEvt.clientX ?? (moveEvt.touches && moveEvt.touches[0]?.clientX);
|
||||
if (clientX === undefined) return;
|
||||
|
||||
const deltaX = startX - clientX;
|
||||
|
||||
if (isVfs) {
|
||||
const maxVfs = Math.min(window.innerWidth - startWidth - 40, 1200);
|
||||
const newVfsWidth = Math.max(260, Math.min(maxVfs, Math.round(startVfsWidth + deltaX)));
|
||||
currentVfsWidth = newVfsWidth;
|
||||
vfsAutomat.actions.setVfsWidth(newVfsWidth);
|
||||
} else {
|
||||
const maxWidth = Math.min(window.innerWidth - 40, 1400);
|
||||
const newWidth = Math.max(MIN_DRAWER_WIDTH, Math.min(maxWidth, Math.round(startWidth + deltaX)));
|
||||
currentChatWidth = newWidth;
|
||||
this.setState({ drawerWidth: newWidth });
|
||||
}
|
||||
};
|
||||
|
||||
const onEnd = () => {
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
window.removeEventListener('pointermove', onMove);
|
||||
window.removeEventListener('pointerup', onEnd);
|
||||
window.removeEventListener('pointercancel', onEnd);
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onEnd);
|
||||
window.removeEventListener('touchmove', onMove);
|
||||
window.removeEventListener('touchend', onEnd);
|
||||
|
||||
this.setState({ isResizing: false });
|
||||
if (isVfs) {
|
||||
vfsAutomat.actions.setVfsWidth(currentVfsWidth);
|
||||
} else {
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onMove, { passive: false });
|
||||
@@ -199,6 +298,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
|
||||
|
||||
handleSelectConversation = (e) => {
|
||||
const id = Number(e.target.value);
|
||||
if (id) {
|
||||
@@ -427,6 +527,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
modelslugflag,
|
||||
modelreasoning,
|
||||
modelslug,
|
||||
isVfsOpen,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
@@ -695,11 +796,36 @@ export default class ChatDrawer extends PureComponent {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<IconButton size="small" onClick={this.handleClose} aria-label="Chat schließen" sx={{ ml: 1, flexShrink: 0 }}>
|
||||
<CloseRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1, flexShrink: 0 }}>
|
||||
<Tooltip title={isVfsOpen ? 'VFS-Dateisystem schließen' : 'VFS-Dateisystem parallel öffnen'}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => vfsAutomat.actions.toggleVfs()}
|
||||
aria-label="VFS Dateisystem"
|
||||
sx={{
|
||||
p: 0.5,
|
||||
color: isVfsOpen ? 'primary.main' : 'text.secondary',
|
||||
bgcolor: isVfsOpen ? 'action.selected' : 'transparent',
|
||||
borderRadius: 1.5,
|
||||
border: '1px solid',
|
||||
borderColor: isVfsOpen ? 'primary.main' : 'transparent',
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
bgcolor: 'action.hover',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FolderOpenRoundedIcon sx={{ fontSize: 19 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<IconButton size="small" onClick={this.handleClose} aria-label="Chat schließen" sx={{ p: 0.5 }}>
|
||||
<CloseRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Conversation Selector Bar */}
|
||||
{conversations.length > 0 && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
@@ -1095,7 +1221,8 @@ export default class ChatDrawer extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen, drawerWidth, isResizing } = this.state;
|
||||
const { isOpen, drawerWidth, isResizing, isVfsOpen, vfsWidth } = this.state;
|
||||
const totalWidth = isVfsOpen ? drawerWidth + vfsWidth : drawerWidth;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1106,10 +1233,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
sx={(theme) => ({
|
||||
zIndex: theme.zIndex.drawer + 2,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: { xs: '100vw', sm: `${drawerWidth}px` },
|
||||
width: { xs: '100vw', sm: `${totalWidth}px` },
|
||||
maxWidth: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexDirection: 'row',
|
||||
boxShadow: SHADOW_DRAWER,
|
||||
position: 'fixed',
|
||||
overflow: 'visible',
|
||||
@@ -1122,10 +1249,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
},
|
||||
paper: {
|
||||
sx: {
|
||||
width: { xs: '100vw', sm: `${drawerWidth}px` },
|
||||
width: { xs: '100vw', sm: `${totalWidth}px` },
|
||||
maxWidth: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexDirection: 'row',
|
||||
boxShadow: SHADOW_DRAWER,
|
||||
position: 'fixed',
|
||||
overflow: 'visible',
|
||||
@@ -1177,11 +1304,79 @@ export default class ChatDrawer extends PureComponent {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{this.renderHeader()}
|
||||
{this.renderBody()}
|
||||
{this.renderFooter()}
|
||||
{/* Left: VFS Filesystem Editor Panel (parallel to chat) */}
|
||||
{isVfsOpen && (
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', sm: `${vfsWidth}px` },
|
||||
minWidth: { sm: 340 },
|
||||
maxWidth: { sm: 'calc(100vw - 320px)' },
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<VfsEditorPanel onInsertToChat={this.handleInsertToChat} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Vertical Split Resizer between VFS and Chat */}
|
||||
{isVfsOpen && (
|
||||
<Box
|
||||
onPointerDown={this.handleVfsResizerStart}
|
||||
onMouseDown={this.handleVfsResizerStart}
|
||||
title="Ziehen zum Anpassen der Spaltenbreiten"
|
||||
sx={{
|
||||
width: 8,
|
||||
cursor: 'col-resize',
|
||||
bgcolor: 'divider',
|
||||
flexShrink: 0,
|
||||
display: { xs: 'none', sm: 'flex' },
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
touchAction: 'none',
|
||||
userSelect: 'none',
|
||||
transition: 'background-color 0.15s, box-shadow 0.15s',
|
||||
'&:hover, &:active': {
|
||||
bgcolor: 'primary.main',
|
||||
boxShadow: `0 0 8px ${BLUE_GLOW}`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 2,
|
||||
height: 36,
|
||||
borderRadius: 1,
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Right: Chat Panel */}
|
||||
<Box
|
||||
sx={{
|
||||
width: isVfsOpen ? { xs: '100%', sm: `${drawerWidth}px` } : '100%',
|
||||
flexGrow: 1,
|
||||
minWidth: { sm: 320 },
|
||||
display: isVfsOpen ? { xs: 'none', sm: 'flex' } : 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
bgcolor: 'background.paper',
|
||||
}}
|
||||
>
|
||||
{this.renderHeader()}
|
||||
{this.renderBody()}
|
||||
{this.renderFooter()}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user