1
1
import * as React from "react"
2
- import { useEffect , useRef , useState , useMemo } from "react"
2
+ import { useEffect , useRef , useState } from "react"
3
3
import type { FC } from "react"
4
4
import styled from "@emotion/styled"
5
5
import Typography from "@mui/material/Typography"
6
6
import classNames from "classnames"
7
7
import { RiSendPlaneFill , RiStopFill , RiMoreFill } from "@remixicon/react"
8
8
import { Input , AdornmentButton } from "../Input/Input"
9
- import type { AiChatMessage , AiChatProps } from "./types"
10
- import { EntryScreen } from "./EntryScreen"
9
+ import type { AiChatDisplayProps , AiChatProps } from "./types"
11
10
import Markdown from "react-markdown"
12
11
import { ScrollSnap } from "../ScrollSnap/ScrollSnap"
13
12
import { SrAnnouncer } from "../SrAnnouncer/SrAnnouncer"
14
13
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden"
15
14
import { Alert } from "../Alert/Alert"
16
15
import { ChatTitle } from "./ChatTitle"
17
- import { useAiChat } from "./utils "
16
+ import { AiChatProvider , useAiChat } from "./AiChatContext "
18
17
import { useScrollSnap } from "../ScrollSnap/useScrollSnap"
19
- import type { Message } from "@ai-sdk/react "
18
+ import { EntryScreen } from "./EntryScreen "
20
19
21
20
const classes = {
22
21
root : "MitAiChat--root" ,
@@ -179,78 +178,48 @@ const Disclaimer = styled(Typography)(({ theme }) => ({
179
178
textAlign : "center" ,
180
179
} ) )
181
180
182
- const AiChat : FC < AiChatProps > = ( {
183
- entryScreenTitle,
184
- entryScreenEnabled = true ,
181
+ const AiChatDisplay : FC < AiChatDisplayProps > = ( {
185
182
conversationStarters,
186
- initialMessages : _initialMessages ,
187
183
askTimTitle,
188
- requestOpts ,
189
- parseContent ,
184
+ entryScreenEnabled = true ,
185
+ entryScreenTitle ,
190
186
srLoadingMessages,
191
187
placeholder = "" ,
192
188
className,
193
189
scrollElement,
194
- chatId,
195
190
ref,
196
191
...others // Could contain data attributes
197
192
} ) => {
198
193
const containerRef = useRef < HTMLDivElement > ( null )
199
194
const messagesContainerRef = useRef < HTMLDivElement > ( null )
200
- const [ showEntryScreen , setShowEntryScreen ] = useState ( entryScreenEnabled )
201
195
const chatScreenRef = useRef < HTMLDivElement > ( null )
202
- const [ initialMessages , setInitialMessages ] = useState < AiChatMessage [ ] > ( )
203
196
const promptInputRef = useRef < HTMLDivElement > ( null )
204
197
205
198
const {
206
- messages : unparsed ,
199
+ messages,
207
200
input,
208
201
handleInputChange,
209
202
handleSubmit,
210
203
append,
211
204
isLoading,
212
205
stop,
213
206
error,
214
- } = useAiChat ( requestOpts , {
215
207
initialMessages,
216
- id : chatId ,
217
- } )
208
+ } = useAiChat ( )
218
209
219
210
useScrollSnap ( {
220
211
scrollElement : scrollElement || messagesContainerRef . current ,
221
212
contentElement : scrollElement ? messagesContainerRef . current : null ,
222
213
threshold : 200 ,
223
214
} )
224
215
225
- useEffect ( ( ) => {
226
- if ( _initialMessages ) {
227
- const prefix = Math . random ( ) . toString ( ) . slice ( 2 )
228
- setInitialMessages (
229
- _initialMessages . map ( ( m , i ) => ( {
230
- ...m ,
231
- id : `initial-${ prefix } -${ i } ` ,
232
- } ) ) ,
233
- )
234
- }
235
- } , [ _initialMessages ] )
236
-
216
+ const [ showEntryScreen , setShowEntryScreen ] = useState ( entryScreenEnabled )
237
217
useEffect ( ( ) => {
238
218
if ( ! showEntryScreen ) {
239
219
promptInputRef . current ?. querySelector ( "input" ) ?. focus ( )
240
220
}
241
221
} , [ showEntryScreen ] )
242
222
243
- const messages = useMemo ( ( ) => {
244
- const initial = initialMessages ?. map ( ( m ) => m . id )
245
- return unparsed . map ( ( m : Message ) => {
246
- if ( m . role === "assistant" && ! initial ?. includes ( m . id ) ) {
247
- const content = parseContent ? parseContent ( m . content ) : m . content
248
- return { ...m , content }
249
- }
250
- return m
251
- } )
252
- } , [ parseContent , unparsed , initialMessages ] )
253
-
254
223
const showStarters = messages . length === ( initialMessages ?. length || 0 )
255
224
256
225
const waiting =
@@ -270,19 +239,7 @@ const AiChat: FC<AiChatProps> = ({
270
239
const externalScroll = ! ! scrollElement
271
240
272
241
return (
273
- < Container
274
- className = { className }
275
- ref = { containerRef }
276
- /**
277
- * Changing the `useChat` chatId seems to persist some state between
278
- * hook calls. This can cause strange effects like loading API responses
279
- * for previous chatId into new chatId.
280
- *
281
- * To avoid this, let's change the key, this will force React to make a new component
282
- * not sharing any of the old state.
283
- */
284
- key = { chatId }
285
- >
242
+ < Container className = { className } ref = { containerRef } >
286
243
{ showEntryScreen ? (
287
244
< EntryScreen
288
245
className = { classes . entryScreenContainer }
@@ -318,7 +275,7 @@ const AiChat: FC<AiChatProps> = ({
318
275
externalScroll = { externalScroll }
319
276
ref = { messagesContainerRef }
320
277
>
321
- { messages . map ( ( m : Message ) => (
278
+ { messages . map ( ( m ) => (
322
279
< MessageRow
323
280
key = { m . id }
324
281
data-chat-role = { m . role }
@@ -442,5 +399,23 @@ const AiChat: FC<AiChatProps> = ({
442
399
)
443
400
}
444
401
445
- export { AiChat }
446
- export type { AiChatProps }
402
+ const AiChat : FC < AiChatProps > = ( {
403
+ requestOpts,
404
+ initialMessages,
405
+ chatId,
406
+ parseContent,
407
+ ...displayProps
408
+ } ) => {
409
+ return (
410
+ < AiChatProvider
411
+ requestOpts = { requestOpts }
412
+ chatId = { chatId }
413
+ initialMessages = { initialMessages }
414
+ parseContent = { parseContent }
415
+ >
416
+ < AiChatDisplay { ...displayProps } />
417
+ </ AiChatProvider >
418
+ )
419
+ }
420
+
421
+ export { AiChatDisplay , AiChat }
0 commit comments