11import { useTranslation } from 'react-i18next' ;
2- import React , { useEffect , useRef , useState } from 'react' ;
2+ import React , { useCallback , useEffect , useRef , useState } from 'react' ;
33import { useRecoilValue } from 'recoil' ;
44import { FlexBox , Icon , Text , TextArea } from '@ui5/webcomponents-react' ;
55import Message , {
@@ -16,14 +16,25 @@ import getFollowUpQuestions from 'components/KymaCompanion/api/getFollowUpQuesti
1616import getChatResponse from 'components/KymaCompanion/api/getChatResponse' ;
1717import { usePromptSuggestions } from 'components/KymaCompanion/hooks/usePromptSuggestions' ;
1818import { AIError } from '../KymaCompanion' ;
19+ import ContextLabel from './ContextLabel/ContextLabel' ;
1920import './Chat.scss' ;
2021
21- enum Author {
22+ export enum Author {
2223 USER = 'user' ,
2324 AI = 'ai' ,
2425}
2526
26- export interface MessageType {
27+ export enum ChatItemType {
28+ MESSAGE = 'message' ,
29+ CONTEXT = 'context' ,
30+ }
31+
32+ interface BaseChatItem {
33+ type : ChatItemType ;
34+ }
35+
36+ interface MessageChatItem extends BaseChatItem {
37+ type : ChatItemType . MESSAGE ;
2738 author : Author ;
2839 messageChunks : MessageChunk [ ] ;
2940 isLoading : boolean ;
@@ -32,9 +43,16 @@ export interface MessageType {
3243 hasError ?: boolean | undefined ;
3344}
3445
46+ interface ContextChatItem extends BaseChatItem {
47+ type : ChatItemType . CONTEXT ;
48+ labelText : string ;
49+ }
50+
51+ export type ChatItem = MessageChatItem | ContextChatItem ;
52+
3553type ChatProps = {
36- chatHistory : MessageType [ ] ;
37- setChatHistory : React . Dispatch < React . SetStateAction < MessageType [ ] > > ;
54+ chatHistory : ChatItem [ ] ;
55+ setChatHistory : React . Dispatch < React . SetStateAction < ChatItem [ ] > > ;
3856 loading : boolean ;
3957 setLoading : React . Dispatch < React . SetStateAction < boolean > > ;
4058 isReset : boolean ;
@@ -66,22 +84,54 @@ export const Chat = ({
6684 initialSuggestionsLoading,
6785 currentResource,
6886 } = usePromptSuggestions ( isReset , setIsReset , {
69- skip : chatHistory . length > 1 ,
87+ skip :
88+ chatHistory . filter ( item => item . type === ChatItemType . MESSAGE ) . length > 1 ,
7089 } ) ;
7190
72- const addMessage = ( { author, messageChunks, isLoading } : MessageType ) => {
73- setChatHistory ( prevItems =>
74- prevItems . concat ( { author, messageChunks, isLoading } ) ,
75- ) ;
91+ const handleContextChange = useCallback ( ( ) => {
92+ const newContext = currentResource . resourceName
93+ ? `${ currentResource . resourceType } > ${ currentResource . resourceName } `
94+ : currentResource . resourceType ;
95+
96+ const lastContextItem = chatHistory
97+ . slice ( )
98+ . reverse ( )
99+ . find (
100+ ( item ) : item is ContextChatItem => item . type === ChatItemType . CONTEXT ,
101+ ) ;
102+
103+ if ( ! lastContextItem || lastContextItem . labelText !== newContext ) {
104+ const contextItem : ContextChatItem = {
105+ type : ChatItemType . CONTEXT ,
106+ labelText : newContext ,
107+ } ;
108+ setChatHistory ( prevItems => prevItems . concat ( contextItem ) ) ;
109+ }
110+ } , [ currentResource , chatHistory , setChatHistory ] ) ;
111+
112+ const addMessage = ( {
113+ author,
114+ messageChunks,
115+ isLoading,
116+ } : Omit < MessageChatItem , 'type' > ) => {
117+ const messageItem : MessageChatItem = {
118+ type : ChatItemType . MESSAGE ,
119+ author,
120+ messageChunks,
121+ isLoading,
122+ } ;
123+ setChatHistory ( prevItems => prevItems . concat ( messageItem ) ) ;
76124 } ;
77125
78- const updateLatestMessage = ( updates : Partial < MessageType > ) => {
79- setChatHistory ( prevMessages => {
80- if ( prevMessages . length === 0 ) return prevMessages ;
126+ const updateLatestMessage = ( updates : Partial < MessageChatItem > ) => {
127+ setChatHistory ( prevItems => {
128+ if ( prevItems . length === 0 ) return prevItems ;
129+
130+ const [ latestItem ] = prevItems . slice ( - 1 ) ;
131+ if ( latestItem . type !== ChatItemType . MESSAGE ) return prevItems ;
81132
82- const [ latestMessage ] = prevMessages . slice ( - 1 ) ;
83- return prevMessages . slice ( 0 , - 1 ) . concat ( {
84- ...latestMessage ,
133+ return prevItems . slice ( 0 , - 1 ) . concat ( {
134+ ...latestItem ,
85135 ...updates ,
86136 } ) ;
87137 } ) ;
@@ -91,35 +141,42 @@ export const Chat = ({
91141 response : MessageChunk ,
92142 isLoading : boolean ,
93143 ) => {
94- setChatHistory ( prevMessages => {
95- const [ latestMessage ] = prevMessages . slice ( - 1 ) ;
96- return prevMessages . slice ( 0 , - 1 ) . concat ( {
97- ...latestMessage ,
98- messageChunks : latestMessage . messageChunks . concat ( response ) ,
144+ setChatHistory ( prevItems => {
145+ const [ latestItem ] = prevItems . slice ( - 1 ) ;
146+ if ( latestItem . type !== ChatItemType . MESSAGE ) return prevItems ;
147+
148+ return prevItems . slice ( 0 , - 1 ) . concat ( {
149+ ...latestItem ,
150+ messageChunks : latestItem . messageChunks . concat ( response ) ,
99151 isLoading,
100152 } ) ;
101153 } ) ;
102154 } ;
103155
104156 const removeLastMessage = ( ) => {
105- setChatHistory ( prevMessages => prevMessages . slice ( 0 , - 1 ) ) ;
157+ setChatHistory ( prevItems => prevItems . slice ( 0 , - 1 ) ) ;
106158 } ;
107159
108160 const setErrorOnLastUserMsg = ( ) => {
109- setChatHistory ( prevMessages => {
110- const lastUserMsgIdx = prevMessages . findLastIndex ( msg => {
111- return msg . author === 'user' ;
112- } ) ;
161+ setChatHistory ( prevItems => {
162+ const lastUserMsgIdx = prevItems . findLastIndex (
163+ item =>
164+ item . type === ChatItemType . MESSAGE && item . author === Author . USER ,
165+ ) ;
166+
113167 if ( lastUserMsgIdx === - 1 ) {
114- return prevMessages ;
168+ return prevItems ;
115169 }
116170
117- return prevMessages . map ( ( msg , idx ) => {
118- if ( idx === lastUserMsgIdx ) {
119- msg . hasError = true ;
120- msg . isLoading = false ;
171+ return prevItems . map ( ( item , idx ) => {
172+ if ( idx === lastUserMsgIdx && item . type === ChatItemType . MESSAGE ) {
173+ return {
174+ ...item ,
175+ hasError : true ,
176+ isLoading : false ,
177+ } ;
121178 }
122- return msg ;
179+ return item ;
123180 } ) ;
124181 } ) ;
125182 } ;
@@ -220,11 +277,14 @@ export const Chat = ({
220277 } ;
221278
222279 const retryPreviousPrompt = ( ) => {
223- const lastUserMsgIdx = chatHistory . findLastIndex (
224- v => v . author === Author . USER ,
280+ const messageItems = chatHistory . filter (
281+ ( item ) : item is MessageChatItem => item . type === ChatItemType . MESSAGE ,
282+ ) ;
283+ const lastUserMsg = messageItems . findLast (
284+ msg => msg . author === Author . USER ,
225285 ) ;
226- const previousPrompt = chatHistory . at ( lastUserMsgIdx ) ?. messageChunks [ 0 ] . data
227- . answer . content ;
286+ const previousPrompt = lastUserMsg ?. messageChunks [ 0 ] . data . answer . content ;
287+
228288 if ( previousPrompt ) {
229289 removeLastMessage ( ) ;
230290 sendPrompt ( previousPrompt ) ;
@@ -234,6 +294,9 @@ export const Chat = ({
234294 const sendPrompt = ( query : string ) => {
235295 setError ( { message : null , displayRetry : false } ) ;
236296 setLoading ( true ) ;
297+
298+ handleContextChange ( ) ;
299+
237300 addMessage ( {
238301 author : Author . USER ,
239302 messageChunks : [
@@ -285,7 +348,11 @@ export const Chat = ({
285348 } ;
286349
287350 useEffect ( ( ) => {
288- if ( chatHistory . length === 1 ) {
351+ const messageCount = chatHistory . filter (
352+ item => item . type === ChatItemType . MESSAGE ,
353+ ) . length ;
354+
355+ if ( messageCount === 1 ) {
289356 if ( initialSuggestionsLoading ) {
290357 updateLatestMessage ( {
291358 messageChunks : [
@@ -338,32 +405,36 @@ export const Chat = ({
338405 className = "chat-list sap-margin-x-tiny sap-margin-top-small"
339406 ref = { containerRef }
340407 >
341- { chatHistory . map ( ( message , index ) => {
408+ { chatHistory . map ( ( item , index ) => {
409+ if ( item . type === ChatItemType . CONTEXT ) {
410+ return < ContextLabel key = { index } labelText = { item . labelText } /> ;
411+ }
412+
342413 const isLast = index === chatHistory . length - 1 ;
343- return message . author === Author . AI ? (
414+ return item . author === Author . AI ? (
344415 < React . Fragment key = { index } >
345416 < Message
346- author = { message . author }
347- messageChunks = { message . messageChunks }
348- isLoading = { message . isLoading }
349- hasError = { message ?. hasError ?? false }
417+ author = { item . author }
418+ messageChunks = { item . messageChunks }
419+ isLoading = { item . isLoading }
420+ hasError = { item ?. hasError ?? false }
350421 isLatestMessage = { isLast }
351422 />
352- { isLast && ! message . isLoading && (
423+ { isLast && ! item . isLoading && (
353424 < Bubbles
354425 onClick = { sendPrompt }
355- suggestions = { message . suggestions }
356- isLoading = { message . suggestionsLoading ?? false }
426+ suggestions = { item . suggestions }
427+ isLoading = { item . suggestionsLoading ?? false }
357428 />
358429 ) }
359430 </ React . Fragment >
360431 ) : (
361432 < Message
362433 author = { Author . USER }
363434 key = { index }
364- messageChunks = { message . messageChunks }
365- isLoading = { message . isLoading }
366- hasError = { message ?. hasError ?? false }
435+ messageChunks = { item . messageChunks }
436+ isLoading = { item . isLoading }
437+ hasError = { item ?. hasError ?? false }
367438 isLatestMessage = { isLast }
368439 />
369440 ) ;
0 commit comments