@@ -39,6 +39,8 @@ export const ChatView: React.FC<ChatViewProps> = ({ selectedConversation, showCh
3939 const [ resumeCandidates , setResumeCandidates ] = useState < Conversation [ ] > ( [ ] ) ;
4040 const [ resumeLoading , setResumeLoading ] = useState ( false ) ;
4141 const [ resumeOnlyProject , setResumeOnlyProject ] = useState ( true ) ;
42+ const [ resumeSelectMode , setResumeSelectMode ] = useState ( false ) ;
43+ const [ resumeSelected , setResumeSelected ] = useState < Record < string , boolean > > ( { } ) ;
4244 const [ categoryOpen , setCategoryOpen ] = useState ( false ) ;
4345 const [ newCategoryName , setNewCategoryName ] = useState ( "" ) ;
4446
@@ -251,14 +253,69 @@ export const ChatView: React.FC<ChatViewProps> = ({ selectedConversation, showCh
251253 < DialogHeader >
252254 < div className = "flex items-center justify-between gap-2" >
253255 < DialogTitle > Resume a previous session</ DialogTitle >
254- < label className = "flex items-center gap-2 text-xs text-muted-foreground" >
255- < input
256- type = "checkbox"
257- checked = { resumeOnlyProject }
258- onChange = { ( e ) => setResumeOnlyProject ( e . target . checked ) }
259- />
260- Only current project
261- </ label >
256+ < div className = "flex items-center gap-2" >
257+ < label className = "flex items-center gap-2 text-xs text-muted-foreground" >
258+ < input
259+ type = "checkbox"
260+ checked = { resumeSelectMode }
261+ onChange = { ( e ) => {
262+ const enabled = e . target . checked ;
263+ setResumeSelectMode ( enabled ) ;
264+ if ( ! enabled ) setResumeSelected ( { } ) ;
265+ } }
266+ />
267+ Select mode
268+ </ label >
269+ { resumeSelectMode && (
270+ < >
271+ < Button
272+ size = "sm"
273+ variant = "outline"
274+ onClick = { ( ) => {
275+ if ( resumeCandidates . length === 0 ) return ;
276+ const allSelected : Record < string , boolean > = { } ;
277+ for ( const c of resumeCandidates ) {
278+ const fp = ( c as any ) . filePath as string | undefined ;
279+ if ( fp ) allSelected [ fp ] = true ;
280+ }
281+ setResumeSelected ( allSelected ) ;
282+ } }
283+ >
284+ Select all
285+ </ Button >
286+ < Button
287+ size = "sm"
288+ variant = "destructive"
289+ disabled = { Object . values ( resumeSelected ) . filter ( Boolean ) . length === 0 }
290+ onClick = { async ( ) => {
291+ const selectedPaths = resumeCandidates
292+ . map ( ( c ) => ( c as any ) . filePath as string | undefined )
293+ . filter ( ( fp ) : fp is string => ! ! fp && resumeSelected [ fp ] ) ;
294+ if ( selectedPaths . length === 0 ) return ;
295+ try {
296+ // Delete files in parallel
297+ await Promise . all (
298+ selectedPaths . map ( ( filePath ) =>
299+ invoke ( "delete_session_file" , { filePath } )
300+ . catch ( ( err ) => {
301+ console . error ( "Failed to delete session file:" , filePath , err ) ;
302+ } )
303+ )
304+ ) ;
305+ const selectedSet = new Set ( selectedPaths ) ;
306+ setResumeCandidates ( ( prev ) => prev . filter ( ( c ) => ! selectedSet . has ( ( c as any ) . filePath ) ) ) ;
307+ setResumeSelected ( { } ) ;
308+ setResumeSelectMode ( false ) ;
309+ } catch ( err ) {
310+ console . error ( "Bulk delete failed:" , err ) ;
311+ }
312+ } }
313+ >
314+ Delete ({ Object . values ( resumeSelected ) . filter ( Boolean ) . length } )
315+ </ Button >
316+ </ >
317+ ) }
318+ </ div >
262319 </ div >
263320 < div className = "text-xs text-muted-foreground" >
264321 Current project: { useFolderStore . getState ( ) . currentFolder || '(none)' }
@@ -278,20 +335,44 @@ export const ChatView: React.FC<ChatViewProps> = ({ selectedConversation, showCh
278335 ) }
279336 </ div >
280337 ) : (
281- resumeCandidates . map ( ( c ) => (
282- < button
283- key = { c . id + ( c . filePath || "" ) }
284- className = "w-full text-left p-3 hover:bg-accent"
285- onClick = { ( ) => {
286- // Route through existing handler which also sets resumePath via store
287- handleConversationSelect ( c ) ;
288- setResumeOpen ( false ) ;
289- } }
290- >
291- < div className = "text-sm font-medium truncate" > { c . title } </ div >
292- < div className = "text-xs text-muted-foreground truncate" > { c . projectRealpath || "(unknown project)" } </ div >
293- </ button >
294- ) )
338+ resumeCandidates . map ( ( c ) => {
339+ const fp = ( c as any ) . filePath as string | undefined ;
340+ const selected = fp ? ! ! resumeSelected [ fp ] : false ;
341+ return (
342+ < div
343+ key = { c . id + ( fp || "" ) }
344+ className = { `w-full text-left p-3 hover:bg-accent flex items-center gap-3 ${ selected ? 'bg-accent/50' : '' } ` }
345+ onClick = { ( ) => {
346+ if ( resumeSelectMode ) {
347+ if ( ! fp ) return ;
348+ setResumeSelected ( ( prev ) => ( { ...prev , [ fp ] : ! prev [ fp ] } ) ) ;
349+ return ;
350+ }
351+ // Route through existing handler which also sets resumePath via store
352+ handleConversationSelect ( c ) ;
353+ setResumeOpen ( false ) ;
354+ } }
355+ >
356+ { resumeSelectMode && (
357+ < input
358+ type = "checkbox"
359+ checked = { selected }
360+ onChange = { ( e ) => {
361+ e . stopPropagation ( ) ;
362+ if ( ! fp ) return ;
363+ const checked = e . target . checked ;
364+ setResumeSelected ( ( prev ) => ( { ...prev , [ fp ] : checked } ) ) ;
365+ } }
366+ onClick = { ( e ) => e . stopPropagation ( ) }
367+ />
368+ ) }
369+ < div className = "min-w-0 flex-1" >
370+ < div className = "text-sm font-medium truncate" > { c . title } </ div >
371+ < div className = "text-xs text-muted-foreground truncate" > { c . projectRealpath || "(unknown project)" } </ div >
372+ </ div >
373+ </ div >
374+ ) ;
375+ } )
295376 ) }
296377 </ div >
297378 </ DialogContent >
0 commit comments