Skip to content

Commit 2e7f2b6

Browse files
committed
Rename 'broadcast' to 'multicast' in code, and much improve the Panes and Multicase modes - #388
1 parent aad0eae commit 2e7f2b6

File tree

6 files changed

+69
-69
lines changed

6 files changed

+69
-69
lines changed

src/apps/chat/AppChat.tsx

+37-34
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const SPECIAL_ID_WIPE_ALL: DConversationId = 'wipe-chats';
5555
export function AppChat() {
5656

5757
// state
58-
const [isComposerBroadcast, setIsComposerBroadcast] = React.useState(false);
58+
const [isComposerMulticast, setIsComposerMulticast] = React.useState(false);
5959
const [isMessageSelectionMode, setIsMessageSelectionMode] = React.useState(false);
6060
const [diagramConfig, setDiagramConfig] = React.useState<DiagramConfig | null>(null);
6161
const [tradeConfig, setTradeConfig] = React.useState<TradeConfig | null>(null);
@@ -116,10 +116,9 @@ export function AppChat() {
116116

117117
// Window actions
118118

119-
const isSplitPane = chatPanes.length > 1;
120-
const panesConversationIDs = chatPanes.length > 0 ? chatPanes.map((pane) => pane.conversationId) : [null];
121-
const showBroadcastControl = isSplitPane && new Set(panesConversationIDs).size >= 2;
122-
const isRealBroadcast = showBroadcastControl && isComposerBroadcast;
119+
const isMultiPane = chatPanes.length >= 2;
120+
const isMultiConversationId = isMultiPane && new Set(chatPanes.map((pane) => pane.conversationId)).size >= 2;
121+
const willMulticast = isComposerMulticast && isMultiConversationId;
123122

124123
const setFocusedConversationId = React.useCallback((conversationId: DConversationId | null) => {
125124
conversationId && openConversationInFocusedPane(conversationId);
@@ -129,12 +128,12 @@ export function AppChat() {
129128
conversationId && openConversationInSplitPane(conversationId);
130129
}, [openConversationInSplitPane]);
131130

132-
const toggleSplitPane = React.useCallback(() => {
133-
if (isSplitPane)
131+
const handleToggleMultiPane = React.useCallback(() => {
132+
if (isMultiPane)
134133
removeOtherPanes();
135134
else
136135
duplicateFocusedPane();
137-
}, [duplicateFocusedPane, isSplitPane, removeOtherPanes]);
136+
}, [duplicateFocusedPane, isMultiPane, removeOtherPanes]);
138137

139138
const handleNavigateHistory = React.useCallback((direction: 'back' | 'forward') => {
140139
if (navigateHistoryInFocusedPane(direction))
@@ -240,19 +239,19 @@ export function AppChat() {
240239
}
241240
const userText = multiPartMessage[0].text;
242241

243-
// broadcast mode scatterer
244-
const uniqueConversationIds = new Set([conversationId]);
245-
if (isRealBroadcast)
246-
chatPanes.forEach(pane => pane.conversationId && uniqueConversationIds.add(pane.conversationId));
242+
// multicast: send the message to all the panes
243+
const uniqueIds = new Set([conversationId]);
244+
if (willMulticast)
245+
chatPanes.forEach(pane => pane.conversationId && uniqueIds.add(pane.conversationId));
247246

248-
// we loop to handle both the normal and broadcast modes
247+
// we loop to handle both the normal and multicast modes
249248
let enqueued = false;
250-
for (const targetConversationId of uniqueConversationIds) {
251-
const targetConversation = getConversation(targetConversationId);
252-
if (targetConversation) {
249+
for (const _cId of uniqueIds) {
250+
const _conversation = getConversation(_cId);
251+
if (_conversation) {
253252
// start execution fire/forget
254-
void _handleExecute(chatModeId, targetConversationId, [
255-
...targetConversation.messages,
253+
void _handleExecute(chatModeId, _cId, [
254+
..._conversation.messages,
256255
createDMessage('user', userText),
257256
]);
258257
enqueued = true;
@@ -321,7 +320,7 @@ export function AppChat() {
321320
const handleConversationBranch = React.useCallback((conversationId: DConversationId, messageId: string | null): DConversationId | null => {
322321
showNextTitleChange.current = true;
323322
const branchedConversationId = branchConversation(conversationId, messageId);
324-
if (isSplitPane)
323+
if (isMultiPane)
325324
openSplitConversationId(branchedConversationId);
326325
else
327326
setFocusedConversationId(branchedConversationId);
@@ -335,7 +334,7 @@ export function AppChat() {
335334
},
336335
});
337336
return branchedConversationId;
338-
}, [branchConversation, isSplitPane, openSplitConversationId, setFocusedConversationId]);
337+
}, [branchConversation, isMultiPane, openSplitConversationId, setFocusedConversationId]);
339338

340339
const handleConversationFlatten = React.useCallback((conversationId: DConversationId) => setFlattenConversationId(conversationId), []);
341340

@@ -422,14 +421,14 @@ export function AppChat() {
422421
hasConversations={!areChatsEmpty}
423422
isConversationEmpty={isFocusedChatEmpty}
424423
isMessageSelectionMode={isMessageSelectionMode}
425-
isSplitPane={isSplitPane}
426-
setIsMessageSelectionMode={setIsMessageSelectionMode}
424+
isMultiPane={isMultiPane}
427425
onConversationBranch={handleConversationBranch}
428426
onConversationClear={handleConversationClear}
429427
onConversationFlatten={handleConversationFlatten}
430-
onToggleSplitPanes={toggleSplitPane}
428+
onToggleMultiPane={handleToggleMultiPane}
429+
setIsMessageSelectionMode={setIsMessageSelectionMode}
431430
/>,
432-
[areChatsEmpty, focusedConversationId, handleConversationBranch, handleConversationClear, handleConversationFlatten, isFocusedChatEmpty, isMessageSelectionMode, isMobile, isSplitPane, toggleSplitPane],
431+
[areChatsEmpty, focusedConversationId, handleConversationBranch, handleConversationClear, handleConversationFlatten, handleToggleMultiPane, isFocusedChatEmpty, isMessageSelectionMode, isMobile, isMultiPane],
433432
);
434433

435434
usePluggableOptimaLayout(drawerContent, centerItems, menuItems, 'AppChat');
@@ -456,18 +455,23 @@ export function AppChat() {
456455
const setFocus = chatPanes.length < 2 || !event.altKey;
457456
setFocusedPane(setFocus ? idx : -1);
458457
}}
459-
onCollapse={() => removePane(idx)}
458+
onCollapse={() => {
459+
// the small delay does not look good but lets the Panel state settle
460+
// setTimeout(() => removePane(idx), 50);
461+
// NOTE: seems there's an issue anyway with the Pane locking the screen, so we'll just call it directly
462+
removePane(idx);
463+
}}
460464
style={{
461465
// for anchoring the scroll button in place
462466
position: 'relative',
463-
...(panesConversationIDs.length >= 2 ? {
467+
...(isMultiPane ? {
464468
borderRadius: '0.375rem',
465469
border: `2px solid ${idx === focusedPaneIndex
466-
? (isRealBroadcast ? theme.palette.warning.solidBg : theme.palette.primary.solidBg)
467-
: (isRealBroadcast ? theme.palette.warning.softActiveBg : theme.palette.background.level1)}`,
468-
filter: (isRealBroadcast || idx === focusedPaneIndex)
469-
? undefined :
470-
'grayscale(60%)',
470+
? ((willMulticast || !isMultiConversationId) ? theme.palette.warning.solidBg : theme.palette.primary.solidBg)
471+
: ((willMulticast || !isMultiConversationId) ? theme.palette.warning.softActiveBg : theme.palette.background.level1)}`,
472+
filter: (!willMulticast && idx !== focusedPaneIndex)
473+
? (!isMultiConversationId ? 'grayscale(66.67%)' /* clone of the same */ : 'grayscale(66.67%)')
474+
: undefined,
471475
} : {}),
472476
}}
473477
>
@@ -529,12 +533,11 @@ export function AppChat() {
529533
composerTextAreaRef={composerTextAreaRef}
530534
conversationId={focusedConversationId}
531535
capabilityHasT2I={capabilityHasT2I}
536+
isMulticast={!isMultiConversationId ? null : isComposerMulticast}
532537
isDeveloperMode={focusedSystemPurposeId === 'Developer'}
533538
onAction={handleComposerAction}
534539
onTextImagine={handleTextImagine}
535-
isBroadcast={isComposerBroadcast}
536-
onSetBroadcast={setIsComposerBroadcast}
537-
showBroadcastControl={showBroadcastControl}
540+
setIsMulticast={setIsComposerMulticast}
538541
sx={{
539542
zIndex: 21, // position: 'sticky', bottom: 0,
540543
backgroundColor: themeBgAppChatComposer,

src/apps/chat/components/applayout/ChatPageMenuItems.tsx

+10-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
1313
import VerticalSplitOutlinedIcon from '@mui/icons-material/VerticalSplitOutlined';
1414

1515
import type { DConversationId } from '~/common/state/store-chats';
16-
import { GoodTooltip } from '~/common/components/GoodTooltip';
1716
import { KeyStroke } from '~/common/components/KeyStroke';
1817
import { useOptimaDrawers } from '~/common/layout/optima/useOptimaDrawers';
1918

@@ -26,12 +25,12 @@ export function ChatPageMenuItems(props: {
2625
hasConversations: boolean,
2726
isConversationEmpty: boolean,
2827
isMessageSelectionMode: boolean,
29-
isSplitPane: boolean
30-
setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
28+
isMultiPane: boolean,
3129
onConversationBranch: (conversationId: DConversationId, messageId: string | null) => void,
3230
onConversationClear: (conversationId: DConversationId) => void,
3331
onConversationFlatten: (conversationId: DConversationId) => void,
34-
onToggleSplitPanes: () => void,
32+
onToggleMultiPane: () => void,
33+
setIsMessageSelectionMode: (isMessageSelectionMode: boolean) => void,
3534
}) {
3635

3736
// external state
@@ -80,16 +79,13 @@ export function ChatPageMenuItems(props: {
8079
</MenuItem>
8180

8281
{/* Split/Unsplit panes */}
83-
<GoodTooltip title={props.isSplitPane ? 'Close Split Panes' : 'Split Conversation Vertically'}>
84-
<MenuItem onClick={props.onToggleSplitPanes}>
85-
<ListItemDecorator>{
86-
props.isMobile
87-
? (props.isSplitPane ? <HorizontalSplitIcon /> : <HorizontalSplitOutlinedIcon />)
88-
: (props.isSplitPane ? <VerticalSplitIcon /> : <VerticalSplitOutlinedIcon />)
89-
}</ListItemDecorator>
90-
{props.isSplitPane ? 'Unsplit' : props.isMobile ? 'Split Down' : 'Split Right'}
91-
</MenuItem>
92-
</GoodTooltip>
82+
<MenuItem onClick={props.onToggleMultiPane}>
83+
<ListItemDecorator>{props.isMobile
84+
? (props.isMultiPane ? <HorizontalSplitIcon /> : <HorizontalSplitOutlinedIcon />)
85+
: (props.isMultiPane ? <VerticalSplitIcon /> : <VerticalSplitOutlinedIcon />)
86+
}</ListItemDecorator>
87+
{props.isMultiPane ? 'Unsplit' : props.isMobile ? 'Split Down' : 'Split Right'}
88+
</MenuItem>
9389

9490
<ListDivider />
9591

src/apps/chat/components/composer/Composer.tsx

+13-12
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import type { DLLM } from '~/modules/llms/store-llms';
2323
import type { LLMOptionsOpenAI } from '~/modules/llms/vendors/openai/openai.vendor';
2424
import { useBrowseCapability } from '~/modules/browse/store-module-browsing';
2525

26-
import { ChatBroadcastOffIcon } from '~/common/components/icons/ChatBroadcastOffIcon';
27-
import { ChatBroadcastOnIcon } from '~/common/components/icons/ChatBroadcastOnIcon';
26+
import { ChatMulticastOffIcon } from '~/common/components/icons/ChatMulticastOffIcon';
27+
import { ChatMulticastOnIcon } from '~/common/components/icons/ChatMulticastOnIcon';
2828
import { DConversationId, useChatStore } from '~/common/state/store-chats';
2929
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
3030
import { SpeechResult, useSpeechRecognition } from '~/common/components/useSpeechRecognition';
@@ -84,12 +84,11 @@ export function Composer(props: {
8484
composerTextAreaRef: React.RefObject<HTMLTextAreaElement>;
8585
conversationId: DConversationId | null;
8686
capabilityHasT2I: boolean;
87+
isMulticast: boolean | null;
8788
isDeveloperMode: boolean;
8889
onAction: (chatModeId: ChatModeId, conversationId: DConversationId, multiPartMessage: ComposerOutputMultiPart) => boolean;
8990
onTextImagine: (conversationId: DConversationId, text: string) => void;
90-
isBroadcast: boolean;
91-
onSetBroadcast: (broadcast: boolean) => void;
92-
showBroadcastControl: boolean;
91+
setIsMulticast: (on: boolean) => void;
9392
sx?: SxProps;
9493
}) {
9594

@@ -716,14 +715,16 @@ export function Composer(props: {
716715

717716
</Box>
718717

719-
{/* [desktop] Broadcast switch (under the Chat button) */}
720-
{isDesktop && props.showBroadcastControl && isText && (
721-
<FormControl orientation='horizontal' sx={{ minHeight: '2.25rem', justifyContent: 'space-between', alignItems: 'center' }}>
722-
<FormLabel sx={{ gap: 1 }}>
723-
{props.isBroadcast ? <ChatBroadcastOnIcon sx={{ color: 'warning.solidBg' }} /> : <ChatBroadcastOffIcon />}
724-
Broadcast {props.isBroadcast ? ' · On' : ''}
718+
{/* [desktop] Multicast switch (under the Chat button) */}
719+
{isDesktop && props.isMulticast !== null && (
720+
<FormControl orientation='horizontal' sx={{ minHeight: '2.25rem', justifyContent: 'space-between' }}>
721+
<FormLabel sx={{ gap: 1, flexFlow: 'row nowrap' }}>
722+
<Box sx={{ display: { xs: 'none', lg: 'inline-block' } }}>
723+
{props.isMulticast ? <ChatMulticastOnIcon sx={{ color: 'warning.solidBg' }} /> : <ChatMulticastOffIcon />}
724+
</Box>
725+
{props.isMulticast ? 'Multichat · On' : 'Multichat'}
725726
</FormLabel>
726-
<Switch color={props.isBroadcast ? 'primary' : undefined} checked={props.isBroadcast} onChange={(e) => props.onSetBroadcast(e.target.checked)} />
727+
<Switch color={props.isMulticast ? 'primary' : undefined} checked={props.isMulticast} onChange={(e) => props.setIsMulticast(e.target.checked)} />
727728
</FormControl>
728729
)}
729730

src/common/components/icons/ChatBroadcastOffIcon.tsx src/common/components/icons/ChatMulticastOffIcon.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { SvgIcon } from '@mui/joy';
66
/*
77
* Source: the PodcastsIcon from '@mui/icons-material/Podcasts';
88
*/
9-
export function ChatBroadcastOffIcon(props: { sx?: SxProps }) {
9+
export function ChatMulticastOffIcon(props: { sx?: SxProps }) {
1010
return (
1111
<SvgIcon viewBox='0 0 24 24' width='24' height='24' {...props}>
1212
<path d='M14 12c0 .74-.4 1.38-1 1.72V22h-2v-8.28c-.6-.35-1-.98-1-1.72 0-1.1.9-2 2-2s2 .9 2 2'></path>

src/common/components/icons/ChatBroadcastOnIcon.tsx src/common/components/icons/ChatMulticastOnIcon.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { SvgIcon } from '@mui/joy';
66
/*
77
* Source: the PodcastsIcon from '@mui/icons-material/Podcasts';
88
*/
9-
export function ChatBroadcastOnIcon(props: { sx?: SxProps }) {
9+
export function ChatMulticastOnIcon(props: { sx?: SxProps }) {
1010
return (
1111
<SvgIcon viewBox='0 0 24 24' width='24' height='24' {...props}>
1212
<path d='M14 12c0 .74-.4 1.38-1 1.72V22h-2v-8.28c-.6-.35-1-.98-1-1.72 0-1.1.9-2 2-2s2 .9 2 2m-2-6c-3.31 0-6 2.69-6 6 0 1.74.75 3.31 1.94 4.4l1.42-1.42C8.53 14.25 8 13.19 8 12c0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.19-.53 2.25-1.36 2.98l1.42 1.42C17.25 15.31 18 13.74 18 12c0-3.31-2.69-6-6-6m0-4C6.48 2 2 6.48 2 12c0 2.85 1.2 5.41 3.11 7.24l1.42-1.42C4.98 16.36 4 14.29 4 12c0-4.41 3.59-8 8-8s8 3.59 8 8c0 2.29-.98 4.36-2.53 5.82l1.42 1.42C20.8 17.41 22 14.85 22 12c0-5.52-4.48-10-10-10'></path>

src/common/components/useSingleTabEnforcer.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as React from 'react';
77
*/
88
class AloneDetector {
99
private readonly clientId: string;
10-
private readonly broadcastChannel: BroadcastChannel;
10+
private readonly bChannel: BroadcastChannel;
1111

1212
private aloneCallback: ((isAlone: boolean) => void) | null;
1313
private aloneTimerId: number | undefined;
@@ -17,15 +17,15 @@ class AloneDetector {
1717
this.clientId = Math.random().toString(36).substring(2, 10);
1818
this.aloneCallback = onAlone;
1919

20-
this.broadcastChannel = new BroadcastChannel(channelName);
21-
this.broadcastChannel.onmessage = this.handleIncomingMessage;
20+
this.bChannel = new BroadcastChannel(channelName);
21+
this.bChannel.onmessage = this.handleIncomingMessage;
2222

2323
}
2424

2525
public onUnmount(): void {
2626
// close channel
27-
this.broadcastChannel.onmessage = null;
28-
this.broadcastChannel.close();
27+
this.bChannel.onmessage = null;
28+
this.bChannel.close();
2929

3030
// clear timeout
3131
if (this.aloneTimerId)
@@ -38,7 +38,7 @@ class AloneDetector {
3838
public checkIfAlone(): void {
3939

4040
// triggers other clients
41-
this.broadcastChannel.postMessage({ type: 'CHECK', sender: this.clientId });
41+
this.bChannel.postMessage({ type: 'CHECK', sender: this.clientId });
4242

4343
// if no response within 500ms, assume this client is alone
4444
this.aloneTimerId = window.setTimeout(() => {
@@ -56,7 +56,7 @@ class AloneDetector {
5656
switch (event.data.type) {
5757

5858
case 'CHECK':
59-
this.broadcastChannel.postMessage({ type: 'ALIVE', sender: this.clientId });
59+
this.bChannel.postMessage({ type: 'ALIVE', sender: this.clientId });
6060
break;
6161

6262
case 'ALIVE':

0 commit comments

Comments
 (0)