@@ -89,6 +89,16 @@ export interface BubbleProps
89
89
* @default "solid"
90
90
*/
91
91
background ?: "transparent" | "solid" ;
92
+ /**
93
+ * Custom pending content to display when pending is true.
94
+ * @description If not provided, will use default dots animation.
95
+ */
96
+ pending ?: React . ReactNode ;
97
+ /**
98
+ * Whether the bubble is in pending state.
99
+ * @default false
100
+ */
101
+ isPending ?: boolean ;
92
102
}
93
103
94
104
export function Bubble ( {
@@ -97,10 +107,26 @@ export function Bubble({
97
107
size,
98
108
align,
99
109
background = "solid" ,
110
+ pending,
111
+ isPending = false ,
100
112
...props
101
113
} : BubbleProps ) {
102
114
const { isDark } = useTheme ( ) ;
103
115
116
+ const defaultPending = (
117
+ < div className = "flex items-center space-x-1 py-1" >
118
+ < div className = "w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
119
+ < div
120
+ className = "w-2 h-2 bg-gray-400 rounded-full animate-bounce"
121
+ style = { { animationDelay : "0.1s" } }
122
+ />
123
+ < div
124
+ className = "w-2 h-2 bg-gray-400 rounded-full animate-bounce"
125
+ style = { { animationDelay : "0.2s" } }
126
+ />
127
+ </ div >
128
+ ) ;
129
+
104
130
return (
105
131
< div
106
132
data-slot = "bubble"
@@ -112,50 +138,55 @@ export function Bubble({
112
138
align,
113
139
background,
114
140
} ) ,
141
+ pending && "flex items-center" ,
115
142
) ,
116
143
) }
117
144
{ ...props }
118
145
>
119
- < Markdown
120
- remarkPlugins = { [ remarkGfm , remarkMath ] }
121
- components = { {
122
- code ( props ) {
123
- const { children, className, ref : _ref , ...rest } = props ;
124
- const match = / l a n g u a g e - ( \w + ) / . exec ( className || "" ) ;
125
- return match ? (
126
- < div className = "w-full overflow-x-auto border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 rounded-lg" >
127
- < SyntaxHighlighter
128
- { ...rest }
129
- PreTag = "div"
130
- language = { match [ 1 ] }
131
- style = { isDark ? vscDarkPlus : oneLight }
132
- customStyle = { {
133
- background : "transparent" ,
134
- margin : 0 ,
135
- padding : "1rem" ,
136
- borderRadius : "0.5rem" ,
137
- overflowX : "auto" ,
138
- } }
139
- codeTagProps = { {
140
- style : {
141
- fontFamily : "monospace" ,
142
- fontSize : "0.875rem" ,
143
- } ,
144
- } }
145
- >
146
- { String ( children ) . replace ( / \n $ / , "" ) }
147
- </ SyntaxHighlighter >
148
- </ div >
149
- ) : (
150
- < code { ...rest } className = { className } >
151
- { children }
152
- </ code >
153
- ) ;
154
- } ,
155
- } }
156
- >
157
- { text }
158
- </ Markdown >
146
+ { isPending ? (
147
+ pending || defaultPending
148
+ ) : (
149
+ < Markdown
150
+ remarkPlugins = { [ remarkGfm , remarkMath ] }
151
+ components = { {
152
+ code ( props ) {
153
+ const { children, className, ref : _ref , ...rest } = props ;
154
+ const match = / l a n g u a g e - ( \w + ) / . exec ( className || "" ) ;
155
+ return match ? (
156
+ < div className = "w-full overflow-x-auto border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 rounded-lg" >
157
+ < SyntaxHighlighter
158
+ { ...rest }
159
+ PreTag = "div"
160
+ language = { match [ 1 ] }
161
+ style = { isDark ? vscDarkPlus : oneLight }
162
+ customStyle = { {
163
+ background : "transparent" ,
164
+ margin : 0 ,
165
+ padding : "1rem" ,
166
+ borderRadius : "0.5rem" ,
167
+ overflowX : "auto" ,
168
+ } }
169
+ codeTagProps = { {
170
+ style : {
171
+ fontFamily : "monospace" ,
172
+ fontSize : "0.875rem" ,
173
+ } ,
174
+ } }
175
+ >
176
+ { String ( children ) . replace ( / \n $ / , "" ) }
177
+ </ SyntaxHighlighter >
178
+ </ div >
179
+ ) : (
180
+ < code { ...rest } className = { className } >
181
+ { children }
182
+ </ code >
183
+ ) ;
184
+ } ,
185
+ } }
186
+ >
187
+ { text }
188
+ </ Markdown >
189
+ ) }
159
190
</ div >
160
191
) ;
161
192
}
@@ -202,13 +233,27 @@ export interface BubbleListProps extends React.ComponentProps<"div"> {
202
233
* @default "right-solid"
203
234
*/
204
235
background ?: "transparent" | "solid" | "left-solid" | "right-solid" ;
236
+ isPending ?: boolean ;
237
+ assistant ?: {
238
+ avatar ?: AvatarProps ;
239
+ align ?: "left" | "right" ;
240
+ } ;
205
241
footer ?: React . ReactNode ;
242
+ pending ?: React . ReactNode ;
206
243
}
207
244
208
245
export function BubbleList ( {
209
246
className,
210
247
background = "right-solid" ,
211
248
footer,
249
+ pending,
250
+ assistant = {
251
+ avatar : {
252
+ text : "A" ,
253
+ } ,
254
+ align : "left" ,
255
+ } ,
256
+ isPending = true ,
212
257
...props
213
258
} : BubbleListProps ) {
214
259
const { messages } = props ;
@@ -222,7 +267,7 @@ export function BubbleList({
222
267
block : "end" ,
223
268
} ) ;
224
269
}
225
- } , [ messages ] ) ;
270
+ } , [ messages , isPending ] ) ;
226
271
227
272
return (
228
273
< div
@@ -269,6 +314,33 @@ export function BubbleList({
269
314
/>
270
315
</ div >
271
316
) ) }
317
+ { isPending && (
318
+ < div
319
+ key = "pending"
320
+ data-slot = "bubble-item"
321
+ className = { twMerge (
322
+ clsx ( assistant ?. align === "right" && "flex-row-reverse" ) ,
323
+ "flex items-start gap-2 w-full" ,
324
+ ) }
325
+ >
326
+ < Avatar className = "flex-shrink-0" { ...( assistant ?. avatar || { } ) } />
327
+ < Bubble
328
+ isPending = { isPending }
329
+ pending = { pending }
330
+ text = ""
331
+ align = { assistant ?. align || "left" }
332
+ background = {
333
+ ( background === "left-solid" &&
334
+ ( assistant ?. align || "left" ) === "left" ) ||
335
+ ( background === "right-solid" &&
336
+ ( assistant ?. align || "left" ) === "right" ) ||
337
+ background === "solid"
338
+ ? "solid"
339
+ : "transparent"
340
+ }
341
+ />
342
+ </ div >
343
+ ) }
272
344
</ div >
273
345
{ footer && (
274
346
< div
0 commit comments