diff --git a/apps/chat-e2e/src/assertions/api/apiAssertion.ts b/apps/chat-e2e/src/assertions/api/apiAssertion.ts
index 08d3a9d169..86def932fb 100644
--- a/apps/chat-e2e/src/assertions/api/apiAssertion.ts
+++ b/apps/chat-e2e/src/assertions/api/apiAssertion.ts
@@ -70,10 +70,19 @@ export class ApiAssertion {
.toBe(expectedTemperature);
}
- public async assertRequestPrompt(request: ChatBody, expectedPrompt: string) {
- expect
- .soft(request.prompt, ExpectedMessages.chatRequestPromptIsValid)
- .toBe(expectedPrompt);
+ public async assertRequestPrompt(
+ request: ChatBody,
+ expectedPrompt: string | undefined,
+ ) {
+ if (request.prompt === undefined) {
+ expect
+ .soft(request.prompt, ExpectedMessages.chatRequestPromptIsValid)
+ .toBeUndefined();
+ } else {
+ expect
+ .soft(request.prompt, ExpectedMessages.chatRequestPromptIsValid)
+ .toBe(expectedPrompt);
+ }
}
public async assertRequestAddons(
diff --git a/apps/chat-e2e/src/assertions/baseAssertion.ts b/apps/chat-e2e/src/assertions/baseAssertion.ts
index 3cc909acc4..64f56111ab 100644
--- a/apps/chat-e2e/src/assertions/baseAssertion.ts
+++ b/apps/chat-e2e/src/assertions/baseAssertion.ts
@@ -139,10 +139,42 @@ export class BaseAssertion {
.toHaveText(expectedText);
}
+ public async assertElementAttribute(
+ element: BaseElement | Locator,
+ attribute: string,
+ expectedValue: string,
+ expectedMessage?: string,
+ ) {
+ const elementLocator =
+ element instanceof BaseElement
+ ? element.getElementLocator()
+ : (element as Locator);
+ await expect
+ .soft(
+ elementLocator,
+ expectedMessage ?? ExpectedMessages.elementAttributeValueIsValid,
+ )
+ .toHaveAttribute(attribute, expectedValue);
+ }
+
public async assertElementColor(element: BaseElement, expectedColor: string) {
const style = await element.getComputedStyleProperty(Styles.color);
expect
.soft(style[0], ExpectedMessages.elementColorIsValid)
.toBe(expectedColor);
}
+
+ public async assertElementsCount(
+ element: BaseElement | Locator,
+ expectedCount: number,
+ ) {
+ const elementsCount =
+ element instanceof BaseElement
+ ? await element.getElementsCount()
+ : await element.count();
+
+ expect
+ .soft(elementsCount, ExpectedMessages.elementsCountIsValid)
+ .toBe(expectedCount);
+ }
}
diff --git a/apps/chat-e2e/src/assertions/chatMessagesAssertion.ts b/apps/chat-e2e/src/assertions/chatMessagesAssertion.ts
index c4f89eb71d..01675738d1 100644
--- a/apps/chat-e2e/src/assertions/chatMessagesAssertion.ts
+++ b/apps/chat-e2e/src/assertions/chatMessagesAssertion.ts
@@ -105,16 +105,14 @@ export class ChatMessagesAssertion extends BaseAssertion {
message: string | number,
expectedState: ElementState,
) {
- const chatMessage = this.chatMessages.getChatMessage(message);
- await chatMessage.scrollIntoViewIfNeeded();
- await chatMessage.hover();
- const editIcon = this.chatMessages.setMessageTemplateIcon(chatMessage);
+ const chatMessage = await this.chatMessages.hoverOverMessage(message);
+ const templateIcon = this.chatMessages.setMessageTemplateIcon(chatMessage);
expectedState === 'visible'
? await expect
- .soft(editIcon, ExpectedMessages.buttonIsVisible)
+ .soft(templateIcon, ExpectedMessages.buttonIsVisible)
.toBeVisible()
: await expect
- .soft(editIcon, ExpectedMessages.buttonIsNotVisible)
+ .soft(templateIcon, ExpectedMessages.buttonIsNotVisible)
.toBeHidden();
}
diff --git a/apps/chat-e2e/src/assertions/folderAssertion.ts b/apps/chat-e2e/src/assertions/folderAssertion.ts
index ceeabc5362..cc1754e47e 100644
--- a/apps/chat-e2e/src/assertions/folderAssertion.ts
+++ b/apps/chat-e2e/src/assertions/folderAssertion.ts
@@ -290,6 +290,20 @@ export class FolderAssertion extends BaseAssertion {
.toBe(expectedColor);
}
+ public async assertFolderEntityColor(
+ folder: TreeEntity,
+ folderEntity: TreeEntity,
+ expectedColor: string,
+ ) {
+ const folderEntityElement = this.folder.getFolderEntityNameElement(
+ folder.name,
+ folderEntity.name,
+ folder.index,
+ folderEntity.index,
+ );
+ await this.assertElementColor(folderEntityElement, expectedColor);
+ }
+
public async assertFolderEditInputState(expectedState: ElementState) {
const editInputLocator = this.folder
.getEditFolderInput()
@@ -352,6 +366,20 @@ export class FolderAssertion extends BaseAssertion {
await this.assertElementState(entityArrowIcon, expectedState);
}
+ public async assertFolderEntityIcon(
+ folder: TreeEntity,
+ folderEntity: TreeEntity,
+ expectedIcon: string,
+ ) {
+ const folderEntityIcon = this.folder.getFolderEntityIcon(
+ folder.name,
+ folderEntity.name,
+ folder.index,
+ folderEntity.index,
+ );
+ await this.assertEntityIcon(folderEntityIcon, expectedIcon);
+ }
+
public async assertFoldersCount(expectedCount: number) {
const actualFoldersCount = await this.folder.getFoldersCount();
expect
diff --git a/apps/chat-e2e/src/assertions/index.ts b/apps/chat-e2e/src/assertions/index.ts
index a4c2e4c361..65ff770f43 100644
--- a/apps/chat-e2e/src/assertions/index.ts
+++ b/apps/chat-e2e/src/assertions/index.ts
@@ -39,3 +39,4 @@ export * from './addonsDialogAssertion';
export * from './marketplaceAgentsAssertion';
export * from './conversationToCompareAssertion';
export * from './conversationToPublishAssertion';
+export * from './publishFolderAssertion';
diff --git a/apps/chat-e2e/src/assertions/manageAttachmentsAssertion.ts b/apps/chat-e2e/src/assertions/manageAttachmentsAssertion.ts
index dfb55b06b2..aaf837d334 100644
--- a/apps/chat-e2e/src/assertions/manageAttachmentsAssertion.ts
+++ b/apps/chat-e2e/src/assertions/manageAttachmentsAssertion.ts
@@ -79,10 +79,15 @@ export class ManageAttachmentsAssertion {
state: ElementCaretState,
) {
const sectionElement = this.attachFilesModal.getSectionElement(section);
- const isExpanded =
- await this.attachFilesModal.isSectionExpanded(sectionElement);
+ const filesSection = this.attachFilesModal.getFilesSection(sectionElement);
state === 'expanded'
- ? expect(isExpanded, `Section "${section}" is ${state}`).toBeTruthy()
- : expect(isExpanded, `Section "${section}" is ${state}`).toBeFalsy();
+ ? await expect(
+ filesSection,
+ `Section "${section}" is ${state}`,
+ ).toBeVisible()
+ : await expect(
+ filesSection,
+ `Section "${section}" is ${state}`,
+ ).toBeHidden();
}
}
diff --git a/apps/chat-e2e/src/assertions/messageTemplateModalAssertion.ts b/apps/chat-e2e/src/assertions/messageTemplateModalAssertion.ts
new file mode 100644
index 0000000000..c7fa8e17ac
--- /dev/null
+++ b/apps/chat-e2e/src/assertions/messageTemplateModalAssertion.ts
@@ -0,0 +1,11 @@
+import { BaseAssertion } from '@/src/assertions/baseAssertion';
+import { MessageTemplateModal } from '@/src/ui/webElements';
+
+export class MessageTemplateModalAssertion extends BaseAssertion {
+ readonly messageTemplateModal: MessageTemplateModal;
+
+ constructor(messageTemplateModal: MessageTemplateModal) {
+ super();
+ this.messageTemplateModal = messageTemplateModal;
+ }
+}
diff --git a/apps/chat-e2e/src/assertions/publishFolderAssertion.ts b/apps/chat-e2e/src/assertions/publishFolderAssertion.ts
new file mode 100644
index 0000000000..dc9ea1181c
--- /dev/null
+++ b/apps/chat-e2e/src/assertions/publishFolderAssertion.ts
@@ -0,0 +1,47 @@
+import { FolderAssertion } from '@/src/assertions/folderAssertion';
+import { PublishingExpectedMessages, TreeEntity } from '@/src/testData';
+import { PublishFolder } from '@/src/ui/webElements/entityTree';
+
+export class PublishFolderAssertion<
+ T extends PublishFolder,
+> extends FolderAssertion {
+ readonly publishFolder: T;
+
+ constructor(publishFolder: T) {
+ super(publishFolder);
+ this.publishFolder = publishFolder;
+ }
+
+ public async assertFolderEntityVersion(
+ folder: TreeEntity,
+ folderEntity: TreeEntity,
+ expectedVersion: string,
+ ) {
+ await this.assertElementText(
+ this.publishFolder.getFolderEntityVersion(
+ folder.name,
+ folderEntity.name,
+ folder.index,
+ folderEntity.index,
+ ),
+ expectedVersion,
+ PublishingExpectedMessages.entityVersionIsValid,
+ );
+ }
+
+ public async assertFolderEntityVersionColor(
+ folder: TreeEntity,
+ folderEntity: TreeEntity,
+ expectedColor: string,
+ ) {
+ await this.assertElementColor(
+ this.publishFolder.getFolderEntityVersionElement(
+ folder.name,
+ folderEntity.name,
+ folder.index,
+ folderEntity.index,
+ ),
+ expectedColor,
+ );
+ }
+}
diff --git a/apps/chat-e2e/src/assertions/renameConversationModalAssertion.ts b/apps/chat-e2e/src/assertions/renameConversationModalAssertion.ts
new file mode 100644
index 0000000000..777296e8ab
--- /dev/null
+++ b/apps/chat-e2e/src/assertions/renameConversationModalAssertion.ts
@@ -0,0 +1,56 @@
+import { BaseAssertion } from '@/src/assertions/baseAssertion';
+import { RenameConversationModal } from '@/src/ui/webElements/renameConversationModal';
+import { expect } from '@playwright/test';
+
+export class RenameConversationModalAssertion extends BaseAssertion {
+ readonly renameModal: RenameConversationModal;
+
+ constructor(renameModal: RenameConversationModal) {
+ super();
+ this.renameModal = renameModal;
+ }
+
+ async assertModalIsVisible() {
+ await this.assertElementState(
+ this.renameModal.getElementLocator(),
+ 'visible',
+ 'Rename Conversation Modal should be visible',
+ );
+ }
+
+ async assertModalTitle(expectedTitle: string) {
+ await expect
+ .soft(
+ this.renameModal.title.getElementLocator(),
+ 'Rename Conversation Modal title should match',
+ )
+ .toHaveText(expectedTitle);
+ }
+
+ async assertInputValue(expectedValue: string) {
+ await expect
+ .soft(
+ this.renameModal.nameInput.getElementLocator(),
+ 'Rename Conversation Modal input value should match',
+ )
+ .toHaveText(expectedValue);
+ }
+
+ async assertSaveButtonIsEnabled() {
+ await expect
+ .soft(
+ this.renameModal.saveButton.getElementLocator(),
+ 'Save button should be enabled',
+ )
+ .toBeEnabled();
+ }
+
+ async assertSaveButtonIsDisabled() {
+ await expect
+ .soft(
+ this.renameModal.saveButton.getElementLocator(),
+ 'Save button should be disabled',
+ )
+ .toBeDisabled();
+ }
+}
diff --git a/apps/chat-e2e/src/core/dialAdminFixtures.ts b/apps/chat-e2e/src/core/dialAdminFixtures.ts
index adf2c3a17b..31770cc37e 100644
--- a/apps/chat-e2e/src/core/dialAdminFixtures.ts
+++ b/apps/chat-e2e/src/core/dialAdminFixtures.ts
@@ -15,6 +15,7 @@ import {
ChatHeaderAssertion,
ChatMessagesAssertion,
MenuAssertion,
+ PublishFolderAssertion,
TooltipAssertion,
} from '@/src/assertions';
import { ConversationToApproveAssertion } from '@/src/assertions/conversationToApproveAssertion';
@@ -28,11 +29,11 @@ import {
ApproveRequiredConversationsTree,
ConversationsToApproveTree,
ConversationsTree,
+ FolderConversationsToApprove,
FolderPrompts,
Folders,
OrganizationConversationsTree,
PromptsTree,
- PublishFolder,
} from '@/src/ui/webElements/entityTree';
import { Tooltip } from '@/src/ui/webElements/tooltip';
import { Page } from '@playwright/test';
@@ -56,6 +57,7 @@ const dialAdminTest = dialTest.extend<{
adminOrganizationFolderConversationAssertions: FolderAssertion;
adminPublishingApprovalModalAssertion: PublishingApprovalModalAssertion;
adminConversationToApproveAssertion: ConversationToApproveAssertion;
+ adminFolderToApproveAssertion: PublishFolderAssertion;
adminPublicationReviewControl: PublicationReviewControl;
adminChatHeader: ChatHeader;
adminChatMessages: ChatMessages;
@@ -63,7 +65,6 @@ const dialAdminTest = dialTest.extend<{
adminApproveRequiredConversationDropdownMenu: DropdownMenu;
adminTooltip: Tooltip;
adminOrganizationConversations: OrganizationConversationsTree;
- adminPublishingApprovalFolderConversationsAssertion: FolderAssertion;
adminChatHeaderAssertion: ChatHeaderAssertion;
adminChatMessagesAssertion: ChatMessagesAssertion;
adminOrganizationFolderDropdownMenuAssertion: MenuAssertion;
@@ -176,16 +177,6 @@ const dialAdminTest = dialTest.extend<{
adminChatBar.getOrganizationConversationsTree();
await use(adminOrganizationConversations);
},
- adminPublishingApprovalFolderConversationsAssertion: async (
- { adminPublishingApprovalModal },
- use,
- ) => {
- const adminPublishingApprovalFolderConversationsAssertion =
- new FolderAssertion(
- adminPublishingApprovalModal.getFolderConversationsToApprove(),
- );
- await use(adminPublishingApprovalFolderConversationsAssertion);
- },
adminChatHeaderAssertion: async ({ adminChatHeader }, use) => {
const adminChatHeaderAssertion = new ChatHeaderAssertion(adminChatHeader);
await use(adminChatHeaderAssertion);
@@ -231,6 +222,15 @@ const dialAdminTest = dialTest.extend<{
new ConversationToApproveAssertion(adminConversationsToApprove);
await use(adminConversationToApproveAssertion);
},
+ adminFolderToApproveAssertion: async (
+ { adminPublishingApprovalModal },
+ use,
+ ) => {
+ const adminFolderToApproveAssertion = new PublishFolderAssertion(
+ adminPublishingApprovalModal.getFolderConversationsToApprove(),
+ );
+ await use(adminFolderToApproveAssertion);
+ },
adminOrganizationFolderDropdownMenuAssertion: async (
{ adminOrganizationFolderDropdownMenu },
use,
diff --git a/apps/chat-e2e/src/core/dialFixtures.ts b/apps/chat-e2e/src/core/dialFixtures.ts
index b5daac0d17..405bed34ca 100644
--- a/apps/chat-e2e/src/core/dialFixtures.ts
+++ b/apps/chat-e2e/src/core/dialFixtures.ts
@@ -10,7 +10,9 @@ import {
ChatNotFound,
ConversationSettingsModal,
ConversationToCompare,
+ MessageTemplateModal,
PromptBar,
+ PublishingRules,
SelectFolderModal,
SendMessage,
} from '../ui/webElements';
@@ -38,6 +40,7 @@ import {
PromptAssertion,
PromptListAssertion,
PromptModalAssertion,
+ PublishFolderAssertion,
PublishingRequestModalAssertion,
SendMessageAssertion,
ShareApiAssertion,
@@ -50,6 +53,8 @@ import {
import { AddonsDialogAssertion } from '@/src/assertions/addonsDialogAssertion';
import { ConversationToPublishAssertion } from '@/src/assertions/conversationToPublishAssertion';
import { ManageAttachmentsAssertion } from '@/src/assertions/manageAttachmentsAssertion';
+import { MessageTemplateModalAssertion } from '@/src/assertions/messageTemplateModalAssertion';
+import { RenameConversationModalAssertion } from '@/src/assertions/renameConversationModalAssertion';
import { SelectFolderModalAssertion } from '@/src/assertions/selectFolderModalAssertion';
import { SettingsModalAssertion } from '@/src/assertions/settingsModalAssertion';
import { SideBarEntityAssertion } from '@/src/assertions/sideBarEntityAssertion';
@@ -106,6 +111,7 @@ import { ModelInfoTooltip } from '@/src/ui/webElements/modelInfoTooltip';
import { PlaybackControl } from '@/src/ui/webElements/playbackControl';
import { PromptModalDialog } from '@/src/ui/webElements/promptModalDialog';
import { PublishingRequestModal } from '@/src/ui/webElements/publishingRequestModal';
+import { RenameConversationModal } from '@/src/ui/webElements/renameConversationModal';
import { Search } from '@/src/ui/webElements/search';
import { SettingsModal } from '@/src/ui/webElements/settingsModal';
import { ShareModal } from '@/src/ui/webElements/shareModal';
@@ -114,6 +120,7 @@ import { TemperatureSlider } from '@/src/ui/webElements/temperatureSlider';
import { Tooltip } from '@/src/ui/webElements/tooltip';
import { UploadFromDeviceModal } from '@/src/ui/webElements/uploadFromDeviceModal';
import { VariableModalDialog } from '@/src/ui/webElements/variableModalDialog';
+import { BucketUtil } from '@/src/utils';
import { allure } from 'allure-playwright';
import path from 'path';
import { APIRequestContext } from 'playwright-core';
@@ -158,6 +165,7 @@ const dialTest = test.extend<
folderConversations: FolderConversations;
folderPrompts: FolderPrompts;
organizationConversations: OrganizationConversationsTree;
+ organizationFolderConversations: Folders;
conversationSettingsModal: ConversationSettingsModal;
talkToAgentDialog: TalkToAgentDialog;
talkToAgents: MarketplaceAgents;
@@ -173,6 +181,8 @@ const dialTest = test.extend<
promptDropdownMenu: DropdownMenu;
confirmationDialog: ConfirmationDialog;
promptModalDialog: PromptModalDialog;
+ renameConversationModal: RenameConversationModal;
+ renameConversationModalAssertion: RenameConversationModalAssertion;
variableModalDialog: VariableModalDialog;
chatHeader: ChatHeader;
modelInfoTooltip: ModelInfoTooltip;
@@ -215,6 +225,7 @@ const dialTest = test.extend<
selectFolderModal: SelectFolderModal;
selectFolders: Folders;
attachedAllFiles: Folders;
+ messageTemplateModal: MessageTemplateModal;
manageAttachmentsAssertion: ManageAttachmentsAssertion;
settingsModal: SettingsModal;
publishingRequestModal: PublishingRequestModal;
@@ -223,6 +234,7 @@ const dialTest = test.extend<
publicationApiHelper: PublicationApiHelper;
adminPublicationApiHelper: PublicationApiHelper;
publishRequestBuilder: PublishRequestBuilder;
+ publishingRules: PublishingRules;
conversationAssertion: ConversationAssertion;
chatBarFolderAssertion: FolderAssertion;
organizationConversationAssertion: SideBarEntityAssertion;
@@ -266,6 +278,9 @@ const dialTest = test.extend<
publishingRequestFolderConversationAssertion: FolderAssertion;
talkToAgentDialogAssertion: TalkToAgentDialogAssertion;
conversationToPublishAssertion: ConversationToPublishAssertion;
+ folderToPublishAssertion: PublishFolderAssertion;
+ organizationFolderConversationAssertions: FolderAssertion;
+ messageTemplateModalAssertion: MessageTemplateModalAssertion;
}
>({
// eslint-disable-next-line no-empty-pattern
@@ -440,6 +455,11 @@ const dialTest = test.extend<
const conversationSettingsModal = new ConversationSettingsModal(page);
await use(conversationSettingsModal);
},
+ organizationFolderConversations: async ({ chatBar }, use) => {
+ const organizationFolderConversations =
+ chatBar.getOrganizationFolderConversations();
+ await use(organizationFolderConversations);
+ },
talkToAgentDialog: async ({ page }, use) => {
const talkToAgentDialog = new TalkToAgentDialog(page);
await use(talkToAgentDialog);
@@ -488,6 +508,18 @@ const dialTest = test.extend<
const promptModalDialog = new PromptModalDialog(page);
await use(promptModalDialog);
},
+ renameConversationModal: async ({ page }, use) => {
+ const renameConversationModal = new RenameConversationModal(page);
+ await use(renameConversationModal);
+ },
+ renameConversationModalAssertion: async (
+ { renameConversationModal },
+ use,
+ ) => {
+ const renameConversationModalAssertion =
+ new RenameConversationModalAssertion(renameConversationModal);
+ await use(renameConversationModalAssertion);
+ },
variableModalDialog: async ({ page }, use) => {
const variableModalDialog = new VariableModalDialog(page);
await use(variableModalDialog);
@@ -564,6 +596,7 @@ const dialTest = test.extend<
) => {
const additionalSecondShareUserFileApiHelper = new FileApiHelper(
additionalSecondShareUserRequestContext,
+ BucketUtil.getAdditionalSecondShareUserBucket(),
);
await use(additionalSecondShareUserFileApiHelper);
},
@@ -683,6 +716,10 @@ const dialTest = test.extend<
const attachedAllFiles = attachFilesModal.getAllFolderFiles();
await use(attachedAllFiles);
},
+ messageTemplateModal: async ({ page }, use) => {
+ const messageTemplateModal = new MessageTemplateModal(page);
+ await use(messageTemplateModal);
+ },
settingsModal: async ({ page }, use) => {
const settingsModal = new SettingsModal(page);
await use(settingsModal);
@@ -716,6 +753,10 @@ const dialTest = test.extend<
const publishRequestBuilder = new PublishRequestBuilder();
await use(publishRequestBuilder);
},
+ publishingRules: async ({ publishingRequestModal }, use) => {
+ const publishingRules = publishingRequestModal.getPublishingRules();
+ await use(publishingRules);
+ },
conversationAssertion: async ({ conversations }, use) => {
const conversationAssertion = new ConversationAssertion(conversations);
await use(conversationAssertion);
@@ -930,6 +971,21 @@ const dialTest = test.extend<
);
await use(conversationToPublishAssertion);
},
+ folderToPublishAssertion: async ({ publishingRequestModal }, use) => {
+ const folderToPublishAssertion = new PublishFolderAssertion(
+ publishingRequestModal.getFolderConversationsToPublish(),
+ );
+ await use(folderToPublishAssertion);
+ },
+ organizationFolderConversationAssertions: async (
+ { organizationFolderConversations },
+ use,
+ ) => {
+ const organizationFolderConversationAssertions = new FolderAssertion(
+ organizationFolderConversations,
+ );
+ await use(organizationFolderConversationAssertions);
+ },
// eslint-disable-next-line no-empty-pattern
apiAssertion: async ({}, use) => {
const apiAssertion = new ApiAssertion();
@@ -940,6 +996,12 @@ const dialTest = test.extend<
const shareApiAssertion = new ShareApiAssertion();
await use(shareApiAssertion);
},
+ messageTemplateModalAssertion: async ({ messageTemplateModal }, use) => {
+ const messageTemplateModalAssertion = new MessageTemplateModalAssertion(
+ messageTemplateModal,
+ );
+ await use(messageTemplateModalAssertion);
+ },
});
export default dialTest;
diff --git a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
index c8ba4f6ac2..36c2eb7418 100644
--- a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
+++ b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
@@ -198,6 +198,7 @@ const dialSharedWithMeTest = dialTest.extend<{
) => {
const additionalShareUserFileApiHelper = new FileApiHelper(
additionalShareUserRequestContext,
+ BucketUtil.getAdditionalShareUserBucket(),
);
await use(additionalShareUserFileApiHelper);
},
diff --git a/apps/chat-e2e/src/testData/api/fileApiHelper.ts b/apps/chat-e2e/src/testData/api/fileApiHelper.ts
index 96893dfa84..ec649df0a1 100644
--- a/apps/chat-e2e/src/testData/api/fileApiHelper.ts
+++ b/apps/chat-e2e/src/testData/api/fileApiHelper.ts
@@ -6,8 +6,16 @@ import { BucketUtil, ItemUtil } from '@/src/utils';
import { expect } from '@playwright/test';
import * as fs from 'fs';
import path from 'path';
+import { APIRequestContext } from 'playwright-core';
export class FileApiHelper extends BaseApiHelper {
+ private readonly userBucket?: string;
+
+ constructor(request: APIRequestContext, userBucket?: string) {
+ super(request);
+ this.userBucket = userBucket;
+ }
+
public async putFile(filename: string, parentPath?: string) {
const encodedFilename = encodeURIComponent(filename);
const encodedParentPath = parentPath
@@ -15,7 +23,7 @@ export class FileApiHelper extends BaseApiHelper {
: undefined;
const filePath = path.join(Attachment.attachmentPath, filename);
const bufferedFile = fs.readFileSync(filePath);
- const baseUrl = `${API.fileHost}/${BucketUtil.getBucket()}`;
+ const baseUrl = `${API.fileHost}/${this.userBucket ?? BucketUtil.getBucket()}`;
const url = parentPath
? `${baseUrl}/${encodedParentPath}/${encodedFilename}`
: `${baseUrl}/${encodedFilename}`;
@@ -65,7 +73,7 @@ export class FileApiHelper extends BaseApiHelper {
public async listEntities(nodeType: BackendDataNodeType, url?: string) {
const host = url
? `${API.listingHost}/${url.substring(0, url.length - 1)}`
- : `${API.filesListingHost()}/${BucketUtil.getBucket()}`;
+ : `${API.filesListingHost()}/${this.userBucket ?? BucketUtil.getBucket()}`;
const response = await this.request.get(host, {
params: {
filter: nodeType,
diff --git a/apps/chat-e2e/src/testData/api/itemApiHelper.ts b/apps/chat-e2e/src/testData/api/itemApiHelper.ts
index a6f76a2de6..3d676bf8e9 100644
--- a/apps/chat-e2e/src/testData/api/itemApiHelper.ts
+++ b/apps/chat-e2e/src/testData/api/itemApiHelper.ts
@@ -10,10 +10,12 @@ import { APIRequestContext } from 'playwright-core';
export class ItemApiHelper extends BaseApiHelper {
private readonly userBucket?: string;
+
constructor(request: APIRequestContext, userBucket?: string) {
super(request);
this.userBucket = userBucket;
}
+
public async deleteAllData(bucket?: string, isOverlay = false) {
const bucketToUse = this.userBucket ?? bucket;
const conversations = await this.listItems(
diff --git a/apps/chat-e2e/src/testData/api/publicationApiHelper.ts b/apps/chat-e2e/src/testData/api/publicationApiHelper.ts
index 43bc472be7..0cff397e4a 100644
--- a/apps/chat-e2e/src/testData/api/publicationApiHelper.ts
+++ b/apps/chat-e2e/src/testData/api/publicationApiHelper.ts
@@ -81,7 +81,9 @@ export class PublicationApiHelper extends BaseApiHelper {
return JSON.parse(responseText) as Publication;
}
- public async createUnpublishRequest(publicationRequest: Publication) {
+ public async createUnpublishRequest(
+ publicationRequest: Publication | PublicationRequestModel,
+ ) {
const unpublishResources = [];
for (const resource of publicationRequest.resources) {
unpublishResources.push({
diff --git a/apps/chat-e2e/src/testData/conversationHistory/conversationData.ts b/apps/chat-e2e/src/testData/conversationHistory/conversationData.ts
index 08886b7d14..623e7069c8 100644
--- a/apps/chat-e2e/src/testData/conversationHistory/conversationData.ts
+++ b/apps/chat-e2e/src/testData/conversationHistory/conversationData.ts
@@ -8,7 +8,13 @@ import { FolderData } from '@/src/testData/folders/folderData';
import { ItemUtil } from '@/src/utils';
import { DateUtil } from '@/src/utils/dateUtil';
import { GeneratorUtil } from '@/src/utils/generatorUtil';
-import { Message, MessageSettings, Role, Stage } from '@epam/ai-dial-shared';
+import {
+ Message,
+ MessageSettings,
+ Role,
+ Stage,
+ TemplateMapping,
+} from '@epam/ai-dial-shared';
export interface FolderConversation {
conversations: Conversation[];
@@ -96,8 +102,8 @@ export class ConversationData extends FolderData {
}
public prepareModelConversationBasedOnRequests(
- model: DialAIEntityModel | string,
requests: string[],
+ model?: DialAIEntityModel | string,
name?: string,
) {
const basicConversation = this.prepareEmptyConversation(model, name);
@@ -136,8 +142,8 @@ export class ConversationData extends FolderData {
requests[i] = `${i} + ${i + 1} =`;
}
const basicConversation = this.prepareModelConversationBasedOnRequests(
- models[models.length - 1],
requests,
+ models[models.length - 1],
);
const messages = basicConversation.messages;
for (let i = 0; i < models.length; i++) {
@@ -158,12 +164,12 @@ export class ConversationData extends FolderData {
}
public prepareConversationBasedOnPrompt(
- prompt: Prompt,
+ prompt: Prompt | string,
params?: Map,
model?: DialAIEntityModel | string,
name?: string,
) {
- let promptContent = prompt.content!;
+ let promptContent = typeof prompt === 'string' ? prompt : prompt.content!;
const paramRegex = (param: string) =>
new RegExp('\\{\\{' + `(${param}.*?)` + '\\}\\}');
const defaultParamValueRegex = '(?<=\\|)(.*?)(?=\\}})';
@@ -178,19 +184,19 @@ export class ConversationData extends FolderData {
}
}
//set default prompt parameters if absent in params map
- const matchedDefaultValue = promptContent.match(defaultParamValueRegex);
- if (matchedDefaultValue) {
- promptContent = promptContent.replace(
- paramRegex(''),
- matchedDefaultValue[0],
- );
- }
+ const matchedDefaultValues = promptContent.matchAll(
+ new RegExp(defaultParamValueRegex, 'g'),
+ );
+ Array.from(matchedDefaultValues, (match) => {
+ promptContent = promptContent.replace(paramRegex(''), match[0]);
+ });
+
const conversation = this.prepareDefaultConversation(model, name);
const userMessages = conversation.messages.filter((m) => m.role === 'user');
userMessages.forEach((m) => {
(m.templateMapping! as Record)[promptContent] =
- prompt.content!;
+ typeof prompt === 'string' ? prompt : prompt.content!;
m.content = promptContent;
});
return conversation;
@@ -788,4 +794,18 @@ export class ConversationData extends FolderData {
replayConversation.replay.replayAsIs = true;
return replayConversation;
}
+
+ public prepareConversationBasedOnMessageTemplate(
+ request: string,
+ templateMap: Map,
+ ) {
+ const conversation = this.prepareModelConversationBasedOnRequests([
+ request,
+ ]);
+ const userMessage = conversation.messages.find((m) => m.role === 'user')!;
+ const templateMapping: TemplateMapping[] = [];
+ templateMap.forEach((k, v) => templateMapping.push([v, k]));
+ userMessage.templateMapping = templateMapping;
+ return conversation;
+ }
}
diff --git a/apps/chat-e2e/src/testData/expectedConstants.ts b/apps/chat-e2e/src/testData/expectedConstants.ts
index cff81a54e9..429cb45769 100644
--- a/apps/chat-e2e/src/testData/expectedConstants.ts
+++ b/apps/chat-e2e/src/testData/expectedConstants.ts
@@ -15,6 +15,7 @@ export const ExpectedConstants = {
`${ExpectedConstants.newFolderTitle} ${index}`,
newPromptFolderWithIndexTitle: (index: number) =>
`${ExpectedConstants.newFolderTitle} ${index}`,
+ renameConversationModalTitle: 'Rename conversation',
emptyString: '',
defaultTemperature: '1',
signInButtonTitle: 'Sign in with Credentials',
@@ -192,8 +193,23 @@ export const ExpectedConstants = {
continueReviewButtonTitle: 'Continue review',
goToReviewButtonTitle: 'Go to a review',
reviewResourcesTooltip: `It's required to review all resources`,
- duplicatedUnpublishingError: (name: string) =>
- `"${name}" have already been unpublished. You can't approve this request.`,
+ duplicatedUnpublishingError: (...names: string[]) => {
+ const namesString = names.map((name) => `"${name}"`).join(', ');
+ return `${namesString} have already been unpublished. You can't approve this request.`;
+ },
+ messageTemplateModalTitle: 'Message template',
+ messageTemplateModalDescription:
+ 'Copy a part of the message into the first input and provide a template with template variables into the second input',
+ messageTemplateModalOriginalMessageLabel: 'Original message:',
+ messageTemplateContentPlaceholder: 'A part of the message',
+ messageTemplateValuePlaceholder:
+ 'Your template. Use {{}} to denote a variable',
+ originalMessageTemplateErrorMessage:
+ 'This part was not found in the original message',
+ messageTemplateMissingVarErrorMessage:
+ 'Template must have at least one variable',
+ messageTemplateRequiredField: 'Please fill in this required field',
+ messageTemplateMismatchTextErrorMessage: `Template doesn't match the message text`,
};
export enum Types {
diff --git a/apps/chat-e2e/src/testData/publishing/publishRequestBuilder.ts b/apps/chat-e2e/src/testData/publishing/publishRequestBuilder.ts
index 0ad6c76dc6..16734e0cd0 100644
--- a/apps/chat-e2e/src/testData/publishing/publishRequestBuilder.ts
+++ b/apps/chat-e2e/src/testData/publishing/publishRequestBuilder.ts
@@ -6,6 +6,12 @@ import {
import { ExpectedConstants } from '@/src/testData';
import { Attachment, PublishActions } from '@epam/ai-dial-shared';
+export interface PublicationResource {
+ action: PublishActions;
+ sourceUrl?: string;
+ targetUrl: string;
+}
+
export class PublishRequestBuilder {
private publishRequest: PublicationRequestModel;
@@ -45,14 +51,21 @@ export class PublishRequestBuilder {
withConversationResource(
conversation: Conversation,
+ action: PublishActions,
version?: string,
): PublishRequestBuilder {
const targetResource = conversation.id.split('/').slice(2).join('/');
- const resource = {
- action: PublishActions.ADD,
- sourceUrl: conversation.id,
- targetUrl: `conversations/${this.getPublishRequest().targetFolder}${targetResource}__${version ?? ExpectedConstants.defaultAppVersion}`,
+ const targetUrl = `conversations/${this.getPublishRequest().targetFolder}${targetResource}__${version ?? ExpectedConstants.defaultAppVersion}`;
+ let resource: PublicationResource = {
+ action: action,
+ targetUrl: targetUrl,
};
+ if (action === 'ADD' || action === 'ADD_IF_ABSENT') {
+ resource = {
+ ...resource,
+ sourceUrl: conversation.id,
+ };
+ }
this.publishRequest.resources.push(resource);
return this;
}
diff --git a/apps/chat-e2e/src/tests/abortedReplay.test.ts b/apps/chat-e2e/src/tests/abortedReplay.test.ts
index 9c42cebf0f..3593b909be 100644
--- a/apps/chat-e2e/src/tests/abortedReplay.test.ts
+++ b/apps/chat-e2e/src/tests/abortedReplay.test.ts
@@ -75,14 +75,14 @@ dialTest(
async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- firstRandomModel,
[firstUserRequest],
+ firstRandomModel,
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- secondRandomModel,
[secondUserRequest],
+ secondRandomModel,
);
conversationData.resetData();
historyConversation = conversationData.prepareHistoryConversation(
@@ -392,10 +392,7 @@ dialTest(
requests.push(GeneratorUtil.randomString(200));
}
const conversation =
- conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- requests,
- );
+ conversationData.prepareModelConversationBasedOnRequests(requests);
replayConversation =
conversationData.prepareDefaultReplayConversation(conversation);
await dataInjector.createConversations([
diff --git a/apps/chat-e2e/src/tests/accountSettings.test.ts b/apps/chat-e2e/src/tests/accountSettings.test.ts
index d15c2f157e..a05b4d4708 100644
--- a/apps/chat-e2e/src/tests/accountSettings.test.ts
+++ b/apps/chat-e2e/src/tests/accountSettings.test.ts
@@ -112,8 +112,8 @@ dialTest(
const request = GeneratorUtil.randomString(170);
const name = GeneratorUtil.randomString(170);
conversation = conversationData.prepareModelConversationBasedOnRequests(
- ModelsUtil.getDefaultModel()!,
[request],
+ ModelsUtil.getDefaultModel()!,
name,
);
await dataInjector.createConversations([conversation]);
diff --git a/apps/chat-e2e/src/tests/chatApi/entitySimpleRequest.test.ts b/apps/chat-e2e/src/tests/chatApi/entitySimpleRequest.test.ts
index d84129f87c..32e48445ba 100644
--- a/apps/chat-e2e/src/tests/chatApi/entitySimpleRequest.test.ts
+++ b/apps/chat-e2e/src/tests/chatApi/entitySimpleRequest.test.ts
@@ -16,8 +16,8 @@ for (const entity of entitySimpleRequests) {
dialTest.skip(process.env.E2E_HOST === undefined, skipReason);
const conversation =
conversationData.prepareModelConversationBasedOnRequests(
- entity.entityId,
[entity.request],
+ entity.entityId,
);
if (entity.systemPrompt) {
conversation.prompt = entity.systemPrompt;
@@ -48,8 +48,8 @@ dialTest(
);
const conversation =
conversationData.prepareModelConversationBasedOnRequests(
- replayEntity.entityId,
[replayEntity.request],
+ replayEntity.entityId,
);
conversationData.resetData();
const replayConversation =
diff --git a/apps/chat-e2e/src/tests/chatBarConversation.test.ts b/apps/chat-e2e/src/tests/chatBarConversation.test.ts
index 3992d2c7fc..d7c32abe39 100644
--- a/apps/chat-e2e/src/tests/chatBarConversation.test.ts
+++ b/apps/chat-e2e/src/tests/chatBarConversation.test.ts
@@ -13,7 +13,6 @@ import {
} from '@/src/testData';
import { Colors, Overflow, Styles } from '@/src/ui/domData';
import { ChatBarSelectors } from '@/src/ui/selectors';
-import { EditInput } from '@/src/ui/webElements';
import { GeneratorUtil } from '@/src/utils';
import { ModelsUtil } from '@/src/utils/modelsUtil';
import { expect } from '@playwright/test';
@@ -102,6 +101,8 @@ dialTest(
conversationData,
dataInjector,
setTestIds,
+ renameConversationModal,
+ renameConversationModalAssertion,
}) => {
setTestIds('EPMRTC-588', 'EPMRTC-816', 'EPMRTC-1494');
const newName = 'new name to cancel';
@@ -149,20 +150,25 @@ dialTest(
async () => {
await conversations.openEntityDropdownMenu(conversationName);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- const chatNameOverflow = await conversations
- .getEntityName(conversationName)
- .getComputedStyleProperty(Styles.text_overflow);
+ await renameConversationModalAssertion.assertModalIsVisible();
+ await renameConversationModalAssertion.assertModalTitle(
+ ExpectedConstants.renameConversationModalTitle,
+ );
+ const modalInputValue = await renameConversationModal.getInputValue();
expect
- .soft(chatNameOverflow[0], ExpectedMessages.chatNameIsTruncated)
- .toBe(undefined);
+ .soft(
+ modalInputValue,
+ 'Modal input should contain the initial conversation name',
+ )
+ .toBe(conversationName);
},
);
await dialTest.step(
'Set new conversation name, cancel edit and verify conversation with initial name shown',
async () => {
- await conversations.openEditEntityNameMode(newName);
- await conversations.getEditInputActions().clickCancelButton();
+ await renameConversationModal.nameInput.fillInInput(newName);
+ await renameConversationModal.cancelButton.click();
await expect
.soft(
conversations.getEntityByName(newName),
@@ -199,6 +205,7 @@ dialTest(
conversationData,
dataInjector,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-584', 'EPMRTC-819');
const conversation = conversationData.prepareDefaultConversation();
@@ -209,7 +216,7 @@ dialTest(
await dialHomePage.waitForPageLoaded();
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(newName, {
+ await renameConversationModal.editConversationNameWithSaveButton(newName, {
isHttpMethodTriggered: false,
});
await expect
@@ -258,6 +265,7 @@ dialTest(
setTestIds,
errorPopup,
errorToast,
+ renameConversationModal,
}) => {
setTestIds(
'EPMRTC-585',
@@ -281,7 +289,7 @@ dialTest(
await conversations.selectConversation(conversation.name);
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithEnter(
+ await renameConversationModal.editConversationNameWithEnter(
newLongNameWithMiddleSpacesEndDot,
);
@@ -374,6 +382,7 @@ dialTest(
conversationData,
dataInjector,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds(
'EPMRTC-595',
@@ -383,7 +392,6 @@ dialTest(
'EPMRTC-1574',
'EPMRTC-1276',
);
- let editInputContainer: EditInput;
const newNameWithEndDot = 'updated folder name.';
let conversation: Conversation;
@@ -399,52 +407,54 @@ dialTest(
await dialHomePage.waitForPageLoaded();
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- editInputContainer =
- await conversations.openEditEntityNameMode(newNameWithEndDot);
- await conversations.getEditInputActions().clickTickButton();
+ await renameConversationModal.editConversationNameWithSaveButton(
+ newNameWithEndDot,
+ { isHttpMethodTriggered: false },
+ );
const errorMessage = await errorToast.getElementContent();
expect
.soft(errorMessage, ExpectedMessages.notAllowedNameErrorShown)
.toBe(ExpectedConstants.nameWithDotErrorMessage);
+ await errorToast.closeToast();
},
);
await dialTest.step(
'Start typing prohibited symbols and verify they are not displayed in text input',
async () => {
- await editInputContainer.editInput.click();
- await editInputContainer.editValue(
+ await renameConversationModal.editInputValue(
ExpectedConstants.restrictedNameChars,
);
- const inputContent = await editInputContainer.getEditInputValue();
+ const inputContent = await renameConversationModal.getInputValue();
expect
.soft(inputContent, ExpectedMessages.charactersAreNotDisplayed)
.toBe('');
+ await renameConversationModal.cancelButton.click();
},
);
- await dialTest.step(
- 'Set empty conversation name or spaces and verify initial name is preserved',
- async () => {
- const name = GeneratorUtil.randomArrayElement(['', ' ']);
- editInputContainer = await conversations.openEditEntityNameMode(name);
- await conversations.getEditInputActions().clickTickButton();
- await expect
- .soft(
- conversations.getEntityByName(conversation.name),
- ExpectedMessages.conversationNameNotUpdated,
- )
- .toBeVisible();
- },
- );
+ //TODO decide if that is a correct behavior - to delete this step since the new modal doesn't let us press the "save" button with spaces in the input
+ // await dialTest.step(
+ // 'Set empty conversation name or spaces and verify initial name is preserved',
+ // async () => {
+ // const name = GeneratorUtil.randomArrayElement(['', ' ']);
+ // await renameConversationModal.editConversationNameWithEnter(name);
+ // await expect
+ // .soft(
+ // conversations.getEntityByName(conversation.name),
+ // ExpectedMessages.conversationNameNotUpdated,
+ // )
+ // .toBeVisible();
+ // },
+ // );
await dialTest.step(
'Verify renaming conversation to the name with special symbols is successful',
async () => {
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(
+ await renameConversationModal.editConversationNameWithSaveButton(
ExpectedConstants.allowedSpecialChars,
{ isHttpMethodTriggered: false },
);
@@ -1200,10 +1210,7 @@ dialTest(
conversationData.resetData();
const firstConversation =
- conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [request],
- );
+ conversationData.prepareModelConversationBasedOnRequests([request]);
firstConversation.folderId = firstFolder.id;
firstConversation.id = `${firstConversation.folderId}/${firstConversation.id}`;
conversationData.resetData();
@@ -1221,8 +1228,8 @@ dialTest(
const thirdConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[request],
+ defaultModel,
specialSymbolsName(),
);
thirdConversation.folderId = secondFolder.id;
@@ -1310,6 +1317,7 @@ dialTest(
chatMessages,
chat,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-2849', 'EPMRTC-2959');
const updatedConversationName = `😂👍🥳 😷 🤧 🤠 🥴😇 😈 ⭐あおㅁㄹñ¿äß`;
@@ -1328,7 +1336,7 @@ dialTest(
await conversations.selectConversation(conversation.name);
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(
+ await renameConversationModal.editConversationNameWithSaveButton(
updatedConversationName,
);
await expect
diff --git a/apps/chat-e2e/src/tests/chatExportImport.test.ts b/apps/chat-e2e/src/tests/chatExportImport.test.ts
index e291bee314..fd8022085d 100644
--- a/apps/chat-e2e/src/tests/chatExportImport.test.ts
+++ b/apps/chat-e2e/src/tests/chatExportImport.test.ts
@@ -406,8 +406,8 @@ dialTest(
async () => {
importedRootConversation =
conversationData.prepareModelConversationBasedOnRequests(
- simpleRequestModel!,
requests,
+ simpleRequestModel!,
);
threeConversationsData = ImportConversation.prepareConversationFile(
importedRootConversation,
diff --git a/apps/chat-e2e/src/tests/chatHeader.test.ts b/apps/chat-e2e/src/tests/chatHeader.test.ts
index d7e2e15d9d..309fb530e2 100644
--- a/apps/chat-e2e/src/tests/chatHeader.test.ts
+++ b/apps/chat-e2e/src/tests/chatHeader.test.ts
@@ -179,10 +179,11 @@ dialTest(
setTestIds('EPMRTC-490', 'EPMRTC-491');
let conversation: Conversation;
await dialTest.step('Prepare conversation with history', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- ['first request', 'second request', 'third request'],
- );
+ conversation = conversationData.prepareModelConversationBasedOnRequests([
+ 'first request',
+ 'second request',
+ 'third request',
+ ]);
await dataInjector.createConversations([conversation]);
});
diff --git a/apps/chat-e2e/src/tests/chatSelectionFunctionality.test.ts b/apps/chat-e2e/src/tests/chatSelectionFunctionality.test.ts
index 0895242f11..d665a55b49 100644
--- a/apps/chat-e2e/src/tests/chatSelectionFunctionality.test.ts
+++ b/apps/chat-e2e/src/tests/chatSelectionFunctionality.test.ts
@@ -33,6 +33,7 @@ dialTest(
shareModal,
conversationDropdownMenu,
downloadAssertion,
+ renameConversationModal,
}) => {
setTestIds(
'EPMRTC-934',
@@ -106,7 +107,9 @@ dialTest(
await dialTest.step('Click on Rename, rename and confirm', async () => {
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
firstConversation.name = 'Renamed chat';
- await conversations.editConversationNameWithTick(firstConversation.name);
+ await renameConversationModal.editConversationNameWithSaveButton(
+ firstConversation.name,
+ );
await conversations.getEntityByName(firstConversation.name).waitFor();
await conversationAssertion.assertSelectedConversation(
secondConversation.name,
diff --git a/apps/chat-e2e/src/tests/compareMode.test.ts b/apps/chat-e2e/src/tests/compareMode.test.ts
index e36358fbcc..5fd6fd0930 100644
--- a/apps/chat-e2e/src/tests/compareMode.test.ts
+++ b/apps/chat-e2e/src/tests/compareMode.test.ts
@@ -25,7 +25,9 @@ dialTest.beforeAll(async () => {
allModels = ModelsUtil.getModels().filter((m) => m.iconUrl !== undefined);
defaultModel = ModelsUtil.getDefaultModel()!;
aModel = GeneratorUtil.randomArrayElement(
- allModels.filter((m) => m.id !== defaultModel.id),
+ allModels.filter(
+ (m) => m.id !== defaultModel.id && m.features?.systemPrompt,
+ ),
);
bModel = GeneratorUtil.randomArrayElement(
allModels.filter((m) => m.id !== defaultModel.id && m.id !== aModel.id),
@@ -105,8 +107,8 @@ dialTest(
conversationData.resetData();
secondModelConversation =
conversationData.prepareModelConversationBasedOnRequests(
- aModel,
[request!],
+ aModel,
conversationName,
);
conversationData.resetData();
@@ -393,36 +395,36 @@ dialTest(
async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[firstRequest, secondRequest],
+ defaultModel,
'firstConv',
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[secondRequest, firstRequest],
+ defaultModel,
'secondConv',
);
conversationData.resetData();
thirdConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[firstRequest],
+ defaultModel,
'thirdConv',
);
conversationData.resetData();
forthConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[firstRequest, thirdRequest],
+ defaultModel,
'forthConv',
);
conversationData.resetData();
fifthConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[firstRequest.toLowerCase(), secondRequest],
+ defaultModel,
'fifthConv',
);
@@ -644,15 +646,15 @@ dialTest(
await dialTest.step('Prepare two conversations for comparing', async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- aModel,
request,
+ aModel,
conversationName,
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- bModel,
request,
+ bModel,
conversationName2,
);
await dataInjector.createConversations([
@@ -1043,29 +1045,29 @@ dialTest(
async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[request],
+ defaultModel,
request,
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[request],
+ defaultModel,
'When was epam officially founded',
);
conversationData.resetData();
thirdConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[request],
+ defaultModel,
'Renamed epam systems',
);
conversationData.resetData();
fourthConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[request],
+ defaultModel,
'epam_systems',
);
@@ -1492,6 +1494,7 @@ dialTest(
conversationDropdownMenu,
compare,
compareConversation,
+ renameConversationModal,
}) => {
setTestIds(
'EPMRTC-560',
@@ -1511,15 +1514,14 @@ dialTest(
async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
firstConversationRequests,
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- aModel,
secondConversationRequests,
+ aModel,
);
await dataInjector.createConversations([
@@ -1612,7 +1614,9 @@ dialTest(
const newLeftChatName = GeneratorUtil.randomString(7);
await conversations.openEntityDropdownMenu(updatedRequestContent, 1);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(newLeftChatName);
+ await renameConversationModal.editConversationNameWithSaveButton(
+ newLeftChatName,
+ );
const chatTitle = await leftChatHeader.chatTitle.getElementContent();
expect
diff --git a/apps/chat-e2e/src/tests/conversationNameNumeration.test.ts b/apps/chat-e2e/src/tests/conversationNameNumeration.test.ts
index bd84b27fa5..438e9b5f70 100644
--- a/apps/chat-e2e/src/tests/conversationNameNumeration.test.ts
+++ b/apps/chat-e2e/src/tests/conversationNameNumeration.test.ts
@@ -79,6 +79,7 @@ dialTest.skip(
dataInjector,
conversationDropdownMenu,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-1625');
let firstConversation: Conversation;
@@ -136,7 +137,7 @@ dialTest.skip(
async () => {
await conversations.openEntityDropdownMenu(thirdConversationName);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(
+ await renameConversationModal.editConversationNameWithSaveButton(
GeneratorUtil.randomString(7),
{ isHttpMethodTriggered: false },
);
@@ -366,6 +367,7 @@ dialTest(
localStorageManager,
errorToast,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-2915', 'EPMRTC-2956', 'EPMRTC-2931');
const duplicatedName = GeneratorUtil.randomString(7);
@@ -421,12 +423,8 @@ dialTest(
secondFolderConversation.name,
);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- const editFolderConversationInputActions =
- folderConversations.getEditFolderEntityInputActions();
- await folderConversations
- .getEditFolderEntityInput()
- .editValue(duplicatedName);
- await editFolderConversationInputActions.clickTickButton();
+ await renameConversationModal.editInputValue(duplicatedName);
+ await renameConversationModal.saveButton.click();
await expect
.soft(
@@ -443,7 +441,7 @@ dialTest(
),
);
await errorToast.closeToast();
- await editFolderConversationInputActions.clickCancelButton();
+ await renameConversationModal.cancelButton.click();
},
);
@@ -527,6 +525,7 @@ dialTest(
errorToast,
conversationDropdownMenu,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-2933');
let firstConversation: Conversation;
@@ -549,8 +548,8 @@ dialTest(
await dialHomePage.waitForPageLoaded();
await conversations.openEntityDropdownMenu(secondConversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.openEditEntityNameMode(firstConversation.name);
- await conversations.getEditInputActions().clickTickButton();
+ await renameConversationModal.editInputValue(firstConversation.name);
+ await renameConversationModal.saveButton.click();
await expect
.soft(
diff --git a/apps/chat-e2e/src/tests/duplicateConversation.test.ts b/apps/chat-e2e/src/tests/duplicateConversation.test.ts
index ac31f8cf33..325aeebd15 100644
--- a/apps/chat-e2e/src/tests/duplicateConversation.test.ts
+++ b/apps/chat-e2e/src/tests/duplicateConversation.test.ts
@@ -1,5 +1,4 @@
import { Conversation } from '@/chat/types/chat';
-import { DialAIEntityModel } from '@/chat/types/models';
import dialTest from '@/src/core/dialFixtures';
import {
CollapsedSections,
@@ -8,15 +7,8 @@ import {
FolderConversation,
MenuOptions,
} from '@/src/testData';
-import { ModelsUtil } from '@/src/utils';
import { expect } from '@playwright/test';
-let defaultModel: DialAIEntityModel;
-
-dialTest.beforeAll(async () => {
- defaultModel = ModelsUtil.getDefaultModel()!;
-});
-
dialTest(
'Duplicate chat located in today.\n' +
'Duplicate chat located in today several times to check postfixes',
@@ -35,10 +27,10 @@ dialTest(
const secondRequest = 'second request';
await dialTest.step('Prepare conversation with some history', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [firstRequest, secondRequest],
- );
+ conversation = conversationData.prepareModelConversationBasedOnRequests([
+ firstRequest,
+ secondRequest,
+ ]);
await dataInjector.createConversations([conversation]);
});
diff --git a/apps/chat-e2e/src/tests/messageTemplate.test.ts b/apps/chat-e2e/src/tests/messageTemplate.test.ts
new file mode 100644
index 0000000000..bbcab2e2a7
--- /dev/null
+++ b/apps/chat-e2e/src/tests/messageTemplate.test.ts
@@ -0,0 +1,852 @@
+import { Conversation } from '@/chat/types/chat';
+import { Prompt } from '@/chat/types/prompt';
+import dialTest from '@/src/core/dialFixtures';
+import { ExpectedConstants, MockedChatApiResponseBodies } from '@/src/testData';
+import { Attributes, ThemeColorAttributes } from '@/src/ui/domData';
+import { keys } from '@/src/ui/keyboard';
+import { GeneratorUtil } from '@/src/utils';
+
+const requestContent =
+ 'Spanish is the 2nd most widely spoken language in the world.\n' +
+ 'Spanish has a royal family.\n' +
+ 'Spanish people do not consider paella as Spain’s national dish.';
+const firstRowFirstVar = '{{Language}}';
+const firstRowSecondVar = '{{number of place}}';
+const secondRowFirstVar = '{{Country}}';
+const secondRowSecondVar = '{{adjective}}';
+const thirdRowFirstVar = '{{Language2}}';
+const firstRowContent = 'Spanish is the 2nd';
+const firstRowTemplate = `${firstRowFirstVar} is the ${firstRowSecondVar}`;
+const secondRowContent = 'Spain’s national dish';
+const secondRowTemplate = `${secondRowFirstVar}’s ${secondRowSecondVar} dish`;
+const thirdRowContent = 'Spanish';
+const thirdRowTemplate = thirdRowFirstVar;
+
+dialTest(
+ 'Message template: Show more/less, Original message, tips.\n' +
+ 'Message template: new row appears if to type anything in "a part of the message" when \'your template\' is empty (and vice versa).\n' +
+ 'Message template: the changes are not saved if to close the window on X.\n' +
+ 'Message template: Delete is not available for the initial row, other rows can be deleted.\n' +
+ 'Message template: the window is not closed if to click on any area outside the window.\n' +
+ "Message template: the order of the 'part of the messages' is set by user, no auto-sorting",
+ async ({
+ dialHomePage,
+ conversations,
+ messageTemplateModal,
+ page,
+ messageTemplateModalAssertion,
+ chatMessages,
+ conversationData,
+ dataInjector,
+ setTestIds,
+ }) => {
+ setTestIds(
+ 'EPMRTC-4251',
+ 'EPMRTC-4271',
+ 'EPMRTC-4268',
+ 'EPMRTC-4272',
+ 'EPMRTC-4269',
+ 'EPMRTC-4274',
+ );
+ const requestContent = GeneratorUtil.randomString(40)
+ .concat(' ')
+ .repeat(10);
+ const truncatedRequestContent = requestContent
+ .substring(0, 160)
+ .concat('...');
+ let conversation: Conversation;
+
+ await dialTest.step('Prepare conversation with long request', async () => {
+ conversation = conversationData.prepareModelConversationBasedOnRequests([
+ requestContent,
+ ]);
+ await dataInjector.createConversations([conversation]);
+ });
+
+ await dialTest.step(
+ 'Open "Message Template" modal and verify its content',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await conversations.selectConversation(conversation.name);
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.title,
+ ExpectedConstants.messageTemplateModalTitle,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.description,
+ ExpectedConstants.messageTemplateModalDescription,
+ );
+ await messageTemplateModalAssertion.assertElementAttribute(
+ messageTemplateModal.getTemplateRowContent(1),
+ Attributes.placeholder,
+ ExpectedConstants.messageTemplateContentPlaceholder,
+ );
+ await messageTemplateModalAssertion.assertElementAttribute(
+ messageTemplateModal.getTemplateRowValue(1),
+ Attributes.placeholder,
+ ExpectedConstants.messageTemplateValuePlaceholder,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Verify original message is cut with dots',
+ async () => {
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.originalMessageContent,
+ truncatedRequestContent,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Click on "Show more" button and verify full message is displayed',
+ async () => {
+ await messageTemplateModal.showMoreButton.click();
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.originalMessageContent,
+ requestContent,
+ );
+ await messageTemplateModal.showLessButton.click();
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.originalMessageContent,
+ truncatedRequestContent,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Set cursor in the first row and verify no new row is added',
+ async () => {
+ await messageTemplateModal.getTemplateRowContent(1).click();
+ await messageTemplateModal.getTemplateRowValue(1).click();
+ await messageTemplateModalAssertion.assertElementsCount(
+ messageTemplateModal.templateRows,
+ 1,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Close the modal, reopen it again and verify changes are not saved',
+ async () => {
+ await messageTemplateModal.cancelButton.click();
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementsCount(
+ messageTemplateModal.templateRows,
+ 1,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Click outside the modal and verify it is not closed',
+ async () => {
+ await page.mouse.click(0, 0);
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal,
+ 'visible',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Type request chars in the first row and verify new row is added, delete button is available for the first row',
+ async () => {
+ const matchContent = requestContent.split(' ')[0].split('');
+ const values = [
+ GeneratorUtil.randomArrayElement(matchContent),
+ GeneratorUtil.randomArrayElement(matchContent),
+ GeneratorUtil.randomArrayElement(matchContent),
+ ];
+ for (let i = 0; i < values.length; i++) {
+ await messageTemplateModal
+ .getTemplateRowContent(i + 1)
+ .fill(values[i]);
+ await messageTemplateModalAssertion.assertElementsCount(
+ messageTemplateModal.templateRows,
+ i + 2,
+ );
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getTemplateRowDeleteButton(i + 1),
+ 'visible',
+ );
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getTemplateRowDeleteButton(i + 2),
+ 'hidden',
+ );
+ await messageTemplateModal
+ .getTemplateRowValue(i + 1)
+ .fill(`{{${values[i]}}}`);
+ }
+ await messageTemplateModal.saveChanges();
+ await chatMessages.openMessageTemplateModal(1);
+
+ for (let i = 0; i < values.length; i++) {
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(i + 1),
+ values[i],
+ );
+ }
+ },
+ );
+ },
+);
+
+dialTest(
+ 'Message template: Preview mode based on three variables.\n' +
+ 'Message template: changes are saved: add a row, delete the row, update values',
+ async ({
+ dialHomePage,
+ conversations,
+ messageTemplateModal,
+ messageTemplateModalAssertion,
+ chatMessages,
+ conversationData,
+ dataInjector,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-4270', 'EPMRTC-4276');
+ const updatedSecondRowContent = secondRowContent.substring(0, 3);
+ const updatedSecondRowTemplate = `{{${secondRowContent.substring(0, 3)}}}`;
+ const rowsMap = new Map([
+ [firstRowContent, firstRowTemplate],
+ [secondRowContent, secondRowTemplate],
+ [thirdRowContent, thirdRowTemplate],
+ ]);
+ const expectedPreviewContent = `${firstRowFirstVar} is the ${firstRowSecondVar} most widely spoken language in the world.\n${thirdRowFirstVar} has a royal family.\n${thirdRowFirstVar} people do not consider paella as ${secondRowFirstVar}’s ${secondRowSecondVar} dish.`;
+ let conversation: Conversation;
+
+ await dialTest.step(
+ 'Prepare conversation with specified request',
+ async () => {
+ conversation = conversationData.prepareModelConversationBasedOnRequests(
+ [requestContent],
+ );
+ await dataInjector.createConversations([conversation]);
+ },
+ );
+
+ await dialTest.step(
+ 'Open "Message Template" modal and fill in rows with values',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await conversations.selectConversation(conversation.name);
+ await chatMessages.openMessageTemplateModal(1);
+ for (const [key, value] of rowsMap.entries()) {
+ const index = Array.from(rowsMap.keys()).indexOf(key);
+ await messageTemplateModal.getTemplateRowContent(index + 1).fill(key);
+ await messageTemplateModal.getTemplateRowValue(index + 1).fill(value);
+ }
+ },
+ );
+
+ await dialTest.step(
+ 'Switch to Preview tab and verify the content',
+ async () => {
+ await messageTemplateModal.previewTab.click();
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.templatePreview,
+ expectedPreviewContent,
+ );
+ for (const variable of [
+ firstRowFirstVar,
+ firstRowSecondVar,
+ secondRowFirstVar,
+ secondRowSecondVar,
+ thirdRowFirstVar,
+ ]) {
+ await messageTemplateModalAssertion.assertElementAttribute(
+ messageTemplateModal.templatePreviewVar(variable),
+ Attributes.class,
+ ThemeColorAttributes.textAccentTertiary,
+ );
+ }
+ },
+ );
+
+ await dialTest.step(
+ 'Save changes, reopen "Message Template" modal and verify the changes are saved',
+ async () => {
+ await messageTemplateModal.saveChanges();
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementsCount(
+ messageTemplateModal.templateRows,
+ rowsMap.size + 1,
+ );
+ for (const [key, value] of rowsMap.entries()) {
+ const index = Array.from(rowsMap.keys()).indexOf(key);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(index + 1),
+ key,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(index + 1),
+ value,
+ );
+ }
+ },
+ );
+
+ await dialTest.step(
+ 'Delete the first row, update the second one and save',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowContent(2)
+ .fill(updatedSecondRowContent);
+ await messageTemplateModal
+ .getTemplateRowValue(2)
+ .fill(updatedSecondRowTemplate);
+ await messageTemplateModal.getTemplateRowDeleteButton(1).click();
+ await messageTemplateModal.saveChanges();
+ },
+ );
+
+ await dialTest.step(
+ 'Reopen "Message Template" modal and verify the changes are saved',
+ async () => {
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(1),
+ updatedSecondRowContent,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(1),
+ updatedSecondRowTemplate,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(2),
+ thirdRowContent,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(2),
+ thirdRowTemplate,
+ );
+ },
+ );
+ },
+);
+
+dialTest(
+ 'Message template: updated user-message influences on the template: original text is updated, deleted text is removed from the template',
+ async ({
+ dialHomePage,
+ conversations,
+ messageTemplateModal,
+ messageTemplateModalAssertion,
+ chatMessages,
+ conversationData,
+ dataInjector,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-4273');
+ const request = requestContent.split('\n').slice(0, 2).join('\n');
+ const rowsMap = new Map([
+ [firstRowContent, firstRowTemplate],
+ [thirdRowContent, thirdRowTemplate],
+ ]);
+ const updatedRequest = GeneratorUtil.randomString(5).concat(
+ request.split('\n')[1],
+ );
+ let conversation: Conversation;
+
+ await dialTest.step(
+ 'Prepare conversation with message template',
+ async () => {
+ conversation =
+ conversationData.prepareConversationBasedOnMessageTemplate(
+ request,
+ rowsMap,
+ );
+ await dataInjector.createConversations([conversation]);
+ },
+ );
+
+ await dialTest.step(
+ 'Edit conversation request by removing the first line',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await conversations.selectConversation(conversation.name);
+ await chatMessages.openEditMessageMode(1);
+ await dialHomePage.mockChatTextResponse(
+ MockedChatApiResponseBodies.simpleTextBody,
+ );
+ await chatMessages.editMessage(request, updatedRequest);
+ },
+ );
+
+ await dialTest.step(
+ 'Open "Message Template" modal and verify template rows are updated',
+ async () => {
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.originalMessageContent,
+ updatedRequest,
+ );
+ await messageTemplateModalAssertion.assertElementsCount(
+ messageTemplateModal.templateRows,
+ 2,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(1),
+ thirdRowContent,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(1),
+ thirdRowTemplate,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Open template preview and verify it is updated according to changes',
+ async () => {
+ await messageTemplateModal.previewTab.click();
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.templatePreview,
+ updatedRequest.replace(thirdRowContent, thirdRowFirstVar),
+ );
+ },
+ );
+ },
+);
+
+dialTest(
+ 'Message template: error "This part was not found in the original message" disappears if to correct the message after it was copy-pasted.\n' +
+ 'Message template: error "This part was not found in the original message".\n' +
+ 'Message template: error "Template must have at least one variable".\n' +
+ 'Message template: Preview and Save buttons are disabled if there is any error.\n' +
+ 'Message template: error "Please fill in this required field".\n' +
+ `Message template: fill in 'template' field when 'a part of the message' is empty` +
+ `Message template: error "Template doesn't match the message text".\n` +
+ 'Message template: errors are not applied to other fields from the row below, when the row is deleted',
+ async ({
+ dialHomePage,
+ conversations,
+ messageTemplateModal,
+ page,
+ messageTemplateModalAssertion,
+ chatMessages,
+ conversationData,
+ dataInjector,
+ setTestIds,
+ }) => {
+ setTestIds(
+ 'EPMRTC-4297',
+ 'EPMRTC-4260',
+ 'EPMRTC-4262',
+ 'EPMRTC-4304',
+ 'EPMRTC-4264',
+ 'EPMRTC-4265',
+ 'EPMRTC-4263',
+ 'EPMRTC-4266',
+ );
+ const request = requestContent.split('\n')[0];
+ const mismatchWord = 'food';
+ const firstRowMismatchContent = `Spanish is the 2nd most widely spoken ${mismatchWord}`;
+ let conversation: Conversation;
+
+ await dialTest.step(
+ 'Prepare conversation with specified request',
+ async () => {
+ conversation = conversationData.prepareModelConversationBasedOnRequests(
+ [request],
+ );
+ await dataInjector.createConversations([conversation]);
+ },
+ );
+
+ await dialTest.step(
+ 'Open "Message Template" modal, paste the text that does not match the request and verify error appears',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await conversations.selectConversation(conversation.name);
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModal.getTemplateRowContent(1).click();
+ await dialHomePage.copyToClipboard(firstRowMismatchContent);
+ await dialHomePage.pasteFromClipboard();
+ for (let i = 1; i <= 2; i++) {
+ await page.keyboard.press(keys.backspace);
+ }
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ ExpectedConstants.originalMessageTemplateErrorMessage,
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.saveTemplate,
+ 'disabled',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.previewTab,
+ 'disabled',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Correct the text to match the request and verify error disappears',
+ async () => {
+ for (let i = 1; i <= mismatchWord.length; i++) {
+ await page.keyboard.press(keys.backspace);
+ }
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.saveTemplate,
+ 'disabled',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.previewTab,
+ 'disabled',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Type the text that match the request but in capitals and verify error appears',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowContent(1)
+ .fill(request.toUpperCase());
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ ExpectedConstants.originalMessageTemplateErrorMessage,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Type incorrect value into template field and verify error appears',
+ async () => {
+ for (const variable of [GeneratorUtil.randomString(5), '{{}}']) {
+ await messageTemplateModal.getTemplateRowValue(1).fill(variable);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ ExpectedConstants.messageTemplateMissingVarErrorMessage,
+ );
+ }
+ },
+ );
+
+ await dialTest.step(
+ 'Set correct value into template field and verify error disappears',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowValue(1)
+ .fill(secondRowFirstVar);
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ 'hidden',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Delete the row and verify Save and Preview buttons are enabled',
+ async () => {
+ await messageTemplateModal.getTemplateRowDeleteButton(1).click();
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.saveTemplate,
+ 'enabled',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.previewTab,
+ 'enabled',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Set the correct value in the template field and verify error is displayed',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowValue(1)
+ .fill(firstRowFirstVar);
+ await messageTemplateModal.getTemplateRowContent(1).click();
+ await messageTemplateModal.getTemplateRowValue(1).click();
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ ExpectedConstants.messageTemplateRequiredField,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Set correct value into message, clear the row fields and verify error appears',
+ async () => {
+ await messageTemplateModal.getTemplateRowContent(1).fill(request);
+
+ await messageTemplateModal.getTemplateRowContent(1).fill('');
+ await messageTemplateModal.getTemplateRowValue(1).fill('');
+
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ ExpectedConstants.messageTemplateRequiredField,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ ExpectedConstants.messageTemplateRequiredField,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Set correct values into row fields and verify errors disappear',
+ async () => {
+ await messageTemplateModal.getTemplateRowContent(1).fill(request);
+ await messageTemplateModal
+ .getTemplateRowValue(1)
+ .fill(firstRowFirstVar);
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ 'hidden',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Set the template that does not match the message and verify error is displayed',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowContent(1)
+ .fill(firstRowContent);
+ await messageTemplateModal
+ .getTemplateRowValue(1)
+ .fill(
+ firstRowTemplate.substring(
+ 0,
+ firstRowTemplate.indexOf(firstRowSecondVar),
+ ),
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ ExpectedConstants.messageTemplateMismatchTextErrorMessage,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Correct the template value and verify error disappears',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowValue(1)
+ .fill(firstRowTemplate);
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.saveTemplate,
+ 'enabled',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.previewTab,
+ 'enabled',
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Fill in one more row with the correct values, clear the 1st row and verify errors are displayed',
+ async () => {
+ await messageTemplateModal
+ .getTemplateRowContent(2)
+ .fill(thirdRowContent);
+ await messageTemplateModal
+ .getTemplateRowValue(2)
+ .fill(thirdRowTemplate);
+
+ await messageTemplateModal.getTemplateRowContent(1).fill('');
+ await messageTemplateModal.getTemplateRowValue(1).fill('');
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ ExpectedConstants.messageTemplateRequiredField,
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ ExpectedConstants.messageTemplateRequiredField,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Delete row with errors and verify no errors are displayed for the valid row',
+ async () => {
+ await messageTemplateModal.getTemplateRowDeleteButton(1).click();
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowContent(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getFieldBottomMessage(
+ messageTemplateModal.getTemplateRowValue(1),
+ ),
+ 'hidden',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.saveTemplate,
+ 'enabled',
+ );
+ await messageTemplateModalAssertion.assertElementActionabilityState(
+ messageTemplateModal.previewTab,
+ 'enabled',
+ );
+ },
+ );
+ },
+);
+
+dialTest(
+ 'Message template created for the user-message with parametrized prompt',
+ async ({
+ dialHomePage,
+ messageTemplateModal,
+ messageTemplateModalAssertion,
+ chatMessages,
+ promptData,
+ sendMessage,
+ dataInjector,
+ variableModalDialog,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-4298');
+ const aVar = 'A';
+ const aVarPlaceholder = `{{${aVar}}}`;
+ const aValue = '1';
+ const bVar = 'B';
+ const bVarPlaceholder = `{{${bVar}}}`;
+ const bValue = '2';
+ const cVar = 'C';
+ const cValue = '10';
+ const cVarPlaceholder = `{{${cVar}|${cValue}}}`;
+ const dVar = 'D';
+ const dValue = '5';
+ const dVarPlaceholder = `{{${dVar}|${dValue}}}`;
+ const firstPromptContent = (a: string, b: string) =>
+ `Calculate ${aVar} + ${bVar}, where ${aVar} = ${a} and ${bVar} = ${b}`;
+ const secondPromptContent = (c: string, d: string) =>
+ `Calculate ${cVar} - ${dVar}, where ${cVar} = ${c} and ${dVar} = ${d}`;
+ const fullRequest = `${firstPromptContent(aValue, bValue)} AND ${secondPromptContent(cValue, dValue)}`;
+ let firstPrompt: Prompt;
+ let secondPrompt: Prompt;
+
+ await dialTest.step(
+ 'Prepare prompt with params and default values',
+ async () => {
+ firstPrompt = promptData.preparePrompt(
+ firstPromptContent(aVarPlaceholder, bVarPlaceholder),
+ );
+ promptData.resetData();
+ secondPrompt = promptData.preparePrompt(
+ secondPromptContent(cVarPlaceholder, dVarPlaceholder),
+ );
+ await dataInjector.createPrompts([firstPrompt, secondPrompt]);
+ },
+ );
+
+ await dialTest.step(
+ 'Create new conversation based on both prompts',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await sendMessage.messageInput.fillInInput(`/${firstPrompt.name}`);
+ await sendMessage
+ .getPromptList()
+ .selectPromptWithMouse(firstPrompt.name, {
+ triggeredHttpMethod: 'GET',
+ });
+ await variableModalDialog.setVariableValue(aVar, aValue);
+ await variableModalDialog.setVariableValue(bVar, bValue);
+ await variableModalDialog.submitButton.click();
+ await sendMessage.typeInInput(' AND ');
+ await sendMessage.messageInput.typeInInput(`/${secondPrompt.name}`);
+ await sendMessage
+ .getPromptList()
+ .selectPromptWithMouse(secondPrompt.name, {
+ triggeredHttpMethod: 'GET',
+ });
+ await variableModalDialog.submitButton.click();
+ await dialHomePage.mockChatTextResponse(
+ MockedChatApiResponseBodies.simpleTextBody,
+ );
+ await sendMessage.sendMessageButton.click();
+ },
+ );
+
+ await dialTest.step(
+ 'Open request message template and verify prompts are displayed in the rows',
+ async () => {
+ await chatMessages.openMessageTemplateModal(1);
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.originalMessageContent,
+ fullRequest,
+ );
+
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(1),
+ firstPromptContent(aValue, bValue),
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowContent(2),
+ secondPromptContent(cValue, dValue),
+ );
+
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(1),
+ firstPromptContent(aVarPlaceholder, bVarPlaceholder),
+ );
+ await messageTemplateModalAssertion.assertElementText(
+ messageTemplateModal.getTemplateRowValue(2),
+ secondPromptContent(cVarPlaceholder, dVarPlaceholder),
+ );
+ },
+ );
+ },
+);
diff --git a/apps/chat-e2e/src/tests/monitoring/renameConversation.test.ts b/apps/chat-e2e/src/tests/monitoring/renameConversation.test.ts
index 27f7d4bdad..657dccb391 100644
--- a/apps/chat-e2e/src/tests/monitoring/renameConversation.test.ts
+++ b/apps/chat-e2e/src/tests/monitoring/renameConversation.test.ts
@@ -12,6 +12,7 @@ dialTest(
conversationData,
dataInjector,
conversationAssertion,
+ renameConversationModal,
}) => {
const updatedConversationName = GeneratorUtil.randomString(5);
let conversation: Conversation;
@@ -27,7 +28,9 @@ dialTest(
await conversations.selectConversation(conversation.name);
await conversations.openEntityDropdownMenu(conversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.editConversationNameWithTick(updatedConversationName);
+ await renameConversationModal.editConversationNameWithSaveButton(
+ updatedConversationName,
+ );
await conversationAssertion.assertEntityState(
{ name: updatedConversationName },
'visible',
diff --git a/apps/chat-e2e/src/tests/playBack.test.ts b/apps/chat-e2e/src/tests/playBack.test.ts
index e8c22952c0..e73b74f213 100644
--- a/apps/chat-e2e/src/tests/playBack.test.ts
+++ b/apps/chat-e2e/src/tests/playBack.test.ts
@@ -420,7 +420,6 @@ dialTest(
'Prepare playback conversation based on 2 requests',
async () => {
conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
['1st request', '2nd request'],
);
conversationData.resetData();
@@ -672,7 +671,6 @@ dialTest(
'Prepare playback conversation based on 2 requests and played back till the last message',
async () => {
conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
['1+2=', '2+3='],
);
conversationData.resetData();
@@ -751,7 +749,6 @@ dialTest(
'Prepare playback conversation based on several long requests',
async () => {
conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[GeneratorUtil.randomString(3000)],
);
conversationData.resetData();
diff --git a/apps/chat-e2e/src/tests/promptUsage.test.ts b/apps/chat-e2e/src/tests/promptUsage.test.ts
index 85613c0acc..3d9d7884c0 100644
--- a/apps/chat-e2e/src/tests/promptUsage.test.ts
+++ b/apps/chat-e2e/src/tests/promptUsage.test.ts
@@ -121,26 +121,31 @@ dialTest(
dialTest(
'Prompt text without parameters appears one by one in Input message box.\n' +
- 'The text entered by user remains if to use prompt with parameters in Input message box',
+ 'The text entered by user remains if to use prompt with parameters in Input message box.\n' +
+ 'Message template created for the user-message with prompt without parameters',
async ({
dialHomePage,
promptData,
dataInjector,
sendMessage,
+ chatMessages,
+ messageTemplateModal,
+ messageTemplateModalAssertion,
sendMessageAssertion,
variableModalDialog,
setTestIds,
}) => {
- setTestIds('EPMRTC-3823', 'EPMRTC-3803');
+ setTestIds('EPMRTC-3823', 'EPMRTC-3803', 'EPMRTC-4371');
let simplePrompt: Prompt;
let promptWithVariable: Prompt;
+ const simplePromptContent = GeneratorUtil.randomString(10);
const promptVariable = 'A';
const promptWithVariableContent = (variable: string) =>
`Calculate ${variable} * 100`;
await dialTest.step('Prepare 2 prompts', async () => {
simplePrompt = promptData.preparePrompt(
- GeneratorUtil.randomString(10),
+ simplePromptContent,
undefined,
ExpectedConstants.newPromptTitle(1),
);
@@ -184,6 +189,22 @@ dialTest(
);
},
);
+
+ await dialTest.step(
+ 'Send the request, open request message template and verify prompt w/o params is not displayed in the rows',
+ async () => {
+ await dialHomePage.mockChatTextResponse(
+ MockedChatApiResponseBodies.simpleTextBody,
+ );
+ await sendMessage.sendMessageButton.click();
+ await chatMessages.openMessageTemplateModal(1);
+
+ await messageTemplateModalAssertion.assertElementState(
+ messageTemplateModal.getTemplateRowContent(simplePromptContent),
+ 'hidden',
+ );
+ },
+ );
},
);
diff --git a/apps/chat-e2e/src/tests/publishConversation.test.ts b/apps/chat-e2e/src/tests/publishConversation.test.ts
index 93274ae34f..a99330ef4e 100644
--- a/apps/chat-e2e/src/tests/publishConversation.test.ts
+++ b/apps/chat-e2e/src/tests/publishConversation.test.ts
@@ -11,6 +11,7 @@ import {
} from '@/src/testData';
import { UploadDownloadData } from '@/src/ui/pages';
import { GeneratorUtil, ModelsUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
const publicationsToUnpublish: Publication[] = [];
@@ -428,7 +429,7 @@ dialAdminTest(
await dataInjector.createConversations([conversation]);
const publishRequest = publishRequestBuilder
.withName(publicationName)
- .withConversationResource(conversation)
+ .withConversationResource(conversation, PublishActions.ADD)
.build();
await publicationApiHelper.createPublishRequest(publishRequest);
conversationData.resetData();
diff --git a/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts b/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
index bef7a70672..5b6bf4f05d 100644
--- a/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
+++ b/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
@@ -10,6 +10,7 @@ import {
PublishPath,
} from '@/src/testData';
import { GeneratorUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
const publicationsToUnpublish: Publication[] = [];
@@ -75,7 +76,10 @@ dialAdminTest(
const publishRequest = publishRequestBuilder
.withName(GeneratorUtil.randomPublicationRequestName())
.withTargetFolder(organizationFolderNames[i - 1])
- .withConversationResource(publishRequestConversations[i - 1])
+ .withConversationResource(
+ publishRequestConversations[i - 1],
+ PublishActions.ADD,
+ )
.build();
const publication =
await publicationApiHelper.createPublishRequest(publishRequest);
@@ -258,7 +262,8 @@ dialAdminTest(
const requestName = GeneratorUtil.randomPublicationRequestName();
const newFolderName = GeneratorUtil.randomString(maxNameLength * 1.5);
const cutNewFolderName = newFolderName.substring(0, maxNameLength);
- const publicationPath = `${PublishPath.Organization}/${cutNewFolderName}/${ExpectedConstants.newFolderWithIndexTitle(1)}/${ExpectedConstants.newFolderWithIndexTitle(2)}/${ExpectedConstants.newFolderWithIndexTitle(3)}`;
+ const defaultFolderName = ExpectedConstants.newFolderWithIndexTitle(1);
+ const publicationPath = `${PublishPath.Organization}/${cutNewFolderName}/${defaultFolderName}/${defaultFolderName}/${defaultFolderName}`;
await dialTest.step('Prepare a new conversation to publish', async () => {
conversationToPublish = conversationData.prepareDefaultConversation();
@@ -279,11 +284,11 @@ dialAdminTest(
await selectFolderModal.newFolderButton.click();
await selectFoldersAssertion.assertFolderEditInputState('visible');
await selectFoldersAssertion.assertFolderEditInputValue(
- ExpectedConstants.newFolderWithIndexTitle(1),
+ defaultFolderName,
);
await selectFolders.getEditFolderInputActions().clickTickButton();
await selectFoldersAssertion.assertFolderState(
- { name: ExpectedConstants.newFolderWithIndexTitle(1) },
+ { name: defaultFolderName },
'visible',
);
},
@@ -292,9 +297,7 @@ dialAdminTest(
await dialTest.step(
'Open folder dropdown menu and verify available options',
async () => {
- await selectFolders.openFolderDropdownMenu(
- ExpectedConstants.newFolderWithIndexTitle(1),
- );
+ await selectFolders.openFolderDropdownMenu(defaultFolderName);
await folderDropdownMenuAssertion.assertMenuOptions([
MenuOptions.rename,
MenuOptions.delete,
@@ -318,11 +321,11 @@ dialAdminTest(
await selectFolders.openFolderDropdownMenu(cutNewFolderName);
await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder);
await selectFoldersAssertion.assertFolderEditInputValue(
- ExpectedConstants.newFolderWithIndexTitle(1),
+ defaultFolderName,
);
await selectFolders.getEditFolderInputActions().clickTickButton();
await selectFoldersAssertion.assertFolderState(
- { name: ExpectedConstants.newFolderWithIndexTitle(1) },
+ { name: defaultFolderName },
'visible',
);
});
@@ -331,9 +334,7 @@ dialAdminTest(
'Verify error message appears on adding more than 3 sub-folders',
async () => {
for (let i = 1; i <= maxNestedLevel - 1; i++) {
- await selectFolders.openFolderDropdownMenu(
- ExpectedConstants.newFolderWithIndexTitle(i),
- );
+ await selectFolders.openFolderDropdownMenu(defaultFolderName, i);
await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder);
if (i === maxNestedLevel - 1) {
await errorToastAssertion.assertToastMessage(
@@ -343,7 +344,10 @@ dialAdminTest(
} else {
await selectFolders.getEditFolderInputActions().clickTickButton();
await selectFoldersAssertion.assertFolderState(
- { name: ExpectedConstants.newFolderWithIndexTitle(i + 1) },
+ {
+ name: defaultFolderName,
+ index: i + 1,
+ },
'visible',
);
}
@@ -371,16 +375,12 @@ dialAdminTest(
if (i === maxNestedLevel - 1) {
await selectFolders.getEditFolderInputActions().clickTickButton();
}
- await selectFolderModal.selectFolder(
- ExpectedConstants.newFolderWithIndexTitle(i),
- );
+ await selectFolderModal.selectFolder(defaultFolderName, i);
await selectFoldersAssertion.assertFolderSelectedState(
- { name: ExpectedConstants.newFolderWithIndexTitle(i) },
+ { name: defaultFolderName, index: i },
true,
);
- await selectFolders.expandFolder(
- ExpectedConstants.newFolderWithIndexTitle(i),
- );
+ await selectFolders.expandFolder(defaultFolderName, undefined, i);
}
},
);
@@ -445,8 +445,6 @@ dialAdminTest(
await adminPublicationReviewControl.backToPublicationRequest();
await adminPublishingApprovalModal.approveRequest();
- await dialHomePage.reloadPage();
- await dialHomePage.waitForPageLoaded();
await adminOrganizationFolderConversationAssertions.assertFolderState(
{ name: cutNewFolderName },
'visible',
@@ -457,17 +455,18 @@ dialAdminTest(
);
for (let i = 1; i <= maxNestedLevel - 1; i++) {
await adminOrganizationFolderConversationAssertions.assertFolderState(
- { name: ExpectedConstants.newFolderWithIndexTitle(i) },
+ { name: defaultFolderName, index: i },
'visible',
);
await adminOrganizationFolderConversations.expandFolder(
- ExpectedConstants.newFolderWithIndexTitle(i),
- { httpHost: ExpectedConstants.newFolderWithIndexTitle(i) },
+ defaultFolderName,
+ { httpHost: defaultFolderName },
+ i,
);
}
await adminOrganizationFolderConversationAssertions.assertFolderEntityState(
- { name: ExpectedConstants.newFolderWithIndexTitle(3) },
+ { name: defaultFolderName, index: 3 },
{ name: conversationToPublish.name },
'visible',
);
diff --git a/apps/chat-e2e/src/tests/publishFolderWithConversation.test.ts b/apps/chat-e2e/src/tests/publishFolderWithConversation.test.ts
index 90b0ac4baf..e62cdde276 100644
--- a/apps/chat-e2e/src/tests/publishFolderWithConversation.test.ts
+++ b/apps/chat-e2e/src/tests/publishFolderWithConversation.test.ts
@@ -12,6 +12,7 @@ import {
PublishPath,
} from '@/src/testData';
import { GeneratorUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
const publicationsToUnpublish: Publication[] = [];
const levelsCount = 4;
@@ -40,7 +41,7 @@ dialAdminTest(
adminTooltip,
adminApproveRequiredConversationsAssertion,
adminPublishingApprovalModalAssertion,
- adminPublishingApprovalFolderConversationsAssertion,
+ adminFolderToApproveAssertion,
adminChatHeaderAssertion,
adminChatMessagesAssertion,
adminOrganizationFolderConversations,
@@ -188,7 +189,7 @@ dialAdminTest(
'Verify folders hierarchy with non empty conversations is displayed on "Publication approval" modal, "Approve" button is disabled',
async () => {
for (const conversation of allConversations) {
- await adminPublishingApprovalFolderConversationsAssertion.assertFolderEntityState(
+ await adminFolderToApproveAssertion.assertFolderEntityState(
{ name: nestedFolders[0].name },
{ name: conversation.name },
conversation.name === emptyConversation.name ||
@@ -401,7 +402,7 @@ dialAdminTest(
adminPublicationReviewControl,
adminApproveRequiredConversations,
adminPublishingApprovalModalAssertion,
- adminPublishingApprovalFolderConversationsAssertion,
+ adminFolderToApproveAssertion,
adminPublishingApprovalModal,
adminOrganizationFolderConversations,
adminOrganizationFolderConversationAssertions,
@@ -435,6 +436,7 @@ dialAdminTest(
.withName(GeneratorUtil.randomPublicationRequestName())
.withConversationResource(
publishedFolderConversation.conversations[0],
+ PublishActions.ADD,
)
.build();
const publication =
@@ -491,14 +493,16 @@ dialAdminTest(
for (let i = 1; i <= levelsCount - 2; i++) {
await selectFolders.openFolderDropdownMenu(
- ExpectedConstants.newFolderWithIndexTitle(i),
+ ExpectedConstants.newFolderWithIndexTitle(1),
+ i,
);
await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder);
await selectFolders.getEditFolderInputActions().clickTickButton();
}
await selectFolderModal.selectFolder(
- ExpectedConstants.newFolderWithIndexTitle(levelsCount - 1),
+ ExpectedConstants.newFolderWithIndexTitle(1),
+ levelsCount - 1,
);
await selectFolderModal.clickSelectFolderButton();
await errorToastAssertion.assertToastMessage(
@@ -571,7 +575,7 @@ dialAdminTest(
await dialAdminTest.step(
'Verify folders hierarchy is displayed on "Publication approval" modal and "Publish to" path',
async () => {
- await adminPublishingApprovalFolderConversationsAssertion.assertFolderEntityState(
+ await adminFolderToApproveAssertion.assertFolderEntityState(
{ name: folderConversationToPublish.folders.name },
{ name: folderConversationToPublish.conversations[0].name },
'visible',
diff --git a/apps/chat-e2e/src/tests/replay.test.ts b/apps/chat-e2e/src/tests/replay.test.ts
index 4c8e055bf4..5b4e526f90 100644
--- a/apps/chat-e2e/src/tests/replay.test.ts
+++ b/apps/chat-e2e/src/tests/replay.test.ts
@@ -21,7 +21,9 @@ let aModel: DialAIEntityModel;
let bModel: DialAIEntityModel;
dialTest.beforeAll(async () => {
- allModels = ModelsUtil.getModels().filter((m) => m.iconUrl != undefined);
+ allModels = ModelsUtil.getLatestModels().filter(
+ (m) => m.iconUrl != undefined,
+ );
defaultModel = ModelsUtil.getDefaultModel()!;
aModel = GeneratorUtil.randomArrayElement(
allModels.filter((m) => m.id !== defaultModel.id),
@@ -595,6 +597,7 @@ dialTest(
chatMessages,
setTestIds,
conversationDropdownMenu,
+ renameConversationModal,
}) => {
setTestIds('EPMRTC-505', 'EPMRTC-506', 'EPMRTC-515', 'EPMRTC-516');
let conversation: Conversation;
@@ -604,7 +607,6 @@ dialTest(
'Prepare conversation to replay with updated name',
async () => {
conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
['1+2'],
);
replayConversation =
@@ -626,7 +628,7 @@ dialTest(
await conversations.openEntityDropdownMenu(replayConversation.name);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
replayConversation.name = GeneratorUtil.randomString(7);
- await conversations.editConversationNameWithTick(
+ await renameConversationModal.editConversationNameWithSaveButton(
replayConversation.name,
);
diff --git a/apps/chat-e2e/src/tests/scrolling.test.ts b/apps/chat-e2e/src/tests/scrolling.test.ts
index c6e40194e3..f11e5c7aa5 100644
--- a/apps/chat-e2e/src/tests/scrolling.test.ts
+++ b/apps/chat-e2e/src/tests/scrolling.test.ts
@@ -37,10 +37,9 @@ dialTest(
let conversation: Conversation;
await dialTest.step('Prepare conversation with long response', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [GeneratorUtil.randomString(3000)],
- );
+ conversation = conversationData.prepareModelConversationBasedOnRequests([
+ GeneratorUtil.randomString(3000),
+ ]);
await dataInjector.createConversations([conversation]);
});
@@ -146,10 +145,9 @@ dialTest(
let conversation: Conversation;
await dialTest.step('Prepare conversation with long response', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [GeneratorUtil.randomString(3000)],
- );
+ conversation = conversationData.prepareModelConversationBasedOnRequests([
+ GeneratorUtil.randomString(3000),
+ ]);
await dataInjector.createConversations([conversation]);
});
@@ -203,16 +201,14 @@ dialTest(
'Prepare two conversations with long responses',
async () => {
firstConversation =
- conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [GeneratorUtil.randomString(3000)],
- );
+ conversationData.prepareModelConversationBasedOnRequests([
+ GeneratorUtil.randomString(3000),
+ ]);
conversationData.resetData();
secondConversation =
- conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- [GeneratorUtil.randomString(3000)],
- );
+ conversationData.prepareModelConversationBasedOnRequests([
+ GeneratorUtil.randomString(3000),
+ ]);
await dataInjector.createConversations([
firstConversation,
secondConversation,
@@ -335,21 +331,21 @@ dialTest(
async () => {
firstConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[
GeneratorUtil.randomString(2000),
GeneratorUtil.randomString(2000),
],
+ defaultModel,
firstConversationName,
);
conversationData.resetData();
secondConversation =
conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
[
GeneratorUtil.randomString(2000),
GeneratorUtil.randomString(2000),
],
+ defaultModel,
secondConversationName,
);
await dataInjector.createConversations([
@@ -519,10 +515,10 @@ dialTest(
await dialTest.step(
'Prepare conversation with 3 long requests',
async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- userRequests,
- );
+ conversation =
+ conversationData.prepareModelConversationBasedOnRequests(
+ userRequests,
+ );
await dataInjector.createConversations([conversation]);
},
);
diff --git a/apps/chat-e2e/src/tests/selectUploadFolder.test.ts b/apps/chat-e2e/src/tests/selectUploadFolder.test.ts
index 14ded2c9d4..62942374b6 100644
--- a/apps/chat-e2e/src/tests/selectUploadFolder.test.ts
+++ b/apps/chat-e2e/src/tests/selectUploadFolder.test.ts
@@ -91,7 +91,7 @@ dialTest(
await dialTest.step(
'Select created folder and verify correct path is displayed in "Upload to" field, the field is highlighted and has text_overflow=ellipsis property',
async () => {
- await selectFolderModal.selectFolder(updatedFolderName, {
+ await selectFolderModal.selectFolder(updatedFolderName, 1, {
triggeredApiHost: API.listingHost,
});
await selectFolderModal.selectFolderButton.click();
diff --git a/apps/chat-e2e/src/tests/sharedChatIcons.test.ts b/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
index f92d1a2a9e..7e97c2ce15 100644
--- a/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
+++ b/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
@@ -321,6 +321,7 @@ dialTest(
conversationSettingsModal,
chat,
setTestIds,
+ renameConversationModal,
}) => {
setTestIds(
'EPMRTC-1514',
@@ -402,8 +403,8 @@ dialTest(
secondConversationToShare.name,
);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations.getEditEntityInput().editValue(newName);
- await conversations.getEditInputActions().clickTickButton();
+ await renameConversationModal.editInputValue(newName);
+ await renameConversationModal.saveButton.click();
await confirmationDialogAssertion.assertConfirmationDialogTitle(
ExpectedConstants.renameSharedConversationDialogTitle,
);
diff --git a/apps/chat-e2e/src/tests/sharedFilesAttachments.test.ts b/apps/chat-e2e/src/tests/sharedFilesAttachments.test.ts
index e7d0fb2944..047ab7e1f6 100644
--- a/apps/chat-e2e/src/tests/sharedFilesAttachments.test.ts
+++ b/apps/chat-e2e/src/tests/sharedFilesAttachments.test.ts
@@ -67,6 +67,7 @@ dialSharedWithMeTest(
additionalShareUserFileApiHelper,
errorToast,
additionalShareUserManageAttachmentsAssertion,
+ renameConversationModal,
}) => {
dialSharedWithMeTest.slow();
setTestIds(
@@ -362,10 +363,10 @@ dialSharedWithMeTest(
);
conversationWithTwoResponses.name = GeneratorUtil.randomString(10);
await conversationDropdownMenu.selectMenuOption(MenuOptions.rename);
- await conversations
- .getEditEntityInput()
- .editValue(conversationWithTwoResponses.name);
- await conversations.getEditInputActions().clickTickButton();
+ await renameConversationModal.editInputValue(
+ conversationWithTwoResponses.name,
+ );
+ await renameConversationModal.saveButton.click();
await confirmationDialog.confirm({
triggeredHttpMethod: 'DELETE',
});
@@ -504,6 +505,7 @@ dialSharedWithMeTest(
conversationData,
dataInjector,
fileApiHelper,
+ additionalShareUserFileApiHelper,
mainUserShareApiHelper,
additionalUserShareApiHelper,
additionalShareUserSendMessage,
@@ -580,6 +582,11 @@ dialSharedWithMeTest(
await fileApiHelper.putFile(
user1ConversationInFolderImageInResponse1,
);
+
+ //upload file into 'All files' section to have it visible
+ await additionalShareUserFileApiHelper.putFile(
+ Attachment.heartImageName,
+ );
},
);
diff --git a/apps/chat-e2e/src/tests/unpublishConversation.test.ts b/apps/chat-e2e/src/tests/unpublishConversation.test.ts
index 89995df967..c5d78aa3d0 100644
--- a/apps/chat-e2e/src/tests/unpublishConversation.test.ts
+++ b/apps/chat-e2e/src/tests/unpublishConversation.test.ts
@@ -11,6 +11,7 @@ import {
import { PublicationProps } from '@/src/testData/api';
import { Colors } from '@/src/ui/domData';
import { GeneratorUtil, ModelsUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
dialAdminTest(
'Unpublish single chat without attachments.\n' +
@@ -65,7 +66,7 @@ dialAdminTest(
const publishRequest = publishRequestBuilder
.withName(GeneratorUtil.randomPublicationRequestName())
- .withConversationResource(publishedConversation)
+ .withConversationResource(publishedConversation, PublishActions.ADD)
.build();
const publication =
await publicationApiHelper.createPublishRequest(publishRequest);
diff --git a/apps/chat-e2e/src/tests/unpublishFolderWithConversations.test.ts b/apps/chat-e2e/src/tests/unpublishFolderWithConversations.test.ts
new file mode 100644
index 0000000000..aa6b656e4d
--- /dev/null
+++ b/apps/chat-e2e/src/tests/unpublishFolderWithConversations.test.ts
@@ -0,0 +1,1093 @@
+import { Conversation } from '@/chat/types/chat';
+import { FolderInterface } from '@/chat/types/folder';
+import { Publication, PublicationRequestModel } from '@/chat/types/publication';
+import dialAdminTest from '@/src/core/dialAdminFixtures';
+import dialTest from '@/src/core/dialFixtures';
+import {
+ CheckboxState,
+ ExpectedConstants,
+ FolderConversation,
+ MenuOptions,
+ PublishPath,
+} from '@/src/testData';
+import { PublicationProps } from '@/src/testData/api';
+import { Colors } from '@/src/ui/domData';
+import { GeneratorUtil, ModelsUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
+
+let expectedConversationIcon: string;
+let folderConversationToUnpublish: Conversation;
+
+dialTest.beforeAll(async ({ iconApiHelper }) => {
+ const defaultModel = ModelsUtil.getDefaultModel()!;
+ expectedConversationIcon = iconApiHelper.getEntityIcon(defaultModel);
+});
+
+dialAdminTest(
+ 'Unpublish chat inside folder.\n' +
+ 'Unpublish request for folder structure where one chat was already unpublished.\n' +
+ 'Unpublish all chats from folder: folder is deleted from Organization',
+ async ({
+ dialHomePage,
+ conversationData,
+ publishRequestBuilder,
+ publicationApiHelper,
+ adminPublicationApiHelper,
+ dataInjector,
+ organizationFolderConversations,
+ conversationDropdownMenu,
+ publishingRequestModal,
+ conversationToPublishAssertion,
+ baseAssertion,
+ publishingRules,
+ publishingRequestModalAssertion,
+ adminDialHomePage,
+ adminApproveRequiredConversations,
+ adminPublishingApprovalModal,
+ adminPublicationReviewControl,
+ adminOrganizationFolderConversations,
+ adminApproveRequiredConversationsAssertion,
+ adminOrganizationFolderConversationAssertions,
+ adminPublishingApprovalModalAssertion,
+ adminFolderToApproveAssertion,
+ organizationFolderConversationAssertions,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-3386', 'EPMRTC-3802', 'EPMRTC-3389');
+ let firstConversation: Conversation;
+ let secondConversation: Conversation;
+ let conversationsFolder: FolderConversation;
+ let publishedFolderName: string;
+ const firstConversationUnpublishingRequestName =
+ GeneratorUtil.randomUnpublishRequestName();
+ const secondConversationUnpublishingRequestName =
+ GeneratorUtil.randomUnpublishRequestName();
+ let firstUnpublishApiModels: {
+ request: PublicationRequestModel;
+ response: Publication;
+ };
+ let folderPublicationRequest: Publication;
+ let publishPath: string;
+ let unpublishFolderResponse: PublicationProps;
+
+ await dialTest.step(
+ 'Create and approve publishing of folder with 2 conversations inside',
+ async () => {
+ firstConversation = conversationData.prepareDefaultConversation();
+ conversationData.resetData();
+ secondConversation = conversationData.prepareDefaultConversation();
+ conversationData.resetData();
+ conversationsFolder = conversationData.prepareConversationsInFolder([
+ firstConversation,
+ secondConversation,
+ ]);
+ await dataInjector.createConversations(
+ conversationsFolder.conversations,
+ conversationsFolder.folders,
+ );
+ publishedFolderName = conversationsFolder.folders.name;
+ publishPath = `${PublishPath.Organization}/${publishedFolderName}`;
+
+ const publishRequest = publishRequestBuilder
+ .withName(GeneratorUtil.randomPublicationRequestName())
+ .withConversationResource(firstConversation, PublishActions.ADD)
+ .withConversationResource(secondConversation, PublishActions.ADD)
+ .build();
+ folderPublicationRequest =
+ await publicationApiHelper.createPublishRequest(publishRequest);
+ await adminPublicationApiHelper.approveRequest(
+ folderPublicationRequest,
+ );
+ },
+ );
+
+ await dialTest.step(
+ 'Select "Unpublish" menu option for the 1st conversation and verify "Publish request" modal is opened',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversations.expandFolder(publishedFolderName);
+ await organizationFolderConversations.openFolderEntityDropdownMenu(
+ publishedFolderName,
+ firstConversation.name,
+ );
+ await conversationDropdownMenu.selectMenuOption(MenuOptions.unpublish);
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'visible',
+ );
+ await baseAssertion.assertElementText(
+ publishingRequestModal.getChangePublishToPath().path,
+ publishPath,
+ );
+
+ await conversationToPublishAssertion.assertEntityState(
+ { name: firstConversation.name },
+ 'visible',
+ );
+ await conversationToPublishAssertion.assertEntityColor(
+ { name: firstConversation.name },
+ Colors.textError,
+ );
+ await conversationToPublishAssertion.assertEntityCheckboxState(
+ { name: firstConversation.name },
+ CheckboxState.checked,
+ );
+
+ await conversationToPublishAssertion.assertEntityVersion(
+ { name: firstConversation.name },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await conversationToPublishAssertion.assertEntityVersionColor(
+ { name: firstConversation.name },
+ Colors.textError,
+ );
+ await conversationToPublishAssertion.assertTreeEntityIcon(
+ { name: firstConversation.name },
+ expectedConversationIcon,
+ );
+
+ await conversationToPublishAssertion.assertEntityState(
+ { name: secondConversation.name },
+ 'hidden',
+ );
+
+ await baseAssertion.assertElementText(
+ publishingRules.publishingPath,
+ publishedFolderName,
+ );
+ await baseAssertion.assertElementState(
+ publishingRules.addRuleButton,
+ 'visible',
+ );
+ await baseAssertion.assertElementsCount(publishingRules.allRules, 0);
+ },
+ );
+
+ await dialTest.step('Set a valid request name and submit', async () => {
+ await publishingRequestModal.requestName.fillInInput(
+ firstConversationUnpublishingRequestName,
+ );
+ firstUnpublishApiModels =
+ await publishingRequestModal.sendPublicationRequest();
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'hidden',
+ );
+ });
+
+ await dialTest.step(
+ 'Create unpublish request for the 2nd folder conversation',
+ async () => {
+ await organizationFolderConversations.openFolderEntityDropdownMenu(
+ publishedFolderName,
+ secondConversation.name,
+ );
+ await conversationDropdownMenu.selectMenuOption(MenuOptions.unpublish);
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'visible',
+ );
+ await publishingRequestModal.requestName.fillInInput(
+ secondConversationUnpublishingRequestName,
+ );
+ await publishingRequestModal.sendPublicationRequest();
+ },
+ );
+
+ await dialTest.step(
+ 'Create one more unpublish request for the whole folder',
+ async () => {
+ const unpublishFolderRequestModel = publishRequestBuilder
+ .withConversationResource(firstConversation, PublishActions.DELETE)
+ .withConversationResource(secondConversation, PublishActions.DELETE)
+ .build();
+ unpublishFolderResponse =
+ await publicationApiHelper.createUnpublishRequest(
+ unpublishFolderRequestModel,
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Login as admin and verify conversation unpublishing request is displayed under "Approve required" section',
+ async () => {
+ await adminDialHomePage.openHomePage();
+ await adminDialHomePage.waitForPageLoaded();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: firstConversationUnpublishingRequestName },
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Expand request folder and verify "Publication approval" modal is displayed',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ firstConversationUnpublishingRequestName,
+ );
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ publishedFolderName,
+ { isHttpMethodTriggered: false },
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityState(
+ { name: firstConversationUnpublishingRequestName },
+ { name: firstConversation.name },
+ 'visible',
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityColor(
+ { name: firstConversationUnpublishingRequestName },
+ { name: firstConversation.name },
+ Colors.textError,
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityState(
+ { name: firstConversationUnpublishingRequestName },
+ { name: secondConversation.name },
+ 'hidden',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementState(
+ adminPublishingApprovalModal,
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Verify only 1t conversation is displayed on "Publication approval" modal',
+ async () => {
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.publishToPath,
+ publishPath,
+ );
+ await adminPublishingApprovalModalAssertion.assertRequestCreationDate(
+ firstUnpublishApiModels.response,
+ );
+ await adminPublishingApprovalModalAssertion.assertAvailabilityLabelState(
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersion(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersionColor(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityIcon(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ expectedConversationIcon,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: secondConversation.name },
+ 'hidden',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admins reviews and approves the request and verify publication disappears from "Approve required", only one conversation left in "Organization" section',
+ async () => {
+ await adminPublishingApprovalModal.goToEntityReview();
+ await adminPublicationReviewControl.backToPublicationRequest();
+ await adminPublishingApprovalModal.approveRequest();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: firstConversationUnpublishingRequestName },
+ 'hidden',
+ );
+ await adminOrganizationFolderConversations.expandFolder(
+ publishedFolderName,
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ 'hidden',
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: secondConversation.name },
+ 'visible',
+ );
+
+ await dialHomePage.reloadPage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversations.expandFolder(publishedFolderName);
+ await organizationFolderConversationAssertions.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ 'hidden',
+ );
+ await organizationFolderConversationAssertions.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: secondConversation.name },
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admin expands the folder unpublishing request and verify error message is displayed instead of "Go to a review" link',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ unpublishFolderResponse.name!,
+ );
+ await adminPublishingApprovalModalAssertion.assertElementActionabilityState(
+ adminPublishingApprovalModal.approveButton,
+ 'disabled',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementState(
+ adminPublishingApprovalModal.goToReviewButton,
+ 'hidden',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.duplicatedUnpublishingError,
+ ExpectedConstants.duplicatedUnpublishingError(firstConversation.name),
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Verify unpublished conversation is marked as grey under the request and on side panel',
+ async () => {
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ Colors.controlsBackgroundDisable,
+ );
+
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: firstConversation.name },
+ Colors.controlsBackgroundDisable,
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admins reviews and approves the second request and verifies published folder disappears from "Organization" section',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ secondConversationUnpublishingRequestName,
+ );
+ await adminPublishingApprovalModal.goToEntityReview();
+ await adminPublicationReviewControl.backToPublicationRequest();
+ await adminPublishingApprovalModal.approveRequest();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: secondConversationUnpublishingRequestName },
+ 'hidden',
+ );
+ //TODO: enable when the issue is fixed https://github.com/epam/ai-dial-chat/issues/2727
+ // await adminOrganizationFolderConversationAssertions.assertFolderState(
+ // { name: publishedFolderName },
+ // 'hidden',
+ // );
+
+ await dialHomePage.reloadPage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversationAssertions.assertFolderState(
+ { name: publishedFolderName },
+ 'hidden',
+ );
+ },
+ );
+ },
+);
+
+dialAdminTest(
+ 'Unpublish request for folder with more than one chat.\n' +
+ '2 Unpublish requests for folder structure.\n' +
+ 'Admin review 2 Unpublish requests for chat from folder',
+ async ({
+ dialHomePage,
+ conversationData,
+ publishRequestBuilder,
+ publicationApiHelper,
+ adminPublicationApiHelper,
+ dataInjector,
+ organizationFolderConversations,
+ conversationDropdownMenu,
+ publishingRequestModal,
+ conversationToPublishAssertion,
+ publishingRequestModalAssertion,
+ adminDialHomePage,
+ adminApproveRequiredConversations,
+ adminPublishingApprovalModal,
+ baseAssertion,
+ adminPublicationReviewControl,
+ adminApproveRequiredConversationsAssertion,
+ adminOrganizationFolderConversationAssertions,
+ adminPublishingApprovalModalAssertion,
+ adminFolderToApproveAssertion,
+ organizationFolderConversationAssertions,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-3429', 'EPMRTC-3800', 'EPMRTC-3801');
+ let firstConversation: Conversation;
+ let secondConversation: Conversation;
+ let conversationsFolder: FolderConversation;
+ let publishedFolderName: string;
+ const firstFolderUnpublishingRequestName =
+ GeneratorUtil.randomUnpublishRequestName();
+ const secondFolderUnpublishingRequestName =
+ GeneratorUtil.randomUnpublishRequestName();
+ let firstUnpublishApiModels: {
+ request: PublicationRequestModel;
+ response: Publication;
+ };
+ let folderPublicationRequest: Publication;
+ let folderConversations: string[];
+
+ await dialTest.step(
+ 'Create and approve publishing of folder with 2 conversations inside',
+ async () => {
+ firstConversation = conversationData.prepareDefaultConversation();
+ conversationData.resetData();
+ secondConversation = conversationData.prepareDefaultConversation();
+ conversationData.resetData();
+ conversationsFolder = conversationData.prepareConversationsInFolder([
+ firstConversation,
+ secondConversation,
+ ]);
+ await dataInjector.createConversations(
+ conversationsFolder.conversations,
+ conversationsFolder.folders,
+ );
+ publishedFolderName = conversationsFolder.folders.name;
+
+ const publishRequest = publishRequestBuilder
+ .withName(GeneratorUtil.randomPublicationRequestName())
+ .withConversationResource(firstConversation, PublishActions.ADD)
+ .withConversationResource(secondConversation, PublishActions.ADD)
+ .build();
+ folderPublicationRequest =
+ await publicationApiHelper.createPublishRequest(publishRequest);
+ await adminPublicationApiHelper.approveRequest(
+ folderPublicationRequest,
+ );
+ folderConversations = [firstConversation.name, secondConversation.name];
+ },
+ );
+
+ await dialTest.step(
+ 'Select "Unpublish" menu option for the folder and verify "Publish request" modal is opened',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversations.openFolderDropdownMenu(
+ publishedFolderName,
+ );
+ await conversationDropdownMenu.selectMenuOption(MenuOptions.unpublish);
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'visible',
+ );
+ for (const conversation of folderConversations) {
+ await conversationToPublishAssertion.assertEntityState(
+ { name: conversation },
+ 'visible',
+ );
+ await conversationToPublishAssertion.assertEntityColor(
+ { name: conversation },
+ Colors.textError,
+ );
+ await conversationToPublishAssertion.assertEntityCheckboxState(
+ { name: conversation },
+ CheckboxState.checked,
+ );
+ await conversationToPublishAssertion.assertEntityVersion(
+ { name: conversation },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await conversationToPublishAssertion.assertEntityVersionColor(
+ { name: conversation },
+ Colors.textError,
+ );
+ await conversationToPublishAssertion.assertTreeEntityIcon(
+ { name: conversation },
+ expectedConversationIcon,
+ );
+ }
+ },
+ );
+
+ await dialTest.step('Set a valid request name and submit', async () => {
+ await publishingRequestModal.requestName.fillInInput(
+ firstFolderUnpublishingRequestName,
+ );
+ firstUnpublishApiModels =
+ await publishingRequestModal.sendPublicationRequest();
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'hidden',
+ );
+ });
+
+ await dialTest.step(
+ 'Create one more unpublish request for the folder conversation',
+ async () => {
+ await organizationFolderConversations.openFolderDropdownMenu(
+ publishedFolderName,
+ );
+ await conversationDropdownMenu.selectMenuOption(MenuOptions.unpublish);
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'visible',
+ );
+ await publishingRequestModal.requestName.fillInInput(
+ secondFolderUnpublishingRequestName,
+ );
+ await publishingRequestModal.sendPublicationRequest();
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Login as admin and verify both folder unpublishing requests are displayed under "Approve required" section',
+ async () => {
+ await adminDialHomePage.openHomePage();
+ await adminDialHomePage.waitForPageLoaded();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: firstFolderUnpublishingRequestName },
+ 'visible',
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: secondFolderUnpublishingRequestName },
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Expand the first request folder and verify "Publication approval" modal is displayed',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ firstFolderUnpublishingRequestName,
+ );
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ publishedFolderName,
+ { isHttpMethodTriggered: false },
+ );
+ for (const conversation of folderConversations) {
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityState(
+ { name: firstFolderUnpublishingRequestName },
+ { name: conversation },
+ 'visible',
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityColor(
+ { name: firstFolderUnpublishingRequestName },
+ { name: conversation },
+ Colors.textError,
+ );
+ }
+ await adminPublishingApprovalModalAssertion.assertElementState(
+ adminPublishingApprovalModal,
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Verify both conversations are displayed on "Publication approval" modal',
+ async () => {
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.publishToPath,
+ PublishPath.Organization,
+ );
+ await adminPublishingApprovalModalAssertion.assertRequestCreationDate(
+ firstUnpublishApiModels.response,
+ );
+ await adminPublishingApprovalModalAssertion.assertAvailabilityLabelState(
+ 'visible',
+ );
+ for (const conversation of folderConversations) {
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: conversation },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: conversation },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersion(
+ { name: publishedFolderName },
+ { name: conversation },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersionColor(
+ { name: publishedFolderName },
+ { name: conversation },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityIcon(
+ { name: publishedFolderName },
+ { name: conversation },
+ expectedConversationIcon,
+ );
+ }
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admins reviews the 1st request conversation, back to publication and checks the 1st unpublishing modal is opened',
+ async () => {
+ await adminPublishingApprovalModal.goToEntityReview();
+ await adminPublicationReviewControl.backToPublicationRequest();
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.publishName,
+ firstFolderUnpublishingRequestName,
+ );
+ await adminPublishingApprovalModal.goToEntityReview();
+ await adminPublicationReviewControl.backToPublicationRequest();
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admins approves the 1st request conversation and verifies folder disappears from "Organization" section ',
+ async () => {
+ await adminPublishingApprovalModal.approveRequest();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: firstFolderUnpublishingRequestName },
+ 'hidden',
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderState(
+ { name: publishedFolderName },
+ 'hidden',
+ );
+
+ await dialHomePage.reloadPage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversationAssertions.assertFolderState(
+ { name: publishedFolderName },
+ 'hidden',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admin expands the 2nd folder unpublishing request and verifies error message is displayed instead of "Go to a review" link',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ secondFolderUnpublishingRequestName,
+ );
+ await adminPublishingApprovalModalAssertion.assertElementActionabilityState(
+ adminPublishingApprovalModal.approveButton,
+ 'disabled',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementState(
+ adminPublishingApprovalModal.goToReviewButton,
+ 'hidden',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.duplicatedUnpublishingError,
+ ExpectedConstants.duplicatedUnpublishingError(
+ ...baseAssertion.sortStringsArray(
+ folderConversations,
+ (f) => f.toLowerCase(),
+ 'asc',
+ ),
+ ),
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Verify unpublished conversations are marked as grey under the request and on side panel',
+ async () => {
+ for (const conversation of folderConversations) {
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: conversation },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: conversation },
+ Colors.controlsBackgroundDisable,
+ );
+
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: publishedFolderName },
+ { name: conversation },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: publishedFolderName },
+ { name: conversation },
+ Colors.controlsBackgroundDisable,
+ );
+ }
+ },
+ );
+ },
+);
+
+dialAdminTest(
+ 'Unpublish folder from folder structure',
+ async ({
+ dialHomePage,
+ conversationData,
+ publishRequestBuilder,
+ publicationApiHelper,
+ adminPublicationApiHelper,
+ dataInjector,
+ organizationFolderConversations,
+ conversationDropdownMenu,
+ publishingRequestModal,
+ publishingRules,
+ folderToPublishAssertion,
+ publishingRequestModalAssertion,
+ adminDialHomePage,
+ adminApproveRequiredConversations,
+ adminPublishingApprovalModal,
+ adminPublicationReviewControl,
+ adminApproveRequiredConversationsAssertion,
+ adminOrganizationFolderConversations,
+ adminOrganizationFolderConversationAssertions,
+ adminPublishingApprovalModalAssertion,
+ adminFolderToApproveAssertion,
+ organizationFolderConversationAssertions,
+ setTestIds,
+ }) => {
+ setTestIds('EPMRTC-3808');
+ let nestedFolders: FolderInterface[];
+ let nestedConversations: Conversation[];
+ const nestedFolderLevel = 2;
+ const folderUnpublishingRequestName =
+ GeneratorUtil.randomUnpublishRequestName();
+ let unpublishApiModels: {
+ request: PublicationRequestModel;
+ response: Publication;
+ };
+ let publishPath: string;
+ let rootFolderName: string;
+ let rootFolderConversationName: string;
+ let innerFolderName: string;
+ let innerFolderConversationName: string;
+
+ await dialTest.step(
+ 'Create and approve publishing of 2 nested folders with 2 conversations inside',
+ async () => {
+ nestedFolders = conversationData.prepareNestedFolder(nestedFolderLevel);
+ nestedConversations =
+ conversationData.prepareConversationsForNestedFolders(nestedFolders);
+ await dataInjector.createConversations(
+ nestedConversations,
+ ...nestedFolders,
+ );
+
+ const publishRequest = publishRequestBuilder
+ .withName(GeneratorUtil.randomPublicationRequestName())
+ .withConversationResource(nestedConversations[0], PublishActions.ADD)
+ .withConversationResource(nestedConversations[1], PublishActions.ADD)
+ .build();
+ const folderPublicationRequest =
+ await publicationApiHelper.createPublishRequest(publishRequest);
+ await adminPublicationApiHelper.approveRequest(
+ folderPublicationRequest,
+ );
+
+ rootFolderName = nestedFolders[0].name;
+ rootFolderConversationName = nestedConversations[0].name;
+ innerFolderName = nestedFolders[1].name;
+ innerFolderConversationName = nestedConversations[1].name;
+ publishPath = `${PublishPath.Organization}/${rootFolderName}`;
+ folderConversationToUnpublish = nestedConversations[0];
+ },
+ );
+
+ await dialTest.step(
+ 'Select "Unpublish" menu option for the nested folder and verify "Publish request" modal is opened',
+ async () => {
+ await dialHomePage.openHomePage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversations.expandFolder(rootFolderName);
+ await organizationFolderConversations.openFolderDropdownMenu(
+ innerFolderName,
+ );
+ await conversationDropdownMenu.selectMenuOption(MenuOptions.unpublish);
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'visible',
+ );
+ await folderToPublishAssertion.assertFolderState(
+ { name: innerFolderName },
+ 'visible',
+ );
+ await folderToPublishAssertion.assertFolderEntityState(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ 'visible',
+ );
+ await folderToPublishAssertion.assertFolderState(
+ { name: rootFolderName },
+ 'hidden',
+ );
+ await folderToPublishAssertion.assertFolderEntityColor(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ Colors.textError,
+ );
+ await folderToPublishAssertion.assertFolderCheckboxState(
+ { name: innerFolderName },
+ CheckboxState.checked,
+ );
+ await folderToPublishAssertion.assertFolderEntityCheckboxState(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ CheckboxState.checked,
+ );
+ await folderToPublishAssertion.assertFolderEntityVersion(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await folderToPublishAssertion.assertFolderEntityVersionColor(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ Colors.textError,
+ );
+ await folderToPublishAssertion.assertFolderEntityIcon(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ expectedConversationIcon,
+ );
+ await publishingRequestModalAssertion.assertElementText(
+ publishingRequestModal.getChangePublishToPath().path,
+ publishPath,
+ );
+ await publishingRequestModalAssertion.assertElementText(
+ publishingRules.publishingPath,
+ rootFolderName,
+ );
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRules.addRuleButton,
+ 'visible',
+ );
+ await publishingRequestModalAssertion.assertElementsCount(
+ publishingRules.allRules,
+ 0,
+ );
+ },
+ );
+
+ await dialTest.step('Set a valid request name and submit', async () => {
+ await publishingRequestModal.requestName.fillInInput(
+ folderUnpublishingRequestName,
+ );
+ unpublishApiModels =
+ await publishingRequestModal.sendPublicationRequest();
+ await publishingRequestModalAssertion.assertElementState(
+ publishingRequestModal,
+ 'hidden',
+ );
+ });
+
+ await dialAdminTest.step(
+ 'Login as admin and verify inner folder unpublishing request is displayed under "Approve required" section',
+ async () => {
+ await adminDialHomePage.openHomePage();
+ await adminDialHomePage.waitForPageLoaded();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: folderUnpublishingRequestName },
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Expand the request folder and verify "Publication approval" modal is displayed',
+ async () => {
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ folderUnpublishingRequestName,
+ );
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ rootFolderName,
+ { isHttpMethodTriggered: false },
+ );
+ await adminApproveRequiredConversations.expandApproveRequiredFolder(
+ innerFolderName,
+ { isHttpMethodTriggered: false },
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityState(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ 'visible',
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityColor(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ Colors.textError,
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: rootFolderName },
+ 'visible',
+ );
+ await adminApproveRequiredConversationsAssertion.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: rootFolderConversationName },
+ 'hidden',
+ );
+ await adminPublishingApprovalModalAssertion.assertElementState(
+ adminPublishingApprovalModal,
+ 'visible',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Verify only inner folder with content is displayed on "Publication approval" modal',
+ async () => {
+ await adminPublishingApprovalModalAssertion.assertElementText(
+ adminPublishingApprovalModal.publishToPath,
+ publishPath,
+ );
+ await adminPublishingApprovalModalAssertion.assertRequestCreationDate(
+ unpublishApiModels.response,
+ );
+ await adminPublishingApprovalModalAssertion.assertAvailabilityLabelState(
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderState(
+ { name: innerFolderName },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityColor(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersion(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ ExpectedConstants.defaultAppVersion,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityVersionColor(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ Colors.textError,
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityIcon(
+ { name: innerFolderName },
+ { name: innerFolderConversationName },
+ expectedConversationIcon,
+ );
+ await adminFolderToApproveAssertion.assertFolderState(
+ { name: rootFolderName },
+ 'visible',
+ );
+ await adminFolderToApproveAssertion.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: rootFolderConversationName },
+ 'hidden',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Admins reviews and approves the request conversation and checks only root folder with content is displayed in the "Organization" section',
+ async () => {
+ await adminPublishingApprovalModal.goToEntityReview();
+ await adminPublicationReviewControl.backToPublicationRequest();
+ await adminPublishingApprovalModal.approveRequest();
+ await adminApproveRequiredConversationsAssertion.assertFolderState(
+ { name: folderUnpublishingRequestName },
+ 'hidden',
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderState(
+ { name: rootFolderName },
+ 'visible',
+ );
+ await adminOrganizationFolderConversations.expandFolder(rootFolderName);
+ await adminOrganizationFolderConversationAssertions.assertFolderState(
+ { name: innerFolderName },
+ 'hidden',
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: rootFolderConversationName },
+ 'visible',
+ );
+ await adminOrganizationFolderConversationAssertions.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: innerFolderConversationName },
+ 'hidden',
+ );
+ },
+ );
+
+ await dialAdminTest.step(
+ 'Main user refreshes the page and verifies only root folder remains in the "Organization" section',
+ async () => {
+ await dialHomePage.reloadPage();
+ await dialHomePage.waitForPageLoaded();
+ await organizationFolderConversationAssertions.assertFolderState(
+ { name: rootFolderName },
+ 'visible',
+ );
+ await organizationFolderConversations.expandFolder(rootFolderName);
+ await organizationFolderConversationAssertions.assertFolderState(
+ { name: innerFolderName },
+ 'hidden',
+ );
+ await organizationFolderConversationAssertions.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: rootFolderConversationName },
+ 'visible',
+ );
+ await organizationFolderConversationAssertions.assertFolderEntityState(
+ { name: rootFolderName },
+ { name: innerFolderConversationName },
+ 'hidden',
+ );
+ },
+ );
+ },
+);
+
+dialTest.afterAll(
+ async ({
+ publicationApiHelper,
+ adminPublicationApiHelper,
+ publishRequestBuilder,
+ }) => {
+ const publishRequest = publishRequestBuilder
+ .withConversationResource(
+ folderConversationToUnpublish,
+ PublishActions.DELETE,
+ )
+ .build();
+ const folderPublicationRequest =
+ await publicationApiHelper.createUnpublishRequest(publishRequest);
+ await adminPublicationApiHelper.approveRequest(folderPublicationRequest);
+ },
+);
diff --git a/apps/chat-e2e/src/tests/workWithModels.test.ts b/apps/chat-e2e/src/tests/workWithModels.test.ts
index cdb6d96c2f..e213899152 100644
--- a/apps/chat-e2e/src/tests/workWithModels.test.ts
+++ b/apps/chat-e2e/src/tests/workWithModels.test.ts
@@ -21,11 +21,9 @@ const requestTerm = 'qwer';
const request = 'write cinderella story';
const expectedResponse = 'The sky is blue.';
const promptContent = `Type: "${expectedResponse}" if user types ${requestTerm}`;
-let defaultModel: DialAIEntityModel;
let simpleRequestModel: DialAIEntityModel | undefined;
dialTest.beforeAll(async () => {
- defaultModel = ModelsUtil.getDefaultModel()!;
simpleRequestModel = ModelsUtil.getModelForSimpleRequest();
});
@@ -47,10 +45,8 @@ dialTest(
'write down 100 adjectives',
];
await dialTest.step('Prepare model conversation', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- userRequests,
- );
+ conversation =
+ conversationData.prepareModelConversationBasedOnRequests(userRequests);
await dataInjector.createConversations([conversation]);
});
@@ -192,10 +188,8 @@ dialTest(
let conversation: Conversation;
const userRequests = ['1+2=', '2+3=', '3+4='];
await dialTest.step('Prepare conversation with 3 requests', async () => {
- conversation = conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- userRequests,
- );
+ conversation =
+ conversationData.prepareModelConversationBasedOnRequests(userRequests);
await dataInjector.createConversations([conversation]);
});
@@ -283,10 +277,7 @@ dialTest(
}) => {
setTestIds('EPMRTC-488', 'EPMRTC-489');
const conversation =
- conversationData.prepareModelConversationBasedOnRequests(
- defaultModel,
- userRequests,
- );
+ conversationData.prepareModelConversationBasedOnRequests(userRequests);
await dialTest.step('Prepare conversation with 3 requests', async () => {
await dataInjector.createConversations([conversation]);
});
diff --git a/apps/chat-e2e/src/ui/domData/colors.ts b/apps/chat-e2e/src/ui/domData/colors.ts
index 1efcb0d9d3..201ebc2617 100644
--- a/apps/chat-e2e/src/ui/domData/colors.ts
+++ b/apps/chat-e2e/src/ui/domData/colors.ts
@@ -40,3 +40,7 @@ export function removeAlpha(color: string): string {
export const ColorsWithoutAlpha = Object.fromEntries(
Object.entries(Colors).map(([key, value]) => [key, removeAlpha(value)]),
) as typeof Colors;
+
+export enum ThemeColorAttributes {
+ textAccentTertiary = 'text-accent-tertiary',
+}
diff --git a/apps/chat-e2e/src/ui/selectors/chatSelectors.ts b/apps/chat-e2e/src/ui/selectors/chatSelectors.ts
index e3e479cc69..2dd0364e64 100644
--- a/apps/chat-e2e/src/ui/selectors/chatSelectors.ts
+++ b/apps/chat-e2e/src/ui/selectors/chatSelectors.ts
@@ -155,3 +155,18 @@ export const PublicationReviewControls = {
nextButton: '[data-qa="next-chat-review-button"]',
backToPublication: '[data-qa="back-to-publication"]',
};
+
+export const RenameConversationModalSelectors = {
+ modal: '[data-qa="rename-conversation-modal"]',
+ saveButton: '[data-qa="save"]',
+ cancelButton: '[data-qa="cancel"]',
+ title: '[data-qa="title"]',
+};
+
+export const PublishingRulesSelectors = {
+ rulesContainer: '[data-qa="rules-container"]',
+ path: '[data-qa="published-path"]',
+ rulesList: '[data-qa="rules-list"]',
+ rule: '[data-qa="rule"]',
+ addRuleButton: '[data-qa="add-rule"]',
+};
diff --git a/apps/chat-e2e/src/ui/selectors/dialogSelectors.ts b/apps/chat-e2e/src/ui/selectors/dialogSelectors.ts
index 29247bfe5a..22aed06b6f 100644
--- a/apps/chat-e2e/src/ui/selectors/dialogSelectors.ts
+++ b/apps/chat-e2e/src/ui/selectors/dialogSelectors.ts
@@ -197,3 +197,21 @@ export const TalkToAgentDialogSelectors = {
searchAgent: '[data-qa="search-agents"]',
goToMyWorkspaceButton: '[data-qa="go-to-my-workspace"]',
};
+
+export const MessageTemplateModalSelectors = {
+ messageTemplateModal: '[data-qa="message-templates-dialog"]',
+ modalTitle: '[data-qa="modal-entity-name"]',
+ description: '[data-qa="description"]',
+ originalMessageLabel: '[data-qa="original-message-label"]',
+ setTemplateTab: '[data-qa="set-template-tab"]',
+ previewTab: '[data-qa="preview-tab"]',
+ originalMessageContent: '[data-qa="original-message-content"]',
+ templateRow: '[data-qa="template-row"]',
+ templateRowContent: '[data-qa="template-content"]',
+ templateRowValue: '[data-qa="template-value"]',
+ deleteRow: '[name="delete-row"]',
+ saveButton: '[data-qa="save-button"]',
+ templatePreview: '[data-qa="result-message-template"]',
+ showMoreButton: '[data-qa="show-more"]',
+ showLessButton: '[data-qa="show-less"]',
+};
diff --git a/apps/chat-e2e/src/ui/webElements/attachFilesModal.ts b/apps/chat-e2e/src/ui/webElements/attachFilesModal.ts
index 3ebe32ce87..d0472b2335 100644
--- a/apps/chat-e2e/src/ui/webElements/attachFilesModal.ts
+++ b/apps/chat-e2e/src/ui/webElements/attachFilesModal.ts
@@ -163,6 +163,11 @@ export class AttachFilesModal extends BaseElement {
SelectFolderModalSelectors.newFolderButton,
);
+ public getFilesSection = (sectionElement: BaseElement) =>
+ sectionElement
+ .getChildElementBySelector(AttachFilesModalSelectors.fileSection)
+ .getElementLocator();
+
public closeButton = this.getChildElementBySelector(IconSelectors.cancelIcon);
public async checkAttachedFile(
@@ -209,12 +214,4 @@ export class AttachFilesModal extends BaseElement {
ErrorLabelSelectors.errorText,
).getElementContent();
}
-
- public async isSectionExpanded(
- sectionElement: BaseElement,
- ): Promise {
- return sectionElement
- .getChildElementBySelector(AttachFilesModalSelectors.fileSection)
- .isVisible();
- }
}
diff --git a/apps/chat-e2e/src/ui/webElements/chatMessages.ts b/apps/chat-e2e/src/ui/webElements/chatMessages.ts
index 9a14c0c308..debb781091 100644
--- a/apps/chat-e2e/src/ui/webElements/chatMessages.ts
+++ b/apps/chat-e2e/src/ui/webElements/chatMessages.ts
@@ -521,10 +521,15 @@ export class ChatMessages extends BaseElement {
await editIcon.click();
}
- public async waitForEditMessageIcon(message: string | number) {
+ public async hoverOverMessage(message: string | number) {
const chatMessage = this.getChatMessage(message);
await chatMessage.scrollIntoViewIfNeeded();
await chatMessage.hover();
+ return chatMessage;
+ }
+
+ public async waitForEditMessageIcon(message: string | number) {
+ const chatMessage = await this.hoverOverMessage(message);
const editIcon = this.messageEditIcon(chatMessage);
await editIcon.waitFor();
return editIcon;
@@ -570,4 +575,9 @@ export class ChatMessages extends BaseElement {
await this.waitForResponseReceived();
}
}
+
+ public async openMessageTemplateModal(message: string | number) {
+ const chatMessage = await this.hoverOverMessage(message);
+ await this.setMessageTemplateIcon(chatMessage).click();
+ }
}
diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/folders.ts b/apps/chat-e2e/src/ui/webElements/entityTree/folders.ts
index 1939da79c6..a65fbca58c 100644
--- a/apps/chat-e2e/src/ui/webElements/entityTree/folders.ts
+++ b/apps/chat-e2e/src/ui/webElements/entityTree/folders.ts
@@ -314,6 +314,37 @@ export class Folders extends BaseElement {
.nth(entityIndex ? entityIndex - 1 : 0);
}
+ public getFolderEntityNameElement(
+ folderName: string,
+ entityName: string,
+ folderIndex?: number,
+ entityIndex?: number,
+ ) {
+ return this.createElementFromLocator(
+ this.getFolderEntity(
+ folderName,
+ entityName,
+ folderIndex,
+ entityIndex,
+ ).locator(EntitySelectors.entityName),
+ );
+ }
+
+ getFolderEntityIcon(
+ folderName: string,
+ entityName: string,
+ folderIndex?: number,
+ entityIndex?: number,
+ ) {
+ const folderEntity = this.getFolderEntity(
+ folderName,
+ entityName,
+ folderIndex,
+ entityIndex,
+ );
+ return this.getElementIcon(folderEntity);
+ }
+
public folderEntityDotsMenu = (folderName: string, entityName: string) => {
return this.getFolderEntity(folderName, entityName).locator(
MenuSelectors.dotsMenu,
@@ -400,13 +431,11 @@ export class Folders extends BaseElement {
folderIndex?: number,
entityIndex?: number,
) {
- const folderEntityColor = await this.createElementFromLocator(
- this.getFolderEntity(
- folderName,
- entityName,
- folderIndex,
- entityIndex,
- ).locator(EntitySelectors.entityName),
+ const folderEntityColor = await this.getFolderEntityNameElement(
+ folderName,
+ entityName,
+ folderIndex,
+ entityIndex,
).getComputedStyleProperty(Styles.color);
return folderEntityColor[0];
}
diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/publishFolder.ts b/apps/chat-e2e/src/ui/webElements/entityTree/publishFolder.ts
index a2787c57d6..18de241617 100644
--- a/apps/chat-e2e/src/ui/webElements/entityTree/publishFolder.ts
+++ b/apps/chat-e2e/src/ui/webElements/entityTree/publishFolder.ts
@@ -13,8 +13,24 @@ export class PublishFolder extends Folders {
entityName,
folderIndex,
entityIndex,
- )
- .locator('~*')
- .locator(PublishEntitySelectors.version);
+ ).locator(
+ `~*${PublishEntitySelectors.version}, ~* > ${PublishEntitySelectors.version}`,
+ );
+ }
+
+ public getFolderEntityVersionElement(
+ folderName: string,
+ entityName: string,
+ folderIndex?: number,
+ entityIndex?: number,
+ ) {
+ return this.createElementFromLocator(
+ this.getFolderEntityVersion(
+ folderName,
+ entityName,
+ folderIndex,
+ entityIndex,
+ ),
+ );
}
}
diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/baseSideBarConversationTree.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/baseSideBarConversationTree.ts
index 9dd0606a57..1c2c81fc2b 100644
--- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/baseSideBarConversationTree.ts
+++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/baseSideBarConversationTree.ts
@@ -1,5 +1,4 @@
import { isApiStorageType } from '@/src/hooks/global-setup';
-import { keys } from '@/src/ui/keyboard';
import { ChatBarSelectors } from '@/src/ui/selectors';
import { SideBarEntitiesTree } from '@/src/ui/webElements/entityTree/sidebar/sideBarEntitiesTree';
@@ -25,32 +24,4 @@ export class BaseSideBarConversationTree extends SideBarEntitiesTree {
ChatBarSelectors.selectedEntity,
);
}
-
- public async editConversationNameWithTick(
- newName: string,
- { isHttpMethodTriggered = true }: { isHttpMethodTriggered?: boolean } = {},
- ) {
- await this.openEditEntityNameMode(newName);
- const editInputActions = this.getEditInputActions();
- if (isApiStorageType && isHttpMethodTriggered) {
- const respPromise = this.page.waitForResponse(
- (resp) => resp.request().method() === 'DELETE',
- );
- await editInputActions.clickTickButton();
- return respPromise;
- }
- await editInputActions.clickTickButton();
- }
-
- public async editConversationNameWithEnter(newName: string) {
- await this.openEditEntityNameMode(newName);
- if (isApiStorageType) {
- const respPromise = this.page.waitForResponse(
- (resp) => resp.request().method() === 'DELETE',
- );
- await this.page.keyboard.press(keys.enter);
- return respPromise;
- }
- await this.page.keyboard.press(keys.enter);
- }
}
diff --git a/apps/chat-e2e/src/ui/webElements/index.ts b/apps/chat-e2e/src/ui/webElements/index.ts
index 6f030e7650..d83f30d15e 100644
--- a/apps/chat-e2e/src/ui/webElements/index.ts
+++ b/apps/chat-e2e/src/ui/webElements/index.ts
@@ -45,6 +45,7 @@ export * from './publishingRequestModal';
export * from './changePath';
export * from './publishingApprovalModal';
export * from './publicationReviewControl';
+export * from './publishingRules';
export * from './baseLayoutContainer';
export * from './marketplace/agentDetailsModal';
export * from './marketplace/marketplaceAgents';
@@ -54,3 +55,4 @@ export * from './marketplace/marketplaceFilter';
export * from './marketplace/marketplaceHeader';
export * from './marketplace/marketplaceSidebar';
export * from './talkToAgentDialog';
+export * from './messageTemplateModal';
diff --git a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
index 708018c06c..c87abd74a7 100644
--- a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
+++ b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
@@ -38,9 +38,7 @@ export class MarketplaceAgents extends BaseElement {
public getAgent = (entity: DialAIEntityModel | string) => {
let agent;
if (typeof entity === 'string') {
- agent = agent = this.rootLocator
- .filter({ has: this.agentName(entity) })
- .first();
+ agent = this.rootLocator.filter({ has: this.agentName(entity) }).first();
} else {
//if agent has version in the config
if (entity.version) {
diff --git a/apps/chat-e2e/src/ui/webElements/messageTemplateModal.ts b/apps/chat-e2e/src/ui/webElements/messageTemplateModal.ts
new file mode 100644
index 0000000000..9ac0c6b067
--- /dev/null
+++ b/apps/chat-e2e/src/ui/webElements/messageTemplateModal.ts
@@ -0,0 +1,98 @@
+import { Tags } from '@/src/ui/domData';
+import {
+ ErrorLabelSelectors,
+ IconSelectors,
+ MessageTemplateModalSelectors,
+} from '@/src/ui/selectors';
+import { BaseElement } from '@/src/ui/webElements/baseElement';
+import { Locator, Page } from '@playwright/test';
+
+export class MessageTemplateModal extends BaseElement {
+ constructor(page: Page) {
+ super(page, MessageTemplateModalSelectors.messageTemplateModal);
+ }
+
+ public title = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.modalTitle,
+ );
+ public description = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.description,
+ );
+ public originalMessageLabel = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.originalMessageLabel,
+ );
+ public setTemplateTab = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.setTemplateTab,
+ );
+ public previewTab = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.previewTab,
+ );
+ public originalMessageContent = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.originalMessageContent,
+ );
+ public templateRows = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.templateRow,
+ );
+ public templatePreview = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.templatePreview,
+ );
+ public templatePreviewVar = (variable: string) =>
+ this.templatePreview
+ .getChildElementBySelector(Tags.span)
+ .getElementLocatorByText(variable);
+ public saveTemplate = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.saveButton,
+ );
+ public showMoreButton = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.showMoreButton,
+ );
+ public showLessButton = this.getChildElementBySelector(
+ MessageTemplateModalSelectors.showLessButton,
+ );
+ cancelButton = this.getChildElementBySelector(IconSelectors.cancelIcon);
+ public getFieldBottomMessage = (field: Locator) =>
+ field.locator(`~${ErrorLabelSelectors.fieldError}`);
+
+ public getTemplateRowContent = (rowContent: string | number) => {
+ if (typeof rowContent === 'string') {
+ return new BaseElement(
+ this.page,
+ `${MessageTemplateModalSelectors.templateRowContent}:text-is('${rowContent}')`,
+ ).getElementLocator();
+ } else {
+ return this.getChildElementBySelector(
+ MessageTemplateModalSelectors.templateRowContent,
+ ).getNthElement(rowContent);
+ }
+ };
+
+ public getTemplateRow = (rowContent: string | number) => {
+ if (typeof rowContent === 'string') {
+ return this.rootLocator
+ .filter({ has: this.getTemplateRowContent(rowContent) })
+ .first();
+ } else {
+ return this.templateRows.getNthElement(rowContent);
+ }
+ };
+
+ public getTemplateRowValue = (rowContent: string | number) => {
+ return this.getTemplateRow(rowContent).locator(
+ MessageTemplateModalSelectors.templateRowValue,
+ );
+ };
+
+ public getTemplateRowDeleteButton = (rowContent: string | number) => {
+ return this.getTemplateRow(rowContent).locator(
+ MessageTemplateModalSelectors.deleteRow,
+ );
+ };
+
+ public async saveChanges() {
+ const responsePromise = this.page.waitForResponse(
+ (r) => r.request().method() === 'PUT',
+ );
+ await this.saveTemplate.click();
+ await responsePromise;
+ }
+}
diff --git a/apps/chat-e2e/src/ui/webElements/publishingRequestModal.ts b/apps/chat-e2e/src/ui/webElements/publishingRequestModal.ts
index 60d0602732..ce44542709 100644
--- a/apps/chat-e2e/src/ui/webElements/publishingRequestModal.ts
+++ b/apps/chat-e2e/src/ui/webElements/publishingRequestModal.ts
@@ -14,6 +14,7 @@ import {
FolderPromptsToPublish,
PromptsToPublishTree,
} from '@/src/ui/webElements/entityTree';
+import { PublishingRules } from '@/src/ui/webElements/publishingRules';
import { Page } from '@playwright/test';
export class PublishingRequestModal extends BaseElement {
@@ -40,6 +41,7 @@ export class PublishingRequestModal extends BaseElement {
private applicationsToPublishTree!: ApplicationsToPublishTree;
//change publish path element
private changePublishToPath!: ChangePath;
+ private publishingRules!: PublishingRules;
getConversationsToPublishTree(): ConversationsToPublishTree {
if (!this.conversationsToPublishTree) {
@@ -108,6 +110,13 @@ export class PublishingRequestModal extends BaseElement {
return this.changePublishToPath;
}
+ getPublishingRules(): PublishingRules {
+ if (!this.publishingRules) {
+ this.publishingRules = new PublishingRules(this.page, this.rootLocator);
+ }
+ return this.publishingRules;
+ }
+
public requestName = this.getChildElementBySelector(
PublishingModalSelectors.requestName,
);
diff --git a/apps/chat-e2e/src/ui/webElements/publishingRules.ts b/apps/chat-e2e/src/ui/webElements/publishingRules.ts
new file mode 100644
index 0000000000..b6b3e00028
--- /dev/null
+++ b/apps/chat-e2e/src/ui/webElements/publishingRules.ts
@@ -0,0 +1,22 @@
+import { PublishingRulesSelectors } from '@/src/ui/selectors';
+import { BaseElement } from '@/src/ui/webElements/baseElement';
+import { Locator, Page } from '@playwright/test';
+
+export class PublishingRules extends BaseElement {
+ constructor(page: Page, parentLocator: Locator) {
+ super(page, PublishingRulesSelectors.rulesContainer, parentLocator);
+ }
+
+ public publishingPath = this.getChildElementBySelector(
+ PublishingRulesSelectors.path,
+ );
+ public rulesList = this.getChildElementBySelector(
+ PublishingRulesSelectors.rulesList,
+ );
+ public addRuleButton = this.rulesList.getChildElementBySelector(
+ PublishingRulesSelectors.addRuleButton,
+ );
+ public allRules = this.rulesList.getChildElementBySelector(
+ PublishingRulesSelectors.rule,
+ );
+}
diff --git a/apps/chat-e2e/src/ui/webElements/renameConversationModal.ts b/apps/chat-e2e/src/ui/webElements/renameConversationModal.ts
new file mode 100644
index 0000000000..af78f85929
--- /dev/null
+++ b/apps/chat-e2e/src/ui/webElements/renameConversationModal.ts
@@ -0,0 +1,76 @@
+import { BaseElement } from './baseElement';
+
+import { isApiStorageType } from '@/src/hooks/global-setup';
+import { Attributes, Tags } from '@/src/ui/domData';
+import { keys } from '@/src/ui/keyboard';
+import { RenameConversationModalSelectors } from '@/src/ui/selectors';
+import { Page } from '@playwright/test';
+
+export class RenameConversationModal extends BaseElement {
+ constructor(page: Page) {
+ super(page, RenameConversationModalSelectors.modal);
+ }
+
+ public cancelButton = this.getChildElementBySelector(
+ RenameConversationModalSelectors.cancelButton,
+ );
+ public saveButton = this.getChildElementBySelector(
+ RenameConversationModalSelectors.saveButton,
+ );
+ public nameInput = this.getChildElementBySelector(Tags.input);
+ public title = this.getChildElementBySelector(
+ RenameConversationModalSelectors.title,
+ );
+
+ private async editConversationName(
+ newName: string,
+ confirmationAction: () => Promise,
+ { isHttpMethodTriggered = true }: { isHttpMethodTriggered?: boolean } = {},
+ ) {
+ await this.nameInput.fillInInput(newName);
+ if (isApiStorageType && isHttpMethodTriggered) {
+ const respPromise = this.page.waitForResponse(
+ (resp) => resp.request().method() === 'DELETE',
+ );
+ await confirmationAction();
+ await respPromise;
+ } else {
+ await confirmationAction();
+ }
+ }
+
+ async editConversationNameWithSaveButton(
+ newName: string,
+ options?: { isHttpMethodTriggered?: boolean },
+ ) {
+ await this.editConversationName(
+ newName,
+ () => this.saveButton.click(),
+ options,
+ );
+ }
+
+ async editConversationNameWithEnter(
+ newName: string,
+ options?: { isHttpMethodTriggered?: boolean },
+ ) {
+ await this.editConversationName(
+ newName,
+ () => this.page.keyboard.press(keys.enter),
+ options,
+ );
+ }
+
+ async close() {
+ await this.cancelButton.click();
+ }
+
+ async getInputValue() {
+ return this.nameInput.getAttribute(Attributes.value);
+ }
+
+ public async editInputValue(newValue: string) {
+ await this.page.keyboard.press(keys.ctrlPlusA);
+ await this.nameInput.fillInInput(newValue);
+ }
+}
diff --git a/apps/chat-e2e/src/ui/webElements/selectFolderModal.ts b/apps/chat-e2e/src/ui/webElements/selectFolderModal.ts
index 129f3675e8..b2cb5cd54e 100644
--- a/apps/chat-e2e/src/ui/webElements/selectFolderModal.ts
+++ b/apps/chat-e2e/src/ui/webElements/selectFolderModal.ts
@@ -54,9 +54,13 @@ export class SelectFolderModal extends BaseElement {
public async selectFolder(
folderName: string,
+ index?: number,
{ triggeredApiHost }: { triggeredApiHost?: string } = {},
) {
- const folderToSelect = this.getSelectFolders().getFolderName(folderName);
+ const folderToSelect = this.getSelectFolders().getFolderName(
+ folderName,
+ index,
+ );
let respPremise: Promise;
if (triggeredApiHost) {
respPremise = this.page.waitForResponse((r) =>
diff --git a/apps/chat/src/components/Chat/ChangePathDialog.tsx b/apps/chat/src/components/Chat/ChangePathDialog.tsx
index ae49d43211..5497466bb8 100644
--- a/apps/chat/src/components/Chat/ChangePathDialog.tsx
+++ b/apps/chat/src/components/Chat/ChangePathDialog.tsx
@@ -2,6 +2,7 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
+import { updateEntitiesFoldersAndIds } from '@/src/utils/app/common';
import { constructPath } from '@/src/utils/app/file';
import {
getChildAndCurrentFoldersIdsById,
@@ -9,6 +10,7 @@ import {
getNextDefaultName,
getPathToFolderById,
sortByName,
+ updateMovedFolderId,
validateFolderRenaming,
} from '@/src/utils/app/folders';
@@ -160,10 +162,17 @@ export const ChangePathDialog = ({
setErrorMessage(t(error) as string);
return;
}
+ const { updatedOpenedFoldersIds } = updateEntitiesFoldersAndIds(
+ [],
+ [],
+ (id) => updateMovedFolderId(folderId, newFolderId, id),
+ openedFoldersIds,
+ );
dispatch(actions.renameTemporaryFolder({ folderId, name: newName }));
+ setOpenedFoldersIds(updatedOpenedFoldersIds);
},
- [actions, dispatch, folders, t],
+ [actions, dispatch, folders, t, openedFoldersIds, setOpenedFoldersIds],
);
const handleAddFolder = useCallback(
@@ -175,12 +184,15 @@ export const ChangePathDialog = ({
false,
true,
);
+ const id = constructPath(parentFolderId, folderName);
- setSelectedFolderId(constructPath(parentFolderId, folderName));
+ setSelectedFolderId(id);
dispatch(
actions.createTemporaryFolder({
- relativePath: parentFolderId,
+ folderId: parentFolderId,
+ name: folderName,
+ id,
}),
);
diff --git a/apps/chat/src/components/Chat/Chat.tsx b/apps/chat/src/components/Chat/Chat.tsx
index 8e313add86..3af1b22c8c 100644
--- a/apps/chat/src/components/Chat/Chat.tsx
+++ b/apps/chat/src/components/Chat/Chat.tsx
@@ -834,9 +834,8 @@ export function Chat() {
const isolatedModelId = useAppSelector(
SettingsSelectors.selectIsolatedModelId,
);
- const activeModel = useAppSelector((state) =>
- ModelsSelectors.selectModel(state, isolatedModelId || ''),
- );
+ const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap);
+ const activeModel = modelsMap[isolatedModelId || ''];
const selectedPublication = useAppSelector(
PublicationSelectors.selectSelectedPublication,
);
diff --git a/apps/chat/src/components/Chat/ChatHeader/Header.tsx b/apps/chat/src/components/Chat/ChatHeader/Header.tsx
index 271f53815a..f526fbbccc 100644
--- a/apps/chat/src/components/Chat/ChatHeader/Header.tsx
+++ b/apps/chat/src/components/Chat/ChatHeader/Header.tsx
@@ -90,12 +90,21 @@ export const ChatHeader = ({
const isSelectMode = useAppSelector(
ConversationsSelectors.selectIsSelectMode,
);
- const isTopChatModelSettingsEnabled = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.TopChatModelSettings),
+
+ const enabledFeatures = useAppSelector(
+ SettingsSelectors.selectEnabledFeatures,
+ );
+ const isTopChatModelSettingsEnabled = enabledFeatures.has(
+ Feature.TopChatModelSettings,
);
- const isChatbarEnabled = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.ConversationsSection),
+ const isTopContextMenuHidden = enabledFeatures.has(
+ Feature.HideTopContextMenu,
);
+ const isChangeAgentDisallowed = enabledFeatures.has(
+ Feature.DisallowChangeAgent,
+ );
+ const isChatbarEnabled = enabledFeatures.has(Feature.ConversationsSection);
+
const selectedConversations = useAppSelector(
ConversationsSelectors.selectSelectedConversations,
);
@@ -111,14 +120,6 @@ export const ChatHeader = ({
const screenState = useScreenState();
- const isTopContextMenuHidden = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.HideTopContextMenu),
- );
-
- const isChangeAgentDisallowed = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.DisallowChangeAgent),
- );
-
const isContextMenuVisible =
isChatbarEnabled && !isSelectMode && !isTopContextMenuHidden;
diff --git a/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/ChatMessageTemplatesModal.tsx b/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/ChatMessageTemplatesModal.tsx
index cb95dfa8e4..832ca9673a 100644
--- a/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/ChatMessageTemplatesModal.tsx
+++ b/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/ChatMessageTemplatesModal.tsx
@@ -147,14 +147,14 @@ export const ChatMessageTemplatesModal = ({
setPreviewMode(false)}
- dataQA="save-button"
+ dataQA="set-template-tab"
>
{t('Set template')}
setPreviewMode(true)}
- dataQA="save-button"
+ dataQA="preview-tab"
disabled={isInvalid}
>
{t('Preview')}
@@ -182,11 +182,8 @@ export const ChatMessageTemplatesModal = ({
>
{t('Original message:')}
-
-
+
+
{collapsed
? `${message.content
.trim()
@@ -203,7 +200,7 @@ export const ChatMessageTemplatesModal = ({
diff --git a/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/TemplateRow.tsx b/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/TemplateRow.tsx
index 781d9e13a5..992bf5b3ce 100644
--- a/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/TemplateRow.tsx
+++ b/apps/chat/src/components/Chat/ChatMessage/ChatMessageTemplatesModal/TemplateRow.tsx
@@ -139,7 +139,10 @@ export const TemplateRow = ({
);
return (
-
+
);
diff --git a/apps/chat/src/components/Chat/ChatSettings/AddonsDialog.tsx b/apps/chat/src/components/Chat/ChatSettings/AddonsDialog.tsx
index 18138ea1b1..b5bfc91394 100644
--- a/apps/chat/src/components/Chat/ChatSettings/AddonsDialog.tsx
+++ b/apps/chat/src/components/Chat/ChatSettings/AddonsDialog.tsx
@@ -14,6 +14,8 @@ import { Translation } from '@/src/types/translation';
import { AddonsSelectors } from '@/src/store/addons/addons.reducers';
import { useAppSelector } from '@/src/store/hooks';
+import { OUTSIDE_PRESS } from '@/src/constants/modal';
+
import Modal from '@/src/components/Common/Modal';
import { ModelIcon } from '../../Chatbar/ModelIcon';
@@ -181,7 +183,7 @@ export const AddonsDialog: FC
= ({
state={isOpen ? ModalState.OPENED : ModalState.CLOSED}
hideClose
containerClassName="flex h-fit max-h-full h-[700px] w-full grow justify-between flex-col gap-4 divide-tertiary py-4 md:grow-0 xl:max-w-[720px] 2xl:max-w-[780px]"
- dismissProps={{ outsidePress: true }}
+ dismissProps={OUTSIDE_PRESS}
>
{t('Addons (max 10)')}
diff --git a/apps/chat/src/components/Chat/ConversationContextMenu.tsx b/apps/chat/src/components/Chat/ConversationContextMenu.tsx
index 11a3d7c4f5..77a68a730c 100644
--- a/apps/chat/src/components/Chat/ConversationContextMenu.tsx
+++ b/apps/chat/src/components/Chat/ConversationContextMenu.tsx
@@ -1,8 +1,16 @@
import { useDismiss, useFloating, useInteractions } from '@floating-ui/react';
-import { MouseEventHandler, useCallback, useEffect, useState } from 'react';
+import {
+ MouseEventHandler,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
import { useTranslation } from 'next-i18next';
+import { useScreenState } from '@/src/hooks/useScreenState';
+
import { isEntityNameOnSameLevelUnique } from '@/src/utils/app/common';
import { constructPath } from '@/src/utils/app/file';
import { getNextDefaultName } from '@/src/utils/app/folders';
@@ -15,7 +23,7 @@ import { defaultMyItemsFilters } from '@/src/utils/app/search';
import { translate } from '@/src/utils/app/translation';
import { Conversation } from '@/src/types/chat';
-import { FeatureType, isNotLoaded } from '@/src/types/common';
+import { FeatureType, ScreenState, isNotLoaded } from '@/src/types/common';
import { MoveToFolderProps } from '@/src/types/folder';
import { ContextMenuProps } from '@/src/types/menu';
import { SharingType } from '@/src/types/share';
@@ -29,9 +37,10 @@ import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { ImportExportActions } from '@/src/store/import-export/importExport.reducers';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';
import { ShareActions } from '@/src/store/share/share.reducers';
-import { UIActions } from '@/src/store/ui/ui.reducers';
+import { UIActions, UISelectors } from '@/src/store/ui/ui.reducers';
import { DEFAULT_FOLDER_NAME } from '@/src/constants/default-ui-settings';
+import { PINNED_CONVERSATIONS_SECTION_NAME } from '@/src/constants/sections';
import { PublishModal } from '@/src/components/Chat/Publish/PublishWizard';
import { ExportModal } from '@/src/components/Chatbar/ExportModal';
@@ -51,7 +60,6 @@ interface ConversationContextMenuProps {
setIsOpen: (v: boolean) => void;
isHeaderMenu?: boolean;
publicationUrl?: string;
- onStartRename?: () => void;
className?: string;
TriggerIcon?: ContextMenuProps['TriggerIcon'];
disabledState?: boolean;
@@ -60,7 +68,6 @@ interface ConversationContextMenuProps {
export const ConversationContextMenu = ({
conversation,
publicationUrl,
- onStartRename,
isOpen,
setIsOpen,
className,
@@ -71,15 +78,17 @@ export const ConversationContextMenu = ({
const { t } = useTranslation(Translation.Chat);
const dispatch = useAppDispatch();
-
- const folders = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredFolders(
- state,
- defaultMyItemsFilters,
- '',
- true,
- ),
+ const selectFilteredFoldersSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredFolders(
+ defaultMyItemsFilters,
+ '',
+ true,
+ ),
+ [],
);
+
+ const folders = useAppSelector(selectFilteredFoldersSelector);
const allConversations = useAppSelector(
ConversationsSelectors.selectConversations,
);
@@ -87,6 +96,13 @@ export const ConversationContextMenu = ({
SettingsSelectors.selectIsPublishingEnabled(state, FeatureType.Chat),
);
+ const collapsedSectionsSelector = useMemo(
+ () => UISelectors.selectCollapsedSections(FeatureType.Chat),
+ [],
+ );
+
+ const collapsedSections = useAppSelector(collapsedSectionsSelector);
+
const [isShowMoveToModal, setIsShowMoveToModal] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isShowExportModal, setIsShowExportModal] = useState(false);
@@ -94,6 +110,8 @@ export const ConversationContextMenu = ({
const [isPublishing, setIsPublishing] = useState(false);
const [isUnpublishing, setIsUnpublishing] = useState(false);
+ const screenState = useScreenState();
+
const { refs, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
@@ -126,6 +144,13 @@ export const ConversationContextMenu = ({
setIsShowExportModal(false);
}, []);
+ useEffect(() => {
+ if (screenState !== ScreenState.MOBILE) {
+ setIsShowMoveToModal(false);
+ handleCloseExportModal();
+ }
+ }, [handleCloseExportModal, screenState]);
+
const handleOpenDeleteModal: MouseEventHandler
=
useCallback((e) => {
e.stopPropagation();
@@ -194,6 +219,15 @@ export const ConversationContextMenu = ({
}),
);
}
+
+ dispatch(
+ UIActions.setCollapsedSections({
+ featureType: FeatureType.Chat,
+ collapsedSections: collapsedSections.filter(
+ (section) => section !== PINNED_CONVERSATIONS_SECTION_NAME,
+ ),
+ }),
+ );
dispatch(
ConversationsActions.updateConversation({
id: conversation.id,
@@ -205,7 +239,7 @@ export const ConversationContextMenu = ({
}),
);
},
- [allConversations, conversation, dispatch, folders, t],
+ [allConversations, collapsedSections, conversation, dispatch, folders, t],
);
const handleCompare: MouseEventHandler =
@@ -303,6 +337,10 @@ export const ConversationContextMenu = ({
setIsDeleting(false);
}, [conversation.id, conversation.sharedWithMe, dispatch]);
+ const handleOpenRenameModal = useCallback(() => {
+ dispatch(ConversationsActions.setRenamingConversationId(conversation.id));
+ }, [conversation, dispatch]);
+
return (
<>
-
- {isShowMoveToModal && (
- {
- setIsShowMoveToModal(false);
- }}
- folders={folders}
- onMoveToFolder={handleMoveToFolder}
- />
- )}
-
-
-
- {isShowExportModal && (
-
- )}
-
+ {isShowMoveToModal && (
+ {
+ setIsShowMoveToModal(false);
+ }}
+ folders={folders}
+ onMoveToFolder={handleMoveToFolder}
+ />
+ )}
+
+ {isShowExportModal && (
+
+ )}
{isPublishingEnabled && (isPublishing || isUnpublishing) && (
)}
- {
- setIsDeleting(false);
- if (result) handleDelete();
- }}
- />
+ {isDeleting && (
+ {
+ setIsDeleting(false);
+ if (result) handleDelete();
+ }}
+ />
+ )}
>
);
};
diff --git a/apps/chat/src/components/Chat/EmptyChatDescription.tsx b/apps/chat/src/components/Chat/EmptyChatDescription.tsx
index bbd96ca59e..6e97cbd179 100644
--- a/apps/chat/src/components/Chat/EmptyChatDescription.tsx
+++ b/apps/chat/src/components/Chat/EmptyChatDescription.tsx
@@ -42,19 +42,20 @@ const EmptyChatDescriptionView = ({
const dispatch = useAppDispatch();
const { t } = useTranslation(Translation.Chat);
-
- const model = useAppSelector((state) =>
- ModelsSelectors.selectModel(state, conversation.model.id),
- );
+ const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap);
+ const model = modelsMap[conversation.model.id];
const installedModelIds = useAppSelector(
ModelsSelectors.selectInstalledModelIds,
);
const models = useAppSelector(ModelsSelectors.selectModels);
- const isEmptyChatChangeAgentHidden = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.HideEmptyChatChangeAgent),
+ const enabledFeatures = useAppSelector(
+ SettingsSelectors.selectEnabledFeatures,
+ );
+ const isEmptyChatChangeAgentHidden = enabledFeatures.has(
+ Feature.HideEmptyChatChangeAgent,
);
- const isEmptyChatSettingsEnabled = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.EmptyChatSettings),
+ const isEmptyChatSettingsEnabled = enabledFeatures.has(
+ Feature.EmptyChatSettings,
);
const isExternal = isEntityIdExternal(conversation);
diff --git a/apps/chat/src/components/Chat/MainModalManager.jsx b/apps/chat/src/components/Chat/MainModalManager.jsx
new file mode 100644
index 0000000000..ff0109c99e
--- /dev/null
+++ b/apps/chat/src/components/Chat/MainModalManager.jsx
@@ -0,0 +1,13 @@
+import { ReplaceConfirmationModal } from '../Common/ReplaceConfirmationModal/ReplaceConfirmationModal';
+import { RenameConversationModal } from './RenameConversationModal';
+import ShareModal from './ShareModal';
+
+export const MainModalManager = () => {
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/apps/chat/src/components/Chat/MessageAttachment.tsx b/apps/chat/src/components/Chat/MessageAttachment.tsx
index 308d7f6fb9..6e737ac7e2 100644
--- a/apps/chat/src/components/Chat/MessageAttachment.tsx
+++ b/apps/chat/src/components/Chat/MessageAttachment.tsx
@@ -189,9 +189,12 @@ const AttachmentUrlRendererComponent = ({
const mappedVisualizers = useAppSelector(
SettingsSelectors.selectMappedVisualizers,
);
-
- const isCustomAttachmentType = useAppSelector((state) =>
- SettingsSelectors.selectIsCustomAttachmentType(state, attachmentType),
+ const selectIsCustomAttachmentTypeSelector = useMemo(
+ () => SettingsSelectors.selectIsCustomAttachmentType(attachmentType),
+ [attachmentType],
+ );
+ const isCustomAttachmentType = useAppSelector(
+ selectIsCustomAttachmentTypeSelector,
);
if (mappedVisualizers && isCustomAttachmentType) {
@@ -223,8 +226,12 @@ export const MessageAttachment = ({ attachment, isInner }: Props) => {
const [isExpanded, setIsExpanded] = useState(false);
const anchorRef = useRef(null);
- const isCustomAttachmentType = useAppSelector((state) =>
- SettingsSelectors.selectIsCustomAttachmentType(state, attachment.type),
+ const selectIsCustomAttachmentTypeSelector = useMemo(
+ () => SettingsSelectors.selectIsCustomAttachmentType(attachment.type),
+ [attachment.type],
+ );
+ const isCustomAttachmentType = useAppSelector(
+ selectIsCustomAttachmentTypeSelector,
);
useEffect(() => {
diff --git a/apps/chat/src/components/Chat/ModelVersionSelect.tsx b/apps/chat/src/components/Chat/ModelVersionSelect.tsx
index 62ecb64f6a..4eaa090a90 100644
--- a/apps/chat/src/components/Chat/ModelVersionSelect.tsx
+++ b/apps/chat/src/components/Chat/ModelVersionSelect.tsx
@@ -85,7 +85,7 @@ export const ModelVersionSelect = ({
{
const dispatch = useAppDispatch();
- const openedFoldersIds = useAppSelector((state) =>
- UISelectors.selectOpenedFoldersIds(state, FeatureType.Prompt),
+ const openedFolderIdsSelector = useMemo(
+ () => UISelectors.selectOpenedFoldersIds(FeatureType.Prompt),
+ [],
);
+
+ const openedFoldersIds = useAppSelector(openedFolderIdsSelector);
const searchTerm = useAppSelector(PromptsSelectors.selectSearchTerm);
const prompts = useAppSelector(PromptsSelectors.selectPrompts);
const highlightedFolders = useAppSelector(
@@ -235,9 +238,12 @@ export const ConversationPublicationResources = ({
}: PublicationResources) => {
const dispatch = useAppDispatch();
- const openedFoldersIds = useAppSelector((state) =>
- UISelectors.selectOpenedFoldersIds(state, FeatureType.Chat),
+ const openedFolderIdsSelector = useMemo(
+ () => UISelectors.selectOpenedFoldersIds(FeatureType.Chat),
+ [],
);
+
+ const openedFoldersIds = useAppSelector(openedFolderIdsSelector);
const searchTerm = useAppSelector(ConversationsSelectors.selectSearchTerm);
const conversations = useAppSelector(
ConversationsSelectors.selectConversations,
@@ -372,9 +378,12 @@ export const FilePublicationResources = ({
}: PublicationResources) => {
const dispatch = useAppDispatch();
- const openedFoldersIds = useAppSelector((state) =>
- UISelectors.selectOpenedFoldersIds(state, FeatureType.File),
+ const openedFolderIdsSelector = useMemo(
+ () => UISelectors.selectOpenedFoldersIds(FeatureType.File),
+ [],
);
+
+ const openedFoldersIds = useAppSelector(openedFolderIdsSelector);
const files = useAppSelector(FilesSelectors.selectFiles);
const allFolders = useAppSelector(FilesSelectors.selectFolders);
diff --git a/apps/chat/src/components/Chat/Publish/PublishWizard.tsx b/apps/chat/src/components/Chat/Publish/PublishWizard.tsx
index e9a48541f9..ec6766777b 100644
--- a/apps/chat/src/components/Chat/Publish/PublishWizard.tsx
+++ b/apps/chat/src/components/Chat/Publish/PublishWizard.tsx
@@ -515,13 +515,23 @@ export function PublishModal<
))
)}
{!isRulesLoading && path && (
-
-
+
+
{path.split('/').pop()}
-
+
{otherTargetAudienceFilters.map((item) => (
-
+
@@ -559,6 +569,7 @@ export function PublishModal<
diff --git a/apps/chat/src/components/Chat/Publish/ReviewApplicationDialog.tsx b/apps/chat/src/components/Chat/Publish/ReviewApplicationDialog.tsx
index fa6849fd0b..db1136d20c 100644
--- a/apps/chat/src/components/Chat/Publish/ReviewApplicationDialog.tsx
+++ b/apps/chat/src/components/Chat/Publish/ReviewApplicationDialog.tsx
@@ -4,6 +4,8 @@ import { ApplicationSelectors } from '@/src/store/application/application.reduce
import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { PublicationActions } from '@/src/store/publication/publication.reducers';
+import { MOUSE_OUTSIDE_PRESS_EVENT } from '@/src/constants/modal';
+
import Modal from '../../Common/Modal';
import { Spinner } from '../../Common/Spinner';
import { ReviewApplicationDialogView } from './ReviewApplicationDialogView';
@@ -26,7 +28,7 @@ export function ReviewApplicationDialog() {
overlayClassName="fixed inset-0 top-[48px]"
state={ModalState.OPENED}
containerClassName="flex flex-col gap-4 sm:w-[600px] w-full"
- dismissProps={{ outsidePressEvent: 'mousedown' }}
+ dismissProps={MOUSE_OUTSIDE_PRESS_EVENT}
>
{isLoading ? (
diff --git a/apps/chat/src/components/Chat/RenameConversationModal.tsx b/apps/chat/src/components/Chat/RenameConversationModal.tsx
new file mode 100644
index 0000000000..2da3b4be9c
--- /dev/null
+++ b/apps/chat/src/components/Chat/RenameConversationModal.tsx
@@ -0,0 +1,232 @@
+import {
+ KeyboardEvent,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import {
+ doesHaveDotsInTheEnd,
+ isEntityNameOnSameLevelUnique,
+ prepareEntityName,
+} from '@/src/utils/app/common';
+import { notAllowedSymbolsRegex } from '@/src/utils/app/file';
+
+import { ModalState } from '@/src/types/modal';
+import { Translation } from '@/src/types/translation';
+
+import {
+ ConversationsActions,
+ ConversationsSelectors,
+} from '@/src/store/conversations/conversations.reducers';
+import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
+import { UIActions } from '@/src/store/ui/ui.reducers';
+
+import { DISALLOW_INTERACTIONS } from '@/src/constants/modal';
+
+import { ConfirmDialog } from '../Common/ConfirmDialog';
+import Modal from '../Common/Modal';
+
+export const RenameConversationModal = () => {
+ const renamingConversation = useAppSelector(
+ ConversationsSelectors.selectRenamingConversation,
+ );
+ if (renamingConversation) {
+ return
;
+ }
+};
+
+const RenameConversationView = () => {
+ const { t } = useTranslation(Translation.Chat);
+ const dispatch = useAppDispatch();
+ const inputRef = useRef
(null);
+
+ const allConversations = useAppSelector(
+ ConversationsSelectors.selectConversations,
+ );
+
+ const renamingConversation = useAppSelector(
+ ConversationsSelectors.selectRenamingConversation,
+ );
+
+ const [newConversationName, setNewConversationName] = useState('');
+ const [isConfirmRenaming, setIsConfirmRenaming] = useState(false);
+ const [originConversationName, setOriginConversationName] = useState('');
+
+ useEffect(() => {
+ if (renamingConversation) {
+ setNewConversationName(renamingConversation.name || '');
+ setOriginConversationName(renamingConversation.name || '');
+ setTimeout(() => {
+ inputRef.current?.focus();
+ inputRef.current?.select();
+ });
+ } else {
+ setNewConversationName('');
+ setOriginConversationName('');
+ setIsConfirmRenaming(false);
+ }
+ }, [renamingConversation]);
+
+ const newName = useMemo(
+ () => prepareEntityName(newConversationName, { forRenaming: true }),
+ [newConversationName],
+ );
+
+ const performRename = useCallback(
+ (name: string) => {
+ if (!name.trim()) return;
+ if (name.length > 0 && renamingConversation) {
+ dispatch(
+ ConversationsActions.updateConversation({
+ id: renamingConversation.id,
+ values: {
+ name,
+ isNameChanged: true,
+ isShared: false,
+ },
+ }),
+ );
+ dispatch(ConversationsActions.setRenamingConversationId(null));
+ }
+ },
+ [renamingConversation, dispatch],
+ );
+
+ const handleRename = useCallback(() => {
+ if (!renamingConversation) return;
+
+ if (
+ !isEntityNameOnSameLevelUnique(
+ newName,
+ renamingConversation,
+ allConversations,
+ )
+ ) {
+ dispatch(
+ UIActions.showErrorToast(
+ t(
+ 'Conversation with name "{{newName}}" already exists in this folder.',
+ {
+ ns: 'chat',
+ newName,
+ },
+ ),
+ ),
+ );
+
+ return;
+ }
+
+ if (doesHaveDotsInTheEnd(newName)) {
+ dispatch(
+ UIActions.showErrorToast(
+ t('Using a dot at the end of a name is not permitted.'),
+ ),
+ );
+ return;
+ }
+
+ if (
+ renamingConversation.isShared &&
+ newName !== renamingConversation.name
+ ) {
+ setIsConfirmRenaming(true);
+ return;
+ }
+
+ performRename(newName);
+ }, [
+ newName,
+ renamingConversation,
+ allConversations,
+ performRename,
+ dispatch,
+ t,
+ ]);
+
+ const handleEnterDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ e.stopPropagation();
+ handleRename();
+ }
+ },
+ [handleRename],
+ );
+
+ const handleClose = useCallback(() => {
+ dispatch(ConversationsActions.setRenamingConversationId(null));
+ }, [dispatch]);
+
+ return (
+
+
+ {t('Rename conversation')}
+
+ e.target.select()}
+ onChange={(e) =>
+ setNewConversationName(
+ e.target.value.replaceAll(notAllowedSymbolsRegex, ''),
+ )
+ }
+ onKeyDown={handleEnterDown}
+ className="w-full rounded border border-primary bg-transparent px-3 py-2.5 leading-4 outline-none placeholder:text-secondary focus-visible:border-accent-primary"
+ />
+
+
+
+
+ {
+ setIsConfirmRenaming(false);
+ if (result) performRename(newName);
+ handleClose();
+ }}
+ />
+
+ );
+};
diff --git a/apps/chat/src/components/Chat/ShareModal.tsx b/apps/chat/src/components/Chat/ShareModal.tsx
index d9568a4837..d521bb23d7 100644
--- a/apps/chat/src/components/Chat/ShareModal.tsx
+++ b/apps/chat/src/components/Chat/ShareModal.tsx
@@ -19,10 +19,21 @@ import { Translation } from '@/src/types/translation';
import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { ShareActions, ShareSelectors } from '@/src/store/share/share.reducers';
+import { OUTSIDE_PRESS_AND_MOUSE_EVENT } from '@/src/constants/modal';
+
import Modal from '../Common/Modal';
import Tooltip from '../Common/Tooltip';
-export default function ShareModal() {
+export const ShareModal = () => {
+ const isShareModalClosed = useAppSelector(
+ ShareSelectors.selectShareModalClosed,
+ );
+ if (!isShareModalClosed) {
+ return ;
+ }
+};
+
+export default function ShareModalView() {
const { t } = useTranslation(Translation.SideBar);
const dispatch = useAppDispatch();
@@ -84,7 +95,7 @@ export default function ShareModal() {
state={modalState}
onClose={handleClose}
heading={`${t('Share')}: ${shareResourceName?.trim()}`}
- dismissProps={{ outsidePress: true }}
+ dismissProps={OUTSIDE_PRESS_AND_MOUSE_EVENT}
>
diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx
index aeef93a7d5..28705f470f 100644
--- a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx
+++ b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx
@@ -1,4 +1,5 @@
import {
+ IconFileDescription,
IconPencilMinus,
IconPlayerPlay,
IconPlaystationSquare,
@@ -18,8 +19,10 @@ import {
getApplicationSimpleStatus,
getModelShortDescription,
isApplicationStatusUpdating,
+ isExecutableApp,
} from '@/src/utils/app/application';
import { getRootId } from '@/src/utils/app/id';
+import { isEntityIdPublic } from '@/src/utils/app/publications';
import { PseudoModel, isPseudoModel } from '@/src/utils/server/api';
import {
@@ -50,7 +53,8 @@ import { ApplicationTopic } from '@/src/components/Marketplace/ApplicationTopic'
import { FunctionStatusIndicator } from '@/src/components/Marketplace/FunctionStatusIndicator';
import LoaderIcon from '@/public/images/icons/loader.svg';
-import { Feature } from '@epam/ai-dial-shared';
+import UnpublishIcon from '@/public/images/icons/unpublish.svg';
+import { Feature, PublishActions } from '@epam/ai-dial-shared';
const DESKTOP_ICON_SIZE = 80;
const TABLET_ICON_SIZE = 48;
@@ -78,10 +82,11 @@ interface ApplicationCardProps {
disabled: boolean;
isUnavailableModel: boolean;
onClick: (entity: DialAIEntityModel) => void;
- onPublish: (entity: DialAIEntityModel) => void;
+ onPublish: (entity: DialAIEntityModel, action: PublishActions) => void;
onDelete: (entity: DialAIEntityModel) => void;
onEdit: (entity: DialAIEntityModel) => void;
onSelectVersion: (entity: DialAIEntityModel) => void;
+ onOpenLogs: (entity: DialAIEntityModel) => void;
}
export const TalkToCard = ({
@@ -95,6 +100,7 @@ export const TalkToCard = ({
onEdit,
onPublish,
onSelectVersion,
+ onOpenLogs,
}: ApplicationCardProps) => {
const { t } = useTranslation(Translation.Marketplace);
@@ -109,6 +115,10 @@ export const TalkToCard = ({
);
const isAdmin = useAppSelector(AuthSelectors.selectIsAdmin);
+ const isMyApp = entity.id.startsWith(
+ getRootId({ featureType: FeatureType.Application }),
+ );
+ const isExecutable = isExecutableApp(entity) && (isMyApp || isAdmin);
const screenState = useScreenState();
const versionsToSelect = useMemo(() => {
@@ -205,7 +215,29 @@ export const TalkToCard = ({
Icon: IconWorldShare,
onClick: (e: React.MouseEvent) => {
e.stopPropagation();
- onPublish(entity);
+ onPublish?.(entity, PublishActions.ADD);
+ },
+ },
+ {
+ name: t('Unpublish'),
+ dataQa: 'unpublish',
+ display: isEntityIdPublic(entity) && !!onPublish,
+ Icon: UnpublishIcon,
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onPublish?.(entity, PublishActions.DELETE);
+ },
+ },
+ {
+ name: t('Logs'),
+ dataQa: 'app-logs',
+ display:
+ isExecutable && playerStatus === SimpleApplicationStatus.UNDEPLOY,
+ Icon: IconFileDescription,
+ onClick: (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ onOpenLogs(entity);
},
},
{
@@ -230,10 +262,12 @@ export const TalkToCard = ({
isCodeAppsEnabled,
PlayerIcon,
onEdit,
- isModifyDisabled,
onPublish,
+ isExecutable,
onDelete,
+ isModifyDisabled,
handleUpdateFunctionStatus,
+ onOpenLogs,
],
);
diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx
index b904a3c61a..d9434e8b79 100644
--- a/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx
+++ b/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx
@@ -7,7 +7,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useTranslation } from 'next-i18next';
-import { useRouter } from 'next/router';
+import Link from 'next/link';
import classNames from 'classnames';
@@ -35,6 +35,7 @@ import { ApplicationActions } from '@/src/store/application/application.reducers
import { ConversationsActions } 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 { REPLAY_AS_IS_MODEL } from '@/src/constants/chat';
import { MarketplaceQueryParams } from '@/src/constants/marketplace';
@@ -45,9 +46,10 @@ import { ConfirmDialog } from '@/src/components/Common/ConfirmDialog';
import Modal from '@/src/components/Common/Modal';
import { NoResultsFound } from '@/src/components/Common/NoResultsFound';
+import { ApplicationLogs } from '../../Marketplace/ApplicationLogs';
import { TalkToCard } from './TalkToCard';
-import { PublishActions, ShareEntity } from '@epam/ai-dial-shared';
+import { Feature, PublishActions, ShareEntity } from '@epam/ai-dial-shared';
import chunk from 'lodash-es/chunk';
import orderBy from 'lodash-es/orderBy';
import range from 'lodash-es/range';
@@ -80,8 +82,12 @@ interface SliderModelsGroupProps {
rowsCount: number;
onEditApplication: (entity: DialAIEntityModel) => void;
onDeleteApplication: (entity: DialAIEntityModel) => void;
- onSetPublishEntity: (entity: DialAIEntityModel) => void;
+ onSetPublishEntity: (
+ entity: DialAIEntityModel,
+ action: PublishActions,
+ ) => void;
onSelectModel: (entity: DialAIEntityModel) => void;
+ onOpenLogs: (entity: DialAIEntityModel) => void;
}
const SliderModelsGroup = ({
modelsGroup,
@@ -92,6 +98,7 @@ const SliderModelsGroup = ({
onDeleteApplication,
onSetPublishEntity,
onSelectModel,
+ onOpenLogs,
}: SliderModelsGroupProps) => {
const config = getMaxChunksCountConfig();
@@ -144,6 +151,7 @@ const SliderModelsGroup = ({
onPublish={onSetPublishEntity}
onSelectVersion={onSelectModel}
onClick={onSelectModel}
+ onOpenLogs={onOpenLogs}
/>
);
})}
@@ -176,8 +184,6 @@ const TalkToModalView = ({
}: TalkToModalViewProps) => {
const { t } = useTranslation(Translation.Chat);
- const router = useRouter();
-
const dispatch = useDispatch();
const allModels = useAppSelector(ModelsSelectors.selectModels);
@@ -192,12 +198,15 @@ const TalkToModalView = ({
const [activeSlide, setActiveSlide] = useState(0);
const [editModel, setEditModel] = useState();
const [deleteModel, setDeleteModel] = useState();
- const [publishModel, setPublishModel] = useState<
- ShareEntity & { iconUrl?: string }
- >();
+ const [logModel, setLogModel] = useState();
+ const [publishModel, setPublishModel] = useState<{
+ entity: ShareEntity & { iconUrl?: string };
+ action: PublishActions;
+ }>();
const [sliderHeight, setSliderHeight] = useState(0);
const [sharedConversationNewModel, setSharedConversationNewModel] =
useState();
+ const [isOpenLogs, setIsOpenLogs] = useState();
const sliderRef = useRef(null);
@@ -206,6 +215,9 @@ const TalkToModalView = ({
const isPlayback = conversation.playback?.isPlayback;
const isReplay = conversation.replay?.isReplay;
const config = getMaxChunksCountConfig();
+ const isMarketplaceEnabled = useAppSelector((state) =>
+ SettingsSelectors.isFeatureEnabled(state, Feature.Marketplace),
+ );
const displayedModels = useMemo(() => {
const currentModel = modelsMap[conversation.model.id];
@@ -428,17 +440,32 @@ const TalkToModalView = ({
[deleteModel, dispatch],
);
- const handleSetPublishEntity = useCallback((entity: DialAIEntityModel) => {
- setPublishModel({
- name: entity.name,
- id: ApiUtils.decodeApiUrl(entity.id),
- folderId: getFolderIdFromEntityId(entity.id),
- iconUrl: entity.iconUrl,
- });
- }, []);
+ const handleSetPublishEntity = useCallback(
+ (entity: DialAIEntityModel, action: PublishActions) =>
+ setPublishModel({
+ entity: {
+ name: entity.name,
+ id: ApiUtils.decodeApiUrl(entity.id),
+ folderId: getFolderIdFromEntityId(entity.id),
+ iconUrl: entity.iconUrl,
+ },
+ action,
+ }),
+ [],
+ );
const handlePublishClose = useCallback(() => setPublishModel(undefined), []);
+ const handleCloseApplicationLogs = useCallback(
+ () => setIsOpenLogs(false),
+ [setIsOpenLogs],
+ );
+
+ const handleOpenApplicationLogs = useCallback((entity: DialAIEntityModel) => {
+ setIsOpenLogs(true);
+ setLogModel(entity);
+ }, []);
+
const handleDeleteApplication = useCallback(
(entity: DialAIEntityModel) => {
setDeleteModel(entity);
@@ -522,6 +549,7 @@ const TalkToModalView = ({
onDeleteApplication={handleDeleteApplication}
onSetPublishEntity={handleSetPublishEntity}
onSelectModel={handleSelectModel}
+ onOpenLogs={handleOpenApplicationLogs}
/>
))
) : (
@@ -577,21 +605,22 @@ const TalkToModalView = ({
>
)}
-
+ {isMarketplaceEnabled && (
+
+ conversation.playback?.isPlayback ? e.preventDefault() : null
+ }
+ className={classNames(
+ 'mt-4 text-accent-primary md:mt-0',
+ conversation.playback?.isPlayback && 'cursor-not-allowed',
+ )}
+ data-qa="go-to-my-workspace"
+ >
+ {t('Go to My workspace')}
+
+ )}
@@ -628,11 +657,18 @@ const TalkToModalView = ({
)}
{publishModel && (
+ )}
+ {logModel && isOpenLogs && (
+
)}
diff --git a/apps/chat/src/components/Chatbar/ChatFolders.tsx b/apps/chat/src/components/Chatbar/ChatFolders.tsx
index 47503e503d..d715311b71 100644
--- a/apps/chat/src/components/Chatbar/ChatFolders.tsx
+++ b/apps/chat/src/components/Chatbar/ChatFolders.tsx
@@ -66,31 +66,37 @@ const ChatFolderTemplate = ({
const dispatch = useAppDispatch();
const searchTerm = useAppSelector(ConversationsSelectors.selectSearchTerm);
- const conversations = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredConversations(
- state,
- filters,
- searchTerm,
- ),
+ const selectFilteredConversationsSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredConversations(filters, searchTerm),
+ [filters, searchTerm],
);
+ const conversations = useAppSelector(selectFilteredConversationsSelector);
const allConversations = useAppSelector(
ConversationsSelectors.selectConversations,
);
const allFolders = useAppSelector(ConversationsSelectors.selectFolders);
- const conversationFolders = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredFolders(
- state,
- filters,
- searchTerm,
- includeEmpty,
- ),
+ const selectFilteredFoldersSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredFolders(
+ filters,
+ searchTerm,
+ includeEmpty,
+ ),
+ [filters, includeEmpty, searchTerm],
);
+
+ const conversationFolders = useAppSelector(selectFilteredFoldersSelector);
const highlightedFolders = useAppSelector(
ConversationsSelectors.selectSelectedConversationsFoldersIds,
);
- const openedFoldersIds = useAppSelector((state) =>
- UISelectors.selectOpenedFoldersIds(state, FeatureType.Chat),
+
+ const openedFolderIdsSelector = useMemo(
+ () => UISelectors.selectOpenedFoldersIds(FeatureType.Chat),
+ [],
);
+
+ const openedFoldersIds = useAppSelector(openedFolderIdsSelector);
const loadingFolderIds = useAppSelector(
ConversationsSelectors.selectLoadingFolderIds,
);
@@ -100,9 +106,14 @@ const ChatFolderTemplate = ({
const isConversationsStreaming = useAppSelector(
ConversationsSelectors.selectIsConversationsStreaming,
);
+
+ const chosenFolderIdsSelector = useMemo(
+ () => ConversationsSelectors.selectChosenFolderIds(conversations),
+ [conversations],
+ );
+
const { fullyChosenFolderIds, partialChosenFolderIds } = useAppSelector(
- (state) =>
- ConversationsSelectors.selectChosenFolderIds(state, conversations),
+ chosenFolderIdsSelector,
);
const selectedConversations = useAppSelector(
ConversationsSelectors.selectSelectedItems,
@@ -321,21 +332,22 @@ export const ChatSection = ({
const [isSectionHighlighted, setIsSectionHighlighted] = useState(false);
const searchTerm = useAppSelector(ConversationsSelectors.selectSearchTerm);
- const rootFolders = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredFolders(
- state,
- filters,
- searchTerm,
- showEmptyFolders,
- ),
- );
- const rootConversations = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredConversations(
- state,
- filters,
- searchTerm,
- ),
+ const selectFilteredFoldersSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredFolders(
+ filters,
+ searchTerm,
+ showEmptyFolders,
+ ),
+ [filters, searchTerm, showEmptyFolders],
+ );
+ const rootFolders = useAppSelector(selectFilteredFoldersSelector);
+ const selectFilteredConversationsSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredConversations(filters, searchTerm),
+ [filters, searchTerm],
);
+ const rootConversations = useAppSelector(selectFilteredConversationsSelector);
const selectedFoldersIds = useAppSelector(
ConversationsSelectors.selectSelectedConversationsFoldersIds,
);
@@ -443,13 +455,18 @@ export function ChatFolders() {
const isSharingEnabled = useAppSelector((state) =>
SettingsSelectors.isSharingEnabled(state, FeatureType.Chat),
);
- const publicationItems = useAppSelector((state) =>
- PublicationSelectors.selectFilteredPublications(
- state,
- publicationFeatureTypes,
- ),
+
+ const publicationItemsSelector = useMemo(
+ () =>
+ PublicationSelectors.selectFilteredPublications(
+ publicationFeatureTypes,
+ true,
+ ),
+ [],
);
+ const publicationItems = useAppSelector(publicationItemsSelector);
+
const toApproveFolderItem = {
hidden: !publicationItems.length,
name: APPROVE_REQUIRED_SECTION_NAME,
diff --git a/apps/chat/src/components/Chatbar/Chatbar.tsx b/apps/chat/src/components/Chatbar/Chatbar.tsx
index fc43e9d227..3689b4b8d8 100644
--- a/apps/chat/src/components/Chatbar/Chatbar.tsx
+++ b/apps/chat/src/components/Chatbar/Chatbar.tsx
@@ -1,8 +1,10 @@
import { IconApps } from '@tabler/icons-react';
-import { DragEvent, useCallback } from 'react';
+import { DragEvent, useCallback, useMemo } from 'react';
import { useTranslation } from 'next-i18next';
-import { useRouter } from 'next/router';
+import Link from 'next/link';
+
+import classNames from 'classnames';
import { isEntityNameOnSameLevelUnique } from '@/src/utils/app/common';
import { getConversationRootId } from '@/src/utils/app/id';
@@ -20,6 +22,8 @@ import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';
import { UIActions, UISelectors } from '@/src/store/ui/ui.reducers';
+import { CONVERSATIONS_DATE_SECTIONS } from '@/src/constants/sections';
+
import Tooltip from '../Common/Tooltip';
import Sidebar from '../Sidebar';
import { ChatFolders } from './ChatFolders';
@@ -29,19 +33,19 @@ import { Conversations } from './Conversations';
import { ConversationInfo, Feature } from '@epam/ai-dial-shared';
const ChatActionsBlock = () => {
- const router = useRouter();
const { t } = useTranslation(Translation.SideBar);
const messageIsStreaming = useAppSelector(
ConversationsSelectors.selectIsConversationsStreaming,
);
- const isNewConversationDisabled = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.HideNewConversation),
+ const enabledFeatures = useAppSelector(
+ SettingsSelectors.selectEnabledFeatures,
);
-
- const isMarketplaceEnabled = useAppSelector((state) =>
- SettingsSelectors.isFeatureEnabled(state, Feature.Marketplace),
+ const isNewConversationDisabled = enabledFeatures.has(
+ Feature.HideNewConversation,
);
+ const isMarketplaceEnabled = enabledFeatures.has(Feature.Marketplace);
+
if (isNewConversationDisabled) {
return null;
}
@@ -50,17 +54,23 @@ const ChatActionsBlock = () => {
<>
{isMarketplaceEnabled && (
-
+
)}
>
@@ -87,20 +97,30 @@ export const Chatbar = () => {
ConversationsSelectors.selectMyItemsFilters,
);
- const filteredConversations = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredConversations(
- state,
- myItemsFilters,
- searchTerm,
- ),
+ const collapsedSectionsSelector = useMemo(
+ () => UISelectors.selectCollapsedSections(FeatureType.Chat),
+ [],
+ );
+
+ const collapsedSections = useAppSelector(collapsedSectionsSelector);
+
+ const selectFilteredConversationsSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredConversations(
+ myItemsFilters,
+ searchTerm,
+ ),
+ [myItemsFilters, searchTerm],
+ );
+ const filteredConversations = useAppSelector(
+ selectFilteredConversationsSelector,
);
- const filteredFolders = useAppSelector((state) =>
- ConversationsSelectors.selectFilteredFolders(
- state,
- myItemsFilters,
- searchTerm,
- ),
+ const selectFilteredFoldersSelector = useMemo(
+ () =>
+ ConversationsSelectors.selectFilteredFolders(myItemsFilters, searchTerm),
+ [myItemsFilters, searchTerm],
);
+ const filteredFolders = useAppSelector(selectFilteredFoldersSelector);
const handleDrop = useCallback(
(e: DragEvent) => {
@@ -132,6 +152,14 @@ export const Chatbar = () => {
return;
}
+ dispatch(
+ UIActions.setCollapsedSections({
+ featureType: FeatureType.Chat,
+ collapsedSections: collapsedSections.filter(
+ (section) => section !== CONVERSATIONS_DATE_SECTIONS.today,
+ ),
+ }),
+ );
dispatch(
ConversationsActions.updateConversation({
id: conversation.id,
@@ -142,7 +170,7 @@ export const Chatbar = () => {
}
}
},
- [allConversations, dispatch, t],
+ [allConversations, collapsedSections, dispatch, t],
);
const handleSearchTerm = useCallback(
@@ -173,9 +201,9 @@ export const Chatbar = () => {
filteredFolders={filteredFolders}
searchTerm={searchTerm}
searchFilters={searchFilters}
- handleSearchTerm={handleSearchTerm}
- handleSearchFilters={handleSearchFilters}
- handleDrop={handleDrop}
+ onSearchTerm={handleSearchTerm}
+ onSearchFilters={handleSearchFilters}
+ onDrop={handleDrop}
footerComponent={
}
areEntitiesUploaded={areEntitiesUploaded}
/>
diff --git a/apps/chat/src/components/Chatbar/ChatbarSettings.tsx b/apps/chat/src/components/Chatbar/ChatbarSettings.tsx
index 1e6cc1fc5b..ec0e620136 100644
--- a/apps/chat/src/components/Chatbar/ChatbarSettings.tsx
+++ b/apps/chat/src/components/Chatbar/ChatbarSettings.tsx
@@ -62,10 +62,14 @@ export const ChatbarSettings = () => {
const isSelectMode = useAppSelector(
ConversationsSelectors.selectIsSelectMode,
);
- const collapsedSections = useAppSelector((state) =>
- UISelectors.selectCollapsedSections(state, FeatureType.Chat),
+
+ const collapsedSectionsSelector = useMemo(
+ () => UISelectors.selectCollapsedSections(FeatureType.Chat),
+ [],
);
+ const collapsedSections = useAppSelector(collapsedSectionsSelector);
+
const handleToggleCompare = useCallback(() => {
dispatch(
ConversationsActions.createNewConversations({
diff --git a/apps/chat/src/components/Chatbar/Conversation.tsx b/apps/chat/src/components/Chatbar/Conversation.tsx
index f4bfe46ad1..caf63e85bc 100644
--- a/apps/chat/src/components/Chatbar/Conversation.tsx
+++ b/apps/chat/src/components/Chatbar/Conversation.tsx
@@ -1,11 +1,8 @@
-import { IconCheck, IconX } from '@tabler/icons-react';
+import { IconCheck } from '@tabler/icons-react';
import {
DragEvent,
- KeyboardEvent,
MouseEvent,
- MouseEventHandler,
useCallback,
- useEffect,
useMemo,
useRef,
useState,
@@ -16,16 +13,11 @@ import { useTranslation } from 'next-i18next';
import classNames from 'classnames';
import {
- doesHaveDotsInTheEnd,
hasInvalidNameInPath,
isEntityNameInvalid,
- isEntityNameOnSameLevelUnique,
isEntityNameOrPathInvalid,
- prepareEntityName,
- trimEndDots,
} from '@/src/utils/app/common';
import { getEntityNameError } from '@/src/utils/app/errors';
-import { notAllowedSymbolsRegex } from '@/src/utils/app/file';
import { isEntityIdExternal } from '@/src/utils/app/id';
import { hasParentWithFloatingOverlay } from '@/src/utils/app/modals';
import { MoveType, getDragImage } from '@/src/utils/app/move';
@@ -43,16 +35,13 @@ import {
PublicationActions,
PublicationSelectors,
} from '@/src/store/publication/publication.reducers';
-import { UIActions } from '@/src/store/ui/ui.reducers';
-import SidebarActionButton from '@/src/components/Buttons/SidebarActionButton';
import { ConversationContextMenu } from '@/src/components/Chat/ConversationContextMenu';
import { PlaybackIcon } from '@/src/components/Chat/Playback/PlaybackIcon';
import { ReplayAsIsIcon } from '@/src/components/Chat/ReplayAsIsIcon';
import ShareIcon from '@/src/components/Common/ShareIcon';
import { ReviewDot } from '../Chat/Publish/ReviewDot';
-import { ConfirmDialog } from '../Common/ConfirmDialog';
import Tooltip from '../Common/Tooltip';
import { ModelIcon } from './ModelIcon';
@@ -180,10 +169,7 @@ export function ConversationView({
/>
)}
-
+
{conversation.name}
@@ -215,11 +202,8 @@ export const ConversationComponent = ({
level,
additionalItemData,
}: Props) => {
- const { t } = useTranslation(Translation.Chat);
-
const dispatch = useAppDispatch();
- const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap);
const selectedConversationIds = useAppSelector(
ConversationsSelectors.selectSelectedConversationsIds,
);
@@ -227,16 +211,9 @@ export const ConversationComponent = ({
const messageIsStreaming = useAppSelector(
ConversationsSelectors.selectIsConversationsStreaming,
);
- const allConversations = useAppSelector(
- ConversationsSelectors.selectConversations,
- );
- const [isRenaming, setIsRenaming] = useState(false);
- const [renameValue, setRenameValue] = useState('');
const buttonRef = useRef
(null);
- const inputRef = useRef(null);
const [isContextMenu, setIsContextMenu] = useState(false);
- const [isConfirmRenaming, setIsConfirmRenaming] = useState(false);
const isSelected = selectedConversationIds.includes(conversation.id);
@@ -261,81 +238,6 @@ export const ConversationComponent = ({
const isExternal = isEntityIdExternal(conversation);
- const performRename = useCallback(
- (name: string) => {
- if (name.length > 0) {
- dispatch(
- ConversationsActions.updateConversation({
- id: conversation.id,
- values: {
- name,
- isNameChanged: true,
- isShared: false,
- },
- }),
- );
-
- setRenameValue('');
- setIsContextMenu(false);
- }
-
- setIsRenaming(false);
- },
- [conversation.id, dispatch],
- );
-
- const handleRename = useCallback(() => {
- const newName = prepareEntityName(renameValue, { forRenaming: true });
- setRenameValue(newName);
-
- if (
- !isEntityNameOnSameLevelUnique(newName, conversation, allConversations)
- ) {
- dispatch(
- UIActions.showErrorToast(
- t(
- 'Conversation with name "{{newName}}" already exists in this folder.',
- {
- ns: 'chat',
- newName,
- },
- ),
- ),
- );
-
- return;
- }
-
- if (doesHaveDotsInTheEnd(newName)) {
- dispatch(
- UIActions.showErrorToast(
- t('Using a dot at the end of a name is not permitted.'),
- ),
- );
- return;
- }
-
- if (conversation.isShared && newName !== conversation.name) {
- setIsConfirmRenaming(true);
- setIsContextMenu(false);
- setIsRenaming(false);
- return;
- }
-
- performRename(trimEndDots(newName));
- }, [allConversations, conversation, dispatch, performRename, renameValue, t]);
-
- const handleEnterDown = useCallback(
- (e: KeyboardEvent) => {
- e.stopPropagation();
- if (e.key === 'Enter') {
- e.preventDefault();
- handleRename();
- }
- },
- [handleRename],
- );
-
const handleDragStart = useCallback(
(e: DragEvent, conversation: ConversationInfo) => {
if (
@@ -354,28 +256,6 @@ export const ConversationComponent = ({
[isConversationsStreaming, isExternal, isSelectMode],
);
- const handleCancelRename: MouseEventHandler = useCallback(
- (e) => {
- e.stopPropagation();
- setIsRenaming(false);
- },
- [],
- );
-
- const handleStartRename = useCallback(() => {
- setIsRenaming(true);
- setRenameValue(conversation.name);
- }, [conversation.name]);
-
- useEffect(() => {
- if (isRenaming) {
- setTimeout(() => {
- inputRef.current?.focus();
- inputRef.current?.select();
- }); // set auto-focus
- }
- }, [isRenaming]);
-
const handleContextMenuOpen = (e: MouseEvent) => {
if (hasParentWithFloatingOverlay(e.target as Element)) {
return;
@@ -386,22 +266,12 @@ export const ConversationComponent = ({
};
const isHighlighted = !isSelectMode
- ? (isSelected &&
- (!additionalItemData?.publicationUrl ||
- selectedPublicationUrl === additionalItemData.publicationUrl)) ||
- isRenaming
+ ? isSelected &&
+ (!additionalItemData?.publicationUrl ||
+ selectedPublicationUrl === additionalItemData.publicationUrl)
: isChosen;
const isNameOrPathInvalid = isEntityNameOrPathInvalid(conversation);
- useEffect(() => {
- if (isSelectMode) {
- setIsRenaming(false);
- }
- }, [isSelectMode]);
-
- const iconSize = additionalItemData?.isSidePanelItem ? 24 : 18;
- const strokeWidth = additionalItemData?.isSidePanelItem ? 1.5 : 2;
-
return (
- {isRenaming ? (
-
-
- {conversation.isReplay && (
-
-
-
- )}
-
- {conversation.isPlayback && (
-
-
-
- )}
-
- {!conversation.isReplay && !conversation.isPlayback && (
-
- )}
-
-
- setRenameValue(
- e.target.value.replaceAll(notAllowedSymbolsRegex, ''),
- )
- }
- onKeyDown={handleEnterDown}
- autoFocus
- ref={inputRef}
- />
-
- ) : (
-
- {!isSelectMode && !isRenaming && !messageIsStreaming && (
+ {!isSelectMode && !messageIsStreaming && (
)}
-
- {isRenaming && (
-
- handleRename()}
- dataQA="confirm-edit"
- >
-
-
-
-
-
-
- )}
-
{
- setIsConfirmRenaming(false);
-
- if (result) {
- performRename(
- prepareEntityName(renameValue, { forRenaming: true }),
- );
- }
-
- setIsContextMenu(false);
- setIsRenaming(false);
- }}
- />
);
};
diff --git a/apps/chat/src/components/Chatbar/Conversations.tsx b/apps/chat/src/components/Chatbar/Conversations.tsx
index af5406a92e..d626347197 100644
--- a/apps/chat/src/components/Chatbar/Conversations.tsx
+++ b/apps/chat/src/components/Chatbar/Conversations.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from 'react';
+import { memo, useEffect, useMemo, useState } from 'react';
import { sortByDateAndName } from '@/src/utils/app/conversation';
import { getConversationRootId } from '@/src/utils/app/id';
@@ -33,7 +33,7 @@ interface SortedConversations {
other: SortedBlock;
}
-export const Conversations = ({ conversations }: Props) => {
+const _Conversations = ({ conversations }: Props) => {
const [sortedConversations, setSortedConversations] =
useState();
@@ -141,3 +141,5 @@ export const Conversations = ({ conversations }: Props) => {
);
};
+
+export const Conversations = memo(_Conversations);
diff --git a/apps/chat/src/components/Chatbar/ConversationsRenderer.tsx b/apps/chat/src/components/Chatbar/ConversationsRenderer.tsx
index 820e356c6f..7d900f5ef3 100644
--- a/apps/chat/src/components/Chatbar/ConversationsRenderer.tsx
+++ b/apps/chat/src/components/Chatbar/ConversationsRenderer.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useSectionToggle } from '@/src/hooks/useSectionToggle';
@@ -16,6 +16,10 @@ interface ConversationsRendererProps {
label: string;
}
+const additionalConvData = {
+ isSidePanelItem: true,
+};
+
export const ConversationsRenderer = ({
conversations,
label,
@@ -31,41 +35,34 @@ export const ConversationsRenderer = ({
FeatureType.Chat,
);
- const additionalConvData = useMemo(
- () => ({
- isSidePanelItem: true,
- }),
- [],
- );
-
useEffect(() => {
setIsSectionHighlighted(
conversations.some((conv) => selectedConversationsIds.includes(conv.id)),
);
}, [selectedConversationsIds, conversations]);
+ if (!conversations.length) {
+ return null;
+ }
+
return (
- <>
- {conversations.length > 0 && (
-
-
- {conversations.map((conversation) => (
-
- ))}
-
-
- )}
- >
+
+
+ {conversations.map((conversation) => (
+
+ ))}
+
+
);
};
diff --git a/apps/chat/src/components/Chatbar/ExportModal.tsx b/apps/chat/src/components/Chatbar/ExportModal.tsx
index fa6ca6e521..0e45896f2f 100644
--- a/apps/chat/src/components/Chatbar/ExportModal.tsx
+++ b/apps/chat/src/components/Chatbar/ExportModal.tsx
@@ -3,23 +3,25 @@ import { useTranslation } from 'next-i18next';
import { ModalState } from '@/src/types/modal';
import { Translation } from '@/src/types/translation';
+import { OUTSIDE_PRESS } from '@/src/constants/modal';
+
import Modal from '../Common/Modal';
interface Props {
onExport: (args?: { withAttachments?: boolean }) => void;
onClose: () => void;
- isOpen: boolean;
}
-export const ExportModal = ({ onExport, onClose, isOpen }: Props) => {
+export const ExportModal = ({ onExport, onClose }: Props) => {
const { t } = useTranslation(Translation.SideBar);
+
return (
{t('Export')}
diff --git a/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx b/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx
index 03a1ec2563..88c05f32f9 100644
--- a/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx
+++ b/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx
@@ -1,4 +1,3 @@
-import { UseDismissProps } from '@floating-ui/react';
import React, { useCallback, useMemo } from 'react';
import { ApplicationType } from '@/src/types/applications';
@@ -7,6 +6,8 @@ import { ModalState } from '@/src/types/modal';
import { ApplicationSelectors } from '@/src/store/application/application.reducers';
import { useAppSelector } from '@/src/store/hooks';
+import { MOUSE_OUTSIDE_PRESS_EVENT } from '@/src/constants/modal';
+
import { ApplicationWizardHeader } from '@/src/components/Common/ApplicationWizard/ApplicationWizardHeader';
import { CodeAppView } from '@/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView';
import { CustomAppView } from '@/src/components/Common/ApplicationWizard/CustomAppView';
@@ -14,7 +15,6 @@ import { QuickAppView } from '@/src/components/Common/ApplicationWizard/QuickApp
import Modal from '@/src/components/Common/Modal';
import { Spinner } from '@/src/components/Common/Spinner';
-const modalDismissProps = { outsidePressEvent: 'mousedown' } as UseDismissProps;
interface ApplicationWizardProps {
isOpen: boolean;
onClose: (value: boolean) => void;
@@ -60,7 +60,7 @@ export const ApplicationWizard: React.FC
= ({
onClose={handleClose}
dataQa="application-dialog"
containerClassName="flex w-full flex-col pt-2 md:grow-0 xl:max-w-[720px] 2xl:max-w-[780px] !bg-layer-2"
- dismissProps={modalDismissProps}
+ dismissProps={MOUSE_OUTSIDE_PRESS_EVENT}
hideClose
>
{isLoading ? (
diff --git a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeEditor.tsx b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeEditor.tsx
index f020861a99..da90187bea 100644
--- a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeEditor.tsx
+++ b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeEditor.tsx
@@ -127,10 +127,11 @@ const editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
const CodeEditorView = ({ selectedFileId }: CodeEditorViewProps) => {
const dispatch = useAppDispatch();
-
- const fileContent = useAppSelector((state) =>
- CodeEditorSelectors.selectFileContent(state, selectedFileId),
+ const selectFileContentSelector = useMemo(
+ () => CodeEditorSelectors.selectFileContent(selectedFileId),
+ [selectedFileId],
);
+ const fileContent = useAppSelector(selectFileContentSelector);
const isContentLoading = useAppSelector(
CodeEditorSelectors.selectIsFileContentLoading,
);
diff --git a/apps/chat/src/components/Common/ConfirmDialog.tsx b/apps/chat/src/components/Common/ConfirmDialog.tsx
index ee37e3a8cd..a0ae5f57c4 100644
--- a/apps/chat/src/components/Common/ConfirmDialog.tsx
+++ b/apps/chat/src/components/Common/ConfirmDialog.tsx
@@ -2,6 +2,8 @@ import { useId, useRef } from 'react';
import { ModalState } from '@/src/types/modal';
+import { DISALLOW_INTERACTIONS } from '@/src/constants/modal';
+
import Modal from '@/src/components/Common/Modal';
interface Props {
@@ -34,7 +36,7 @@ export const ConfirmDialog = ({
onClose={() => onClose(false)}
dataQa="confirmation-dialog"
containerClassName="inline-block w-full min-w-[90%] px-3 py-4 md:p-6 text-center md:min-w-[300px] md:max-w-[500px]"
- dismissProps={{ outsidePressEvent: 'mousedown', outsidePress: true }}
+ dismissProps={DISALLOW_INTERACTIONS}
hideClose
heading={heading}
headingClassName={headingClassName}
diff --git a/apps/chat/src/components/Common/ItemContextMenu.tsx b/apps/chat/src/components/Common/ItemContextMenu.tsx
index a4cca3e269..f54d5fb3ec 100644
--- a/apps/chat/src/components/Common/ItemContextMenu.tsx
+++ b/apps/chat/src/components/Common/ItemContextMenu.tsx
@@ -99,6 +99,7 @@ export default function ItemContextMenu({
TriggerIcon,
}: ItemContextMenuProps) {
const { t } = useTranslation(Translation.SideBar);
+
const isPublishingEnabled = useAppSelector((state) =>
SettingsSelectors.selectIsPublishingEnabled(state, featureType),
);
diff --git a/apps/chat/src/components/Common/MoveToFolderMobileModal.tsx b/apps/chat/src/components/Common/MoveToFolderMobileModal.tsx
index 318f8db744..7fa509bf48 100644
--- a/apps/chat/src/components/Common/MoveToFolderMobileModal.tsx
+++ b/apps/chat/src/components/Common/MoveToFolderMobileModal.tsx
@@ -1,26 +1,27 @@
-import { FloatingOverlay } from '@floating-ui/react';
-import { IconFolderPlus, IconX } from '@tabler/icons-react';
-import { useCallback, useEffect } from 'react';
+import { IconFolderPlus } from '@tabler/icons-react';
+import { useCallback } from 'react';
import { useTranslation } from 'next-i18next';
import { FolderInterface, MoveToFolderProps } from '@/src/types/folder';
+import { ModalState } from '@/src/types/modal';
import { Translation } from '@/src/types/translation';
+import Modal from './Modal';
+
interface MoveToFolderMobileModalProps {
folders: FolderInterface[];
- onOpen?: () => void;
onClose: () => void;
onMoveToFolder: (args: { folderId?: string; isNewFolder?: boolean }) => void;
}
export const MoveToFolderMobileModal = ({
folders,
- onOpen,
onMoveToFolder,
onClose,
}: MoveToFolderMobileModalProps) => {
const { t } = useTranslation(Translation.SideBar);
+
const handleMoveToFolder = useCallback(
({ isNewFolder, folderId }: MoveToFolderProps) => {
onMoveToFolder({ isNewFolder, folderId });
@@ -29,18 +30,17 @@ export const MoveToFolderMobileModal = ({
[onMoveToFolder, onClose],
);
- useEffect(() => {
- onOpen?.();
- }, [onOpen]);
-
return (
-
+
{t('Move to')}
-
-
-
-
+
);
};
diff --git a/apps/chat/src/components/Common/ReplaceConfirmationModal/ReplaceConfirmationModal.tsx b/apps/chat/src/components/Common/ReplaceConfirmationModal/ReplaceConfirmationModal.tsx
index 44aa182e2b..406cc15d2e 100644
--- a/apps/chat/src/components/Common/ReplaceConfirmationModal/ReplaceConfirmationModal.tsx
+++ b/apps/chat/src/components/Common/ReplaceConfirmationModal/ReplaceConfirmationModal.tsx
@@ -23,19 +23,26 @@ import {
ImportExportSelectors,
} from '@/src/store/import-export/importExport.reducers';
+import { OUTSIDE_PRESS_AND_MOUSE_EVENT } from '@/src/constants/modal';
+
import Modal from '../Modal';
import { ReplaceSelector } from './Components';
import { ConversationsList } from './ConversationsList';
import { FilesList } from './FilesList';
import { PromptsList } from './PromptsList';
-interface Props {
- isOpen: boolean;
-}
-
export type OnItemEvent = (actionOption: string, entityId: unknown) => void;
-export const ReplaceConfirmationModal = ({ isOpen }: Props) => {
+export const ReplaceConfirmationModal = () => {
+ const isReplaceModalOpened = useAppSelector(
+ ImportExportSelectors.selectIsShowReplaceDialog,
+ );
+ if (isReplaceModalOpened) {
+ return
;
+ }
+};
+
+export const ReplaceConfirmationModalView = () => {
const { t } = useTranslation(Translation.Chat);
const dispatch = useAppDispatch();
@@ -171,14 +178,14 @@ export const ReplaceConfirmationModal = ({ isOpen }: Props) => {
return (
{
return;
}}
hideClose
dataQa="replace-confirmation-modal"
containerClassName="flex w-full min-h-[595px] flex-col gap-4 pt-4 sm:w-[525px] md:pt-6"
- dismissProps={{ outsidePressEvent: 'mousedown' }}
+ dismissProps={OUTSIDE_PRESS_AND_MOUSE_EVENT}
>
diff --git a/apps/chat/src/components/Common/SelectFolder/SelectFolder.tsx b/apps/chat/src/components/Common/SelectFolder/SelectFolder.tsx
index 27137a0168..4e8df52d91 100644
--- a/apps/chat/src/components/Common/SelectFolder/SelectFolder.tsx
+++ b/apps/chat/src/components/Common/SelectFolder/SelectFolder.tsx
@@ -6,6 +6,8 @@ import { useTranslation } from 'next-i18next';
import { ModalState } from '@/src/types/modal';
import { Translation } from '@/src/types/translation';
+import { OUTSIDE_PRESS_AND_MOUSE_EVENT } from '@/src/constants/modal';
+
import Modal from '@/src/components/Common/Modal';
interface Props {
@@ -33,7 +35,7 @@ export const SelectFolder = ({
onClose={onClose}
dataQa={modalDataQa}
containerClassName="flex flex-col gap-4 md:min-w-[425px] w-[525px] sm:w-[525px] max-w-full"
- dismissProps={{ outsidePressEvent: 'mousedown', outsidePress: true }}
+ dismissProps={OUTSIDE_PRESS_AND_MOUSE_EVENT}
>
diff --git a/apps/chat/src/components/Common/Tooltip.tsx b/apps/chat/src/components/Common/Tooltip.tsx
index 9d212f125c..fcc009efbf 100644
--- a/apps/chat/src/components/Common/Tooltip.tsx
+++ b/apps/chat/src/components/Common/Tooltip.tsx
@@ -234,7 +234,11 @@ export default function Tooltip({
...tooltipProps
}: TooltipOptions) {
if (hideTooltip || !tooltip)
- return
{children};
+ return (
+
+ {children}
+
+ );
return (
diff --git a/apps/chat/src/components/Files/AttachLinkDialog.tsx b/apps/chat/src/components/Files/AttachLinkDialog.tsx
index bd84e0c9a2..b8170d2840 100644
--- a/apps/chat/src/components/Files/AttachLinkDialog.tsx
+++ b/apps/chat/src/components/Files/AttachLinkDialog.tsx
@@ -9,6 +9,8 @@ import { DialLink } from '@/src/types/files';
import { ModalState } from '@/src/types/modal';
import { Translation } from '@/src/types/translation';
+import { OUTSIDE_PRESS } from '@/src/constants/modal';
+
import Modal from '@/src/components/Common/Modal';
import { FieldErrorMessage } from '../Common/Forms/FieldErrorMessage';
@@ -59,7 +61,7 @@ export const AttachLinkDialog = ({ onClose }: Props) => {
overlayClassName="fixed inset-0"
containerClassName="inline-block w-full overflow-y-auto px-3 py-4 align-bottom transition-all md:p-6 xl:max-h-[800px] xl:max-w-[720px] 2xl:max-w-[780px]"
heading={t('Attach link')}
- dismissProps={{ outsidePress: true }}
+ dismissProps={OUTSIDE_PRESS}
>