/ hooks / useSkillImprovementSurvey.ts
useSkillImprovementSurvey.ts
  1  import { useCallback, useRef, useState } from 'react'
  2  import type { FeedbackSurveyResponse } from '../components/FeedbackSurvey/utils.js'
  3  import {
  4    type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  5    type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
  6    logEvent,
  7  } from '../services/analytics/index.js'
  8  import { useAppState, useSetAppState } from '../state/AppState.js'
  9  import type { Message } from '../types/message.js'
 10  import type { SkillUpdate } from '../utils/hooks/skillImprovement.js'
 11  import { applySkillImprovement } from '../utils/hooks/skillImprovement.js'
 12  import { createSystemMessage } from '../utils/messages.js'
 13  
 14  type SkillImprovementSuggestion = {
 15    skillName: string
 16    updates: SkillUpdate[]
 17  }
 18  
 19  type SetMessages = (fn: (prev: Message[]) => Message[]) => void
 20  
 21  export function useSkillImprovementSurvey(setMessages: SetMessages): {
 22    isOpen: boolean
 23    suggestion: SkillImprovementSuggestion | null
 24    handleSelect: (selected: FeedbackSurveyResponse) => void
 25  } {
 26    const suggestion = useAppState(s => s.skillImprovement.suggestion)
 27    const setAppState = useSetAppState()
 28    const [isOpen, setIsOpen] = useState(false)
 29    const lastSuggestionRef = useRef(suggestion)
 30    const loggedAppearanceRef = useRef(false)
 31  
 32    // Track the suggestion for display even after clearing AppState
 33    if (suggestion) {
 34      lastSuggestionRef.current = suggestion
 35    }
 36  
 37    // Open when a new suggestion arrives
 38    if (suggestion && !isOpen) {
 39      setIsOpen(true)
 40      if (!loggedAppearanceRef.current) {
 41        loggedAppearanceRef.current = true
 42        logEvent('tengu_skill_improvement_survey', {
 43          event_type:
 44            'appeared' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 45          // _PROTO_skill_name routes to the privileged skill_name BQ column.
 46          // Unredacted names don't go in additional_metadata.
 47          _PROTO_skill_name: (suggestion.skillName ??
 48            'unknown') as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
 49        })
 50      }
 51    }
 52  
 53    const handleSelect = useCallback(
 54      (selected: FeedbackSurveyResponse) => {
 55        const current = lastSuggestionRef.current
 56        if (!current) return
 57  
 58        const applied = selected !== 'dismissed'
 59  
 60        logEvent('tengu_skill_improvement_survey', {
 61          event_type:
 62            'responded' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 63          response: (applied
 64            ? 'applied'
 65            : 'dismissed') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 66          // _PROTO_skill_name routes to the privileged skill_name BQ column.
 67          // Unredacted names don't go in additional_metadata.
 68          _PROTO_skill_name:
 69            current.skillName as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
 70        })
 71  
 72        if (applied) {
 73          void applySkillImprovement(current.skillName, current.updates).then(
 74            () => {
 75              setMessages(prev => [
 76                ...prev,
 77                createSystemMessage(
 78                  `Skill "${current.skillName}" updated with improvements.`,
 79                  'suggestion',
 80                ),
 81              ])
 82            },
 83          )
 84        }
 85  
 86        // Close and clear
 87        setIsOpen(false)
 88        loggedAppearanceRef.current = false
 89        setAppState(prev => {
 90          if (!prev.skillImprovement.suggestion) return prev
 91          return {
 92            ...prev,
 93            skillImprovement: { suggestion: null },
 94          }
 95        })
 96      },
 97      [setAppState, setMessages],
 98    )
 99  
100    return {
101      isOpen,
102      suggestion: lastSuggestionRef.current,
103      handleSelect,
104    }
105  }