Skip to content

Commit 675b5e1

Browse files
feat: support batch history deletion (RooCodeInc#2918)
* feat: support batch history deletion Single history item deletion is too small and "Delete All History" is too large on granuality. For long term Cline users and Cline devs/testers it would be convenient to batch deletion these history items. On `HistoryView` page, this commit add: 1. `CheckBox` for every history item 2. `Select All` & `Deselect All` buttons (work with search filter) 3. `Delete Selected` button for batch deletion (only appears when item(s) is/are selected) History task's `onclick` is pointed to `showTaskWithId` for quick showing this task. * fix failed ellipsis checking * HistoryView: remove unused import * fix: style improvement 1. Selection buttons moved up to align with 'Done' button 2. Delete All History button hidden when any items are selected 3. Checkboxes moved below and align with message text * restore unnecessary changes * Improve styles and Delete selected button * changeset --------- Co-authored-by: frostbournesb <[email protected]>
1 parent 2fe2405 commit 675b5e1

File tree

5 files changed

+103
-19
lines changed

5 files changed

+103
-19
lines changed

.changeset/warm-gorillas-beam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Batch selection and deletion of tasks in history

src/core/controller/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@ export class Controller {
335335
case "showTaskWithId":
336336
this.showTaskWithId(message.text!)
337337
break
338-
case "deleteTaskWithId":
339-
this.deleteTaskWithId(message.text!)
338+
case "deleteTasksWithIds":
339+
this.deleteTasksWithIds(message.text!)
340340
break
341341
case "exportTaskWithId":
342342
this.exportTaskWithId(message.text!)
@@ -1712,6 +1712,12 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
17121712
this.refreshTotalTasksSize()
17131713
}
17141714

1715+
async deleteTasksWithIds(ids: string) {
1716+
for (const id of JSON.parse(ids) as string[]) {
1717+
await this.deleteTaskWithId(id)
1718+
}
1719+
}
1720+
17151721
async deleteTaskFromState(id: string) {
17161722
// Remove the task from history
17171723
const taskHistory = ((await getGlobalState(this.context, "taskHistory")) as HistoryItem[] | undefined) || []

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface WebviewMessage {
1818
| "selectImages"
1919
| "exportCurrentTask"
2020
| "showTaskWithId"
21-
| "deleteTaskWithId"
21+
| "deleteTasksWithIds"
2222
| "exportTaskWithId"
2323
| "resetState"
2424
| "requestOllamaModels"

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ const DeleteButton: React.FC<{
669669
}> = ({ taskSize, taskId }) => (
670670
<VSCodeButton
671671
appearance="icon"
672-
onClick={() => vscode.postMessage({ type: "deleteTaskWithId", text: taskId })}
672+
onClick={() => vscode.postMessage({ type: "deleteTasksWithIds", text: JSON.stringify([taskId]) })}
673673
style={{ padding: "0px 0px" }}>
674674
<div
675675
style={{

webview-ui/src/components/history/HistoryView.tsx

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { VSCodeButton, VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react"
1+
import { VSCodeButton, VSCodeTextField, VSCodeRadioGroup, VSCodeRadio, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
22
import { useExtensionState } from "@/context/ExtensionStateContext"
33
import { vscode } from "@/utils/vscode"
44
import { Virtuoso } from "react-virtuoso"
@@ -22,6 +22,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
2222
const [sortOption, setSortOption] = useState<SortOption>("newest")
2323
const [lastNonRelevantSort, setLastNonRelevantSort] = useState<SortOption | null>("newest")
2424
const [deleteAllDisabled, setDeleteAllDisabled] = useState(false)
25+
const [selectedItems, setSelectedItems] = useState<string[]>([])
2526

2627
const handleMessage = useCallback((event: MessageEvent<ExtensionMessage>) => {
2728
if (event.data.type === "relinquishControl") {
@@ -45,12 +46,29 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
4546
}
4647
}, [searchQuery, sortOption, lastNonRelevantSort])
4748

48-
const handleHistorySelect = useCallback((id: string) => {
49+
const handleShowTaskWithId = useCallback((id: string) => {
4950
vscode.postMessage({ type: "showTaskWithId", text: id })
5051
}, [])
5152

53+
const handleHistorySelect = useCallback((itemId: string, checked: boolean) => {
54+
setSelectedItems((prev) => {
55+
if (checked) {
56+
return [...prev, itemId]
57+
} else {
58+
return prev.filter((id) => id !== itemId)
59+
}
60+
})
61+
}, [])
62+
5263
const handleDeleteHistoryItem = useCallback((id: string) => {
53-
vscode.postMessage({ type: "deleteTaskWithId", text: id })
64+
vscode.postMessage({ type: "deleteTasksWithIds", text: JSON.stringify([id]) })
65+
}, [])
66+
67+
const handleDeleteSelectedHistoryItems = useCallback((ids: string[]) => {
68+
if (ids.length > 0) {
69+
vscode.postMessage({ type: "deleteTasksWithIds", text: JSON.stringify(ids) })
70+
setSelectedItems([])
71+
}
5472
}, [])
5573

5674
const formatDate = useCallback((timestamp: number) => {
@@ -113,6 +131,24 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
113131
return results
114132
}, [presentableTasks, searchQuery, fuse, sortOption])
115133

134+
// Calculate total size of selected items
135+
const selectedItemsSize = useMemo(() => {
136+
if (selectedItems.length === 0) return 0
137+
138+
return taskHistory.filter((item) => selectedItems.includes(item.id)).reduce((total, item) => total + (item.size || 0), 0)
139+
}, [selectedItems, taskHistory])
140+
141+
const handleBatchHistorySelect = useCallback(
142+
(selectAll: boolean) => {
143+
if (selectAll) {
144+
setSelectedItems(taskHistorySearchResults.map((item) => item.id))
145+
} else {
146+
setSelectedItems([])
147+
}
148+
},
149+
[taskHistorySearchResults],
150+
)
151+
116152
return (
117153
<>
118154
<style>
@@ -216,6 +252,20 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
216252
Most Relevant
217253
</VSCodeRadio>
218254
</VSCodeRadioGroup>
255+
<div style={{ display: "flex", justifyContent: "flex-end", gap: "10px" }}>
256+
<VSCodeButton
257+
onClick={() => {
258+
handleBatchHistorySelect(true)
259+
}}>
260+
Select All
261+
</VSCodeButton>
262+
<VSCodeButton
263+
onClick={() => {
264+
handleBatchHistorySelect(false)
265+
}}>
266+
Select None
267+
</VSCodeButton>
268+
</div>
219269
</div>
220270
</div>
221271
<div style={{ flexGrow: 1, overflowY: "auto", margin: 0 }}>
@@ -249,16 +299,28 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
249299
cursor: "pointer",
250300
borderBottom:
251301
index < taskHistory.length - 1 ? "1px solid var(--vscode-panel-border)" : "none",
252-
}}
253-
onClick={() => handleHistorySelect(item.id)}>
302+
display: "flex",
303+
}}>
304+
<VSCodeCheckbox
305+
className="pl-3 pr-1 py-auto"
306+
checked={selectedItems.includes(item.id)}
307+
onClick={(e) => {
308+
const checked = (e.target as HTMLInputElement).checked
309+
handleHistorySelect(item.id, checked)
310+
e.stopPropagation()
311+
}}
312+
/>
254313
<div
255314
style={{
256315
display: "flex",
257316
flexDirection: "column",
258317
gap: "8px",
259318
padding: "12px 20px",
319+
paddingLeft: "16px",
260320
position: "relative",
261-
}}>
321+
flexGrow: 1,
322+
}}
323+
onClick={() => handleShowTaskWithId(item.id)}>
262324
<div
263325
style={{
264326
display: "flex",
@@ -468,15 +530,26 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
468530
padding: "10px 10px",
469531
borderTop: "1px solid var(--vscode-panel-border)",
470532
}}>
471-
<DangerButton
472-
style={{ width: "100%" }}
473-
disabled={deleteAllDisabled || taskHistory.length === 0}
474-
onClick={() => {
475-
setDeleteAllDisabled(true)
476-
vscode.postMessage({ type: "clearAllTaskHistory" })
477-
}}>
478-
Delete All History{totalTasksSize !== null ? ` (${formatSize(totalTasksSize)})` : ""}
479-
</DangerButton>
533+
{selectedItems.length > 0 ? (
534+
<DangerButton
535+
style={{ width: "100%" }}
536+
onClick={() => {
537+
handleDeleteSelectedHistoryItems(selectedItems)
538+
}}>
539+
Delete {selectedItems.length > 1 ? selectedItems.length : ""} Selected
540+
{selectedItemsSize > 0 ? ` (${formatSize(selectedItemsSize)})` : ""}
541+
</DangerButton>
542+
) : (
543+
<DangerButton
544+
style={{ width: "100%" }}
545+
disabled={deleteAllDisabled || taskHistory.length === 0}
546+
onClick={() => {
547+
setDeleteAllDisabled(true)
548+
vscode.postMessage({ type: "clearAllTaskHistory" })
549+
}}>
550+
Delete All History{totalTasksSize !== null ? ` (${formatSize(totalTasksSize)})` : ""}
551+
</DangerButton>
552+
)}
480553
</div>
481554
</div>
482555
</>

0 commit comments

Comments
 (0)