diff --git a/packages/shared/src/tasks/api.ts b/packages/shared/src/tasks/api.ts index 26b4f611..8368565b 100644 --- a/packages/shared/src/tasks/api.ts +++ b/packages/shared/src/tasks/api.ts @@ -46,10 +46,10 @@ const resumeTask = defineRequest<{ taskId: string }, void>("resumeTask"); const viewInCoder = defineCommand<{ taskId: string }>("viewInCoder"); const viewLogs = defineCommand<{ taskId: string }>("viewLogs"); const downloadLogs = defineCommand<{ taskId: string }>("downloadLogs"); -const sendTaskMessage = defineCommand<{ - taskId: string; - message: string; -}>("sendTaskMessage"); +const sendTaskMessage = defineRequest< + { taskId: string; message: string }, + void +>("sendTaskMessage"); const taskUpdated = defineNotification("taskUpdated"); const tasksUpdated = defineNotification("tasksUpdated"); @@ -68,11 +68,11 @@ export const TasksApi = { deleteTask, pauseTask, resumeTask, + sendTaskMessage, // Commands viewInCoder, viewLogs, downloadLogs, - sendTaskMessage, // Notifications taskUpdated, tasksUpdated, diff --git a/packages/tasks/src/App.tsx b/packages/tasks/src/App.tsx index f0f21690..bebfe413 100644 --- a/packages/tasks/src/App.tsx +++ b/packages/tasks/src/App.tsx @@ -12,10 +12,12 @@ import { ErrorState, NoTemplateState, NotSupportedState, + TaskDetailView, TaskList, } from "./components"; import { useCollapsibleToggle } from "./hooks/useCollapsibleToggle"; import { useScrollableHeight } from "./hooks/useScrollableHeight"; +import { useSelectedTask } from "./hooks/useSelectedTask"; import { useTasksData } from "./hooks/useTasksData"; type CollapsibleElement = React.ComponentRef; @@ -34,6 +36,9 @@ export default function App() { persistUiState, } = useTasksData(); + const { selectedTask, isLoadingDetails, selectTask, deselectTask } = + useSelectedTask(tasks); + const [createRef, createOpen, setCreateOpen] = useCollapsibleToggle(initialCreateExpanded); const [historyRef, historyOpen, _setHistoryOpen] = @@ -46,7 +51,9 @@ export default function App() { const { onNotification } = useIpc(); useEffect(() => { - return onNotification(TasksApi.showCreateForm, () => setCreateOpen(true)); + return onNotification(TasksApi.showCreateForm, () => { + setCreateOpen(true); + }); }, [onNotification, setCreateOpen]); useEffect(() => { @@ -96,12 +103,15 @@ export default function App() { open={historyOpen} > - { - // Task detail view will be added in next PR - }} - /> + {selectedTask ? ( + + ) : isLoadingDetails ? ( +
+ +
+ ) : ( + + )}
diff --git a/packages/tasks/src/components/ActionMenu.tsx b/packages/tasks/src/components/ActionMenu.tsx index 548e0d25..82fa5488 100644 --- a/packages/tasks/src/components/ActionMenu.tsx +++ b/packages/tasks/src/components/ActionMenu.tsx @@ -4,7 +4,7 @@ import { VscodeIcon, VscodeProgressRing, } from "@vscode-elements/react-elements"; -import { useState, useRef, useEffect, useCallback } from "react"; +import { useState, useRef, useEffect } from "react"; interface ActionMenuAction { separator?: false; @@ -50,10 +50,10 @@ export function ActionMenu({ items }: ActionMenuProps) { const isOpen = position !== null; - const dropdownRefCallback = useCallback((node: HTMLDivElement | null) => { + const dropdownRefCallback = (node: HTMLDivElement | null) => { dropdownRef.current = node; node?.focus(); - }, []); + }; function onKeyDown(event: React.KeyboardEvent) { if (event.key === "Escape") { diff --git a/packages/tasks/src/components/AgentChatHistory.tsx b/packages/tasks/src/components/AgentChatHistory.tsx new file mode 100644 index 00000000..2a406291 --- /dev/null +++ b/packages/tasks/src/components/AgentChatHistory.tsx @@ -0,0 +1,76 @@ +import { useEffect, useRef } from "react"; + +import type { LogsStatus, TaskLogEntry } from "@repo/shared"; + +interface AgentChatHistoryProps { + logs: TaskLogEntry[]; + logsStatus: LogsStatus; + isThinking: boolean; +} + +function getEmptyMessage(logsStatus: LogsStatus): string { + switch (logsStatus) { + case "not_available": + return "Logs not available in current task state"; + case "error": + return "Failed to load logs"; + default: + return "No messages yet"; + } +} + +export function AgentChatHistory({ + logs, + logsStatus, + isThinking, +}: AgentChatHistoryProps) { + const containerRef = useRef(null); + const isAtBottomRef = useRef(true); + + const handleScroll = () => { + const container = containerRef.current; + if (!container) return; + const distanceFromBottom = + container.scrollHeight - container.scrollTop - container.clientHeight; + isAtBottomRef.current = distanceFromBottom <= 50; + }; + + useEffect(() => { + if (containerRef.current && isAtBottomRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [logs, isThinking]); + + return ( +
+
Agent chat history
+
+ {logs.length === 0 ? ( +
+ {getEmptyMessage(logsStatus)} +
+ ) : ( + logs.map((log) => ( +
+ {log.content} +
+ )) + )} + {isThinking && ( +
Thinking...
+ )} +
+
+ ); +} diff --git a/packages/tasks/src/components/CreateTaskSection.tsx b/packages/tasks/src/components/CreateTaskSection.tsx index 7e9c1f87..1bdaff29 100644 --- a/packages/tasks/src/components/CreateTaskSection.tsx +++ b/packages/tasks/src/components/CreateTaskSection.tsx @@ -9,6 +9,8 @@ import { useState } from "react"; import { useTasksApi } from "../hooks/useTasksApi"; +import { PromptField } from "./PromptField"; + import type { CreateTaskParams, TaskTemplate } from "@repo/shared"; interface CreateTaskSectionProps { @@ -40,26 +42,16 @@ export function CreateTaskSection({ templates }: CreateTaskSectionProps) { } }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); - handleSubmit(); - } - }; - return (
-
-