import { createEffect, createMemo, createSignal, onCleanup } from "solid-js" import path from "path" import { useTuiPaths } from "../../context/runtime" import { errorMessage } from "../../util/error" import { useDialog } from "../../ui/dialog" import { useSDK } from "../../context/sdk" import { useSync } from "../../context/sync" import { useToast } from "../../ui/toast" import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session" import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes" import { useHomeSessionDestination } from "../../routes/home/session-destination" import { useProject } from "../../context/project" function moveReminderText(directory: string) { return `The user has changed the current working directory to "${directory}". This is still the same project but at a possibly new location; take this into account when working with any files from now on.` } export function usePromptMove(input: { projectID: () => string | undefined; sessionID: () => string | undefined }) { const dialog = useDialog() const sdk = useSDK() const sync = useSync() const toast = useToast() const homeDestination = useHomeSessionDestination() const project = useProject() const paths = useTuiPaths() const [creating, setCreating] = createSignal(false) const [creatingDots, setCreatingDots] = createSignal(3) const [progress, setProgress] = createSignal() async function create(context?: string) { const projectID = input.projectID() if (!projectID) return setCreating(true) setProgress("Creating copy") try { const generated = await sdk.client.experimental.projectCopy.generateName( { projectID, context }, { throwOnError: true }, ) const result = await sdk.client.v2.projectCopy.create( { projectID, location: { directory: sdk.directory }, strategy: "git_worktree", directory: path.join(paths.worktree, projectID.slice(0, 6)), name: generated.data.name, }, { throwOnError: true }, ) const directory = result.data?.directory if (!directory) throw new Error("No project copy directory returned") // Call a location-based route to make sure it's bootstrapped // before moving on await sdk.client.path.get({ directory }, { throwOnError: true }) setProgress("Creating session") return directory } catch (err) { homeDestination?.clear() setProgress(undefined) setCreating(false) toast.show({ title: "Creating workspace failed", message: errorMessage(err), variant: "error" }) return } } function open() { const projectID = input.projectID() if (!projectID) return const sessionID = input.sessionID() const session = sessionID ? sync.session.get(sessionID) : undefined dialog.replace(() => ( homeDestination?.setDestination(selection)} onSelect={(selection) => { const sessionID = input.sessionID() if (!sessionID) { homeDestination?.setDestination(selection) dialog.clear() return } void moveExistingSession(sessionID, selection) }} /> )) } function sessionContext(sessionID: string) { const session = sync.session.get(sessionID) const messages = (sync.data.message[sessionID] ?? []) .slice(-6) .map((message) => [ message.role + ":", ...(sync.data.part[message.id] ?? []).flatMap((part) => (part.type === "text" ? [part.text] : [])), ].join(" "), ) return [session?.title, ...messages].filter(Boolean).join("\n") || undefined } async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) { const session = sync.session.get(sessionID) const status = await sdk.client.vcs.status({ directory: session?.directory }).catch(() => undefined) const choice = status?.data?.length ? await DialogWorkspaceFileChanges.show(dialog, status.data) : "no" if (!choice) return dialog.clear() const directory = selection.type === "new" ? await create(sessionContext(sessionID)) : selection.directory if (!directory) { setProgress(undefined) dialog.clear() return } setProgress("Moving session") try { await sdk.client.experimental.controlPlane.moveSession( { sessionID, destination: { directory }, moveChanges: choice === "yes", }, { throwOnError: true }, ) await sdk.client.session .promptAsync({ sessionID, directory, noReply: true, parts: [ { type: "text", text: moveReminderText(directory), synthetic: true, }, ], }) .catch(() => undefined) dialog.clear() } catch (error) { toast.error(error) dialog.clear() } finally { setProgress(undefined) setCreating(false) } } const pending = createMemo(() => Boolean(homeDestination?.destination())) const pendingNew = createMemo(() => homeDestination?.destination()?.type === "new") async function getDirectory(context?: string) { const value = homeDestination?.destination() if (!value) return if (value.type === "directory") { return value.directory } return await create(context) } function startSubmit() { if (progress()) setProgress("Submitting prompt") } function finishSubmit() { homeDestination?.clear() setProgress(undefined) setCreating(false) } createEffect(() => { if (!creating()) { setCreatingDots(3) return } const timer = setInterval(() => setCreatingDots((dots) => (dots % 3) + 1), 1000) onCleanup(() => clearInterval(timer)) }) return { creating, creatingDots, finishSubmit, getDirectory, open, pending, pendingNew, progress, startSubmit, } }