diff --git a/src/app/projects/[projectId]/components/newChat/NewChat.tsx b/src/app/projects/[projectId]/components/newChat/NewChat.tsx index a81f944..3e615a2 100644 --- a/src/app/projects/[projectId]/components/newChat/NewChat.tsx +++ b/src/app/projects/[projectId]/components/newChat/NewChat.tsx @@ -120,7 +120,7 @@ export const NewChat: FC<{ {startNewChat.isPending ? ( <> - Starting... This may take a while. + Sending... This may take a while. ) : ( <> diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx index da68545..8fd7b59 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx @@ -45,7 +45,10 @@ export const SessionPageContent: FC<{ // 自動スクロール処理 useEffect(() => { - if (isRunningTask && conversations.length !== previousConversationLength) { + if ( + (isRunningTask || isPausedTask) && + conversations.length !== previousConversationLength + ) { setPreviousConversationLength(conversations.length); const scrollContainer = scrollContainerRef.current; if (scrollContainer) { @@ -55,7 +58,7 @@ export const SessionPageContent: FC<{ }); } } - }, [conversations, isRunningTask, previousConversationLength]); + }, [conversations, isRunningTask, isPausedTask, previousConversationLength]); return (
@@ -134,7 +137,7 @@ export const SessionPageContent: FC<{
-
+
{ - setMessage(""); if (sessionId !== response.sessionId) { router.push( `/projects/${projectId}/sessions/${response.sessionId}#message-${response.userMessageId}`, ); } + + setMessage(""); }, }); @@ -122,7 +123,7 @@ export const ResumeChat: FC<{ {resumeChat.isPending ? ( <> - Starting... This may take a while. + Sending... This may take a while. ) : isPausedTask || isRunningTask ? ( <> diff --git a/src/server/service/claude-code/ClaudeCodeTaskController.ts b/src/server/service/claude-code/ClaudeCodeTaskController.ts index a6951c1..1591566 100644 --- a/src/server/service/claude-code/ClaudeCodeTaskController.ts +++ b/src/server/service/claude-code/ClaudeCodeTaskController.ts @@ -1,7 +1,7 @@ import { execSync } from "node:child_process"; import { query } from "@anthropic-ai/claude-code"; -import { ulid } from "ulid"; import prexit from "prexit"; +import { ulid } from "ulid"; import { type EventBus, getEventBus } from "../events/EventBus"; import { createMessageGenerator } from "./createMessageGenerator"; import type { @@ -48,14 +48,15 @@ export class ClaudeCodeTaskController { ); if (existingTask) { - return this.continueTask(existingTask, message); + return await this.continueTask(existingTask, message); } else { return await this.startTask(currentSession, message); } } - private continueTask(task: AliveClaudeCodeTask, message: string) { + private async continueTask(task: AliveClaudeCodeTask, message: string) { task.setNextMessage(message); + await task.awaitFirstMessage(); return task; } @@ -67,8 +68,13 @@ export class ClaudeCodeTaskController { }, message: string, ) { - const { generateMessages, setNextMessage } = - createMessageGenerator(message); + const { + generateMessages, + setNextMessage, + setFirstMessagePromise, + resolveFirstMessage, + awaitFirstMessage, + } = createMessageGenerator(message); const task: PendingClaudeCodeTask = { status: "pending", @@ -78,6 +84,9 @@ export class ClaudeCodeTaskController { cwd: currentSession.cwd, generateMessages, setNextMessage, + setFirstMessagePromise, + resolveFirstMessage, + awaitFirstMessage, onMessageHandlers: [], }; @@ -120,25 +129,31 @@ export class ClaudeCodeTaskController { // 初回の system message だとまだ history ファイルが作成されていないので if ( - !resolved && (message.type === "user" || message.type === "assistant") && message.uuid !== undefined ) { - const runningTask: RunningClaudeCodeTask = { - status: "running", - id: task.id, - projectId: task.projectId, - cwd: task.cwd, - generateMessages: task.generateMessages, - setNextMessage: task.setNextMessage, - onMessageHandlers: task.onMessageHandlers, - userMessageId: message.uuid, - sessionId: message.session_id, - abortController: abortController, - }; - this.tasks.push(runningTask); - aliveTaskResolve(runningTask); - resolved = true; + if (!resolved) { + const runningTask: RunningClaudeCodeTask = { + status: "running", + id: task.id, + projectId: task.projectId, + cwd: task.cwd, + generateMessages: task.generateMessages, + setNextMessage: task.setNextMessage, + resolveFirstMessage: task.resolveFirstMessage, + setFirstMessagePromise: task.setFirstMessagePromise, + awaitFirstMessage: task.awaitFirstMessage, + onMessageHandlers: task.onMessageHandlers, + userMessageId: message.uuid, + sessionId: message.session_id, + abortController: abortController, + }; + this.tasks.push(runningTask); + aliveTaskResolve(runningTask); + resolved = true; + } + + resolveFirstMessage(); } await Promise.all( @@ -152,6 +167,8 @@ export class ClaudeCodeTaskController { ...currentTask, status: "paused", }); + resolved = true; + setFirstMessagePromise(); } } @@ -204,6 +221,9 @@ export class ClaudeCodeTaskController { cwd: task.cwd, generateMessages: task.generateMessages, setNextMessage: task.setNextMessage, + resolveFirstMessage: task.resolveFirstMessage, + setFirstMessagePromise: task.setFirstMessagePromise, + awaitFirstMessage: task.awaitFirstMessage, onMessageHandlers: task.onMessageHandlers, baseSessionId: task.baseSessionId, userMessageId: task.userMessageId, diff --git a/src/server/service/claude-code/createMessageGenerator.ts b/src/server/service/claude-code/createMessageGenerator.ts index 1e86f96..2534178 100644 --- a/src/server/service/claude-code/createMessageGenerator.ts +++ b/src/server/service/claude-code/createMessageGenerator.ts @@ -8,11 +8,11 @@ export type MessageGenerator = () => AsyncGenerator< unknown >; -const createPromise = () => { - let promiseResolve: ((value: string) => void) | undefined; +const createPromise = () => { + let promiseResolve: ((value: T) => void) | undefined; let promiseReject: ((reason?: unknown) => void) | undefined; - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { promiseResolve = resolve; promiseReject = reject; }); @@ -33,8 +33,12 @@ export const createMessageGenerator = ( ): { generateMessages: MessageGenerator; setNextMessage: (message: string) => void; + setFirstMessagePromise: () => void; + resolveFirstMessage: () => void; + awaitFirstMessage: () => Promise; } => { - let currentPromise = createPromise(); + let sendMessagePromise = createPromise(); + let receivedFirstMessagePromise = createPromise(); const createMessage = (message: string): SDKUserMessage => { return { @@ -50,19 +54,34 @@ export const createMessageGenerator = ( yield createMessage(firstMessage); while (true) { - const message = await currentPromise.promise; - currentPromise = createPromise(); + const message = await sendMessagePromise.promise; + sendMessagePromise = createPromise(); yield createMessage(message); } } const setNextMessage = (message: string) => { - currentPromise.resolve(message); + sendMessagePromise.resolve(message); + }; + + const setFirstMessagePromise = () => { + receivedFirstMessagePromise = createPromise(); + }; + + const resolveFirstMessage = () => { + receivedFirstMessagePromise.resolve(undefined); + }; + + const awaitFirstMessage = async () => { + await receivedFirstMessagePromise.promise; }; return { generateMessages, setNextMessage, + setFirstMessagePromise, + resolveFirstMessage, + awaitFirstMessage, }; }; diff --git a/src/server/service/claude-code/types.ts b/src/server/service/claude-code/types.ts index 12e2908..b43535d 100644 --- a/src/server/service/claude-code/types.ts +++ b/src/server/service/claude-code/types.ts @@ -7,6 +7,9 @@ type BaseClaudeCodeTask = { cwd: string; generateMessages: MessageGenerator; setNextMessage: (message: string) => void; + resolveFirstMessage: () => void; + setFirstMessagePromise: () => void; + awaitFirstMessage: () => Promise; onMessageHandlers: OnMessage[]; }; @@ -33,6 +36,7 @@ type CompletedClaudeCodeTask = BaseClaudeCodeTask & { sessionId: string; userMessageId: string; abortController: AbortController; + resolveFirstMessage: () => void; }; type FailedClaudeCodeTask = BaseClaudeCodeTask & {