Skip to content

Commit 50efea7

Browse files
feat(lightspeed): sorting chats, persisting chat order, pinned chat, toggle pinned chat
Signed-off-by: its-mitesh-kumar <[email protected]>
1 parent 6b30326 commit 50efea7

File tree

10 files changed

+460
-30
lines changed

10 files changed

+460
-30
lines changed

workspaces/lightspeed/plugins/lightspeed/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@
5555
"@backstage/theme": "^0.7.0",
5656
"@material-ui/core": "^4.9.13",
5757
"@material-ui/lab": "^4.0.0-alpha.61",
58+
"@monaco-editor/react": "^4.6.0",
5859
"@mui/icons-material": "^6.1.8",
5960
"@mui/material": "^5.12.2",
60-
"@patternfly/chatbot": "6.4.1",
61+
"@patternfly/chatbot": "6.5.0-prerelease.28",
6162
"@patternfly/react-core": "6.4.0",
6263
"@patternfly/react-icons": "^6.3.1",
6364
"@red-hat-developer-hub/backstage-plugin-lightspeed-common": "workspace:^",
6465
"@tanstack/react-query": "^5.59.15",
66+
"monaco-editor": "^0.52.0",
6567
"react-markdown": "^9.0.1",
6668
"react-use": "^17.2.4"
6769
},

workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx

Lines changed: 120 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,22 @@ import {
3535
MessageProps,
3636
} from '@patternfly/chatbot';
3737
import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
38-
import { DropdownItem, DropEvent, Title } from '@patternfly/react-core';
39-
import { PlusIcon, SearchIcon } from '@patternfly/react-icons';
38+
import {
39+
DropdownItem,
40+
DropEvent,
41+
MenuToggle,
42+
MenuToggleElement,
43+
Select,
44+
SelectList,
45+
SelectOption,
46+
Title,
47+
Tooltip,
48+
} from '@patternfly/react-core';
49+
import {
50+
PlusIcon,
51+
SearchIcon,
52+
SortAmountDownIcon,
53+
} from '@patternfly/react-icons';
4054
import { useQueryClient } from '@tanstack/react-query';
4155

4256
import { supportedFileTypes, TEMP_CONVERSATION_ID } from '../const';
@@ -47,6 +61,7 @@ import {
4761
useIsMobile,
4862
useLastOpenedConversation,
4963
useLightspeedDeletePermission,
64+
usePinnedChatsSettings,
5065
} from '../hooks';
5166
import { useLightspeedUpdatePermission } from '../hooks/useLightspeedUpdatePermission';
5267
import { useTranslation } from '../hooks/useTranslation';
@@ -56,6 +71,7 @@ import { getAttachments } from '../utils/attachment-utils';
5671
import {
5772
getCategorizeMessages,
5873
getFootnoteProps,
74+
SortOption,
5975
} from '../utils/lightspeed-chatbox-utils';
6076
import Attachment from './Attachment';
6177
import { useFileAttachmentContext } from './AttachmentContext';
@@ -100,6 +116,10 @@ const useStyles = makeStyles(theme => ({
100116
maxWidth: '100%',
101117
},
102118
},
119+
sortDropdown: {
120+
padding: 0,
121+
margin: 0,
122+
},
103123
}));
104124

