Date: Tue, 2 Apr 2024 12:29:49 +0200
Subject: [PATCH 03/38] feat: added loading indicator & connected initial
suggestions with chat api
---
.../AIassistant/components/AIOpener.js | 88 +++++++++++++++++++
.../AIassistant/components/AIOpener.scss | 14 +++
.../AIassistant/components/Chat/Chat.js | 71 ++++++++-------
.../components/Chat/messages/Bubbles.js | 13 +--
.../components/Chat/messages/PlainMessage.js | 7 +-
.../AIassistant/state/initalPromptAtom.ts | 12 +++
.../AIassistant/utils/getChatResponse.js | 2 -
.../views/ClusterOverview/ClusterOverview.js | 79 +----------------
.../ClusterOverview/ClusterOverview.scss | 11 ---
.../HelmReleases/HelmReleasesDetails.js | 14 +--
.../ResourceDetails/ResourceDetails.js | 13 +--
src/styles/index.scss | 4 -
12 files changed, 170 insertions(+), 158 deletions(-)
create mode 100644 src/components/AIassistant/components/AIOpener.js
create mode 100644 src/components/AIassistant/components/AIOpener.scss
create mode 100644 src/components/AIassistant/state/initalPromptAtom.ts
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
new file mode 100644
index 0000000000..b400b405b5
--- /dev/null
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -0,0 +1,88 @@
+import {
+ Button,
+ CustomListItem,
+ FlexBox,
+ Icon,
+ Input,
+ List,
+ Popover,
+ Text,
+ Title,
+} from '@ui5/webcomponents-react';
+import { spacing } from '@ui5/webcomponents-react-base';
+import { useTranslation } from 'react-i18next';
+import { useState } from 'react';
+import { useSetRecoilState } from 'recoil';
+import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
+import { initialPromptState } from '../state/initalPromptAtom';
+import getPromptSuggestions from 'components/AIassistant/utils/getPromptSuggestions';
+import './AIOpener.scss';
+
+export default function AIOpener() {
+ const { t } = useTranslation();
+ const setOpenAssistant = useSetRecoilState(showAIassistantState);
+ const setInitialPrompt = useSetRecoilState(initialPromptState);
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const [suggestions, setSuggestions] = useState([]);
+
+ return (
+ <>
+
+
{}} />}
+ value={''}
+ onKeyDown={e => {}}
+ onInput={e => {}}
+ placeholder="Ask about this cluster"
+ />
+ }
+ onAfterClose={() => setPopoverOpen(false)}
+ opener="openPopoverBtn"
+ placementType="Bottom"
+ horizontalAlign="Right"
+ >
+ {'Suggestions'}
+
+
+ {suggestions.map((suggestion, index) => (
+ {
+ setInitialPrompt(suggestion);
+ setPopoverOpen(false);
+ setOpenAssistant(true);
+ }}
+ >
+
+ {suggestion}
+
+
+
+ ))}
+
+
+
+ >
+ );
+}
diff --git a/src/components/AIassistant/components/AIOpener.scss b/src/components/AIassistant/components/AIOpener.scss
new file mode 100644
index 0000000000..c85d271016
--- /dev/null
+++ b/src/components/AIassistant/components/AIOpener.scss
@@ -0,0 +1,14 @@
+.ai-button {
+ color: var(--sapContent_Illustrative_Color1);
+}
+
+.list-item-content {
+ width: 100%;
+
+ .text {
+ max-width: 85%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index ad3bb40496..5dc520a518 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -1,10 +1,12 @@
+import { useTranslation } from 'react-i18next';
import { useEffect, useRef, useState } from 'react';
+import { useRecoilValue } from 'recoil';
import { FlexBox, Icon, Input } from '@ui5/webcomponents-react';
import { spacing } from '@ui5/webcomponents-react-base';
+import { initialPromptState } from 'components/AIassistant/state/initalPromptAtom';
import PlainMessage from './messages/PlainMessage';
import Bubbles from './messages/Bubbles';
import ErrorMessage from './messages/ErrorMessage';
-import { useTranslation } from 'react-i18next';
import getChatResponse from 'components/AIassistant/utils/getChatResponse';
import './Chat.scss';
@@ -14,27 +16,35 @@ export default function Chat() {
const [inputValue, setInputValue] = useState('');
const [chatHistory, setChatHistory] = useState([]);
const [errorOccured, setErrorOccured] = useState(false);
- const addMessage = (author, message) => {
- setChatHistory(prevItems => [...prevItems, { author, message }]);
+ const initialPrompt = useRecoilValue(initialPromptState);
+ const addMessage = (author, message, isLoading) => {
+ setChatHistory(prevItems => [...prevItems, { author, message, isLoading }]);
};
const handleSuccess = response => {
- addMessage('ai', response);
+ setChatHistory(prevItems => {
+ const newArray = [...prevItems];
+ newArray[newArray.length - 1] = {
+ author: 'ai',
+ message: response,
+ isLoading: false,
+ };
+ return newArray;
+ });
};
const handleError = () => {
setErrorOccured(true);
- setChatHistory(prevItems => prevItems.slice(0, -1));
+ setChatHistory(prevItems => prevItems.slice(0, -2));
};
- const onClickBubble = prompt => {
+ const onSendPrompt = prompt => {
setErrorOccured(false);
- addMessage('user', prompt);
- return getChatResponse(prompt, handleSuccess, handleError);
+ addMessage('user', prompt, false);
+ getChatResponse(prompt, handleSuccess, handleError);
+ addMessage('ai', null, true);
};
const onSubmitInput = () => {
const prompt = inputValue;
setInputValue('');
- setErrorOccured(false);
- addMessage('user', prompt);
- return getChatResponse(prompt, handleSuccess, handleError);
+ onSendPrompt(prompt);
};
const scrollToBottom = () => {
@@ -45,6 +55,11 @@ export default function Chat() {
});
};
+ useEffect(() => {
+ if (chatHistory.length === 0) onSendPrompt(initialPrompt);
+ // eslint-disable-next-line
+ }, []);
+
useEffect(() => {
const delay = errorOccured ? 500 : 0;
setTimeout(() => {
@@ -52,6 +67,12 @@ export default function Chat() {
}, delay);
}, [chatHistory, errorOccured]);
+ const test_suggestions = [
+ 'test123123123123123xyzxyzuwquxzytsabcde123456',
+ 'Throw an error',
+ 'What is your favorite football team?',
+ ];
+
return (
-
{chatHistory.map((message, index) => {
return message.author === 'ai' ? (
<>
-
+ {index === chatHistory.length - 1 && !message.isLoading && (
+
+ )}
>
) : (
>
- ) : (
+ return (
{suggestions.map((suggestion, index) => (
diff --git a/src/components/AIassistant/components/Chat/messages/PlainMessage.js b/src/components/AIassistant/components/Chat/messages/PlainMessage.js
index 0351306f52..30b4855f65 100644
--- a/src/components/AIassistant/components/Chat/messages/PlainMessage.js
+++ b/src/components/AIassistant/components/Chat/messages/PlainMessage.js
@@ -1,10 +1,11 @@
-import { Text } from '@ui5/webcomponents-react';
+import { BusyIndicator, Text } from '@ui5/webcomponents-react';
import './PlainMessage.scss';
-export default function PlainMessage({ className, message }) {
+export default function PlainMessage({ className, message, isLoading }) {
return (
- {message}
+ {isLoading && }
+ {message && {message}}
);
}
diff --git a/src/components/AIassistant/state/initalPromptAtom.ts b/src/components/AIassistant/state/initalPromptAtom.ts
new file mode 100644
index 0000000000..863e3e63c6
--- /dev/null
+++ b/src/components/AIassistant/state/initalPromptAtom.ts
@@ -0,0 +1,12 @@
+import { atom, RecoilState } from 'recoil';
+
+type InitalPrompt = string;
+
+const DEFAULT_INITIAL_PROMPT = '';
+
+export const initialPromptState: RecoilState = atom(
+ {
+ key: 'initialPromptState',
+ default: DEFAULT_INITIAL_PROMPT,
+ },
+);
diff --git a/src/components/AIassistant/utils/getChatResponse.js b/src/components/AIassistant/utils/getChatResponse.js
index e441e85faa..76f39f6a58 100644
--- a/src/components/AIassistant/utils/getChatResponse.js
+++ b/src/components/AIassistant/utils/getChatResponse.js
@@ -39,10 +39,8 @@ export default async function getChatResponse(
},
).then(result => result.json());
handleSuccess(response);
- return true;
} catch (error) {
handleError();
console.error('Error fetching data:', error);
- return false;
}
}
diff --git a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
index cdf1c05491..9bccfa789e 100644
--- a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
+++ b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
@@ -1,15 +1,5 @@
-import React, { useState } from 'react';
-import {
- Button,
- CustomListItem,
- FlexBox,
- Icon,
- Input,
- List,
- Popover,
- Text,
- Title,
-} from '@ui5/webcomponents-react';
+import React from 'react';
+import { Button, FlexBox, Title } from '@ui5/webcomponents-react';
import { ClusterNodes } from './ClusterNodes';
import { ClusterValidation } from './ClusterValidation/ClusterValidation';
import { useFeature } from 'hooks/useFeature';
@@ -27,9 +17,7 @@ import { useNavigate } from 'react-router-dom';
import { deleteCluster } from 'components/Clusters/shared';
import { spacing } from '@ui5/webcomponents-react-base';
import './ClusterOverview.scss';
-import { useSetRecoilState } from 'recoil';
-import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
-import getPromptSuggestions from 'components/AIassistant/utils/getPromptSuggestions';
+import AIOpener from 'components/AIassistant/components/AIOpener';
const Injections = React.lazy(() =>
import('../../../Extensibility/ExtensibilityInjections'),
@@ -46,9 +34,6 @@ export function ClusterOverview() {
const [DeleteMessageBox, handleResourceDelete] = useDeleteResource({
resourceType: t('clusters.labels.name'),
});
- const setOpenAssistant = useSetRecoilState(showAIassistantState);
- const [popoverOpen, setPopoverOpen] = useState(false);
- const [suggestions, setSuggestions] = useState([]);
const actions = (
{data && }
diff --git a/src/components/Clusters/views/ClusterOverview/ClusterOverview.scss b/src/components/Clusters/views/ClusterOverview/ClusterOverview.scss
index 23050fd7db..d8b334a441 100644
--- a/src/components/Clusters/views/ClusterOverview/ClusterOverview.scss
+++ b/src/components/Clusters/views/ClusterOverview/ClusterOverview.scss
@@ -1,14 +1,3 @@
.gardener-provider {
text-transform: uppercase;
}
-
-.list-item-content {
- width: 100%;
-
- .text {
- max-width: 85%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-}
diff --git a/src/components/HelmReleases/HelmReleasesDetails.js b/src/components/HelmReleases/HelmReleasesDetails.js
index 872004a063..5eb2db2c73 100644
--- a/src/components/HelmReleases/HelmReleasesDetails.js
+++ b/src/components/HelmReleases/HelmReleasesDetails.js
@@ -8,25 +8,21 @@ import { HelmReleaseData } from './HelmReleaseData';
import { HelmReleaseStatus } from './HelmReleaseStatus';
import { OtherReleaseVersions } from './OtherReleaseVersions';
import { findRecentRelease } from './findRecentRelease';
-import { useSetRecoilState } from 'recoil';
import { ResourceCreate } from 'shared/components/ResourceCreate/ResourceCreate';
import { useUrl } from 'hooks/useUrl';
import YamlUploadDialog from 'resources/Namespaces/YamlUpload/YamlUploadDialog';
+import AIOpener from 'components/AIassistant/components/AIOpener';
import { ResourceDescription } from 'components/HelmReleases';
import HelmReleasesYaml from './HelmReleasesYaml';
import { ErrorBoundary } from 'shared/components/ErrorBoundary/ErrorBoundary';
import { showYamlTab } from './index';
import { Link } from 'shared/components/Link/Link';
-import { Button } from '@ui5/webcomponents-react';
-import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { createPortal } from 'react-dom';
function HelmReleasesDetails({ releaseName, namespace }) {
const { t } = useTranslation();
const { namespaceUrl } = useUrl();
- const setOpenAssistant = useSetRecoilState(showAIassistantState);
-
const { data, loading } = useGetList(s => s.type === 'helm.sh/release.v1')(
namespace === '-all-'
? `/api/v1/secrets?labelSelector=name==${releaseName}`
@@ -100,13 +96,7 @@ function HelmReleasesDetails({ releaseName, namespace }) {
/>
- setOpenAssistant(true)}
- className="ai-button"
- >
- {t('ai-assistant.use-ai')}
-
+
>
)}
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index a3bafe6ae0..11f1359131 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -23,16 +23,16 @@ import { useVersionWarning } from 'hooks/useVersionWarning';
import { Tooltip } from '../Tooltip/Tooltip';
import YamlUploadDialog from 'resources/Namespaces/YamlUpload/YamlUploadDialog';
+import AIOpener from 'components/AIassistant/components/AIOpener';
import { createPortal } from 'react-dom';
import ResourceDetailsCard from './ResourceDetailsCard';
import { ResourceStatusCard } from '../ResourceStatusCard/ResourceStatusCard';
import { EMPTY_TEXT_PLACEHOLDER } from '../../constants';
import { ReadableElapsedTimeFromNow } from '../ReadableElapsedTimeFromNow/ReadableElapsedTimeFromNow';
import { HintButton } from '../DescriptionHint/DescriptionHint';
-import { useRecoilValue, useSetRecoilState } from 'recoil';
+import { useRecoilValue } from 'recoil';
import { useFeature } from 'hooks/useFeature';
import { columnLayoutState } from 'state/columnLayoutAtom';
-import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
// This component is loaded after the page mounts.
// Don't try to load it on scroll. It was tested.
@@ -173,7 +173,6 @@ function Resource({
resource.kind,
);
const [showTitleDescription, setShowTitleDescription] = useState(false);
- const setOpenAssistant = useSetRecoilState(showAIassistantState);
const pluralizedResourceKind = pluralize(prettifiedResourceKind);
useWindowTitle(windowTitle || pluralizedResourceKind);
@@ -368,13 +367,7 @@ function Resource({
{title ?? t('common.headers.resource-details')}
- setOpenAssistant(true)}
- className="ai-button"
- >
- {t('ai-assistant.use-ai')}
-
+
Date: Tue, 2 Apr 2024 13:35:30 +0200
Subject: [PATCH 04/38] fix: disable input while waiting for response & hide
open-Button when assistant is already open
---
.../AIassistant/components/AIOpener.js | 11 +++++---
.../AIassistant/components/Chat/Chat.js | 27 ++++++++++++-------
2 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index b400b405b5..2e6f464c15 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -12,7 +12,7 @@ import {
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
-import { useSetRecoilState } from 'recoil';
+import { useRecoilState, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
import getPromptSuggestions from 'components/AIassistant/utils/getPromptSuggestions';
@@ -20,12 +20,14 @@ import './AIOpener.scss';
export default function AIOpener() {
const { t } = useTranslation();
- const setOpenAssistant = useSetRecoilState(showAIassistantState);
+ const [assistantOpen, setOpenAssistant] = useRecoilState(
+ showAIassistantState,
+ );
const setInitialPrompt = useSetRecoilState(initialPromptState);
const [popoverOpen, setPopoverOpen] = useState(false);
const [suggestions, setSuggestions] = useState([]);
- return (
+ return !assistantOpen ? (
<>
{
setInitialPrompt(suggestion);
- setPopoverOpen(false);
setOpenAssistant(true);
}}
>
@@ -84,5 +85,7 @@ export default function AIOpener() {
>
+ ) : (
+ <>>
);
}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 5dc520a518..38e85ceca4 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -17,9 +17,11 @@ export default function Chat() {
const [chatHistory, setChatHistory] = useState([]);
const [errorOccured, setErrorOccured] = useState(false);
const initialPrompt = useRecoilValue(initialPromptState);
+
const addMessage = (author, message, isLoading) => {
setChatHistory(prevItems => [...prevItems, { author, message, isLoading }]);
};
+
const handleSuccess = response => {
setChatHistory(prevItems => {
const newArray = [...prevItems];
@@ -31,17 +33,21 @@ export default function Chat() {
return newArray;
});
};
+
const handleError = () => {
setErrorOccured(true);
setChatHistory(prevItems => prevItems.slice(0, -2));
};
+
const onSendPrompt = prompt => {
setErrorOccured(false);
addMessage('user', prompt, false);
getChatResponse(prompt, handleSuccess, handleError);
addMessage('ai', null, true);
};
+
const onSubmitInput = () => {
+ if (inputValue.length === 0) return;
const prompt = inputValue;
setInputValue('');
onSendPrompt(prompt);
@@ -67,12 +73,6 @@ export default function Chat() {
}, delay);
}, [chatHistory, errorOccured]);
- const test_suggestions = [
- 'test123123123123123xyzxyzuwquxzytsabcde123456',
- 'Throw an error',
- 'What is your favorite football team?',
- ];
-
return (
)}
>
@@ -114,13 +120,14 @@ export default function Chat() {
}
+ disabled={chatHistory[chatHistory.length - 1]?.isLoading}
+ placeholder={t('ai-assistant.placeholder')}
value={inputValue}
+ icon={}
onKeyDown={e => {
- if (e.key === 'Enter' && inputValue.length > 0) onSubmitInput();
+ if (e.key === 'Enter') onSubmitInput();
}}
onInput={e => setInputValue(e.target.value)}
- placeholder={t('ai-assistant.placeholder')}
/>
From 092089d359ef8746c8469efc182ed13764f53b13 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 3 Apr 2024 19:26:21 +0200
Subject: [PATCH 05/38] feat: better design of open-assistant-popover
---
public/i18n/en.yaml | 5 +-
.../AIassistant/components/AIOpener.js | 68 +++++++++++++------
.../AIassistant/components/AIOpener.scss | 24 +++++--
3 files changed, 67 insertions(+), 30 deletions(-)
diff --git a/public/i18n/en.yaml b/public/i18n/en.yaml
index 1bab163a66..f61fb1dd10 100644
--- a/public/i18n/en.yaml
+++ b/public/i18n/en.yaml
@@ -5,6 +5,10 @@ apps:
title: Apps
ai-assistant:
name: Joule
+ opener:
+ use-ai: Use AI
+ suggestions: Suggestions
+ input-placeholder: Ask about this resource
error:
title: Service is interrupted
subtitle: A temporary interruption occured. Please try again.
@@ -14,7 +18,6 @@ ai-assistant:
tabs:
chat: Chat
page-insights: Page Insights
- use-ai: Use AI
cluster-overview:
headers:
cpu: CPU
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 2e6f464c15..01fb1dba3d 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -5,6 +5,7 @@ import {
Icon,
Input,
List,
+ Loader,
Popover,
Text,
Title,
@@ -26,50 +27,75 @@ export default function AIOpener() {
const setInitialPrompt = useSetRecoilState(initialPromptState);
const [popoverOpen, setPopoverOpen] = useState(false);
const [suggestions, setSuggestions] = useState([]);
+ const [inputValue, setInputValue] = useState('');
- return !assistantOpen ? (
+ const onSubmitInput = () => {
+ if (inputValue.length === 0) return;
+ const prompt = inputValue;
+ setInputValue('');
+ setInitialPrompt(prompt);
+ setPopoverOpen(false);
+ setOpenAssistant(true);
+ };
+
+ return (
<>
{
- const suggestions = await getPromptSuggestions();
- setSuggestions(suggestions);
setPopoverOpen(true);
+ if (suggestions.length === 0) {
+ const suggestions = await getPromptSuggestions();
+ setSuggestions(suggestions);
+ }
}}
>
- {t('ai-assistant.use-ai')}
+ {t('ai-assistant.opener.use-ai')}
{}} />}
- value={''}
- onKeyDown={e => {}}
- onInput={e => {}}
- placeholder="Ask about this cluster"
- />
- }
onAfterClose={() => setPopoverOpen(false)}
opener="openPopoverBtn"
placementType="Bottom"
horizontalAlign="Right"
>
- {'Suggestions'}
-
-
+ }
+ value={inputValue}
+ onKeyDown={e => {
+ if (e.key === 'Enter') onSubmitInput();
+ }}
+ onInput={e => setInputValue(e.target.value)}
+ placeholder={t('ai-assistant.opener.input-placeholder')}
+ className="popover-input"
+ />
+
+ {t('ai-assistant.opener.suggestions')}
+
+ {suggestions.length === 0 ? (
+
+
+
+ ) : (
+
{suggestions.map((suggestion, index) => (
{
setInitialPrompt(suggestion);
+ setPopoverOpen(false);
setOpenAssistant(true);
}}
+ className="custom-list-item"
>
))}
-
+ )}
>
- ) : (
- <>>
);
}
diff --git a/src/components/AIassistant/components/AIOpener.scss b/src/components/AIassistant/components/AIOpener.scss
index c85d271016..7e3a1be58e 100644
--- a/src/components/AIassistant/components/AIOpener.scss
+++ b/src/components/AIassistant/components/AIOpener.scss
@@ -1,14 +1,24 @@
.ai-button {
color: var(--sapContent_Illustrative_Color1);
}
+.suggestions-popover::part(content) {
+ padding: 0.5rem;
+}
+
+.suggestions-popover {
+ .popover-input {
+ min-width: 250px;
+ }
+
+ .custom-list-item::part(native-li) {
+ padding: 0.5rem;
+ }
-.list-item-content {
- width: 100%;
+ .list-item-content {
+ width: 100%;
- .text {
- max-width: 85%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ .text {
+ width: 90%;
+ }
}
}
From e4311d267be6424ce4fbee703e920be8fec8cc5a Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 3 Apr 2024 20:45:14 +0200
Subject: [PATCH 06/38] feat: added error handling to
inital-suggestions-popover
---
public/i18n/en.yaml | 2 +
.../{utils => api}/getChatResponse.js | 9 ---
.../AIassistant/api/getPromptSuggestions.js | 35 ++++++++++
.../AIassistant/components/AIOpener.js | 69 +++++++++++--------
.../AIassistant/components/AIOpener.scss | 9 ++-
.../AIassistant/components/Chat/Chat.js | 14 ++--
.../AIassistant/utils/getPromptSuggestions.js | 30 --------
7 files changed, 91 insertions(+), 77 deletions(-)
rename src/components/AIassistant/{utils => api}/getChatResponse.js (85%)
create mode 100644 src/components/AIassistant/api/getPromptSuggestions.js
delete mode 100644 src/components/AIassistant/utils/getPromptSuggestions.js
diff --git a/public/i18n/en.yaml b/public/i18n/en.yaml
index f61fb1dd10..308e02d13a 100644
--- a/public/i18n/en.yaml
+++ b/public/i18n/en.yaml
@@ -9,6 +9,7 @@ ai-assistant:
use-ai: Use AI
suggestions: Suggestions
input-placeholder: Ask about this resource
+ error-message: Couldn't fetch suggestions. Please try again.
error:
title: Service is interrupted
subtitle: A temporary interruption occured. Please try again.
@@ -276,6 +277,7 @@ common:
remove-all: Remove all
reset: Reset
restart: Restart
+ retry: Retry
save: Save
submit: Submit
update: Update
diff --git a/src/components/AIassistant/utils/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
similarity index 85%
rename from src/components/AIassistant/utils/getChatResponse.js
rename to src/components/AIassistant/api/getChatResponse.js
index 76f39f6a58..6bddab4674 100644
--- a/src/components/AIassistant/utils/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,18 +1,9 @@
-function delay() {
- return new Promise(resolve => setTimeout(resolve, 3000));
-}
-
export default async function getChatResponse(
prompt,
handleSuccess,
handleError,
) {
try {
- if (prompt === 'Throw an error') {
- await delay();
- throw new Error('This is a custom error message.');
- }
-
const { response } = await fetch(
'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm',
{
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
new file mode 100644
index 0000000000..b3cbfe0002
--- /dev/null
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -0,0 +1,35 @@
+export default async function getPromptSuggestions(
+ pageType = 'statefulsets.apps',
+ namespace = 'kyma-system',
+ nodeName = '',
+) {
+ try {
+ let { results } = await fetch(
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init',
+ {
+ headers: {
+ accept: 'application/json, text/plain, */*',
+ 'accept-language': 'en-US,en;q=0.9,de;q=0.8',
+ 'content-type': 'application/json',
+ 'sec-ch-ua':
+ '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"macOS"',
+ 'sec-fetch-dest': 'empty',
+ 'sec-fetch-mode': 'cors',
+ 'sec-fetch-site': 'cross-site',
+ },
+ referrer: 'https://ai.kyma.dev.sap/',
+ referrerPolicy: 'strict-origin-when-cross-origin',
+ body: `{"page_type":"${pageType}","namespace":"${namespace}","node_name":"${nodeName}"}`,
+ method: 'POST',
+ mode: 'cors',
+ credentials: 'omit',
+ },
+ ).then(result => result.json());
+ return results;
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ return false;
+ }
+}
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 01fb1dba3d..d0f50dd24e 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -16,7 +16,7 @@ import { useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
-import getPromptSuggestions from 'components/AIassistant/utils/getPromptSuggestions';
+import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestions';
import './AIOpener.scss';
export default function AIOpener() {
@@ -28,14 +28,32 @@ export default function AIOpener() {
const [popoverOpen, setPopoverOpen] = useState(false);
const [suggestions, setSuggestions] = useState([]);
const [inputValue, setInputValue] = useState('');
+ const [errorOccured, setErrorOccured] = useState(false);
+
+ const fetchSuggestions = async () => {
+ setErrorOccured(false);
+ setPopoverOpen(true);
+ if (suggestions.length === 0) {
+ const promptSuggestions = await getPromptSuggestions();
+ if (!promptSuggestions) {
+ setErrorOccured(true);
+ } else {
+ setSuggestions(promptSuggestions);
+ }
+ }
+ };
+
+ const sendInitialPrompt = prompt => {
+ setInitialPrompt(prompt);
+ setPopoverOpen(false);
+ setOpenAssistant(true);
+ };
const onSubmitInput = () => {
if (inputValue.length === 0) return;
const prompt = inputValue;
setInputValue('');
- setInitialPrompt(prompt);
- setPopoverOpen(false);
- setOpenAssistant(true);
+ sendInitialPrompt(prompt);
};
return (
@@ -45,13 +63,7 @@ export default function AIOpener() {
className="ai-button"
id="openPopoverBtn"
disabled={assistantOpen}
- onClick={async () => {
- setPopoverOpen(true);
- if (suggestions.length === 0) {
- const suggestions = await getPromptSuggestions();
- setSuggestions(suggestions);
- }
- }}
+ onClick={fetchSuggestions}
>
{t('ai-assistant.opener.use-ai')}
@@ -66,9 +78,7 @@ export default function AIOpener() {
}
value={inputValue}
- onKeyDown={e => {
- if (e.key === 'Enter') onSubmitInput();
- }}
+ onKeyDown={e => e.key === 'Enter' && onSubmitInput()}
onInput={e => setInputValue(e.target.value)}
placeholder={t('ai-assistant.opener.input-placeholder')}
className="popover-input"
@@ -76,7 +86,20 @@ export default function AIOpener() {
{t('ai-assistant.opener.suggestions')}
- {suggestions.length === 0 ? (
+ {errorOccured ? (
+
+
+ {t('ai-assistant.opener.error-message')}
+
+
+ {t('common.buttons.retry')}
+
+
+ ) : suggestions.length === 0 ? (
(
{
- setInitialPrompt(suggestion);
- setPopoverOpen(false);
- setOpenAssistant(true);
- }}
+ onClick={() => sendInitialPrompt(suggestion)}
className="custom-list-item"
>
-
- {suggestion}
-
-
+ {suggestion}
+
))}
diff --git a/src/components/AIassistant/components/AIOpener.scss b/src/components/AIassistant/components/AIOpener.scss
index 7e3a1be58e..89c53bdc39 100644
--- a/src/components/AIassistant/components/AIOpener.scss
+++ b/src/components/AIassistant/components/AIOpener.scss
@@ -1,21 +1,26 @@
.ai-button {
color: var(--sapContent_Illustrative_Color1);
}
+
.suggestions-popover::part(content) {
padding: 0.5rem;
}
.suggestions-popover {
.popover-input {
- min-width: 250px;
+ min-width: 225px;
+ width: 100%;
}
.custom-list-item::part(native-li) {
padding: 0.5rem;
}
- .list-item-content {
+ .custom-list-item {
width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
.text {
width: 90%;
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 38e85ceca4..aaff81607e 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -7,7 +7,7 @@ import { initialPromptState } from 'components/AIassistant/state/initalPromptAto
import PlainMessage from './messages/PlainMessage';
import Bubbles from './messages/Bubbles';
import ErrorMessage from './messages/ErrorMessage';
-import getChatResponse from 'components/AIassistant/utils/getChatResponse';
+import getChatResponse from 'components/AIassistant/api/getChatResponse';
import './Chat.scss';
export default function Chat() {
@@ -39,7 +39,7 @@ export default function Chat() {
setChatHistory(prevItems => prevItems.slice(0, -2));
};
- const onSendPrompt = prompt => {
+ const sendPrompt = prompt => {
setErrorOccured(false);
addMessage('user', prompt, false);
getChatResponse(prompt, handleSuccess, handleError);
@@ -50,7 +50,7 @@ export default function Chat() {
if (inputValue.length === 0) return;
const prompt = inputValue;
setInputValue('');
- onSendPrompt(prompt);
+ sendPrompt(prompt);
};
const scrollToBottom = () => {
@@ -62,7 +62,7 @@ export default function Chat() {
};
useEffect(() => {
- if (chatHistory.length === 0) onSendPrompt(initialPrompt);
+ if (chatHistory.length === 0) sendPrompt(initialPrompt);
// eslint-disable-next-line
}, []);
@@ -96,7 +96,7 @@ export default function Chat() {
{index === chatHistory.length - 1 && !message.isLoading && (
}
- onKeyDown={e => {
- if (e.key === 'Enter') onSubmitInput();
- }}
+ onKeyDown={e => e.key === 'Enter' && onSubmitInput()}
onInput={e => setInputValue(e.target.value)}
/>
diff --git a/src/components/AIassistant/utils/getPromptSuggestions.js b/src/components/AIassistant/utils/getPromptSuggestions.js
deleted file mode 100644
index 968987e168..0000000000
--- a/src/components/AIassistant/utils/getPromptSuggestions.js
+++ /dev/null
@@ -1,30 +0,0 @@
-export default async function getPromptSuggestions(
- pageType = 'statefulsets.apps',
- namespace = 'kyma-system',
- nodeName = '',
-) {
- let { results } = await fetch(
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init',
- {
- headers: {
- accept: 'application/json, text/plain, */*',
- 'accept-language': 'en-US,en;q=0.9,de;q=0.8',
- 'content-type': 'application/json',
- 'sec-ch-ua':
- '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
- 'sec-ch-ua-mobile': '?0',
- 'sec-ch-ua-platform': '"macOS"',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'cors',
- 'sec-fetch-site': 'cross-site',
- },
- referrer: 'https://ai.kyma.dev.sap/',
- referrerPolicy: 'strict-origin-when-cross-origin',
- body: `{"page_type":"${pageType}","namespace":"${namespace}","node_name":"${nodeName}"}`,
- method: 'POST',
- mode: 'cors',
- credentials: 'omit',
- },
- ).then(result => result.json());
- return results;
-}
From 07ae000d43e871da9050ae90ed9ee6ddce1bc68e Mon Sep 17 00:00:00 2001
From: chriskari
Date: Thu, 4 Apr 2024 09:21:45 +0200
Subject: [PATCH 07/38] fix: error bug in suggestions popover & disabled
buttons
---
src/components/AIassistant/api/getChatResponse.js | 4 ++--
src/components/AIassistant/api/getPromptSuggestions.js | 6 +++---
src/components/AIassistant/components/AIOpener.js | 6 ++++--
src/components/AIassistant/components/AIassistant.js | 1 +
src/components/AIassistant/components/Chat/Chat.js | 2 +-
5 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 6bddab4674..e6ab22ee79 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,8 +1,8 @@
-export default async function getChatResponse(
+export default async function getChatResponse({
prompt,
handleSuccess,
handleError,
-) {
+}) {
try {
const { response } = await fetch(
'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm',
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index b3cbfe0002..7b8b3359f3 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -1,8 +1,8 @@
-export default async function getPromptSuggestions(
+export default async function getPromptSuggestions({
pageType = 'statefulsets.apps',
- namespace = 'kyma-system',
+ namespace,
nodeName = '',
-) {
+}) {
try {
let { results } = await fetch(
'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init',
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index d0f50dd24e..ced808d9a2 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -10,6 +10,7 @@ import {
Text,
Title,
} from '@ui5/webcomponents-react';
+import { useUrl } from 'hooks/useUrl';
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
@@ -24,6 +25,7 @@ export default function AIOpener() {
const [assistantOpen, setOpenAssistant] = useRecoilState(
showAIassistantState,
);
+ const { namespace } = useUrl();
const setInitialPrompt = useSetRecoilState(initialPromptState);
const [popoverOpen, setPopoverOpen] = useState(false);
const [suggestions, setSuggestions] = useState([]);
@@ -34,7 +36,7 @@ export default function AIOpener() {
setErrorOccured(false);
setPopoverOpen(true);
if (suggestions.length === 0) {
- const promptSuggestions = await getPromptSuggestions();
+ const promptSuggestions = await getPromptSuggestions({ namespace });
if (!promptSuggestions) {
setErrorOccured(true);
} else {
@@ -95,7 +97,7 @@ export default function AIOpener() {
{t('ai-assistant.opener.error-message')}
-
+
{t('common.buttons.retry')}
diff --git a/src/components/AIassistant/components/AIassistant.js b/src/components/AIassistant/components/AIassistant.js
index 5471d7a13f..3299943752 100644
--- a/src/components/AIassistant/components/AIassistant.js
+++ b/src/components/AIassistant/components/AIassistant.js
@@ -34,6 +34,7 @@ export default function AIassistant() {
design="Transparent"
icon="full-screen"
className="action"
+ disabled
/>
{
setErrorOccured(false);
addMessage('user', prompt, false);
- getChatResponse(prompt, handleSuccess, handleError);
+ getChatResponse({ prompt, handleSuccess, handleError });
addMessage('ai', null, true);
};
From 41746e0bc71c06456bd6bd2ced60ee41d602011a Mon Sep 17 00:00:00 2001
From: chriskari
Date: Fri, 5 Apr 2024 18:32:48 +0200
Subject: [PATCH 08/38] feat: added some markdown formatting for responses of
LLM
---
.../AIassistant/api/getChatResponse.js | 14 -----
.../AIassistant/api/getPromptSuggestions.js | 12 ----
.../AIassistant/components/Chat/Chat.js | 6 +-
.../components/Chat/messages/CodePanel.js | 26 ++++++++
.../components/Chat/messages/CodePanel.scss | 28 +++++++++
.../components/Chat/messages/Message.js | 59 +++++++++++++++++++
.../components/Chat/messages/Message.scss | 17 ++++++
.../Chat/messages/MessageWithList.js | 39 ------------
.../Chat/messages/MessageWithList.scss | 15 -----
.../components/Chat/messages/PlainMessage.js | 11 ----
.../Chat/messages/PlainMessage.scss | 4 --
11 files changed, 133 insertions(+), 98 deletions(-)
create mode 100644 src/components/AIassistant/components/Chat/messages/CodePanel.js
create mode 100644 src/components/AIassistant/components/Chat/messages/CodePanel.scss
create mode 100644 src/components/AIassistant/components/Chat/messages/Message.js
create mode 100644 src/components/AIassistant/components/Chat/messages/Message.scss
delete mode 100644 src/components/AIassistant/components/Chat/messages/MessageWithList.js
delete mode 100644 src/components/AIassistant/components/Chat/messages/MessageWithList.scss
delete mode 100644 src/components/AIassistant/components/Chat/messages/PlainMessage.js
delete mode 100644 src/components/AIassistant/components/Chat/messages/PlainMessage.scss
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index e6ab22ee79..7098ad0917 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -9,24 +9,10 @@ export default async function getChatResponse({
{
headers: {
accept: 'application/json, text/plain, */*',
- 'accept-language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
- 'cache-control': 'no-cache',
'content-type': 'application/json',
- pragma: 'no-cache',
- 'sec-ch-ua':
- '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
- 'sec-ch-ua-mobile': '?0',
- 'sec-ch-ua-platform': '"macOS"',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'cors',
- 'sec-fetch-site': 'cross-site',
},
- referrer: 'https://ai.kyma.dev.sap/',
- referrerPolicy: 'strict-origin-when-cross-origin',
body: `{"question":"${prompt}"}`,
method: 'POST',
- mode: 'cors',
- credentials: 'omit',
},
).then(result => result.json());
handleSuccess(response);
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 7b8b3359f3..5f78cf91bd 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -9,22 +9,10 @@ export default async function getPromptSuggestions({
{
headers: {
accept: 'application/json, text/plain, */*',
- 'accept-language': 'en-US,en;q=0.9,de;q=0.8',
'content-type': 'application/json',
- 'sec-ch-ua':
- '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
- 'sec-ch-ua-mobile': '?0',
- 'sec-ch-ua-platform': '"macOS"',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'cors',
- 'sec-fetch-site': 'cross-site',
},
- referrer: 'https://ai.kyma.dev.sap/',
- referrerPolicy: 'strict-origin-when-cross-origin',
body: `{"page_type":"${pageType}","namespace":"${namespace}","node_name":"${nodeName}"}`,
method: 'POST',
- mode: 'cors',
- credentials: 'omit',
},
).then(result => result.json());
return results;
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 71ae0e64fc..76805abfb2 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil';
import { FlexBox, Icon, Input } from '@ui5/webcomponents-react';
import { spacing } from '@ui5/webcomponents-react-base';
import { initialPromptState } from 'components/AIassistant/state/initalPromptAtom';
-import PlainMessage from './messages/PlainMessage';
+import Message from './messages/Message';
import Bubbles from './messages/Bubbles';
import ErrorMessage from './messages/ErrorMessage';
import getChatResponse from 'components/AIassistant/api/getChatResponse';
@@ -87,7 +87,7 @@ export default function Chat() {
{chatHistory.map((message, index) => {
return message.author === 'ai' ? (
<>
-
) : (
-
+
+ {code}
+
+
+ ) : (
+
+
+ {code}
+
+
+ );
+}
+
+function parseCodeText(text) {
+ const lines = text.split('\n');
+ const language = lines.shift();
+ const code = lines.join('\n');
+ return { language, code };
+}
diff --git a/src/components/AIassistant/components/Chat/messages/CodePanel.scss b/src/components/AIassistant/components/Chat/messages/CodePanel.scss
new file mode 100644
index 0000000000..c4b3a7b5f4
--- /dev/null
+++ b/src/components/AIassistant/components/Chat/messages/CodePanel.scss
@@ -0,0 +1,28 @@
+.code-response {
+ background-color: #484848;
+ color: white;
+ padding: 0.75rem;
+ border-radius: 4px;
+
+ .text {
+ color: white;
+ }
+}
+
+.code-panel::part(header) {
+ background-color: #484848;
+ color: white;
+ border-radius: 4px 4px 0 0;
+ font-size: 0.9rem;
+}
+
+.code-panel::part(content) {
+ background-color: #484848;
+ border-radius: 0 0 4px 4px;
+}
+
+.code-panel {
+ .text {
+ color: white;
+ }
+}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
new file mode 100644
index 0000000000..2f21755afc
--- /dev/null
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -0,0 +1,59 @@
+import { BusyIndicator, Text } from '@ui5/webcomponents-react';
+import CodePanel from './CodePanel';
+import './Message.scss';
+
+export default function Message({ className, message, isLoading }) {
+ const segmentedText = formatText(message);
+ return (
+
+ {isLoading && }
+ {segmentedText && (
+
+ {segmentedText.map((segment, index) =>
+ segment.type === 'bold' ? (
+
+ {segment.content}
+
+ ) : segment.type === 'code' ? (
+
+ ) : segment.type === 'highlighted' ? (
+
+ {segment.content}
+
+ ) : (
+ segment.content
+ ),
+ )}
+
+ )}
+
+ );
+}
+
+function formatText(text) {
+ if (!text) return [];
+ const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```)|(`(.*?)`)|[^*`]+/g;
+ return text.match(regex).map(segment => {
+ if (segment.startsWith('**')) {
+ return {
+ type: 'bold',
+ content: segment.replace(/\*\*/g, ''),
+ };
+ } else if (segment.startsWith('```')) {
+ return {
+ type: 'code',
+ content: segment.replace(/```/g, ''),
+ };
+ } else if (segment.startsWith('`')) {
+ return {
+ type: 'highlighted',
+ content: segment.replace(/`/g, ''),
+ };
+ } else {
+ return {
+ type: 'normal',
+ content: segment,
+ };
+ }
+ });
+}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.scss b/src/components/AIassistant/components/Chat/messages/Message.scss
new file mode 100644
index 0000000000..a1c7fde92a
--- /dev/null
+++ b/src/components/AIassistant/components/Chat/messages/Message.scss
@@ -0,0 +1,17 @@
+.message {
+ max-width: 240px;
+ padding: 12px;
+
+ .bold {
+ font-weight: bold;
+ font-size: 1rem;
+ }
+
+ .highlighted {
+ background-color: var(--sapContent_LabelColor);
+ color: white;
+ padding: 0.2rem 0.25rem;
+ margin: 0.2rem 0;
+ border-radius: 4px;
+ }
+}
diff --git a/src/components/AIassistant/components/Chat/messages/MessageWithList.js b/src/components/AIassistant/components/Chat/messages/MessageWithList.js
deleted file mode 100644
index 8d3e045546..0000000000
--- a/src/components/AIassistant/components/Chat/messages/MessageWithList.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
- CustomListItem,
- FlexBox,
- Icon,
- List,
- Text,
-} from '@ui5/webcomponents-react';
-import { spacing } from '@ui5/webcomponents-react-base';
-import './MessageWithList.scss';
-
-export default function MessageWithList({
- className,
- message,
- items,
- appendix,
-}) {
- return (
-
-
{message}
-
-
- {items.map((item, index) => (
-
-
- {item}
-
-
-
- ))}
-
-
-
{appendix}
-
- );
-}
diff --git a/src/components/AIassistant/components/Chat/messages/MessageWithList.scss b/src/components/AIassistant/components/Chat/messages/MessageWithList.scss
deleted file mode 100644
index b2bdbd3c41..0000000000
--- a/src/components/AIassistant/components/Chat/messages/MessageWithList.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.message-with-list {
- max-width: 225px;
- padding: 12px;
-
- .list-item-content {
- width: 100%;
-
- .text {
- max-width: 85%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
-}
diff --git a/src/components/AIassistant/components/Chat/messages/PlainMessage.js b/src/components/AIassistant/components/Chat/messages/PlainMessage.js
deleted file mode 100644
index 30b4855f65..0000000000
--- a/src/components/AIassistant/components/Chat/messages/PlainMessage.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { BusyIndicator, Text } from '@ui5/webcomponents-react';
-import './PlainMessage.scss';
-
-export default function PlainMessage({ className, message, isLoading }) {
- return (
-
- {isLoading && }
- {message && {message}}
-
- );
-}
diff --git a/src/components/AIassistant/components/Chat/messages/PlainMessage.scss b/src/components/AIassistant/components/Chat/messages/PlainMessage.scss
deleted file mode 100644
index df378de169..0000000000
--- a/src/components/AIassistant/components/Chat/messages/PlainMessage.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.plain-message {
- max-width: 225px;
- padding: 12px;
-}
From 48375aa827bb24afc843984fc16dec67385189ba Mon Sep 17 00:00:00 2001
From: chriskari
Date: Mon, 8 Apr 2024 10:08:53 +0200
Subject: [PATCH 09/38] fix: markdown formatting adjustments
---
.../AIassistant/components/Chat/messages/CodePanel.js | 3 ++-
src/components/AIassistant/components/Chat/messages/Message.js | 2 +-
.../AIassistant/components/Chat/messages/Message.scss | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/components/AIassistant/components/Chat/messages/CodePanel.js b/src/components/AIassistant/components/Chat/messages/CodePanel.js
index 990ecc12e9..b57e40069b 100644
--- a/src/components/AIassistant/components/Chat/messages/CodePanel.js
+++ b/src/components/AIassistant/components/Chat/messages/CodePanel.js
@@ -21,6 +21,7 @@ export default function CodePanel({ text }) {
function parseCodeText(text) {
const lines = text.split('\n');
const language = lines.shift();
- const code = lines.join('\n');
+ const nonEmptyLines = lines.filter(line => line.trim() !== '');
+ const code = nonEmptyLines.join('\n');
return { language, code };
}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
index 2f21755afc..0ec758f677 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.js
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -32,7 +32,7 @@ export default function Message({ className, message, isLoading }) {
function formatText(text) {
if (!text) return [];
- const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```)|(`(.*?)`)|[^*`]+/g;
+ const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```\s)|(`(.*?)`)|[^*`]+/g;
return text.match(regex).map(segment => {
if (segment.startsWith('**')) {
return {
diff --git a/src/components/AIassistant/components/Chat/messages/Message.scss b/src/components/AIassistant/components/Chat/messages/Message.scss
index a1c7fde92a..f7956eb857 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.scss
+++ b/src/components/AIassistant/components/Chat/messages/Message.scss
@@ -11,7 +11,7 @@
background-color: var(--sapContent_LabelColor);
color: white;
padding: 0.2rem 0.25rem;
- margin: 0.2rem 0;
+ margin: 0.1rem 0;
border-radius: 4px;
}
}
From 33062da766331cce16a1eaa60d20dab9d16b7e66 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 9 Apr 2024 13:05:45 +0200
Subject: [PATCH 10/38] feat: initial suggestions now depend on currently
opened resource
---
.../AIassistant/api/getPromptSuggestions.js | 8 +-
.../AIassistant/components/AIOpener.js | 126 +++++++++---------
.../AIassistant/components/AIOpener.scss | 2 +-
.../components/Chat/messages/Bubbles.scss | 4 +-
.../components/Chat/messages/CodePanel.js | 11 +-
.../components/Chat/messages/Message.js | 31 +----
.../AIassistant/utils/formatMarkdown.js | 35 +++++
.../views/ClusterOverview/ClusterOverview.js | 5 +-
.../HelmReleases/HelmReleasesDetails.js | 6 +-
.../ResourceDetails/ResourceDetails.js | 6 +-
10 files changed, 126 insertions(+), 108 deletions(-)
create mode 100644 src/components/AIassistant/utils/formatMarkdown.js
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 5f78cf91bd..1f8be0f754 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -1,7 +1,7 @@
export default async function getPromptSuggestions({
- pageType = 'statefulsets.apps',
- namespace,
- nodeName = '',
+ namespace = '',
+ resourceType = '',
+ resourceName = '',
}) {
try {
let { results } = await fetch(
@@ -11,7 +11,7 @@ export default async function getPromptSuggestions({
accept: 'application/json, text/plain, */*',
'content-type': 'application/json',
},
- body: `{"page_type":"${pageType}","namespace":"${namespace}","node_name":"${nodeName}"}`,
+ body: `{"resource_type":"${resourceType}","resource_name":"${resourceName}","namespace":"${namespace}"}`,
method: 'POST',
},
).then(result => result.json());
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index ced808d9a2..894b7a55a8 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -10,7 +10,6 @@ import {
Text,
Title,
} from '@ui5/webcomponents-react';
-import { useUrl } from 'hooks/useUrl';
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
@@ -18,14 +17,14 @@ import { useRecoilState, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestions';
+import { createPortal } from 'react-dom';
import './AIOpener.scss';
-export default function AIOpener() {
+export default function AIOpener({ namespace, resourceType, resourceName }) {
const { t } = useTranslation();
const [assistantOpen, setOpenAssistant] = useRecoilState(
showAIassistantState,
);
- const { namespace } = useUrl();
const setInitialPrompt = useSetRecoilState(initialPromptState);
const [popoverOpen, setPopoverOpen] = useState(false);
const [suggestions, setSuggestions] = useState([]);
@@ -36,7 +35,11 @@ export default function AIOpener() {
setErrorOccured(false);
setPopoverOpen(true);
if (suggestions.length === 0) {
- const promptSuggestions = await getPromptSuggestions({ namespace });
+ const promptSuggestions = await getPromptSuggestions({
+ namespace,
+ resourceType,
+ resourceName,
+ });
if (!promptSuggestions) {
setErrorOccured(true);
} else {
@@ -69,62 +72,65 @@ export default function AIOpener() {
>
{t('ai-assistant.opener.use-ai')}
- setPopoverOpen(false)}
- opener="openPopoverBtn"
- placementType="Bottom"
- horizontalAlign="Right"
- >
- }
- value={inputValue}
- onKeyDown={e => e.key === 'Enter' && onSubmitInput()}
- onInput={e => setInputValue(e.target.value)}
- placeholder={t('ai-assistant.opener.input-placeholder')}
- className="popover-input"
- />
-
- {t('ai-assistant.opener.suggestions')}
-
- {errorOccured ? (
-
-
- {t('ai-assistant.opener.error-message')}
-
-
- {t('common.buttons.retry')}
-
-
- ) : suggestions.length === 0 ? (
-
-
-
- ) : (
-
- {suggestions.map((suggestion, index) => (
- sendInitialPrompt(suggestion)}
- className="custom-list-item"
- >
- {suggestion}
-
-
- ))}
-
- )}
-
+ {createPortal(
+ setPopoverOpen(false)}
+ opener="openPopoverBtn"
+ placementType="Bottom"
+ horizontalAlign="Right"
+ >
+ }
+ value={inputValue}
+ onKeyDown={e => e.key === 'Enter' && onSubmitInput()}
+ onInput={e => setInputValue(e.target.value)}
+ placeholder={t('ai-assistant.opener.input-placeholder')}
+ className="popover-input"
+ />
+
+ {t('ai-assistant.opener.suggestions')}
+
+ {errorOccured ? (
+
+
+ {t('ai-assistant.opener.error-message')}
+
+
+ {t('common.buttons.retry')}
+
+
+ ) : suggestions.length === 0 ? (
+
+
+
+ ) : (
+
+ {suggestions.map((suggestion, index) => (
+ sendInitialPrompt(suggestion)}
+ className="custom-list-item"
+ >
+ {suggestion}
+
+
+ ))}
+
+ )}
+ ,
+ document.body,
+ )}
>
);
}
diff --git a/src/components/AIassistant/components/AIOpener.scss b/src/components/AIassistant/components/AIOpener.scss
index 89c53bdc39..4a0a177f93 100644
--- a/src/components/AIassistant/components/AIOpener.scss
+++ b/src/components/AIassistant/components/AIOpener.scss
@@ -1,5 +1,5 @@
.ai-button {
- color: var(--sapContent_Illustrative_Color1);
+ color: var(--sapChart_OrderedColor_5);
}
.suggestions-popover::part(content) {
diff --git a/src/components/AIassistant/components/Chat/messages/Bubbles.scss b/src/components/AIassistant/components/Chat/messages/Bubbles.scss
index 1a2ad9540e..331767d2b6 100644
--- a/src/components/AIassistant/components/Chat/messages/Bubbles.scss
+++ b/src/components/AIassistant/components/Chat/messages/Bubbles.scss
@@ -4,11 +4,11 @@
.bubble-button {
align-self: flex-start;
- color: var(--sapContent_Illustrative_Color1);
+ color: var(--sapChart_OrderedColor_5);
}
.bubble-button:hover {
background-color: var(--sapBackgroundColor1);
- border-color: var(--sapContent_Illustrative_Color1);
+ border-color: var(--sapChart_OrderedColor_5);
}
}
diff --git a/src/components/AIassistant/components/Chat/messages/CodePanel.js b/src/components/AIassistant/components/Chat/messages/CodePanel.js
index b57e40069b..78c18285af 100644
--- a/src/components/AIassistant/components/Chat/messages/CodePanel.js
+++ b/src/components/AIassistant/components/Chat/messages/CodePanel.js
@@ -1,8 +1,9 @@
import { Text, Panel } from '@ui5/webcomponents-react';
+import { formatCodeSegment } from 'components/AIassistant/utils/formatMarkdown';
import './CodePanel.scss';
export default function CodePanel({ text }) {
- const { language, code } = parseCodeText(text);
+ const { language, code } = formatCodeSegment(text);
return !language ? (
@@ -17,11 +18,3 @@ export default function CodePanel({ text }) {
);
}
-
-function parseCodeText(text) {
- const lines = text.split('\n');
- const language = lines.shift();
- const nonEmptyLines = lines.filter(line => line.trim() !== '');
- const code = nonEmptyLines.join('\n');
- return { language, code };
-}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
index 0ec758f677..3d45a4bf5a 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.js
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -1,9 +1,10 @@
import { BusyIndicator, Text } from '@ui5/webcomponents-react';
+import { segmentMarkdownText } from 'components/AIassistant/utils/formatMarkdown';
import CodePanel from './CodePanel';
import './Message.scss';
export default function Message({ className, message, isLoading }) {
- const segmentedText = formatText(message);
+ const segmentedText = segmentMarkdownText(message);
return (
{isLoading && }
@@ -29,31 +30,3 @@ export default function Message({ className, message, isLoading }) {
);
}
-
-function formatText(text) {
- if (!text) return [];
- const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```\s)|(`(.*?)`)|[^*`]+/g;
- return text.match(regex).map(segment => {
- if (segment.startsWith('**')) {
- return {
- type: 'bold',
- content: segment.replace(/\*\*/g, ''),
- };
- } else if (segment.startsWith('```')) {
- return {
- type: 'code',
- content: segment.replace(/```/g, ''),
- };
- } else if (segment.startsWith('`')) {
- return {
- type: 'highlighted',
- content: segment.replace(/`/g, ''),
- };
- } else {
- return {
- type: 'normal',
- content: segment,
- };
- }
- });
-}
diff --git a/src/components/AIassistant/utils/formatMarkdown.js b/src/components/AIassistant/utils/formatMarkdown.js
new file mode 100644
index 0000000000..c725f97162
--- /dev/null
+++ b/src/components/AIassistant/utils/formatMarkdown.js
@@ -0,0 +1,35 @@
+export function segmentMarkdownText(text) {
+ if (!text) return [];
+ const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```\s)|(`(.*?)`)|[^*`]+/g;
+ return text.match(regex).map(segment => {
+ if (segment.startsWith('**')) {
+ return {
+ type: 'bold',
+ content: segment.replace(/\*\*/g, ''),
+ };
+ } else if (segment.startsWith('```')) {
+ return {
+ type: 'code',
+ content: segment.replace(/```/g, ''),
+ };
+ } else if (segment.startsWith('`')) {
+ return {
+ type: 'highlighted',
+ content: segment.replace(/`/g, ''),
+ };
+ } else {
+ return {
+ type: 'normal',
+ content: segment,
+ };
+ }
+ });
+}
+
+export function formatCodeSegment(text) {
+ const lines = text.split('\n');
+ const language = lines.shift();
+ const nonEmptyLines = lines.filter(line => line.trim() !== '');
+ const code = nonEmptyLines.join('\n');
+ return { language, code };
+}
diff --git a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
index 477e738226..3d6b642b49 100644
--- a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
+++ b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
@@ -85,7 +85,10 @@ export function ClusterOverview() {
{t('cluster-overview.headers.cluster-details')}
-
+
{data && }
diff --git a/src/components/HelmReleases/HelmReleasesDetails.js b/src/components/HelmReleases/HelmReleasesDetails.js
index 5eb2db2c73..39682ba4da 100644
--- a/src/components/HelmReleases/HelmReleasesDetails.js
+++ b/src/components/HelmReleases/HelmReleasesDetails.js
@@ -96,7 +96,11 @@ function HelmReleasesDetails({ releaseName, namespace }) {
/>
-
+
>
)}
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index 42ba161b40..792c686b08 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -369,7 +369,11 @@ function Resource({
{title ?? t('common.headers.resource-details')}
-
+
Date: Tue, 9 Apr 2024 23:14:25 +0200
Subject: [PATCH 11/38] feat: assistant closes when open resource changes
---
src/components/AIassistant/components/AIOpener.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 894b7a55a8..c75a9dc6ce 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -12,7 +12,7 @@ import {
} from '@ui5/webcomponents-react';
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
@@ -31,6 +31,13 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
const [inputValue, setInputValue] = useState('');
const [errorOccured, setErrorOccured] = useState(false);
+ useEffect(() => {
+ return () => {
+ setOpenAssistant(false);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const fetchSuggestions = async () => {
setErrorOccured(false);
setPopoverOpen(true);
From 23901afeec491b5483c8c1ebfd7bc2b1fd137939 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 10 Apr 2024 13:13:00 +0200
Subject: [PATCH 12/38] feat: added more error handling & fullscreen-mode
---
.../AIassistant/components/AIOpener.js | 18 ++++++++----
.../AIassistant/components/AIassistant.js | 23 ++++++++++-----
.../AIassistant/components/Chat/Chat.js | 7 ++++-
.../components/Chat/messages/ErrorMessage.js | 15 ++++++++--
.../AIassistant/state/showAIassistantAtom.ts | 10 +++++--
src/components/App/App.scss | 8 ++++++
src/components/App/App.tsx | 28 ++++++++++++-------
7 files changed, 80 insertions(+), 29 deletions(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index c75a9dc6ce..97e2c29056 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -22,7 +22,7 @@ import './AIOpener.scss';
export default function AIOpener({ namespace, resourceType, resourceName }) {
const { t } = useTranslation();
- const [assistantOpen, setOpenAssistant] = useRecoilState(
+ const [showAssistant, setShowAssistant] = useRecoilState(
showAIassistantState,
);
const setInitialPrompt = useSetRecoilState(initialPromptState);
@@ -30,10 +30,11 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
const [suggestions, setSuggestions] = useState([]);
const [inputValue, setInputValue] = useState('');
const [errorOccured, setErrorOccured] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
return () => {
- setOpenAssistant(false);
+ setShowAssistant({ show: false, fullScreen: false });
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -42,11 +43,13 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
setErrorOccured(false);
setPopoverOpen(true);
if (suggestions.length === 0) {
+ setIsLoading(true);
const promptSuggestions = await getPromptSuggestions({
namespace,
resourceType,
resourceName,
});
+ setIsLoading(false);
if (!promptSuggestions) {
setErrorOccured(true);
} else {
@@ -58,7 +61,10 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
const sendInitialPrompt = prompt => {
setInitialPrompt(prompt);
setPopoverOpen(false);
- setOpenAssistant(true);
+ setShowAssistant({
+ show: true,
+ fullScreen: false,
+ });
};
const onSubmitInput = () => {
@@ -74,7 +80,7 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
icon="ai"
className="ai-button"
id="openPopoverBtn"
- disabled={assistantOpen}
+ disabled={showAssistant.show}
onClick={fetchSuggestions}
>
{t('ai-assistant.opener.use-ai')}
@@ -99,7 +105,7 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
{t('ai-assistant.opener.suggestions')}
- {errorOccured ? (
+ {errorOccured || (!isLoading && suggestions.length === 0) ? (
- ) : suggestions.length === 0 ? (
+ ) : isLoading ? (
+ setShowAssistant({
+ show: true,
+ fullScreen: !showAssistant.fullScreen,
+ })
+ }
/>
{
- setOpenAssistant(false);
- }}
+ onClick={() =>
+ setShowAssistant({ show: false, fullScreen: false })
+ }
/>
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 76805abfb2..7d1b5d3fa2 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -115,7 +115,12 @@ export default function Chat() {
/>
);
})}
- {errorOccured && }
+ {errorOccured && (
+ sendPrompt(initialPrompt)}
+ />
+ )}
+ >
+ {errorOnInitialMessage && (
+
+ {t('common.buttons.retry')}
+
+ )}
+
);
}
diff --git a/src/components/AIassistant/state/showAIassistantAtom.ts b/src/components/AIassistant/state/showAIassistantAtom.ts
index ac035680cf..92baec53e1 100644
--- a/src/components/AIassistant/state/showAIassistantAtom.ts
+++ b/src/components/AIassistant/state/showAIassistantAtom.ts
@@ -1,8 +1,14 @@
import { atom, RecoilState } from 'recoil';
-type ShowAIassistant = boolean;
+type ShowAIassistant = {
+ show: boolean;
+ fullScreen: boolean;
+};
-const DEFAULT_SHOW_AI_ASSISTANT = false;
+const DEFAULT_SHOW_AI_ASSISTANT: ShowAIassistant = {
+ show: false,
+ fullScreen: false,
+};
export const showAIassistantState: RecoilState
= atom<
ShowAIassistant
diff --git a/src/components/App/App.scss b/src/components/App/App.scss
index f4bda11d4e..a658d7ce23 100644
--- a/src/components/App/App.scss
+++ b/src/components/App/App.scss
@@ -1,3 +1,11 @@
+#splitter-layout {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
#html-wrap {
position: absolute;
top: 0;
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 27ba4b5058..fa096b787a 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -71,16 +71,20 @@ export default function App() {
useAppTracking();
useAfterInitHook(kubeconfigIdState);
- const assistantOpen = useRecoilValue(showAIassistantState);
+ const showAssistant = useRecoilValue(showAIassistantState);
return (
-
-
+
+
@@ -113,8 +117,12 @@ export default function App() {
- {assistantOpen ? (
-
+ {showAssistant.show ? (
+
From bd71c24c90cb4569092c8aae9f3e95cc248f20ab Mon Sep 17 00:00:00 2001
From: chriskari
Date: Thu, 11 Apr 2024 14:26:31 +0200
Subject: [PATCH 13/38] fix: small code clean-up
---
.../AIassistant/components/AIassistant.js | 95 ++++++++++---------
.../AIassistant/components/AIassistant.scss | 37 ++++----
src/components/App/App.scss | 5 -
src/components/App/App.tsx | 4 +-
4 files changed, 69 insertions(+), 72 deletions(-)
diff --git a/src/components/AIassistant/components/AIassistant.js b/src/components/AIassistant/components/AIassistant.js
index a2d82c48ea..e78a47906f 100644
--- a/src/components/AIassistant/components/AIassistant.js
+++ b/src/components/AIassistant/components/AIassistant.js
@@ -22,53 +22,54 @@ export default function AIassistant() {
);
return (
-
-
- {t('ai-assistant.name')}
-
-
-
-
- setShowAssistant({
- show: true,
- fullScreen: !showAssistant.fullScreen,
- })
- }
- />
-
- setShowAssistant({ show: false, fullScreen: false })
- }
- />
-
-
- }
- >
-
+
+
+ {t('ai-assistant.name')}
+
+
+
+
+ setShowAssistant({
+ show: true,
+ fullScreen: !showAssistant.fullScreen,
+ })
+ }
+ />
+
+ setShowAssistant({ show: false, fullScreen: false })
+ }
+ />
+
+
+ }
>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/components/AIassistant/components/AIassistant.scss b/src/components/AIassistant/components/AIassistant.scss
index 8f9859048f..c85a662389 100644
--- a/src/components/AIassistant/components/AIassistant.scss
+++ b/src/components/AIassistant/components/AIassistant.scss
@@ -1,25 +1,28 @@
-.ai_assistant {
- height: 100%;
+#assistant_wrapper {
width: 100%;
- &__header {
- background-color: var(--sapContent_Illustrative_Color1);
- min-height: 60px;
- padding-left: 8px;
- padding-right: 8px;
+ .ai_assistant {
+ height: 100%;
+ width: 100%;
- .title {
- color: white;
- text-shadow: none;
- }
+ &__header {
+ background-color: var(--sapContent_Illustrative_Color1);
+ min-height: 60px;
+ padding: 0.5rem;
+
+ .title {
+ color: white;
+ text-shadow: none;
+ }
- .action {
- color: white;
- background: transparent;
+ .action {
+ color: white;
+ background: transparent;
+ }
}
- }
- .tab-container {
- height: calc(100vh - 60px - 1.4rem);
+ .tab-container {
+ height: calc(100vh - 60px - 1.4rem);
+ }
}
}
diff --git a/src/components/App/App.scss b/src/components/App/App.scss
index a658d7ce23..f72c0f17d6 100644
--- a/src/components/App/App.scss
+++ b/src/components/App/App.scss
@@ -25,8 +25,3 @@
min-height: 0;
position: relative;
}
-
-#assistant_wrapper {
- padding: 0 1rem 1rem 0;
- width: 100%;
-}
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index fa096b787a..875e745840 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -123,9 +123,7 @@ export default function App() {
size={showAssistant.fullScreen ? '100%' : '20%'}
minSize={325}
>
-
+
) : (
<>>
From 9d001835ec311deeca29c20348b05cedaf62a26a Mon Sep 17 00:00:00 2001
From: chriskari
Date: Fri, 12 Apr 2024 17:03:07 +0200
Subject: [PATCH 14/38] feat: added streaming of responses to UI
---
.../AIassistant/api/getChatResponse.js | 59 +++++++++++++------
.../AIassistant/api/getPromptSuggestions.js | 25 ++++----
.../AIassistant/components/AIOpener.js | 2 +-
.../AIassistant/components/Chat/Chat.js | 4 +-
.../components/Chat/messages/Message.js | 6 ++
5 files changed, 66 insertions(+), 30 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 7098ad0917..32fc28e1e0 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -3,21 +3,46 @@ export default async function getChatResponse({
handleSuccess,
handleError,
}) {
- try {
- const { response } = await fetch(
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm',
- {
- headers: {
- accept: 'application/json, text/plain, */*',
- 'content-type': 'application/json',
- },
- body: `{"question":"${prompt}"}`,
- method: 'POST',
- },
- ).then(result => result.json());
- handleSuccess(response);
- } catch (error) {
- handleError();
- console.error('Error fetching data:', error);
- }
+ const url =
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/chat'; /*'http://127.0.0.1:5000/api/v1/chat';*/
+ const payload = { question: prompt, session_id: 'abcdef12345' };
+
+ fetch(url, {
+ headers: {
+ accept: 'application/json',
+ 'content-type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ method: 'POST',
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ readChunk(reader, decoder, handleSuccess, handleError);
+ })
+ .catch(error => {
+ handleError();
+ console.error('Error fetching data:', error);
+ });
+}
+
+function readChunk(reader, decoder, handleSuccess, handleError) {
+ reader
+ .read()
+ .then(({ done, value }) => {
+ if (done) {
+ return;
+ }
+ const chunk = decoder.decode(value, { stream: true });
+ console.log(chunk);
+ handleSuccess(chunk);
+ readChunk(reader, decoder, handleSuccess, handleError);
+ })
+ .catch(error => {
+ handleError();
+ console.error('Error reading stream:', error);
+ });
}
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 1f8be0f754..715e6f9779 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -4,17 +4,22 @@ export default async function getPromptSuggestions({
resourceName = '',
}) {
try {
- let { results } = await fetch(
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init',
- {
- headers: {
- accept: 'application/json, text/plain, */*',
- 'content-type': 'application/json',
- },
- body: `{"resource_type":"${resourceType}","resource_name":"${resourceName}","namespace":"${namespace}"}`,
- method: 'POST',
+ const url =
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init'; /*'http://127.0.0.1:5000/api/v1/llm/init';*/
+ const payload = {
+ page_type: 'deployments.apps',
+ session_id: 'abcdef12345',
+ namespace: namespace,
+ }; //`{"resource_type":"${resourceType}","resource_name":"${resourceName}","namespace":"${namespace}"}`
+
+ let { results } = await fetch(url, {
+ headers: {
+ accept: 'application/json, text/plain, */*',
+ 'content-type': 'application/json',
},
- ).then(result => result.json());
+ body: JSON.stringify(payload),
+ method: 'POST',
+ }).then(result => result.json());
return results;
} catch (error) {
console.error('Error fetching data:', error);
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 97e2c29056..da8dbe7fa1 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -42,7 +42,7 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
const fetchSuggestions = async () => {
setErrorOccured(false);
setPopoverOpen(true);
- if (suggestions.length === 0) {
+ if (!isLoading && suggestions.length === 0) {
setIsLoading(true);
const promptSuggestions = await getPromptSuggestions({
namespace,
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 7d1b5d3fa2..cf31d5d4a9 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -41,7 +41,7 @@ export default function Chat() {
const sendPrompt = prompt => {
setErrorOccured(false);
- addMessage('user', prompt, false);
+ addMessage('user', { step: 'output', result: prompt }, false);
getChatResponse({ prompt, handleSuccess, handleError });
addMessage('ai', null, true);
};
@@ -100,7 +100,7 @@ export default function Chat() {
suggestions={
message.suggestions ?? [
'test123123123123123xyzxyzuwquxzytsabcde123456',
- 'Throw an error',
+ "Hey, how's it going?",
'What is your favorite football team?',
]
}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
index 3d45a4bf5a..cd6b1040f8 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.js
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -4,6 +4,12 @@ import CodePanel from './CodePanel';
import './Message.scss';
export default function Message({ className, message, isLoading }) {
+ if (typeof message === 'string') {
+ const jsonMessage = JSON.parse(message);
+ message = jsonMessage;
+ }
+
+ message = message?.result;
const segmentedText = segmentMarkdownText(message);
return (
From e07c5bf0b2397fd0ef5bb08b1b3e5b723122079b Mon Sep 17 00:00:00 2001
From: chriskari
Date: Sun, 14 Apr 2024 15:48:09 +0200
Subject: [PATCH 15/38] feat: added ui elements for streaming of message chunks
---
.../AIassistant/api/getChatResponse.js | 3 +-
.../AIassistant/components/Chat/Chat.js | 27 ++++++------
.../components/Chat/messages/Message.js | 42 +++++++++++++++----
.../components/Chat/messages/Message.scss | 21 ++++++++++
4 files changed, 70 insertions(+), 23 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 32fc28e1e0..652e7aa479 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -36,8 +36,7 @@ function readChunk(reader, decoder, handleSuccess, handleError) {
if (done) {
return;
}
- const chunk = decoder.decode(value, { stream: true });
- console.log(chunk);
+ const chunk = JSON.parse(decoder.decode(value, { stream: true }));
handleSuccess(chunk);
readChunk(reader, decoder, handleSuccess, handleError);
})
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index cf31d5d4a9..5727b55611 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -18,19 +18,20 @@ export default function Chat() {
const [errorOccured, setErrorOccured] = useState(false);
const initialPrompt = useRecoilValue(initialPromptState);
- const addMessage = (author, message, isLoading) => {
- setChatHistory(prevItems => [...prevItems, { author, message, isLoading }]);
+ const addMessage = (author, messageChunks, isLoading) => {
+ setChatHistory(prevItems =>
+ prevItems.concat({ author, messageChunks, isLoading }),
+ );
};
const handleSuccess = response => {
- setChatHistory(prevItems => {
- const newArray = [...prevItems];
- newArray[newArray.length - 1] = {
+ setChatHistory(prevMessages => {
+ const [latestMessage] = prevMessages.slice(-1);
+ return prevMessages.slice(0, -1).concat({
author: 'ai',
- message: response,
- isLoading: false,
- };
- return newArray;
+ messageChunks: latestMessage.messageChunks.concat(response),
+ isLoading: response?.step !== 'output',
+ });
});
};
@@ -41,9 +42,9 @@ export default function Chat() {
const sendPrompt = prompt => {
setErrorOccured(false);
- addMessage('user', { step: 'output', result: prompt }, false);
+ addMessage('user', [{ step: 'output', result: prompt }], false);
getChatResponse({ prompt, handleSuccess, handleError });
- addMessage('ai', null, true);
+ addMessage('ai', [], true);
};
const onSubmitInput = () => {
@@ -90,7 +91,7 @@ export default function Chat() {
{index === chatHistory.length - 1 && !message.isLoading && (
@@ -111,7 +112,7 @@ export default function Chat() {
);
})}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
index cd6b1040f8..454e606074 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.js
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -1,19 +1,45 @@
-import { BusyIndicator, Text } from '@ui5/webcomponents-react';
+import {
+ BusyIndicator,
+ FlexBox,
+ ObjectStatus,
+ Text,
+} from '@ui5/webcomponents-react';
import { segmentMarkdownText } from 'components/AIassistant/utils/formatMarkdown';
import CodePanel from './CodePanel';
import './Message.scss';
-export default function Message({ className, message, isLoading }) {
- if (typeof message === 'string') {
- const jsonMessage = JSON.parse(message);
- message = jsonMessage;
+export default function Message({ className, messageChunks, isLoading }) {
+ if (isLoading) {
+ return (
+
+ {messageChunks.length > 0 ? (
+ messageChunks.map((chunk, index) => (
+
+ {chunk?.result}
+
+ {index !== messageChunks.length - 1 ? (
+
+ ) : (
+
+ )}
+
+
+ ))
+ ) : (
+
+ )}
+
+ );
}
- message = message?.result;
- const segmentedText = segmentMarkdownText(message);
+ const segmentedText = segmentMarkdownText(messageChunks.slice(-1)[0]?.result);
return (
- {isLoading &&
}
{segmentedText && (
{segmentedText.map((segment, index) =>
diff --git a/src/components/AIassistant/components/Chat/messages/Message.scss b/src/components/AIassistant/components/Chat/messages/Message.scss
index f7956eb857..4f86da9a05 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.scss
+++ b/src/components/AIassistant/components/Chat/messages/Message.scss
@@ -2,6 +2,27 @@
max-width: 240px;
padding: 12px;
+ &.loading {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .loading-item {
+ gap: 8px;
+
+ .text {
+ flex-grow: 1;
+ }
+
+ .loading-status {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 35px;
+ }
+ }
+ }
+
.bold {
font-weight: bold;
font-size: 1rem;
From 124abed1d898f6ddd8d395a9a4046206538eb313 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Mon, 15 Apr 2024 11:14:47 +0200
Subject: [PATCH 16/38] fix: adjusted styling in fullscreen-mode
---
src/components/AIassistant/components/AIassistant.js | 6 ++++--
.../AIassistant/components/AIassistant.scss | 12 ++++++++++++
src/components/AIassistant/components/Chat/Chat.js | 7 ++++---
.../AIassistant/components/Chat/messages/Bubbles.js | 8 ++++++--
.../components/Chat/messages/Bubbles.scss | 4 ++++
.../components/Chat/messages/Message.scss | 4 ++++
6 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/src/components/AIassistant/components/AIassistant.js b/src/components/AIassistant/components/AIassistant.js
index e78a47906f..5266bb01f9 100644
--- a/src/components/AIassistant/components/AIassistant.js
+++ b/src/components/AIassistant/components/AIassistant.js
@@ -60,10 +60,12 @@ export default function AIassistant() {
-
+
diff --git a/src/components/AIassistant/components/AIassistant.scss b/src/components/AIassistant/components/AIassistant.scss
index c85a662389..d9fdfe529c 100644
--- a/src/components/AIassistant/components/AIassistant.scss
+++ b/src/components/AIassistant/components/AIassistant.scss
@@ -24,5 +24,17 @@
.tab-container {
height: calc(100vh - 60px - 1.4rem);
}
+
+ .tab-container::part(content) {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .tab-container.fullscreen::part(content) {
+ width: 60%;
+ margin-left: 20%;
+ margin-right: 20%;
+ }
}
}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 5727b55611..cb67b900cd 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -10,7 +10,7 @@ import ErrorMessage from './messages/ErrorMessage';
import getChatResponse from 'components/AIassistant/api/getChatResponse';
import './Chat.scss';
-export default function Chat() {
+export default function Chat({ isFullScreen }) {
const { t } = useTranslation();
const containerRef = useRef(null);
const [inputValue, setInputValue] = useState('');
@@ -90,13 +90,14 @@ export default function Chat() {
<>
{index === chatHistory.length - 1 && !message.isLoading && (
);
diff --git a/src/components/AIassistant/components/Chat/messages/Bubbles.js b/src/components/AIassistant/components/Chat/messages/Bubbles.js
index 3386201850..470fce12d3 100644
--- a/src/components/AIassistant/components/Chat/messages/Bubbles.js
+++ b/src/components/AIassistant/components/Chat/messages/Bubbles.js
@@ -1,9 +1,13 @@
import { Button, FlexBox } from '@ui5/webcomponents-react';
import './Bubbles.scss';
-export default function Bubbles({ suggestions, onClick }) {
+export default function Bubbles({ suggestions, onClick, className }) {
return (
-
+
{suggestions.map((suggestion, index) => (
Date: Mon, 15 Apr 2024 18:04:00 +0200
Subject: [PATCH 17/38] fix: adjusted init-call format to latest changes
---
.../AIassistant/api/getChatResponse.js | 2 +-
.../AIassistant/api/getPromptSuggestions.js | 16 ++++++++++------
.../AIassistant/components/AIOpener.js | 8 +++++++-
.../HelmReleases/HelmReleasesDetails.js | 1 +
.../ResourceDetails/ResourceDetails.js | 1 +
5 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 652e7aa479..aa74899783 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -4,7 +4,7 @@ export default async function getChatResponse({
handleError,
}) {
const url =
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/chat'; /*'http://127.0.0.1:5000/api/v1/chat';*/
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/chat';
const payload = { question: prompt, session_id: 'abcdef12345' };
fetch(url, {
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 715e6f9779..3b0c4e4979 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -1,16 +1,20 @@
+import { extractApiGroup } from 'resources/Roles/helpers';
+
export default async function getPromptSuggestions({
namespace = '',
resourceType = '',
+ groupVersion = '',
resourceName = '',
}) {
try {
const url =
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init'; /*'http://127.0.0.1:5000/api/v1/llm/init';*/
- const payload = {
- page_type: 'deployments.apps',
- session_id: 'abcdef12345',
- namespace: namespace,
- }; //`{"resource_type":"${resourceType}","resource_name":"${resourceName}","namespace":"${namespace}"}`
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init';
+ const apiGroup = extractApiGroup(groupVersion);
+ const payload = JSON.parse(
+ `{"resource_type":"${resourceType.toLowerCase()}${
+ apiGroup.length ? `.${apiGroup}` : ''
+ }","resource_name":"${resourceName}","namespace":"${namespace}","session_id":"abcdef12345"}`,
+ );
let { results } = await fetch(url, {
headers: {
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index da8dbe7fa1..de8c78d7c0 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -20,7 +20,12 @@ import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestion
import { createPortal } from 'react-dom';
import './AIOpener.scss';
-export default function AIOpener({ namespace, resourceType, resourceName }) {
+export default function AIOpener({
+ namespace,
+ resourceType,
+ groupVersion,
+ resourceName,
+}) {
const { t } = useTranslation();
const [showAssistant, setShowAssistant] = useRecoilState(
showAIassistantState,
@@ -47,6 +52,7 @@ export default function AIOpener({ namespace, resourceType, resourceName }) {
const promptSuggestions = await getPromptSuggestions({
namespace,
resourceType,
+ groupVersion,
resourceName,
});
setIsLoading(false);
diff --git a/src/components/HelmReleases/HelmReleasesDetails.js b/src/components/HelmReleases/HelmReleasesDetails.js
index 39682ba4da..b915b331f9 100644
--- a/src/components/HelmReleases/HelmReleasesDetails.js
+++ b/src/components/HelmReleases/HelmReleasesDetails.js
@@ -99,6 +99,7 @@ function HelmReleasesDetails({ releaseName, namespace }) {
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index 516c883548..8abc9097df 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -373,6 +373,7 @@ function Resource({
From b1c51f5e84a960d9401afe196753bc977651fb4d Mon Sep 17 00:00:00 2001
From: chriskari
Date: Mon, 15 Apr 2024 19:05:02 +0200
Subject: [PATCH 18/38] fix: added funny follow-up questions and fixed styling
issue
---
src/components/AIassistant/components/AIOpener.scss | 6 ++++++
src/components/AIassistant/components/Chat/Chat.js | 6 +++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/components/AIassistant/components/AIOpener.scss b/src/components/AIassistant/components/AIOpener.scss
index 4a0a177f93..ed346246e3 100644
--- a/src/components/AIassistant/components/AIOpener.scss
+++ b/src/components/AIassistant/components/AIOpener.scss
@@ -17,6 +17,12 @@
}
.custom-list-item {
+ .text {
+ width: 90%;
+ }
+ }
+
+ .custom-list-item::part(content) {
width: 100%;
display: flex;
align-items: center;
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index cb67b900cd..68be94c501 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -101,9 +101,9 @@ export default function Chat({ isFullScreen }) {
onClick={sendPrompt}
suggestions={
message.suggestions ?? [
- 'test123123123123123xyzxyzuwquxzytsabcde123456',
- "Hey, how's it going?",
- 'What is your favorite football team?',
+ 'What is the meaning of life?',
+ 'Where to buy cheap bitcoins?',
+ 'What should I do next?',
]
}
/>
From 2ff2aea9303aac9c1b0f12ed809766e3173c29df Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 16 Apr 2024 12:35:09 +0200
Subject: [PATCH 19/38] feat: creation of sessionID
---
.../AIassistant/api/getChatResponse.js | 3 ++-
.../AIassistant/api/getPromptSuggestions.js | 3 ++-
.../AIassistant/components/AIOpener.js | 16 ++++++++++++++--
.../AIassistant/components/Chat/Chat.js | 4 +++-
.../AIassistant/state/sessionIDAtom.ts | 10 ++++++++++
5 files changed, 31 insertions(+), 5 deletions(-)
create mode 100644 src/components/AIassistant/state/sessionIDAtom.ts
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index aa74899783..2c9e7c41d9 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -2,10 +2,11 @@ export default async function getChatResponse({
prompt,
handleSuccess,
handleError,
+ sessionID,
}) {
const url =
'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/chat';
- const payload = { question: prompt, session_id: 'abcdef12345' };
+ const payload = { question: prompt, session_id: sessionID };
fetch(url, {
headers: {
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 3b0c4e4979..96ae391de8 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -5,6 +5,7 @@ export default async function getPromptSuggestions({
resourceType = '',
groupVersion = '',
resourceName = '',
+ sessionID = '',
}) {
try {
const url =
@@ -13,7 +14,7 @@ export default async function getPromptSuggestions({
const payload = JSON.parse(
`{"resource_type":"${resourceType.toLowerCase()}${
apiGroup.length ? `.${apiGroup}` : ''
- }","resource_name":"${resourceName}","namespace":"${namespace}","session_id":"abcdef12345"}`,
+ }","resource_name":"${resourceName}","namespace":"${namespace}","session_id":"${sessionID}"}`,
);
let { results } = await fetch(url, {
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index de8c78d7c0..48d4ac1d4e 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -13,12 +13,15 @@ import {
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
import { useEffect, useState } from 'react';
-import { useRecoilState, useSetRecoilState } from 'recoil';
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestions';
import { createPortal } from 'react-dom';
import './AIOpener.scss';
+import CryptoJS from 'crypto-js';
+import { authDataState } from 'state/authDataAtom';
+import { sessionIDState } from '../state/sessionIDAtom';
export default function AIOpener({
namespace,
@@ -36,6 +39,14 @@ export default function AIOpener({
const [inputValue, setInputValue] = useState('');
const [errorOccured, setErrorOccured] = useState(false);
const [isLoading, setIsLoading] = useState(false);
+ const authData = useRecoilValue(authDataState);
+ const [sessionID, setSessionID] = useRecoilState(sessionIDState);
+
+ useEffect(() => {
+ if (authData) {
+ setSessionID(CryptoJS.SHA256(authData).toString(CryptoJS.enc.Hex));
+ }
+ }, [authData, sessionID, setSessionID]);
useEffect(() => {
return () => {
@@ -54,6 +65,7 @@ export default function AIOpener({
resourceType,
groupVersion,
resourceName,
+ sessionID,
});
setIsLoading(false);
if (!promptSuggestions) {
@@ -86,7 +98,7 @@ export default function AIOpener({
icon="ai"
className="ai-button"
id="openPopoverBtn"
- disabled={showAssistant.show}
+ disabled={showAssistant.show || !sessionID}
onClick={fetchSuggestions}
>
{t('ai-assistant.opener.use-ai')}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 68be94c501..92d6cb298f 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -9,6 +9,7 @@ import Bubbles from './messages/Bubbles';
import ErrorMessage from './messages/ErrorMessage';
import getChatResponse from 'components/AIassistant/api/getChatResponse';
import './Chat.scss';
+import { sessionIDState } from 'components/AIassistant/state/sessionIDAtom';
export default function Chat({ isFullScreen }) {
const { t } = useTranslation();
@@ -17,6 +18,7 @@ export default function Chat({ isFullScreen }) {
const [chatHistory, setChatHistory] = useState([]);
const [errorOccured, setErrorOccured] = useState(false);
const initialPrompt = useRecoilValue(initialPromptState);
+ const sessionID = useRecoilValue(sessionIDState);
const addMessage = (author, messageChunks, isLoading) => {
setChatHistory(prevItems =>
@@ -43,7 +45,7 @@ export default function Chat({ isFullScreen }) {
const sendPrompt = prompt => {
setErrorOccured(false);
addMessage('user', [{ step: 'output', result: prompt }], false);
- getChatResponse({ prompt, handleSuccess, handleError });
+ getChatResponse({ prompt, handleSuccess, handleError, sessionID });
addMessage('ai', [], true);
};
diff --git a/src/components/AIassistant/state/sessionIDAtom.ts b/src/components/AIassistant/state/sessionIDAtom.ts
new file mode 100644
index 0000000000..6ad48d75bb
--- /dev/null
+++ b/src/components/AIassistant/state/sessionIDAtom.ts
@@ -0,0 +1,10 @@
+import { atom, RecoilState } from 'recoil';
+
+type SessionID = string;
+
+const DEFAULT_SESSION_ID = '';
+
+export const sessionIDState: RecoilState = atom({
+ key: 'sessionIDState',
+ default: DEFAULT_SESSION_ID,
+});
From bec9f2a10276720bc2d148b56c13b6943f3fadcb Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 16 Apr 2024 13:18:51 +0200
Subject: [PATCH 20/38] fix: bug with hashing json objects
---
src/components/AIassistant/components/AIOpener.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 48d4ac1d4e..31b113803e 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -44,7 +44,9 @@ export default function AIOpener({
useEffect(() => {
if (authData) {
- setSessionID(CryptoJS.SHA256(authData).toString(CryptoJS.enc.Hex));
+ setSessionID(
+ CryptoJS.SHA256(JSON.stringify(authData)).toString(CryptoJS.enc.Hex),
+ );
}
}, [authData, sessionID, setSessionID]);
From 4f1fe8e503b6ccf37e30a982615b0ab84f477343 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 16 Apr 2024 16:04:26 +0200
Subject: [PATCH 21/38] feat: added uuid fingerprint to sessionID
---
package-lock.json | 12 ++++++++++++
package.json | 1 +
.../AIassistant/components/AIOpener.js | 16 +++++-----------
.../AIassistant/utils/generateSesssionID.js | 19 +++++++++++++++++++
4 files changed, 37 insertions(+), 11 deletions(-)
create mode 100644 src/components/AIassistant/utils/generateSesssionID.js
diff --git a/package-lock.json b/package-lock.json
index eb40b1c4d8..9b6bd8964b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,6 +35,7 @@
"cronstrue": "^1.114.0",
"didyoumean": "^1.2.2",
"file-saver": "^2.0.2",
+ "fingerprintjs2": "^2.1.4",
"graphviz-react": "^1.2.5",
"http-status-codes": "^2.2.0",
"i18next": "^22.0.4",
@@ -12964,6 +12965,12 @@
"node": ">=6"
}
},
+ "node_modules/fingerprintjs2": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/fingerprintjs2/-/fingerprintjs2-2.1.4.tgz",
+ "integrity": "sha512-veP2yVsnYvjDVkzZMyIEwpqCAQfsBLH+U4PK5MlFAnLjZrttbdRqEArE1fPcnJFz5oS5CrdONbsV7J6FGpIJEQ==",
+ "deprecated": "Package has been renamed to @fingerprintjs/fingerprintjs. Install @fingerprintjs/fingerprintjs to get updates."
+ },
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -41274,6 +41281,11 @@
"locate-path": "^3.0.0"
}
},
+ "fingerprintjs2": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/fingerprintjs2/-/fingerprintjs2-2.1.4.tgz",
+ "integrity": "sha512-veP2yVsnYvjDVkzZMyIEwpqCAQfsBLH+U4PK5MlFAnLjZrttbdRqEArE1fPcnJFz5oS5CrdONbsV7J6FGpIJEQ=="
+ },
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
diff --git a/package.json b/package.json
index d9779e9b07..4a58a7b995 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"cronstrue": "^1.114.0",
"didyoumean": "^1.2.2",
"file-saver": "^2.0.2",
+ "fingerprintjs2": "^2.1.4",
"graphviz-react": "^1.2.5",
"http-status-codes": "^2.2.0",
"i18next": "^22.0.4",
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 31b113803e..b9a105ee21 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -19,9 +19,9 @@ import { initialPromptState } from '../state/initalPromptAtom';
import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestions';
import { createPortal } from 'react-dom';
import './AIOpener.scss';
-import CryptoJS from 'crypto-js';
import { authDataState } from 'state/authDataAtom';
import { sessionIDState } from '../state/sessionIDAtom';
+import generateSessionID from '../utils/generateSesssionID';
export default function AIOpener({
namespace,
@@ -40,15 +40,7 @@ export default function AIOpener({
const [errorOccured, setErrorOccured] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const authData = useRecoilValue(authDataState);
- const [sessionID, setSessionID] = useRecoilState(sessionIDState);
-
- useEffect(() => {
- if (authData) {
- setSessionID(
- CryptoJS.SHA256(JSON.stringify(authData)).toString(CryptoJS.enc.Hex),
- );
- }
- }, [authData, sessionID, setSessionID]);
+ const setSessionID = useSetRecoilState(sessionIDState);
useEffect(() => {
return () => {
@@ -62,6 +54,8 @@ export default function AIOpener({
setPopoverOpen(true);
if (!isLoading && suggestions.length === 0) {
setIsLoading(true);
+ const sessionID = await generateSessionID(authData);
+ setSessionID(sessionID);
const promptSuggestions = await getPromptSuggestions({
namespace,
resourceType,
@@ -100,7 +94,7 @@ export default function AIOpener({
icon="ai"
className="ai-button"
id="openPopoverBtn"
- disabled={showAssistant.show || !sessionID}
+ disabled={showAssistant.show}
onClick={fetchSuggestions}
>
{t('ai-assistant.opener.use-ai')}
diff --git a/src/components/AIassistant/utils/generateSesssionID.js b/src/components/AIassistant/utils/generateSesssionID.js
new file mode 100644
index 0000000000..028f0b8298
--- /dev/null
+++ b/src/components/AIassistant/utils/generateSesssionID.js
@@ -0,0 +1,19 @@
+import CryptoJS from 'crypto-js';
+import Fingerprint2 from 'fingerprintjs2';
+
+export default async function generateSessionID(authData) {
+ const uuid = await generateBrowserFingerprint();
+ return CryptoJS.SHA256(uuid + JSON.stringify(authData)).toString(
+ CryptoJS.enc.Hex,
+ );
+}
+
+const generateBrowserFingerprint = async () => {
+ return await new Promise(resolve => {
+ Fingerprint2.get(components => {
+ const values = components.map(component => component.value);
+ const fingerprint = Fingerprint2.x64hash128(values.join(''), 31);
+ resolve(fingerprint);
+ });
+ });
+};
From 64282b5653fbf07581432414bcf08cdc5a436b18 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 23 Apr 2024 09:42:43 +0200
Subject: [PATCH 22/38] feat: added follow-up questions to Companion-UI
---
.../AIassistant/api/getChatResponse.js | 16 ++++++----
.../AIassistant/api/getFollowUpQuestions.js | 22 ++++++++++++++
.../AIassistant/components/Chat/Chat.js | 30 ++++++++++++-------
.../components/Chat/messages/Bubbles.js | 4 ++-
.../components/Chat/messages/Message.scss | 4 +++
5 files changed, 59 insertions(+), 17 deletions(-)
create mode 100644 src/components/AIassistant/api/getFollowUpQuestions.js
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 2c9e7c41d9..5d8e10d35a 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,6 +1,6 @@
export default async function getChatResponse({
prompt,
- handleSuccess,
+ handleChatResponse,
handleError,
sessionID,
}) {
@@ -22,7 +22,7 @@ export default async function getChatResponse({
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
- readChunk(reader, decoder, handleSuccess, handleError);
+ readChunk(reader, decoder, handleChatResponse, handleError, sessionID);
})
.catch(error => {
handleError();
@@ -30,7 +30,13 @@ export default async function getChatResponse({
});
}
-function readChunk(reader, decoder, handleSuccess, handleError) {
+function readChunk(
+ reader,
+ decoder,
+ handleChatResponse,
+ handleError,
+ sessionID,
+) {
reader
.read()
.then(({ done, value }) => {
@@ -38,8 +44,8 @@ function readChunk(reader, decoder, handleSuccess, handleError) {
return;
}
const chunk = JSON.parse(decoder.decode(value, { stream: true }));
- handleSuccess(chunk);
- readChunk(reader, decoder, handleSuccess, handleError);
+ handleChatResponse(chunk);
+ readChunk(reader, decoder, handleChatResponse, handleError, sessionID);
})
.catch(error => {
handleError();
diff --git a/src/components/AIassistant/api/getFollowUpQuestions.js b/src/components/AIassistant/api/getFollowUpQuestions.js
new file mode 100644
index 0000000000..5d5ca785b5
--- /dev/null
+++ b/src/components/AIassistant/api/getFollowUpQuestions.js
@@ -0,0 +1,22 @@
+export default async function getFollowUpQuestions({
+ sessionID = '',
+ handleFollowUpQuestions,
+}) {
+ try {
+ const url =
+ 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/followup';
+ const payload = JSON.parse(`{"session_id":"${sessionID}"}`);
+
+ let { results } = await fetch(url, {
+ headers: {
+ accept: 'application/json, text/plain, */*',
+ 'content-type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ method: 'POST',
+ }).then(result => result.json());
+ handleFollowUpQuestions(results);
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 92d6cb298f..0164629c4b 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -8,8 +8,9 @@ import Message from './messages/Message';
import Bubbles from './messages/Bubbles';
import ErrorMessage from './messages/ErrorMessage';
import getChatResponse from 'components/AIassistant/api/getChatResponse';
-import './Chat.scss';
import { sessionIDState } from 'components/AIassistant/state/sessionIDAtom';
+import getFollowUpQuestions from 'components/AIassistant/api/getFollowUpQuestions';
+import './Chat.scss';
export default function Chat({ isFullScreen }) {
const { t } = useTranslation();
@@ -26,17 +27,30 @@ export default function Chat({ isFullScreen }) {
);
};
- const handleSuccess = response => {
+ const handleChatResponse = response => {
+ const isLoading = response?.step !== 'output';
+ if (!isLoading) {
+ getFollowUpQuestions({ sessionID, handleFollowUpQuestions });
+ }
setChatHistory(prevMessages => {
const [latestMessage] = prevMessages.slice(-1);
return prevMessages.slice(0, -1).concat({
author: 'ai',
messageChunks: latestMessage.messageChunks.concat(response),
- isLoading: response?.step !== 'output',
+ isLoading,
});
});
};
+ const handleFollowUpQuestions = questions => {
+ setChatHistory(prevMessages => {
+ const [latestMessage] = prevMessages.slice(-1);
+ return prevMessages
+ .slice(0, -1)
+ .concat({ ...latestMessage, suggestions: questions });
+ });
+ };
+
const handleError = () => {
setErrorOccured(true);
setChatHistory(prevItems => prevItems.slice(0, -2));
@@ -45,7 +59,7 @@ export default function Chat({ isFullScreen }) {
const sendPrompt = prompt => {
setErrorOccured(false);
addMessage('user', [{ step: 'output', result: prompt }], false);
- getChatResponse({ prompt, handleSuccess, handleError, sessionID });
+ getChatResponse({ prompt, handleChatResponse, handleError, sessionID });
addMessage('ai', [], true);
};
@@ -101,13 +115,7 @@ export default function Chat({ isFullScreen }) {
key={index + '.2'}
className={isFullScreen ? 'fullscreen' : ''}
onClick={sendPrompt}
- suggestions={
- message.suggestions ?? [
- 'What is the meaning of life?',
- 'Where to buy cheap bitcoins?',
- 'What should I do next?',
- ]
- }
+ suggestions={message.suggestions}
/>
)}
>
diff --git a/src/components/AIassistant/components/Chat/messages/Bubbles.js b/src/components/AIassistant/components/Chat/messages/Bubbles.js
index 470fce12d3..309af694ae 100644
--- a/src/components/AIassistant/components/Chat/messages/Bubbles.js
+++ b/src/components/AIassistant/components/Chat/messages/Bubbles.js
@@ -2,7 +2,7 @@ import { Button, FlexBox } from '@ui5/webcomponents-react';
import './Bubbles.scss';
export default function Bubbles({ suggestions, onClick, className }) {
- return (
+ return suggestions ? (
))}
+ ) : (
+ <>>
);
}
diff --git a/src/components/AIassistant/components/Chat/messages/Message.scss b/src/components/AIassistant/components/Chat/messages/Message.scss
index 7275c18072..7801364d81 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.scss
+++ b/src/components/AIassistant/components/Chat/messages/Message.scss
@@ -35,6 +35,10 @@
margin: 0.1rem 0;
border-radius: 4px;
}
+
+ .text {
+ line-height: 1.25;
+ }
}
.message.fullscreen {
From 74894d8040926ead385ab3ba870171a57b5d6579 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 23 Apr 2024 09:45:26 +0200
Subject: [PATCH 23/38] feat: layout is now rezisable
---
src/components/App/App.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 875e745840..e350e034e2 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -76,7 +76,7 @@ export default function App() {
return (
{showAssistant.show ? (
From 97b64b18e57156400a593a47f50840cd5a5dbe6a Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 23 Apr 2024 10:01:34 +0200
Subject: [PATCH 24/38] fix: resizing bugs
---
src/components/App/App.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index e350e034e2..8f4dde7988 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -76,7 +76,7 @@ export default function App() {
return (
{showAssistant.show ? (
From b888e10f68b9373f4090463eb03f6416e5625398 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 23 Apr 2024 16:10:14 +0200
Subject: [PATCH 25/38] fix: some smaller styling adjustments
---
public/i18n/en.yaml | 2 +-
.../AIassistant/components/AIassistant.js | 12 ++++------
.../AIassistant/components/AIassistant.scss | 24 +++++++++++--------
.../AIassistant/components/Chat/Chat.js | 7 +++---
.../components/Chat/messages/Bubbles.js | 8 ++-----
.../components/Chat/messages/Bubbles.scss | 6 +----
.../components/Chat/messages/Message.scss | 8 ++-----
7 files changed, 28 insertions(+), 39 deletions(-)
diff --git a/public/i18n/en.yaml b/public/i18n/en.yaml
index 0f72cf8817..34c012b6e7 100644
--- a/public/i18n/en.yaml
+++ b/public/i18n/en.yaml
@@ -6,7 +6,7 @@ apps:
ai-assistant:
name: Joule
opener:
- use-ai: Use AI
+ use-ai: AI Companion
suggestions: Suggestions
input-placeholder: Ask about this resource
error-message: Couldn't fetch suggestions. Please try again.
diff --git a/src/components/AIassistant/components/AIassistant.js b/src/components/AIassistant/components/AIassistant.js
index 5266bb01f9..b4194df4b3 100644
--- a/src/components/AIassistant/components/AIassistant.js
+++ b/src/components/AIassistant/components/AIassistant.js
@@ -12,7 +12,7 @@ import { spacing } from '@ui5/webcomponents-react-base';
import { useRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import Chat from './Chat/Chat';
-import PageInsights from './PageInsights/PageInsights';
+//import PageInsights from './PageInsights/PageInsights';
import './AIassistant.scss';
export default function AIassistant() {
@@ -60,16 +60,14 @@ export default function AIassistant() {
-
+
-
+ {/*
-
+ */}
diff --git a/src/components/AIassistant/components/AIassistant.scss b/src/components/AIassistant/components/AIassistant.scss
index d9fdfe529c..99d0f4f2bd 100644
--- a/src/components/AIassistant/components/AIassistant.scss
+++ b/src/components/AIassistant/components/AIassistant.scss
@@ -24,17 +24,21 @@
.tab-container {
height: calc(100vh - 60px - 1.4rem);
}
+ }
+}
- .tab-container::part(content) {
- width: 100%;
- margin-left: 0;
- margin-right: 0;
- }
+@container (max-width: 950px) {
+ .tab-container::part(content) {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
- .tab-container.fullscreen::part(content) {
- width: 60%;
- margin-left: 20%;
- margin-right: 20%;
- }
+@container (min-width: 950px) {
+ .tab-container::part(content) {
+ width: 70%;
+ margin-left: 15%;
+ margin-right: 15%;
}
}
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 0164629c4b..63718fe632 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -12,7 +12,7 @@ import { sessionIDState } from 'components/AIassistant/state/sessionIDAtom';
import getFollowUpQuestions from 'components/AIassistant/api/getFollowUpQuestions';
import './Chat.scss';
-export default function Chat({ isFullScreen }) {
+export default function Chat() {
const { t } = useTranslation();
const containerRef = useRef(null);
const [inputValue, setInputValue] = useState('');
@@ -106,14 +106,13 @@ export default function Chat({ isFullScreen }) {
<>
{index === chatHistory.length - 1 && !message.isLoading && (
@@ -122,7 +121,7 @@ export default function Chat({ isFullScreen }) {
) : (
);
diff --git a/src/components/AIassistant/components/Chat/messages/Bubbles.js b/src/components/AIassistant/components/Chat/messages/Bubbles.js
index 309af694ae..1057e1171d 100644
--- a/src/components/AIassistant/components/Chat/messages/Bubbles.js
+++ b/src/components/AIassistant/components/Chat/messages/Bubbles.js
@@ -1,13 +1,9 @@
import { Button, FlexBox } from '@ui5/webcomponents-react';
import './Bubbles.scss';
-export default function Bubbles({ suggestions, onClick, className }) {
+export default function Bubbles({ suggestions, onClick }) {
return suggestions ? (
-
+
{suggestions.map((suggestion, index) => (
Date: Tue, 23 Apr 2024 23:55:09 +0200
Subject: [PATCH 26/38] added error handling and fixed two-chunks-bug
---
src/components/AIassistant/api/getChatResponse.js | 13 +++++++++++--
.../AIassistant/api/getFollowUpQuestions.js | 3 ++-
.../AIassistant/api/getPromptSuggestions.js | 3 ++-
src/components/AIassistant/components/AIOpener.js | 2 +-
4 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 5d8e10d35a..32c4709f54 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -12,6 +12,7 @@ export default async function getChatResponse({
headers: {
accept: 'application/json',
'content-type': 'application/json',
+ 'x-user': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
@@ -43,8 +44,16 @@ function readChunk(
if (done) {
return;
}
- const chunk = JSON.parse(decoder.decode(value, { stream: true }));
- handleChatResponse(chunk);
+ // Also handles the rare case of two chunks being sent at once
+ const receivedString = decoder.decode(value, { stream: true });
+ const chunks = receivedString.match(/{[^{}]*}/g);
+ chunks.forEach(chunk => {
+ const jsonChunk = JSON.parse(chunk);
+ if ('error' in jsonChunk) {
+ throw new Error(jsonChunk.error);
+ }
+ handleChatResponse(jsonChunk);
+ });
readChunk(reader, decoder, handleChatResponse, handleError, sessionID);
})
.catch(error => {
diff --git a/src/components/AIassistant/api/getFollowUpQuestions.js b/src/components/AIassistant/api/getFollowUpQuestions.js
index 5d5ca785b5..9fa1dbc84e 100644
--- a/src/components/AIassistant/api/getFollowUpQuestions.js
+++ b/src/components/AIassistant/api/getFollowUpQuestions.js
@@ -9,8 +9,9 @@ export default async function getFollowUpQuestions({
let { results } = await fetch(url, {
headers: {
- accept: 'application/json, text/plain, */*',
+ accept: 'application/json',
'content-type': 'application/json',
+ 'x-user': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index 96ae391de8..bc1d138ae2 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -19,8 +19,9 @@ export default async function getPromptSuggestions({
let { results } = await fetch(url, {
headers: {
- accept: 'application/json, text/plain, */*',
+ accept: 'application/json',
'content-type': 'application/json',
+ 'x-user': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index b9a105ee21..ec2af8c655 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -18,10 +18,10 @@ import { showAIassistantState } from 'components/AIassistant/state/showAIassista
import { initialPromptState } from '../state/initalPromptAtom';
import getPromptSuggestions from 'components/AIassistant/api/getPromptSuggestions';
import { createPortal } from 'react-dom';
-import './AIOpener.scss';
import { authDataState } from 'state/authDataAtom';
import { sessionIDState } from '../state/sessionIDAtom';
import generateSessionID from '../utils/generateSesssionID';
+import './AIOpener.scss';
export default function AIOpener({
namespace,
From c90ab5669d5399625ceebeda4acaa2cdc54a4c29 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 24 Apr 2024 00:13:22 +0200
Subject: [PATCH 27/38] fix: cleared unique-key console-warnings
---
src/components/AIassistant/components/Chat/Chat.js | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 63718fe632..78888a7bd7 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
-import { useEffect, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { FlexBox, Icon, Input } from '@ui5/webcomponents-react';
import { spacing } from '@ui5/webcomponents-react-base';
@@ -103,25 +103,23 @@ export default function Chat() {
>
{chatHistory.map((message, index) => {
return message.author === 'ai' ? (
- <>
+
{index === chatHistory.length - 1 && !message.isLoading && (
)}
- >
+
) : (
);
From 592c78d04e090df6c76195fb53f077c830e5fd7e Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 24 Apr 2024 09:24:39 +0200
Subject: [PATCH 28/38] fix: assistant remains open on edit-view
---
src/components/AIassistant/components/AIOpener.js | 9 +--------
.../views/ClusterOverview/ClusterOverview.js | 13 +++++++++++--
src/components/HelmReleases/HelmReleasesDetails.js | 11 +++++++++++
.../components/ResourceDetails/ResourceDetails.js | 13 +++++++++++--
4 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index ec2af8c655..2664f4630e 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -12,7 +12,7 @@ import {
} from '@ui5/webcomponents-react';
import { spacing } from '@ui5/webcomponents-react-base';
import { useTranslation } from 'react-i18next';
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
import { initialPromptState } from '../state/initalPromptAtom';
@@ -42,13 +42,6 @@ export default function AIOpener({
const authData = useRecoilValue(authDataState);
const setSessionID = useSetRecoilState(sessionIDState);
- useEffect(() => {
- return () => {
- setShowAssistant({ show: false, fullScreen: false });
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
const fetchSuggestions = async () => {
setErrorOccured(false);
setPopoverOpen(true);
diff --git a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
index 3d6b642b49..5846d3135e 100644
--- a/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
+++ b/src/components/Clusters/views/ClusterOverview/ClusterOverview.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { Button, FlexBox, Title } from '@ui5/webcomponents-react';
import { ClusterNodes } from './ClusterNodes';
import { ClusterValidation } from './ClusterValidation/ClusterValidation';
@@ -16,10 +16,11 @@ import { useNotification } from 'shared/contexts/NotificationContext';
import { useNavigate } from 'react-router-dom';
import { deleteCluster } from 'components/Clusters/shared';
import { spacing } from '@ui5/webcomponents-react-base';
-import './ClusterOverview.scss';
import AIOpener from 'components/AIassistant/components/AIOpener';
import { useSetRecoilState } from 'recoil';
import { showYamlUploadDialogState } from 'state/showYamlUploadDialogAtom';
+import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
+import './ClusterOverview.scss';
const Injections = React.lazy(() =>
import('../../../Extensibility/ExtensibilityInjections'),
@@ -37,6 +38,14 @@ export function ClusterOverview() {
resourceType: t('clusters.labels.name'),
});
const setShowAdd = useSetRecoilState(showYamlUploadDialogState);
+ const setShowAssistant = useSetRecoilState(showAIassistantState);
+
+ useEffect(() => {
+ return () => {
+ setShowAssistant({ show: false, fullScreen: false });
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
const actions = [
{
+ return () => {
+ setShowAssistant({ show: false, fullScreen: false });
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
const { data, loading } = useGetList(s => s.type === 'helm.sh/release.v1')(
namespace === '-all-'
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index 8abc9097df..bb26e79935 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -1,4 +1,4 @@
-import React, { createContext, Suspense, useState } from 'react';
+import React, { createContext, Suspense, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import pluralize from 'pluralize';
import { useTranslation } from 'react-i18next';
@@ -30,9 +30,10 @@ import { ResourceStatusCard } from '../ResourceStatusCard/ResourceStatusCard';
import { EMPTY_TEXT_PLACEHOLDER } from '../../constants';
import { ReadableElapsedTimeFromNow } from '../ReadableElapsedTimeFromNow/ReadableElapsedTimeFromNow';
import { HintButton } from '../DescriptionHint/DescriptionHint';
-import { useRecoilValue } from 'recoil';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useFeature } from 'hooks/useFeature';
import { columnLayoutState } from 'state/columnLayoutAtom';
+import { showAIassistantState } from 'components/AIassistant/state/showAIassistantAtom';
// This component is loaded after the page mounts.
// Don't try to load it on scroll. It was tested.
@@ -173,6 +174,7 @@ function Resource({
resource.kind,
);
const [showTitleDescription, setShowTitleDescription] = useState(false);
+ const setShowAssistant = useSetRecoilState(showAIassistantState);
const pluralizedResourceKind = pluralize(prettifiedResourceKind);
useWindowTitle(windowTitle || pluralizedResourceKind);
@@ -190,6 +192,13 @@ function Resource({
const protectedResource = isProtected(resource);
+ useEffect(() => {
+ return () => {
+ setShowAssistant({ show: false, fullScreen: false });
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const deleteButtonWrapper = children => {
if (protectedResource) {
return (
From 16cebd54388b8554192faea1f9c049ba680fea64 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Wed, 24 Apr 2024 15:00:46 +0200
Subject: [PATCH 29/38] feat: added markdown formatting of links
---
src/components/AIassistant/api/getChatResponse.js | 11 ++++++-----
.../AIassistant/components/Chat/messages/Message.js | 5 +++++
src/components/AIassistant/utils/formatMarkdown.js | 10 +++++++++-
3 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 32c4709f54..eecaf0763c 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -46,13 +46,14 @@ function readChunk(
}
// Also handles the rare case of two chunks being sent at once
const receivedString = decoder.decode(value, { stream: true });
- const chunks = receivedString.match(/{[^{}]*}/g);
+ const chunks = receivedString.match(/{[^{}]*}/g).map(chunk => {
+ return JSON.parse(chunk);
+ });
chunks.forEach(chunk => {
- const jsonChunk = JSON.parse(chunk);
- if ('error' in jsonChunk) {
- throw new Error(jsonChunk.error);
+ if ('error' in chunk) {
+ throw new Error(chunk.error);
}
- handleChatResponse(jsonChunk);
+ handleChatResponse(chunk);
});
readChunk(reader, decoder, handleChatResponse, handleError, sessionID);
})
diff --git a/src/components/AIassistant/components/Chat/messages/Message.js b/src/components/AIassistant/components/Chat/messages/Message.js
index 454e606074..dca76c0123 100644
--- a/src/components/AIassistant/components/Chat/messages/Message.js
+++ b/src/components/AIassistant/components/Chat/messages/Message.js
@@ -1,6 +1,7 @@
import {
BusyIndicator,
FlexBox,
+ Link,
ObjectStatus,
Text,
} from '@ui5/webcomponents-react';
@@ -53,6 +54,10 @@ export default function Message({ className, messageChunks, isLoading }) {
{segment.content}
+ ) : segment.type === 'link' ? (
+
+ {segment.content.name}
+
) : (
segment.content
),
diff --git a/src/components/AIassistant/utils/formatMarkdown.js b/src/components/AIassistant/utils/formatMarkdown.js
index c725f97162..944f783ec1 100644
--- a/src/components/AIassistant/utils/formatMarkdown.js
+++ b/src/components/AIassistant/utils/formatMarkdown.js
@@ -1,6 +1,6 @@
export function segmentMarkdownText(text) {
if (!text) return [];
- const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```\s)|(`(.*?)`)|[^*`]+/g;
+ const regex = /(\*\*(.*?)\*\*)|(```([\s\S]*?)```\s)|(`(.*?)`)|\[(.*?)\]\((.*?)\)|[^[\]*`]+/g;
return text.match(regex).map(segment => {
if (segment.startsWith('**')) {
return {
@@ -17,6 +17,14 @@ export function segmentMarkdownText(text) {
type: 'highlighted',
content: segment.replace(/`/g, ''),
};
+ } else if (segment.startsWith('[') && segment.endsWith(')')) {
+ return {
+ type: 'link',
+ content: {
+ name: segment.match(/\[(.*?)\]/)[0].replace(/^\[|\]$/g, ''),
+ address: segment.match(/\((.*?)\)/)[0].replace(/^\(|\)$/g, ''),
+ },
+ };
} else {
return {
type: 'normal',
From d00dbb7db4704a03fd1dd30b672165fe6733d020 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 30 Apr 2024 16:12:06 +0200
Subject: [PATCH 30/38] feat: switched to secure internal cluster endpoints
---
.../AIassistant/api/getChatResponse.js | 15 ++++++++++---
.../AIassistant/api/getFollowUpQuestions.js | 15 ++++++++++---
.../AIassistant/api/getPromptSuggestions.js | 14 +++++++++---
.../AIassistant/components/AIOpener.js | 6 +++++
.../AIassistant/components/Chat/Chat.js | 22 +++++++++++++++++--
5 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index eecaf0763c..9d54c31c9e 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,18 +1,27 @@
+import { getClusterConfig } from 'state/utils/getBackendInfo';
+
export default async function getChatResponse({
prompt,
handleChatResponse,
handleError,
sessionID,
+ clusterUrl,
+ token,
+ certificateAuthorityData,
}) {
- const url =
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/chat';
+ const { backendAddress } = getClusterConfig();
+ const url = `${backendAddress}/api/v1/namespaces/ai-core/services/http:ai-backend-clusterip:5000/proxy/api/v1/chat`;
const payload = { question: prompt, session_id: sessionID };
+ const k8sAuthorization = `Bearer ${token}`;
fetch(url, {
headers: {
accept: 'application/json',
'content-type': 'application/json',
- 'x-user': sessionID,
+ 'X-Cluster-Certificate-Authority-Data': certificateAuthorityData,
+ 'X-Cluster-Url': clusterUrl,
+ 'X-K8s-Authorization': k8sAuthorization,
+ 'X-User': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
diff --git a/src/components/AIassistant/api/getFollowUpQuestions.js b/src/components/AIassistant/api/getFollowUpQuestions.js
index 9fa1dbc84e..8d8ccea53d 100644
--- a/src/components/AIassistant/api/getFollowUpQuestions.js
+++ b/src/components/AIassistant/api/getFollowUpQuestions.js
@@ -1,17 +1,26 @@
+import { getClusterConfig } from 'state/utils/getBackendInfo';
+
export default async function getFollowUpQuestions({
sessionID = '',
handleFollowUpQuestions,
+ clusterUrl,
+ token,
+ certificateAuthorityData,
}) {
try {
- const url =
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/followup';
+ const { backendAddress } = getClusterConfig();
+ const url = `${backendAddress}/api/v1/namespaces/ai-core/services/http:ai-backend-clusterip:5000/proxy/api/v1/llm/followup`;
const payload = JSON.parse(`{"session_id":"${sessionID}"}`);
+ const k8sAuthorization = `Bearer ${token}`;
let { results } = await fetch(url, {
headers: {
accept: 'application/json',
'content-type': 'application/json',
- 'x-user': sessionID,
+ 'X-Cluster-Certificate-Authority-Data': certificateAuthorityData,
+ 'X-Cluster-Url': clusterUrl,
+ 'X-K8s-Authorization': k8sAuthorization,
+ 'X-User': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
diff --git a/src/components/AIassistant/api/getPromptSuggestions.js b/src/components/AIassistant/api/getPromptSuggestions.js
index bc1d138ae2..b2448f4392 100644
--- a/src/components/AIassistant/api/getPromptSuggestions.js
+++ b/src/components/AIassistant/api/getPromptSuggestions.js
@@ -1,3 +1,4 @@
+import { getClusterConfig } from 'state/utils/getBackendInfo';
import { extractApiGroup } from 'resources/Roles/helpers';
export default async function getPromptSuggestions({
@@ -6,22 +7,29 @@ export default async function getPromptSuggestions({
groupVersion = '',
resourceName = '',
sessionID = '',
+ clusterUrl,
+ token,
+ certificateAuthorityData,
}) {
try {
- const url =
- 'https://api-backend.c-5cb6076.stage.kyma.ondemand.com/api/v1/llm/init';
+ const { backendAddress } = getClusterConfig();
+ const url = `${backendAddress}/api/v1/namespaces/ai-core/services/http:ai-backend-clusterip:5000/proxy/api/v1/llm/init`;
const apiGroup = extractApiGroup(groupVersion);
const payload = JSON.parse(
`{"resource_type":"${resourceType.toLowerCase()}${
apiGroup.length ? `.${apiGroup}` : ''
}","resource_name":"${resourceName}","namespace":"${namespace}","session_id":"${sessionID}"}`,
);
+ const k8sAuthorization = `Bearer ${token}`;
let { results } = await fetch(url, {
headers: {
accept: 'application/json',
'content-type': 'application/json',
- 'x-user': sessionID,
+ 'X-Cluster-Certificate-Authority-Data': certificateAuthorityData,
+ 'X-Cluster-Url': clusterUrl,
+ 'X-K8s-Authorization': k8sAuthorization,
+ 'X-User': sessionID,
},
body: JSON.stringify(payload),
method: 'POST',
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index 2664f4630e..c6b6fb196d 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -22,6 +22,7 @@ import { authDataState } from 'state/authDataAtom';
import { sessionIDState } from '../state/sessionIDAtom';
import generateSessionID from '../utils/generateSesssionID';
import './AIOpener.scss';
+import { clusterState } from 'state/clusterAtom';
export default function AIOpener({
namespace,
@@ -41,6 +42,7 @@ export default function AIOpener({
const [isLoading, setIsLoading] = useState(false);
const authData = useRecoilValue(authDataState);
const setSessionID = useSetRecoilState(sessionIDState);
+ const cluster = useRecoilValue(clusterState);
const fetchSuggestions = async () => {
setErrorOccured(false);
@@ -55,6 +57,10 @@ export default function AIOpener({
groupVersion,
resourceName,
sessionID,
+ clusterUrl: cluster.currentContext.cluster.cluster.server,
+ token: cluster.currentContext.user.user.token,
+ certificateAuthorityData:
+ cluster.currentContext.cluster.cluster['certificate-authority-data'],
});
setIsLoading(false);
if (!promptSuggestions) {
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index 78888a7bd7..fe9de450ac 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -10,6 +10,7 @@ import ErrorMessage from './messages/ErrorMessage';
import getChatResponse from 'components/AIassistant/api/getChatResponse';
import { sessionIDState } from 'components/AIassistant/state/sessionIDAtom';
import getFollowUpQuestions from 'components/AIassistant/api/getFollowUpQuestions';
+import { clusterState } from 'state/clusterAtom';
import './Chat.scss';
export default function Chat() {
@@ -20,6 +21,7 @@ export default function Chat() {
const [errorOccured, setErrorOccured] = useState(false);
const initialPrompt = useRecoilValue(initialPromptState);
const sessionID = useRecoilValue(sessionIDState);
+ const cluster = useRecoilValue(clusterState);
const addMessage = (author, messageChunks, isLoading) => {
setChatHistory(prevItems =>
@@ -30,7 +32,14 @@ export default function Chat() {
const handleChatResponse = response => {
const isLoading = response?.step !== 'output';
if (!isLoading) {
- getFollowUpQuestions({ sessionID, handleFollowUpQuestions });
+ getFollowUpQuestions({
+ sessionID,
+ handleFollowUpQuestions,
+ clusterUrl: cluster.currentContext.cluster.cluster.server,
+ token: cluster.currentContext.user.user.token,
+ certificateAuthorityData:
+ cluster.currentContext.cluster.cluster['certificate-authority-data'],
+ });
}
setChatHistory(prevMessages => {
const [latestMessage] = prevMessages.slice(-1);
@@ -59,7 +68,16 @@ export default function Chat() {
const sendPrompt = prompt => {
setErrorOccured(false);
addMessage('user', [{ step: 'output', result: prompt }], false);
- getChatResponse({ prompt, handleChatResponse, handleError, sessionID });
+ getChatResponse({
+ prompt,
+ handleChatResponse,
+ handleError,
+ sessionID,
+ clusterUrl: cluster.currentContext.cluster.cluster.server,
+ token: cluster.currentContext.user.user.token,
+ certificateAuthorityData:
+ cluster.currentContext.cluster.cluster['certificate-authority-data'],
+ });
addMessage('ai', [], true);
};
From 821806c26d9d578b8046de2c5a8c37c8eb34cc9d Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 14 May 2024 09:45:51 +0200
Subject: [PATCH 31/38] fix: get token from right path in code
---
src/components/AIassistant/components/AIOpener.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/AIassistant/components/AIOpener.js b/src/components/AIassistant/components/AIOpener.js
index c6b6fb196d..0aa821bc41 100644
--- a/src/components/AIassistant/components/AIOpener.js
+++ b/src/components/AIassistant/components/AIOpener.js
@@ -58,7 +58,7 @@ export default function AIOpener({
resourceName,
sessionID,
clusterUrl: cluster.currentContext.cluster.cluster.server,
- token: cluster.currentContext.user.user.token,
+ token: authData.token,
certificateAuthorityData:
cluster.currentContext.cluster.cluster['certificate-authority-data'],
});
From e7002b82acec0b057c86baf52d7a71f9fb8df0fc Mon Sep 17 00:00:00 2001
From: chriskari
Date: Tue, 14 May 2024 10:26:20 +0200
Subject: [PATCH 32/38] fix: mistake from merging with main
---
.../components/ResourceDetails/ResourceDetails.js | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index c1576c9b54..e123311cc1 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -400,19 +400,6 @@ function Resource({
resourceName={resource?.metadata?.name}
/>
-
- {resourceDetailsCard}
- {resourceStatusCard && resourceStatusCard}
-
{!disableResourceDetailsCard && (
<>
Date: Tue, 14 May 2024 10:35:01 +0200
Subject: [PATCH 33/38] fix: mistake from merging with main
---
.../ResourceDetails/ResourceDetails.js | 37 +++++++------------
1 file changed, 14 insertions(+), 23 deletions(-)
diff --git a/src/shared/components/ResourceDetails/ResourceDetails.js b/src/shared/components/ResourceDetails/ResourceDetails.js
index e123311cc1..a8a22e8b9d 100644
--- a/src/shared/components/ResourceDetails/ResourceDetails.js
+++ b/src/shared/components/ResourceDetails/ResourceDetails.js
@@ -385,32 +385,23 @@ function Resource({
/>,
document.body,
)}
-
-
- {title ?? t('common.headers.resource-details')}
-
-
-
{!disableResourceDetailsCard && (
<>
-
- {title ?? t('common.headers.resource-details')}
-
+
+ {title ?? t('common.headers.resource-details')}
+
+
+
Date: Tue, 14 May 2024 14:08:12 +0200
Subject: [PATCH 34/38] fix: added correct token path also for other api calls
---
src/components/AIassistant/components/Chat/Chat.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/AIassistant/components/Chat/Chat.js b/src/components/AIassistant/components/Chat/Chat.js
index fe9de450ac..c64f64dbe5 100644
--- a/src/components/AIassistant/components/Chat/Chat.js
+++ b/src/components/AIassistant/components/Chat/Chat.js
@@ -11,6 +11,7 @@ import getChatResponse from 'components/AIassistant/api/getChatResponse';
import { sessionIDState } from 'components/AIassistant/state/sessionIDAtom';
import getFollowUpQuestions from 'components/AIassistant/api/getFollowUpQuestions';
import { clusterState } from 'state/clusterAtom';
+import { authDataState } from 'state/authDataAtom';
import './Chat.scss';
export default function Chat() {
@@ -22,6 +23,7 @@ export default function Chat() {
const initialPrompt = useRecoilValue(initialPromptState);
const sessionID = useRecoilValue(sessionIDState);
const cluster = useRecoilValue(clusterState);
+ const authData = useRecoilValue(authDataState);
const addMessage = (author, messageChunks, isLoading) => {
setChatHistory(prevItems =>
@@ -36,7 +38,7 @@ export default function Chat() {
sessionID,
handleFollowUpQuestions,
clusterUrl: cluster.currentContext.cluster.cluster.server,
- token: cluster.currentContext.user.user.token,
+ token: authData.token,
certificateAuthorityData:
cluster.currentContext.cluster.cluster['certificate-authority-data'],
});
@@ -74,7 +76,7 @@ export default function Chat() {
handleError,
sessionID,
clusterUrl: cluster.currentContext.cluster.cluster.server,
- token: cluster.currentContext.user.user.token,
+ token: authData.token,
certificateAuthorityData:
cluster.currentContext.cluster.cluster['certificate-authority-data'],
});
From 66c6f0477edbad715886530fa8a41d39e4f4a64d Mon Sep 17 00:00:00 2001
From: chriskari
Date: Thu, 16 May 2024 20:57:39 +0200
Subject: [PATCH 35/38] fix: adjusted response parsing to handle nested braces
---
.../AIassistant/api/getChatResponse.js | 3 +-
.../AIassistant/components/IntroBox.js | 22 ----------
.../AIassistant/components/IntroBox.scss | 44 -------------------
.../AIassistant/utils/parseNestedBrackets.js | 24 ++++++++++
4 files changed, 26 insertions(+), 67 deletions(-)
delete mode 100644 src/components/AIassistant/components/IntroBox.js
delete mode 100644 src/components/AIassistant/components/IntroBox.scss
create mode 100644 src/components/AIassistant/utils/parseNestedBrackets.js
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index 9d54c31c9e..b265431519 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,4 +1,5 @@
import { getClusterConfig } from 'state/utils/getBackendInfo';
+import { parseNestedBrackets } from '../utils/parseNestedBrackets';
export default async function getChatResponse({
prompt,
@@ -55,7 +56,7 @@ function readChunk(
}
// Also handles the rare case of two chunks being sent at once
const receivedString = decoder.decode(value, { stream: true });
- const chunks = receivedString.match(/{[^{}]*}/g).map(chunk => {
+ const chunks = parseNestedBrackets(receivedString).map(chunk => {
return JSON.parse(chunk);
});
chunks.forEach(chunk => {
diff --git a/src/components/AIassistant/components/IntroBox.js b/src/components/AIassistant/components/IntroBox.js
deleted file mode 100644
index 110a8b0a0d..0000000000
--- a/src/components/AIassistant/components/IntroBox.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { FlexBox, Icon, Text } from '@ui5/webcomponents-react';
-import './IntroBox.scss';
-
-export default function IntroBox() {
- const { t } = useTranslation();
- return (
-
-
-
-
-
-
- {t('ai-assistant.introduction1')}
-
-
- {t('ai-assistant.introduction2')}
-
-
-
- );
-}
diff --git a/src/components/AIassistant/components/IntroBox.scss b/src/components/AIassistant/components/IntroBox.scss
deleted file mode 100644
index bc2325fecd..0000000000
--- a/src/components/AIassistant/components/IntroBox.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.intro-box {
- height: 300px;
- width: 100%;
- background: linear-gradient(
- to bottom,
- var(--sapContent_Illustrative_Color1),
- #a100c2
- );
-
- .illustration {
- height: 235px;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .joule-icon {
- color: white;
- width: 75px;
- height: 75px;
- }
- }
-
- .introduction {
- height: 65px;
- padding: 1rem;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- gap: 0.5rem;
-
- .text {
- color: white;
- font-weight: lighter;
- }
-
- #text1 {
- font-size: 16px;
- }
-
- #text2 {
- font-size: 28px;
- }
- }
-}
diff --git a/src/components/AIassistant/utils/parseNestedBrackets.js b/src/components/AIassistant/utils/parseNestedBrackets.js
new file mode 100644
index 0000000000..cd52e0a616
--- /dev/null
+++ b/src/components/AIassistant/utils/parseNestedBrackets.js
@@ -0,0 +1,24 @@
+// input: "{sample}{string {with}}{multiple {nested{braces}}}"
+// output: ["{sample}", "{string {with}}", "{multiple {nested{braces}}}"]
+export function parseNestedBrackets(text) {
+ const output = [];
+ let openBraces = 0;
+ let startIndex = 0;
+
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i];
+ if (char === '{') {
+ if (openBraces === 0) {
+ startIndex = i;
+ }
+ openBraces++;
+ }
+ if (char === '}') {
+ openBraces--;
+ if (openBraces === 0) {
+ output.push(text.substring(startIndex, i + 1));
+ }
+ }
+ }
+ return output;
+}
From 015882196aad6245184b101a34ccd6c08d296603 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Thu, 16 May 2024 21:00:21 +0200
Subject: [PATCH 36/38] fix: improved method name
---
src/components/AIassistant/api/getChatResponse.js | 4 ++--
src/components/AIassistant/utils/parseNestedBrackets.js | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/AIassistant/api/getChatResponse.js b/src/components/AIassistant/api/getChatResponse.js
index b265431519..22bc2b4080 100644
--- a/src/components/AIassistant/api/getChatResponse.js
+++ b/src/components/AIassistant/api/getChatResponse.js
@@ -1,5 +1,5 @@
import { getClusterConfig } from 'state/utils/getBackendInfo';
-import { parseNestedBrackets } from '../utils/parseNestedBrackets';
+import { parseWithNestedBrackets } from '../utils/parseNestedBrackets';
export default async function getChatResponse({
prompt,
@@ -56,7 +56,7 @@ function readChunk(
}
// Also handles the rare case of two chunks being sent at once
const receivedString = decoder.decode(value, { stream: true });
- const chunks = parseNestedBrackets(receivedString).map(chunk => {
+ const chunks = parseWithNestedBrackets(receivedString).map(chunk => {
return JSON.parse(chunk);
});
chunks.forEach(chunk => {
diff --git a/src/components/AIassistant/utils/parseNestedBrackets.js b/src/components/AIassistant/utils/parseNestedBrackets.js
index cd52e0a616..c78d68cded 100644
--- a/src/components/AIassistant/utils/parseNestedBrackets.js
+++ b/src/components/AIassistant/utils/parseNestedBrackets.js
@@ -1,6 +1,6 @@
// input: "{sample}{string {with}}{multiple {nested{braces}}}"
// output: ["{sample}", "{string {with}}", "{multiple {nested{braces}}}"]
-export function parseNestedBrackets(text) {
+export function parseWithNestedBrackets(text) {
const output = [];
let openBraces = 0;
let startIndex = 0;
From 51ed6ad374186dd6d3f030955b8fc047e147a522 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Fri, 17 May 2024 01:02:08 +0200
Subject: [PATCH 37/38] fix: small styling issue
---
src/components/AIassistant/components/AIassistant.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/AIassistant/components/AIassistant.scss b/src/components/AIassistant/components/AIassistant.scss
index 99d0f4f2bd..a52347dc11 100644
--- a/src/components/AIassistant/components/AIassistant.scss
+++ b/src/components/AIassistant/components/AIassistant.scss
@@ -1,5 +1,5 @@
#assistant_wrapper {
- width: 100%;
+ width: calc(100% - 1rem);
.ai_assistant {
height: 100%;
From 087430efde29f11acf39724176e100f539f43701 Mon Sep 17 00:00:00 2001
From: chriskari
Date: Fri, 17 May 2024 01:19:40 +0200
Subject: [PATCH 38/38] fix: css container query
---
src/components/AIassistant/components/AIassistant.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/AIassistant/components/AIassistant.scss b/src/components/AIassistant/components/AIassistant.scss
index a52347dc11..d23dd95abc 100644
--- a/src/components/AIassistant/components/AIassistant.scss
+++ b/src/components/AIassistant/components/AIassistant.scss
@@ -23,6 +23,7 @@
.tab-container {
height: calc(100vh - 60px - 1.4rem);
+ container-type: inline-size;
}
}
}