All files / webview/contexts ChatContext.tsx

61.29% Statements 19/31
25% Branches 2/8
62.5% Functions 5/8
72% Lines 18/25

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                                                            4x     515x 515x 515x 515x   515x   515x   1x 1x           1x         515x                               515x                           515x 515x                                       515x       541x 541x 541x    
import { createContext, type ReactNode, useCallback, useContext, useMemo } from 'react'
import { useChatState } from '../hooks/useChatState'
import { useConversationState } from '../hooks/useConversationState'
import { type PendingInput, type PendingPermission, usePermissionState } from '../hooks/usePermissionState'
import type { MessageEntry } from '../lib/types'
import { vscode } from '../lib/vscode'
import { useRouter } from './MessageRouterContext'
 
interface ChatState {
  messages: MessageEntry[]
  setMessages: React.Dispatch<React.SetStateAction<MessageEntry[]>>
  streamingText: string
  streamingThinking: string
  isRunning: boolean
  setIsRunning: (v: boolean) => void
  pendingPermission: PendingPermission | null
  setPendingPermission: (v: PendingPermission | null) => void
  pendingInput: PendingInput | null
  setPendingInput: (v: PendingInput | null) => void
  conversations: { id: string; title: string; updatedAt: number }[]
  setConversations: React.Dispatch<React.SetStateAction<{ id: string; title: string; updatedAt: number }[]>>
  activeConversationId: string | null
  conversationSearchResults: { id: string; title: string; updatedAt: number; snippet: string }[] | null
  setConversationSearchResults: (v: null) => void
  handleAbort: () => void
  handlePermissionRespond: (behavior: 'allow' | 'deny' | 'always') => void
  handlePermissionInstruct: (instructions: string) => void
  handleInputRespond: (answers: Record<string, string>) => void
}
 
const ChatCtx = createContext<ChatState | null>(null)
 
export function ChatProvider({ children }: { children: ReactNode }) {
  const router = useRouter()
  const chat = useChatState(router)
  const permissions = usePermissionState(router, chat.setMessages)
  const conversations = useConversationState(router)
 
  const handleAbort = useCallback(() => vscode.postMessage({ type: 'abort' }), [])
 
  const handlePermissionRespond = useCallback(
    (behavior: 'allow' | 'deny' | 'always') => {
      Iif (!permissions.pendingPermission) return
      vscode.postMessage({
        type: 'user',
        subtype: 'permission',
        requestId: permissions.pendingPermission.requestId,
        behavior,
      })
      permissions.setPendingPermission(null)
    },
    [permissions.pendingPermission, permissions.setPendingPermission],
  )
 
  const handlePermissionInstruct = useCallback(
    (instructions: string) => {
      if (!permissions.pendingPermission) return
      chat.setMessages((prev) => [...prev, { role: 'denied-with-instructions', instructions }])
      vscode.postMessage({
        type: 'user',
        subtype: 'permission',
        requestId: permissions.pendingPermission.requestId,
        behavior: 'deny',
        feedback: instructions,
      })
      permissions.setPendingPermission(null)
    },
    [permissions.pendingPermission, permissions.setPendingPermission, chat.setMessages],
  )
 
  const handleInputRespond = useCallback(
    (answers: Record<string, string>) => {
      if (!permissions.pendingInput) return
      vscode.postMessage({
        type: 'user',
        subtype: 'input',
        requestId: permissions.pendingInput.requestId,
        answers,
      })
      permissions.setPendingInput(null)
    },
    [permissions.pendingInput, permissions.setPendingInput],
  )
 
  const value = useMemo(
    () => ({
      ...chat,
      ...permissions,
      ...conversations,
      handleAbort,
      handlePermissionRespond,
      handlePermissionInstruct,
      handleInputRespond,
    }),
    [
      chat,
      permissions,
      conversations,
      handleAbort,
      handlePermissionRespond,
      handlePermissionInstruct,
      handleInputRespond,
    ],
  )
 
  return <ChatCtx.Provider value={value}>{children}</ChatCtx.Provider>
}
 
export function useChat(): ChatState {
  const ctx = useContext(ChatCtx)
  Iif (!ctx) throw new Error('useChat must be used within ChatProvider')
  return ctx
}