@@ -6,16 +6,23 @@ import {
66 RefreshCw ,
77 RotateCcw ,
88 Search ,
9+ Trash2 ,
910} from "lucide-react" ;
1011import { useEffect , useMemo , useState } from "react" ;
1112
12- import { listArchivedSessions , restoreSession } from "./agentRpc" ;
1313import {
14+ deleteArchivedSessions ,
15+ listArchivedSessions ,
16+ restoreSession ,
17+ } from "./agentRpc" ;
18+ import {
19+ archivedSessionIdsForWorkspace ,
1420 archivedSessionPath ,
1521 archivedSessionTitle ,
1622 filterArchivedSessions ,
1723 formatArchivedTime ,
1824 groupArchivedSessions ,
25+ removeArchivedSessions ,
1926 type ArchivedSessionSort ,
2027} from "./archivedSessions" ;
2128import SettingsSelect from "./components/SettingsSelect" ;
@@ -25,6 +32,18 @@ import { conciseError } from "./utils/errors";
2532
2633const ALL_WORKSPACES = "all" ;
2734
35+ type DeleteTarget =
36+ | {
37+ kind : "session" ;
38+ sessionIds : string [ ] ;
39+ label : string ;
40+ }
41+ | {
42+ kind : "workspace" ;
43+ sessionIds : string [ ] ;
44+ label : string ;
45+ } ;
46+
2847export default function ArchivedSessionsSettings ( ) {
2948 const [ sessions , setSessions ] = useState < SessionSummary [ ] > ( [ ] ) ;
3049 const [ loading , setLoading ] = useState ( true ) ;
@@ -33,6 +52,8 @@ export default function ArchivedSessionsSettings() {
3352 const [ workspacePath , setWorkspacePath ] = useState ( ALL_WORKSPACES ) ;
3453 const [ sort , setSort ] = useState < ArchivedSessionSort > ( "archived-desc" ) ;
3554 const [ restoringId , setRestoringId ] = useState < string > ( ) ;
55+ const [ deleteTarget , setDeleteTarget ] = useState < DeleteTarget > ( ) ;
56+ const [ deleting , setDeleting ] = useState ( false ) ;
3657 const [ reloadKey , setReloadKey ] = useState ( 0 ) ;
3758
3859 useEffect ( ( ) => {
@@ -54,6 +75,18 @@ export default function ArchivedSessionsSettings() {
5475 } ;
5576 } , [ reloadKey ] ) ;
5677
78+ useEffect ( ( ) => {
79+ if ( ! deleteTarget ) return ;
80+ const handleKeyDown = ( event : KeyboardEvent ) : void => {
81+ if ( event . key !== "Escape" ) return ;
82+ event . preventDefault ( ) ;
83+ event . stopImmediatePropagation ( ) ;
84+ if ( ! deleting ) setDeleteTarget ( undefined ) ;
85+ } ;
86+ document . addEventListener ( "keydown" , handleKeyDown , true ) ;
87+ return ( ) => document . removeEventListener ( "keydown" , handleKeyDown , true ) ;
88+ } , [ deleteTarget , deleting ] ) ;
89+
5790 const untitledLabel = t ( "conversation.new" ) ;
5891 const unknownWorkspaceLabel = t ( "settings.archivedUnknownWorkspace" ) ;
5992 const locale = getLanguage ( ) === "zh" ? "zh-CN" : "en" ;
@@ -72,6 +105,17 @@ export default function ArchivedSessionsSettings() {
72105 ] ;
73106 } , [ locale , sessions , unknownWorkspaceLabel ] ) ;
74107
108+ useEffect ( ( ) => {
109+ if (
110+ loading ||
111+ workspacePath === ALL_WORKSPACES ||
112+ workspaceOptions . some ( ( option ) => option . value === workspacePath )
113+ ) {
114+ return ;
115+ }
116+ setWorkspacePath ( ALL_WORKSPACES ) ;
117+ } , [ loading , workspaceOptions , workspacePath ] ) ;
118+
75119 const filtered = useMemo (
76120 ( ) =>
77121 filterArchivedSessions ( sessions , {
@@ -96,23 +140,58 @@ export default function ArchivedSessionsSettings() {
96140 ( ) => groupArchivedSessions ( filtered , unknownWorkspaceLabel ) ,
97141 [ filtered , unknownWorkspaceLabel ] ,
98142 ) ;
143+ const mutationBusy = restoringId !== undefined || deleting ;
99144
100145 const handleRestore = async ( sessionId : string ) : Promise < void > => {
101- if ( restoringId ) return ;
146+ if ( mutationBusy ) return ;
102147 setRestoringId ( sessionId ) ;
103148 setError ( undefined ) ;
104149 try {
105150 await restoreSession ( sessionId ) ;
106- setSessions ( ( current ) =>
107- current . filter ( ( session ) => session . id !== sessionId ) ,
108- ) ;
151+ setSessions ( ( current ) => removeArchivedSessions ( current , [ sessionId ] ) ) ;
109152 } catch ( restoreError ) {
110153 setError ( conciseError ( restoreError ) ) ;
111154 } finally {
112155 setRestoringId ( undefined ) ;
113156 }
114157 } ;
115158
159+ const requestWorkspaceDelete = (
160+ workspaceId : string ,
161+ path : string ,
162+ ) : void => {
163+ if ( mutationBusy ) return ;
164+ const sessionIds = archivedSessionIdsForWorkspace ( sessions , workspaceId ) ;
165+ if ( sessionIds . length === 0 ) return ;
166+ setDeleteTarget ( { kind : "workspace" , sessionIds, label : path } ) ;
167+ } ;
168+
169+ const handleDelete = async ( ) : Promise < void > => {
170+ if ( ! deleteTarget || mutationBusy ) return ;
171+ setDeleting ( true ) ;
172+ setError ( undefined ) ;
173+ try {
174+ const deletedSessionIds = await deleteArchivedSessions (
175+ deleteTarget . sessionIds ,
176+ ) ;
177+ setSessions ( ( current ) =>
178+ removeArchivedSessions ( current , deletedSessionIds ) ,
179+ ) ;
180+ setDeleteTarget ( undefined ) ;
181+ } catch ( deleteError ) {
182+ const message = conciseError ( deleteError ) ;
183+ try {
184+ const items = await listArchivedSessions ( ) ;
185+ setSessions ( items . filter ( ( session ) => session . archived ) ) ;
186+ } catch {
187+ // Preserve the original mutation error; Retry performs a full reload.
188+ }
189+ setError ( message ) ;
190+ } finally {
191+ setDeleting ( false ) ;
192+ }
193+ } ;
194+
116195 return (
117196 < section
118197 className = "settings-archived"
@@ -196,7 +275,7 @@ export default function ArchivedSessionsSettings() {
196275 ) : groups . length > 0 ? (
197276 < div className = "settings-archived-list" >
198277 { groups . map ( ( group ) => (
199- < section className = "settings-archived-group" key = { group . path } >
278+ < section className = "settings-archived-group" key = { group . workspaceId } >
200279 < div className = "settings-archived-workspace" >
201280 < Folder size = { 15 } aria-hidden = "true" />
202281 < span title = { group . path } > { group . path } </ span >
@@ -205,36 +284,70 @@ export default function ArchivedSessionsSettings() {
205284 count : group . sessions . length ,
206285 } ) }
207286 </ small >
287+ < button
288+ className = "settings-archived-delete-all"
289+ type = "button"
290+ disabled = { mutationBusy }
291+ aria-label = { t ( "settings.archivedDeleteAllLabel" , {
292+ path : group . path ,
293+ } ) }
294+ onClick = { ( ) =>
295+ requestWorkspaceDelete ( group . workspaceId , group . path )
296+ }
297+ >
298+ < Trash2 size = { 13 } />
299+ { t ( "settings.archivedDeleteAll" ) }
300+ </ button >
208301 </ div >
209302 < div className = "settings-archived-card" >
210- { group . sessions . map ( ( session ) => (
211- < div className = "settings-archived-row" key = { session . id } >
212- < div className = "settings-archived-meta" >
213- < strong title = { archivedSessionTitle ( session , untitledLabel ) } >
214- { archivedSessionTitle ( session , untitledLabel ) }
215- </ strong >
216- < span >
217- { t ( "settings.archivedAt" , {
218- time : formatArchivedTime ( session . updatedAt ) ,
219- } ) }
220- </ span >
303+ { group . sessions . map ( ( session ) => {
304+ const title = archivedSessionTitle ( session , untitledLabel ) ;
305+ return (
306+ < div className = "settings-archived-row" key = { session . id } >
307+ < div className = "settings-archived-meta" >
308+ < strong title = { title } > { title } </ strong >
309+ < span >
310+ { t ( "settings.archivedAt" , {
311+ time : formatArchivedTime ( session . updatedAt ) ,
312+ } ) }
313+ </ span >
314+ </ div >
315+ < div className = "settings-archived-actions" >
316+ < button
317+ className = "settings-archived-restore"
318+ type = "button"
319+ disabled = { mutationBusy }
320+ onClick = { ( ) => void handleRestore ( session . id ) }
321+ >
322+ < RotateCcw
323+ size = { 14 }
324+ className = {
325+ restoringId === session . id ? "spinning" : undefined
326+ }
327+ />
328+ { restoringId === session . id
329+ ? t ( "settings.archivedRestoring" )
330+ : t ( "settings.archivedRestore" ) }
331+ </ button >
332+ < button
333+ className = "settings-archived-delete"
334+ type = "button"
335+ disabled = { mutationBusy }
336+ onClick = { ( ) =>
337+ setDeleteTarget ( {
338+ kind : "session" ,
339+ sessionIds : [ session . id ] ,
340+ label : title ,
341+ } )
342+ }
343+ >
344+ < Trash2 size = { 14 } />
345+ { t ( "settings.archivedDelete" ) }
346+ </ button >
347+ </ div >
221348 </ div >
222- < button
223- className = "settings-archived-restore"
224- type = "button"
225- disabled = { restoringId !== undefined }
226- onClick = { ( ) => void handleRestore ( session . id ) }
227- >
228- < RotateCcw
229- size = { 14 }
230- className = { restoringId === session . id ? "spinning" : undefined }
231- />
232- { restoringId === session . id
233- ? t ( "settings.archivedRestoring" )
234- : t ( "settings.archivedRestore" ) }
235- </ button >
236- </ div >
237- ) ) }
349+ ) ;
350+ } ) }
238351 </ div >
239352 </ section >
240353 ) ) }
@@ -246,6 +359,63 @@ export default function ArchivedSessionsSettings() {
246359 : t ( "settings.archivedNoMatch" ) }
247360 </ div >
248361 ) }
362+
363+ { deleteTarget && (
364+ < div
365+ className = "settings-archived-confirm-backdrop"
366+ onMouseDown = { ( event ) => {
367+ if ( event . target === event . currentTarget && ! deleting ) {
368+ setDeleteTarget ( undefined ) ;
369+ }
370+ } }
371+ >
372+ < div
373+ className = "settings-archived-confirm"
374+ role = "alertdialog"
375+ aria-modal = "true"
376+ aria-labelledby = "archived-delete-confirm-title"
377+ >
378+ < span className = "settings-archived-confirm-icon" >
379+ < Trash2 size = { 19 } />
380+ </ span >
381+ < h4 id = "archived-delete-confirm-title" >
382+ { t (
383+ deleteTarget . kind === "session"
384+ ? "settings.archivedDeleteSessionTitle"
385+ : "settings.archivedDeleteWorkspaceTitle" ,
386+ ) }
387+ </ h4 >
388+ < p >
389+ { deleteTarget . kind === "session"
390+ ? t ( "settings.archivedDeleteSessionCopy" , {
391+ title : deleteTarget . label ,
392+ } )
393+ : t ( "settings.archivedDeleteWorkspaceCopy" , {
394+ path : deleteTarget . label ,
395+ count : deleteTarget . sessionIds . length ,
396+ } ) }
397+ </ p >
398+ < div className = "settings-archived-confirm-actions" >
399+ < button
400+ type = "button"
401+ autoFocus
402+ disabled = { deleting }
403+ onClick = { ( ) => setDeleteTarget ( undefined ) }
404+ >
405+ { t ( "common.cancel" ) }
406+ </ button >
407+ < button
408+ className = "danger"
409+ type = "button"
410+ disabled = { deleting }
411+ onClick = { ( ) => void handleDelete ( ) }
412+ >
413+ { deleting ? t ( "settings.archivedDeleting" ) : t ( "settings.archivedConfirmDelete" ) }
414+ </ button >
415+ </ div >
416+ </ div >
417+ </ div >
418+ ) }
249419 </ section >
250420 ) ;
251421}
0 commit comments