Skip to content

Commit

Permalink
feat(chat): integrate with DIAL stateful API (Issue #165, #265) (#611)
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyaBondar authored Feb 12, 2024
1 parent 0122f90 commit 0c313f9
Show file tree
Hide file tree
Showing 105 changed files with 4,540 additions and 2,229 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^__" }
],
"@typescript-eslint/no-explicit-any": "warn"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ This project leverages environment variables for configuration.
| `REQUEST_API_KEY_CODE` | No | Request API Key Code used when sending request api key info to Azure Functions API Host | Any string | |
| `CODE_GENERATION_WARNING` | No | Warning text regarding code generation | Any string | |
| `SHOW_TOKEN_SUB` | No | Show token sub in refresh login error logs | `true`, `false` | false |
| `STORAGE_TYPE` | No | Type of storage used for getting and saving information generated by user. Now supported only `browserStorage` | `browserStorage`, `api`,`apiMock` | `browserStorage` |
| `STORAGE_TYPE` | No | Type of storage used for getting and saving information generated by user. Now supported only `api` | `browserStorage`, `api` | `api` |
| `KEEP_ALIVE_TIMEOUT` | No | Determines the maximum time in milliseconds in seconds that a connection may be idle before it is closed by the server. This is needed because infrastructure usually have default keep alive timeout 60 seconds and next server should have bigger value. Used only when running dockerfile. | Any number string | 61000 |
| `TRACES_URL` | No | Traces URL | Any string | |

Expand Down
9 changes: 7 additions & 2 deletions apps/chat/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,17 @@
"lint:fix": {},
"test": {
"options": {
"reportsDirectory": "../../coverage/libs/shared"
"reportsDirectory": "../../coverage/apps/chat"
}
},
"test:watch": {
"options": {
"reportsDirectory": "../../coverage/apps/chat"
}
},
"test:coverage": {
"options": {
"reportsDirectory": "../../coverage/libs/shared"
"reportsDirectory": "../../coverage/apps/chat"
}
},
"format": {},
Expand Down
2 changes: 2 additions & 0 deletions apps/chat/src/components/Chat/ChangePathDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
validateFolderRenaming,
} from '@/src/utils/app/folders';

import { FeatureType } from '@/src/types/common';
import { SharingType } from '@/src/types/share';
import { Translation } from '@/src/types/translation';

Expand Down Expand Up @@ -194,6 +195,7 @@ export const ChangePathDialog = ({
onDeleteFolder: handleDeleteFolder,
onAddFolder: handleAddFolder,
newAddedFolderId: newFolderId,
featureType: FeatureType.File,
}}
handleToggleFolder={handleToggleFolder}
isAllEntitiesOpened={isAllFoldersOpened}
Expand Down
57 changes: 45 additions & 12 deletions apps/chat/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { useTranslation } from 'next-i18next';

import { clearStateForMessages } from '@/src/utils/app/clear-messages-state';
import { throttle } from '@/src/utils/data/throttle';

Expand All @@ -12,7 +14,8 @@ import {
Replay,
Role,
} from '@/src/types/chat';
import { EntityType } from '@/src/types/common';
import { EntityType, UploadStatus } from '@/src/types/common';
import { Translation } from '@/src/types/translation';

