@@ -3,12 +3,17 @@ import { classNames } from '../utils/misc';
33import { Conversation } from '../utils/types' ;
44import StorageUtils from '../utils/storage' ;
55import { useNavigate , useParams } from 'react-router' ;
6- import { XMarkIcon } from '@heroicons/react/24/outline' ;
6+ import { EllipsisVerticalIcon , XMarkIcon } from '@heroicons/react/24/outline' ;
7+ import { BtnWithTooltips } from '../utils/common' ;
8+ import { useAppContext } from '../utils/app.context' ;
9+ import toast from 'react-hot-toast' ;
710
811export default function Sidebar ( ) {
912 const params = useParams ( ) ;
1013 const navigate = useNavigate ( ) ;
1114
15+ const { isGenerating } = useAppContext ( ) ;
16+
1217 const [ conversations , setConversations ] = useState < Conversation [ ] > ( [ ] ) ;
1318 const [ currConv , setCurrConv ] = useState < Conversation | null > ( null ) ;
1419
@@ -63,17 +68,45 @@ export default function Sidebar() {
6368 + New conversation
6469 </ div >
6570 { conversations . map ( ( conv ) => (
66- < div
71+ < ConversationItem
6772 key = { conv . id }
68- className = { classNames ( {
69- 'btn btn-ghost justify-start font-normal' : true ,
70- 'btn-soft' : conv . id === currConv ?. id ,
71- } ) }
72- onClick = { ( ) => navigate ( `/chat/${ conv . id } ` ) }
73- dir = "auto"
74- >
75- < span className = "truncate" > { conv . name } </ span >
76- </ div >
73+ conv = { conv }
74+ isCurrConv = { currConv ?. id === conv . id }
75+ onSelect = { ( ) => {
76+ navigate ( `/chat/${ conv . id } ` ) ;
77+ } }
78+ onDelete = { ( ) => {
79+ if ( isGenerating ( conv . id ) ) {
80+ toast . error ( 'Cannot delete conversation while generating' ) ;
81+ return ;
82+ }
83+ if (
84+ window . confirm ( 'Are you sure to delete this conversation?' )
85+ ) {
86+ toast . success ( 'Conversation deleted' ) ;
87+ StorageUtils . remove ( conv . id ) ;
88+ navigate ( '/' ) ;
89+ }
90+ } }
91+ onDownload = { ( ) => {
92+ if ( isGenerating ( conv . id ) ) {
93+ toast . error ( 'Cannot download conversation while generating' ) ;
94+ return ;
95+ }
96+ const conversationJson = JSON . stringify ( conv , null , 2 ) ;
97+ const blob = new Blob ( [ conversationJson ] , {
98+ type : 'application/json' ,
99+ } ) ;
100+ const url = URL . createObjectURL ( blob ) ;
101+ const a = document . createElement ( 'a' ) ;
102+ a . href = url ;
103+ a . download = `conversation_${ conv . id } .json` ;
104+ document . body . appendChild ( a ) ;
105+ a . click ( ) ;
106+ document . body . removeChild ( a ) ;
107+ URL . revokeObjectURL ( url ) ;
108+ } }
109+ />
77110 ) ) }
78111 < div className = "text-center text-xs opacity-40 mt-auto mx-4" >
79112 Conversations are saved to browser's IndexedDB
@@ -83,3 +116,57 @@ export default function Sidebar() {
83116 </ >
84117 ) ;
85118}
119+
120+ function ConversationItem ( {
121+ conv,
122+ isCurrConv,
123+ onSelect,
124+ onDelete,
125+ onDownload,
126+ } : {
127+ conv : Conversation ;
128+ isCurrConv : boolean ;
129+ onSelect : ( ) => void ;
130+ onDelete : ( ) => void ;
131+ onDownload : ( ) => void ;
132+ } ) {
133+ return (
134+ < div
135+ className = { classNames ( {
136+ 'group flex flex-row btn btn-ghost justify-start items-center font-normal' :
137+ true ,
138+ 'btn-soft' : isCurrConv ,
139+ } ) }
140+ >
141+ < div
142+ key = { conv . id }
143+ className = "w-full overflow-hidden truncate text-start"
144+ onClick = { onSelect }
145+ dir = "auto"
146+ >
147+ { conv . name }
148+ </ div >
149+ < div className = "dropdown dropdown-end h-5 opacity-0 group-hover:opacity-100" >
150+ < BtnWithTooltips
151+ className = "cursor-pointer block group-hover:block"
152+ onClick = { ( ) => { } }
153+ tooltipsContent = "More"
154+ >
155+ < EllipsisVerticalIcon className = "w-5 h-5" />
156+ </ BtnWithTooltips >
157+ { /* dropdown menu */ }
158+ < ul
159+ tabIndex = { 0 }
160+ className = "dropdown-content menu bg-base-100 rounded-box z-[1] w-32 p-2 shadow"
161+ >
162+ < li onClick = { onDownload } >
163+ < a > Download</ a >
164+ </ li >
165+ < li className = "text-error" onClick = { onDelete } >
166+ < a > Delete</ a >
167+ </ li >
168+ </ ul >
169+ </ div >
170+ </ div >
171+ ) ;
172+ }
0 commit comments