feat(core): moving sessions (#30640)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useToast } from "@tui/ui/toast"
|
||||
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
|
||||
import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes"
|
||||
import { useHomeSessionDestination } from "../../routes/home/session-destination"
|
||||
|
||||
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 [creating, setCreating] = createSignal(false)
|
||||
const [creatingDots, setCreatingDots] = createSignal(3)
|
||||
const [progress, setProgress] = createSignal<string>()
|
||||
|
||||
async function create(context?: string) {
|
||||
const projectID = input.projectID()
|
||||
if (!projectID) return
|
||||
setCreating(true)
|
||||
setProgress("Creating copy")
|
||||
try {
|
||||
const result = await sdk.client.experimental.projectCopy.create(
|
||||
{
|
||||
projectID,
|
||||
strategy: "git_worktree",
|
||||
directory: path.join(Global.Path.data, "worktree", projectID.slice(0, 6)),
|
||||
context,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
const directory = result.data?.directory
|
||||
if (!directory) throw new Error("No project copy directory returned")
|
||||
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
|
||||
dialog.replace(() => (
|
||||
<DialogMoveSession
|
||||
projectID={projectID}
|
||||
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")
|
||||
await sdk.client.experimental.controlPlane
|
||||
.moveSession(
|
||||
{
|
||||
sessionID,
|
||||
destination: { directory },
|
||||
moveChanges: choice === "yes",
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
.then(() => 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user