Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions public/i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,8 @@ kyma-companion:
error:
title: Service is interrupted
subtitle: A temporary interruption occured. Please try again.
chat-error: An error occurred
suggestions-error: No suggestions available
introduction: Hi, I am your Kyma assistant! You can ask me any question, and I will try to help you to the best of my abilities. Meanwhile, you can check the suggested questions below; you may find them helpful!
introduction-no-suggestions: Hi, I am your Kyma assistant! You can ask me any question, and I will try to help you to the best of my abilities. While I don't have any initial suggestions for this resource, feel free to ask me anything you'd like!
placeholder: Message Joule
Expand Down
8 changes: 2 additions & 6 deletions src/components/KymaCompanion/api/getChatResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type GetChatResponseArgs = {
resourceName?: string;
sessionID: string;
handleChatResponse: (chunk: MessageChunk) => void;
handleError: (error?: Error) => void;
handleError: (error?: string) => void;
clusterUrl: string;
clusterAuth: ClusterAuth;
certificateAuthorityData: string;
Expand Down Expand Up @@ -87,7 +87,7 @@ function readChunk(
reader: ReadableStreamDefaultReader<Uint8Array>,
decoder: TextDecoder,
handleChatResponse: (chunk: any) => void,
handleError: (error?: Error) => void,
handleError: (error?: string) => void,
sessionID: string,
) {
reader
Expand All @@ -98,10 +98,6 @@ function readChunk(
}
const receivedString = decoder.decode(value, { stream: true });
const chunk = JSON.parse(receivedString);
if (chunk?.data?.error) {
handleError(chunk.data.error);
return;
}
handleChatResponse(chunk);
readChunk(reader, decoder, handleChatResponse, handleError, sessionID);
})
Expand Down
9 changes: 6 additions & 3 deletions src/components/KymaCompanion/api/getFollowUpQuestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getClusterConfig } from 'state/utils/getBackendInfo';
interface GetFollowUpQuestionsParams {
sessionID?: string;
handleFollowUpQuestions: (results: any) => void;
handleError: (error?: Error) => void;
handleFollowUpError: () => void;
clusterUrl: string;
token: string;
certificateAuthorityData: string;
Expand All @@ -12,7 +12,7 @@ interface GetFollowUpQuestionsParams {
export default async function getFollowUpQuestions({
sessionID = '',
handleFollowUpQuestions,
handleError,
handleFollowUpError,
clusterUrl,
token,
certificateAuthorityData,
Expand All @@ -35,10 +35,13 @@ export default async function getFollowUpQuestions({
});

const promptSuggestions = (await response.json()).promptSuggestions;
if (!promptSuggestions) {
throw new Error('No follow-up qeustions available');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error('No follow-up qeustions available');
throw new Error('No follow-up questions available');

}

handleFollowUpQuestions(promptSuggestions);
} catch (error) {
handleError();
handleFollowUpError();
console.error('Error fetching data:', error);
}
}
7 changes: 2 additions & 5 deletions src/components/KymaCompanion/components/Chat/Chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
display: none;
}

.left-aligned {
.message-container.left-aligned {
position: relative;
align-self: flex-start;
border-radius: 8px 8px 8px 0;
}

.right-aligned {
.message-container.right-aligned {
position: relative;
align-self: flex-end;
border-radius: 8px 8px 0 8px;
background-color: var(--sapAssistant_Question_Background);
}
}

Expand Down
89 changes: 67 additions & 22 deletions src/components/KymaCompanion/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { authDataState } from 'state/authDataAtom';
import getFollowUpQuestions from 'components/KymaCompanion/api/getFollowUpQuestions';
import getChatResponse from 'components/KymaCompanion/api/getChatResponse';
import { usePromptSuggestions } from 'components/KymaCompanion/hooks/usePromptSuggestions';
import { AIError } from '../KymaCompanion';
import './Chat.scss';

export interface MessageType {
Expand All @@ -19,6 +20,7 @@ export interface MessageType {
isLoading: boolean;
suggestions?: string[];
suggestionsLoading?: boolean;
hasError?: boolean | undefined;
}

type ChatProps = {
Expand All @@ -28,8 +30,8 @@ type ChatProps = {
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
isReset: boolean;
setIsReset: React.Dispatch<React.SetStateAction<boolean>>;
error: string | null;
setError: React.Dispatch<React.SetStateAction<string | null>>;
error: AIError;
setError: React.Dispatch<React.SetStateAction<AIError>>;
};

export const Chat = ({
Expand Down Expand Up @@ -78,18 +80,34 @@ export const Chat = ({

const handleChatResponse = (response: MessageChunk) => {
const isLoading = response?.data?.answer?.next !== '__end__';

if (!isLoading) {
setFollowUpLoading();
getFollowUpQuestions({
sessionID,
handleFollowUpQuestions,
handleError,
clusterUrl: cluster.currentContext.cluster.cluster.server,
token: authData.token,
certificateAuthorityData:
cluster.currentContext.cluster.cluster['certificate-authority-data'],
});
const finalTask = response.data.answer?.tasks?.at(-1);
const hasError = finalTask?.status === 'error';

if (hasError) {
const allTasksError =
response.data.answer?.tasks?.every(task => task.status === 'error') ??
false;
const displayRetry = response.data.error !== null || allTasksError;
handleError(response.data.answer.content, displayRetry);
return;
} else {
setFollowUpLoading();
getFollowUpQuestions({
sessionID,
handleFollowUpQuestions,
handleFollowUpError,
clusterUrl: cluster.currentContext.cluster.cluster.server,
token: authData.token,
certificateAuthorityData:
cluster.currentContext.cluster.cluster[
'certificate-authority-data'
],
});
}
}

setChatHistory(prevMessages => {
const [latestMessage] = prevMessages.slice(-1);
return prevMessages.slice(0, -1).concat({
Expand All @@ -101,7 +119,7 @@ export const Chat = ({
};

const setFollowUpLoading = () => {
setError(null);
setError({ message: null, displayRetry: false });
setLoading(true);
updateLatestMessage({ suggestionsLoading: true });
};
Expand All @@ -111,14 +129,36 @@ export const Chat = ({
setLoading(false);
};

const handleError = (error?: Error) => {
setError(error?.message ?? t('kyma-companion.error.subtitle'));
const handleFollowUpError = () => {
updateLatestMessage({
hasError: true,
suggestionsLoading: false,
});
setLoading(false);
};

const handleError = (error?: string, displayRetry?: boolean) => {
const errorMessage = error ?? t('kyma-companion.error.subtitle') ?? '';
setError({
message: errorMessage,
displayRetry: displayRetry ?? false,
});
setChatHistory(prevItems => prevItems.slice(0, -1));
updateLatestMessage({ hasError: true });
setLoading(false);
};

const retryPreviousPrompt = () => {
const previousPrompt = chatHistory.at(-1)?.messageChunks[0].data.answer
.content;
if (previousPrompt) {
setChatHistory(prevItems => prevItems.slice(0, -1));
sendPrompt(previousPrompt);
}
};

const sendPrompt = (query: string) => {
setError(null);
setError({ message: null, displayRetry: false });
setLoading(true);
addMessage({
author: 'user',
Expand Down Expand Up @@ -225,12 +265,15 @@ export const Chat = ({
ref={containerRef}
>
{chatHistory.map((message, index) => {
const isLast = index === chatHistory.length - 1;
return message.author === 'ai' ? (
<React.Fragment key={index}>
<Message
className="left-aligned sap-margin-begin-tiny"
author="ai"
messageChunks={message.messageChunks}
isLoading={message.isLoading}
hasError={message?.hasError ?? false}
isLatestMessage={isLast}
/>
{index === chatHistory.length - 1 && !message.isLoading && (
<Bubbles
Expand All @@ -242,18 +285,20 @@ export const Chat = ({
</React.Fragment>
) : (
<Message
author="user"
key={index}
className="right-aligned sap-margin-end-tiny"
messageChunks={message.messageChunks}
isLoading={message.isLoading}
hasError={message?.hasError ?? false}
isLatestMessage={isLast}
/>
);
})}
{error && (
{error.message && (
<ErrorMessage
errorMessage={error}
errorOnInitialMessage={chatHistory.length === 0}
retryPrompt={() => {}}
errorMessage={error.message ?? t('kyma-companion.error.subtitle')}
retryPrompt={() => retryPreviousPrompt()}
displayRetry={error.displayRetry}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
import { Button, Card, IllustratedMessage } from '@ui5/webcomponents-react';
import {
Button,
Card,
IllustratedMessage,
Text,
} from '@ui5/webcomponents-react';
import { useTranslation } from 'react-i18next';

interface ErrorMessageProps {
errorMessage: string;
errorOnInitialMessage: boolean;
displayRetry: boolean;
retryPrompt: () => void;
}

export default function ErrorMessage({
errorMessage,
errorOnInitialMessage,
displayRetry,
retryPrompt,
}: ErrorMessageProps): JSX.Element {
const { t } = useTranslation();

return (
<div className="sap-margin-x-tiny sap-margin-y-small">
<div className="sap-margin-x-tiny sap-margin-bottom-small">
<Card>
<IllustratedMessage
name="Connection"
design="Spot"
key="error-message"
titleText={t('kyma-companion.error.title')}
subtitleText={errorMessage}
subtitle={
<Text className="sap-margin-bottom-tiny">{errorMessage}</Text>
}
className="sap-margin-top-small no-padding"
>
{errorOnInitialMessage && (
{displayRetry && (
<Button
onClick={retryPrompt}
design="Emphasized"
Expand Down
Loading
Loading