105125
type LightspeedChatProps = {
@@ -134,14 +154,23 @@ export const LightspeedChat = ({
134154
const [newChatCreated, setNewChatCreated] = useState<boolean>(false);
135155
const [isSendButtonDisabled, setIsSendButtonDisabled] =
136156
useState<boolean>(false);
137-
const [isPinningChatsEnabled, setIsPinningChatsEnabled] = useState(true); // read from user settings in future
138-
const [pinnedChats, setPinnedChats] = useState<string[]>([]); // read from user settings in future
139157
const [targetConversationId, setTargetConversationId] = useState<string>('');
140158
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
141159
const [isRenameModalOpen, setIsRenameModalOpen] = useState<boolean>(false);
160+
const [isSortSelectOpen, setIsSortSelectOpen] = useState<boolean>(false);
142161
const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } =
143162
useLastOpenedConversation(user);
144163

164+
const {
165+
isPinningChatsEnabled,
166+
pinnedChats,
167+
selectedSort,
168+
handlePinningChatsToggle,
169+
pinChat,
170+
unpinChat,
171+
handleSortChange,
172+
} = usePinnedChatsSettings(user);
173+
145174
const {
146175
uploadError,
147176
showAlert,
@@ -159,12 +188,6 @@ export const LightspeedChat = ({
159188
}
160189
}, [lastOpenedId, isReady]);
161190

162-
useEffect(() => {
163-
if (!isPinningChatsEnabled) {
164-
setPinnedChats([]);
165-
}
166-
}, [isPinningChatsEnabled]);
167-
168191
const queryClient = useQueryClient();
169192

170193
const {
@@ -282,14 +305,6 @@ export const LightspeedChat = ({
282305
setIsDeleteModalOpen(false);
283306
}, [clearLastOpenedId, lastOpenedId, onNewChat, targetConversationId]);
284307

285-
const pinChat = (convId: string) => {
286-
setPinnedChats(prev => [...prev, convId]); // write to user settings in future
287-
};
288-
289-
const unpinChat = (convId: string) => {
290-
setPinnedChats(prev => prev.filter(id => id !== convId)); // write to user settings in future
291-
};
292-
293308
const additionalMessageProps = useCallback(
294309
(conversationSummary: ConversationSummary) => {
295310
const isChatFavorite = pinnedChats?.find(
@@ -337,7 +352,15 @@ export const LightspeedChat = ({
337352
),
338353
};
339354
},
340-
[pinnedChats, hasDeleteAccess, isPinningChatsEnabled, hasUpdateAccess, t],
355+
[
356+
pinnedChats,
357+
hasDeleteAccess,
358+
isPinningChatsEnabled,
359+
hasUpdateAccess,
360+
t,
361+
pinChat,
362+
unpinChat,
363+
],
341364
);
342365

343366
const categorizedMessages = useMemo(
@@ -347,8 +370,9 @@ export const LightspeedChat = ({
347370
pinnedChats,
348371
additionalMessageProps,
349372
t,
373+
selectedSort,
350374
),
351-
[additionalMessageProps, conversations, pinnedChats, t],
375+
[additionalMessageProps, conversations, pinnedChats, t, selectedSort],
352376
);
353377

354378
const filterConversations = useCallback(
@@ -469,6 +493,80 @@ export const LightspeedChat = ({
469493
setIsDrawerOpen(isOpen => !isOpen);
470494
}, []);
471495

496+
const onSortToggle = useCallback(() => {
497+
setIsSortSelectOpen(prev => !prev);
498+
}, []);
499+
500+
const onSortSelect = useCallback(
501+
(_event?: React.MouseEvent<Element>, value?: string | number) => {
502+
handleSortChange(value as SortOption);
503+
setIsSortSelectOpen(false);
504+
},
505+
[handleSortChange],
506+
);
507+
508+
const getSortLabel = useCallback(
509+
(option: SortOption): string => {
510+
const labels: Record<SortOption, string> = {
511+
newest: t('sort.newest'),
512+
oldest: t('sort.oldest'),
513+
alphabeticalAsc: t('sort.alphabeticalAsc'),
514+
alphabeticalDesc: t('sort.alphabeticalDesc'),
515+
};
516+
return labels[option];
517+
},
518+
[t],
519+
);
520+
521+
const sortDropdown = useMemo(
522+
() => (
523+
<Select
524+
id="sort-select"
525+
isOpen={isSortSelectOpen}
526+
selected={selectedSort}
527+
onSelect={onSortSelect}
528+
onOpenChange={(isOpen: boolean) => setIsSortSelectOpen(isOpen)}
529+
popperProps={{ position: 'end' }}
530+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
531+
<Tooltip
532+
content={`${t('sort.label')} - ${getSortLabel(selectedSort)}`}
533+
>
534+
<MenuToggle
535+
ref={toggleRef}
536+
aria-label={t('sort.label')}
537+
variant="plain"
538+
onClick={onSortToggle}
539+
isExpanded={isSortSelectOpen}
540+
>
541+
<SortAmountDownIcon />
542+
</MenuToggle>
543+
</Tooltip>
544+
)}
545+
shouldFocusToggleOnSelect
546+
>
547+
<SelectList className={classes.sortDropdown}>
548+
<SelectOption value="newest">{t('sort.newest')}</SelectOption>
549+
<SelectOption value="oldest">{t('sort.oldest')}</SelectOption>
550+
<SelectOption value="alphabeticalAsc">
551+
{t('sort.alphabeticalAsc')}
552+
</SelectOption>
553+
<SelectOption value="alphabeticalDesc">
554+
{t('sort.alphabeticalDesc')}
555+
</SelectOption>
556+
</SelectList>
557+
</Select>
558+
),
559+
[
560+
isSortSelectOpen,
561+
selectedSort,
562+
onSortSelect,
563+
onSortToggle,
564+
getSortLabel,
565+
t,
566+
classes.sortDropdown,
567+
],
568+
);
569+
472570
const handleAttach = (data: File[], event: DropEvent) => {
473571
event.preventDefault();
474572
handleFileUpload(data);
@@ -527,7 +625,7 @@ export const LightspeedChat = ({
527625
handleSelectedModel={item => handleSelectedModel(item)}
528626
models={models}
529627
isPinningChatsEnabled={isPinningChatsEnabled}
530-
onPinnedChatsToggle={setIsPinningChatsEnabled}
628+
onPinnedChatsToggle={handlePinningChatsToggle}
531629
/>
532630
</ChatbotHeader>
533631
<Divider />
@@ -560,6 +658,7 @@ export const LightspeedChat = ({
560658
setFilterValue('');
561659
},
562660
}}
661+
searchActionEnd={sortDropdown}
563662
noResultsState={
564663
filterValue &&
565664
Object.keys(filterConversations(filterValue)).length === 0

workspaces/lightspeed/plugins/lightspeed/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export * from './useIsMobile';
2323
export * from './useLastOpenedConversation';
2424
export * from './useLightspeedDeletePermission';
2525
export * from './useLightspeedViewPermission';
26+
export * from './usePinnedChatsSettings';
2627
export * from './useTranslation';

0 commit comments

Comments
 (0)