diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index 12f5cb998..ce4b082a7 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -665,6 +665,7 @@ export function AppChat() { {isMultiPane && !isZenMode && ( diff --git a/src/apps/chat/components/PaneTitleOverlay.tsx b/src/apps/chat/components/PaneTitleOverlay.tsx index aeb5e9ab3..6068f0437 100644 --- a/src/apps/chat/components/PaneTitleOverlay.tsx +++ b/src/apps/chat/components/PaneTitleOverlay.tsx @@ -1,50 +1,148 @@ import * as React from 'react'; -import { Sheet } from '@mui/joy'; + +import { Box, IconButton, Sheet } from '@mui/joy'; +import ClearIcon from '@mui/icons-material/Clear'; +import EditRoundedIcon from '@mui/icons-material/EditRounded'; +import OpenInFullIcon from '@mui/icons-material/OpenInFull'; import type { DConversationId } from '~/common/stores/chat/chat.conversation'; +import { InlineTextarea } from '~/common/components/InlineTextarea'; import { useConversationTitle } from '~/common/stores/chat/hooks/useConversationTitle'; +import { chatPanesActions } from './panes/store-panes-manager'; -const _style = { - position: 'absolute', - top: 0, - left: '50%', - transform: 'translateX(-50%)', - zIndex: 10, - p: '1px 1rem 4px', - fontSize: 'sm', - fontWeight: 'md', - borderBottomLeftRadius: '8px', - borderBottomRightRadius: '8px', - // boxShadow: 'xs', - // border: '1px solid', - // borderColor: 'background.popup', - borderTop: 'none', - maxWidth: '78%', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', +const _styles = { + tileBar: { + position: 'absolute', + top: 0, + left: '50%', + transform: 'translateX(-50%)', + zIndex: 10, + padding: '0 0.125rem 0.125rem', + fontSize: 'sm', + fontWeight: 'md', + borderBottomLeftRadius: '8px', + borderBottomRightRadius: '8px', + // boxShadow: 'xs', + // border: '1px solid', + // borderColor: 'background.popup', + borderTop: 'none', + maxWidth: '78%', + display: 'flex', + alignItems: 'center', + gap: 1, + } as const, + title: { + flex: 1, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + } as const, + toolButton: { + '--IconButton-size': '1.5rem', + backgroundColor: 'transparent', + } as const, + toolIconLg: { + fontSize: 'lg', + } as const, } as const; -export function PaneTitleOverlay(props: { conversationId: DConversationId | null, isFocused: boolean }) { +export function PaneTitleOverlay(props: { + paneIdx: number, + conversationId: DConversationId | null, + isFocused: boolean, +}) { + + // state + const [editingTitle, setEditingTitle] = React.useState(false); // external state - const title = useConversationTitle(props.conversationId); - if (!title || title?.length < 3) - return null; + const { title, setUserTitle } = useConversationTitle(props.conversationId); + // if (!title || title?.length < 3) + // return null; + + + // close tabs handlers + + const handleCloseThis = React.useCallback(() => { + chatPanesActions().removePane(props.paneIdx); + }, [props.paneIdx]); + + const handleCloseOthers = React.useCallback(() => { + chatPanesActions().removeOtherPanes(props.paneIdx); + }, [props.paneIdx]); + + + // title handles + + const handleTitleEditBegin = React.useCallback(() => { + setEditingTitle(true); + }, []); + + const handleTitleEditChange = React.useCallback((newTitle: string) => { + setUserTitle(newTitle); + setEditingTitle(false); + }, [setUserTitle]); + + const handleTitleEditEnd = React.useCallback(() => { + setEditingTitle(false); + }, []); + // don't render if not focused // if (!props.isFocused) // return null; + const hasTitle = title && title.length > 0; + const color = props.isFocused ? 'primary' : 'neutral'; + const variantO = props.isFocused ? 'solid' : 'outlined'; + const variantP = props.isFocused ? 'solid' : 'plain'; + return ( - {title} + {/* Close Others*/} + {/**/} + {!editingTitle && + + } + {/**/} + + {/* Title */} + {editingTitle ? ( + + ) : hasTitle ? ( + + {title} + + ) : !!props.conversationId && ( + + + + )} + + {/* Close This */} + {/**/} + {!editingTitle && + + } + {/**/} ); } \ No newline at end of file diff --git a/src/common/stores/chat/hooks/useConversationTitle.ts b/src/common/stores/chat/hooks/useConversationTitle.ts index 5a13a78d2..e320044d9 100644 --- a/src/common/stores/chat/hooks/useConversationTitle.ts +++ b/src/common/stores/chat/hooks/useConversationTitle.ts @@ -1,12 +1,25 @@ +import * as React from 'react'; import { useShallow } from 'zustand/react/shallow'; import { conversationTitle, DConversationId } from '../chat.conversation'; import { useChatStore } from '../store-chats'; -export function useConversationTitle(conversationId: DConversationId | null) { - return useChatStore(useShallow(({ conversations }) => { +export function useConversationTitle(conversationId: DConversationId | null, fallbackTitle?: string) { + + // react to the title + const { title, setUserTitle: storeSetUserTitle } = useChatStore(useShallow(({ conversations, setUserTitle }) => { const conversation = conversationId ? conversations.find(_c => _c.id === conversationId) : null; - return conversation ? conversationTitle(conversation) : null; + return { + title: conversation ? conversationTitle(conversation, fallbackTitle) : null, + setUserTitle, + }; })); + + // closure to set the title + const setUserTitle = React.useCallback((newTitle: string) => { + conversationId && storeSetUserTitle(conversationId, newTitle); + }, [conversationId, storeSetUserTitle]); + + return { title, setUserTitle }; }