mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Beam: change the Fusion model
This commit is contained in:
@@ -11,6 +11,7 @@ export const beamCardClasses = {
|
||||
errored: 'beamCard-Errored',
|
||||
selectable: 'beamCard-Selectable',
|
||||
attractive: 'beamCard-Attractive',
|
||||
smashTop: 'beamCard-SmashTop',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,6 +44,11 @@ export const BeamCard = styled(Box)(({ theme }) => ({
|
||||
[`&.${beamCardClasses.attractive}`]: {
|
||||
animation: `${animationShadowLimey} 2s linear infinite`,
|
||||
},
|
||||
[`&.${beamCardClasses.smashTop}`]: {
|
||||
borderTop: 'none',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
},
|
||||
|
||||
position: 'relative',
|
||||
|
||||
@@ -56,6 +62,36 @@ export const BeamCard = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
BeamCard.displayName = 'BeamCard'; // [shared] scatter/gather pane style
|
||||
|
||||
|
||||
export const beamCardMessageWrapperSx: SxProps = {
|
||||
minHeight: '1.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// uncomment the following to limit the message height
|
||||
// overflow: 'auto',
|
||||
// maxHeight: 'calc(0.8 * (100vh - 16rem))',
|
||||
// aspectRatio: 1,
|
||||
}
|
||||
|
||||
export const beamCardMessageSx: SxProps = {
|
||||
// style: to undo the style of ChatMessage
|
||||
backgroundColor: 'none',
|
||||
border: 'none',
|
||||
mx: -1.5, // compensates for the marging (e.g. RenderChatText, )
|
||||
my: 0,
|
||||
px: 0,
|
||||
py: 0,
|
||||
}
|
||||
|
||||
export const beamCardMessageScrollingSx: SxProps = {
|
||||
...beamCardMessageSx,
|
||||
overflow: 'auto',
|
||||
maxHeight: 'max(18rem, calc(60lvh - 16rem))',
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Props for the two panes.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Alert, Box } from '@mui/joy';
|
||||
|
||||
import { Alert, Box, CircularProgress } from '@mui/joy';
|
||||
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom';
|
||||
import { animationEnterScaleUp } from '~/common/util/animUtils';
|
||||
import { useUICounter } from '~/common/state/store-ui';
|
||||
|
||||
import { BeamExplainer } from './BeamExplainer';
|
||||
import { BeamGatherInput } from './gather/BeamGatherInput';
|
||||
import { BeamGatherOutput } from './gather/BeamGatherOutput';
|
||||
import { BeamFusionGrid } from './gather/BeamFusionGrid';
|
||||
import { BeamGatherPane } from './gather/BeamGatherPane';
|
||||
import { BeamRayGrid } from './scatter/BeamRayGrid';
|
||||
import { BeamScatterInput } from './scatter/BeamScatterInput';
|
||||
@@ -23,6 +24,9 @@ export function BeamView(props: {
|
||||
showExplainer?: boolean,
|
||||
}) {
|
||||
|
||||
// state
|
||||
const [warnIsScattering, setWarnIsScattering] = React.useState(false);
|
||||
|
||||
// external state
|
||||
const { novel: explainerUnseen, touch: explainerCompleted, forget: explainerShow } = useUICounter('beam-wizard');
|
||||
const {
|
||||
@@ -32,12 +36,19 @@ export function BeamView(props: {
|
||||
const {
|
||||
/* root */ inputHistory, inputIssues, inputReady,
|
||||
/* scatter */ isScattering, raysReady,
|
||||
/* gather (composite) */ canGather,
|
||||
} = useBeamStore(props.beamStore, useShallow(state => ({
|
||||
inputHistory: state.inputHistory, inputIssues: state.inputIssues, inputReady: state.inputReady,
|
||||
isScattering: state.isScattering, raysReady: state.raysReady,
|
||||
inputHistory: state.inputHistory,
|
||||
inputIssues: state.inputIssues,
|
||||
inputReady: state.inputReady,
|
||||
// scatter
|
||||
isScattering: state.isScattering,
|
||||
raysReady: state.raysReady,
|
||||
// gather (composite)
|
||||
canGather: state.raysReady >= 2 && state.currentFactoryId !== null && state.currentGatherLlmId !== null,
|
||||
})));
|
||||
const rayIds = useBeamStore(props.beamStore, useShallow(state => state.rays.map(ray => ray.rayId)));
|
||||
// const fusionIds = useBeamStore(props.beamStore, useShallow(state => state.fusions.map(fusion => fusion.fusionId)));
|
||||
const fusionIds = useBeamStore(props.beamStore, useShallow(state => state.fusions.map(fusion => fusion.fusionId)));
|
||||
|
||||
// derived state
|
||||
const raysCount = rayIds.length;
|
||||
@@ -50,6 +61,32 @@ export function BeamView(props: {
|
||||
const handleRayIncreaseCount = React.useCallback(() => setRayCount(raysCount + 1), [setRayCount, raysCount]);
|
||||
|
||||
|
||||
const handleCreateFusion = React.useCallback(() => {
|
||||
// if scatter is busy, ask for confirmation
|
||||
if (isScattering) {
|
||||
setWarnIsScattering(true);
|
||||
return;
|
||||
}
|
||||
props.beamStore.getState().createFusion();
|
||||
}, [isScattering, props.beamStore]);
|
||||
|
||||
|
||||
const handleStopScatterConfirmation = React.useCallback(() => {
|
||||
setWarnIsScattering(false);
|
||||
stopScatteringAll();
|
||||
handleCreateFusion();
|
||||
}, [handleCreateFusion, stopScatteringAll]);
|
||||
|
||||
const handleStopScatterDenial = React.useCallback(() => setWarnIsScattering(false), []);
|
||||
|
||||
|
||||
// (this is great ux) scatter freed up while we were asking the question, proceed
|
||||
React.useEffect(() => {
|
||||
if (warnIsScattering && !isScattering)
|
||||
handleStopScatterConfirmation();
|
||||
}, [handleStopScatterConfirmation, isScattering, warnIsScattering]);
|
||||
|
||||
|
||||
// runnning
|
||||
|
||||
// [effect] pre-populate a default number of rays
|
||||
@@ -66,6 +103,7 @@ export function BeamView(props: {
|
||||
return (
|
||||
<ScrollToBottom disableAutoStick>
|
||||
|
||||
{/* Main V-Layout */}
|
||||
<Box sx={{
|
||||
// scroller fill
|
||||
minHeight: '100%',
|
||||
@@ -114,43 +152,54 @@ export function BeamView(props: {
|
||||
isMobile={props.isMobile}
|
||||
rayIds={rayIds}
|
||||
onIncreaseRayCount={handleRayIncreaseCount}
|
||||
// linkedLlmId={lastGatherLlmId}
|
||||
// linkedLlmId={currentGatherLlmId}
|
||||
/>
|
||||
|
||||
|
||||
{/* Gapper between Rays and Merge, without compromising the auto margin of the Ray Grid */}
|
||||
<Box />
|
||||
|
||||
{/* Fusion Config */}
|
||||
<BeamGatherInput
|
||||
beamStore={props.beamStore}
|
||||
/>
|
||||
|
||||
{/*<BeamFusionGrid*/}
|
||||
{/* beamStore={props.beamStore}*/}
|
||||
{/* canAddFusion={raysReady >= 2}*/}
|
||||
{/* fusionIds={fusionIds}*/}
|
||||
{/* isMobile={props.isMobile}*/}
|
||||
{/* onAddFusion={() => alert('add fusion xxx')}*/}
|
||||
{/* raysCount={raysCount}*/}
|
||||
{/*/>*/}
|
||||
|
||||
|
||||
{/* Gather Controls */}
|
||||
<BeamGatherPane
|
||||
isMobile={props.isMobile}
|
||||
beamStore={props.beamStore}
|
||||
gatherCount={raysReady}
|
||||
scatterBusy={isScattering}
|
||||
canGather={canGather}
|
||||
isMobile={props.isMobile}
|
||||
onAddFusion={handleCreateFusion}
|
||||
raysReady={raysReady}
|
||||
/>
|
||||
|
||||
{/* Fusion Output */}
|
||||
<BeamGatherOutput
|
||||
{/* Fusion Grid */}
|
||||
<BeamFusionGrid
|
||||
beamStore={props.beamStore}
|
||||
canGather={canGather}
|
||||
fusionIds={fusionIds}
|
||||
isMobile={props.isMobile}
|
||||
onAddFusion={handleCreateFusion}
|
||||
raysCount={raysCount}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Confirm Stop Scattering */}
|
||||
{warnIsScattering && (
|
||||
<ConfirmationModal
|
||||
open
|
||||
onClose={handleStopScatterDenial}
|
||||
onPositive={handleStopScatterConfirmation}
|
||||
// lowStakes
|
||||
noTitleBar
|
||||
confirmationText='Some responses are still being generated. Do you want to stop and proceed with merging the available responses now?'
|
||||
positiveActionText='Proceed with Merge'
|
||||
negativeActionText='Wait for All Responses'
|
||||
negativeActionStartDecorator={
|
||||
<CircularProgress color='neutral' sx={{ '--CircularProgress-size': '24px', '--CircularProgress-trackThickness': '1px' }} />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
</ScrollToBottom>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,5 @@ export const SCATTER_RAY_SHOW_DRAG_HANDLE = false;
|
||||
|
||||
// BEAM Gather configuration
|
||||
export const GATHER_COLOR = 'success' as const;
|
||||
export const GATHER_DEFAULT_TO_FIRST_FUSION = true;
|
||||
export const GATHER_PLACEHOLDER = '📦 ...';
|
||||
export const GATHER_SHOW_SYSTEM_PROMPT = false;
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, IconButton, SvgIconProps, Typography } from '@mui/joy';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import PlayArrowRoundedIcon from '@mui/icons-material/PlayArrowRounded';
|
||||
import RemoveCircleOutlineRoundedIcon from '@mui/icons-material/RemoveCircleOutlineRounded';
|
||||
import ReplayRoundedIcon from '@mui/icons-material/ReplayRounded';
|
||||
import StopRoundedIcon from '@mui/icons-material/StopRounded';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
|
||||
import { ChatMessageMemo } from '../../../apps/chat/components/message/ChatMessage';
|
||||
|
||||
import { findVendorById } from '~/modules/llms/vendors/vendors.registry';
|
||||
import { findLLMOrThrow } from '~/modules/llms/store-llms';
|
||||
|
||||
import { GoodTooltip } from '~/common/components/GoodTooltip';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { animationEnterBelow } from '~/common/util/animUtils';
|
||||
import { copyToClipboard } from '~/common/util/clipboardUtils';
|
||||
|
||||
import { BFusion, fusionIsError, fusionIsFusing, fusionIsIdle, fusionIsStopped, fusionIsUsableOutput } from './beam.gather';
|
||||
import { BeamCard, beamCardClasses, beamCardMessageScrollingSx, beamCardMessageSx, beamCardMessageWrapperSx } from '../BeamCard';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { GATHER_COLOR } from '../beam.config';
|
||||
import { findFusionFactory, FusionFactorySpec } from './instructions/beam.gather.factories';
|
||||
import { useBeamCardScrolling } from '../store-module-beam';
|
||||
|
||||
|
||||
const fusionCardSx: SxProps = {
|
||||
// [`&.${beamCardClasses.idle}`]: {
|
||||
// pb: 0, // Peekaboo (shrink height)
|
||||
// },
|
||||
|
||||
// boxShadow: 'sm',
|
||||
// borderColor: `${GATHER_COLOR}.outlinedBorder`,
|
||||
};
|
||||
|
||||
|
||||
const FusionControlsMemo = React.memo(FusionControls);
|
||||
|
||||
function FusionControls(props: {
|
||||
fusion: BFusion,
|
||||
factory: FusionFactorySpec,
|
||||
isEmpty: boolean,
|
||||
isFusing: boolean,
|
||||
isStopped: boolean,
|
||||
llmLabel: string,
|
||||
llmVendorIcon?: React.FunctionComponent<SvgIconProps>,
|
||||
onRemove: () => void,
|
||||
onToggleGenerate: () => void,
|
||||
}) {
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
// color='success'
|
||||
// variant='solid'
|
||||
// invertedColors
|
||||
sx={{
|
||||
// mx: -1, mt: -1, px: 1, py: 0,
|
||||
borderRadius: 'sm',
|
||||
display: 'flex', alignItems: 'center', gap: 1,
|
||||
}}
|
||||
>
|
||||
|
||||
{/* LLM Icon */}
|
||||
{!!props.llmVendorIcon && (
|
||||
<GoodTooltip title={props.llmLabel}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<props.llmVendorIcon sx={{ fontSize: 'lg', my: 'auto' }} />
|
||||
</Box>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{/* Factory Icon */}
|
||||
{!!props.factory.Icon && (
|
||||
<props.factory.Icon sx={{ fontSize: 'lg', my: 'auto' }} />
|
||||
)}
|
||||
|
||||
{/* Title / Progress Component */}
|
||||
<Box sx={{
|
||||
flex: 1,
|
||||
// ml: -1, my: -1, pl: 1, py: 0.5, borderRadius: 'md',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
{props.fusion.fusingProgressComponent
|
||||
// Show the progress in place of the title
|
||||
? props.fusion.fusingProgressComponent
|
||||
: (
|
||||
<Typography sx={{ fontSize: 'sm', fontWeight: 'md' }}>
|
||||
{props.factory.label + ' Merge'} {props.isStopped && <em> - Interrupted</em>}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{!props.isFusing ? (
|
||||
<GoodTooltip title='Generate'>
|
||||
<IconButton size='sm' variant='plain' color='success' onClick={props.onToggleGenerate}>
|
||||
{props.isEmpty ? <PlayArrowRoundedIcon sx={{ fontSize: 'xl2' }} /> : <ReplayRoundedIcon />}
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
) : (
|
||||
<GoodTooltip title='Stop'>
|
||||
<IconButton size='sm' variant='plain' color='danger' onClick={props.onToggleGenerate}>
|
||||
<StopRoundedIcon />
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
<GoodTooltip title='Remove'>
|
||||
<IconButton size='sm' variant='plain' color='neutral' onClick={props.onRemove}>
|
||||
<RemoveCircleOutlineRoundedIcon />
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function BeamFusion(props: {
|
||||
beamStore: BeamStoreApi,
|
||||
fusionId: string,
|
||||
}) {
|
||||
|
||||
// external state
|
||||
const fusion = useBeamStore(props.beamStore, store => store.fusions.find(fusion => fusion.fusionId === props.fusionId) ?? null);
|
||||
const cardScrolling = useBeamCardScrolling();
|
||||
|
||||
// derived state
|
||||
const isIdle = fusionIsIdle(fusion);
|
||||
const isError = fusionIsError(fusion);
|
||||
const isFusing = fusionIsFusing(fusion);
|
||||
const isStopped = fusionIsStopped(fusion);
|
||||
const isUsable = fusionIsUsableOutput(fusion);
|
||||
const showUseButtons = isUsable && !isFusing;
|
||||
|
||||
const factory = findFusionFactory(fusion?.factoryId);
|
||||
|
||||
const { removeFusion, toggleFusionGathering } = props.beamStore.getState();
|
||||
|
||||
|
||||
// get LLM Label and Vendor Icon
|
||||
const llmId = fusion?.llmId ?? null;
|
||||
const { llmLabel, llmVendorIcon } = React.useMemo(() => {
|
||||
if (llmId) {
|
||||
try {
|
||||
const llm = findLLMOrThrow(llmId);
|
||||
return {
|
||||
llmLabel: llm.label,
|
||||
llmVendorIcon: findVendorById(llm._source?.vId)?.Icon,
|
||||
};
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return { llmLabel: 'Model unknown', llmVendorIcon: undefined };
|
||||
}, [llmId]);
|
||||
|
||||
|
||||
// handlers
|
||||
const handleFusionCopy = React.useCallback(() => {
|
||||
const { fusions } = props.beamStore.getState();
|
||||
const fusion = fusions.find(fusion => fusion.fusionId === props.fusionId);
|
||||
if (fusion?.outputDMessage?.text)
|
||||
copyToClipboard(fusion.outputDMessage.text, 'Merge');
|
||||
}, [props.beamStore, props.fusionId]);
|
||||
|
||||
const handleFusionUse = React.useCallback(() => {
|
||||
// get snapshot values, so we don't have to react to the hook
|
||||
const { fusions, onSuccessCallback } = props.beamStore.getState();
|
||||
const fusion = fusions.find(fusion => fusion.fusionId === props.fusionId);
|
||||
if (fusion?.outputDMessage?.text && onSuccessCallback)
|
||||
onSuccessCallback(fusion.outputDMessage.text, fusion.llmId || '');
|
||||
}, [props.beamStore, props.fusionId]);
|
||||
|
||||
|
||||
const handleFusionRemove = React.useCallback(() => {
|
||||
removeFusion(props.fusionId);
|
||||
}, [props.fusionId, removeFusion]);
|
||||
|
||||
const handleToggleFusionGather = React.useCallback(() => {
|
||||
toggleFusionGathering(props.fusionId);
|
||||
}, [props.fusionId, toggleFusionGathering]);
|
||||
|
||||
// escape hatch: no factory, no fusion - nothing to do
|
||||
if (!fusion || !factory)
|
||||
return;
|
||||
|
||||
return (
|
||||
<BeamCard
|
||||
className={
|
||||
(isIdle ? beamCardClasses.idle : '')
|
||||
+ (isError ? beamCardClasses.errored + ' ' : '')
|
||||
+ (isUsable ? beamCardClasses.selectable + ' ' : '')
|
||||
+ (isFusing ? beamCardClasses.attractive + ' ' : '')
|
||||
// + (beamCardClasses.smashTop + ' ')
|
||||
}
|
||||
>
|
||||
|
||||
{/* Controls Row */}
|
||||
<FusionControlsMemo
|
||||
fusion={fusion}
|
||||
factory={factory}
|
||||
isEmpty={!isUsable}
|
||||
isFusing={isFusing}
|
||||
isStopped={isStopped}
|
||||
llmLabel={llmLabel}
|
||||
llmVendorIcon={llmVendorIcon}
|
||||
onRemove={handleFusionRemove}
|
||||
onToggleGenerate={handleToggleFusionGather}
|
||||
/>
|
||||
|
||||
|
||||
{/* Show issue, if any */}
|
||||
{isError && <InlineError error={fusion?.errorText || 'Merge Issue'} />}
|
||||
|
||||
{/* Dyanmic: the progress, set by the execution chain */}
|
||||
{/*{fusion?.fusingProgressComponent && fusion.fusingProgressComponent}*/}
|
||||
|
||||
{/* Dynamic: instruction-specific components */}
|
||||
{!!fusion?.fusingInstructionComponent && fusion.fusingInstructionComponent}
|
||||
|
||||
|
||||
{/* Output Message */}
|
||||
{(!!fusion?.outputDMessage?.text || fusion?.stage === 'fusing') && (
|
||||
<Box sx={beamCardMessageWrapperSx}>
|
||||
{!!fusion.outputDMessage && (
|
||||
<ChatMessageMemo
|
||||
message={fusion.outputDMessage}
|
||||
fitScreen={true}
|
||||
showAvatar={false}
|
||||
adjustContentScaling={-1}
|
||||
sx={!cardScrolling ? beamCardMessageSx : beamCardMessageScrollingSx}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Use Fusion */}
|
||||
{showUseButtons && (
|
||||
<Box sx={{ mt: 'auto', mb: -1, mr: -1, placeSelf: 'end', display: 'flex', gap: 1 }}>
|
||||
|
||||
{/* Copy */}
|
||||
<GoodTooltip title='Copy'>
|
||||
<IconButton
|
||||
onClick={handleFusionCopy}
|
||||
>
|
||||
<ContentCopyIcon sx={{ fontSize: 'md' }} />
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
|
||||
{/* Continue */}
|
||||
<GoodTooltip title='Use this message'>
|
||||
<IconButton
|
||||
size='sm'
|
||||
// variant='solid'
|
||||
color={GATHER_COLOR}
|
||||
disabled={isFusing}
|
||||
onClick={handleFusionUse}
|
||||
// endDecorator={<TelegramIcon />}
|
||||
sx={{
|
||||
// ...BEAM_BTN_SX,
|
||||
// fontSize: 'xs',
|
||||
// backgroundColor: 'background.popup',
|
||||
// border: '1px solid',
|
||||
// borderColor: `${GATHER_COLOR}.outlinedBorder`,
|
||||
// boxShadow: `0 4px 16px -4px rgb(var(--joy-palette-${GATHER_COLOR}-mainChannel) / 20%)`,
|
||||
animation: `${animationEnterBelow} 0.1s ease-out`,
|
||||
// whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{/*Ok*/}
|
||||
<TelegramIcon />
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</BeamCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import * as React from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { SxProps, VariantProp } from '@mui/joy/styles/types';
|
||||
import { Box, Button, Typography } from '@mui/joy';
|
||||
import AddCircleOutlineRoundedIcon from '@mui/icons-material/AddCircleOutlineRounded';
|
||||
|
||||
import { BeamFusion } from '~/modules/beam/gather/BeamFusion';
|
||||
import { findFusionFactory, FusionFactorySpec } from '~/modules/beam/gather/instructions/beam.gather.factories';
|
||||
|
||||
import { BeamCard, beamCardClasses } from '../BeamCard';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { GATHER_COLOR } from '../beam.config';
|
||||
|
||||
|
||||
const fusionGridDesktopSx: SxProps = {
|
||||
mt: 'calc(-1 * var(--Pad))', // absorb parent 'gap' to previous
|
||||
|
||||
px: 'var(--Pad)',
|
||||
pb: 'var(--Pad)',
|
||||
// backgroundColor: 'neutral.solidBg',
|
||||
// mb:'auto',
|
||||
|
||||
// like rayGridDesktopSx
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(max(min(100%, 390px), 100%/5), 1fr))',
|
||||
gap: 'var(--Pad)',
|
||||
} as const;
|
||||
|
||||
const fusionGridMobileSx: SxProps = {
|
||||
...fusionGridDesktopSx,
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
|
||||
} as const;
|
||||
|
||||
|
||||
export function FusionAddButton(props: {
|
||||
canGather: boolean,
|
||||
currentFactory: FusionFactorySpec | null,
|
||||
onAddFusion: () => void,
|
||||
sx?: SxProps,
|
||||
small?: boolean,
|
||||
textOverride?: string,
|
||||
variant?: VariantProp,
|
||||
}) {
|
||||
if (props.currentFactory === null) return null;
|
||||
return (
|
||||
<Button
|
||||
size={props.small ? 'sm' : undefined}
|
||||
color={GATHER_COLOR}
|
||||
variant={props.variant}
|
||||
disabled={!props.canGather}
|
||||
onClick={props.onAddFusion}
|
||||
startDecorator={props.currentFactory?.Icon ? <props.currentFactory.Icon /> : <AddCircleOutlineRoundedIcon />}
|
||||
sx={{
|
||||
// justifyContent: 'end',
|
||||
// gap: 1,
|
||||
...props.sx,
|
||||
}}
|
||||
>
|
||||
{props.textOverride || props.currentFactory?.addLabel}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function BeamFusionGrid(props: {
|
||||
beamStore: BeamStoreApi,
|
||||
canGather: boolean,
|
||||
fusionIds: string[],
|
||||
isMobile: boolean,
|
||||
onAddFusion: () => void,
|
||||
raysCount: number,
|
||||
}) {
|
||||
|
||||
// external state
|
||||
const {
|
||||
currentFactory,
|
||||
} = useBeamStore(props.beamStore, useShallow(state => ({
|
||||
currentFactory: findFusionFactory(state.currentFactoryId),
|
||||
})));
|
||||
|
||||
|
||||
// derived state
|
||||
const isEmpty = props.fusionIds.length === 0;
|
||||
const isNoFactorySelected = currentFactory === null;
|
||||
|
||||
|
||||
// to balance things out with the ray grid, we may need to pad the items
|
||||
// const targetCount = props.raysCount + 1;
|
||||
// const fusionCount = props.fusionIds.length + 1;
|
||||
// // const padItems = targetCount - fusionCount;
|
||||
// const padItems = 1;
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
...(props.isMobile ? fusionGridMobileSx : fusionGridDesktopSx),
|
||||
...(isEmpty ? {
|
||||
backgroundColor: 'neutral.solidBg',
|
||||
} : {
|
||||
pt: 'var(--Pad)',
|
||||
}),
|
||||
}}>
|
||||
|
||||
{/* Fusions */}
|
||||
{props.fusionIds.map((fusionId) => (
|
||||
<BeamFusion
|
||||
key={'fusion-' + fusionId}
|
||||
beamStore={props.beamStore}
|
||||
fusionId={fusionId}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Add Fusion (Card) */}
|
||||
{(isEmpty || !isNoFactorySelected) && (
|
||||
<BeamCard
|
||||
className={isEmpty ? beamCardClasses.smashTop : undefined}
|
||||
sx={{
|
||||
backgroundColor: props.canGather ? `${GATHER_COLOR}.softBg` : undefined,
|
||||
// boxShadow: `0px 6px 16px -12px rgb(var(--joy-palette-${props.canGather ? GATHER_COLOR : 'neutral'}-darkChannel) / 40%)`,
|
||||
mb: 'auto',
|
||||
}}
|
||||
>
|
||||
{isNoFactorySelected ? null : props.canGather ? <Box sx={{ display: 'flex', flexDirection: props.isMobile ? 'column-reverse' : undefined, alignItems: props.isMobile ? undefined : 'center', gap: 1 }}>
|
||||
|
||||
<FusionAddButton
|
||||
// small
|
||||
// variant='soft'
|
||||
canGather={props.canGather}
|
||||
currentFactory={currentFactory}
|
||||
onAddFusion={props.onAddFusion}
|
||||
sx={{
|
||||
minHeight: props.isMobile ? 'calc(2 * var(--Card-padding) + 2rem - 0.5rem)' : undefined,
|
||||
// marginBottom: 'calc(-1 * var(--Card-padding) + 0.25rem)',
|
||||
// marginInline: 'calc(-1 * var(--Card-padding) + 0.375rem)',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography level='body-sm' variant='soft' color={GATHER_COLOR}>
|
||||
{currentFactory.description}
|
||||
</Typography>
|
||||
|
||||
</Box> : (
|
||||
<Typography level='body-sm'>
|
||||
You need two or more replies for a {currentFactory?.label?.toLocaleLowerCase() ?? ''} merge.
|
||||
</Typography>
|
||||
)}
|
||||
</BeamCard>
|
||||
)}
|
||||
|
||||
{/* Pad items: N * <Box/> */}
|
||||
{/*{padItems > 0 && (*/}
|
||||
{/* Array.from({ length: padItems }).map((_, index) => (*/}
|
||||
{/* <Box key={'pad-' + index} />*/}
|
||||
{/* ))*/}
|
||||
{/*)}*/}
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import type { ChatGenerateInstruction } from './instructions/ChatGenerateInstruc
|
||||
import type { Instruction } from './instructions/beam.gather.execution';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { GATHER_SHOW_SYSTEM_PROMPT } from '../beam.config';
|
||||
import { fusionIsEditable } from './beam.gather';
|
||||
import { useModuleBeamStore } from '../store-module-beam';
|
||||
|
||||
|
||||
@@ -184,11 +183,10 @@ export function BeamGatherInput(props: {
|
||||
const {
|
||||
currentFusionId, currentIsEditable, currentInstructions,
|
||||
} = useBeamStore(props.beamStore, useShallow(store => {
|
||||
const fusion = store.currentFusionId !== null ? store.fusions.find(fusion => fusion.fusionId === store.currentFusionId) ?? null : null;
|
||||
return {
|
||||
currentFusionId: fusion?.fusionId ?? null,
|
||||
currentIsEditable: fusion ? fusionIsEditable(fusion) : false,
|
||||
currentInstructions: fusion?.instructions ?? [],
|
||||
currentFusionId: null, // fusion?.fusionId ?? null,
|
||||
currentIsEditable: false, // fusion ? fusionIsEditable(fusion) : false,
|
||||
currentInstructions: [], // fusion?.instructions ?? [],
|
||||
};
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, Button, IconButton } from '@mui/joy';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
|
||||
import { ChatMessageMemo } from '../../../apps/chat/components/message/ChatMessage';
|
||||
|
||||
import { GoodTooltip } from '~/common/components/GoodTooltip';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { animationEnterBelow } from '~/common/util/animUtils';
|
||||
import { copyToClipboard } from '~/common/util/clipboardUtils';
|
||||
|
||||
import { BEAM_BTN_SX, BEAM_INVERT_BACKGROUND, GATHER_COLOR } from '../beam.config';
|
||||
import { BeamCard, beamCardClasses } from '../BeamCard';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { fusionIsError, fusionIsFusing, fusionIsIdle, fusionIsUsableOutput } from './beam.gather';
|
||||
|
||||
|
||||
const outputWrapperSx: SxProps = {
|
||||
mt: 'calc(-1 * var(--Pad))', // absorb parent 'gap' to previous
|
||||
px: 'var(--Pad)',
|
||||
pb: 'var(--Pad_2)',
|
||||
};
|
||||
|
||||
const outputWrapperINVSx: SxProps = {
|
||||
...outputWrapperSx,
|
||||
backgroundColor: 'neutral.solidBg',
|
||||
};
|
||||
|
||||
|
||||
const fusionCardSx: SxProps = {
|
||||
// [`&.${beamCardClasses.idle}`]: {
|
||||
// pb: 0, // Peekaboo (shrink height)
|
||||
// },
|
||||
|
||||
// boxShadow: 'sm',
|
||||
// borderColor: `${GATHER_COLOR}.outlinedBorder`,
|
||||
borderTop: 'none',
|
||||
// borderRadius: 'sm',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
};
|
||||
|
||||
export const fusionChatMessageSx: SxProps = {
|
||||
// style: to undo the style of ChatMessage
|
||||
backgroundColor: 'none',
|
||||
border: 'none',
|
||||
mx: -1.5, // compensates for the marging (e.g. RenderChatText, )
|
||||
my: 0,
|
||||
px: 0,
|
||||
py: 0,
|
||||
} as const;
|
||||
|
||||
|
||||
// const placeholderMessage = createDMessage('assistant', 'Click the Merge button to combine the Beams.');
|
||||
|
||||
|
||||
export function BeamGatherOutput(props: {
|
||||
isMobile: boolean,
|
||||
beamStore: BeamStoreApi
|
||||
}) {
|
||||
|
||||
// external state - we work on 'currentFusionId'
|
||||
const { fusion } = useBeamStore(props.beamStore, useShallow(store => {
|
||||
const fusion = store.currentFusionId !== null ? store.fusions.find(fusion => fusion.fusionId === store.currentFusionId) ?? null : null;
|
||||
return {
|
||||
fusion,
|
||||
};
|
||||
}));
|
||||
|
||||
// derived state
|
||||
const isIdle = fusionIsIdle(fusion);
|
||||
const isError = fusionIsError(fusion);
|
||||
const isFusing = fusionIsFusing(fusion);
|
||||
const isUsableOutput = fusionIsUsableOutput(fusion);
|
||||
const showUseOutputButtons = isUsableOutput && !isFusing;
|
||||
|
||||
|
||||
// handlers
|
||||
const handleFusionCopy = React.useCallback(() => {
|
||||
const { _currentFusion } = props.beamStore.getState();
|
||||
const fusion = _currentFusion();
|
||||
if (fusion?.outputDMessage?.text)
|
||||
copyToClipboard(fusion.outputDMessage.text, 'Fusion');
|
||||
}, [props.beamStore]);
|
||||
|
||||
const handleFusionUse = React.useCallback(() => {
|
||||
// get snapshot values, so we don't have to react to the hook
|
||||
const { _currentFusion, onSuccessCallback, lastGatherLlmId } = props.beamStore.getState();
|
||||
const fusion = _currentFusion();
|
||||
if (fusion?.outputDMessage?.text && onSuccessCallback)
|
||||
onSuccessCallback(fusion.outputDMessage.text, lastGatherLlmId || '');
|
||||
}, [props.beamStore]);
|
||||
|
||||
// if (isIdle)
|
||||
// return null;
|
||||
|
||||
return (
|
||||
<Box sx={BEAM_INVERT_BACKGROUND ? outputWrapperINVSx : outputWrapperSx}>
|
||||
<BeamCard
|
||||
className={`${isIdle ? beamCardClasses.idle : ''} ${showUseOutputButtons ? beamCardClasses.selectable : ''}`}
|
||||
sx={fusionCardSx}
|
||||
>
|
||||
|
||||
{/* Show issue, if any */}
|
||||
{isError && <InlineError error={fusion?.errorText || 'Merge Issue'} />}
|
||||
|
||||
{/* Dyanmic: the progress, set by the execution chain */}
|
||||
{fusion?.fusingProgressComponent && fusion.fusingProgressComponent}
|
||||
|
||||
{/* Dynamic: instruction-specific components */}
|
||||
{!!fusion?.fusingInstructionComponent && fusion.fusingInstructionComponent}
|
||||
|
||||
{/* Output */}
|
||||
{!!fusion?.outputDMessage && (
|
||||
<ChatMessageMemo
|
||||
message={fusion.outputDMessage}
|
||||
fitScreen={props.isMobile}
|
||||
showAvatar={false}
|
||||
adjustContentScaling={-1}
|
||||
sx={fusionChatMessageSx}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Use Output */}
|
||||
{showUseOutputButtons && (
|
||||
<Box sx={{ mt: 'auto', placeSelf: 'end', display: 'flex', gap: 2 }}>
|
||||
|
||||
{/* Copy */}
|
||||
<GoodTooltip title='Copy'>
|
||||
<IconButton
|
||||
onClick={handleFusionCopy}
|
||||
>
|
||||
<ContentCopyIcon sx={{ fontSize: 'md' }} />
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
|
||||
{/* Continue */}
|
||||
<GoodTooltip title='Use this message'>
|
||||
<Button
|
||||
variant='solid' color={GATHER_COLOR}
|
||||
disabled={isFusing}
|
||||
onClick={handleFusionUse}
|
||||
endDecorator={<TelegramIcon />}
|
||||
sx={{
|
||||
...BEAM_BTN_SX,
|
||||
whiteSpace: 'nowrap',
|
||||
// backgroundColor: 'background.popup',
|
||||
// border: '1px solid',
|
||||
// borderColor: `${GATHER_COLOR}.outlinedBorder`,
|
||||
boxShadow: `0 4px 16px -4px rgb(var(--joy-palette-${GATHER_COLOR}-mainChannel) / 20%)`,
|
||||
animation: `${animationEnterBelow} 0.1s ease-out`,
|
||||
}}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
</GoodTooltip>
|
||||
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</BeamCard>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,27 +2,19 @@ import * as React from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { ColorPaletteProp, SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, Button, ButtonGroup, CircularProgress, FormControl, Typography } from '@mui/joy';
|
||||
import { Box, Button, ButtonGroup, FormControl, Typography } from '@mui/joy';
|
||||
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||
import AutoAwesomeMotionTwoToneIcon from '@mui/icons-material/AutoAwesomeMotionTwoTone';
|
||||
import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined';
|
||||
import MergeRoundedIcon from '@mui/icons-material/MergeRounded';
|
||||
import StopRoundedIcon from '@mui/icons-material/StopRounded';
|
||||
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { FormLabelStart } from '~/common/components/forms/FormLabelStart';
|
||||
import { GoodTooltip } from '~/common/components/GoodTooltip';
|
||||
import { ScrollToBottomButton } from '~/common/scroll-to-bottom/ScrollToBottomButton';
|
||||
import { animationColorBeamGather, animationShadowRingLimey } from '~/common/util/animUtils';
|
||||
import { animationColorBeamGather } from '~/common/util/animUtils';
|
||||
import { useLLMSelect } from '~/common/components/forms/useLLMSelect';
|
||||
import { useScrollToBottom } from '~/common/scroll-to-bottom/useScrollToBottom';
|
||||
|
||||
import { BEAM_BTN_SX, GATHER_COLOR } from '../beam.config';
|
||||
import { BeamGatherDropdown } from './BeamGatherPaneDropdown';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { FUSION_FACTORIES } from './instructions/beam.gather.factories';
|
||||
import { FFactoryId, findFusionFactory, FUSION_FACTORIES } from './instructions/beam.gather.factories';
|
||||
import { GATHER_COLOR } from '../beam.config';
|
||||
import { beamPaneSx } from '../BeamCard';
|
||||
import { fusionIsFusing, fusionIsUsableOutput } from './beam.gather';
|
||||
import { useModuleBeamStore } from '../store-module-beam';
|
||||
|
||||
|
||||
@@ -40,10 +32,10 @@ const gatherPaneSx: SxProps = {
|
||||
boxShadow: `0px 6px 20px -8px rgb(var(--joy-palette-neutral-darkChannel) / 30%)`,
|
||||
[`&.${gatherPaneClasses.ready}`]: {
|
||||
backgroundColor: 'background.popup',
|
||||
boxShadow: `0px 6px 16px -8px rgb(var(--joy-palette-neutral-darkChannel) / 40%)`,
|
||||
boxShadow: `0px 6px 16px -8px rgb(var(--joy-palette-success-darkChannel) / 40%)`,
|
||||
},
|
||||
[`&.${gatherPaneClasses.busy}`]: {
|
||||
animation: `${animationShadowRingLimey} 2s linear infinite`,
|
||||
// animation: `${animationShadowRingLimey} 2s linear infinite`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -65,97 +57,52 @@ const desktopGatherPaneSx: SxProps = {
|
||||
|
||||
|
||||
export function BeamGatherPane(props: {
|
||||
isMobile: boolean,
|
||||
beamStore: BeamStoreApi,
|
||||
gatherCount: number,
|
||||
scatterBusy: boolean,
|
||||
canGather: boolean,
|
||||
isMobile: boolean,
|
||||
onAddFusion: () => void,
|
||||
raysReady: number,
|
||||
}) {
|
||||
|
||||
// state
|
||||
const [warnScatterBusy, setWarnScatterBusy] = React.useState(false);
|
||||
|
||||
// external state
|
||||
const { setStickToBottom } = useScrollToBottom();
|
||||
// const { setStickToBottom } = useScrollToBottom();
|
||||
const gatherShowDevMethods = useModuleBeamStore(state => state.gatherShowDevMethods);
|
||||
const {
|
||||
lastGatherLlmId, fusions, currentFusionId, isGatheringAny, isCurrentFusionGoodToGo,
|
||||
setLastGatherLlmId, setCurrentFusionId, currentFusionStart, currentFusionStop,
|
||||
stopScatteringAll,
|
||||
} = useBeamStore(props.beamStore, useShallow(state => {
|
||||
const currentFusion = state._currentFusion();
|
||||
const isCurrentFusionGoodToGo = fusionIsUsableOutput(currentFusion) && !fusionIsFusing(currentFusion);
|
||||
return {
|
||||
// state
|
||||
lastGatherLlmId: state.lastGatherLlmId,
|
||||
currentFusionId: state.currentFusionId,
|
||||
fusions: state.fusions,
|
||||
isGatheringAny: state.isGatheringAny,
|
||||
isCurrentFusionGoodToGo,
|
||||
currentFactory, currentFactoryId, currentGatherLlmId, isGatheringAny,
|
||||
setCurrentFactoryId, setCurrentGatherLlmId,
|
||||
} = useBeamStore(props.beamStore, useShallow(state => ({
|
||||
// state
|
||||
currentFactory: findFusionFactory(state.currentFactoryId),
|
||||
currentFactoryId: state.currentFactoryId,
|
||||
currentGatherLlmId: state.currentGatherLlmId,
|
||||
isGatheringAny: state.isGatheringAny,
|
||||
|
||||
// actions
|
||||
setLastGatherLlmId: state.setLastGatherLlmId,
|
||||
setCurrentFusionId: state.setCurrentFusionId,
|
||||
currentFusionStart: state.currentFusionStart,
|
||||
currentFusionStop: state.currentFusionStop,
|
||||
|
||||
// (external slice) scatter actions
|
||||
stopScatteringAll: state.stopScatteringAll,
|
||||
};
|
||||
}));
|
||||
// actions
|
||||
setCurrentFactoryId: state.setCurrentFactoryId,
|
||||
setCurrentGatherLlmId: state.setCurrentGatherLlmId,
|
||||
})));
|
||||
const [_, gatherLlmComponent, gatherLlmIcon] = useLLMSelect(
|
||||
lastGatherLlmId, setLastGatherLlmId, props.isMobile ? '' : 'Merge Model', true,
|
||||
currentGatherLlmId, setCurrentGatherLlmId, props.isMobile ? '' : 'Merge Model', true,
|
||||
);
|
||||
|
||||
|
||||
// derived state
|
||||
const { gatherCount } = props;
|
||||
const isNoFactorySelected = currentFactoryId === null;
|
||||
|
||||
const hasInputs = gatherCount >= 2;
|
||||
// const CurrentFactoryIcon = currentFactory?.Icon ?? null;
|
||||
// const currentFactoryDescription = currentFactory?.description ?? '';
|
||||
|
||||
const gatherEnabled = hasInputs && !isGatheringAny && currentFusionId !== null;
|
||||
|
||||
// const currentFusion = currentFusionId !== null ? fusions.find(fusion => fusion.fusionId === currentFusionId) ?? null : null;
|
||||
|
||||
// const currentFactoryId = currentFusion ? currentFusion.factoryId : null;
|
||||
|
||||
// const CurrentFusionIcon = currentFactoryId ? FUSION_FACTORIES.find(factory => factory.id === currentFactoryId)?.Icon ?? null : null;
|
||||
|
||||
const handleFusionActivate = React.useCallback((fusionId: string, shiftPressed: boolean) => {
|
||||
setStickToBottom(true);
|
||||
setCurrentFusionId((fusionId !== currentFusionId || !shiftPressed) ? fusionId : null);
|
||||
}, [currentFusionId, setCurrentFusionId, setStickToBottom]);
|
||||
|
||||
|
||||
const handleCurrentFusionStart = React.useCallback(() => {
|
||||
// if scatter is busy, ask for confirmation
|
||||
if (props.scatterBusy) {
|
||||
setWarnScatterBusy(true);
|
||||
return;
|
||||
}
|
||||
const { inputHistory, rays } = props.beamStore.getState();
|
||||
currentFusionStart(inputHistory ? [...inputHistory] : [], rays.map(ray => ray.message));
|
||||
}, [currentFusionStart, props.beamStore, props.scatterBusy]);
|
||||
|
||||
const handleStopScatterConfirmation = React.useCallback(() => {
|
||||
setWarnScatterBusy(false);
|
||||
stopScatteringAll();
|
||||
handleCurrentFusionStart();
|
||||
}, [handleCurrentFusionStart, stopScatteringAll]);
|
||||
|
||||
const handleStopScatterDenial = React.useCallback(() => setWarnScatterBusy(false), []);
|
||||
|
||||
// (this is great ux) scatter freed up while we were asking the question, proceed
|
||||
React.useEffect(() => {
|
||||
if (warnScatterBusy && !props.scatterBusy)
|
||||
handleStopScatterConfirmation();
|
||||
}, [handleStopScatterConfirmation, props.scatterBusy, warnScatterBusy]);
|
||||
const handleFactoryActivate = React.useCallback((factoryId: FFactoryId, shiftPressed: boolean) => {
|
||||
// setStickToBottom(true);
|
||||
setCurrentFactoryId((factoryId !== currentFactoryId || !shiftPressed) ? factoryId : null);
|
||||
}, [currentFactoryId, setCurrentFactoryId]);
|
||||
|
||||
|
||||
const MainLlmIcon = gatherLlmIcon || (isGatheringAny ? AutoAwesomeIcon : AutoAwesomeOutlinedIcon);
|
||||
|
||||
return <>
|
||||
return (
|
||||
<Box
|
||||
className={`${hasInputs ? gatherPaneClasses.ready : ''} ${isGatheringAny ? gatherPaneClasses.busy : ''}`}
|
||||
className={`${props.canGather ? gatherPaneClasses.ready : ''} ${isGatheringAny ? gatherPaneClasses.busy : ''}`}
|
||||
sx={props.isMobile ? mobileGatherPaneSx : desktopGatherPaneSx}
|
||||
>
|
||||
|
||||
@@ -171,7 +118,7 @@ export function BeamGatherPane(props: {
|
||||
</Typography>
|
||||
<Typography level='body-sm' sx={{ whiteSpace: 'nowrap' }}>
|
||||
{/* may merge or not (hasInputs) N replies.. put this in pretty messages */}
|
||||
{hasInputs ? `Combine the ${gatherCount} replies` : 'Two replies or more'}
|
||||
{props.canGather ? `Combine the ${props.raysReady} replies` : 'Two replies or more'}
|
||||
</Typography>
|
||||
</div>
|
||||
<ScrollToBottomButton inline />
|
||||
@@ -179,111 +126,56 @@ export function BeamGatherPane(props: {
|
||||
|
||||
{/* Method */}
|
||||
<FormControl sx={{ my: '-0.25rem' }}>
|
||||
{!props.isMobile && (
|
||||
<FormLabelStart
|
||||
title={<><AutoAwesomeMotionTwoToneIcon sx={{ fontSize: 'md', mr: 0.5 }} />Method</>}
|
||||
sx={/*{ mb: '0.25rem' }*/ undefined}
|
||||
/>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<ButtonGroup variant='outlined'>
|
||||
{fusions.map(fusion => {
|
||||
// get the factory, for additional info
|
||||
const factory = FUSION_FACTORIES.find(factory => factory.id === fusion.factoryId);
|
||||
if (!factory) return null;
|
||||
{/*{!props.isMobile && <FormLabelStart title='Method' />}*/}
|
||||
<ButtonGroup
|
||||
variant='outlined'
|
||||
size='md'
|
||||
sx={{ boxShadow: isNoFactorySelected ? 'xs' : undefined }}
|
||||
>
|
||||
{FUSION_FACTORIES.map(factory => {
|
||||
const { factoryId, label, isDev } = factory;
|
||||
|
||||
// ignore dev fusions, if not asked for it
|
||||
if (factory.isDev && !gatherShowDevMethods) return null;
|
||||
// ignore dev fusions, if not asked for it
|
||||
if (isDev && !gatherShowDevMethods) return null;
|
||||
|
||||
// const buttonColor: ColorPaletteProp = fusion.status === 'error' ? 'danger'
|
||||
// : fusion.status === 'fusing' ? 'warning'
|
||||
// : fusion.status === 'success' ? GATHER_COLOR
|
||||
// : fusion.status === 'stopped' ? GATHER_COLOR
|
||||
// : 'neutral';
|
||||
|
||||
const isActive = fusion.fusionId === currentFusionId;
|
||||
const buttonColor: ColorPaletteProp = isActive /*&& (fusion.status === 'success' || fusion.status === 'stopped')*/
|
||||
? GATHER_COLOR
|
||||
: 'neutral';
|
||||
return (
|
||||
<Button
|
||||
key={'fusion-' + fusion.fusionId}
|
||||
color={buttonColor}
|
||||
size='sm'
|
||||
onClick={event => handleFusionActivate(fusion.fusionId, !!event?.shiftKey)}
|
||||
startDecorator={(isActive && factory.Icon) ? <factory.Icon /> : null}
|
||||
sx={{
|
||||
backgroundColor: isActive ? `${buttonColor}.softBg` : 'background.popup',
|
||||
fontWeight: isActive ? 'lg' : 400, /* reset, from 600 */
|
||||
// minHeight: '2.25rem',
|
||||
}}
|
||||
>
|
||||
<GoodTooltip title={factory.description}>
|
||||
<span>
|
||||
{factory.label}
|
||||
</span>
|
||||
</GoodTooltip>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</ButtonGroup>
|
||||
{/*{(props.fusionIndex !== null) && (*/}
|
||||
{/* <Tooltip disableInteractive title='Customize This Merge'>*/}
|
||||
{/* <IconButton size='sm' color='success' disabled={props.gatherBusy || isEditable..} onClick={handleFusionCopyAsCustom}>*/}
|
||||
{/* {isEditable... ? null : <EditRoundedIcon />}*/}
|
||||
{/* </IconButton>*/}
|
||||
{/* </Tooltip>*/}
|
||||
{/*)}*/}
|
||||
</Box>
|
||||
const isActive = factoryId === currentFactoryId;
|
||||
const buttonColor: ColorPaletteProp = isActive ? GATHER_COLOR : 'neutral';
|
||||
return (
|
||||
<Button
|
||||
key={'factory-' + factoryId}
|
||||
color={buttonColor}
|
||||
onClick={event => handleFactoryActivate(factoryId, !!event?.shiftKey)}
|
||||
// startDecorator={(isActive && Icon) ? <Icon /> : null}
|
||||
sx={{
|
||||
backgroundColor: isActive ? `${buttonColor}.softBg` : 'background.popup',
|
||||
// fontWeight: isActive ? 'lg' : 'md', /* reset, from 600 */
|
||||
// minHeight: '2.25rem',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</ButtonGroup>
|
||||
</FormControl>
|
||||
|
||||
{/* LLM */}
|
||||
<Box sx={{ my: '-0.25rem', minWidth: 190, maxWidth: 220 }}>
|
||||
<Box sx={{ my: '-0.25rem', minWidth: 190, maxWidth: 300 }}>
|
||||
{gatherLlmComponent}
|
||||
</Box>
|
||||
|
||||
{/* Start / Stop buttons */}
|
||||
{!isGatheringAny ? (
|
||||
<Button
|
||||
// key='gather-start' // used for animation triggering, which we don't have now
|
||||
variant={isCurrentFusionGoodToGo ? 'soft' : 'solid'} color={GATHER_COLOR}
|
||||
disabled={!gatherEnabled || isGatheringAny} loading={isGatheringAny}
|
||||
endDecorator={/*CurrentFusionIcon ? <CurrentFusionIcon /> :*/ <MergeRoundedIcon />}
|
||||
onClick={handleCurrentFusionStart}
|
||||
sx={BEAM_BTN_SX}
|
||||
>
|
||||
Merge
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
// key='gather-stop'
|
||||
variant='solid' color='danger'
|
||||
endDecorator={<StopRoundedIcon />}
|
||||
onClick={currentFusionStop}
|
||||
sx={BEAM_BTN_SX}
|
||||
>
|
||||
Stop
|
||||
</Button>
|
||||
)}
|
||||
{/* Add Fusion */}
|
||||
{/*<FusionAddButton*/}
|
||||
{/* textOverride='Add'*/}
|
||||
{/* canGather={props.canGather}*/}
|
||||
{/* currentFactory={currentFactory}*/}
|
||||
{/* onAddFusion={props.onAddFusion}*/}
|
||||
{/* sx={BEAM_BTN_SX}*/}
|
||||
{/*/>*/}
|
||||
|
||||
{/* pad */}
|
||||
<Box />
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Confirm Stop Scattering */}
|
||||
{warnScatterBusy && (
|
||||
<ConfirmationModal
|
||||
open
|
||||
onClose={handleStopScatterDenial}
|
||||
onPositive={handleStopScatterConfirmation}
|
||||
// lowStakes
|
||||
noTitleBar
|
||||
confirmationText='Some responses are still being generated. Do you want to stop and proceed with merging the available responses now?'
|
||||
positiveActionText='Proceed with Merge'
|
||||
negativeActionText='Wait for All Responses'
|
||||
negativeActionStartDecorator={
|
||||
<CircularProgress color='neutral' sx={{ '--CircularProgress-size': '24px', '--CircularProgress-trackThickness': '1px' }} />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
</>;
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import type { StateCreator } from 'zustand/vanilla';
|
||||
import type { DLLMId } from '~/modules/llms/store-llms';
|
||||
|
||||
import type { DMessage } from '~/common/state/store-chats';
|
||||
import { FUSION_FACTORIES } from './instructions/beam.gather.factories';
|
||||
import { GATHER_DEFAULT_TO_FIRST_FUSION, GATHER_PLACEHOLDER } from '../beam.config';
|
||||
|
||||
import { FFactoryId, findFusionFactory, FUSION_FACTORIES } from './instructions/beam.gather.factories';
|
||||
import { GATHER_PLACEHOLDER } from '../beam.config';
|
||||
import { RootStoreSlice } from '../store-beam-vanilla';
|
||||
import { ScatterStoreSlice } from '../scatter/beam.scatter';
|
||||
import { gatherStartFusion, gatherStopFusion, Instruction } from './instructions/beam.gather.execution';
|
||||
|
||||
|
||||
@@ -25,8 +28,11 @@ type BFusionStage =
|
||||
export interface BFusion {
|
||||
// const
|
||||
readonly fusionId: BFusionId;
|
||||
readonly factoryId: string;
|
||||
readonly instructions: Readonly<Instruction[]>;
|
||||
readonly factoryId: FFactoryId;
|
||||
|
||||
// options
|
||||
instructions: Instruction[];
|
||||
llmId: DLLMId | null;
|
||||
|
||||
// status
|
||||
stage: BFusionStage;
|
||||
@@ -39,11 +45,14 @@ export interface BFusion {
|
||||
fusingInstructionComponent?: React.ReactNode;
|
||||
}
|
||||
|
||||
const createBFusion = (factoryId: string, instructions: Instruction[]): BFusion => ({
|
||||
const createBFusion = (factoryId: FFactoryId, instructions: Instruction[], llmId: DLLMId | null): BFusion => ({
|
||||
// const
|
||||
fusionId: uuidv4(),
|
||||
factoryId,
|
||||
|
||||
// options
|
||||
instructions,
|
||||
llmId,
|
||||
|
||||
// status
|
||||
stage: 'idle',
|
||||
@@ -69,6 +78,10 @@ export function fusionIsFusing(fusion: BFusion | null): boolean {
|
||||
return fusion?.stage === 'fusing';
|
||||
}
|
||||
|
||||
export function fusionIsStopped(fusion: BFusion | null): boolean {
|
||||
return fusion?.stage === 'stopped';
|
||||
}
|
||||
|
||||
export function fusionIsUsableOutput(fusion: BFusion | null): boolean {
|
||||
const message = fusion?.outputDMessage ?? null;
|
||||
return !!message && !!message.updated && !!message.text && message.text !== GATHER_PLACEHOLDER;
|
||||
@@ -83,9 +96,9 @@ export function fusionIsError(fusion: BFusion | null): boolean {
|
||||
|
||||
interface GatherStateSlice {
|
||||
|
||||
lastGatherLlmId: DLLMId | null;
|
||||
currentFactoryId: FFactoryId | null;
|
||||
currentGatherLlmId: DLLMId | null;
|
||||
|
||||
currentFusionId: BFusionId | null;
|
||||
fusions: BFusion[];
|
||||
|
||||
// derived state (just acts as a cache to avoid re-calculating)
|
||||
@@ -97,14 +110,11 @@ export const reInitGatherStateSlice = (prevFusions: BFusion[], gatherLlmId: DLLM
|
||||
// stop any ongoing fusions
|
||||
prevFusions.forEach(gatherStopFusion);
|
||||
|
||||
// fully use new fusions
|
||||
const newFusions = FUSION_FACTORIES.map(factory => createBFusion(factory.id, factory.createInstructions()));
|
||||
|
||||
return {
|
||||
lastGatherLlmId: gatherLlmId, // may be re-set during open() of the Beam Store
|
||||
currentFactoryId: null,
|
||||
currentGatherLlmId: gatherLlmId, // may be re-set during open() of the Beam Store
|
||||
|
||||
currentFusionId: (GATHER_DEFAULT_TO_FIRST_FUSION && newFusions.length) ? newFusions[0].fusionId : null,
|
||||
fusions: newFusions,
|
||||
fusions: [],
|
||||
|
||||
isGatheringAny: false,
|
||||
};
|
||||
@@ -114,42 +124,36 @@ export type FusionUpdateOrFn = Partial<BFusion> | ((fusion: BFusion) => (Partial
|
||||
|
||||
export interface GatherStoreSlice extends GatherStateSlice {
|
||||
|
||||
setLastGatherLlmId: (llmId: DLLMId | null) => void;
|
||||
|
||||
setCurrentFusionId: (fusionId: BFusionId | null) => void;
|
||||
_currentFusion: () => BFusion | null;
|
||||
setCurrentGatherLlmId: (llmId: DLLMId | null) => void;
|
||||
setCurrentFactoryId: (factoryId: FFactoryId | null) => void;
|
||||
|
||||
_fusionUpdate: (fusionId: BFusionId, update: FusionUpdateOrFn) => void;
|
||||
fusionRecreateAsCustom: (sourceFusionId: BFusionId) => void;
|
||||
fusionInstructionUpdate: (fusionId: BFusionId, instructionIndex: number, update: Partial<Instruction>) => void;
|
||||
fusionSetLlmId: (fusionId: BFusionId, llmId: DLLMId | null) => void;
|
||||
|
||||
currentFusionStart: (chatHistory: DMessage[], rays: DMessage[]) => void;
|
||||
currentFusionStop: () => void;
|
||||
createFusion: () => void;
|
||||
removeFusion: (fusionId: BFusionId) => void;
|
||||
toggleFusionGathering: (fusionId: BFusionId) => void;
|
||||
|
||||
}
|
||||
|
||||
export const createGatherSlice: StateCreator<GatherStoreSlice, [], [], GatherStoreSlice> = (_set, _get) => ({
|
||||
export const createGatherSlice: StateCreator<RootStoreSlice & ScatterStoreSlice & GatherStoreSlice, [], [], GatherStoreSlice> = (_set, _get) => ({
|
||||
|
||||
// initial state
|
||||
...reInitGatherStateSlice([], null),
|
||||
|
||||
|
||||
setLastGatherLlmId: (llmId: DLLMId | null) =>
|
||||
setCurrentFactoryId: (factoryId: FFactoryId | null) =>
|
||||
_set({
|
||||
lastGatherLlmId: llmId,
|
||||
currentFactoryId: factoryId,
|
||||
}),
|
||||
|
||||
|
||||
setCurrentFusionId: (fusionId: BFusionId | null) =>
|
||||
setCurrentGatherLlmId: (llmId: DLLMId | null) =>
|
||||
_set({
|
||||
currentFusionId: fusionId,
|
||||
currentGatherLlmId: llmId,
|
||||
}),
|
||||
|
||||
_currentFusion: () => {
|
||||
const { currentFusionId, fusions } = _get();
|
||||
return currentFusionId !== null ? fusions.find(fusion => fusion.fusionId === currentFusionId) ?? null : null;
|
||||
},
|
||||
|
||||
|
||||
_fusionUpdate: (fusionId: BFusionId, update: FusionUpdateOrFn) => {
|
||||
const { fusions } = _get();
|
||||
@@ -169,16 +173,16 @@ export const createGatherSlice: StateCreator<GatherStoreSlice, [], [], GatherSto
|
||||
},
|
||||
|
||||
fusionRecreateAsCustom: (sourceFusionId: BFusionId) => {
|
||||
const { fusions } = _get();
|
||||
const { fusions, currentGatherLlmId } = _get();
|
||||
|
||||
// finds the fusion and its factory
|
||||
const sourceFusion = fusions.find(fusion => fusion.fusionId === sourceFusionId);
|
||||
const sourceFusionFactory = sourceFusion ? FUSION_FACTORIES.find(spec => spec.id === sourceFusion.factoryId) : undefined;
|
||||
const sourceFusionFactory = findFusionFactory(sourceFusion?.factoryId);
|
||||
if (!sourceFusion || !sourceFusionFactory)
|
||||
return;
|
||||
|
||||
// create a custom from the source fusion factory
|
||||
const newCustomFusion: BFusion = createBFusion('custom', sourceFusionFactory.createInstructions());
|
||||
const newCustomFusion: BFusion = createBFusion('custom', sourceFusionFactory.createInstructions(), currentGatherLlmId);
|
||||
|
||||
// replace the only editable fusion with the new custom fusion
|
||||
_set({
|
||||
@@ -187,7 +191,6 @@ export const createGatherSlice: StateCreator<GatherStoreSlice, [], [], GatherSto
|
||||
gatherStopFusion(_f);
|
||||
return newCustomFusion;
|
||||
}),
|
||||
currentFusionId: newCustomFusion.fusionId,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -199,21 +202,55 @@ export const createGatherSlice: StateCreator<GatherStoreSlice, [], [], GatherSto
|
||||
),
|
||||
})),
|
||||
|
||||
fusionSetLlmId: (fusionId: BFusionId, llmId: DLLMId | null) =>
|
||||
_get()._fusionUpdate(fusionId, {
|
||||
llmId,
|
||||
}),
|
||||
|
||||
currentFusionStart: (chatHistory: DMessage[], rays: DMessage[]) => {
|
||||
const { lastGatherLlmId, _currentFusion, _fusionUpdate } = _get();
|
||||
const fusion = _currentFusion();
|
||||
|
||||
createFusion: () => {
|
||||
// get factory
|
||||
const { currentFactoryId, currentGatherLlmId, fusions, toggleFusionGathering } = _get();
|
||||
const factory = FUSION_FACTORIES.find(factory => factory.factoryId === currentFactoryId);
|
||||
if (!factory)
|
||||
return;
|
||||
|
||||
// create and append the fusion
|
||||
const newFusion = createBFusion(factory.factoryId, factory.createInstructions(), currentGatherLlmId);
|
||||
_set({
|
||||
fusions: [...fusions, newFusion],
|
||||
});
|
||||
|
||||
// start the fusion
|
||||
toggleFusionGathering(newFusion.fusionId);
|
||||
},
|
||||
|
||||
removeFusion: (fusionId: BFusionId) => {
|
||||
const fusion = _get().fusions.find(fusion => fusion.fusionId === fusionId);
|
||||
if (fusion) {
|
||||
const onUpdate = (update: FusionUpdateOrFn) => _fusionUpdate(fusion.fusionId, update);
|
||||
gatherStartFusion(fusion, chatHistory, rays, lastGatherLlmId, onUpdate);
|
||||
gatherStopFusion(fusion);
|
||||
_set(state => ({
|
||||
fusions: state.fusions.filter(fusion => fusion.fusionId !== fusionId),
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
currentFusionStop: () => {
|
||||
const { _currentFusion, _fusionUpdate } = _get();
|
||||
const fusion = _currentFusion();
|
||||
if (fusion)
|
||||
_fusionUpdate(fusion.fusionId, gatherStopFusion(fusion));
|
||||
|
||||
toggleFusionGathering: (fusionId: BFusionId) => {
|
||||
// this will start/stop the fusion
|
||||
const fusion = _get().fusions.find(fusion => fusion.fusionId === fusionId);
|
||||
if (!fusion) return;
|
||||
|
||||
// stop if fusing
|
||||
if (fusion?.stage === 'fusing')
|
||||
return gatherStopFusion(fusion);
|
||||
|
||||
// start if idle/stopped
|
||||
const { inputHistory, rays, currentGatherLlmId, _fusionUpdate } = _get();
|
||||
const chatMessages = inputHistory ? [...inputHistory] : [];
|
||||
const rayMessages = rays.map(ray => ray.message);
|
||||
const onUpdate = (update: FusionUpdateOrFn) => _fusionUpdate(fusion.fusionId, update);
|
||||
gatherStartFusion(fusion, chatMessages, rayMessages, currentGatherLlmId, onUpdate);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getUXLabsHighPerformance } from '~/common/state/store-ux-labs';
|
||||
|
||||
import type { BaseInstruction, ExecutionInputState } from './beam.gather.execution';
|
||||
import { GATHER_PLACEHOLDER } from '../../beam.config';
|
||||
import { fusionChatMessageSx } from '../BeamGatherOutput';
|
||||
import { beamCardMessageSx } from '../../BeamCard';
|
||||
|
||||
|
||||
type ChatGenerateMethods =
|
||||
@@ -83,7 +83,7 @@ export async function executeChatGenerate(_i: ChatGenerateInstruction, inputs: E
|
||||
fitScreen={true}
|
||||
showAvatar={false}
|
||||
adjustContentScaling={-1}
|
||||
sx={fusionChatMessageSx}
|
||||
sx={beamCardMessageSx}
|
||||
/>,
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -64,7 +64,7 @@ export function UserInputChecklistComponent(props: {
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'grid', gap: 2 }}>
|
||||
<Typography color='primary' sx={{ mt: 1, fontWeight: 'lg', fontSize: 'md' }}>
|
||||
<Typography sx={{ mt: 1, fontWeight: 'md', fontSize: 'sm' }}>
|
||||
Select the Merge options to apply:
|
||||
</Typography>
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ export function gatherStartFusion(
|
||||
if (inputState.chainAbortController.signal.aborted) {
|
||||
return onUpdateBFusion({
|
||||
stage: 'stopped',
|
||||
errorText: 'Merge Canceled.',
|
||||
// errorText: 'Merge Canceled.',
|
||||
fusingProgressComponent: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SvgIcon } from '@mui/material';
|
||||
import CheckBoxOutlinedIcon from '@mui/icons-material/CheckBoxOutlined';
|
||||
import MediationOutlinedIcon from '@mui/icons-material/MediationOutlined';
|
||||
import TableViewRoundedIcon from '@mui/icons-material/TableViewRounded';
|
||||
@@ -7,23 +6,31 @@ import TableViewRoundedIcon from '@mui/icons-material/TableViewRounded';
|
||||
import type { Instruction } from './beam.gather.execution';
|
||||
|
||||
|
||||
interface FusionFactorySpec {
|
||||
id: 'guided' | 'fuse' | 'eval' | 'custom';
|
||||
export type FFactoryId = string;
|
||||
|
||||
export interface FusionFactorySpec {
|
||||
factoryId: FFactoryId;
|
||||
label: string;
|
||||
Icon?: React.FunctionComponent;
|
||||
addLabel: string;
|
||||
Icon?: typeof SvgIcon;
|
||||
description: string;
|
||||
isDev?: boolean;
|
||||
createInstructions: () => Instruction[];
|
||||
}
|
||||
|
||||
export function findFusionFactory(factoryId?: FFactoryId | null): FusionFactorySpec | null {
|
||||
if (!factoryId) return null;
|
||||
return FUSION_FACTORIES.find(f => f.factoryId === factoryId) ?? null;
|
||||
}
|
||||
|
||||
export const FUSION_FACTORIES: FusionFactorySpec[] = [
|
||||
// 1: Guided (Checklist - 3x steps)
|
||||
{
|
||||
id: 'guided',
|
||||
factoryId: 'guided',
|
||||
label: 'Guided',
|
||||
addLabel: 'Add Checklist',
|
||||
Icon: CheckBoxOutlinedIcon,
|
||||
description: 'A brainstorming session with AI, where you first pick your favorite ideas from a list it generates, and then the AI combines those picks into a tailored solution.',
|
||||
description: 'Choose between options extracted by AI from the replies, and the model will combine your selections into a single answer.',
|
||||
// description: 'This approach employs a two-stage, interactive process where an AI first generates a checklist of insights from a conversation for user selection, then synthesizes those selections into a tailored, comprehensive response, integrating user preferences with AI analysis and creativity.',
|
||||
createInstructions: () => [
|
||||
{
|
||||
@@ -82,10 +89,11 @@ The final output should reflect a deep understanding of the user's preferences a
|
||||
|
||||
// 2: Fuse
|
||||
{
|
||||
id: 'fuse',
|
||||
factoryId: 'fuse',
|
||||
label: 'Fuse',
|
||||
addLabel: 'Add Fusion',
|
||||
Icon: MediationOutlinedIcon,
|
||||
description: 'AI combines conversation details and various AI-generated ideas into one clear, comprehensive answer, making sense of diverse insights for you.',
|
||||
description: 'AI combines conversation details and ideas into one clear, comprehensive answer.',
|
||||
createInstructions: () => [
|
||||
{
|
||||
type: 'chat-generate',
|
||||
@@ -108,10 +116,11 @@ Synthesize the perfect cohesive response to my last message that merges the coll
|
||||
|
||||
// 3: Eval
|
||||
{
|
||||
id: 'eval',
|
||||
factoryId: 'eval',
|
||||
label: 'Eval',
|
||||
addLabel: 'Add Critique',
|
||||
Icon: TableViewRoundedIcon,
|
||||
description: 'Analyzes and ranks AI responses, offering a clear, comparative overview to support your choice of answer.',
|
||||
description: 'Analyzes and compares AI responses, offering a structured framework to support your response choice.',
|
||||
isDev: true,
|
||||
createInstructions: () => [
|
||||
{
|
||||
@@ -152,8 +161,9 @@ Only work with the provided {{N}} responses. Begin with listing the criteria.`.t
|
||||
|
||||
// 4: Custom (this may be overwritten by other factories, if editing those)
|
||||
{
|
||||
id: 'custom',
|
||||
factoryId: 'custom',
|
||||
label: 'Custom',
|
||||
addLabel: 'Add Custom',
|
||||
// Icon: BuildCircleOutlinedIcon,
|
||||
description: 'Define your own fusion prompt.',
|
||||
createInstructions: () => [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, IconButton, SvgIconProps } from '@mui/joy';
|
||||
import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
@@ -20,29 +18,13 @@ import { InlineError } from '~/common/components/InlineError';
|
||||
import { copyToClipboard } from '~/common/util/clipboardUtils';
|
||||
import { useLLMSelect } from '~/common/components/forms/useLLMSelect';
|
||||
|
||||
import { BeamCard, beamCardClasses } from '../BeamCard';
|
||||
import { BeamCard, beamCardClasses, beamCardMessageScrollingSx, beamCardMessageSx, beamCardMessageWrapperSx } from '../BeamCard';
|
||||
import { BeamStoreApi, useBeamStore } from '../store-beam.hooks';
|
||||
import { GATHER_COLOR, SCATTER_RAY_SHOW_DRAG_HANDLE } from '../beam.config';
|
||||
import { rayIsError, rayIsImported, rayIsScattering, rayIsSelectable, rayIsUserSelected } from './beam.scatter';
|
||||
import { useBeamRayScrolling } from '../store-module-beam';
|
||||
import { useBeamCardScrolling } from '../store-module-beam';
|
||||
|
||||
|
||||
const chatMessageEmbeddedSx: SxProps = {
|
||||
// style: to undo the style of ChatMessage
|
||||
backgroundColor: 'none',
|
||||
border: 'none',
|
||||
mx: -1.5, // compensates for the marging (e.g. RenderChatText, )
|
||||
my: 0,
|
||||
px: 0,
|
||||
py: 0,
|
||||
};
|
||||
|
||||
const chatMessageEmbeddedScrollingSx: SxProps = {
|
||||
...chatMessageEmbeddedSx,
|
||||
overflow: 'auto',
|
||||
maxHeight: 'max(18rem, calc(60lvh - 16rem))',
|
||||
};
|
||||
|
||||
/*const letterSx: SxProps = {
|
||||
width: '1rem',
|
||||
py: 0.25,
|
||||
@@ -132,7 +114,7 @@ export function BeamRay(props: {
|
||||
|
||||
// external state
|
||||
const ray = useBeamStore(props.beamStore, store => store.rays.find(ray => ray.rayId === props.rayId) ?? null);
|
||||
const rayScrolling = useBeamRayScrolling();
|
||||
const cardScrolling = useBeamCardScrolling();
|
||||
|
||||
// derived state
|
||||
const isError = rayIsError(ray);
|
||||
@@ -140,7 +122,7 @@ export function BeamRay(props: {
|
||||
const isSelectable = rayIsSelectable(ray);
|
||||
const isSelected = rayIsUserSelected(ray);
|
||||
const isImported = rayIsImported(ray);
|
||||
const showUseButton = isSelectable && !isScattering;
|
||||
const showUseButtons = isSelectable && !isScattering;
|
||||
const { removeRay, rayToggleScattering, raySetLlmId } = props.beamStore.getState();
|
||||
|
||||
// This old code used the Gather LLM as Ray fallback - but now we use the last Scatter LLM as fallback
|
||||
@@ -210,27 +192,21 @@ export function BeamRay(props: {
|
||||
|
||||
{/* Ray Message */}
|
||||
{(!!ray?.message?.text || ray?.status === 'scattering') && (
|
||||
<Box sx={{
|
||||
minHeight: '1.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// uncomment the following to limit the message height
|
||||
// overflow: 'auto',
|
||||
// maxHeight: 'calc(0.8 * (100vh - 16rem))',
|
||||
// aspectRatio: 1,
|
||||
}}>
|
||||
<ChatMessageMemo
|
||||
message={ray.message}
|
||||
fitScreen={true}
|
||||
showAvatar={false}
|
||||
adjustContentScaling={-1}
|
||||
sx={rayScrolling ? chatMessageEmbeddedScrollingSx : chatMessageEmbeddedSx}
|
||||
/>
|
||||
<Box sx={beamCardMessageWrapperSx}>
|
||||
{!!ray.message && (
|
||||
<ChatMessageMemo
|
||||
message={ray.message}
|
||||
fitScreen={true}
|
||||
showAvatar={false}
|
||||
adjustContentScaling={-1}
|
||||
sx={!cardScrolling ? beamCardMessageSx : beamCardMessageScrollingSx}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Use Ray */}
|
||||
{showUseButton && (
|
||||
{showUseButtons && (
|
||||
<Box sx={{ mt: 'auto', mb: -1, mr: -1, placeSelf: 'end', height: 'calc(2.25rem - var(--Pad_2))', position: 'relative' }}>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
@@ -239,6 +215,8 @@ export function BeamRay(props: {
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
}}>
|
||||
|
||||
{/* Copy */}
|
||||
{!isImported && (
|
||||
<GoodTooltip title='Copy'>
|
||||
<IconButton
|
||||
@@ -249,6 +227,8 @@ export function BeamRay(props: {
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{/* Continue */}
|
||||
<GoodTooltip title='Choose this message'>
|
||||
<IconButton
|
||||
size='sm'
|
||||
@@ -264,6 +244,7 @@ export function BeamRay(props: {
|
||||
{isImported ? 'From Chat' : /*'Use'*/ <TelegramIcon />}
|
||||
</IconButton>
|
||||
</GoodTooltip>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -71,6 +71,21 @@ export function BeamRayGrid(props: {
|
||||
{/* Merges*/}
|
||||
{/*</Divider>*/}
|
||||
|
||||
{/* Fusions */}
|
||||
{/*{props.fusionIds.map((fusionId) => (*/}
|
||||
{/* <BeamFusion*/}
|
||||
{/* key={'fusion-' + fusionId}*/}
|
||||
{/* beamStore={props.beamStore}*/}
|
||||
{/* fusionId={fusionId}*/}
|
||||
{/* />*/}
|
||||
{/*))}*/}
|
||||
|
||||
{/* Add Fusion */}
|
||||
{/*<BeamFusionAdd*/}
|
||||
{/* beamStore={props.beamStore}*/}
|
||||
{/* isMobile={props.isMobile}*/}
|
||||
{/*/>*/}
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export function BeamScatterDropdown(props: {
|
||||
const [namingOpened, setNamingOpened] = React.useState(false);
|
||||
|
||||
// external state
|
||||
const { scatterPresets, rayScrolling, addScatterPreset, deleteScatterPreset, toggleRayScrolling } = useModuleBeamStore();
|
||||
const { scatterPresets, cardScrolling, addScatterPreset, deleteScatterPreset, toggleCardScrolling } = useModuleBeamStore();
|
||||
|
||||
|
||||
// handlers - load/save presets
|
||||
@@ -137,8 +137,8 @@ export function BeamScatterDropdown(props: {
|
||||
{/* <Typography level='body-sm'>Beam Options</Typography>*/}
|
||||
{/*</ListItem>*/}
|
||||
|
||||
<MenuItem onClick={toggleRayScrolling}>
|
||||
<ListItemDecorator>{rayScrolling && <CheckRoundedIcon />}</ListItemDecorator>
|
||||
<MenuItem onClick={toggleCardScrolling}>
|
||||
<ListItemDecorator>{cardScrolling && <CheckRoundedIcon />}</ListItemDecorator>
|
||||
Scroll Responses
|
||||
</MenuItem>
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ const createRootSlice: StateCreator<BeamStore, [], [], RootStoreSlice> = (_set,
|
||||
|
||||
// update the model only if the dialog was not already open
|
||||
...((!wasOpen && initialChatLLMId) && {
|
||||
lastGatherLlmId: initialChatLLMId,
|
||||
currentGatherLlmId: initialChatLLMId,
|
||||
} satisfies Partial<GatherStoreSlice>),
|
||||
});
|
||||
},
|
||||
@@ -98,7 +98,7 @@ const createRootSlice: StateCreator<BeamStore, [], [], RootStoreSlice> = (_set,
|
||||
terminate: () =>
|
||||
_set(state => ({
|
||||
...initRootStateSlice(),
|
||||
...reInitGatherStateSlice(state.fusions, state.lastGatherLlmId), // remember after termination
|
||||
...reInitGatherStateSlice(state.fusions, state.currentGatherLlmId), // remember after termination
|
||||
...reInitScatterStateSlice(state.rays),
|
||||
})),
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface BeamScatterPreset {
|
||||
interface ModuleBeamStore {
|
||||
// state
|
||||
scatterPresets: BeamScatterPreset[];
|
||||
rayScrolling: boolean;
|
||||
cardScrolling: boolean;
|
||||
gatherShowDevMethods: boolean;
|
||||
gatherShowPrompts: boolean;
|
||||
|
||||
@@ -26,7 +26,7 @@ interface ModuleBeamStore {
|
||||
deleteScatterPreset: (id: string) => void;
|
||||
renameScatterPreset: (id: string, name: string) => void;
|
||||
|
||||
toggleRayScrolling: () => void;
|
||||
toggleCardScrolling: () => void;
|
||||
|
||||
toggleGatherShowDevMethods: () => void;
|
||||
toggleGatherShowPrompts: () => void;
|
||||
@@ -37,7 +37,7 @@ export const useModuleBeamStore = create<ModuleBeamStore>()(persist(
|
||||
(_set, _get) => ({
|
||||
|
||||
scatterPresets: [],
|
||||
rayScrolling: false,
|
||||
cardScrolling: false,
|
||||
gatherShowDevMethods: true,
|
||||
gatherShowPrompts: false,
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useModuleBeamStore = create<ModuleBeamStore>()(persist(
|
||||
})),
|
||||
|
||||
|
||||
toggleRayScrolling: () => _set(state => ({ rayScrolling: !state.rayScrolling })),
|
||||
toggleCardScrolling: () => _set(state => ({ cardScrolling: !state.cardScrolling })),
|
||||
|
||||
|
||||
toggleGatherShowDevMethods: () => _set(state => ({ gatherShowDevMethods: !state.gatherShowDevMethods })),
|
||||
@@ -68,6 +68,6 @@ export const useModuleBeamStore = create<ModuleBeamStore>()(persist(
|
||||
));
|
||||
|
||||
|
||||
export function useBeamRayScrolling() {
|
||||
return useModuleBeamStore((state) => state.rayScrolling);
|
||||
export function useBeamCardScrolling() {
|
||||
return useModuleBeamStore((state) => state.cardScrolling);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user