mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import figures from 'figures'
|
|
import * as React from 'react'
|
|
import { useState } from 'react'
|
|
import type { Root } from '@anthropic/ink'
|
|
import { Box, Text, useAnimationFrame } from '@anthropic/ink'
|
|
import { AppStateProvider } from '../state/AppState.js'
|
|
import {
|
|
checkOutTeleportedSessionBranch,
|
|
processMessagesForTeleportResume,
|
|
type TeleportProgressStep,
|
|
type TeleportResult,
|
|
teleportResumeCodeSession,
|
|
} from '../utils/teleport.js'
|
|
|
|
type Props = {
|
|
currentStep: TeleportProgressStep
|
|
sessionId?: string
|
|
}
|
|
|
|
const SPINNER_FRAMES = ['◐', '◓', '◑', '◒']
|
|
|
|
const STEPS: { key: TeleportProgressStep; label: string }[] = [
|
|
{ key: 'validating', label: 'Validating session' },
|
|
{ key: 'fetching_logs', label: 'Fetching session logs' },
|
|
{ key: 'fetching_branch', label: 'Getting branch info' },
|
|
{ key: 'checking_out', label: 'Checking out branch' },
|
|
]
|
|
|
|
export function TeleportProgress({
|
|
currentStep,
|
|
sessionId,
|
|
}: Props): React.ReactNode {
|
|
const [ref, time] = useAnimationFrame(100)
|
|
const frame = Math.floor(time / 100) % SPINNER_FRAMES.length
|
|
|
|
const currentStepIndex = STEPS.findIndex(s => s.key === currentStep)
|
|
|
|
return (
|
|
<Box ref={ref} flexDirection="column" paddingX={1} paddingY={1}>
|
|
<Box marginBottom={1}>
|
|
<Text bold color="claude">
|
|
{SPINNER_FRAMES[frame]} Teleporting session…
|
|
</Text>
|
|
</Box>
|
|
|
|
{sessionId && (
|
|
<Box marginBottom={1}>
|
|
<Text dimColor>{sessionId}</Text>
|
|
</Box>
|
|
)}
|
|
|
|
<Box flexDirection="column" marginLeft={2}>
|
|
{STEPS.map((step, index) => {
|
|
const isComplete = index < currentStepIndex
|
|
const isCurrent = index === currentStepIndex
|
|
const isPending = index > currentStepIndex
|
|
|
|
let icon: string
|
|
let color: string | undefined
|
|
|
|
if (isComplete) {
|
|
icon = figures.tick
|
|
color = 'green'
|
|
} else if (isCurrent) {
|
|
icon = SPINNER_FRAMES[frame]!
|
|
color = 'claude'
|
|
} else {
|
|
icon = figures.circle
|
|
color = undefined
|
|
}
|
|
|
|
return (
|
|
<Box key={step.key} flexDirection="row">
|
|
<Box width={2}>
|
|
<Text color={color as never} dimColor={isPending}>
|
|
{icon}
|
|
</Text>
|
|
</Box>
|
|
<Text dimColor={isPending} bold={isCurrent}>
|
|
{step.label}
|
|
</Text>
|
|
</Box>
|
|
)
|
|
})}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Teleports to a remote session with progress UI rendered into the existing root.
|
|
* Fetches the session, checks out the branch, and returns the result.
|
|
*/
|
|
export async function teleportWithProgress(
|
|
root: Root,
|
|
sessionId: string,
|
|
): Promise<TeleportResult> {
|
|
// Capture the setState function from the rendered component
|
|
let setStep: (step: TeleportProgressStep) => void = () => {}
|
|
|
|
function TeleportProgressWrapper(): React.ReactNode {
|
|
const [step, _setStep] = useState<TeleportProgressStep>('validating')
|
|
setStep = _setStep
|
|
return <TeleportProgress currentStep={step} sessionId={sessionId} />
|
|
}
|
|
|
|
root.render(
|
|
<AppStateProvider>
|
|
<TeleportProgressWrapper />
|
|
</AppStateProvider>,
|
|
)
|
|
|
|
const result = await teleportResumeCodeSession(sessionId, setStep)
|
|
setStep('checking_out')
|
|
const { branchName, branchError } = await checkOutTeleportedSessionBranch(
|
|
result.branch,
|
|
)
|
|
return {
|
|
messages: processMessagesForTeleportResume(result.log, branchError),
|
|
branchName,
|
|
}
|
|
}
|