Skip to content

Commit 3d2e749

Browse files
authored
Add favoriting to tasks (#638)
* initial work on favorites * filter by favorites * save * save * save * add translations * remove cache files * remove more files * delete * changeset * add polish language * kilocode change
1 parent ec6427e commit 3d2e749

29 files changed

+203
-21
lines changed

.changeset/lazy-parrots-cheat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": minor
3+
---
4+
5+
Added ability to favorite tasks

packages/types/src/history.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const historyItemSchema = z.object({
1616
totalCost: z.number(),
1717
size: z.number().optional(),
1818
workspace: z.string().optional(),
19+
isFavorited: z.boolean().optional(), // kilocode_change
1920
})
2021

2122
export type HistoryItem = z.infer<typeof historyItemSchema>

src/core/task/Task.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export class Task extends EventEmitter<ClineEvents> {
128128
private context: vscode.ExtensionContext // kilocode_change
129129

130130
readonly taskId: string
131+
private taskIsFavorited?: boolean // kilocode_change
131132
readonly instanceId: string
132133

133134
readonly rootTask: Task | undefined = undefined
@@ -226,6 +227,7 @@ export class Task extends EventEmitter<ClineEvents> {
226227
}
227228

228229
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
230+
this.taskIsFavorited = historyItem?.isFavorited // kilocode_change
229231
// normal use-case is usually retry similar history task with new workspace
230232
this.workspacePath = parentTask
231233
? parentTask.workspacePath

src/core/webview/ClineProvider.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
11951195
// get the task directory full path
11961196
const { taskDirPath } = await this.getTaskWithId(id)
11971197

1198+
// kilocode_change start
1199+
// Check if task is favorited
1200+
const history = this.getGlobalState("taskHistory") ?? []
1201+
const task = history.find((item) => item.id === id)
1202+
if (task?.isFavorited) {
1203+
throw new Error("Cannot delete a favorited task. Please unfavorite it first.")
1204+
}
1205+
// kilocode_change end
1206+
11981207
// remove task from stack if it's the current task
11991208
if (id === this.getCurrentCline()?.taskId) {
12001209
// if we found the taskid to delete - call finish to abort this task and allow a new task to be started,
@@ -1844,4 +1853,38 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
18441853
}
18451854
}
18461855
// end kilocode_change
1856+
1857+
// kilocode_change start
1858+
// Add new methods for favorite functionality
1859+
async toggleTaskFavorite(id: string) {
1860+
const history = this.getGlobalState("taskHistory") ?? []
1861+
const updatedHistory = history.map((item) => {
1862+
if (item.id === id) {
1863+
return { ...item, isFavorited: !item.isFavorited }
1864+
}
1865+
return item
1866+
})
1867+
await this.updateGlobalState("taskHistory", updatedHistory)
1868+
await this.postStateToWebview()
1869+
}
1870+
1871+
async getFavoriteTasks(): Promise<HistoryItem[]> {
1872+
const history = this.getGlobalState("taskHistory") ?? []
1873+
return history.filter((item) => item.isFavorited)
1874+
}
1875+
1876+
// Modify batch delete to respect favorites
1877+
async deleteMultipleTasks(taskIds: string[]) {
1878+
const history = this.getGlobalState("taskHistory") ?? []
1879+
const favoritedTaskIds = taskIds.filter((id) => history.find((item) => item.id === id)?.isFavorited)
1880+
1881+
if (favoritedTaskIds.length > 0) {
1882+
throw new Error("Cannot delete favorited tasks. Please unfavorite them first.")
1883+
}
1884+
1885+
for (const id of taskIds) {
1886+
await this.deleteTaskWithId(id)
1887+
}
1888+
}
1889+
// kilocode_change end
18471890
}