import {
AddonsActions,
Expand All @@ -33,6 +36,8 @@ import { UISelectors } from '@/src/store/ui/ui.reducers';

import { DEFAULT_ASSISTANT_SUBMODEL } from '@/src/constants/default-settings';

import Loader from '../Common/Loader';
import { NotFoundEntity } from '../Common/NotFoundEntity';
import { ChatCompareRotate } from './ChatCompareRotate';
import { ChatCompareSelect } from './ChatCompareSelect';
import ChatExternalControls from './ChatExternalControls';
Expand All @@ -51,7 +56,7 @@ import { Feature } from '@epam/ai-dial-shared';

const scrollThrottlingTimeout = 250;

export const Chat = memo(() => {
export const ChatView = memo(() => {
const dispatch = useAppDispatch();
const appName = useAppSelector(SettingsSelectors.selectAppName);
const models = useAppSelector(ModelsSelectors.selectModels);
Expand Down Expand Up @@ -492,9 +497,6 @@ export const Chat = memo(() => {
values: { messages: clearStateForMessages(conversation.messages) },
}),
);
if (temporarySettings.modelId) {
handleSelectModel(conversation, temporarySettings.modelId);
}
handleChangePrompt(conversation, temporarySettings.prompt);
handleChangeTemperature(conversation, temporarySettings.temperature);
if (temporarySettings.currentAssistentModelId) {
Expand All @@ -506,6 +508,9 @@ export const Chat = memo(() => {
if (temporarySettings.addonsIds) {
handleOnApplyAddons(conversation, temporarySettings.addonsIds);
}
if (temporarySettings.modelId) {
handleSelectModel(conversation, temporarySettings.modelId);
}
}
});
}, [
Expand Down Expand Up @@ -808,12 +813,7 @@ export const Chat = memo(() => {
selectedConversations={selectedConversations}
onConversationSelect={(conversation) => {
dispatch(
ConversationsActions.selectConversations({
conversationIds: [
selectedConversations[0].id,
conversation.id,
],
}),
ConversationsActions.selectForCompare(conversation),
);
}}
/>
Expand Down Expand Up @@ -871,4 +871,37 @@ export const Chat = memo(() => {
</div>
);
});
Chat.displayName = 'Chat';
ChatView.displayName = 'ChatView';

export function Chat() {
const { t } = useTranslation(Translation.Chat);

const areSelectedConversationsLoaded = useAppSelector(
ConversationsSelectors.selectAreSelectedConversationsLoaded,
);
const selectedConversationsIds = useAppSelector(
ConversationsSelectors.selectSelectedConversationsIds,
);
const selectedConversations = useAppSelector(
ConversationsSelectors.selectSelectedConversations,
);
if (
!areSelectedConversationsLoaded &&
(!selectedConversations.length ||
selectedConversations.some((conv) => conv.status !== UploadStatus.LOADED))
) {
return <Loader />;
}
if (
selectedConversations.length !== selectedConversationsIds.length ||
selectedConversations.some((conv) => conv.status !== UploadStatus.LOADED)
) {
return (
<NotFoundEntity
entity={t('Conversation')}
additionalText="Please select another conversation."
/>
);
}
return <ChatView />;
}
99 changes: 62 additions & 37 deletions apps/chat/src/components/Chat/ChatCompareSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { useEffect, useMemo, useState } from 'react';
import { IconCheck } from '@tabler/icons-react';
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';

import { useTranslation } from 'next-i18next';

import { isValidConversationForCompare } from '@/src/utils/app/conversation';
import { compareEntitiesByName } from '@/src/utils/app/folders';
import { isMobile } from '@/src/utils/app/mobile';

import { Conversation, Role } from '@/src/types/chat';
import { Conversation, ConversationInfo } from '@/src/types/chat';
import { FeatureType } from '@/src/types/common';
import { Translation } from '@/src/types/translation';

import { ConversationsSelectors } from '@/src/store/conversations/conversations.reducers';
import { useAppSelector } from '@/src/store/hooks';
import { ModelsSelectors } from '@/src/store/models/models.reducers';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';

import { ModelIcon } from '../Chatbar/ModelIcon';
import { Combobox } from '../Common/Combobox';
import Loader from '../Common/Loader';
import ShareIcon from '../Common/ShareIcon';

interface OptionProps {
item: Conversation;
item: ConversationInfo;
}

const Option = ({ item }: OptionProps) => {
Expand Down Expand Up @@ -46,9 +51,9 @@ const Option = ({ item }: OptionProps) => {
};

interface Props {
conversations: Conversation[];
conversations: ConversationInfo[];
selectedConversations: Conversation[];
onConversationSelect: (conversation: Conversation) => void;
onConversationSelect: (conversation: ConversationInfo) => void;
}

export const ChatCompareSelect = ({
Expand All @@ -57,64 +62,78 @@ export const ChatCompareSelect = ({
onConversationSelect,
}: Props) => {
const { t } = useTranslation(Translation.Chat);
const [showAll, setShowAll] = useState(false);

const handleChangeShowAll = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setShowAll(e.target.checked);
},
[],
);

const isLoading = !!useAppSelector(
ConversationsSelectors.selectIsCompareLoading,
);

const [comparableConversations, setComparableConversations] = useState<
Conversation[]
ConversationInfo[]
>([]);

useEffect(() => {
if (selectedConversations.length === 1) {
const selectedConversation = selectedConversations[0];

const comparableConversations = conversations
.filter((conv) => !conv.replay.isReplay)
.filter((conv) => {
if (conv.id === selectedConversation.id) {
return false;
}
const convUserMessages = conv.messages.filter(
(message) => message.role === Role.User,
);
const selectedConvUserMessages = selectedConversation.messages.filter(
(message) => message.role === Role.User,
);

if (convUserMessages.length !== selectedConvUserMessages.length) {
return false;
}

return selectedConvUserMessages.every(
(message, index) =>
message.content === convUserMessages[index].content,
);
});
setComparableConversations(comparableConversations);
const comparableConversations = conversations.filter((conv) =>
showAll
? conv.id !== selectedConversation.id
: isValidConversationForCompare(selectedConversation, conv),
);
setComparableConversations(
comparableConversations.sort(compareEntitiesByName),
);
}
}, [conversations, selectedConversations]);
}, [conversations, selectedConversations, showAll]);

return (
<div
className="flex grow flex-col items-center justify-center p-5 py-2"
className="relative flex grow flex-col items-center justify-center p-5 py-2"
data-qa="conversation-to-compare"
>
<div className="flex max-w-[465px] flex-col gap-3 rounded bg-layer-2 p-6">
<div className="flex max-w-[465px] flex-col gap-3 rounded bg-layer-2 p-6 ">
<div className="flex flex-col gap-2 text-center">
<h5 className="text-base font-semibold">
{t('Select conversation to compare with')}
</h5>
<span className="text-secondary">
(
{t(
'Note: only conversations with same user messages can be compared',
'Only conversations containing the same number of messages can be compared.',
)}
)
</span>
</div>
<div className="relative flex items-center">
<input
name="showAllCheckbox"
checked={showAll}
onChange={handleChangeShowAll}
title=""
type="checkbox"
className="checkbox peer"
/>
<IconCheck
size={18}
className="pointer-events-none invisible absolute text-accent-primary peer-checked:visible"
/>
<label className="" htmlFor="showAllCheckbox">
{t('Show all conversations')}
</label>
</div>
{comparableConversations && (
<Combobox
items={comparableConversations}
getItemLabel={(conversation: Conversation) => conversation.name}
getItemValue={(conversation: Conversation) => conversation.id}
getItemLabel={(conversation: ConversationInfo) => conversation.name}
getItemValue={(conversation: ConversationInfo) => conversation.id}
itemRow={Option}
placeholder={
(comparableConversations?.length > 0
Expand All @@ -124,16 +143,22 @@ export const ChatCompareSelect = ({
disabled={!comparableConversations?.length || isMobile()}
notFoundPlaceholder={t('No conversations available') || ''}
onSelectItem={(itemID: string) => {
const selectedConversation = comparableConversations.filter(
const selectedConversation = comparableConversations.find(
(conv) => conv.id === itemID,
)[0];
);
if (selectedConversation) {
onConversationSelect(selectedConversation);
}
}}
/>
)}
</div>
{isLoading && (
<Loader
dataQa="compare-loader"
containerClassName="absolute bg-blackout h-full"
/>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {

import classNames from 'classnames';

import { UploadStatus } from '@/src/types/common';
import { DialFile } from '@/src/types/files';

interface Props {
Expand All @@ -26,7 +27,7 @@ export const ChatInputAttachment = ({
key={file.id}
className="flex gap-3 rounded border border-primary bg-layer-1 p-3"
>
{file.status !== 'FAILED' ? (
{file.status !== UploadStatus.FAILED ? (
<IconFile className="shrink-0 text-secondary" size={18} />
) : (
<IconExclamationCircle className="shrink-0 text-error" size={18} />
Expand All @@ -37,12 +38,12 @@ export const ChatInputAttachment = ({
<span
className={classNames(
'block max-w-full truncate',
file.status === 'FAILED' && 'text-error',
file.status === UploadStatus.FAILED && 'text-error',
)}
>
{file.name}
</span>
{file.status === 'UPLOADING' && (
{file.status === UploadStatus.LOADING && (
<div className="h-[3px] w-full overflow-hidden rounded-full bg-layer-3">
<div
className="h-full bg-controls-accent"
Expand All @@ -52,7 +53,7 @@ export const ChatInputAttachment = ({
)}
</div>
<div className="flex gap-3">
{onRetryFile && file.status === 'FAILED' && (
{onRetryFile && file.status === UploadStatus.FAILED && (
<button onClick={() => onRetryFile(file.id)}>
<IconReload
className="shrink-0 text-secondary hover:text-accent-primary"
Expand Down
Loading

0 comments on commit 0c313f9

Please sign in to comment.