Drawer: show open beams

This commit is contained in:
Enrico Ros
2025-03-24 14:18:26 -07:00
parent 7c7f1bcd5f
commit 797293ad8d
4 changed files with 58 additions and 4 deletions
@@ -20,6 +20,7 @@ import { autoConversationTitle } from '~/modules/aifn/autotitle/autoTitle';
import type { DConversationId } from '~/common/stores/chat/chat.conversation';
import type { DFolder } from '~/common/stores/folders/store-chat-folders';
import { ANIM_BUSY_TYPING } from '~/common/util/dMessageUtils';
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
import { InlineTextarea } from '~/common/components/InlineTextarea';
import { isDeepEqual } from '~/common/util/hooks/useDeep';
import { useChatStore } from '~/common/stores/chat/store-chats';
@@ -64,6 +65,7 @@ export interface ChatNavigationItemData {
containsImageAssets: boolean;
folder: DFolder | null | undefined; // null: 'All', undefined: do not show folder select
updatedAt: number;
hasBeamOpen: boolean;
messageCount: number;
beingGenerated: boolean;
systemPurposeId: SystemPurposeId;
@@ -106,6 +108,7 @@ function ChatDrawerItem(props: {
containsDocAttachments,
containsImageAssets,
folder,
hasBeamOpen,
messageCount,
beingGenerated,
systemPurposeId,
@@ -210,7 +213,9 @@ function ChatDrawerItem(props: {
{/* Symbol, if globally enabled */}
{(props.showSymbols || isIncognito) && (
<ListItemDecorator>
{isIncognito ? (
{hasBeamOpen ? (
<ChatBeamIcon sx={{ fontSize: 'xl' }} />
) : isIncognito ? (
<VisibilityOffIcon sx={{ fontSize: 'xl' }} />
) : (beingGenerated && props.showSymbols === 'gif') ? (
<Avatar
@@ -286,7 +291,7 @@ function ChatDrawerItem(props: {
</Box>
) : null}
</>, [beingGenerated, containsDocAttachments, containsImageAssets, handleTitleEditBegin, handleTitleEditCancel, handleTitleEditChange, isActive, isEditingTitle, isIncognito, isNew, personaImageURI, personaSymbol, props.showSymbols, searchFrequency, title, userFlagsSummary]);
</>, [beingGenerated, containsDocAttachments, containsImageAssets, handleTitleEditBegin, handleTitleEditCancel, handleTitleEditChange, hasBeamOpen, isActive, isEditingTitle, isIncognito, isNew, personaImageURI, personaSymbol, props.showSymbols, searchFrequency, title, userFlagsSummary]);
const progressBarFixedComponent = React.useMemo(() =>
progress > 0 && (
@@ -1,5 +1,7 @@
import * as React from 'react';
import { useModuleBeamStore } from '~/modules/beam/store-module-beam';
import type { DFolder } from '~/common/stores/folders/store-chat-folders';
import { DMessage, DMessageUserFlag, MESSAGE_FLAG_STARRED, messageFragmentsReduceText, messageHasUserFlag, messageUserFlagToEmoji } from '~/common/stores/chat/chat.message';
import { conversationTitle, DConversationId } from '~/common/stores/chat/chat.conversation';
@@ -94,6 +96,9 @@ export function useChatDrawerRenderItems(
// state
const [_, setJustAMinuteCounter] = React.useState(0);
// external state
const openBeamConversationIds = useModuleBeamStore(state => state.openBeamConversationIds);
// [effect] Refresh every minute because the `getTimeBucketEn` function uses the current time
React.useEffect(() => {
@@ -173,6 +178,7 @@ export function useChatDrawerRenderItems(
? allFolders.find(folder => folder.conversationIds.includes(_c.id)) ?? null
: null,
updatedAt: _c.updated || _c.created || 0,
hasBeamOpen: !!openBeamConversationIds?.[_c.id],
messageCount,
beingGenerated: !!_c._abortController, // FIXME: when the AbortController is moved at the message level, derive the state in the conv
systemPurposeId: _c.systemPurposeId,
@@ -5,6 +5,7 @@ import { bareBonesPromptMixer } from '~/modules/persona/pmix/pmix';
import { SystemPurposes } from '../../data';
import { BeamStore, createBeamVanillaStore } from '~/modules/beam/store-beam_vanilla';
import { useModuleBeamStore } from '~/modules/beam/store-module-beam';
import type { DConversationId } from '~/common/stores/chat/chat.conversation';
import type { DLLMId } from '~/common/stores/llms/llms.types';
@@ -38,6 +39,12 @@ export class ConversationHandler {
constructor(private readonly conversationId: DConversationId) {
this.beamStore = createBeamVanillaStore();
this.overlayStore = createPerChatVanillaStore();
// track the open status of beams - this is meant to be an accelerator for the UI
this.beamStore.subscribe((state, prevState) => {
if (state.isOpen === prevState.isOpen) return;
useModuleBeamStore.getState().setBeamOpenForConversation(this.conversationId, state.isOpen);
});
}
+38 -2
View File
@@ -1,13 +1,14 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { DConversationId } from '~/common/stores/chat/chat.conversation';
import type { DLLMId } from '~/common/stores/llms/llms.types';
import { agiUuid } from '~/common/util/idUtils';
import type { FFactoryId } from './gather/instructions/beam.gather.factories';
/// Presets (persistes as zustand store) ///
/// Presets (persisted as zustand store) ///
export interface BeamConfigSnapshot {
id: string;
@@ -19,6 +20,8 @@ export interface BeamConfigSnapshot {
interface ModuleBeamState {
// stored
presets: BeamConfigSnapshot[];
lastConfig: BeamConfigSnapshot | null;
cardAdd: boolean;
@@ -27,6 +30,10 @@ interface ModuleBeamState {
scatterShowPrevMessages: boolean;
gatherAutoStartAfterScatter: boolean;
gatherShowAllPrompts: boolean;
// non-stored, temporary but useful for the UI
openBeamConversationIds: Record<string, boolean>;
}
interface ModuleBeamStore extends ModuleBeamState {
@@ -43,6 +50,9 @@ interface ModuleBeamStore extends ModuleBeamState {
toggleScatterShowPrevMessages: () => void;
toggleGatherAutoStartAfterScatter: () => void;
toggleGatherShowAllPrompts: () => void;
setBeamOpenForConversation: (conversationId: DConversationId, isOpen: boolean) => void;
clearBeamOpenForConversation: (conversationId: DConversationId) => void;
}
@@ -57,6 +67,7 @@ export const useModuleBeamStore = create<ModuleBeamStore>()(persist(
scatterShowPrevMessages: false,
gatherShowAllPrompts: false,
gatherAutoStartAfterScatter: false,
openBeamConversationIds: {},
addPreset: (name, rayLlmIds, gatherLlmId, gatherFactoryId) => _set(state => ({
@@ -99,11 +110,32 @@ export const useModuleBeamStore = create<ModuleBeamStore>()(persist(
toggleGatherShowAllPrompts: () => _set(state => ({ gatherShowAllPrompts: !state.gatherShowAllPrompts })),
setBeamOpenForConversation: (conversationId, isOpen) => _set(state => {
const openBeams = { ...state.openBeamConversationIds };
if (isOpen)
openBeams[conversationId] = true;
else
delete openBeams[conversationId];
return { openBeamConversationIds: openBeams };
}),
clearBeamOpenForConversation: (conversationId) => _set(state => {
const openBeams = { ...state.openBeamConversationIds };
delete openBeams[conversationId];
return { openBeamConversationIds: openBeams };
}),
}), {
name: 'app-module-beam',
version: 1,
migrate: (state: any, fromVersion: number): ModuleBeamState => {
partialize: (state) => {
// exclude openBeamConversationIds from persistence
const { openBeamConversationIds, ...persistedState } = state;
return persistedState;
},
migrate: (state: any, fromVersion: number): Omit<ModuleBeamState, 'openBeamConversationIds'> => {
// 0 -> 1: rename 'scatterPresets' to 'presets'
if (state && fromVersion === 0 && !state.presets)
return { ...state, presets: state.scatterPresets || [] };
@@ -125,6 +157,10 @@ export function useBeamScatterShowLettering() {
return useModuleBeamStore((state) => state.scatterShowLettering);
}
export function useIsBeamOpenForConversation(conversationId: DConversationId | null): boolean {
return useModuleBeamStore(state => conversationId ? state.openBeamConversationIds[conversationId] ?? false : false);
}
export function updateBeamLastConfig(update: Partial<BeamConfigSnapshot>) {
useModuleBeamStore.getState().updateLastConfig(update);
}