mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2026-01-06 15:14:21 +01:00
feat: abort running task
This commit is contained in:
@@ -119,7 +119,7 @@ export const NewChat: FC<{
|
||||
{startNewChat.isPending ? (
|
||||
<>
|
||||
<LoaderIcon className="w-4 h-4 animate-spin" />
|
||||
Starting...
|
||||
Starting... This may take a while.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowLeftIcon, LoaderIcon } from "lucide-react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { ArrowLeftIcon, LoaderIcon, XIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type { FC } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { honoClient } from "../../../../../../lib/api/client";
|
||||
import { firstCommandToTitle } from "../../../services/firstCommandToTitle";
|
||||
import { useIsResummingTask } from "../hooks/useIsResummingTask";
|
||||
import { useSession } from "../hooks/useSession";
|
||||
@@ -21,6 +23,20 @@ export const SessionPageContent: FC<{
|
||||
sessionId,
|
||||
);
|
||||
|
||||
const abortTask = useMutation({
|
||||
mutationFn: async (sessionId: string) => {
|
||||
const response = await honoClient.api.tasks.abort.$post({
|
||||
json: { sessionId },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
|
||||
const { isResummingTask } = useIsResummingTask(sessionId);
|
||||
|
||||
const [previouConversationLength, setPreviouConversationLength] = useState(0);
|
||||
@@ -77,6 +93,16 @@ export const SessionPageContent: FC<{
|
||||
Conversation is being resumed...
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
abortTask.mutate(sessionId);
|
||||
}}
|
||||
>
|
||||
<XIcon className="w-4 h-4" />
|
||||
Abort
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
AlertCircleIcon,
|
||||
LoaderIcon,
|
||||
@@ -29,6 +29,7 @@ export const ResumeChat: FC<{
|
||||
}> = ({ projectId, sessionId }) => {
|
||||
const router = useRouter();
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const resumeChat = useMutation({
|
||||
mutationFn: async (options: { message: string }) => {
|
||||
@@ -47,6 +48,7 @@ export const ResumeChat: FC<{
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
setMessage("");
|
||||
queryClient.invalidateQueries({ queryKey: ["runningTasks"] });
|
||||
router.push(
|
||||
`/projects/${projectId}/sessions/${response.nextSessionId}#message-${response.userMessageId}`,
|
||||
);
|
||||
|
||||
@@ -228,6 +228,16 @@ export const routes = (app: HonoAppType) => {
|
||||
return c.json({ runningTasks: taskController.runningTasks });
|
||||
})
|
||||
|
||||
.post(
|
||||
"/tasks/abort",
|
||||
zValidator("json", z.object({ sessionId: z.string() })),
|
||||
async (c) => {
|
||||
const { sessionId } = c.req.valid("json");
|
||||
taskController.abortTask(sessionId);
|
||||
return c.json({ message: "Task aborted" });
|
||||
},
|
||||
)
|
||||
|
||||
.get("/events/state_changes", async (c) => {
|
||||
return streamSSE(
|
||||
c,
|
||||
|
||||
@@ -21,6 +21,7 @@ type RunningClaudeCodeTask = BaseClaudeCodeTask & {
|
||||
status: "running";
|
||||
nextSessionId: string;
|
||||
userMessageId: string;
|
||||
abortController: AbortController;
|
||||
};
|
||||
|
||||
type CompletedClaudeCodeTask = BaseClaudeCodeTask & {
|
||||
@@ -104,6 +105,8 @@ export class ClaudeCodeTaskController {
|
||||
|
||||
const handleTask = async () => {
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
|
||||
for await (const message of query({
|
||||
prompt: task.message,
|
||||
options: {
|
||||
@@ -111,6 +114,7 @@ export class ClaudeCodeTaskController {
|
||||
cwd: task.cwd,
|
||||
pathToClaudeCodeExecutable: this.pathToClaudeCodeExecutable,
|
||||
permissionMode: "bypassPermissions",
|
||||
abortController,
|
||||
},
|
||||
})) {
|
||||
// 初回の sysmte message だとまだ history ファイルが作成されていないので
|
||||
@@ -124,6 +128,7 @@ export class ClaudeCodeTaskController {
|
||||
status: "running",
|
||||
nextSessionId: message.session_id,
|
||||
userMessageId: message.uuid,
|
||||
abortController,
|
||||
};
|
||||
this.updateExistingTask(runningTask);
|
||||
runningTaskResolve(runningTask);
|
||||
@@ -167,4 +172,26 @@ export class ClaudeCodeTaskController {
|
||||
|
||||
return runningTaskPromise;
|
||||
}
|
||||
|
||||
public abortTask(sessionId: string) {
|
||||
const task = this.tasks
|
||||
.filter((task) => task.status === "running")
|
||||
.find((task) => task.nextSessionId === sessionId);
|
||||
if (!task) {
|
||||
throw new Error("Running Task not found");
|
||||
}
|
||||
|
||||
task.abortController.abort();
|
||||
this.updateExistingTask({
|
||||
id: task.id,
|
||||
status: "failed",
|
||||
cwd: task.cwd,
|
||||
message: task.message,
|
||||
onMessageHandlers: task.onMessageHandlers,
|
||||
projectId: task.projectId,
|
||||
nextSessionId: task.nextSessionId,
|
||||
sessionId: task.sessionId,
|
||||
userMessageId: task.userMessageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user