feat: add useBranching hook for branch state management
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>
This commit is contained in:
161
frontend/src/hooks/useBranching.ts
Normal file
161
frontend/src/hooks/useBranching.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user