1
- import React , { useEffect , useRef , useState } from "react" ;
1
+ import { useEffect , useRef , useState , useMemo , useCallback } from "react" ;
2
2
import HistoricalMessage from "./HistoricalMessage" ;
3
3
import PromptReply from "./PromptReply" ;
4
+ import StatusResponse from "./StatusResponse" ;
4
5
import { useManageWorkspaceModal } from "../../../Modals/ManageWorkspace" ;
5
6
import ManageWorkspace from "../../../Modals/ManageWorkspace" ;
6
7
import { ArrowDown } from "@phosphor-icons/react" ;
@@ -12,6 +13,7 @@ import { useParams } from "react-router-dom";
12
13
import paths from "@/utils/paths" ;
13
14
import Appearance from "@/models/appearance" ;
14
15
import useTextSize from "@/hooks/useTextSize" ;
16
+ import { v4 } from "uuid" ;
15
17
16
18
export default function ChatHistory ( {
17
19
history = [ ] ,
@@ -136,6 +138,42 @@ export default function ChatHistory({
136
138
) ;
137
139
} ;
138
140
141
+ const compiledHistory = useMemo (
142
+ ( ) =>
143
+ buildMessages ( {
144
+ workspace,
145
+ history,
146
+ regenerateAssistantMessage,
147
+ saveEditedMessage,
148
+ forkThread,
149
+ } ) ,
150
+ [
151
+ workspace ,
152
+ history ,
153
+ regenerateAssistantMessage ,
154
+ saveEditedMessage ,
155
+ forkThread ,
156
+ ]
157
+ ) ;
158
+ const lastMessageInfo = useMemo ( ( ) => getLastMessageInfo ( history ) , [ history ] ) ;
159
+ const renderStatusResponse = useCallback (
160
+ ( item , index ) => {
161
+ const hasSubsequentMessages = index < compiledHistory . length - 1 ;
162
+ return (
163
+ < StatusResponse
164
+ key = { `status-group-${ index } ` }
165
+ messages = { item }
166
+ isThinking = { ! hasSubsequentMessages && lastMessageInfo . isAnimating }
167
+ showCheckmark = {
168
+ hasSubsequentMessages ||
169
+ ( ! lastMessageInfo . isAnimating && ! lastMessageInfo . isStatusResponse )
170
+ }
171
+ />
172
+ ) ;
173
+ } ,
174
+ [ compiledHistory . length , lastMessageInfo ]
175
+ ) ;
176
+
139
177
if ( history . length === 0 && ! hasAttachments ) {
140
178
return (
141
179
< div className = "flex flex-col h-full md:mt-0 pb-44 md:pb-40 w-full justify-end items-center" >
@@ -176,61 +214,14 @@ export default function ChatHistory({
176
214
177
215
return (
178
216
< div
179
- className = { `markdown text-white/80 light:text-theme-text-primary font-light ${ textSizeClass } h-full md:h-[83%] pb-[100px] pt-6 md:pt-0 md:pb-20 md:mx-0 overflow-y-scroll flex flex-col justify-start ${
180
- showScrollbar ? "show-scrollbar" : "no-scroll"
181
- } `}
217
+ className = { `markdown text-white/80 light:text-theme-text-primary font-light ${ textSizeClass } h-full md:h-[83%] pb-[100px] pt-6 md:pt-0 md:pb-20 md:mx-0 overflow-y-scroll flex flex-col justify-start ${ showScrollbar ? "show-scrollbar" : "no-scroll" } ` }
182
218
id = "chat-history"
183
219
ref = { chatHistoryRef }
184
220
onScroll = { handleScroll }
185
221
>
186
- { history . map ( ( props , index ) => {
187
- const isLastBotReply =
188
- index === history . length - 1 && props . role === "assistant" ;
189
-
190
- if ( props ?. type === "statusResponse" && ! ! props . content ) {
191
- return < StatusResponse key = { props . uuid } props = { props } /> ;
192
- }
193
-
194
- if ( props . type === "rechartVisualize" && ! ! props . content ) {
195
- return (
196
- < Chartable key = { props . uuid } workspace = { workspace } props = { props } />
197
- ) ;
198
- }
199
-
200
- if ( isLastBotReply && props . animate ) {
201
- return (
202
- < PromptReply
203
- key = { props . uuid }
204
- uuid = { props . uuid }
205
- reply = { props . content }
206
- pending = { props . pending }
207
- sources = { props . sources }
208
- error = { props . error }
209
- workspace = { workspace }
210
- closed = { props . closed }
211
- />
212
- ) ;
213
- }
214
-
215
- return (
216
- < HistoricalMessage
217
- key = { index }
218
- message = { props . content }
219
- role = { props . role }
220
- workspace = { workspace }
221
- sources = { props . sources }
222
- feedbackScore = { props . feedbackScore }
223
- chatId = { props . chatId }
224
- error = { props . error }
225
- attachments = { props . attachments }
226
- regenerateMessage = { regenerateAssistantMessage }
227
- isLastMessage = { isLastBotReply }
228
- saveEditedMessage = { saveEditedMessage }
229
- forkThread = { forkThread }
230
- metrics = { props . metrics }
231
- />
232
- ) ;
233
- } ) }
222
+ { compiledHistory . map ( ( item , index ) =>
223
+ Array . isArray ( item ) ? renderStatusResponse ( item , index ) : item
224
+ ) }
234
225
{ showing && (
235
226
< ManageWorkspace hideModal = { hideModal } providedSlug = { workspace . slug } />
236
227
) }
@@ -253,21 +244,13 @@ export default function ChatHistory({
253
244
) ;
254
245
}
255
246
256
- function StatusResponse ( { props } ) {
257
- return (
258
- < div className = "flex justify-center items-end w-full" >
259
- < div className = "py-2 px-4 w-full flex gap-x-5 md:max-w-[80%] flex-col" >
260
- < div className = "flex gap-x-5" >
261
- < span
262
- className = { `text-xs inline-block p-2 rounded-lg text-white/60 font-mono whitespace-pre-line` }
263
- >
264
- { props . content }
265
- </ span >
266
- </ div >
267
- </ div >
268
- </ div >
269
- ) ;
270
- }
247
+ const getLastMessageInfo = ( history ) => {
248
+ const lastMessage = history ?. [ history . length - 1 ] || { } ;
249
+ return {
250
+ isAnimating : lastMessage ?. animate ,
251
+ isStatusResponse : lastMessage ?. type === "statusResponse" ,
252
+ } ;
253
+ } ;
271
254
272
255
function WorkspaceChatSuggestions ( { suggestions = [ ] , sendSuggestion } ) {
273
256
if ( suggestions . length === 0 ) return null ;
@@ -286,3 +269,78 @@ function WorkspaceChatSuggestions({ suggestions = [], sendSuggestion }) {
286
269
</ div >
287
270
) ;
288
271
}
272
+
273
+ /**
274
+ * Builds the history of messages for the chat.
275
+ * This is mostly useful for rendering the history in a way that is easy to understand.
276
+ * as well as compensating for agent thinking and other messages that are not part of the history, but
277
+ * are still part of the chat.
278
+ *
279
+ * @param {Object } param0 - The parameters for building the messages.
280
+ * @param {Array } param0.history - The history of messages.
281
+ * @param {Object } param0.workspace - The workspace object.
282
+ * @param {Function } param0.regenerateAssistantMessage - The function to regenerate the assistant message.
283
+ * @param {Function } param0.saveEditedMessage - The function to save the edited message.
284
+ * @param {Function } param0.forkThread - The function to fork the thread.
285
+ * @returns {Array } The compiled history of messages.
286
+ */
287
+ function buildMessages ( {
288
+ history,
289
+ workspace,
290
+ regenerateAssistantMessage,
291
+ saveEditedMessage,
292
+ forkThread,
293
+ } ) {
294
+ return history . reduce ( ( acc , props , index ) => {
295
+ const isLastBotReply =
296
+ index === history . length - 1 && props . role === "assistant" ;
297
+
298
+ if ( props ?. type === "statusResponse" && ! ! props . content ) {
299
+ if ( acc . length > 0 && Array . isArray ( acc [ acc . length - 1 ] ) ) {
300
+ acc [ acc . length - 1 ] . push ( props ) ;
301
+ } else {
302
+ acc . push ( [ props ] ) ;
303
+ }
304
+ return acc ;
305
+ }
306
+
307
+ if ( props . type === "rechartVisualize" && ! ! props . content ) {
308
+ acc . push (
309
+ < Chartable key = { props . uuid } workspace = { workspace } props = { props } />
310
+ ) ;
311
+ } else if ( isLastBotReply && props . animate ) {
312
+ acc . push (
313
+ < PromptReply
314
+ key = { props . uuid || v4 ( ) }
315
+ uuid = { props . uuid }
316
+ reply = { props . content }
317
+ pending = { props . pending }
318
+ sources = { props . sources }
319
+ error = { props . error }
320
+ workspace = { workspace }
321
+ closed = { props . closed }
322
+ />
323
+ ) ;
324
+ } else {
325
+ acc . push (
326
+ < HistoricalMessage
327
+ key = { index }
328
+ message = { props . content }
329
+ role = { props . role }
330
+ workspace = { workspace }
331
+ sources = { props . sources }
332
+ feedbackScore = { props . feedbackScore }
333
+ chatId = { props . chatId }
334
+ error = { props . error }
335
+ attachments = { props . attachments }
336
+ regenerateMessage = { regenerateAssistantMessage }
337
+ isLastMessage = { isLastBotReply }
338
+ saveEditedMessage = { saveEditedMessage }
339
+ forkThread = { forkThread }
340
+ metrics = { props . metrics }
341
+ />
342
+ ) ;
343
+ }
344
+ return acc ;
345
+ } , [ ] ) ;
346
+ }
0 commit comments