Implement session outcomes, step timing, and live timer fixes

This commit is contained in:
Michael Chihlas
2026-02-11 17:52:12 -05:00
parent 2a1ed4d250
commit ca4ce7cad6
15 changed files with 574 additions and 59 deletions

View File

@@ -19,9 +19,10 @@ interface UseCustomStepFlowParams {
setPathTaken: (path: string[]) => void
setDecisions: (decisions: DecisionRecord[]) => void
setNotes: (notes: string) => void
setIsCompleting: (completing: boolean) => void
setError: (error: string | null) => void
onEnterNode: (enteredAtIso: string) => void
isCompleting: boolean
onRequestCompletion: (completionDecision: DecisionRecord, source: 'custom') => void
}
export function useCustomStepFlow({
@@ -36,9 +37,10 @@ export function useCustomStepFlow({
setPathTaken,
setDecisions,
setNotes,
setIsCompleting,
setError,
onEnterNode,
isCompleting,
onRequestCompletion,
}: UseCustomStepFlowParams) {
const navigate = useNavigate()
@@ -112,9 +114,11 @@ export function useCustomStepFlow({
// Navigate back to a previously-created custom step from the decision node
const handleNavigateToCustomStep = (customStep: CustomStep) => {
const enteredAt = new Date().toISOString()
const newPath = [...pathTaken, customStep.id]
setPathTaken(newPath)
setCurrentNodeId(customStep.id)
onEnterNode(enteredAt)
}
// Called when CustomStepModal submits - show action modal instead of inserting directly
@@ -169,6 +173,7 @@ export function useCustomStepFlow({
timestamp: new Date().toISOString()
}
const decisionTimestamp = new Date().toISOString()
const newDecision: DecisionRecord = {
node_id: customStep.id,
question: null,
@@ -176,7 +181,10 @@ export function useCustomStepFlow({
action_performed: `Custom Step: ${pendingStep.title}`,
notes: pendingStep.content.instructions || null,
automation_used: false,
timestamp: new Date().toISOString(),
timestamp: decisionTimestamp,
entered_at: decisionTimestamp,
exited_at: decisionTimestamp,
duration_seconds: 0,
attachments: []
}
@@ -188,6 +196,7 @@ export function useCustomStepFlow({
setDecisions(newDecisions)
setPathTaken(newPath)
setCurrentNodeId(customStep.id)
onEnterNode(decisionTimestamp)
await sessionsApi.update(session.id, {
path_taken: newPath,
@@ -236,9 +245,11 @@ export function useCustomStepFlow({
const handleContinueToDescendant = async () => {
if (!pendingContinuationNodeId || !session) return
const enteredAt = new Date().toISOString()
const newPath = [...pathTaken, pendingContinuationNodeId]
setPathTaken(newPath)
setCurrentNodeId(pendingContinuationNodeId)
onEnterNode(enteredAt)
setNotes('')
setPendingContinuationNodeId(null)
@@ -259,38 +270,18 @@ export function useCustomStepFlow({
const handleCustomBranchComplete = async () => {
if (!session) return
setIsCompleting(true)
setError(null)
try {
const completionDecision: DecisionRecord = {
node_id: currentNodeId,
question: null,
answer: null,
action_performed: 'Custom Branch Completed',
notes: notes || 'Issue resolved via custom troubleshooting steps',
automation_used: false,
timestamp: new Date().toISOString(),
attachments: []
}
await sessionsApi.update(session.id, {
decisions: [...decisions, completionDecision]
})
await sessionsApi.complete(session.id)
if (customSteps.length > 0) {
setShowForkModal(true)
} else {
navigate(`/sessions/${session.id}`)
}
} catch (err) {
console.error('Failed to complete session:', err)
setError('Failed to complete session. Please try again.')
} finally {
setIsCompleting(false)
const completionDecision: DecisionRecord = {
node_id: currentNodeId,
question: null,
answer: null,
action_performed: 'Custom Branch Completed',
notes: notes || 'Issue resolved via custom troubleshooting steps',
automation_used: false,
timestamp: new Date().toISOString(),
attachments: []
}
onRequestCompletion(completionDecision, 'custom')
}
// Fork tree with custom branch

View File

@@ -5,12 +5,23 @@ export function useSessionTimer(startedAt: string | undefined | null): string |
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
useEffect(() => {
// Always clear any previous interval before (re)initializing.
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
if (!startedAt) {
setElapsed(null)
return
}
const startTime = new Date(startedAt).getTime()
const parsedStartTime = new Date(startedAt).getTime()
// If the server timestamp is invalid or ahead of the local clock, fall back to "now"
// so the timer still starts ticking immediately for the user.
const startTime = Number.isNaN(parsedStartTime) || parsedStartTime > Date.now()
? Date.now()
: parsedStartTime
const tick = () => {
const diff = Math.max(0, Math.floor((Date.now() - startTime) / 1000))