All files / web/src/components/toys/number-line createHandleExploreConstant.ts

0% Statements 0/125
0% Branches 0/1
0% Functions 0/1
0% Lines 0/125

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                                                                                                                                                                                                           
import type { MutableRefObject } from 'react'
import { CONSTANT_IDS, EXPLORATION_DISPLAY } from './talkToNumber/explorationRegistry'
import { NARRATION_CONFIGS } from './constants/demos/narrationConfigs'

const DEMO_DISPLAY = EXPLORATION_DISPLAY

interface Narration {
  reset: () => void
  stop: () => void
  resume: (constantId: string) => void
  markTriggered: (constantId: string) => void
  isNarrating: MutableRefObject<boolean>
}

interface CreateHandleExploreConstantOptions {
  audioManager: { stop: () => void }
  voiceStateRef: MutableRefObject<string>
  pendingTourRef: MutableRefObject<string | null>
  voiceExplorationSegmentRef: MutableRefObject<number>
  narration: Narration
  cancelDemo: () => void
  exitTour: () => void
  startTour: () => void
  startDemo: (id: string) => void
  restoreDemo: (id: string, progress: number) => void
  sendSystemMessage: (msg: string, force?: boolean) => void
  onSpeedReset: () => void
  setTappedConstantId: (v: string | null) => void
  setTappedIntValue: (v: number | null) => void
  setHoveredValue: (v: number | null) => void
  tooltipHoveredRef: MutableRefObject<boolean>
}

export function createHandleExploreConstant({
  audioManager,
  voiceStateRef,
  pendingTourRef,
  voiceExplorationSegmentRef,
  narration,
  cancelDemo,
  exitTour,
  startTour,
  startDemo,
  restoreDemo,
  sendSystemMessage,
  onSpeedReset,
  setTappedConstantId,
  setTappedIntValue,
  setHoveredValue,
  tooltipHoveredRef,
}: CreateHandleExploreConstantOptions) {
  return (explorationId: string) => {
    console.log('[NumberLine] handleExploreConstant — exploration:', explorationId)
    audioManager.stop()

    // Route tour explorations to the prime tour handler
    if (!CONSTANT_IDS.has(explorationId)) {
      const onCall = voiceStateRef.current === 'active'
      if (onCall) {
        // Queue the tour to launch after the call ends — the agent will
        // explain the tour, say goodbye, and hang up on its own.
        console.log('[NumberLine] queuing tour for after hangup:', explorationId)
        pendingTourRef.current = explorationId
      } else {
        // Not on a call — start the tour immediately
        cancelDemo()
        setTappedConstantId(null)
        setTappedIntValue(null)
        setHoveredValue(null)
        tooltipHoveredRef.current = false
        startTour()
      }
      return
    }

    // Constant exploration path
    exitTour()
    narration.reset()
    // Reset speed UI to match (narration.reset() resets the ref + audio rate)
    onSpeedReset()

    const onCall = voiceStateRef.current === 'active'
    console.log(
      '[exploration] handleExploreConstant — onCall:',
      onCall,
      'explorationId:',
      explorationId
    )

    if (onCall) {
      // Start paused at progress 0 — narrator introduces first, then calls resume
      console.log('[exploration] starting PAUSED (restoreDemo + markTriggered)')
      restoreDemo(explorationId, 0)
      narration.markTriggered(explorationId) // suppress narration auto-start
    } else {
      startDemo(explorationId)
    }

    // If on a voice call, send companion instructions for the exploration
    if (onCall) {
      const cfg = NARRATION_CONFIGS[explorationId]
      const display = DEMO_DISPLAY[explorationId]
      if (cfg && display) {
        const visualDesc = display.visualDesc
          ? `\n\nWHAT THE ANIMATION SHOWS (for YOUR reference only — do NOT describe this to the child): ${display.visualDesc}`
          : ''

        // Companion rules are in the start_exploration tool output (not here)
        // so the model won't treat them as a "user message" to recite.
        // This message provides constant-specific context only.
        console.log('[exploration] sending intro system message + response.create')
        sendSystemMessage(
          `An animated exploration of ${display.symbol} (${display.name}) is ready to play.` +
            `${visualDesc}\n\n` +
            `Give the child a brief intro — why this constant is special to you personally. Match the child's energy level. ` +
            `Do NOT describe or preview what the animation will show. The visuals should be a surprise. ` +
            `Something like "Oh, I've been wanting to show you this!" or "This is one of my favorite things about living near ${display.symbol}." ` +
            `Keep it to 1-2 sentences, then call resume_exploration.`,
          true // prompt a response so the agent introduces the constant
        )
      }
      voiceExplorationSegmentRef.current = -1
    }
  }
}