Manages branch list and active branch ID with useState. Exposes loadBranches, createFork, switchBranch, updateStatus, reviveBranch, and sendMessage actions via useCallback. Errors surface via toast notifications. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
162 lines
4.5 KiB
TypeScript
162 lines
4.5 KiB
TypeScript
import { useState, useCallback } from 'react'
|
|
import { branchesApi } from '@/api/branches'
|
|
import { toast } from '@/lib/toast'
|
|
import type {
|
|
BranchResponse,
|
|
ForkCreateRequest,
|
|
ForkPointResponse,
|
|
BranchSwitchResponse,
|
|
ReviveRequest,
|
|
BranchMessageRequest,
|
|
BranchMessageResponse,
|
|
} from '@/types/branching'
|
|
|
|
export interface UseBranching {
|
|
// State
|
|
branches: BranchResponse[]
|
|
activeBranchId: string | null
|
|
isLoading: boolean
|
|
isProcessing: boolean
|
|
|
|
// Actions
|
|
loadBranches: (sessionId: string) => Promise<void>
|
|
createFork: (sessionId: string, data: ForkCreateRequest) => Promise<ForkPointResponse | null>
|
|
switchBranch: (sessionId: string, branchId: string) => Promise<BranchSwitchResponse | null>
|
|
updateStatus: (sessionId: string, branchId: string, status: string, reason?: string) => Promise<void>
|
|
reviveBranch: (sessionId: string, branchId: string, data: ReviveRequest) => Promise<void>
|
|
sendMessage: (sessionId: string, branchId: string, data: BranchMessageRequest) => Promise<BranchMessageResponse | null>
|
|
}
|
|
|
|
export function useBranching(): UseBranching {
|
|
const [branches, setBranches] = useState<BranchResponse[]>([])
|
|
const [activeBranchId, setActiveBranchId] = useState<string | null>(null)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [isProcessing, setIsProcessing] = useState(false)
|
|
|
|
const loadBranches = useCallback(async (sessionId: string) => {
|
|
setIsLoading(true)
|
|
try {
|
|
const result = await branchesApi.getBranches(sessionId)
|
|
setBranches(result.branches)
|
|
setActiveBranchId(result.active_branch_id)
|
|
} catch {
|
|
toast.error('Failed to load branches')
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
const createFork = useCallback(async (
|
|
sessionId: string,
|
|
data: ForkCreateRequest
|
|
): Promise<ForkPointResponse | null> => {
|
|
setIsProcessing(true)
|
|
try {
|
|
const result = await branchesApi.createFork(sessionId, data)
|
|
// Reload branches to reflect the new fork
|
|
const updated = await branchesApi.getBranches(sessionId)
|
|
setBranches(updated.branches)
|
|
setActiveBranchId(updated.active_branch_id)
|
|
return result
|
|
} catch {
|
|
toast.error('Failed to create fork')
|
|
return null
|
|
} finally {
|
|
setIsProcessing(false)
|
|
}
|
|
}, [])
|
|
|
|
const switchBranch = useCallback(async (
|
|
sessionId: string,
|
|
branchId: string
|
|
): Promise<BranchSwitchResponse | null> => {
|
|
setIsProcessing(true)
|
|
try {
|
|
const result = await branchesApi.switchBranch(sessionId, branchId)
|
|
setActiveBranchId(result.active_branch_id)
|
|
setBranches(prev =>
|
|
prev.map(b => b.id === result.active_branch_id ? result.branch : b)
|
|
)
|
|
return result
|
|
} catch {
|
|
toast.error('Failed to switch branch')
|
|
return null
|
|
} finally {
|
|
setIsProcessing(false)
|
|
}
|
|
}, [])
|
|
|
|
const updateStatus = useCallback(async (
|
|
sessionId: string,
|
|
branchId: string,
|
|
status: string,
|
|
reason?: string
|
|
) => {
|
|
setIsProcessing(true)
|
|
try {
|
|
await branchesApi.updateBranchStatus(sessionId, branchId, status, reason)
|
|
setBranches(prev =>
|
|
prev.map(b =>
|
|
b.id === branchId
|
|
? { ...b, status: status as BranchResponse['status'], status_reason: reason ?? b.status_reason }
|
|
: b
|
|
)
|
|
)
|
|
} catch {
|
|
toast.error('Failed to update branch status')
|
|
} finally {
|
|
setIsProcessing(false)
|
|
}
|
|
}, [])
|
|
|
|
const reviveBranch = useCallback(async (
|
|
sessionId: string,
|
|
branchId: string,
|
|
data: ReviveRequest
|
|
) => {
|
|
setIsProcessing(true)
|
|
try {
|
|
await branchesApi.reviveBranch(sessionId, branchId, data)
|
|
// Reload to get updated statuses
|
|
const updated = await branchesApi.getBranches(sessionId)
|
|
setBranches(updated.branches)
|
|
setActiveBranchId(updated.active_branch_id)
|
|
toast.success('Branch revived')
|
|
} catch {
|
|
toast.error('Failed to revive branch')
|
|
} finally {
|
|
setIsProcessing(false)
|
|
}
|
|
}, [])
|
|
|
|
const sendMessage = useCallback(async (
|
|
sessionId: string,
|
|
branchId: string,
|
|
data: BranchMessageRequest
|
|
): Promise<BranchMessageResponse | null> => {
|
|
setIsProcessing(true)
|
|
try {
|
|
const result = await branchesApi.sendBranchMessage(sessionId, branchId, data)
|
|
return result
|
|
} catch {
|
|
toast.error('Failed to send message')
|
|
return null
|
|
} finally {
|
|
setIsProcessing(false)
|
|
}
|
|
}, [])
|
|
|
|
return {
|
|
branches,
|
|
activeBranchId,
|
|
isLoading,
|
|
isProcessing,
|
|
loadBranches,
|
|
createFork,
|
|
switchBranch,
|
|
updateStatus,
|
|
reviveBranch,
|
|
sendMessage,
|
|
}
|
|
}
|