src/core/webview/webviewMessageHandler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,5 +1605,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
16051605
}
16061606
break
16071607
}
1608+
// kilocode_change start
1609+
case "toggleTaskFavorite":
1610+
if (message.text) {
1611+
await provider.toggleTaskFavorite(message.text)
1612+
}
1613+
break
1614+
// kilocode_change end
16081615
}
16091616
}

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export interface WebviewMessage {
172172
| "indexCleared"
173173
| "codebaseIndexConfig"
174174
| "telemetrySetting"
175+
| "toggleTaskFavorite" // kilocode_change
175176
text?: string
176177
disabled?: boolean
177178
askResponse?: ClineAskResponse

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,20 @@ import {
1313
} from "@/components/ui"
1414
import { vscode } from "@/utils/vscode"
1515
import { AlertDialogProps } from "@radix-ui/react-alert-dialog"
16+
import { useTaskSearch } from "./useTaskSearch" // kilocode_change
1617

1718
interface BatchDeleteTaskDialogProps extends AlertDialogProps {
1819
taskIds: string[]
1920
}
2021

2122
export const BatchDeleteTaskDialog = ({ taskIds, ...props }: BatchDeleteTaskDialogProps) => {
2223
const { t } = useAppTranslation()
24+
const { tasks } = useTaskSearch() // kilocode_change
2325
const { onOpenChange } = props
2426

27+
const favoritedTasks = tasks.filter((task) => taskIds.includes(task.id) && task.isFavorited) // kilocode_change
28+
const hasFavoritedTasks = favoritedTasks.length > 0 // kilocode_change
29+
2530
const onDelete = useCallback(() => {
2631
if (taskIds.length > 0) {
2732
vscode.postMessage({ type: "deleteMultipleTasksWithIds", ids: taskIds })
@@ -36,6 +41,13 @@ export const BatchDeleteTaskDialog = ({ taskIds, ...props }: BatchDeleteTaskDial
3641
<AlertDialogTitle>{t("history:deleteTasks")}</AlertDialogTitle>
3742
<AlertDialogDescription className="text-vscode-foreground">
3843
<div className="mb-2">{t("history:confirmDeleteTasks", { count: taskIds.length })}</div>
44+
{/* kilocode_change start */}
45+
{hasFavoritedTasks && (
46+
<div className="text-yellow-500 mb-2">
47+
{t("history:deleteTasksFavoritedWarning", { count: favoritedTasks.length })}
48+
</div>
49+
)}
50+
{/* kilocode_change end */}
3951
<div className="text-vscode-editor-foreground bg-vscode-editor-background p-2 rounded text-sm">
4052
{t("history:deleteTasksWarning")}
4153
</div>

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
Button,
1515
} from "@/components/ui"
1616
import { useAppTranslation } from "@/i18n/TranslationContext"
17-
17+
import { useTaskSearch } from "./useTaskSearch" // kilocode_change
1818
import { vscode } from "@/utils/vscode"
1919

2020
interface DeleteTaskDialogProps extends AlertDialogProps {
@@ -24,9 +24,13 @@ interface DeleteTaskDialogProps extends AlertDialogProps {
2424
export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) => {
2525
const { t } = useAppTranslation()
2626
const [isEnterPressed] = useKeyPress("Enter")
27+
const { tasks } = useTaskSearch() // kilocode_change
2728

2829
const { onOpenChange } = props
2930

31+
const task = tasks.find((t) => t.id === taskId) // kilocode_change
32+
const isFavorited = task?.isFavorited // kilocode_change
33+
3034
const onDelete = useCallback(() => {
3135
if (taskId) {
3236
vscode.postMessage({ type: "deleteTaskWithId", text: taskId })
@@ -45,7 +49,14 @@ export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) =>
4549
<AlertDialogContent onEscapeKeyDown={() => onOpenChange?.(false)}>
4650
<AlertDialogHeader>
4751
<AlertDialogTitle>{t("history:deleteTask")}</AlertDialogTitle>
48-
<AlertDialogDescription>{t("history:deleteTaskMessage")}</AlertDialogDescription>
52+
{/* kilocode_change start */}
53+
<AlertDialogDescription>
54+
{isFavorited ? (
55+
<div className="text-yellow-500 mb-2">{t("history:deleteTaskFavoritedWarning")}</div>
56+
) : null}
57+
{t("history:deleteTaskMessage")}
58+
</AlertDialogDescription>
59+
{/* kilocode_change end */}
4960
</AlertDialogHeader>
5061
<AlertDialogFooter>
5162
<AlertDialogCancel asChild>

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
3030
setLastNonRelevantSort,
3131
showAllWorkspaces,
3232
setShowAllWorkspaces,
33+
showFavoritesOnly, // kilocode_change
34+
setShowFavoritesOnly, // kilocode_change
3335
} = useTaskSearch()
3436
const { t } = useAppTranslation()
3537

@@ -166,6 +168,19 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
166168
</label>
167169
</div>
168170

171+
{/* kilocode_change start */}
172+
<div className="flex items-center gap-2">
173+
<Checkbox
174+
id="show-favorites-only"
175+
checked={showFavoritesOnly}
176+
onCheckedChange={(checked) => setShowFavoritesOnly(checked === true)}
177+
variant="description"
178+
/>
179+
<label htmlFor="show-favorites-only" className="text-vscode-foreground cursor-pointer">
180+
{t("history:showFavoritesOnly")}
181+
</label>
182+
</div>
183+
{/* kilocode_change end */}
169184
{/* Select all control in selection mode */}
170185
{isSelectionMode && tasks.length > 0 && (
171186
<div className="flex items-center py-1">

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
3333
}
3434
}
3535

36+
// kilocode_change start
37+
const handleFavoriteClick = (e: React.MouseEvent) => {
38+
e.stopPropagation()
39+
vscode.postMessage({ type: "toggleTaskFavorite", text: item.id })
40+
}
41+
// kilocode_change end
42+
3643
return (
3744
<div className="flex justify-between items-center pb-0">
3845
<div className="flex items-center flex-wrap gap-x-2 text-xs">
@@ -44,6 +51,22 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
4451
{/* Action Buttons */}
4552
{!isSelectionMode && (
4653
<div className="flex flex-row gap-0 items-center opacity-50 hover:opacity-100">
54+
{/* kilocode_change start */}
55+
{/* Favorite Star Button */}
56+
<Button
57+
variant="ghost"
58+
size="icon"
59+
title={item.isFavorited ? t("history:unfavoriteTask") : t("history:favoriteTask")}
60+
data-testid="favorite-task-button"
61+
onClick={handleFavoriteClick}
62+
className={item.isFavorited ? "text-yellow-500" : ""}>
63+
<span
64+
className={`codicon ${item.isFavorited ? "codicon-star-full" : "codicon-star-empty"}`}
65+
style={actionIconStyle}
66+
/>
67+
</Button>
68+
{/* kilocode_change end */}
69+
4770
{isCompact ? (
4871
<CopyButton itemTask={item.task} />
4972
) : (

0 commit comments

Comments
 (0)