@@ -22,16 +22,17 @@ import { useAppDispatch, useAppSelector } from 'redux/store';
2222import { tryParsingOutQuery } from 'utils/tryParsingOutQuery' ;
2323
2424import styles from "./Chat.module.scss"
25+ import { LinkQChatMessageType } from 'redux/chatHistorySlice' ;
2526
2627
2728export function Chat ( ) {
2829 const fullChatHistory = useAppSelector ( state => state . chatHistory . fullChatHistory )
2930 const simpleChatHistory = useAppSelector ( state => state . chatHistory . simpleChatHistory )
3031
31- const showFullChatHistory = useAppSelector ( state => state . chatHistory . showFullChatHistory )
32+ const chatHistoryDisplay = useAppSelector ( state => state . chatHistory . chatHistoryDisplay )
3233
33- //based on showFullChatHistory , decide which chat history to display to the user
34- const chatHistory = showFullChatHistory ? fullChatHistory : simpleChatHistory
34+ //based on chatHistoryDisplay , decide which chat history to display to the user
35+ const chatHistory = chatHistoryDisplay === "full" ? fullChatHistory : simpleChatHistory
3536
3637 const chatScrollBottomRef = useRef < HTMLDivElement > ( null )
3738 useEffect ( ( ) => {
@@ -55,20 +56,16 @@ export function Chat() {
5556 < Settings />
5657
5758 < div id = { styles [ "chat-scroll-container" ] } >
58- { chatHistory . map ( ( c , i ) => {
59- return (
60- < div key = { i } className = { `${ styles [ "chat-row" ] } ${ styles [ c . role ] } ` } >
61- < div className = { styles [ "chat-justify" ] } >
62- { showFullChatHistory && < p > { c . name } , chat #{ c . chatId } </ p > }
63- {
64- c . role === "assistant"
65- ? < RenderLLMResponse text = { c . content } setInputText = { setInputText } />
66- : < pre className = { styles . chat } > { c . content } </ pre >
67- }
68- </ div >
69- </ div >
70- )
71- } ) }
59+ < br />
60+ { chatHistoryDisplay === "condensed" ? (
61+ condenseChat ( fullChatHistory ) . map ( ( c , i ) => (
62+ < RenderCondensedMessage key = { i } condensedChat = { c } setInputText = { setInputText } />
63+ ) )
64+ ) : (
65+ chatHistory . map ( ( c , i ) => (
66+ < RenderChatMessage key = { i } chat = { c } setInputText = { setInputText } />
67+ ) )
68+ ) }
7269 < div ref = { chatScrollBottomRef } />
7370 </ div >
7471
@@ -99,6 +96,68 @@ export function Chat() {
9996 )
10097}
10198
99+ function RenderCondensedMessage ( {
100+ condensedChat,
101+ setInputText,
102+ } :{
103+ condensedChat :CondensedChatType ,
104+ setInputText : React . Dispatch < React . SetStateAction < string > > ,
105+ } ) {
106+ const [ showDetails , setShowDetails ] = useState < boolean > ( false )
107+
108+ const firstChatMessage = condensedChat [ 0 ]
109+ return (
110+ < div className = { styles [ "condensed-chat" ] } >
111+ { firstChatMessage . stage && (
112+ < >
113+ < p > < b > { firstChatMessage . stage . mainStage } </ b > </ p >
114+ < p > { firstChatMessage . stage . subStage } </ p >
115+ { firstChatMessage . stage . description && < p > { firstChatMessage . stage . description } </ p > }
116+ </ >
117+ ) }
118+ < div >
119+ < a aria-label = "Show Details" onClick = { ( ) => setShowDetails ( ! showDetails ) } >
120+ { showDetails ? "Hide Details" : "Show Full Details" }
121+ </ a >
122+ </ div >
123+
124+ { showDetails && (
125+ < div >
126+ < br />
127+ < div className = { styles [ "show-all-content-container" ] } >
128+ { condensedChat . map ( ( c , i ) => (
129+ < RenderChatMessage key = { i } chat = { c } setInputText = { setInputText } />
130+ ) ) }
131+ </ div >
132+ </ div >
133+ ) }
134+ </ div >
135+ )
136+ }
137+
138+ function RenderChatMessage ( {
139+ chat,
140+ setInputText,
141+ } :{
142+ chat :LinkQChatMessageType ,
143+ setInputText : React . Dispatch < React . SetStateAction < string > > ,
144+ } ) {
145+ const chatHistoryDisplay = useAppSelector ( state => state . chatHistory . chatHistoryDisplay )
146+
147+ return (
148+ < div className = { `${ styles [ "chat-row" ] } ${ styles [ chat . role ] } ` } >
149+ < div className = { styles [ "chat-justify" ] } >
150+ { chatHistoryDisplay === "full" || chatHistoryDisplay === "condensed" && < p > { chat . name } , chat #{ chat . chatId } </ p > }
151+ {
152+ chat . role === "assistant"
153+ ? < RenderLLMResponse text = { chat . content } setInputText = { setInputText } />
154+ : < pre className = { styles . chat } > { chat . content } </ pre >
155+ }
156+ </ div >
157+ </ div >
158+ )
159+ }
160+
102161function RenderLLMResponse ( {
103162 setInputText,
104163 text,
@@ -220,6 +279,60 @@ function LinkQDetailedBadgeStatus() {
220279 }
221280
222281 return (
223- < div className = { styles [ "chat-status-badge" ] } > < Badge color = { color } > { displayMessage } </ Badge > </ div >
282+ < div className = { styles [ "chat-status-badge" ] } >
283+ < Badge color = { color } > { displayMessage } </ Badge >
284+ </ div >
224285 )
225- }
286+ }
287+
288+ //the condensed chat type is just one or two grouped chat messages
289+ type CondensedChatType = LinkQChatMessageType [ ]
290+
291+ /**
292+ * Converts the full chat history into the condensed/grouped view for better traceability.
293+ * If two neighboring chat messages have the same stage details, they are condensed/grouped together
294+ * @param fullChatHistory
295+ * @returns array of condensed chat types
296+ */
297+ function condenseChat ( fullChatHistory : LinkQChatMessageType [ ] ) :CondensedChatType [ ] {
298+ const condensedChat :CondensedChatType [ ] = [ ] ;
299+
300+ //loop through the full chat history
301+ for ( let i = 0 ; i < fullChatHistory . length ; ++ i ) {
302+ const currentChatMessage = fullChatHistory [ i ]
303+ const nextChatMessage = fullChatHistory . at ( i + 1 )
304+ if ( i === 0 ) { //HARDCODED ignore the initial system prompt
305+ continue ;
306+ }
307+ //else if the current and next chat message have the same stage details
308+ else if ( nextChatMessage && messagesHaveMatchingStages ( currentChatMessage , nextChatMessage ) ) {
309+ //condense these two messages together
310+ condensedChat . push ( [
311+ currentChatMessage ,
312+ nextChatMessage
313+ ] ) ;
314+ ++ i ; //skip over this next message in the subsequent iteration
315+ }
316+ //else this chat message can stand alone by itself
317+ else if ( currentChatMessage . stage ) {
318+ condensedChat . push ( [ currentChatMessage ] )
319+ }
320+ //else the chat message doesn't have stage info
321+ }
322+
323+ return condensedChat
324+ }
325+
326+ /**
327+ * Checks whether two messages have the same main and sub stage
328+ * @param chatMessage1
329+ * @param chatMessage2
330+ * @returns true if the main and sub stages match (including in the stage is undefined), else false
331+ */
332+ function messagesHaveMatchingStages (
333+ chatMessage1 : LinkQChatMessageType ,
334+ chatMessage2 : LinkQChatMessageType ,
335+ ) {
336+ return chatMessage1 . stage ?. mainStage === chatMessage2 . stage ?. mainStage
337+ || chatMessage1 . stage ?. subStage === chatMessage2 . stage ?. subStage
338+ }
0 commit comments