Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/ca/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/de/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/es/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/fr/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/hi/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/it/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/ja/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/ko/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/pl/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/pt-BR/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/tr/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/vi/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/zh-CN/README.md

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions locales/zh-TW/README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 81 additions & 29 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,49 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
case "deleteTaskWithId":
this.deleteTaskWithId(message.text!)
break
case "deleteMultipleTasksWithIds": {
const ids = message.ids
if (Array.isArray(ids)) {
// Process in batches of 20 (or another reasonable number)
const batchSize = 20
const results = []

// Only log start and end of the operation
console.log(`Batch deletion started: ${ids.length} tasks total`)

for (let i = 0; i < ids.length; i += batchSize) {
const batch = ids.slice(i, i + batchSize)

const batchPromises = batch.map(async (id) => {
try {
await this.deleteTaskWithId(id)
return { id, success: true }
} catch (error) {
// Keep error logging for debugging purposes
console.log(
`Failed to delete task ${id}: ${error instanceof Error ? error.message : String(error)}`,
)
return { id, success: false }
}
})

// Process each batch in parallel but wait for completion before starting the next batch
const batchResults = await Promise.all(batchPromises)
results.push(...batchResults)

// Update the UI after each batch to show progress
await this.postStateToWebview()
}

// Log final results
const successCount = results.filter((r) => r.success).length
const failCount = results.length - successCount
console.log(
`Batch deletion completed: ${successCount}/${ids.length} tasks successful, ${failCount} tasks failed`,
)
}
break
}
case "exportTaskWithId":
this.exportTaskWithId(message.text!)
break
Expand Down Expand Up @@ -2291,40 +2334,49 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements

// this function deletes a task from task hidtory, and deletes it's checkpoints and delete the task folder
async deleteTaskWithId(id: string) {
// get the task directory full path
const { taskDirPath } = await this.getTaskWithId(id)

// remove task from stack if it's the current task
if (id === this.getCurrentCline()?.taskId) {
// if we found the taskid to delete - call finish to abort this task and allow a new task to be started,
// if we are deleting a subtask and parent task is still waiting for subtask to finish - it allows the parent to resume (this case should neve exist)
await this.finishSubTask(`Task failure: It was stopped and deleted by the user.`)
}
try {
// Try to get the task directory full path
const { taskDirPath } = await this.getTaskWithId(id)

// remove task from stack if it's the current task
if (id === this.getCurrentCline()?.taskId) {
// if we found the taskid to delete - call finish to abort this task and allow a new task to be started,
// if we are deleting a subtask and parent task is still waiting for subtask to finish - it allows the parent to resume (this case should neve exist)
await this.finishSubTask(`Task failure: It was stopped and deleted by the user.`)
}

// delete task from the task history state
await this.deleteTaskFromState(id)
// delete task from the task history state
await this.deleteTaskFromState(id)

// Delete associated shadow repository or branch.
// TODO: Store `workspaceDir` in the `HistoryItem` object.
const globalStorageDir = this.contextProxy.globalStorageUri.fsPath
const workspaceDir = this.cwd
// Delete associated shadow repository or branch.
// TODO: Store `workspaceDir` in the `HistoryItem` object.
const globalStorageDir = this.contextProxy.globalStorageUri.fsPath
const workspaceDir = this.cwd

try {
await ShadowCheckpointService.deleteTask({ taskId: id, globalStorageDir, workspaceDir })
} catch (error) {
console.error(
`[deleteTaskWithId${id}] failed to delete associated shadow repository or branch: ${error instanceof Error ? error.message : String(error)}`,
)
}
try {
await ShadowCheckpointService.deleteTask({ taskId: id, globalStorageDir, workspaceDir })
} catch (error) {
console.error(
`[deleteTaskWithId${id}] failed to delete associated shadow repository or branch: ${error instanceof Error ? error.message : String(error)}`,
)
}

// delete the entire task directory including checkpoints and all content
try {
await fs.rm(taskDirPath, { recursive: true, force: true })
console.log(`[deleteTaskWithId${id}] removed task directory`)
// delete the entire task directory including checkpoints and all content
try {
await fs.rm(taskDirPath, { recursive: true, force: true })
console.log(`[deleteTaskWithId${id}] removed task directory`)
} catch (error) {
console.error(
`[deleteTaskWithId${id}] failed to remove task directory: ${error instanceof Error ? error.message : String(error)}`,
)
}
} catch (error) {
console.error(
`[deleteTaskWithId${id}] failed to remove task directory: ${error instanceof Error ? error.message : String(error)}`,
)
// If task is not found, just remove it from state
if (error instanceof Error && error.message === "Task not found") {
await this.deleteTaskFromState(id)
return
}
throw error
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type AudioType = "notification" | "celebration" | "progress_loop"
export interface WebviewMessage {
type:
| "apiConfiguration"
| "deleteMultipleTasksWithIds"
| "currentApiConfigName"
| "saveApiConfiguration"
| "upsertApiConfiguration"
Expand Down Expand Up @@ -136,6 +137,7 @@ export interface WebviewMessage {
payload?: WebViewMessagePayload
source?: "global" | "project"
requestId?: string
ids?: string[]
}

export const checkoutDiffPayloadSchema = z.object({
Expand Down
58 changes: 58 additions & 0 deletions webview-ui/src/components/history/BatchDeleteTaskDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useCallback } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Button,
} from "@/components/ui"
import { vscode } from "@/utils/vscode"
import { AlertDialogProps } from "@radix-ui/react-alert-dialog"

interface BatchDeleteTaskDialogProps extends AlertDialogProps {
taskIds: string[]
}

export const BatchDeleteTaskDialog = ({ taskIds, ...props }: BatchDeleteTaskDialogProps) => {
const { t } = useAppTranslation()
const { onOpenChange } = props

const onDelete = useCallback(() => {
if (taskIds.length > 0) {
vscode.postMessage({ type: "deleteMultipleTasksWithIds", ids: taskIds })
onOpenChange?.(false)
}
}, [taskIds, onOpenChange])

return (
<AlertDialog {...props}>
<AlertDialogContent className="max-w-md">
<AlertDialogHeader>
<AlertDialogTitle>{t("history:deleteTasks")}</AlertDialogTitle>
<AlertDialogDescription className="text-vscode-foreground">
<div className="mb-2">{t("history:confirmDeleteTasks", { count: taskIds.length })}</div>
<div className="text-vscode-editor-foreground bg-vscode-editor-background p-2 rounded text-sm">
{t("history:deleteTasksWarning")}
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="secondary">{t("history:cancel")}</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDelete}>
<span className="codicon codicon-trash mr-1"></span>
{t("history:deleteItems", { count: taskIds.length })}
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
Loading
Loading