Skip to content

Commit 3e02d30

Browse files
authored
feat: implement feature to mark tasks as 'Completed' after successful execution (#98)
1 parent a1aca7f commit 3e02d30

11 files changed

Lines changed: 149 additions & 5 deletions

File tree

.changeset/lovely-avocados-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hai-build-code-generator": minor
3+
---
4+
5+
Marking the Hai tasks as 'Completed' upon successful execution

src/core/controller/index.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,44 @@ export class Controller {
965965
break
966966
}
967967
break
968+
case "writeTaskStatus":
969+
// write status to the file
970+
const folder = message.folder
971+
const taskId = message?.taskId ?? ""
972+
const status = message?.status
973+
const taskIdMatch = taskId.match(/^(\d+)-US(\d+)-TASK(\d+)$/)
974+
if (!folder || !taskIdMatch || !status) {
975+
const message = `Failed to update task status. Error: Either folder, taskId or status is invalid.`
976+
vscode.window.showErrorMessage(message)
977+
} else {
978+
const [_, prdId, usId, taskId] = taskIdMatch
979+
const prdFeatureFilePath = path.join(`${folder}`, "PRD", `PRD${prdId}-feature.json`)
980+
try {
981+
const fileContent = await fs.readFile(prdFeatureFilePath, "utf-8")
982+
const prdFeatureJson = JSON.parse(fileContent)
983+
const feature = prdFeatureJson["features"].find((feature: { id: string }) => feature.id === `US${usId}`)
984+
if (feature) {
985+
const selectedTask = feature["tasks"].find((task: { id: string }) => task.id === `TASK${taskId}`)
986+
selectedTask.status = status
987+
}
988+
989+
await fs.writeFile(prdFeatureFilePath, JSON.stringify(prdFeatureJson, null, 2), "utf-8")
990+
const message = `Successfully marked task as ${status.toLowerCase()}.`
991+
vscode.window.showInformationMessage(message)
992+
await this.postMessageToWebview({
993+
type: "writeTaskStatus",
994+
writeTaskStatusResult: {
995+
success: true,
996+
message,
997+
status,
998+
},
999+
})
1000+
} catch (error) {
1001+
const message = `Failed to mark task as ${status.toLowerCase()}. Error: ${error.message}`
1002+
vscode.window.showErrorMessage(message)
1003+
}
1004+
}
1005+
break
9681006
default:
9691007
this.customWebViewMessageHandlers(message)
9701008
break
@@ -2460,7 +2498,13 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
24602498
.filter((file: string) => file.match(/\-feature.json$/))
24612499
.forEach((file: string) => {
24622500
const content = fs.readFileSync(path.join(`${url}/PRD`, file), "utf-8")
2463-
haiTaskList = [...haiTaskList, ...JSON.parse(content).features]
2501+
const prdId = file.split("-")[0].replace("PRD", "")
2502+
const parsedFeaturesList = JSON.parse(content).features
2503+
const featuresListWithPrdId = parsedFeaturesList.map((feature: any) => ({
2504+
...feature,
2505+
prdId: prdId,
2506+
}))
2507+
haiTaskList = [...haiTaskList, ...featuresListWithPrdId]
24642508
})
24652509
return haiTaskList
24662510
} catch (e) {

src/shared/ExtensionMessage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface ExtensionMessage {
5353
| "expertsUpdated"
5454
| "defaultExpertsLoaded"
5555
| "expertPrompt"
56+
| "writeTaskStatus"
5657
text?: string
5758
bool?: boolean
5859
action?:
@@ -108,6 +109,11 @@ export interface ExtensionMessage {
108109
serverName: string
109110
error?: string
110111
}
112+
writeTaskStatusResult?: {
113+
success: boolean
114+
message: string
115+
status: string
116+
}
111117
}
112118

113119
export type Invoke = "sendMessage" | "primaryButtonClick" | "secondaryButtonClick"

src/shared/WebviewMessage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface WebviewMessage {
9292
| "optionsResponse"
9393
| "requestTotalTasksSize"
9494
| "taskFeedback"
95+
| "writeTaskStatus"
9596
// | "relaunchChromeDebugMode"
9697
text?: string
9798
expert?: string
@@ -110,6 +111,9 @@ export interface WebviewMessage {
110111
isDefault?: boolean
111112
prompt?: string
112113
category?: string
114+
folder?: string
115+
taskId?: string
116+
status?: string
113117

114118
buildContextOptions?: HaiBuildContextOptions
115119
embeddingConfiguration?: EmbeddingConfiguration

webview-ui/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ const AppContent = () => {
241241
hideAnnouncement={() => {
242242
setShowAnnouncement(false)
243243
}}
244+
haiConfig={haiConfig}
244245
/>
245246
</>
246247
)}

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ interface ChatViewProps {
3939
showHistoryView: () => void
4040
onTaskSelect: (task: IHaiClineTask) => void
4141
selectedHaiTask: IHaiClineTask | null
42+
haiConfig: {
43+
[x: string]: any
44+
}
4245
}
4346

4447
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
@@ -50,6 +53,7 @@ const ChatView = ({
5053
showHistoryView,
5154
onTaskSelect,
5255
selectedHaiTask,
56+
haiConfig,
5357
}: ChatViewProps) => {
5458
const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetrySetting } = useExtensionState()
5559

@@ -90,14 +94,18 @@ const ChatView = ({
9094
const disableAutoScrollRef = useRef(false)
9195
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
9296
const [isAtBottom, setIsAtBottom] = useState(false)
97+
const [lastSuccessfullyExecutedTaskId, setLastSuccessfullyExecutedTaskId] = useState<string | undefined>(undefined)
9398

9499
// UI layout depends on the last 2 messages
95100
// (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change
96101
const lastMessage = useMemo(() => messages.at(-1), [messages])
97102
const secondLastMessage = useMemo(() => messages.at(-2), [messages])
98103

99104
useEffect(() => {
100-
selectedHaiTask && setInputValue(`Task: ${selectedHaiTask.list} ${selectedHaiTask.acceptance} ${selectedHaiTask.context}`)
105+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
106+
selectedHaiTask &&
107+
selectedHaiTask?.id &&
108+
setInputValue(`Task: ${selectedHaiTask.list} ${selectedHaiTask.acceptance} ${selectedHaiTask.context}`)
101109
}, [selectedHaiTask])
102110

103111
useDeepCompareEffect(() => {
@@ -200,6 +208,7 @@ const ChatView = ({
200208
setEnableButtons(!isPartial)
201209
setPrimaryButtonText("Start New Task")
202210
setSecondaryButtonText(undefined)
211+
setLastSuccessfullyExecutedTaskId(selectedHaiTask?.id)
203212
break
204213
case "resume_task":
205214
setTextAreaDisabled(false)
@@ -378,6 +387,9 @@ const ChatView = ({
378387
setSelectedImages([])
379388
break
380389
case "completion_result":
390+
clearSelectedHaiTaskId()
391+
startNewTask()
392+
break
381393
case "resume_completed_task":
382394
// extension waiting for feedback. but we can just present a new task button
383395
startNewTask()
@@ -467,6 +479,10 @@ const ChatView = ({
467479
textAreaRef.current?.focus()
468480
}
469481
break
482+
case "chatButtonClicked":
483+
// Starting new chat so setting the current selected HaiTask Id to null
484+
clearSelectedHaiTaskId()
485+
break
470486
}
471487
break
472488
case "selectedImages":
@@ -499,6 +515,10 @@ const ChatView = ({
499515
handleSecondaryButtonClick(message.text ?? "", message.images ?? [])
500516
break
501517
}
518+
break
519+
case "writeTaskStatus":
520+
if (message.writeTaskStatusResult?.success && message.writeTaskStatusResult?.status === "Completed")
521+
setLastSuccessfullyExecutedTaskId(undefined)
502522
}
503523
// textAreaRef.current is not explicitly required here since react guarantees that ref will be stable across re-renders, and we're not using its value but its reference.
504524
},
@@ -790,6 +810,16 @@ const ChatView = ({
790810
[expandedRows, modifiedMessages, groupedMessages.length, toggleRowExpansion, handleRowHeightChange],
791811
)
792812

813+
const clearSelectedHaiTaskId = () => {
814+
onTaskSelect({
815+
acceptance: selectedHaiTask?.acceptance ?? "",
816+
context: selectedHaiTask?.context ?? "",
817+
id: "",
818+
list: selectedHaiTask?.list ?? "",
819+
status: selectedHaiTask?.status ?? "",
820+
})
821+
}
822+
793823
return (
794824
<div
795825
style={{
@@ -910,6 +940,45 @@ const ChatView = ({
910940
/>
911941
</div>
912942
<AutoApproveMenu />
943+
{selectedHaiTask?.id &&
944+
enableButtons &&
945+
!isStreaming &&
946+
lastSuccessfullyExecutedTaskId &&
947+
selectedHaiTask?.id === lastSuccessfullyExecutedTaskId && (
948+
<div style={{ padding: "12px 15px 0px" }}>
949+
<p style={{ margin: "0px 0px 6px" }}>Do you want to mark this task as completed?</p>
950+
<div
951+
style={{
952+
display: "flex",
953+
}}>
954+
<VSCodeButton
955+
appearance="primary"
956+
style={{
957+
marginRight: "6px",
958+
flexGrow: 1,
959+
}}
960+
onClick={async () => {
961+
// write status to the file
962+
vscode.postMessage({
963+
type: "writeTaskStatus",
964+
folder: haiConfig?.folder,
965+
taskId: selectedHaiTask?.id,
966+
status: "Completed",
967+
})
968+
}}>
969+
Yes
970+
</VSCodeButton>
971+
<VSCodeButton
972+
appearance="secondary"
973+
style={{
974+
flexGrow: 1,
975+
}}
976+
onClick={() => setLastSuccessfullyExecutedTaskId(undefined)}>
977+
No
978+
</VSCodeButton>
979+
</div>
980+
</div>
981+
)}
913982
{showScrollToBottom ? (
914983
<div
915984
style={{

webview-ui/src/components/hai/HaiStoryAccordion.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface HaiStoryAccordionProps {
1111
tasks: IHaiTask[]
1212
onTaskSelect: (task: IHaiClineTask) => void
1313
id: string
14+
prdId: string
1415
storyTicketId?: string
1516
isAllExpanded: boolean
1617
}
@@ -24,6 +25,7 @@ export const HaiStoryAccordion: React.FC<HaiStoryAccordionProps> = ({
2425
description,
2526
storyTicketId,
2627
id,
28+
prdId,
2729
isAllExpanded,
2830
}) => {
2931
const [isExpanded, setIsExpanded] = useState<boolean>(true)
@@ -136,7 +138,7 @@ export const HaiStoryAccordion: React.FC<HaiStoryAccordionProps> = ({
136138
<VSCodeButton
137139
appearance="icon"
138140
title="View Story"
139-
onClick={() => onStoryClick({ id, name, description, storyTicketId, tasks })}>
141+
onClick={() => onStoryClick({ id, prdId, name, description, storyTicketId, tasks })}>
140142
<span
141143
className="codicon codicon-eye"
142144
style={{
@@ -169,6 +171,7 @@ export const HaiStoryAccordion: React.FC<HaiStoryAccordionProps> = ({
169171
name={name}
170172
description={description}
171173
task={task}
174+
prdId={prdId}
172175
onTaskSelect={onTaskSelect}
173176
onTaskClick={onTaskClick}
174177
/>

webview-ui/src/components/hai/HaiTaskComponent.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import CopyClipboard from "../common/CopyClipboard"
55

66
interface HaiTaskComponentProps {
77
id: string
8+
prdId: string
89
name: string
910
description: string
1011
task: IHaiTask
1112
onTaskClick: (task: IHaiTask) => void
1213
onTaskSelect: (task: IHaiClineTask) => void
1314
}
1415

15-
const HaiTaskComponent: React.FC<HaiTaskComponentProps> = ({ id, name, description, task, onTaskSelect, onTaskClick }) => {
16+
const HaiTaskComponent: React.FC<HaiTaskComponentProps> = ({ id, prdId, name, description, task, onTaskSelect, onTaskClick }) => {
1617
return (
1718
<div
1819
style={{
@@ -43,6 +44,7 @@ const HaiTaskComponent: React.FC<HaiTaskComponentProps> = ({ id, name, descripti
4344
textOverflow: "ellipsis",
4445
display: "flex",
4546
flexDirection: "row",
47+
alignItems: "center",
4648
}}>
4749
<span
4850
style={{
@@ -65,6 +67,12 @@ const HaiTaskComponent: React.FC<HaiTaskComponentProps> = ({ id, name, descripti
6567
/>
6668
)}{" "}
6769
</span>
70+
{task.status === "Completed" && (
71+
<span
72+
className={`codicon codicon-pass-filled`}
73+
style={{ marginLeft: "4px", color: "green", fontSize: "13px" }}
74+
/>
75+
)}
6876
</div>
6977
<span
7078
style={{
@@ -92,7 +100,7 @@ const HaiTaskComponent: React.FC<HaiTaskComponentProps> = ({ id, name, descripti
92100
onTaskSelect({
93101
context: `${name}: ${description}`,
94102
...task,
95-
id: `${id}-${task.id}`,
103+
id: `${prdId}-${id}-${task.id}`,
96104
})
97105
}}>
98106
<span className="codicon codicon-play" style={{ fontSize: 14, cursor: "pointer" }} />

webview-ui/src/components/hai/hai-tasks-list.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export function HaiTasksList({
210210
name={story.name}
211211
tasks={story.tasks}
212212
id={story.id}
213+
prdId={story.prdId}
213214
onTaskSelect={selectedHaiTask}
214215
onTaskClick={onTaskClick}
215216
onStoryClick={onStoryClick}

webview-ui/src/components/welcome/QuickActions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const createHaiTask = (title: string, description: string, context: string): IHa
1313
list: title,
1414
acceptance: description,
1515
id: "",
16+
status: "",
1617
})
1718

1819
const QuickActions = ({ onTaskSelect }: QuickActionProps) => {

0 commit comments

Comments
 (0)