mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Ephemerals: improve layout
This commit is contained in:
@@ -374,7 +374,6 @@ export function ChatMessageList(props: {
|
||||
sx={{
|
||||
mt: 'auto',
|
||||
overflowY: 'auto',
|
||||
minHeight: 64,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as React from 'react';
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, Grid, IconButton, Sheet, styled, Typography } from '@mui/joy';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import MaximizeIcon from '@mui/icons-material/Maximize';
|
||||
import MinimizeIcon from '@mui/icons-material/Minimize';
|
||||
import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
|
||||
import VerticalSplitOutlinedIcon from '@mui/icons-material/VerticalSplitOutlined';
|
||||
|
||||
@@ -111,107 +112,112 @@ function EphemeralItem(props: {
|
||||
|
||||
const { ephemeral, conversationHandler } = props;
|
||||
|
||||
// Event handlers
|
||||
const handleDelete = React.useCallback(() => {
|
||||
conversationHandler.overlayActions.ephemeralsDelete(ephemeral.id);
|
||||
}, [conversationHandler, ephemeral.id]);
|
||||
|
||||
const handleTogglePinned = React.useCallback(() => {
|
||||
conversationHandler.overlayActions.ephemeralsTogglePinned(ephemeral.id);
|
||||
const handleToggleMinimized = React.useCallback(() => {
|
||||
conversationHandler.overlayActions.ephemeralsToggleMinimized(ephemeral.id);
|
||||
}, [conversationHandler, ephemeral.id]);
|
||||
|
||||
const handleToggleShowState = React.useCallback(() => {
|
||||
conversationHandler.overlayActions.ephemeralsToggleShowState(ephemeral.id);
|
||||
conversationHandler.overlayActions.ephemeralsToggleShowStatePane(ephemeral.id);
|
||||
}, [conversationHandler, ephemeral.id]);
|
||||
|
||||
const showStatePane = ephemeral.showState && !!ephemeral.state;
|
||||
|
||||
return <Box
|
||||
sx={{
|
||||
p: { xs: 1, md: 2 },
|
||||
position: 'relative',
|
||||
const showStatePane = ephemeral.showStatePane && !!ephemeral.state;
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
borderTop: '1px solid',
|
||||
borderTopColor: 'divider',
|
||||
// border: (i < ephemerals.length - 1) ? `2px solid ${theme.palette.divider}` : undefined,
|
||||
}}>
|
||||
|
||||
{/* Title */}
|
||||
{ephemeral.title && (
|
||||
<Typography level='title-sm' sx={{ mt: -0.25, mb: 2, color: 'success.solidBg' }}>
|
||||
{ephemeral.title} Internal Monologue
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Vertical | split */}
|
||||
<Grid container spacing={2}>
|
||||
|
||||
{/* Left pane (log) */}
|
||||
<Grid xs={12} md={showStatePane ? 6 : 12}>
|
||||
{/* New renderer, with */}
|
||||
<Box sx={leftPaneSx}>
|
||||
<ScaledTextBlockRenderer
|
||||
text={ephemeral.text}
|
||||
contentScaling={props.contentScaling}
|
||||
textRenderVariant='markdown'
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Right pane (state) */}
|
||||
{showStatePane && (
|
||||
<Grid xs={12} md={6} sx={rightPaneSx}>
|
||||
<StateRenderer
|
||||
state={ephemeral.state}
|
||||
contentScaling={props.contentScaling}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
{/* Buttons */}
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
m: 1,
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
|
||||
{/* Pin */}
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={ephemeral.pinned ? 'soft' : 'plain'}
|
||||
onClick={handleTogglePinned}
|
||||
sx={{
|
||||
'& > *': { transition: 'transform 0.2s' },
|
||||
}}
|
||||
>
|
||||
<PushPinIcon sx={ephemeral.pinned ? { transform: 'rotate(45deg)' } : undefined} />
|
||||
</IconButton>
|
||||
{/* Top Line - Title and Buttons */}
|
||||
<Box sx={{
|
||||
py: 1,
|
||||
px: { xs: 1, md: 2 },
|
||||
backgroundColor: 'success.softHoverBg',
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
|
||||
{/* Show State */}
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={ephemeral.showState ? 'soft' : 'plain'}
|
||||
onClick={handleToggleShowState}
|
||||
>
|
||||
{ephemeral.showState ? <VerticalSplitIcon /> : <VerticalSplitOutlinedIcon />}
|
||||
</IconButton>
|
||||
<Typography level='title-sm' sx={{ flex: 1, color: 'success.solidBg' }}>
|
||||
{ephemeral.title} Internal Monologue
|
||||
</Typography>
|
||||
|
||||
{/* Close */}
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={ephemeral.done ? 'solid' : 'outlined'}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<CloseRoundedIcon />
|
||||
</IconButton>
|
||||
{/* Show State */}
|
||||
{!ephemeral.minimized && (
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={ephemeral.showStatePane ? 'solid' : 'outlined'}
|
||||
onClick={handleToggleShowState}
|
||||
>
|
||||
{ephemeral.showStatePane ? <VerticalSplitIcon /> : <VerticalSplitOutlinedIcon />}
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
{/* Minimize/Expand Button */}
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={'outlined'}
|
||||
onClick={handleToggleMinimized}
|
||||
>
|
||||
{ephemeral.minimized ? <MaximizeIcon /> : <MinimizeIcon />}
|
||||
</IconButton>
|
||||
|
||||
{/* Close */}
|
||||
<IconButton
|
||||
size='sm'
|
||||
variant={ephemeral.done ? 'solid' : 'outlined'}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<CloseRoundedIcon />
|
||||
</IconButton>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Content */}
|
||||
{!ephemeral.minimized && <Box sx={{
|
||||
py: 1,
|
||||
px: { xs: 1, md: 2 },
|
||||
}}>
|
||||
|
||||
{/* Content Grid */}
|
||||
<Grid container spacing={2} sx={{ mt: 0.5 }}>
|
||||
|
||||
{/* Left pane (log) */}
|
||||
<Grid xs={12} md={showStatePane ? 6 : 12}>
|
||||
{/* New renderer, with */}
|
||||
<Box sx={leftPaneSx}>
|
||||
<ScaledTextBlockRenderer
|
||||
text={ephemeral.text}
|
||||
contentScaling={props.contentScaling}
|
||||
textRenderVariant='markdown'
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Right pane (state) */}
|
||||
{showStatePane && (
|
||||
<Grid xs={12} md={6} sx={rightPaneSx}>
|
||||
<StateRenderer
|
||||
state={ephemeral.state}
|
||||
contentScaling={props.contentScaling}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
</Grid>
|
||||
</Box>}
|
||||
|
||||
</Box>
|
||||
|
||||
</Box>;
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,10 @@ import { getChatLLMId } from '~/common/stores/llms/store-llms';
|
||||
|
||||
import { getChatAutoAI } from '../../apps/chat/store-app-chat';
|
||||
|
||||
import { createDEphemeral } from './store-perchat_ephemerals_slice';
|
||||
import { createDEphemeral, EPHEMERALS_DEFAULT_TIMEOUT } from './store-perchat_ephemerals_slice';
|
||||
import { createPerChatVanillaStore } from './store-perchat_vanilla';
|
||||
|
||||
|
||||
// configuration
|
||||
const EPHEMERAL_DELETION_DELAY = 5 * 1000;
|
||||
|
||||
|
||||
/**
|
||||
* ConversationHandler is a class to overlay state onto a conversation.
|
||||
* It is a singleton per conversationId.
|
||||
@@ -250,16 +246,15 @@ export class ConversationHandler {
|
||||
// Ephemerals
|
||||
|
||||
createEphemeralHandler(title: string, initialText: string) {
|
||||
const { ephemeralsAppend, ephemeralsUpdate, ephemeralsDelete, ephemeralsIsPinned } = this.overlayActions;
|
||||
const { ephemeralsAppend, ephemeralsUpdate, ephemeralsDelete, getEphemeral } = this.overlayActions;
|
||||
|
||||
// create and append
|
||||
const ephemeral = createDEphemeral(title, initialText);
|
||||
const eId = ephemeral.id;
|
||||
ephemeralsAppend(ephemeral);
|
||||
|
||||
// delete if not pinned
|
||||
const deleteIfNotPinned = () => {
|
||||
if (!ephemeralsIsPinned(eId))
|
||||
const deleteIfMinimized = () => {
|
||||
if (getEphemeral(eId)?.minimized)
|
||||
ephemeralsDelete(eId);
|
||||
};
|
||||
|
||||
@@ -269,7 +264,7 @@ export class ConversationHandler {
|
||||
updateState: (state: object) => ephemeralsUpdate(eId, { state }),
|
||||
markAsDone: () => {
|
||||
ephemeralsUpdate(eId, { done: true });
|
||||
setTimeout(deleteIfNotPinned, EPHEMERAL_DELETION_DELAY);
|
||||
setTimeout(deleteIfMinimized, EPHEMERALS_DEFAULT_TIMEOUT);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ import type { StateCreator } from 'zustand/vanilla';
|
||||
import { agiUuid } from '~/common/util/idUtils';
|
||||
|
||||
|
||||
// configuration
|
||||
export const EPHEMERALS_DEFAULT_TIMEOUT = 6000;
|
||||
const EPHEMERALS_DEFAULT_MINIMIZED = true;
|
||||
|
||||
|
||||
/**
|
||||
* DEphemeral: For ReAct sidebars, displayed under the chat
|
||||
*/
|
||||
@@ -11,9 +16,9 @@ export interface DEphemeral {
|
||||
title: string;
|
||||
text: string;
|
||||
state: object;
|
||||
done: boolean;
|
||||
pinned: boolean;
|
||||
showState: boolean;
|
||||
done: boolean; // is complete, shall close after timeout
|
||||
minimized: boolean; // collapsed to a single line
|
||||
showStatePane: boolean; // show the state object
|
||||
}
|
||||
|
||||
type DEphemeralId = string;
|
||||
@@ -25,16 +30,16 @@ export function createDEphemeral(title: string, initialText: string): DEphemeral
|
||||
text: initialText,
|
||||
state: {},
|
||||
done: false,
|
||||
pinned: lastEphemeralPinned,
|
||||
showState: lastEphemeralShowState,
|
||||
minimized: lastMinimized,
|
||||
showStatePane: lastShowStatePane,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// Ephemerals Overlay Store ///
|
||||
|
||||
let lastEphemeralPinned = false;
|
||||
let lastEphemeralShowState = false;
|
||||
let lastMinimized = EPHEMERALS_DEFAULT_MINIMIZED;
|
||||
let lastShowStatePane = false;
|
||||
|
||||
interface EphemeralsOverlayState {
|
||||
|
||||
@@ -48,11 +53,10 @@ export interface EphemeralsOverlayStore extends EphemeralsOverlayState {
|
||||
ephemeralsDelete: (ephemeralId: DEphemeralId) => void;
|
||||
ephemeralsUpdate: (ephemeralId: DEphemeralId, update: Partial<DEphemeral>) => void;
|
||||
|
||||
ephemeralsIsPinned: (ephemeralId: DEphemeralId) => boolean;
|
||||
ephemeralsTogglePinned: (ephemeralId: DEphemeralId) => void;
|
||||
ephemeralsToggleMinimized: (ephemeralId: DEphemeralId) => void;
|
||||
ephemeralsToggleShowStatePane: (ephemeralId: DEphemeralId) => void;
|
||||
|
||||
ephemeralsIsShowState: (ephemeralId: DEphemeralId) => boolean;
|
||||
ephemeralsToggleShowState: (ephemeralId: DEphemeralId) => void;
|
||||
getEphemeral: (ephemeralId: DEphemeralId) => Readonly<DEphemeral> | undefined;
|
||||
|
||||
}
|
||||
|
||||
@@ -75,10 +79,10 @@ export const createEphemeralsOverlayStoreSlice: StateCreator<EphemeralsOverlaySt
|
||||
|
||||
ephemeralsUpdate: (ephemeralId, update) =>
|
||||
_set((state) => {
|
||||
if (update.pinned !== undefined)
|
||||
lastEphemeralPinned = update.pinned;
|
||||
if (update.showState !== undefined)
|
||||
lastEphemeralShowState = update.showState;
|
||||
if (update.minimized !== undefined)
|
||||
lastMinimized = update.minimized;
|
||||
if (update.showStatePane !== undefined)
|
||||
lastShowStatePane = update.showStatePane;
|
||||
return {
|
||||
ephemerals: state.ephemerals.map((e) =>
|
||||
e.id === ephemeralId
|
||||
@@ -88,28 +92,21 @@ export const createEphemeralsOverlayStoreSlice: StateCreator<EphemeralsOverlaySt
|
||||
};
|
||||
}),
|
||||
|
||||
ephemeralsIsPinned: (ephemeralId) =>
|
||||
_get().ephemerals.find((e) => e.id === ephemeralId)?.pinned || false,
|
||||
|
||||
ephemeralsTogglePinned: (ephemeralId) => {
|
||||
const { ephemerals, ephemeralsDelete, ephemeralsUpdate } = _get();
|
||||
const ephemeral = ephemerals.find((e) => e.id === ephemeralId);
|
||||
if (ephemeral) {
|
||||
if (ephemeral.pinned && ephemeral.done)
|
||||
ephemeralsDelete(ephemeralId);
|
||||
else
|
||||
ephemeralsUpdate(ephemeralId, { pinned: !ephemeral.pinned });
|
||||
}
|
||||
},
|
||||
|
||||
ephemeralsIsShowState: (ephemeralId) =>
|
||||
_get().ephemerals.find((e) => e.id === ephemeralId)?.showState || false,
|
||||
|
||||
ephemeralsToggleShowState: (ephemeralId) => {
|
||||
ephemeralsToggleMinimized: (ephemeralId) => {
|
||||
const { ephemerals, ephemeralsUpdate } = _get();
|
||||
const ephemeral = ephemerals.find((e) => e.id === ephemeralId);
|
||||
if (ephemeral)
|
||||
ephemeralsUpdate(ephemeralId, { showState: !ephemeral.showState });
|
||||
ephemeralsUpdate(ephemeralId, { minimized: !ephemeral.minimized });
|
||||
},
|
||||
|
||||
ephemeralsToggleShowStatePane: (ephemeralId) => {
|
||||
const { ephemerals, ephemeralsUpdate } = _get();
|
||||
const ephemeral = ephemerals.find((e) => e.id === ephemeralId);
|
||||
if (ephemeral)
|
||||
ephemeralsUpdate(ephemeralId, { showStatePane: !ephemeral.showStatePane });
|
||||
},
|
||||
|
||||
getEphemeral: (ephemeralId) =>
|
||||
_get().ephemerals.find((e) => e.id === ephemeralId),
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user