diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 25ed763952..4d81f88eea 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -640,6 +640,7 @@ export default function ChatView(props: ChatViewProps) { const setLogicalProjectDraftThreadId = useComposerDraftStore( (store) => store.setLogicalProjectDraftThreadId, ); + const applyComposerDraftStickyState = useComposerDraftStore((store) => store.applyStickyState); const draftThread = useComposerDraftStore((store) => routeKind === "server" ? store.getDraftSessionByRef(routeThreadRef) @@ -3055,131 +3056,41 @@ export default function ChatView(props: ChatViewProps) { ); const onImplementPlanInNewThread = useCallback(async () => { - const api = readEnvironmentApi(environmentId); - if ( - !api || - !activeThread || - !activeProject || - !activeProposedPlan || - !isServerThread || - isSendBusy || - isConnecting || - sendInFlightRef.current - ) { - return; - } - - const sendCtx = composerRef.current?.getSendContext(); - if (!sendCtx) { + if (!activeThread || !activeProject || !activeProposedPlan) { return; } - const { - selectedProvider: ctxSelectedProvider, - selectedModel: ctxSelectedModel, - selectedProviderModels: ctxSelectedProviderModels, - selectedPromptEffort: ctxSelectedPromptEffort, - selectedModelSelection: ctxSelectedModelSelection, - } = sendCtx; - const createdAt = new Date().toISOString(); + const nextDraftId = newDraftId(); const nextThreadId = newThreadId(); const planMarkdown = activeProposedPlan.planMarkdown; const implementationPrompt = buildPlanImplementationPrompt(planMarkdown); - const outgoingImplementationPrompt = formatOutgoingPrompt({ - provider: ctxSelectedProvider, - model: ctxSelectedModel, - models: ctxSelectedProviderModels, - effort: ctxSelectedPromptEffort, - text: implementationPrompt, - }); - const nextThreadTitle = truncate(buildPlanImplementationThreadTitle(planMarkdown)); - const nextThreadModelSelection: ModelSelection = ctxSelectedModelSelection; + const logicalProjectKey = deriveLogicalProjectKey(activeProject); + const activeProjectRef = scopeProjectRef(activeProject.environmentId, activeProject.id); - sendInFlightRef.current = true; - beginLocalDispatch({ preparingWorktree: false }); - const finish = () => { - sendInFlightRef.current = false; - resetLocalDispatch(); - }; + setLogicalProjectDraftThreadId(logicalProjectKey, activeProjectRef, nextDraftId, { + threadId: nextThreadId, + createdAt: new Date().toISOString(), + branch: activeThread.branch ?? null, + worktreePath: activeThread.worktreePath ?? null, + runtimeMode, + interactionMode: DEFAULT_INTERACTION_MODE, + }); + applyComposerDraftStickyState(nextDraftId); + setComposerDraftPrompt(nextDraftId, implementationPrompt); - await api.orchestration - .dispatchCommand({ - type: "thread.create", - commandId: newCommandId(), - threadId: nextThreadId, - projectId: activeProject.id, - title: nextThreadTitle, - modelSelection: nextThreadModelSelection, - runtimeMode, - interactionMode: "default", - branch: activeThread.branch, - worktreePath: activeThread.worktreePath, - createdAt, - }) - .then(() => { - return api.orchestration.dispatchCommand({ - type: "thread.turn.start", - commandId: newCommandId(), - threadId: nextThreadId, - message: { - messageId: newMessageId(), - role: "user", - text: outgoingImplementationPrompt, - attachments: [], - }, - modelSelection: ctxSelectedModelSelection, - titleSeed: nextThreadTitle, - runtimeMode, - interactionMode: "default", - sourceProposedPlan: { - threadId: activeThread.id, - planId: activeProposedPlan.id, - }, - createdAt, - }); - }) - .then(() => { - return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId)); - }) - .then(() => { - // Signal that the plan sidebar should open on the new thread. - planSidebarOpenOnNextThreadRef.current = true; - return navigate({ - to: "/$environmentId/$threadId", - params: { - environmentId: activeThread.environmentId, - threadId: nextThreadId, - }, - }); - }) - .catch(async (err: unknown) => { - await api.orchestration - .dispatchCommand({ - type: "thread.delete", - commandId: newCommandId(), - threadId: nextThreadId, - }) - .catch(() => undefined); - toastManager.add({ - type: "error", - title: "Could not start implementation thread", - description: - err instanceof Error ? err.message : "An error occurred while creating the new thread.", - }); - }) - .then(finish, finish); + await navigate({ + to: "/draft/$draftId", + params: buildDraftThreadRouteParams(nextDraftId), + }); }, [ activeProject, activeProposedPlan, activeThread, - beginLocalDispatch, - isConnecting, - isSendBusy, - isServerThread, + applyComposerDraftStickyState, navigate, - resetLocalDispatch, runtimeMode, - environmentId, + setComposerDraftPrompt, + setLogicalProjectDraftThreadId, ]); const onProviderModelSelect = useCallback(