(props: {
chatController: AIChatController;
chat: AIChatState;
trademark: boolean;
welcomeMessage?: string;
suggestions?: string[];
})
| 155 | * Body of the AI chat window. |
| 156 | */ |
| 157 | export function AIChatBody(props: { |
| 158 | chatController: AIChatController; |
| 159 | chat: AIChatState; |
| 160 | trademark: boolean; |
| 161 | welcomeMessage?: string; |
| 162 | suggestions?: string[]; |
| 163 | }) { |
| 164 | const { chatController, chat, trademark, suggestions } = props; |
| 165 | |
| 166 | const [input, setInput] = React.useState(''); |
| 167 | |
| 168 | const scrollContainerRef = React.useRef<HTMLDivElement>(null); |
| 169 | // Ref for the last user message element |
| 170 | const lastUserMessageRef = React.useRef<HTMLDivElement>(null); |
| 171 | const inputRef = React.useRef<HTMLDivElement>(null); |
| 172 | |
| 173 | const [inputHeight, setInputHeight] = React.useState(0); |
| 174 | const language = useLanguage(); |
| 175 | const now = useNow(60 * 60 * 1000); // Refresh every hour for greeting |
| 176 | |
| 177 | const isEmpty = !chat.messages.length; |
| 178 | |
| 179 | const timeGreeting = React.useMemo(() => { |
| 180 | const hour = new Date(now).getHours(); |
| 181 | if (hour < 6) return tString(language, 'ai_chat_assistant_greeting_night'); |
| 182 | if (hour < 12) return tString(language, 'ai_chat_assistant_greeting_morning'); |
| 183 | if (hour < 18) return tString(language, 'ai_chat_assistant_greeting_afternoon'); |
| 184 | return tString(language, 'ai_chat_assistant_greeting_evening'); |
| 185 | }, [now, language]); |
| 186 | |
| 187 | // Auto-scroll to the latest user message when messages change |
| 188 | React.useEffect(() => { |
| 189 | if (chat.messages.length > 0 && lastUserMessageRef.current) { |
| 190 | lastUserMessageRef.current.scrollIntoView({ |
| 191 | behavior: 'smooth', |
| 192 | block: 'start', |
| 193 | }); |
| 194 | } |
| 195 | }, [chat.messages.length]); |
| 196 | |
| 197 | React.useEffect(() => { |
| 198 | const timeout = setTimeout(() => { |
| 199 | if (lastUserMessageRef.current) { |
| 200 | lastUserMessageRef.current.scrollIntoView({ |
| 201 | behavior: 'smooth', |
| 202 | block: 'start', |
| 203 | }); |
| 204 | } |
| 205 | }, 100); |
| 206 | |
| 207 | // We want the chat messages to scroll underneath the input, but they should scroll past the input when scrolling all the way down. |
| 208 | // The best way to do this is to observe the input height and adjust the padding bottom of the scroll container accordingly. |
| 209 | const observer = new ResizeObserver((entries) => { |
| 210 | entries.forEach((entry) => { |
| 211 | setInputHeight(entry.contentRect.height + 32); |
| 212 | }); |
| 213 | }); |
| 214 | if (inputRef.current) { |
nothing calls this directly
no test coverage detected