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 (
+ : }
+ sx={{
+ // justifyContent: 'end',
+ // gap: 1,
+ ...props.sx,
+ }}
+ >
+ {props.textOverride || props.currentFactory?.addLabel}
+
+ );
+}
+
+
+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 */}
-
- }
- 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
-
-
-
-
- )}
-
-
-
- );
-}
\ 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 ? (
- :*/ }
- onClick={handleCurrentFusionStart}
- sx={BEAM_BTN_SX}
- >
- Merge
-
- ) : (
- }
- onClick={currentFusionStop}
- sx={BEAM_BTN_SX}
- >
- Stop
-
- )}
+ {/* 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*/}
{/**/}
-