({ identifier }: { identifier: string })
| 87 | } |
| 88 | |
| 89 | export default function ChatClient({ identifier }: { identifier: string }) { |
| 90 | const [messages, setMessages] = useState<ChatMessage[]>([]) |
| 91 | const [inputValue, setInputValue] = useState('') |
| 92 | const [isLoading, setIsLoading] = useState(false) |
| 93 | const messagesEndRef = useRef<HTMLDivElement>(null) |
| 94 | const messagesContainerRef = useRef<HTMLDivElement>(null) |
| 95 | const [conversationId] = useState(() => generateId()) |
| 96 | |
| 97 | const [showScrollButton, setShowScrollButton] = useState(false) |
| 98 | const [userHasScrolled, setUserHasScrolled] = useState(false) |
| 99 | const isUserScrollingRef = useRef(false) |
| 100 | |
| 101 | const [isVoiceFirstMode, setIsVoiceFirstMode] = useState(false) |
| 102 | |
| 103 | const { data: chatConfigResult, error: chatConfigError } = useDeployedChatConfig(identifier) |
| 104 | const { data: voiceSettings } = useVoiceSettings() |
| 105 | const { data: starCount } = useGitHubStars() |
| 106 | |
| 107 | const sttAvailable = voiceSettings?.sttAvailable === true |
| 108 | const authRequired = chatConfigResult?.kind === 'auth' ? chatConfigResult.authType : null |
| 109 | const chatConfig = chatConfigResult?.kind === 'config' ? chatConfigResult.config : null |
| 110 | |
| 111 | const welcomeMessage = chatConfig?.customizations?.welcomeMessage |
| 112 | const welcomeChatMessage = useMemo<ChatMessage | null>( |
| 113 | () => |
| 114 | welcomeMessage |
| 115 | ? { |
| 116 | id: 'welcome', |
| 117 | content: welcomeMessage, |
| 118 | type: 'assistant', |
| 119 | timestamp: new Date(), |
| 120 | isInitialMessage: true, |
| 121 | } |
| 122 | : null, |
| 123 | [welcomeMessage] |
| 124 | ) |
| 125 | const displayMessages: ChatMessage[] = welcomeChatMessage |
| 126 | ? [welcomeChatMessage, ...messages] |
| 127 | : messages |
| 128 | |
| 129 | const { isStreamingResponse, abortControllerRef, stopStreaming, handleStreamedResponse } = |
| 130 | useChatStreaming() |
| 131 | const audioContextRef = useRef<AudioContext | null>(null) |
| 132 | const { isPlayingAudio, streamTextToAudio, stopAudio } = useAudioStreaming(audioContextRef) |
| 133 | |
| 134 | const scrollToBottom = useCallback(() => { |
| 135 | if (messagesEndRef.current) { |
| 136 | messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) |
| 137 | } |
| 138 | }, []) |
| 139 | |
| 140 | const scrollToMessage = useCallback( |
| 141 | (messageId: string, scrollToShowOnlyMessage = false) => { |
| 142 | const messageElement = document.querySelector(`[data-message-id="${messageId}"]`) |
| 143 | if (messageElement && messagesContainerRef.current) { |
| 144 | const container = messagesContainerRef.current |
| 145 | const containerRect = container.getBoundingClientRect() |
| 146 | const messageRect = messageElement.getBoundingClientRect() |
nothing calls this directly
no test coverage detected