@@ -7,156 +7,169 @@ import Prompt, { IPromptProps } from '../prompt';
77import { useContext } from '../useContext' ;
88import './index.scss' ;
99
10- export interface IContentProps {
10+ export interface IContentProps <
11+ M extends IMessageProps = IMessageProps ,
12+ P extends IPromptProps = IPromptProps
13+ > {
1114 data : PromptEntity [ ] ;
1215 placeholder ?: React . ReactNode ;
1316 scrollable ?: boolean ;
1417 onRegenerate ?: ( data : MessageEntity , prompt : PromptEntity ) => void ;
1518 onStop ?: ( data : MessageEntity , prompt : PromptEntity ) => void ;
16- replacePrompt ?: ( promptProps : IPromptProps ) => React . ReactNode ;
17- replaceMessage ?: ( messageProps : IMessageProps ) => React . ReactNode ;
19+ replacePrompt ?: ( promptProps : P ) => React . ReactNode ;
20+ replaceMessage ?: ( messageProps : M ) => React . ReactNode ;
1821}
1922
2023export interface IContentRef {
2124 nativeElement : HTMLDivElement | null ;
2225 scrollToBottom : ( ) => void ;
2326}
2427
25- const Content = forwardRef < IContentRef , IContentProps > ( function (
26- { data, placeholder, scrollable = true , onRegenerate, onStop, replacePrompt, replaceMessage } ,
27- forwardedRef
28- ) {
29- const { maxRegenerateCount, copy, regenerate } = useContext ( ) ;
30- const containerRef = useRef < HTMLDivElement > ( null ) ;
28+ const Content = forwardRef (
29+ < M extends IMessageProps = IMessageProps , P extends IPromptProps = IPromptProps > (
30+ {
31+ data,
32+ placeholder,
33+ scrollable = true ,
34+ onRegenerate,
35+ onStop,
36+ replacePrompt,
37+ replaceMessage,
38+ } : IContentProps < M , P > ,
39+ forwardedRef : React . Ref < IContentRef >
40+ ) => {
41+ const { maxRegenerateCount, copy, regenerate } = useContext ( ) ;
42+ const containerRef = useRef < HTMLDivElement > ( null ) ;
3143
32- const [ isStickyAtBottom , setIsStickyAtBottom ] = useState < boolean > ( true ) ;
33- const raf = useRef ( 0 ) ;
44+ const [ isStickyAtBottom , setIsStickyAtBottom ] = useState < boolean > ( true ) ;
45+ const raf = useRef ( 0 ) ;
3446
35- useImperativeHandle ( forwardedRef , ( ) => ( {
36- nativeElement : containerRef . current ,
37- scrollToBottom : ( ) => {
38- raf . current = window . requestAnimationFrame ( ( ) => {
39- containerRef . current ?. scrollTo ( {
40- top : containerRef . current ?. scrollHeight ,
41- left : 0 ,
42- behavior : 'instant' as any ,
47+ useImperativeHandle ( forwardedRef , ( ) => ( {
48+ nativeElement : containerRef . current ,
49+ scrollToBottom : ( ) => {
50+ raf . current = window . requestAnimationFrame ( ( ) => {
51+ containerRef . current ?. scrollTo ( {
52+ top : containerRef . current ?. scrollHeight ,
53+ left : 0 ,
54+ behavior : 'instant' as any ,
55+ } ) ;
4356 } ) ;
44- } ) ;
45- } ,
46- } ) ) ;
57+ } ,
58+ } ) ) ;
4759
48- const checkIfScrolledToBottom = ( ) => {
49- if ( ! containerRef . current ) return false ;
50- const threshold = 5 ;
51- const { scrollTop, clientHeight, scrollHeight } = containerRef . current ;
52- return scrollTop + clientHeight >= scrollHeight - threshold ;
53- } ;
60+ const checkIfScrolledToBottom = ( ) => {
61+ if ( ! containerRef . current ) return false ;
62+ const threshold = 5 ;
63+ const { scrollTop, clientHeight, scrollHeight } = containerRef . current ;
64+ return scrollTop + clientHeight >= scrollHeight - threshold ;
65+ } ;
5466
55- const dataValid = ! ! ( Array . isArray ( data ) && data . length ) ;
56- const lastPrompt = data [ data . length - 1 ] ;
57- const lastMessage = dataValid
58- ? lastPrompt . messages ?. [ lastPrompt . messages . length - 1 ]
59- : undefined ;
67+ const dataValid = ! ! ( Array . isArray ( data ) && data . length ) ;
68+ const lastPrompt = data [ data . length - 1 ] ;
69+ const lastMessage = dataValid
70+ ? lastPrompt . messages ?. [ lastPrompt . messages . length - 1 ]
71+ : undefined ;
6072
61- useLayoutEffect ( ( ) => {
62- const handleScroll = ( ) => {
63- if ( ! containerRef . current ) {
64- return ;
65- }
66- setIsStickyAtBottom ( checkIfScrolledToBottom ( ) ) ;
67- } ;
68- containerRef . current ?. addEventListener ( 'scroll' , handleScroll ) ;
69- return ( ) => {
70- containerRef . current ?. removeEventListener ( 'scroll' , handleScroll ) ;
71- window . cancelAnimationFrame ( raf . current ) ;
72- } ;
73- } , [ ] ) ;
73+ useLayoutEffect ( ( ) => {
74+ const handleScroll = ( ) => {
75+ if ( ! containerRef . current ) {
76+ return ;
77+ }
78+ setIsStickyAtBottom ( checkIfScrolledToBottom ( ) ) ;
79+ } ;
80+ containerRef . current ?. addEventListener ( 'scroll' , handleScroll ) ;
81+ return ( ) => {
82+ containerRef . current ?. removeEventListener ( 'scroll' , handleScroll ) ;
83+ window . cancelAnimationFrame ( raf . current ) ;
84+ } ;
85+ } , [ ] ) ;
7486
75- useLayoutEffect ( ( ) => {
76- raf . current = window . requestAnimationFrame ( ( ) => {
77- if ( ! containerRef . current ) {
78- return ;
79- }
80- if ( dataValid ) {
81- containerRef . current . scrollTop = containerRef . current . scrollHeight ;
82- } else {
83- containerRef . current . scrollTop = 0 ;
84- }
85- } ) ;
86- } , [ lastMessage ?. id ] ) ;
87+ useLayoutEffect ( ( ) => {
88+ raf . current = window . requestAnimationFrame ( ( ) => {
89+ if ( ! containerRef . current ) {
90+ return ;
91+ }
92+ if ( dataValid ) {
93+ containerRef . current . scrollTop = containerRef . current . scrollHeight ;
94+ } else {
95+ containerRef . current . scrollTop = 0 ;
96+ }
97+ } ) ;
98+ } , [ lastMessage ?. id ] ) ;
8799
88- useLayoutEffect ( ( ) => {
89- raf . current = window . requestAnimationFrame ( ( ) => {
90- if ( ! containerRef . current ) {
91- return ;
92- }
93- if ( lastMessage ?. status === MessageStatus . GENERATING && isStickyAtBottom ) {
94- containerRef . current . scrollTop = containerRef . current . scrollHeight ;
95- }
96- } ) ;
97- } , [ lastMessage ?. status , lastMessage ?. content , isStickyAtBottom ] ) ;
100+ useLayoutEffect ( ( ) => {
101+ raf . current = window . requestAnimationFrame ( ( ) => {
102+ if ( ! containerRef . current ) {
103+ return ;
104+ }
105+ if ( lastMessage ?. status === MessageStatus . GENERATING && isStickyAtBottom ) {
106+ containerRef . current . scrollTop = containerRef . current . scrollHeight ;
107+ }
108+ } ) ;
109+ } , [ lastMessage ?. status , lastMessage ?. content , isStickyAtBottom ] ) ;
98110
99- return (
100- < div
101- className = { classNames (
102- 'dtc__aigc__content__container' ,
103- ! scrollable && 'dtc__aigc__content__container--disabled'
104- ) }
105- ref = { containerRef }
106- >
107- { dataValid ? (
108- < div className = "dtc__aigc__content__inner__holder" >
109- { data . map ( ( row , idx ) => {
110- const defaultRegenerate =
111- idx === data . length - 1 && row . messages . length < maxRegenerateCount ;
112- const messageProps : IMessageProps = {
113- prompt : row ,
114- data : row . messages ,
115- regenerate :
116- typeof regenerate === 'function'
117- ? regenerate ( row , idx , data )
118- : regenerate ?? defaultRegenerate ,
119- copy,
120- onRegenerate : ( message ) => onRegenerate ?.( message , row ) ,
121- onStop : ( message ) => onStop ?.( message , row ) ,
122- onLazyRendered : ( renderFn ) => {
123- // 在触发懒加载之前判断是否在底部,如果是则加载完成后滚动到底部
124- const scrolledToBottom = checkIfScrolledToBottom ( ) ;
125- renderFn ( ) . then ( ( ) => {
126- window . requestAnimationFrame ( ( ) => {
127- setIsStickyAtBottom ( scrolledToBottom ) ;
128- if ( scrolledToBottom && containerRef . current ) {
129- containerRef . current . scrollTop =
130- containerRef . current . scrollHeight ;
131- }
111+ return (
112+ < div
113+ className = { classNames (
114+ 'dtc__aigc__content__container' ,
115+ ! scrollable && 'dtc__aigc__content__container--disabled'
116+ ) }
117+ ref = { containerRef }
118+ >
119+ { dataValid ? (
120+ < div className = "dtc__aigc__content__inner__holder" >
121+ { data . map ( ( row , idx ) => {
122+ const defaultRegenerate =
123+ idx === data . length - 1 && row . messages . length < maxRegenerateCount ;
124+ const messageProps = {
125+ prompt : row ,
126+ data : row . messages ,
127+ regenerate :
128+ typeof regenerate === 'function'
129+ ? regenerate ( row , idx , data )
130+ : regenerate ?? defaultRegenerate ,
131+ copy,
132+ onRegenerate : ( message ) => onRegenerate ?.( message , row ) ,
133+ onStop : ( message ) => onStop ?.( message , row ) ,
134+ onLazyRendered : ( renderFn ) => {
135+ // 在触发懒加载之前判断是否在底部,如果是则加载完成后滚动到底部
136+ const scrolledToBottom = checkIfScrolledToBottom ( ) ;
137+ renderFn ( ) . then ( ( ) => {
138+ window . requestAnimationFrame ( ( ) => {
139+ setIsStickyAtBottom ( scrolledToBottom ) ;
140+ if ( scrolledToBottom && containerRef . current ) {
141+ containerRef . current . scrollTop =
142+ containerRef . current . scrollHeight ;
143+ }
144+ } ) ;
132145 } ) ;
133- } ) ;
134- } ,
135- } ;
136- const promptProps : IPromptProps = {
137- data : row ,
138- } ;
139- return (
140- < React . Fragment key = { row . id } >
141- { replacePrompt ? (
142- replacePrompt ( promptProps )
143- ) : (
144- < Prompt { ... promptProps } />
145- ) }
146- { replaceMessage ? (
147- replaceMessage ( messageProps )
148- ) : (
149- < Message { ... messageProps } />
150- ) }
151- </ React . Fragment >
152- ) ;
153- } ) }
154- </ div >
155- ) : (
156- < React . Fragment > { placeholder } </ React . Fragment >
157- ) }
158- </ div >
159- ) ;
160- } ) ;
146+ } ,
147+ } as M ;
148+ const promptProps = {
149+ data : row ,
150+ } as P ;
151+ return (
152+ < React . Fragment key = { row . id } >
153+ { replacePrompt ? (
154+ replacePrompt ( promptProps )
155+ ) : (
156+ < Prompt { ... promptProps } />
157+ ) }
158+ { replaceMessage ? (
159+ replaceMessage ( messageProps )
160+ ) : (
161+ < Message { ... messageProps } />
162+ ) }
163+ </ React . Fragment >
164+ ) ;
165+ } ) }
166+ </ div >
167+ ) : (
168+ < React . Fragment > { placeholder } </ React . Fragment >
169+ ) }
170+ </ div >
171+ ) ;
172+ }
173+ ) ;
161174
162175export default Content ;
0 commit comments