Skip to content

Commit

Permalink
Split Chats: Broadcast mode. Fixes enricoros#388
Browse files Browse the repository at this point in the history
  • Loading branch information
enricoros authored and jimjonesbabyfreshout committed Feb 19, 2024
1 parent 2bcc2fe commit 9bfeade
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 24 deletions.
57 changes: 37 additions & 20 deletions src/apps/chat/AppChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ const SPECIAL_ID_WIPE_ALL: DConversationId = 'wipe-chats';
export function AppChat() {

// state
const [isComposerBroadcast, setIsComposerBroadcast] = React.useState(false);
const [isMessageSelectionMode, setIsMessageSelectionMode] = React.useState(false);
const [diagramConfig, setDiagramConfig] = React.useState<DiagramConfig | null>(null);
const [tradeConfig, setTradeConfig] = React.useState<TradeConfig | null>(null);
const [clearConversationId, setClearConversationId] = React.useState<DConversationId | null>(null);
const [deleteConversationId, setDeleteConversationId] = React.useState<DConversationId | null>(null);
const [flattenConversationId, setFlattenConversationId] = React.useState<DConversationId | null>(null);
const showNextTitle = React.useRef(false);
const showNextTitleChange = React.useRef(false);
const composerTextAreaRef = React.useRef<HTMLTextAreaElement>(null);
const [_activeFolderId, setActiveFolderId] = React.useState<string | null>(null);

Expand Down Expand Up @@ -115,8 +116,10 @@ export function AppChat() {

// Window actions

const panesConversationIDs = chatPanes.length > 0 ? chatPanes.map((pane) => pane.conversationId) : [null];
const isSplitPane = chatPanes.length > 1;
const panesConversationIDs = chatPanes.length > 0 ? chatPanes.map((pane) => pane.conversationId) : [null];
const showBroadcastControl = isSplitPane && new Set(panesConversationIDs).size >= 2;
const isRealBroadcast = showBroadcastControl && isComposerBroadcast;

const setFocusedConversationId = React.useCallback((conversationId: DConversationId | null) => {
conversationId && openConversationInFocusedPane(conversationId);
Expand All @@ -135,12 +138,12 @@ export function AppChat() {

const handleNavigateHistory = React.useCallback((direction: 'back' | 'forward') => {
if (navigateHistoryInFocusedPane(direction))
showNextTitle.current = true;
showNextTitleChange.current = true;
}, [navigateHistoryInFocusedPane]);

React.useEffect(() => {
if (showNextTitle.current) {
showNextTitle.current = false;
if (showNextTitleChange.current) {
showNextTitleChange.current = false;
const title = (focusedChatNumber >= 0 ? `#${focusedChatNumber + 1} 路 ` : '') + (focusedChatTitle || 'New Chat');
const id = addSnackbar({ key: 'focused-title', message: title, type: 'title' });
return () => removeSnackbar(id);
Expand Down Expand Up @@ -237,17 +240,25 @@ export function AppChat() {
}
const userText = multiPartMessage[0].text;

// find conversation
const conversation = getConversation(conversationId);
if (!conversation)
return false;

// start execution (async)
void _handleExecute(chatModeId, conversationId, [
...conversation.messages,
createDMessage('user', userText),
]);
return true;
// broadcast mode scatterer
const uniqueConversationIds = new Set([conversationId]);
if (isRealBroadcast)
chatPanes.forEach(pane => pane.conversationId && uniqueConversationIds.add(pane.conversationId));

// we loop to handle both the normal and broadcast modes
let enqueued = false;
for (const targetConversationId of uniqueConversationIds) {
const targetConversation = getConversation(targetConversationId);
if (targetConversation) {
// start execution fire/forget
void _handleExecute(chatModeId, targetConversationId, [
...targetConversation.messages,
createDMessage('user', userText),
]);
enqueued = true;
}
}
return enqueued;
};

const handleConversationExecuteHistory = React.useCallback(async (conversationId: DConversationId, history: DMessage[]): Promise<void> => {
Expand Down Expand Up @@ -308,7 +319,7 @@ export function AppChat() {
}, []);

const handleConversationBranch = React.useCallback((conversationId: DConversationId, messageId: string | null): DConversationId | null => {
showNextTitle.current = true;
showNextTitleChange.current = true;
const branchedConversationId = branchConversation(conversationId, messageId);
if (isSplitPane)
openSplitConversationId(branchedConversationId);
Expand Down Expand Up @@ -450,10 +461,13 @@ export function AppChat() {
// for anchoring the scroll button in place
position: 'relative',
...(panesConversationIDs.length >= 2 ? {
// border only for active pane (if two or more panes)
border: `2px solid ${idx === focusedPaneIndex ? theme.palette.primary.solidBg : theme.palette.background.level1}`,
filter: idx === focusedPaneIndex ? undefined : 'grayscale(60%)',
borderRadius: '0.375rem',
border: `2px solid ${idx === focusedPaneIndex
? (isRealBroadcast ? theme.palette.warning.solidBg : theme.palette.primary.solidBg)
: (isRealBroadcast ? theme.palette.warning.softActiveBg : theme.palette.background.level1)}`,
filter: (isRealBroadcast || idx === focusedPaneIndex)
? undefined :
'grayscale(60%)',
} : {}),
}}
>
Expand Down Expand Up @@ -518,6 +532,9 @@ export function AppChat() {
isDeveloperMode={focusedSystemPurposeId === 'Developer'}
onAction={handleComposerAction}
onTextImagine={handleTextImagine}
isBroadcast={isComposerBroadcast}
onSetBroadcast={setIsComposerBroadcast}
showBroadcastControl={showBroadcastControl}
sx={{
zIndex: 21, // position: 'sticky', bottom: 0,
backgroundColor: themeBgAppChatComposer,
Expand Down
17 changes: 16 additions & 1 deletion src/apps/chat/components/composer/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shallow } from 'zustand/shallow';
import { fileOpen, FileWithHandle } from 'browser-fs-access';
import { keyframes } from '@emotion/react';

import { Box, Button, ButtonGroup, Card, Dropdown, Grid, IconButton, Menu, MenuButton, MenuItem, Stack, Textarea, Tooltip, Typography } from '@mui/joy';
import { Box, Button, ButtonGroup, Card, Dropdown, FormControl, FormLabel, Grid, IconButton, Menu, MenuButton, MenuItem, Stack, Switch, Textarea, Tooltip, Typography } from '@mui/joy';
import { ColorPaletteProp, SxProps, VariantProp } from '@mui/joy/styles/types';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import AttachFileIcon from '@mui/icons-material/AttachFile';
Expand All @@ -23,6 +23,8 @@ import type { DLLM } from '~/modules/llms/store-llms';
import type { LLMOptionsOpenAI } from '~/modules/llms/vendors/openai/openai.vendor';
import { useBrowseCapability } from '~/modules/browse/store-module-browsing';

import { ChatBroadcastOffIcon } from '~/common/components/icons/ChatBroadcastOffIcon';
import { ChatBroadcastOnIcon } from '~/common/components/icons/ChatBroadcastOnIcon';
import { DConversationId, useChatStore } from '~/common/state/store-chats';
import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
import { SpeechResult, useSpeechRecognition } from '~/common/components/useSpeechRecognition';
Expand Down Expand Up @@ -85,6 +87,9 @@ export function Composer(props: {
isDeveloperMode: boolean;
onAction: (chatModeId: ChatModeId, conversationId: DConversationId, multiPartMessage: ComposerOutputMultiPart) => boolean;
onTextImagine: (conversationId: DConversationId, text: string) => void;
isBroadcast: boolean;
onSetBroadcast: (broadcast: boolean) => void;
showBroadcastControl: boolean;
sx?: SxProps;
}) {

Expand Down Expand Up @@ -711,6 +716,16 @@ export function Composer(props: {

</Box>

{/* [desktop] Broadcast switch (under the Chat button) */}
{isDesktop && props.showBroadcastControl && isText && (
<FormControl orientation='horizontal' sx={{ minHeight: '2.25rem', justifyContent: 'space-between', alignItems: 'center' }}>
<FormLabel sx={{ gap: 1 }}>
{props.isBroadcast ? <ChatBroadcastOnIcon sx={{ color: 'warning.solidBg' }} /> : <ChatBroadcastOffIcon />}
Broadcast {props.isBroadcast ? ' 路 On' : ''}
</FormLabel>
<Switch color={props.isBroadcast ? 'primary' : undefined} checked={props.isBroadcast} onChange={(e) => props.onSetBroadcast(e.target.checked)} />
</FormControl>
)}

{/* [desktop] secondary buttons (aligned to bottom for now, and mutually exclusive) */}
{isDesktop && <Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 1, justifyContent: 'flex-end' }}>
Expand Down
1 change: 1 addition & 0 deletions src/apps/chat/components/message/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export function ChatMessage(props: {

const handleOpsConversationBranch = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation(); // to try to not steal the focus from the banched conversation
props.onConversationBranch && props.onConversationBranch(messageId);
closeOpsMenu();
};
Expand Down
4 changes: 1 addition & 3 deletions src/apps/chat/components/panes/usePanesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ interface AppChatPanesStore {
// state
chatPanes: ChatPane[];
chatPaneFocusIndex: number | null;
// chatPaneInputMode: 'focused' | 'broadcast';

// actions
openConversationInFocusedPane: (conversationId: DConversationId) => void;
Expand Down Expand Up @@ -55,7 +54,6 @@ const useAppChatPanesStore = create<AppChatPanesStore>()(persist(
// Initial state: no panes
chatPanes: [] as ChatPane[],
chatPaneFocusIndex: null as number | null,
// chatPaneInputMode: 'focused' as 'focused' | 'broadcast',

openConversationInFocusedPane: (conversationId: DConversationId) => {
_set((state) => {
Expand Down Expand Up @@ -281,7 +279,7 @@ const useAppChatPanesStore = create<AppChatPanesStore>()(persist(
// play it safe, and make sure a pane exists, and is focused
return {
chatPanes: newPanes.length ? newPanes : [createPane(conversationIds[0] ?? null)],
chatPaneFocusIndex: (newPanes.length && chatPaneFocusIndex !== null && chatPaneFocusIndex < newPanes.length) ? state.chatPaneFocusIndex : 0,
chatPaneFocusIndex: (newPanes.length && chatPaneFocusIndex !== null && chatPaneFocusIndex < newPanes.length) ? chatPaneFocusIndex : 0,
};
}),

Expand Down
15 changes: 15 additions & 0 deletions src/common/components/icons/ChatBroadcastOffIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';

import type { SxProps } from '@mui/joy/styles/types';
import { SvgIcon } from '@mui/joy';

/*
* Source: the PodcastsIcon from '@mui/icons-material/Podcasts';
*/
export function ChatBroadcastOffIcon(props: { sx?: SxProps }) {
return (
<SvgIcon viewBox='0 0 24 24' width='24' height='24' {...props}>
<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>
</SvgIcon>
);
}
15 changes: 15 additions & 0 deletions src/common/components/icons/ChatBroadcastOnIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';

import type { SxProps } from '@mui/joy/styles/types';
import { SvgIcon } from '@mui/joy';

/*
* Source: the PodcastsIcon from '@mui/icons-material/Podcasts';
*/
export function ChatBroadcastOnIcon(props: { sx?: SxProps }) {
return (
<SvgIcon viewBox='0 0 24 24' width='24' height='24' {...props}>
<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>
</SvgIcon>
);
}

0 comments on commit 9bfeade

Please sign in to comment.