From 274f11ef1dbc30dec96259f6e1745cfa72ddd42e Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Tue, 26 Mar 2024 17:36:02 -0700 Subject: [PATCH] Beam: change the Fusion model --- src/modules/beam/BeamCard.tsx | 36 +++ src/modules/beam/BeamView.tsx | 101 +++++-- src/modules/beam/beam.config.ts | 1 - src/modules/beam/gather/BeamFusion.tsx | 283 ++++++++++++++++++ src/modules/beam/gather/BeamFusionGrid.tsx | 160 ++++++++++ src/modules/beam/gather/BeamGatherInput.tsx | 8 +- src/modules/beam/gather/BeamGatherOutput.tsx | 168 ----------- src/modules/beam/gather/BeamGatherPane.tsx | 260 +++++----------- src/modules/beam/gather/beam.gather.ts | 125 +++++--- .../instructions/ChatGenerateInstruction.tsx | 4 +- .../UserInputChecklistComponent.tsx | 2 +- .../instructions/beam.gather.execution.tsx | 2 +- .../instructions/beam.gather.factories.ts | 34 ++- src/modules/beam/scatter/BeamRay.tsx | 59 ++-- src/modules/beam/scatter/BeamRayGrid.tsx | 15 + .../beam/scatter/BeamScatterPaneDropdown.tsx | 6 +- src/modules/beam/store-beam-vanilla.ts | 4 +- src/modules/beam/store-module-beam.tsx | 12 +- 18 files changed, 786 insertions(+), 494 deletions(-) create mode 100644 src/modules/beam/gather/BeamFusion.tsx create mode 100644 src/modules/beam/gather/BeamFusionGrid.tsx delete mode 100644 src/modules/beam/gather/BeamGatherOutput.tsx diff --git a/src/modules/beam/BeamCard.tsx b/src/modules/beam/BeamCard.tsx index b24e2f4d9..a2ea11625 100644 --- a/src/modules/beam/BeamCard.tsx +++ b/src/modules/beam/BeamCard.tsx @@ -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. */ diff --git a/src/modules/beam/BeamView.tsx b/src/modules/beam/BeamView.tsx index ace334d45..15d1cd930 100644 --- a/src/modules/beam/BeamView.tsx +++ b/src/modules/beam/BeamView.tsx @@ -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 ( + {/* Main V-Layout */} + {/* Gapper between Rays and Merge, without compromising the auto margin of the Ray Grid */} - {/* Fusion Config */} - - - {/*= 2}*/} - {/* fusionIds={fusionIds}*/} - {/* isMobile={props.isMobile}*/} - {/* onAddFusion={() => alert('add fusion xxx')}*/} - {/* raysCount={raysCount}*/} - {/*/>*/} - {/* Gather Controls */} - {/* Fusion Output */} - + + {/* Confirm Stop Scattering */} + {warnIsScattering && ( + + } + /> + )} + + ); } diff --git a/src/modules/beam/beam.config.ts b/src/modules/beam/beam.config.ts index 15841fcc3..ed483f81d 100644 --- a/src/modules/beam/beam.config.ts +++ b/src/modules/beam/beam.config.ts @@ -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; diff --git a/src/modules/beam/gather/BeamFusion.tsx b/src/modules/beam/gather/BeamFusion.tsx new file mode 100644 index 000000000..d9cfea3d8 --- /dev/null +++ b/src/modules/beam/gather/BeamFusion.tsx @@ -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, + onRemove: () => void, + onToggleGenerate: () => void, +}) { + + + return ( + + + {/* LLM Icon */} + {!!props.llmVendorIcon && ( + + + + + + )} + + {/* Factory Icon */} + {!!props.factory.Icon && ( + + )} + + {/* Title / Progress Component */} + + {props.fusion.fusingProgressComponent + // Show the progress in place of the title + ? props.fusion.fusingProgressComponent + : ( + + {props.factory.label + ' Merge'} {props.isStopped && - Interrupted} + + )} + + + {!props.isFusing ? ( + + + {props.isEmpty ? : } + + + ) : ( + + + + + + )} + + + + + + + + ); +} + + +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 ( + + + {/* Controls Row */} + + + + {/* Show issue, if any */} + {isError && } + + {/* 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') && ( + + {!!fusion.outputDMessage && ( + + )} + + )} + + {/* Use Fusion */} + {showUseButtons && ( + + + {/* Copy */} + + + + + + + {/* Continue */} + + } + 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*/} + + + + + + )} + + + ); +} \ No newline at end of file diff --git a/src/modules/beam/gather/BeamFusionGrid.tsx b/src/modules/beam/gather/BeamFusionGrid.tsx new file mode 100644 index 000000000..ba2fd5e61 --- /dev/null +++ b/src/modules/beam/gather/BeamFusionGrid.tsx @@ -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 ( + + ); +} + + +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 ( + + + {/* Fusions */} + {props.fusionIds.map((fusionId) => ( + + ))} + + {/* Add Fusion (Card) */} + {(isEmpty || !isNoFactorySelected) && ( + + {isNoFactorySelected ? null : props.canGather ? + + + + + {currentFactory.description} + + + : ( + + You need two or more replies for a {currentFactory?.label?.toLocaleLowerCase() ?? ''} merge. + + )} + + )} + + {/* Pad items: N * */} + {/*{padItems > 0 && (*/} + {/* Array.from({ length: padItems }).map((_, index) => (*/} + {/* */} + {/* ))*/} + {/*)}*/} + + + ); +} \ No newline at end of file diff --git a/src/modules/beam/gather/BeamGatherInput.tsx b/src/modules/beam/gather/BeamGatherInput.tsx index 25d97fee5..e42090245 100644 --- a/src/modules/beam/gather/BeamGatherInput.tsx +++ b/src/modules/beam/gather/BeamGatherInput.tsx @@ -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 ?? [], }; })); diff --git a/src/modules/beam/gather/BeamGatherOutput.tsx b/src/modules/beam/gather/BeamGatherOutput.tsx deleted file mode 100644 index ae5305f1d..000000000 --- a/src/modules/beam/gather/BeamGatherOutput.tsx +++ /dev/null @@ -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 ( - - - - {/* Show issue, if any */} - {isError && } - - {/* Dyanmic: the progress, set by the execution chain */} - {fusion?.fusingProgressComponent && fusion.fusingProgressComponent} - - {/* Dynamic: instruction-specific components */} - {!!fusion?.fusingInstructionComponent && fusion.fusingInstructionComponent} - - {/* Output */} - {!!fusion?.outputDMessage && ( - - )} - - {/* Use Output */} - {showUseOutputButtons && ( - - - {/* Copy */} - - - - - - - {/* Continue */} - - - - - - )} - - - - ); -} \ No newline at end of file diff --git a/src/modules/beam/gather/BeamGatherPane.tsx b/src/modules/beam/gather/BeamGatherPane.tsx index ef57b649f..a491b8e45 100644 --- a/src/modules/beam/gather/BeamGatherPane.tsx +++ b/src/modules/beam/gather/BeamGatherPane.tsx @@ -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 ( @@ -171,7 +118,7 @@ export function BeamGatherPane(props: { {/* 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'} @@ -179,111 +126,56 @@ export function BeamGatherPane(props: { {/* Method */} - {!props.isMobile && ( - Method} - sx={/*{ mb: '0.25rem' }*/ undefined} - /> - )} - - - {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 && }*/} + + {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 ( - - ); - })} - - {/*{(props.fusionIndex !== null) && (*/} - {/* */} - {/* */} - {/* {isEditable... ? null : }*/} - {/* */} - {/* */} - {/*)}*/} - + const isActive = factoryId === currentFactoryId; + const buttonColor: ColorPaletteProp = isActive ? GATHER_COLOR : 'neutral'; + return ( + + ); + })} + {/* LLM */} - + {gatherLlmComponent} - {/* Start / Stop buttons */} - {!isGatheringAny ? ( - - ) : ( - - )} + {/* Add Fusion */} + {/**/} + + {/* pad */} + - - {/* Confirm Stop Scattering */} - {warnScatterBusy && ( - - } - /> - )} - - ; + ); } \ No newline at end of file diff --git a/src/modules/beam/gather/beam.gather.ts b/src/modules/beam/gather/beam.gather.ts index e52d06833..f6e457feb 100644 --- a/src/modules/beam/gather/beam.gather.ts +++ b/src/modules/beam/gather/beam.gather.ts @@ -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; + 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 | ((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) => 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 = (_set, _get) => ({ +export const createGatherSlice: StateCreator = (_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 { - 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 + _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); }, }); diff --git a/src/modules/beam/gather/instructions/ChatGenerateInstruction.tsx b/src/modules/beam/gather/instructions/ChatGenerateInstruction.tsx index 1e3c70306..2a7065be9 100644 --- a/src/modules/beam/gather/instructions/ChatGenerateInstruction.tsx +++ b/src/modules/beam/gather/instructions/ChatGenerateInstruction.tsx @@ -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; diff --git a/src/modules/beam/gather/instructions/UserInputChecklistComponent.tsx b/src/modules/beam/gather/instructions/UserInputChecklistComponent.tsx index cc250a0d3..b47898a2e 100644 --- a/src/modules/beam/gather/instructions/UserInputChecklistComponent.tsx +++ b/src/modules/beam/gather/instructions/UserInputChecklistComponent.tsx @@ -64,7 +64,7 @@ export function UserInputChecklistComponent(props: { return ( - + Select the Merge options to apply: diff --git a/src/modules/beam/gather/instructions/beam.gather.execution.tsx b/src/modules/beam/gather/instructions/beam.gather.execution.tsx index 5a28e347f..8a20e85bd 100644 --- a/src/modules/beam/gather/instructions/beam.gather.execution.tsx +++ b/src/modules/beam/gather/instructions/beam.gather.execution.tsx @@ -130,7 +130,7 @@ export function gatherStartFusion( if (inputState.chainAbortController.signal.aborted) { return onUpdateBFusion({ stage: 'stopped', - errorText: 'Merge Canceled.', + // errorText: 'Merge Canceled.', fusingProgressComponent: undefined, }); } diff --git a/src/modules/beam/gather/instructions/beam.gather.factories.ts b/src/modules/beam/gather/instructions/beam.gather.factories.ts index 7e640091d..892573bc3 100644 --- a/src/modules/beam/gather/instructions/beam.gather.factories.ts +++ b/src/modules/beam/gather/instructions/beam.gather.factories.ts @@ -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: () => [ diff --git a/src/modules/beam/scatter/BeamRay.tsx b/src/modules/beam/scatter/BeamRay.tsx index 0f39b1054..cb18e97a9 100644 --- a/src/modules/beam/scatter/BeamRay.tsx +++ b/src/modules/beam/scatter/BeamRay.tsx @@ -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') && ( - - + + {!!ray.message && ( + + )} )} {/* Use Ray */} - {showUseButton && ( + {showUseButtons && ( + + {/* Copy */} {!isImported && ( )} + + {/* Continue */} } + )} diff --git a/src/modules/beam/scatter/BeamRayGrid.tsx b/src/modules/beam/scatter/BeamRayGrid.tsx index d07d1bb84..710f2d4b1 100644 --- a/src/modules/beam/scatter/BeamRayGrid.tsx +++ b/src/modules/beam/scatter/BeamRayGrid.tsx @@ -71,6 +71,21 @@ export function BeamRayGrid(props: { {/* Merges*/} {/**/} + {/* Fusions */} + {/*{props.fusionIds.map((fusionId) => (*/} + {/* */} + {/*))}*/} + + {/* Add Fusion */} + {/**/} + ); } \ No newline at end of file diff --git a/src/modules/beam/scatter/BeamScatterPaneDropdown.tsx b/src/modules/beam/scatter/BeamScatterPaneDropdown.tsx index 52fc1e012..c1cf20626 100644 --- a/src/modules/beam/scatter/BeamScatterPaneDropdown.tsx +++ b/src/modules/beam/scatter/BeamScatterPaneDropdown.tsx @@ -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: { {/* Beam Options*/} {/**/} - - {rayScrolling && } + + {cardScrolling && } Scroll Responses diff --git a/src/modules/beam/store-beam-vanilla.ts b/src/modules/beam/store-beam-vanilla.ts index 44ed1a26c..e895aebff 100644 --- a/src/modules/beam/store-beam-vanilla.ts +++ b/src/modules/beam/store-beam-vanilla.ts @@ -90,7 +90,7 @@ const createRootSlice: StateCreator = (_set, // update the model only if the dialog was not already open ...((!wasOpen && initialChatLLMId) && { - lastGatherLlmId: initialChatLLMId, + currentGatherLlmId: initialChatLLMId, } satisfies Partial), }); }, @@ -98,7 +98,7 @@ const createRootSlice: StateCreator = (_set, terminate: () => _set(state => ({ ...initRootStateSlice(), - ...reInitGatherStateSlice(state.fusions, state.lastGatherLlmId), // remember after termination + ...reInitGatherStateSlice(state.fusions, state.currentGatherLlmId), // remember after termination ...reInitScatterStateSlice(state.rays), })), diff --git a/src/modules/beam/store-module-beam.tsx b/src/modules/beam/store-module-beam.tsx index ca8ec0440..ce53eafee 100644 --- a/src/modules/beam/store-module-beam.tsx +++ b/src/modules/beam/store-module-beam.tsx @@ -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()(persist( (_set, _get) => ({ scatterPresets: [], - rayScrolling: false, + cardScrolling: false, gatherShowDevMethods: true, gatherShowPrompts: false, @@ -55,7 +55,7 @@ export const useModuleBeamStore = create()(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()(persist( )); -export function useBeamRayScrolling() { - return useModuleBeamStore((state) => state.rayScrolling); +export function useBeamCardScrolling() { + return useModuleBeamStore((state) => state.cardScrolling); }