124 lines · 4.5 KB · TypeScript
RawDownload
import { useCallback } from "react"; import { scopeThreadRef } from "@t3tools/client-runtime/environment";import { EnvironmentProject } from "@t3tools/client-runtime/state/shell";import { mapAtomCommandResult } from "@t3tools/client-runtime/state/runtime";import {  CommandId,  MessageId,  ThreadId,  type ModelSelection,  type ProviderInteractionMode,  type RuntimeMode,} from "@t3tools/contracts";import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git";import * as Cause from "effect/Cause";import { AsyncResult } from "effect/unstable/reactivity"; import { threadEnvironment } from "../../state/threads";import type { DraftComposerImageAttachment } from "../../lib/composerImages";import { makeTurnCommandMetadata } from "../../lib/commandMetadata";import { uuidv4 } from "../../lib/uuid";import { useAtomCommand } from "../../state/use-atom-command";import { setPendingConnectionError } from "../../state/use-remote-environment-registry";import { validateProjectThreadCreation } from "./projectThreadCreationValidation"; function deriveThreadTitleFromPrompt(value: string): string {  const trimmed = value.trim();  if (trimmed.length === 0) {    return "New thread";  }   const compact = trimmed.replace(/\s+/g, " ");  return compact.length <= 72 ? compact : `${compact.slice(0, 69).trimEnd()}...`;} export function useCreateProjectThread() {  const startTurn = useAtomCommand(threadEnvironment.startTurn, { reportFailure: false });   return useCallback(    async (input: {      readonly project: EnvironmentProject;      readonly modelSelection: ModelSelection;      readonly envMode: "local" | "worktree";      readonly branch: string | null;      readonly worktreePath: string | null;      readonly runtimeMode: RuntimeMode;      readonly interactionMode: ProviderInteractionMode;      readonly initialMessageText: string;      readonly initialAttachments: ReadonlyArray<DraftComposerImageAttachment>;    }) => {      const metadata = makeTurnCommandMetadata();      const threadId = ThreadId.make(metadata.threadId);      const initialMessageText = input.initialMessageText.trim();      const nextTitle = deriveThreadTitleFromPrompt(input.initialMessageText);       const validationError = validateProjectThreadCreation({        environmentId: input.project.environmentId,        projectId: input.project.id,        environmentMode: input.envMode,        branch: input.branch,        initialMessageText,      });      if (validationError !== null) {        setPendingConnectionError(validationError.message);        return AsyncResult.failure(Cause.fail(validationError));      }       const isWorktree = input.envMode === "worktree";      const result = await startTurn({        environmentId: input.project.environmentId,        input: {          commandId: CommandId.make(metadata.commandId),          threadId,          message: {            messageId: MessageId.make(metadata.messageId),            role: "user",            text: initialMessageText,            attachments: input.initialAttachments,          },          modelSelection: input.modelSelection,          titleSeed: nextTitle,          runtimeMode: input.runtimeMode,          interactionMode: input.interactionMode,          bootstrap: {            createThread: {              projectId: input.project.id,              title: nextTitle,              modelSelection: input.modelSelection,              runtimeMode: input.runtimeMode,              interactionMode: input.interactionMode,              branch: input.branch,              worktreePath: isWorktree ? null : input.worktreePath,              createdAt: metadata.createdAt,            },            ...(isWorktree              ? {                  prepareWorktree: {                    projectCwd: input.project.workspaceRoot,                    baseBranch: input.branch!,                    branch: buildTemporaryWorktreeBranchName(uuidv4),                  },                  runSetupScript: true,                }              : {}),          },          createdAt: metadata.createdAt,        },      });      if (AsyncResult.isFailure(result)) {        const error = Cause.squash(result.cause);        setPendingConnectionError(          error instanceof Error ? error.message : "The task could not be started.",        );        return AsyncResult.failure(result.cause);      }      setPendingConnectionError(null);       return mapAtomCommandResult(result, () =>        scopeThreadRef(input.project.environmentId, threadId),      );    },    [startTurn],  );}