From b8724c5330698dd60b4c5222ae9405346ac37156 Mon Sep 17 00:00:00 2001 From: Armen Derikyan <82438895+Derikyan@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:55:37 +0400 Subject: [PATCH 01/12] fix(chat): add padding to text in cards (Issue #2833) (#2859) --- apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx index 28705f470..7d6004337 100644 --- a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx +++ b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx @@ -326,7 +326,7 @@ export const TalkToCard = ({ )} -
+
{!!versionsToSelect.length && (

{t('Version')}:

From ddbcdc52318be258699260be3f00ab078257c782 Mon Sep 17 00:00:00 2001 From: nepalevov <33350321+nepalevov@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:32:17 +0100 Subject: [PATCH 02/12] fix(chat): add more language to file extension mappings for code blocks (Issue #2854) (#2858) --- .../src/components/Markdown/CodeBlock.tsx | 13 +- apps/chat/src/utils/app/codeblock.ts | 251 ++++++++++++++++-- 2 files changed, 242 insertions(+), 22 deletions(-) diff --git a/apps/chat/src/components/Markdown/CodeBlock.tsx b/apps/chat/src/components/Markdown/CodeBlock.tsx index aa4752f2e..e51b4b9b0 100644 --- a/apps/chat/src/components/Markdown/CodeBlock.tsx +++ b/apps/chat/src/components/Markdown/CodeBlock.tsx @@ -12,6 +12,7 @@ import classNames from 'classnames'; import { languageExtensionMapping, + languageFilenameMapping, languageNameMapping, } from '@/src/utils/app/codeblock'; @@ -63,15 +64,19 @@ export const CodeBlock: FC = memo( const displayLanguage = languageNameMapping[language] || language; const downloadAsFile = useCallback(() => { - const fileExtension = languageExtensionMapping[displayLanguage] || '.txt'; - const suggestedFileName = `ai-chat-code-${currentDate()}${fileExtension}`; + // languageExtensionMapping allows set empty extension + const fileExtension = languageExtensionMapping[displayLanguage] ?? '.txt'; + // use the specific filename if it exists in languageFilenameMapping + const suggestedFileName = + languageFilenameMapping[displayLanguage] ?? + `ai-chat-code-${currentDate()}${fileExtension}`; const fileName = window.prompt( t('Enter file name') || '', suggestedFileName, ); if (!fileName) { - // user pressed cancel on prompt + // User pressed cancel on prompt return; } @@ -85,7 +90,7 @@ export const CodeBlock: FC = memo( link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); - }, [language, t, value]); + }, [displayLanguage, t, value]); return (
; export const languageExtensionMapping: languageMap = { - javascript: '.js', - python: '.py', - java: '.java', + 'avro-idl': '.avdl', + 'objective-c': '.m', + 'solution-file': '.sln', + 'splunk-spl': '.spl', + 't4-cs': '.tt', + 't4-vb': '.tt', + 'web-idl': '.webidl', + abap: '.abap', + abnf: '.abnf', + actionscript: '.as', + ada: '.ada', + agda: '.agda', + al: '.al', + antlr4: '.g4', + apex: '.cls', + applescript: '.applescript', + aql: '.aql', + arduino: '.ino', + arff: '.arff', + asciidoc: '.adoc', + asm6502: '.asm', + asmatmel: '.asm', + aspnet: '.aspx', + autohotkey: '.ahk', + autoit: '.au3', + avisynth: '.avs', + bash: '.sh', + basic: '.bas', + batch: '.bat', + bicep: '.bicep', + birb: '.birb', + bison: '.y', + bnf: '.bnf', + brainfuck: '.bf', + brightscript: '.brs', + bro: '.bro', + bsl: '.bsl', c: '.c', + cfscript: '.cfc', + chaiscript: '.chai', + cil: '.il', + clojure: '.clj', + cmake: '.cmake', + cobol: '.cob', + coffeescript: '.coffee', + concurnas: '.conc', + coq: '.v', cpp: '.cpp', - 'c++': '.cpp', - 'c#': '.cs', - ruby: '.rb', - php: '.php', - swift: '.swift', - 'objective-c': '.m', - kotlin: '.kt', - typescript: '.ts', + crystal: '.cr', + csharp: '.cs', + cshtml: '.cshtml', + csp: '.csp', + css: '.css', + csv: '.csv', + cypher: '.cql', + d: '.d', + dart: '.dart', + dataweave: '.dwl', + dax: '.dax', + dhall: '.dhall', + diff: '.diff', + dot: '.dot', + ebnf: '.ebnf', + eiffel: '.e', + ejs: '.ejs', + elixir: '.ex', + elm: '.elm', + erb: '.erb', + erlang: '.erl', + etlua: '.etl', + factor: '.factor', + fortran: '.f90', + fsharp: '.fs', + ftl: '.ftl', + gap: '.g', + gcode: '.gcode', + gdscript: '.gd', + gedcom: '.ged', + gherkin: '.feature', + glsl: '.glsl', + gml: '.gml', + gn: '.gn', go: '.go', + graphql: '.graphql', + groovy: '.groovy', + haml: '.haml', + handlebars: '.hbs', + haskell: '.hs', + haxe: '.hx', + hcl: '.hcl', + hlsl: '.hlsl', + hoon: '.hoon', + html: '.html', + ichigojam: '.ijs', + icon: '.icn', + idris: '.idr', + iecst: '.st', + inform7: '.ni', + ini: '.ini', + io: '.io', + j: '.ijs', + java: '.java', + javascript: '.js', + jexl: '.jexl', + jolie: '.iol', + jq: '.jq', + json: '.json', + json5: '.json5', + jsx: '.jsx', + julia: '.jl', + keyman: '.kmn', + kotlin: '.kt', + kumir: '.kum', + kusto: '.kql', + latte: '.latte', + less: '.less', + lilypond: '.ly', + liquid: '.liquid', + lisp: '.lisp', + livescript: '.ls', + llvm: '.ll', + lolcode: '.lol', + lua: '.lua', + magma: '.magma', + markdown: '.md', + matlab: '.m', + maxscript: '.ms', + mel: '.mel', + mermaid: '.mmd', + mizar: '.miz', + monkey: '.monkey', + moonscript: '.moon', + n1ql: '.n1ql', + n4js: '.n4js', + naniscript: '.nani', + nasm: '.asm', + neon: '.neon', + nevod: '.nevod', + nginx: '.conf', + nim: '.nim', + nix: '.nix', + nsis: '.nsi', + ocaml: '.ml', + opencl: '.cl', + openqasm: '.qasm', + oz: '.oz', + parigp: '.gp', + pascal: '.pas', + pascaligo: '.ligo', + patch: '.patch', + pcaxis: '.px', + peoplecode: '.pcode', perl: '.pl', + php: '.php', + plsql: '.pls', + powerquery: '.pq', + powershell: '.ps1', + processing: '.pde', + prolog: '.pl', + promql: '.promql', + properties: '.properties', + protobuf: '.proto', + psl: '.psl', + pug: '.pug', + puppet: '.pp', + pure: '.pure', + purebasic: '.pb', + purescript: '.purs', + python: '.py', + q: '.q', + qml: '.qml', + qore: '.q', + qsharp: '.qs', + r: '.r', + racket: '.rkt', + reason: '.re', + rego: '.rego', + renpy: '.rpy', + rest: '.rest', + rip: '.rip', + roboconf: '.graph', + robotframework: '.robot', + ruby: '.rb', rust: '.rs', + sas: '.sas', + sass: '.sass', scala: '.scala', - haskell: '.hs', - lua: '.lua', + scheme: '.scm', + scss: '.scss', shell: '.sh', + smali: '.smali', + smalltalk: '.st', + smarty: '.tpl', + sml: '.sml', + solidity: '.sol', + soy: '.soy', + sparql: '.rq', + sqf: '.sqf', sql: '.sql', - html: '.html', - css: '.css', - bash: '.bash', - // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component + squirrel: '.nut', + stan: '.stan', + stylus: '.styl', + swift: '.swift', + tap: '.tap', + tcl: '.tcl', + tex: '.tex', + textile: '.textile', + toml: '.toml', + tremor: '.tremor', + tsx: '.tsx', + tt2: '.tt2', + turtle: '.ttl', + twig: '.twig', + typescript: '.ts', + typoscript: '.typoscript', + unrealscript: '.uc', + uorazor: '.cshtml', + v: '.v', + vala: '.vala', + vbnet: '.vb', + velocity: '.vm', + verilog: '.v', + vhdl: '.vhd', + vim: '.vim', + warpscript: '.mc2', + webassembly: '.wat', + xml: '.xml', + xojo: '.xojo_code', + xquery: '.xq', + yaml: '.yaml', + yang: '.yang', + zig: '.zig', + // Aligned with supported languages in prism + // src: https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/languages/prism/supported-languages.js }; export const languageNameMapping: languageMap = { - sh: 'shell', + sh: 'bash', // Treat shell scripts as bash scripts +}; + +export const languageFilenameMapping: languageMap = { + dockerfile: 'Dockerfile', + makefile: 'Makefile', + // Add other specific filenames here }; From 2fa32d6783c5eb45f9338d19717d88bef45316f2 Mon Sep 17 00:00:00 2001 From: Maksim Nartov <81012703+nartovm@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:52:16 +0100 Subject: [PATCH 03/12] feat(chat-e2e): publish prompts test (#2781) Co-authored-by: irinakartun --- apps/chat-e2e/src/assertions/baseAssertion.ts | 45 +- .../assertions/promptPreviewModalAssertion.ts | 56 ++ .../assertions/promptToApproveAssertion.ts | 4 + .../assertions/promptToPublishAssertion.ts | 4 + .../publishedPromptPreviewModalAssertion.ts | 13 + .../sharedPromptPreviewModalAssertion.ts | 67 +- apps/chat-e2e/src/core/dialAdminFixtures.ts | 54 +- apps/chat-e2e/src/core/dialFixtures.ts | 54 +- .../src/core/dialSharedWithMeFixtures.ts | 9 +- .../src/tests/monitoring/sharePrompt.test.ts | 10 +- .../src/tests/promptExportImport.test.ts | 2 +- .../src/tests/publishConversation.test.ts | 10 +- apps/chat-e2e/src/tests/publishPrompt.test.ts | 709 ++++++++++++++++++ .../src/tests/sharedPromptFolderIcons.test.ts | 2 +- .../src/tests/sharedPromptView.test.ts | 10 +- .../tests/sharedWithMePromptFolders.test.ts | 22 +- .../src/tests/sharedWithMePrompts.test.ts | 10 +- .../tests/sidePanelEntityDragAndDrop.test.ts | 4 +- .../src/ui/selectors/sideBarSelectors.ts | 4 +- apps/chat-e2e/src/ui/webElements/chatBar.ts | 12 - .../src/ui/webElements/entityTree/index.ts | 1 - .../approveRequiredConversationsTree.ts | 27 +- .../sidebar/approveRequiredEntitiesTree.ts | 32 + .../sidebar/approveRequiredPrompts.ts | 5 +- .../entityTree/sidebar/folderPrompts.ts | 33 +- ...rPrompts.ts => organizationPromptsTree.ts} | 6 +- apps/chat-e2e/src/ui/webElements/promptBar.ts | 99 ++- .../webElements/promptPreviewModalWindow.ts | 27 + .../publishedPromptPreviewModal.ts | 21 + .../webElements/sharedPromptPreviewModal.ts | 23 +- 30 files changed, 1173 insertions(+), 202 deletions(-) create mode 100644 apps/chat-e2e/src/assertions/promptPreviewModalAssertion.ts create mode 100644 apps/chat-e2e/src/assertions/promptToApproveAssertion.ts create mode 100644 apps/chat-e2e/src/assertions/promptToPublishAssertion.ts create mode 100644 apps/chat-e2e/src/assertions/publishedPromptPreviewModalAssertion.ts create mode 100644 apps/chat-e2e/src/tests/publishPrompt.test.ts create mode 100644 apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredEntitiesTree.ts rename apps/chat-e2e/src/ui/webElements/entityTree/sidebar/{sharedFolderPrompts.ts => organizationPromptsTree.ts} (52%) create mode 100644 apps/chat-e2e/src/ui/webElements/promptPreviewModalWindow.ts create mode 100644 apps/chat-e2e/src/ui/webElements/publishedPromptPreviewModal.ts diff --git a/apps/chat-e2e/src/assertions/baseAssertion.ts b/apps/chat-e2e/src/assertions/baseAssertion.ts index 64f56111a..a8a0165ad 100644 --- a/apps/chat-e2e/src/assertions/baseAssertion.ts +++ b/apps/chat-e2e/src/assertions/baseAssertion.ts @@ -164,6 +164,50 @@ export class BaseAssertion { .toBe(expectedColor); } + public async assertStringTruncatedTo160( + originalString: string | null | undefined, + truncatedString: string | null | undefined, + ) { + const maxLength = 160; + + // Handle null or undefined input + if (originalString == null || truncatedString == null) { + expect + .soft(originalString, 'Original string should not be null or undefined') + .not.toBeNull(); + expect + .soft( + truncatedString, + 'Truncated string should not be null or undefined', + ) + .not.toBeNull(); + return; + } + + // Handle strings shorter than the maximum length + if (originalString.length <= maxLength) { + expect + .soft(truncatedString, 'String should not be truncated') + .toBe(originalString); + return; + } + + // Assert that the truncated string has the correct length + expect + .soft( + truncatedString.length, + 'Truncated string should have a length of 160', + ) + .toBe(maxLength); + // Assert that the truncated string is a substring of the original + expect + .soft( + truncatedString, + 'Truncated string should be a substring of the original', + ) + .toBe(originalString.substring(0, maxLength)); + } + public async assertElementsCount( element: BaseElement | Locator, expectedCount: number, @@ -172,7 +216,6 @@ export class BaseAssertion { element instanceof BaseElement ? await element.getElementsCount() : await element.count(); - expect .soft(elementsCount, ExpectedMessages.elementsCountIsValid) .toBe(expectedCount); diff --git a/apps/chat-e2e/src/assertions/promptPreviewModalAssertion.ts b/apps/chat-e2e/src/assertions/promptPreviewModalAssertion.ts new file mode 100644 index 000000000..21bc9704c --- /dev/null +++ b/apps/chat-e2e/src/assertions/promptPreviewModalAssertion.ts @@ -0,0 +1,56 @@ +import { BaseAssertion } from '@/src/assertions/baseAssertion'; +import { ElementState, ExpectedMessages } from '@/src/testData'; +import { PromptPreviewModalWindow } from '@/src/ui/webElements/promptPreviewModalWindow'; + +export class PromptPreviewModalAssertion extends BaseAssertion { + readonly promptPreviewModal: PromptPreviewModalWindow; + + constructor(promptPreviewModal: PromptPreviewModalWindow) { + super(); + this.promptPreviewModal = promptPreviewModal; + } + + public async assertPromptPreviewModalState(expectedState: ElementState) { + await super.assertElementState(this.promptPreviewModal, expectedState); // Use base class method + } + + public async assertPromptPreviewModalTitle(expectedValue: string) { + await super.assertElementText( + this.promptPreviewModal.modalTitle, + expectedValue, + ExpectedMessages.modalDialogTitleIsValid, + ); // Use base class method + } + + public async assertPromptName(expectedValue: string) { + await super.assertElementText( + this.promptPreviewModal.promptName, + expectedValue, + ExpectedMessages.promptNameValid, + ); // Use base class method + } + + public async assertPromptDescription(expectedValue: string | undefined) { + if (expectedValue === '' || expectedValue === undefined) { + await super.assertElementState( + this.promptPreviewModal.promptDescription, + 'hidden', + ExpectedMessages.promptDescriptionValid, + ); + } else { + await super.assertElementText( + this.promptPreviewModal.promptDescription, + expectedValue, + ExpectedMessages.promptDescriptionValid, + ); + } + } + + public async assertPromptContent(expectedValue: string) { + await super.assertElementText( + this.promptPreviewModal.promptContent, + expectedValue, + ExpectedMessages.promptContentValid, + ); + } +} diff --git a/apps/chat-e2e/src/assertions/promptToApproveAssertion.ts b/apps/chat-e2e/src/assertions/promptToApproveAssertion.ts new file mode 100644 index 000000000..6e977bbab --- /dev/null +++ b/apps/chat-e2e/src/assertions/promptToApproveAssertion.ts @@ -0,0 +1,4 @@ +import { PublishEntityAssertion } from '@/src/assertions/publishEntityAssertion'; +import { PromptsToApproveTree } from '@/src/ui/webElements/entityTree'; + +export class PromptToApproveAssertion extends PublishEntityAssertion {} diff --git a/apps/chat-e2e/src/assertions/promptToPublishAssertion.ts b/apps/chat-e2e/src/assertions/promptToPublishAssertion.ts new file mode 100644 index 000000000..6b082da52 --- /dev/null +++ b/apps/chat-e2e/src/assertions/promptToPublishAssertion.ts @@ -0,0 +1,4 @@ +import { PublishEntityAssertion } from '@/src/assertions/publishEntityAssertion'; +import { PromptsToPublishTree } from '@/src/ui/webElements/entityTree'; + +export class PromptToPublishAssertion extends PublishEntityAssertion {} diff --git a/apps/chat-e2e/src/assertions/publishedPromptPreviewModalAssertion.ts b/apps/chat-e2e/src/assertions/publishedPromptPreviewModalAssertion.ts new file mode 100644 index 000000000..e8a306550 --- /dev/null +++ b/apps/chat-e2e/src/assertions/publishedPromptPreviewModalAssertion.ts @@ -0,0 +1,13 @@ +import { PromptPreviewModalAssertion } from '@/src/assertions/promptPreviewModalAssertion'; +import { PublishedPromptPreviewModal } from '@/src/ui/webElements/publishedPromptPreviewModal'; + +export class PublishedPromptPreviewModalAssertion extends PromptPreviewModalAssertion { + readonly publishedPromptPreviewModal: PublishedPromptPreviewModal; + + constructor(publishedPromptPreviewModal: PublishedPromptPreviewModal) { + super(publishedPromptPreviewModal); + this.publishedPromptPreviewModal = publishedPromptPreviewModal; + } + + // Add assertions specific to PublishedPromptPreviewModal here +} diff --git a/apps/chat-e2e/src/assertions/sharedPromptPreviewModalAssertion.ts b/apps/chat-e2e/src/assertions/sharedPromptPreviewModalAssertion.ts index 56eda8e35..f9fd40907 100644 --- a/apps/chat-e2e/src/assertions/sharedPromptPreviewModalAssertion.ts +++ b/apps/chat-e2e/src/assertions/sharedPromptPreviewModalAssertion.ts @@ -1,74 +1,17 @@ -import { ElementState, ExpectedMessages } from '@/src/testData'; +import { PromptPreviewModalAssertion } from '@/src/assertions/promptPreviewModalAssertion'; +import { ExpectedMessages } from '@/src/testData'; import { Styles } from '@/src/ui/domData'; import { SharedPromptPreviewModal } from '@/src/ui/webElements'; import { expect } from '@playwright/test'; -export class SharedPromptPreviewModalAssertion { +export class SharedPromptPreviewModalAssertion extends PromptPreviewModalAssertion { readonly sharedPromptPreviewModal: SharedPromptPreviewModal; constructor(sharedPromptPreviewModal: SharedPromptPreviewModal) { + super(sharedPromptPreviewModal); this.sharedPromptPreviewModal = sharedPromptPreviewModal; } - public async assertSharedPromptPreviewModalState( - expectedState: ElementState, - ) { - const sharedPromptPreviewModal = - this.sharedPromptPreviewModal.getElementLocator(); - expectedState === 'visible' - ? await expect - .soft(sharedPromptPreviewModal, ExpectedMessages.modalWindowIsOpened) - .toBeVisible() - : await expect - .soft(sharedPromptPreviewModal, ExpectedMessages.modalWindowIsClosed) - .toBeHidden(); - } - - public async assertSharedPromptPreviewModalTitle(expectedValue: string) { - expect - .soft( - await this.sharedPromptPreviewModal.modalTitle.getElementInnerContent(), - ExpectedMessages.modalDialogTitleIsValid, - ) - .toBe(expectedValue); - } - - public async assertSharedPromptName(expectedValue: string) { - expect - .soft( - await this.sharedPromptPreviewModal.promptName.getElementInnerContent(), - ExpectedMessages.promptNameValid, - ) - .toBe(expectedValue); - } - - public async assertSharedPromptDescription( - expectedValue: string | undefined, - ) { - expectedValue === '' || expectedValue === undefined - ? await expect - .soft( - this.sharedPromptPreviewModal.promptDescription.getElementLocator(), - ExpectedMessages.promptDescriptionValid, - ) - .toBeHidden() - : expect - .soft( - await this.sharedPromptPreviewModal.promptDescription.getElementInnerContent(), - ExpectedMessages.promptDescriptionValid, - ) - .toBe(expectedValue); - } - - public async assertSharedPromptContent(expectedValue: string) { - expect - .soft( - await this.sharedPromptPreviewModal.promptContent.getElementInnerContent(), - ExpectedMessages.promptContentValid, - ) - .toBe(expectedValue); - } - public async assertExportButtonColors(expectedColor: string) { const buttonColor = await this.sharedPromptPreviewModal.promptExportButton.getComputedStyleProperty( @@ -76,6 +19,7 @@ export class SharedPromptPreviewModalAssertion { ); const buttonBordersColor = await this.sharedPromptPreviewModal.promptExportButton.getAllBorderColors(); + expect .soft(buttonColor[0], ExpectedMessages.elementColorIsValid) .toBe(expectedColor); @@ -95,6 +39,7 @@ export class SharedPromptPreviewModalAssertion { ); const buttonBordersColor = await this.sharedPromptPreviewModal.promptDeleteButton.getAllBorderColors(); + expect .soft(buttonColor[0], ExpectedMessages.elementColorIsValid) .toBe(expectedColor); diff --git a/apps/chat-e2e/src/core/dialAdminFixtures.ts b/apps/chat-e2e/src/core/dialAdminFixtures.ts index 31770cc37..26ed75487 100644 --- a/apps/chat-e2e/src/core/dialAdminFixtures.ts +++ b/apps/chat-e2e/src/core/dialAdminFixtures.ts @@ -20,6 +20,8 @@ import { } from '@/src/assertions'; import { ConversationToApproveAssertion } from '@/src/assertions/conversationToApproveAssertion'; import { FolderAssertion } from '@/src/assertions/folderAssertion'; +import { PromptToApproveAssertion } from '@/src/assertions/promptToApproveAssertion'; +import { PublishedPromptPreviewModalAssertion } from '@/src/assertions/publishedPromptPreviewModalAssertion'; import { PublishingApprovalModalAssertion } from '@/src/assertions/publishingApprovalModalAssertion'; import { SideBarEntityAssertion } from '@/src/assertions/sideBarEntityAssertion'; import dialTest, { stateFilePath } from '@/src/core/dialFixtures'; @@ -27,14 +29,17 @@ import { LocalStorageManager } from '@/src/core/localStorageManager'; import { AppContainer } from '@/src/ui/webElements/appContainer'; import { ApproveRequiredConversationsTree, + ApproveRequiredPrompts, ConversationsToApproveTree, ConversationsTree, FolderConversationsToApprove, FolderPrompts, Folders, OrganizationConversationsTree, + PromptsToApproveTree, PromptsTree, } from '@/src/ui/webElements/entityTree'; +import { PublishedPromptPreviewModal } from '@/src/ui/webElements/publishedPromptPreviewModal'; import { Tooltip } from '@/src/ui/webElements/tooltip'; import { Page } from '@playwright/test'; @@ -50,13 +55,18 @@ const dialAdminTest = dialTest.extend<{ adminConversations: ConversationsTree; adminPrompts: PromptsTree; adminApproveRequiredConversations: ApproveRequiredConversationsTree; + adminApproveRequiredPrompts: ApproveRequiredPrompts; adminOrganizationFolderConversations: Folders; adminConversationsToApprove: ConversationsToApproveTree; + adminPromptsToApprove: PromptsToApproveTree; adminPublishingApprovalModal: PublishingApprovalModal; + adminPublishedPromptPreviewModal: PublishedPromptPreviewModal; adminApproveRequiredConversationsAssertion: FolderAssertion; + adminApproveRequiredPromptsAssertion: FolderAssertion; adminOrganizationFolderConversationAssertions: FolderAssertion; adminPublishingApprovalModalAssertion: PublishingApprovalModalAssertion; adminConversationToApproveAssertion: ConversationToApproveAssertion; + adminPromptToApproveAssertion: PromptToApproveAssertion; adminFolderToApproveAssertion: PublishFolderAssertion; adminPublicationReviewControl: PublicationReviewControl; adminChatHeader: ChatHeader; @@ -71,7 +81,24 @@ const dialAdminTest = dialTest.extend<{ adminApproveRequiredConversationDropdownMenuAssertion: MenuAssertion; adminTooltipAssertion: TooltipAssertion; adminOrganizationConversationAssertion: SideBarEntityAssertion; + adminPublishedPromptPreviewModalAssertion: PublishedPromptPreviewModalAssertion; }>({ + adminPublishedPromptPreviewModalAssertion: async ( + { adminPublishedPromptPreviewModal }, + use, + ) => { + const adminPublishedPromptPreviewModalAssertion = + new PublishedPromptPreviewModalAssertion( + adminPublishedPromptPreviewModal, + ); + await use(adminPublishedPromptPreviewModalAssertion); + }, + adminPublishedPromptPreviewModal: async ({ adminPage }, use) => { + const publishedPromptPreviewModal = new PublishedPromptPreviewModal( + adminPage, + ); + await use(publishedPromptPreviewModal); + }, adminPage: async ({ browser }, use) => { const context = await browser.newContext({ storageState: stateFilePath(+config.workers! * 3), @@ -114,7 +141,8 @@ const dialAdminTest = dialTest.extend<{ await use(additionalShareUserPrompts); }, adminFolderPrompts: async ({ adminPromptBar }, use) => { - const additionalShareUserFolderPrompts = adminPromptBar.getFolderPrompts(); + const additionalShareUserFolderPrompts = + adminPromptBar.getPinnedFolderPrompts(); await use(additionalShareUserFolderPrompts); }, adminApproveRequiredConversations: async ({ adminChatBar }, use) => { @@ -122,6 +150,11 @@ const dialAdminTest = dialTest.extend<{ adminChatBar.getApproveRequiredConversationsTree(); await use(adminApproveRequiredConversations); }, + adminApproveRequiredPrompts: async ({ adminPromptBar }, use) => { + const adminApproveRequiredPrompts = + adminPromptBar.getApproveRequiredPrompts(); + await use(adminApproveRequiredPrompts); + }, adminOrganizationFolderConversations: async ({ adminChatBar }, use) => { const adminOrganizationFolderConversations = adminChatBar.getOrganizationFolderConversations(); @@ -135,6 +168,11 @@ const dialAdminTest = dialTest.extend<{ adminPublishingApprovalModal.getConversationsToApproveTree(); await use(adminConversationsToApprove); }, + adminPromptsToApprove: async ({ adminPublishingApprovalModal }, use) => { + const adminPromptsToApprove = + adminPublishingApprovalModal.getPromptsToApproveTree(); + await use(adminPromptsToApprove); + }, adminPublishingApprovalModal: async ({ adminPage }, use) => { const adminPublishingApprovalModal = new PublishingApprovalModal(adminPage); await use(adminPublishingApprovalModal); @@ -197,6 +235,14 @@ const dialAdminTest = dialTest.extend<{ ); await use(adminApproveRequiredConversationsAssertion); }, + adminApproveRequiredPromptsAssertion: async ( + { adminApproveRequiredPrompts }, + use, + ) => { + const adminApproveRequiredPromptsAssertion = + new FolderAssertion(adminApproveRequiredPrompts); + await use(adminApproveRequiredPromptsAssertion); + }, adminOrganizationFolderConversationAssertions: async ( { adminOrganizationFolderConversations }, use, @@ -222,6 +268,12 @@ const dialAdminTest = dialTest.extend<{ new ConversationToApproveAssertion(adminConversationsToApprove); await use(adminConversationToApproveAssertion); }, + adminPromptToApproveAssertion: async ({ adminPromptsToApprove }, use) => { + const adminPromptToApproveAssertion = new PromptToApproveAssertion( + adminPromptsToApprove, + ); + await use(adminPromptToApproveAssertion); + }, adminFolderToApproveAssertion: async ( { adminPublishingApprovalModal }, use, diff --git a/apps/chat-e2e/src/core/dialFixtures.ts b/apps/chat-e2e/src/core/dialFixtures.ts index 405bed34c..8e6431d43 100644 --- a/apps/chat-e2e/src/core/dialFixtures.ts +++ b/apps/chat-e2e/src/core/dialFixtures.ts @@ -54,6 +54,7 @@ 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 { PromptToPublishAssertion } from '@/src/assertions/promptToPublishAssertion'; import { RenameConversationModalAssertion } from '@/src/assertions/renameConversationModalAssertion'; import { SelectFolderModalAssertion } from '@/src/assertions/selectFolderModalAssertion'; import { SettingsModalAssertion } from '@/src/assertions/settingsModalAssertion'; @@ -92,9 +93,11 @@ import { FolderPrompts, Folders, OrganizationConversationsTree, + PromptsToPublishTree, PromptsTree, PublishFolder, } from '@/src/ui/webElements/entityTree'; +import { OrganizationPromptsTree } from '@/src/ui/webElements/entityTree/sidebar/organizationPromptsTree'; import { ErrorPopup } from '@/src/ui/webElements/errorPopup'; import { ErrorToast } from '@/src/ui/webElements/errorToast'; import { Filter } from '@/src/ui/webElements/filter'; @@ -164,7 +167,9 @@ const dialTest = test.extend< prompts: PromptsTree; folderConversations: FolderConversations; folderPrompts: FolderPrompts; + organizationFolderPrompts: FolderPrompts; organizationConversations: OrganizationConversationsTree; + organizationPrompts: OrganizationPromptsTree; organizationFolderConversations: Folders; conversationSettingsModal: ConversationSettingsModal; talkToAgentDialog: TalkToAgentDialog; @@ -229,7 +234,8 @@ const dialTest = test.extend< manageAttachmentsAssertion: ManageAttachmentsAssertion; settingsModal: SettingsModal; publishingRequestModal: PublishingRequestModal; - conversationsToPublish: ConversationsToPublishTree; + conversationsToPublishTree: ConversationsToPublishTree; + promptsToPublishTree: PromptsToPublishTree; folderConversationsToPublish: FolderConversationsToPublish; publicationApiHelper: PublicationApiHelper; adminPublicationApiHelper: PublicationApiHelper; @@ -238,6 +244,7 @@ const dialTest = test.extend< conversationAssertion: ConversationAssertion; chatBarFolderAssertion: FolderAssertion; organizationConversationAssertion: SideBarEntityAssertion; + organizationPromptAssertion: SideBarEntityAssertion; errorToastAssertion: ErrorToastAssertion; downloadAssertion: DownloadAssertion; promptModalAssertion: PromptModalAssertion; @@ -245,6 +252,7 @@ const dialTest = test.extend< confirmationDialogAssertion: ConfirmationDialogAssertion; chatBarAssertion: SideBarAssertion; promptBarFolderAssertion: FolderAssertion; + promptBarOrganizationFolderAssertion: FolderAssertion; promptAssertion: PromptAssertion; promptBarAssertion: SideBarAssertion; accountSettingsAssertion: AccountSettingsAssertion; @@ -278,6 +286,7 @@ const dialTest = test.extend< publishingRequestFolderConversationAssertion: FolderAssertion; talkToAgentDialogAssertion: TalkToAgentDialogAssertion; conversationToPublishAssertion: ConversationToPublishAssertion; + promptToPublishAssertion: PromptToPublishAssertion; folderToPublishAssertion: PublishFolderAssertion; organizationFolderConversationAssertions: FolderAssertion; messageTemplateModalAssertion: MessageTemplateModalAssertion; @@ -443,14 +452,22 @@ const dialTest = test.extend< await use(promptFilterDropdownMenu); }, folderPrompts: async ({ promptBar }, use) => { - const folderPrompts = promptBar.getFolderPrompts(); + const folderPrompts = promptBar.getPinnedFolderPrompts(); await use(folderPrompts); }, + organizationFolderPrompts: async ({ promptBar }, use) => { + const organizationFolderPrompts = promptBar.getOrganizationFolderPrompts(); + await use(organizationFolderPrompts); + }, organizationConversations: async ({ chatBar }, use) => { const organizationConversations = chatBar.getOrganizationConversationsTree(); await use(organizationConversations); }, + organizationPrompts: async ({ promptBar }, use) => { + const organizationPrompts = promptBar.getOrganizationPromptsTree(); + await use(organizationPrompts); + }, conversationSettingsModal: async ({ page }, use) => { const conversationSettingsModal = new ConversationSettingsModal(page); await use(conversationSettingsModal); @@ -728,11 +745,16 @@ const dialTest = test.extend< const publishingModal = new PublishingRequestModal(page); await use(publishingModal); }, - conversationsToPublish: async ({ publishingRequestModal }, use) => { + conversationsToPublishTree: async ({ publishingRequestModal }, use) => { const conversationsToPublishTree = publishingRequestModal.getConversationsToPublishTree(); await use(conversationsToPublishTree); }, + promptsToPublishTree: async ({ publishingRequestModal }, use) => { + const promptsToPublishTree = + publishingRequestModal.getPromptsToPublishTree(); + await use(promptsToPublishTree); + }, folderConversationsToPublish: async ({ publishingRequestModal }, use) => { const folderConversationsToPublish = publishingRequestModal.getFolderConversationsToPublish(); @@ -777,6 +799,11 @@ const dialTest = test.extend< ); await use(organizationConversationAssertion); }, + organizationPromptAssertion: async ({ organizationPrompts }, use) => { + const organizationPromptAssertion = + new SideBarEntityAssertion(organizationPrompts); + await use(organizationPromptAssertion); + }, chatBarFolderAssertion: async ({ folderConversations }, use) => { const chatBarFolderAssertion = new FolderAssertion( folderConversations, @@ -816,6 +843,14 @@ const dialTest = test.extend< ); await use(promptBarFolderAssertion); }, + promptBarOrganizationFolderAssertion: async ( + { organizationFolderPrompts }, + use, + ) => { + const promptBarOrganizationFolderAssertion = + new FolderAssertion(organizationFolderPrompts); + await use(promptBarOrganizationFolderAssertion); + }, promptAssertion: async ({ prompts }, use) => { const promptAssertion = new PromptAssertion(prompts); await use(promptAssertion); @@ -965,12 +1000,21 @@ const dialTest = test.extend< ); await use(talkToAgentDialogAssertion); }, - conversationToPublishAssertion: async ({ conversationsToPublish }, use) => { + conversationToPublishAssertion: async ( + { conversationsToPublishTree }, + use, + ) => { const conversationToPublishAssertion = new ConversationToPublishAssertion( - conversationsToPublish, + conversationsToPublishTree, ); await use(conversationToPublishAssertion); }, + promptToPublishAssertion: async ({ promptsToPublishTree }, use) => { + const promptToPublishAssertion = new PromptToPublishAssertion( + promptsToPublishTree, + ); + await use(promptToPublishAssertion); + }, folderToPublishAssertion: async ({ publishingRequestModal }, use) => { const folderToPublishAssertion = new PublishFolderAssertion( publishingRequestModal.getFolderConversationsToPublish(), diff --git a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts index 36c2eb741..c7261af6e 100644 --- a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts +++ b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts @@ -54,7 +54,6 @@ import { ConversationsTree, FolderPrompts, PromptsTree, - SharedFolderPrompts, } from '@/src/ui/webElements/entityTree'; import { SharedFolderConversations } from '@/src/ui/webElements/entityTree/sidebar/sharedFolderConversations'; import { SharedWithMeConversationsTree } from '@/src/ui/webElements/entityTree/sidebar/sharedWithMeConversationsTree'; @@ -74,7 +73,7 @@ const dialSharedWithMeTest = dialTest.extend<{ additionalShareUserSharedWithMeConversations: SharedWithMeConversationsTree; additionalShareUserSharedFolderConversations: SharedFolderConversations; additionalShareUserSharedWithMePrompts: SharedWithMePromptsTree; - additionalShareUserSharedFolderPrompts: SharedFolderPrompts; + additionalShareUserSharedFolderPrompts: FolderPrompts; additionalShareUserChat: Chat; additionalShareUserConversationSettingsModal: ConversationSettingsModal; additionalShareUserAgentSettings: AgentSettings; @@ -111,7 +110,7 @@ const dialSharedWithMeTest = dialTest.extend<{ additionalShareUserSendMessageAssertion: SendMessageAssertion; additionalShareUserVariableModalAssertion: VariableModalAssertion; additionalShareUserConversationDropdownMenu: DropdownMenu; - additionalShareUserSharedFolderPromptsAssertions: FolderAssertion; + additionalShareUserSharedFolderPromptsAssertions: FolderAssertion; additionalShareUserPromptsDropdownMenuAssertion: MenuAssertion; additionalShareUserFolderDropdownMenuAssertion: MenuAssertion; additionalShareUserConfirmationDialogAssertion: ConfirmationDialogAssertion; @@ -445,7 +444,7 @@ const dialSharedWithMeTest = dialTest.extend<{ use, ) => { const additionalShareUserFolderPrompts = - additionalShareUserPromptBar.getFolderPrompts(); + additionalShareUserPromptBar.getPinnedFolderPrompts(); await use(additionalShareUserFolderPrompts); }, additionalShareUserFolderDropdownMenu: async ( @@ -570,7 +569,7 @@ const dialSharedWithMeTest = dialTest.extend<{ use, ) => { const additionalShareUserSharedFolderPromptsAssertions = - new FolderAssertion( + new FolderAssertion( additionalShareUserSharedFolderPrompts, ); await use(additionalShareUserSharedFolderPromptsAssertions); diff --git a/apps/chat-e2e/src/tests/monitoring/sharePrompt.test.ts b/apps/chat-e2e/src/tests/monitoring/sharePrompt.test.ts index b51afe7c5..a373247fe 100644 --- a/apps/chat-e2e/src/tests/monitoring/sharePrompt.test.ts +++ b/apps/chat-e2e/src/tests/monitoring/sharePrompt.test.ts @@ -54,19 +54,19 @@ dialSharedWithMeTest( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptName( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptName( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptDescription( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptDescription( prompt.description, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptContent( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptContent( prompt.content!, ); }, diff --git a/apps/chat-e2e/src/tests/promptExportImport.test.ts b/apps/chat-e2e/src/tests/promptExportImport.test.ts index 3ccc5acc9..a37665840 100644 --- a/apps/chat-e2e/src/tests/promptExportImport.test.ts +++ b/apps/chat-e2e/src/tests/promptExportImport.test.ts @@ -874,7 +874,7 @@ dialTest( await dialTest.step( 'Move 3rd level folder on the 1st level folder and import exported prompt', async () => { - await promptBar.drugAndDropFolderToFolder( + await promptBar.dragAndDropFolderToFolder( nestedFolders[levelsCount - 1].name, nestedFolders[0].name, { isHttpMethodTriggered: true }, diff --git a/apps/chat-e2e/src/tests/publishConversation.test.ts b/apps/chat-e2e/src/tests/publishConversation.test.ts index a99330ef4..d9ad27e52 100644 --- a/apps/chat-e2e/src/tests/publishConversation.test.ts +++ b/apps/chat-e2e/src/tests/publishConversation.test.ts @@ -36,7 +36,7 @@ dialAdminTest( organizationConversations, conversationDropdownMenu, publishingRequestModal, - conversationsToPublish, + conversationsToPublishTree, publishingRequestModalAssertion, iconApiHelper, tooltipAssertion, @@ -137,7 +137,7 @@ dialAdminTest( 'Set publication request name, uncheck conversation and verify tooltip on hover "Send request" button', async () => { await publishingRequestModal.requestName.fillInInput(requestName); - await conversationsToPublish + await conversationsToPublishTree .getEntityCheckbox(conversation.name) .click(); await publishingRequestModal.sendRequestButton.hoverOver(); @@ -148,7 +148,9 @@ dialAdminTest( ); await dialTest.step('Check conversation and send request', async () => { - await conversationsToPublish.getEntityCheckbox(conversation.name).click(); + await conversationsToPublishTree + .getEntityCheckbox(conversation.name) + .click(); publishApiModels = await publishingRequestModal.sendPublicationRequest(); publicationsToUnpublish.push(publishApiModels.response); }); @@ -335,7 +337,7 @@ dialAdminTest( await conversations.openEntityDropdownMenu(conversation.name); await conversationDropdownMenu.selectMenuOption(MenuOptions.publish); await publishingRequestModal.requestName.fillInInput(requestName); - await conversationsToPublish + await conversationsToPublishTree .getEntityVersion(conversation.name) .fill(ExpectedConstants.defaultAppVersion); await publishingRequestModal.sendRequestButton.click(); diff --git a/apps/chat-e2e/src/tests/publishPrompt.test.ts b/apps/chat-e2e/src/tests/publishPrompt.test.ts new file mode 100644 index 000000000..1c11f0333 --- /dev/null +++ b/apps/chat-e2e/src/tests/publishPrompt.test.ts @@ -0,0 +1,709 @@ +import { Prompt } from '@/chat/types/prompt'; +import { Publication, PublicationRequestModel } from '@/chat/types/publication'; +import dialAdminTest from '@/src/core/dialAdminFixtures'; +import dialTest from '@/src/core/dialFixtures'; +import { + API, + ExpectedConstants, + ExpectedMessages, + MenuOptions, + PublishPath, +} from '@/src/testData'; +import { Colors } from '@/src/ui/domData'; +import { GeneratorUtil } from '@/src/utils'; + +const publicationsToUnpublish: Publication[] = []; + +dialAdminTest( + 'Publish single prompt: select folder in Organization path\n' + + 'Publish prompt: create folder in Organization path\n' + + 'Publish single prompt: rename folder in Organization\n' + + 'Publish prompt:add, rename and delete options for new folder in Change path\n' + + 'Publication request name: Spaces at the beginning or end of chat name are removed\n' + + 'Publication request name: spaces in the middle of request name stay', + async ({ + dialHomePage, + promptData, + dataInjector, + prompts, + promptDropdownMenu, + publishingRequestModal, + selectFolderModal, + adminDialHomePage, + adminApproveRequiredPromptsAssertion, + adminApproveRequiredPrompts, + adminPublishingApprovalModal, + adminPublishingApprovalModalAssertion, + setTestIds, + baseAssertion, + selectFolders, + adminPublishedPromptPreviewModal, + adminPromptToApproveAssertion, + adminPublishedPromptPreviewModalAssertion, + promptBarOrganizationFolderAssertion, + organizationFolderPrompts, + confirmationDialog, + folderDropdownMenu, + }) => { + dialAdminTest.slow(); + setTestIds( + 'EPMRTC-3305', + 'EPMRTC-3595', + 'EPMRTC-3313', + 'EPMRTC-3596', + 'EPMRTC-3604', + 'EPMRTC-3606', + ); + let prompt1: Prompt; + let prompt2: Prompt; + const folderName = GeneratorUtil.randomString(10); + const requestName1WithoutLeadingAndTrailingSpaces = `${GeneratorUtil.randomPublicationRequestName()} ${GeneratorUtil.randomString(7)}`; + const requestName1WithSpaces = ` ${requestName1WithoutLeadingAndTrailingSpaces} `; + const requestName2 = GeneratorUtil.randomPublicationRequestName(); + let publishApiModels: { + request: PublicationRequestModel; + response: Publication; + }; + + await dialTest.step('Prepare 2 prompts', async () => { + prompt1 = promptData.prepareDefaultPrompt(); + promptData.resetData(); + prompt2 = promptData.prepareDefaultPrompt(); + await dataInjector.createPrompts([prompt1, prompt2]); + }); + + await dialTest.step('Publish a single prompt', async () => { + await dialHomePage.openHomePage(); + await dialHomePage.waitForPageLoaded(); + await prompts.openEntityDropdownMenu(prompt1.name); + await promptDropdownMenu.selectMenuOption(MenuOptions.publish); + await baseAssertion.assertElementState(publishingRequestModal, 'visible'); + }); + + await dialTest.step( + 'User clicks on "Change path", hover 3 dots on folder1_new, create folder2, then delete it', + async () => { + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + await selectFolderModal.newFolderButton.click(); + await selectFolders.editFolderNameWithEnter(folderName); + await selectFolders.openFolderDropdownMenu(folderName); + await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder); + await selectFolders.editFolderNameWithEnter(`${folderName} 2`); + await selectFolders.openFolderDropdownMenu(`${folderName} 2`); + await folderDropdownMenu.selectMenuOption(MenuOptions.delete); + await confirmationDialog.confirm(); + await selectFolders + .getFolderByName(`${folderName} 2`) + .waitFor({ state: 'hidden' }); + }, + ); + + await dialTest.step( + 'User reloads the page and reopens the modal in order to workaround the 2803 issue', + async () => { + await dialHomePage.reloadPage(); + await dialHomePage.waitForPageLoaded(); + await prompts.openEntityDropdownMenu(prompt1.name); + await promptDropdownMenu.selectMenuOption(MenuOptions.publish); + await baseAssertion.assertElementState( + publishingRequestModal, + 'visible', + ); + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + }, + ); + + await dialTest.step( + 'User creates folder and rename it under Organization, user renames folder', + async () => { + await selectFolderModal.newFolderButton.click(); + await selectFolders.editFolderNameWithEnter(`${folderName}_rename`); + await selectFolders.openFolderDropdownMenu(`${folderName}_rename`); + await folderDropdownMenu.selectMenuOption(MenuOptions.rename); + await selectFolders.editFolderNameWithEnter(folderName); + }, + ); + + await dialTest.step( + 'User reloads the page and reopens the modal in order to workaround the 2803 issue', + async () => { + await dialHomePage.reloadPage(); + await dialHomePage.waitForPageLoaded(); + await prompts.openEntityDropdownMenu(prompt1.name); + await promptDropdownMenu.selectMenuOption(MenuOptions.publish); + await baseAssertion.assertElementState( + publishingRequestModal, + 'visible', + ); + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + await selectFolderModal.newFolderButton.click(); + await selectFolders.editFolderNameWithEnter(folderName); + }, + ); + + await dialTest.step('User selects renamed folder', async () => { + await selectFolderModal.selectFolder(folderName); + await selectFolderModal.clickSelectFolderButton({ + triggeredApiHost: API.publicationRulesList, + }); + }); + + await dialTest.step( + 'Set publication request name, check prompt to publish and send request', + async () => { + await publishingRequestModal.requestName.fillInInput( + requestName1WithSpaces, + ); + await baseAssertion.assertElementText( + publishingRequestModal.getChangePublishToPath().path, + `${PublishPath.Organization}/${folderName}`, + ); + publishApiModels = + await publishingRequestModal.sendPublicationRequest(); + publicationsToUnpublish.push(publishApiModels.response); + }, + ); + + await dialAdminTest.step( + 'Login as admin and verify conversation publishing request is displayed under "Approve required" section', + async () => { + await adminDialHomePage.openHomePage(); + await adminDialHomePage.waitForPageLoaded(); + await adminApproveRequiredPromptsAssertion.assertFolderState( + { name: requestName1WithoutLeadingAndTrailingSpaces }, + 'visible', + ); + }, + ); + + await dialAdminTest.step( + 'Expand request folder and verify "Publication approval" modal is displayed', + async () => { + await adminApproveRequiredPrompts.expandApproveRequiredFolder( + requestName1WithoutLeadingAndTrailingSpaces, + ); + await adminApproveRequiredPromptsAssertion.assertFolderEntityState( + { name: requestName1WithoutLeadingAndTrailingSpaces }, + { name: prompt1.name }, + 'visible', + ); + await adminPublishingApprovalModalAssertion.assertElementState( + adminPublishingApprovalModal, + 'visible', + ); + await adminPublishingApprovalModalAssertion.assertElementText( + adminPublishingApprovalModal.publishToPath, + `Organization/${folderName}`, + ); + await adminPublishingApprovalModalAssertion.assertRequestCreationDate( + publishApiModels.response, + ); + await adminPromptToApproveAssertion.assertEntityState( + { name: prompt1.name }, + 'visible', + ); + await adminPromptToApproveAssertion.assertEntityColor( + { name: prompt1.name }, + Colors.textPrimary, + ); + await adminPromptToApproveAssertion.assertEntityVersion( + { name: prompt1.name }, + ExpectedConstants.defaultAppVersion, + ); + await adminPromptToApproveAssertion.assertEntityVersionColor( + { name: prompt1.name }, + Colors.textPrimary, + ); + //TODO + // await adminPromptToApproveAssertion.assertTreeEntityIcon( + // { name: prompt1.name }, + // expectedConversationIcon, + // ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.goToReviewButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.approveButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementActionabilityState( + adminPublishingApprovalModal.approveButton, + 'disabled', + ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.rejectButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementActionabilityState( + adminPublishingApprovalModal.rejectButton, + 'enabled', + ); + }, + ); + + await dialAdminTest.step( + 'Click on "Go to a review" button and verify conversation details are displayed', + async () => { + await adminPublishingApprovalModal.goToEntityReview({ + isHttpMethodTriggered: false, + }); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalState( + 'visible', + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( + prompt1.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptName( + prompt1.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptContent( + prompt1.content!, + ); + for (const element of [ + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .previousButton, + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .nextButton, + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .backToPublicationRequestButton, + adminPublishedPromptPreviewModal.promptExportButton, + ]) { + await baseAssertion.assertElementState(element, 'visible'); + } + await adminPublishedPromptPreviewModal + .getPublicationReviewControl() + .backToPublicationRequestButton.click(); + await adminPublishingApprovalModal.approveRequest(); + }, + ); + + await dialTest.step( + 'by user1 reload page and check prompt in Organization section inside folder1', + async () => { + await dialHomePage.reloadPage(); + await dialHomePage.waitForPageLoaded(); + await promptBarOrganizationFolderAssertion.assertFolderState( + { name: folderName }, + 'visible', + ); + await organizationFolderPrompts.expandFolder(folderName); + await promptBarOrganizationFolderAssertion.assertFolderEntityState( + { name: folderName }, + { name: prompt1.name }, + 'visible', + ); + }, + ); + + await dialTest.step( + 'Publish a second prompt to an existing folder', + async () => { + await prompts.openEntityDropdownMenu(prompt2.name); + await promptDropdownMenu.selectMenuOption(MenuOptions.publish); + await baseAssertion.assertElementState( + publishingRequestModal, + 'visible', + ); + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + await selectFolderModal.selectFolder(folderName); + await selectFolderModal.clickSelectFolderButton({ + triggeredApiHost: API.publicationRulesList, + }); + }, + ); + + await dialTest.step( + 'Set publication request name, check prompt to publish and send request', + async () => { + await publishingRequestModal.requestName.fillInInput(requestName2); + publishApiModels = + await publishingRequestModal.sendPublicationRequest(); + publicationsToUnpublish.push(publishApiModels.response); + }, + ); + await dialAdminTest.step( + 'Login as admin and verify conversation publishing request is displayed under "Approve required" section', + async () => { + await adminDialHomePage.reloadPage(); + await adminDialHomePage.waitForPageLoaded(); + await adminApproveRequiredPromptsAssertion.assertFolderState( + { name: requestName2 }, + 'visible', + ); + }, + ); + + await dialAdminTest.step( + 'Expand request folder and verify "Publication approval" modal is displayed', + async () => { + await adminApproveRequiredPrompts.expandApproveRequiredFolder( + requestName2, + ); + await adminApproveRequiredPromptsAssertion.assertFolderEntityState( + { name: requestName2 }, + { name: prompt2.name }, + 'visible', + ); + await adminPublishingApprovalModalAssertion.assertElementState( + adminPublishingApprovalModal, + 'visible', + ); + await adminPublishingApprovalModalAssertion.assertElementText( + adminPublishingApprovalModal.publishToPath, + `Organization/${folderName}`, + ); + await adminPublishingApprovalModalAssertion.assertRequestCreationDate( + publishApiModels.response, + ); + await adminPromptToApproveAssertion.assertEntityState( + { name: prompt2.name }, + 'visible', + ); + await adminPromptToApproveAssertion.assertEntityColor( + { name: prompt2.name }, + Colors.textPrimary, + ); + await adminPromptToApproveAssertion.assertEntityVersion( + { name: prompt2.name }, + ExpectedConstants.defaultAppVersion, + ); + await adminPromptToApproveAssertion.assertEntityVersionColor( + { name: prompt2.name }, + Colors.textPrimary, + ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.goToReviewButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.approveButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementActionabilityState( + adminPublishingApprovalModal.approveButton, + 'disabled', + ); + await adminPromptToApproveAssertion.assertElementState( + adminPublishingApprovalModal.rejectButton, + 'visible', + ); + await adminPromptToApproveAssertion.assertElementActionabilityState( + adminPublishingApprovalModal.rejectButton, + 'enabled', + ); + }, + ); + + await dialAdminTest.step( + 'Click on "Go to a review" button and verify conversation details are displayed', + async () => { + await adminPublishingApprovalModal.goToEntityReview({ + isHttpMethodTriggered: false, + }); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalState( + 'visible', + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( + prompt2.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptName( + prompt2.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptContent( + prompt2.content!, + ); + for (const element of [ + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .previousButton, + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .nextButton, + adminPublishedPromptPreviewModal.getPublicationReviewControl() + .backToPublicationRequestButton, + adminPublishedPromptPreviewModal.promptExportButton, + ]) { + await baseAssertion.assertElementState(element, 'visible'); + } + await adminPublishedPromptPreviewModal + .getPublicationReviewControl() + .backToPublicationRequestButton.click(); + await adminPublishingApprovalModal.approveRequest(); + }, + ); + }, +); + +dialAdminTest( + 'Publish prompt: add new folder inside nested folder structure with depth 4\n' + + 'Publish prompt into nested folder structure inside Organization section\n' + + 'Publish request name: tab is changed to space if to use it in chat name\n' + + 'The first 160 symbols from the input text is used as publication request name #1661\n' + + 'Publication request name can not be blank\n' + + 'Publication request name with hieroglyph, specific letters', + async ({ + dialHomePage, + promptData, + dataInjector, + prompts, + promptDropdownMenu, + publishingRequestModal, + selectFolderModal, + adminDialHomePage, + adminApproveRequiredPromptsAssertion, + adminApproveRequiredPrompts, + adminPublishingApprovalModal, + adminPublishingApprovalModalAssertion, + setTestIds, + baseAssertion, + selectFolders, + adminPublishedPromptPreviewModal, + adminPromptToApproveAssertion, + adminPublishedPromptPreviewModalAssertion, + promptBarOrganizationFolderAssertion, + organizationFolderPrompts, + folderDropdownMenu, + errorToastAssertion, + errorToast, + publishingRequestModalAssertion, + tooltipAssertion, + }) => { + dialAdminTest.slow(); + setTestIds( + 'EPMRTC-3599', + 'EPMRTC-3600', + 'EPMRTC-3601', + 'EPMRTC-3602', + 'EPMRTC-3603', + 'EPMRTC-3605', + ); + let prompt1: Prompt; + const folderNameTemplate = GeneratorUtil.randomString(10); + let folderName = folderNameTemplate; + const publicationPath = `${PublishPath.Organization}/${folderNameTemplate} 1/${folderNameTemplate} 2/${folderNameTemplate} 3/${folderNameTemplate} 4`; + const requestName = GeneratorUtil.randomPublicationRequestName(); + const requestNameWithTabs = `${requestName} Name\ttext\t1 한글이라는 고유한 문자 시스템을 사용하는데`; + const requestNameWithoutTabs = `${requestName} Name text 1 한글이라는 고유한 문자 시스템을 사용하는데`; + let publishApiModels: { + request: PublicationRequestModel; + response: Publication; + }; + + await dialTest.step('Prepare a new prompt', async () => { + prompt1 = promptData.prepareDefaultPrompt(); + await dataInjector.createPrompts([prompt1]); + }); + + await dialTest.step('Publish a single prompt', async () => { + await dialHomePage.openHomePage(); + await dialHomePage.waitForPageLoaded(); + await prompts.openEntityDropdownMenu(prompt1.name); + await promptDropdownMenu.selectMenuOption(MenuOptions.publish); + await baseAssertion.assertElementState(publishingRequestModal, 'visible'); + }); + + await dialTest.step( + 'User clicks on "Change path, creates new folder structure : Folder1->Folder1.1->Folder1.1.1-Folder1.1.1.1', + async () => { + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + await selectFolderModal.newFolderButton.click(); + await selectFolders.editFolderNameWithEnter(`${folderNameTemplate} 1`); + for (let i = 1; i < 4; i++) { + await selectFolders.openFolderDropdownMenu( + `${folderNameTemplate} ${i}`, + ); + await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder); + await selectFolders.editFolderNameWithEnter( + `${folderNameTemplate} ${i + 1}`, + ); + } + await selectFolders.openFolderDropdownMenu(`${folderNameTemplate} 4`); + await folderDropdownMenu.selectMenuOption(MenuOptions.addNewFolder); + // Assertions + await errorToastAssertion.assertToastIsVisible(); + await errorToastAssertion.assertToastMessage( + ExpectedConstants.tooManyNestedFolders, + ExpectedMessages.tooManyNestedFolders, + ); + // Bug that closing the toast leads to the closing the modal + await errorToast.closeToast(); + await publishingRequestModal + .getChangePublishToPath() + .changeButton.click(); + await selectFolders + .getFolderByName(folderNameTemplate) + .waitFor({ state: 'visible' }); + for (let i = 1; i < 4; i++) { + await selectFolders + .getFolderByName(`${folderNameTemplate} ${i}`) + .waitFor({ state: 'visible' }); + } + folderName = `${folderNameTemplate} 4`; + }, + ); + + await dialTest.step('User selects nested folder', async () => { + await selectFolderModal.selectFolder(folderName); + await selectFolderModal.clickSelectFolderButton({ + triggeredApiHost: API.publicationRulesList, + }); + }); + + //TODO + //Blocked by 1661 + // await dialTest.step( + // 'Type long solid text of 200 symbols with spaces without enters, it should be truncated to 160 symbols', + // async () => { + // const longName = `${GeneratorUtil.randomString(50)} ${GeneratorUtil.randomString(49)} ${GeneratorUtil.randomString(99)}`; + // await publishingRequestModal.requestName.fillInInput(longName); + // await publishingRequestModal.getChangePublishToPath().changeButton.click(); // Click "Change Path" to move focus + // const truncatedName = await publishingRequestModal.requestName.getElementLocator().inputValue(); + // await baseAssertion.assertStringTruncatedTo160(longName, truncatedName); + // }, + // ); + + await dialTest.step('Check empty publication request name', async () => { + await publishingRequestModalAssertion.assertSendRequestButtonIsDisabled(); + await publishingRequestModal.sendRequestButton.hoverOver(); + await tooltipAssertion.assertTooltipContent( + ExpectedConstants.noPublishNameTooltip, + ); + await publishingRequestModal.requestName.fillInInput(' '); + await publishingRequestModalAssertion.assertSendRequestButtonIsDisabled(); + await publishingRequestModal.sendRequestButton.hoverOver(); + await tooltipAssertion.assertTooltipContent( + ExpectedConstants.noPublishNameTooltip, + ); + await publishingRequestModal.requestName.fillInInput(''); // Clear the input field + }); + + await dialTest.step( + 'Set publication request name, check prompt to publish and send request', + async () => { + await publishingRequestModal.requestName.fillInInput( + requestNameWithTabs, + ); + await baseAssertion.assertElementText( + publishingRequestModal.getChangePublishToPath().path, + publicationPath, + ); + publishApiModels = + await publishingRequestModal.sendPublicationRequest(); + publicationsToUnpublish.push(publishApiModels.response); + }, + ); + + await dialAdminTest.step( + 'Login as admin and verify conversation publishing request is displayed under "Approve required" section', + async () => { + await adminDialHomePage.openHomePage(); + await adminDialHomePage.waitForPageLoaded(); + await adminApproveRequiredPromptsAssertion.assertFolderState( + { name: requestNameWithoutTabs }, + 'visible', + ); + }, + ); + + await dialAdminTest.step( + 'Expand request folder and verify "Publication approval" modal is displayed', + async () => { + await adminApproveRequiredPrompts.expandApproveRequiredFolder( + requestNameWithoutTabs, + ); + await adminApproveRequiredPromptsAssertion.assertFolderEntityState( + { name: requestNameWithoutTabs }, + { name: prompt1.name }, + 'visible', + ); + await adminPublishingApprovalModalAssertion.assertElementState( + adminPublishingApprovalModal, + 'visible', + ); + await adminPromptToApproveAssertion.assertEntityState( + { name: prompt1.name }, + 'visible', + ); + await adminPromptToApproveAssertion.assertEntityColor( + { name: prompt1.name }, + Colors.textPrimary, + ); + await adminPromptToApproveAssertion.assertEntityVersion( + { name: prompt1.name }, + ExpectedConstants.defaultAppVersion, + ); + await adminPromptToApproveAssertion.assertEntityVersionColor( + { name: prompt1.name }, + Colors.textPrimary, + ); + }, + ); + + await dialAdminTest.step( + 'Click on "Go to a review" button and verify conversation details are displayed', + async () => { + await adminPublishingApprovalModal.goToEntityReview({ + isHttpMethodTriggered: false, + }); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalState( + 'visible', + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( + prompt1.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptName( + prompt1.name, + ); + await adminPublishedPromptPreviewModalAssertion.assertPromptContent( + prompt1.content!, + ); + await adminPublishedPromptPreviewModal + .getPublicationReviewControl() + .backToPublicationRequestButton.click(); + await adminPublishingApprovalModal.approveRequest(); + }, + ); + + await dialTest.step( + 'by user1 reload page and check prompt in Organization section inside folder1', + async () => { + await dialHomePage.reloadPage(); + await dialHomePage.waitForPageLoaded(); + for (let i = 1; i < 4; i++) { + await organizationFolderPrompts.expandFolder( + `${folderNameTemplate} ${i}`, + ); + } + await promptBarOrganizationFolderAssertion.assertFolderState( + { name: folderName }, + 'visible', + ); + await organizationFolderPrompts.expandFolder(folderName); + await promptBarOrganizationFolderAssertion.assertFolderEntityState( + { name: folderName }, + { name: prompt1.name }, + 'visible', + ); + }, + ); + }, +); + +dialTest.afterAll( + async ({ publicationApiHelper, adminPublicationApiHelper }) => { + for (const publication of publicationsToUnpublish) { + const unpublishResponse = + await publicationApiHelper.createUnpublishRequest(publication); + await adminPublicationApiHelper.approveRequest(unpublishResponse); + } + }, +); diff --git a/apps/chat-e2e/src/tests/sharedPromptFolderIcons.test.ts b/apps/chat-e2e/src/tests/sharedPromptFolderIcons.test.ts index 0d9602b82..003b4c275 100644 --- a/apps/chat-e2e/src/tests/sharedPromptFolderIcons.test.ts +++ b/apps/chat-e2e/src/tests/sharedPromptFolderIcons.test.ts @@ -80,7 +80,7 @@ dialTest( 'Add a new empty folder to a folder that gonna be shared', async () => { await promptBar.createNewFolder(); - await promptBar.drugAndDropFolderToFolder( + await promptBar.dragAndDropFolderToFolder( ExpectedConstants.newFolderWithIndexTitle(1), nestedFolders[sharedFolderIndex].name, ); diff --git a/apps/chat-e2e/src/tests/sharedPromptView.test.ts b/apps/chat-e2e/src/tests/sharedPromptView.test.ts index 8a1f53a57..fe3100a8d 100644 --- a/apps/chat-e2e/src/tests/sharedPromptView.test.ts +++ b/apps/chat-e2e/src/tests/sharedPromptView.test.ts @@ -56,19 +56,19 @@ dialSharedWithMeTest( await dialSharedWithMeTest.step( 'Verify prompt preview modal is opened, prompt parameters are valid', async () => { - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptName( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptName( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptDescription( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptDescription( prompt.description, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptContent( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptContent( prompt.content!, ); }, diff --git a/apps/chat-e2e/src/tests/sharedWithMePromptFolders.test.ts b/apps/chat-e2e/src/tests/sharedWithMePromptFolders.test.ts index a42909a96..23550d4b5 100644 --- a/apps/chat-e2e/src/tests/sharedWithMePromptFolders.test.ts +++ b/apps/chat-e2e/src/tests/sharedWithMePromptFolders.test.ts @@ -68,7 +68,7 @@ dialSharedWithMeTest( await additionalShareUserDialHomePage.waitForPageLoaded({ isPromptShared: true, }); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); await additionalShareUserPromptPreviewModal.closeButton.click(); @@ -91,7 +91,7 @@ dialSharedWithMeTest( await additionalShareUserDialHomePage.waitForPageLoaded({ isPromptShared: true, }); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); await additionalShareUserPromptPreviewModal.closeButton.click(); @@ -160,10 +160,10 @@ dialSharedWithMeTest( await additionalShareUserDialHomePage.waitForPageLoaded({ isPromptShared: true, }); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( nestedPrompts[sharedFolderIndex].name, ); await additionalShareUserPromptPreviewModal.closeButton.click(); @@ -230,10 +230,10 @@ dialSharedWithMeTest( await additionalShareUserDialHomePage.waitForPageLoaded({ isPromptShared: true, }); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( nestedPrompts[sharedFolderIndex].name, ); await additionalShareUserPromptPreviewModal.closeButton.click(); @@ -304,19 +304,19 @@ dialSharedWithMeTest( await additionalShareUserDialHomePage.waitForPageLoaded({ isPromptShared: true, }); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( nestedPrompts[sharedFolderIndex].name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptName( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptName( nestedPrompts[sharedFolderIndex].name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptDescription( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptDescription( nestedPrompts[sharedFolderIndex].description, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptContent( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptContent( nestedPrompts[sharedFolderIndex].content!, ); await additionalShareUserPromptPreviewModal.closeButton.click(); diff --git a/apps/chat-e2e/src/tests/sharedWithMePrompts.test.ts b/apps/chat-e2e/src/tests/sharedWithMePrompts.test.ts index 437322fda..4e87dd91d 100644 --- a/apps/chat-e2e/src/tests/sharedWithMePrompts.test.ts +++ b/apps/chat-e2e/src/tests/sharedWithMePrompts.test.ts @@ -64,19 +64,19 @@ dialSharedWithMeTest( { name: prompt.name }, Colors.backgroundAccentTertiaryAlphaDark, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalState( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalState( 'visible', ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptPreviewModalTitle( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptPreviewModalTitle( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptName( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptName( prompt.name, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptDescription( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptDescription( promptDescription, ); - await additionalShareUserSharedPromptPreviewModalAssertion.assertSharedPromptContent( + await additionalShareUserSharedPromptPreviewModalAssertion.assertPromptContent( promptContent, ); }, diff --git a/apps/chat-e2e/src/tests/sidePanelEntityDragAndDrop.test.ts b/apps/chat-e2e/src/tests/sidePanelEntityDragAndDrop.test.ts index 4335e15af..cc556b474 100644 --- a/apps/chat-e2e/src/tests/sidePanelEntityDragAndDrop.test.ts +++ b/apps/chat-e2e/src/tests/sidePanelEntityDragAndDrop.test.ts @@ -351,7 +351,7 @@ dialTest( ), ); - await promptBar.drugPromptToFolder( + await promptBar.dragPromptToFolder( ExpectedConstants.newFolderWithIndexTitle(1), prompt.name, ); @@ -422,7 +422,7 @@ dialTest( await dialHomePage.openHomePage(); await dialHomePage.waitForPageLoaded(); await folderPrompts.expandFolder(promptInFolder.folders.name); - await promptBar.drugAndDropPromptToFolderPrompt( + await promptBar.dragAndDropPromptToFolderPrompt( promptInFolder.folders.name, promptInFolder.prompts[0].name, prompt.name, diff --git a/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts b/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts index 230068f49..1ca685561 100644 --- a/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts +++ b/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts @@ -47,12 +47,14 @@ export const PromptBarSelectors = { prompts: '[data-qa="prompts-section-container"] >> [data-qa="prompts"]', prompt: '[data-qa="prompt"]', deletePrompts: '[data-qa="delete-prompts"]', - pinnedChats: () => + pinnedPrompts: () => `${PromptBarSelectors.promptFolders} > [data-qa="pinned-prompts-container"]`, sharedWithMePrompts: () => `${PromptBarSelectors.promptFolders} > ${SideBarSelectors.sharedWithMeContainer}`, approveRequiredPrompts: () => `${PromptBarSelectors.promptFolders} > ${SideBarSelectors.approveRequiredContainer}`, + organizationPrompts: () => + `${PromptBarSelectors.promptFolders} > ${SideBarSelectors.organizationContainer}`, leftResizeIcon: '[data-qa="left-resize-icon"]', newEntity: '[data-qa="new-entity"]', }; diff --git a/apps/chat-e2e/src/ui/webElements/chatBar.ts b/apps/chat-e2e/src/ui/webElements/chatBar.ts index bf6786f21..531fd85f1 100644 --- a/apps/chat-e2e/src/ui/webElements/chatBar.ts +++ b/apps/chat-e2e/src/ui/webElements/chatBar.ts @@ -9,7 +9,6 @@ import { MenuOptions } from '@/src/testData'; import { DropdownMenu } from '@/src/ui/webElements/dropdownMenu'; import { ApproveRequiredConversationsTree, - ApproveRequiredPrompts, ConversationsTree, FolderConversations, Folders, @@ -31,7 +30,6 @@ export class ChatBar extends SideBar { private sharedFolderConversations!: SharedFolderConversations; private approveRequiredConversationsTree!: ApproveRequiredConversationsTree; private organizationFolderConversations!: Folders; - private approveRequiredPrompts!: ApproveRequiredPrompts; private organizationConversations!: OrganizationConversationsTree; private bottomDropdownMenu!: DropdownMenu; public compareButton = this.getChildElementBySelector( @@ -107,16 +105,6 @@ export class ChatBar extends SideBar { return this.organizationFolderConversations; } - getApproveRequiredPrompts(): ApproveRequiredPrompts { - if (!this.approveRequiredPrompts) { - this.approveRequiredPrompts = new ApproveRequiredPrompts( - this.page, - this.getElementLocator(), - ); - } - return this.approveRequiredPrompts; - } - getOrganizationConversationsTree(): OrganizationConversationsTree { if (!this.organizationConversations) { this.organizationConversations = new OrganizationConversationsTree( diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/index.ts b/apps/chat-e2e/src/ui/webElements/entityTree/index.ts index bfb3e7f02..bfba30365 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/index.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/index.ts @@ -13,7 +13,6 @@ export * from './sidebar/folderConversations'; export * from './sidebar/folderPrompts'; export * from './sidebar/sharedFolderConversations'; export * from './sidebar/sharedWithMePromptsTree'; -export * from './sidebar/sharedFolderPrompts'; export * from './sidebar/approveRequiredConversationsTree'; export * from './sidebar/approveRequiredPrompts'; export * from './sidebar/organizationConversationsTree'; diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredConversationsTree.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredConversationsTree.ts index 174a7c0b5..6fd7a9712 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredConversationsTree.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredConversationsTree.ts @@ -1,9 +1,9 @@ -import { API } from '@/src/testData'; +import { ApproveRequiredEntitiesTree } from './approveRequiredEntitiesTree'; + import { ChatBarSelectors, EntitySelectors } from '@/src/ui/selectors'; -import { Folders } from '@/src/ui/webElements/entityTree'; import { Locator, Page } from '@playwright/test'; -export class ApproveRequiredConversationsTree extends Folders { +export class ApproveRequiredConversationsTree extends ApproveRequiredEntitiesTree { constructor(page: Page, parentLocator: Locator) { super( page, @@ -12,25 +12,4 @@ export class ApproveRequiredConversationsTree extends Folders { EntitySelectors.conversation, ); } - - public async expandApproveRequiredFolder( - requestName: string, - options: { isHttpMethodTriggered?: boolean; httpHost?: string } = { - isHttpMethodTriggered: true, - httpHost: API.publicationRequestDetails, - }, - ) { - await this.expandFolder(requestName, options); - } - - public async selectRequestConversation( - requestName: string, - conversationName: string, - ) { - const responsePromise = this.page.waitForResponse( - (r) => r.request().method() === 'GET', - ); - await this.selectFolderEntity(requestName, conversationName); - await responsePromise; - } } diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredEntitiesTree.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredEntitiesTree.ts new file mode 100644 index 000000000..05f050f13 --- /dev/null +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredEntitiesTree.ts @@ -0,0 +1,32 @@ +import { API } from '@/src/testData'; +import { Folders } from '@/src/ui/webElements/entityTree'; +import { Locator, Page } from '@playwright/test'; + +export class ApproveRequiredEntitiesTree extends Folders { + constructor( + page: Page, + parentLocator: Locator, + selector: string, + entitySelector: string, + ) { + super(page, parentLocator, selector, entitySelector); + } + + public async expandApproveRequiredFolder( + requestName: string, + options: { isHttpMethodTriggered?: boolean; httpHost?: string } = { + isHttpMethodTriggered: true, + httpHost: API.publicationRequestDetails, + }, + ) { + await this.expandFolder(requestName, options); + } + + public async selectRequestEntity(requestName: string, entityName: string) { + const responsePromise = this.page.waitForResponse( + (r) => r.request().method() === 'GET', + ); + await this.selectFolderEntity(requestName, entityName); + await responsePromise; + } +} diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredPrompts.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredPrompts.ts index 81931e01e..2b51d357b 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredPrompts.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/approveRequiredPrompts.ts @@ -1,8 +1,9 @@ +import { ApproveRequiredEntitiesTree } from './approveRequiredEntitiesTree'; + import { EntitySelectors, PromptBarSelectors } from '@/src/ui/selectors'; -import { Folders } from '@/src/ui/webElements/entityTree'; import { Locator, Page } from '@playwright/test'; -export class ApproveRequiredPrompts extends Folders { +export class ApproveRequiredPrompts extends ApproveRequiredEntitiesTree { constructor(page: Page, parentLocator: Locator) { super( page, diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/folderPrompts.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/folderPrompts.ts index 64374b5c9..f348c6a0e 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/folderPrompts.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/folderPrompts.ts @@ -3,13 +3,32 @@ import { EntitySelectors, PromptBarSelectors } from '../../../selectors'; import { Folders } from '@/src/ui/webElements/entityTree/folders'; import { Locator, Page } from '@playwright/test'; +export enum PromptBarSection { + Organization = 'Organization', + PinnedPrompts = 'PinnedPrompts', + SharedWithMe = 'SharedWithMe', + ApproveRequired = 'ApproveRequired', +} + export class FolderPrompts extends Folders { - constructor(page: Page, parentLocator: Locator) { - super( - page, - parentLocator, - PromptBarSelectors.pinnedChats(), - EntitySelectors.prompt, - ); + constructor(page: Page, parentLocator: Locator, section?: PromptBarSection) { + let selector: string; + switch (section) { + case PromptBarSection.Organization: + selector = PromptBarSelectors.organizationPrompts(); + break; + case PromptBarSection.PinnedPrompts: + selector = PromptBarSelectors.pinnedPrompts(); + break; + case PromptBarSection.SharedWithMe: + selector = PromptBarSelectors.sharedWithMePrompts(); + break; + case PromptBarSection.ApproveRequired: + selector = PromptBarSelectors.approveRequiredPrompts(); + break; + default: + selector = PromptBarSelectors.pinnedPrompts(); + } + super(page, parentLocator, selector, EntitySelectors.prompt); } } diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sharedFolderPrompts.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/organizationPromptsTree.ts similarity index 52% rename from apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sharedFolderPrompts.ts rename to apps/chat-e2e/src/ui/webElements/entityTree/sidebar/organizationPromptsTree.ts index e2e54adb5..7b7422a6f 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sharedFolderPrompts.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/organizationPromptsTree.ts @@ -1,14 +1,14 @@ import { EntitySelectors, PromptBarSelectors } from '../../../selectors'; -import { Folders } from '@/src/ui/webElements/entityTree'; +import { BaseSideBarConversationTree } from '@/src/ui/webElements/entityTree/sidebar/baseSideBarConversationTree'; import { Locator, Page } from '@playwright/test'; -export class SharedFolderPrompts extends Folders { +export class OrganizationPromptsTree extends BaseSideBarConversationTree { constructor(page: Page, parentLocator: Locator) { super( page, parentLocator, - PromptBarSelectors.sharedWithMePrompts(), + PromptBarSelectors.organizationPrompts(), EntitySelectors.prompt, ); } diff --git a/apps/chat-e2e/src/ui/webElements/promptBar.ts b/apps/chat-e2e/src/ui/webElements/promptBar.ts index aa4fe6586..874a33174 100644 --- a/apps/chat-e2e/src/ui/webElements/promptBar.ts +++ b/apps/chat-e2e/src/ui/webElements/promptBar.ts @@ -2,11 +2,13 @@ import { PromptBarSelectors, SideBarSelectors } from '../selectors'; import { Styles, removeAlpha } from '@/src/ui/domData'; import { + ApproveRequiredPrompts, FolderPrompts, + PromptBarSection, PromptsTree, - SharedFolderPrompts, SharedWithMePromptsTree, } from '@/src/ui/webElements/entityTree'; +import { OrganizationPromptsTree } from '@/src/ui/webElements/entityTree/sidebar/organizationPromptsTree'; import { SideBar } from '@/src/ui/webElements/sideBar'; import { Page } from '@playwright/test'; @@ -16,18 +18,23 @@ export class PromptBar extends SideBar { } private promptsTree!: PromptsTree; - private folderPrompts!: FolderPrompts; private sharedWithMePromptsTree!: SharedWithMePromptsTree; - private sharedFolderPrompts!: SharedFolderPrompts; + private approveRequiredPrompts!: ApproveRequiredPrompts; + private organizationPrompts!: OrganizationPromptsTree; - getFolderPrompts(): FolderPrompts { - if (!this.folderPrompts) { - this.folderPrompts = new FolderPrompts( + private pinnedFolderPrompts!: FolderPrompts; + private sharedFolderPrompts!: FolderPrompts; + private organizationFolderPrompts!: FolderPrompts; + private approveRequiredFolderPrompts!: FolderPrompts; + + getApproveRequiredPrompts(): ApproveRequiredPrompts { + if (!this.approveRequiredPrompts) { + this.approveRequiredPrompts = new ApproveRequiredPrompts( this.page, this.getElementLocator(), ); } - return this.folderPrompts; + return this.approveRequiredPrompts; } getPromptsTree(): PromptsTree { @@ -37,24 +44,24 @@ export class PromptBar extends SideBar { return this.promptsTree; } - getSharedWithMePromptsTree(): SharedWithMePromptsTree { - if (!this.sharedWithMePromptsTree) { - this.sharedWithMePromptsTree = new SharedWithMePromptsTree( + getOrganizationPromptsTree(): OrganizationPromptsTree { + if (!this.organizationPrompts) { + this.organizationPrompts = new OrganizationPromptsTree( this.page, - this.rootLocator, + this.getElementLocator(), ); } - return this.sharedWithMePromptsTree; + return this.organizationPrompts; } - getSharedFolderPrompts(): SharedFolderPrompts { - if (!this.sharedFolderPrompts) { - this.sharedFolderPrompts = new SharedFolderPrompts( + getSharedWithMePromptsTree(): SharedWithMePromptsTree { + if (!this.sharedWithMePromptsTree) { + this.sharedWithMePromptsTree = new SharedWithMePromptsTree( this.page, - this.getElementLocator(), + this.rootLocator, ); } - return this.sharedFolderPrompts; + return this.sharedWithMePromptsTree; } public newEntityButton = this.getChildElementBySelector( @@ -82,12 +89,56 @@ export class PromptBar extends SideBar { return this.newEntityButton.getComputedStyleProperty(Styles.cursor); } + getSharedFolderPrompts(): FolderPrompts { + if (!this.sharedFolderPrompts) { + this.sharedFolderPrompts = new FolderPrompts( + this.page, + this.getElementLocator(), + PromptBarSection.SharedWithMe, + ); + } + return this.sharedFolderPrompts; + } + + getPinnedFolderPrompts(): FolderPrompts { + if (!this.pinnedFolderPrompts) { + this.pinnedFolderPrompts = new FolderPrompts( + this.page, + this.getElementLocator(), + PromptBarSection.PinnedPrompts, + ); + } + return this.pinnedFolderPrompts; + } + + getOrganizationFolderPrompts(): FolderPrompts { + if (!this.organizationFolderPrompts) { + this.organizationFolderPrompts = new FolderPrompts( + this.page, + this.getElementLocator(), + PromptBarSection.Organization, + ); + } + return this.organizationFolderPrompts; + } + + getApproveRequiredFolderPrompts(): FolderPrompts { + if (!this.approveRequiredFolderPrompts) { + this.approveRequiredFolderPrompts = new FolderPrompts( + this.page, + this.getElementLocator(), + PromptBarSection.ApproveRequired, + ); + } + return this.approveRequiredFolderPrompts; + } + public async dragAndDropPromptFromFolder( folderName: string, promptName: string, { isHttpMethodTriggered = false }: { isHttpMethodTriggered?: boolean } = {}, ) { - const folderPrompt = this.getFolderPrompts().getFolderEntity( + const folderPrompt = this.getPinnedFolderPrompts().getFolderEntity( folderName, promptName, ); @@ -96,19 +147,19 @@ export class PromptBar extends SideBar { }); } - public async drugPromptToFolder(folderName: string, promptName: string) { - const folder = this.getFolderPrompts().getFolderByName(folderName); + public async dragPromptToFolder(folderName: string, promptName: string) { + const folder = this.getPinnedFolderPrompts().getFolderByName(folderName); const prompt = this.getPromptsTree().getEntityByName(promptName); await this.dragEntityToFolder(prompt, folder); } - public async drugAndDropPromptToFolderPrompt( + public async dragAndDropPromptToFolderPrompt( folderName: string, folderPromptName: string, promptName: string, { isHttpMethodTriggered = false }: { isHttpMethodTriggered?: boolean } = {}, ) { - const folderPrompt = this.getFolderPrompts().getFolderEntity( + const folderPrompt = this.getPinnedFolderPrompts().getFolderEntity( folderName, folderPromptName, ); @@ -118,12 +169,12 @@ export class PromptBar extends SideBar { }); } - public async drugAndDropFolderToFolder( + public async dragAndDropFolderToFolder( folderNameToMove: string, folderNameToMoveTo: string, { isHttpMethodTriggered = false }: { isHttpMethodTriggered?: boolean } = {}, ) { - const folderPrompts = this.getFolderPrompts(); + const folderPrompts = this.getPinnedFolderPrompts(); const folderToMove = folderPrompts.getFolderByName(folderNameToMove); const folderToMoveTo = folderPrompts.getFolderByName(folderNameToMoveTo); await this.dragAndDropEntityToFolder(folderToMove, folderToMoveTo, { diff --git a/apps/chat-e2e/src/ui/webElements/promptPreviewModalWindow.ts b/apps/chat-e2e/src/ui/webElements/promptPreviewModalWindow.ts new file mode 100644 index 000000000..16c550bbc --- /dev/null +++ b/apps/chat-e2e/src/ui/webElements/promptPreviewModalWindow.ts @@ -0,0 +1,27 @@ +import { IconSelectors } from '@/src/ui/selectors'; +import { PromptPreviewModal } from '@/src/ui/selectors/dialogSelectors'; +import { BaseElement } from '@/src/ui/webElements/baseElement'; +import { Page } from '@playwright/test'; + +export class PromptPreviewModalWindow extends BaseElement { + constructor(page: Page) { + super(page, PromptPreviewModal.promptPreviewModal); + } + + public modalTitle = this.getChildElementBySelector( + PromptPreviewModal.promptPreviewModalTitle, + ); + public promptDescription = this.getChildElementBySelector( + PromptPreviewModal.promptPreviewDescription, + ); + public promptName = this.getChildElementBySelector( + PromptPreviewModal.promptPreviewName, + ); + public promptContent = this.getChildElementBySelector( + PromptPreviewModal.promptPreviewContent, + ); + public promptExportButton = this.getChildElementBySelector( + PromptPreviewModal.promptExportButton, + ); + public closeButton = this.getChildElementBySelector(IconSelectors.cancelIcon); +} diff --git a/apps/chat-e2e/src/ui/webElements/publishedPromptPreviewModal.ts b/apps/chat-e2e/src/ui/webElements/publishedPromptPreviewModal.ts new file mode 100644 index 000000000..f180f02b1 --- /dev/null +++ b/apps/chat-e2e/src/ui/webElements/publishedPromptPreviewModal.ts @@ -0,0 +1,21 @@ +import { PromptPreviewModalWindow } from '@/src/ui/webElements/promptPreviewModalWindow'; +import { PublicationReviewControl } from '@/src/ui/webElements/publicationReviewControl'; +import { Page } from 'playwright-chromium'; + +export class PublishedPromptPreviewModal extends PromptPreviewModalWindow { + constructor(page: Page) { + super(page); + } + + private publicationReviewControl: PublicationReviewControl; + + getPublicationReviewControl(): PublicationReviewControl { + if (!this.publicationReviewControl) { + this.publicationReviewControl = new PublicationReviewControl( + this.page, + this.rootLocator, + ); + } + return this.publicationReviewControl; + } +} diff --git a/apps/chat-e2e/src/ui/webElements/sharedPromptPreviewModal.ts b/apps/chat-e2e/src/ui/webElements/sharedPromptPreviewModal.ts index ceccf2f9c..fc8ecc498 100644 --- a/apps/chat-e2e/src/ui/webElements/sharedPromptPreviewModal.ts +++ b/apps/chat-e2e/src/ui/webElements/sharedPromptPreviewModal.ts @@ -1,36 +1,17 @@ import { isApiStorageType } from '@/src/hooks/global-setup'; -import { IconSelectors } from '@/src/ui/selectors'; import { PromptPreviewModal } from '@/src/ui/selectors/dialogSelectors'; -import { BaseElement } from '@/src/ui/webElements/baseElement'; -import { Page } from '@playwright/test'; +import { PromptPreviewModalWindow } from '@/src/ui/webElements/promptPreviewModalWindow'; -export class SharedPromptPreviewModal extends BaseElement { - constructor(page: Page) { - super(page, PromptPreviewModal.promptPreviewModal); - } - - public modalTitle = this.getChildElementBySelector( - PromptPreviewModal.promptPreviewModalTitle, - ); - public promptName = this.getChildElementBySelector( - PromptPreviewModal.promptPreviewName, - ); +export class SharedPromptPreviewModal extends PromptPreviewModalWindow { public promptDescription = this.getChildElementBySelector( PromptPreviewModal.promptPreviewDescription, ); - public promptContent = this.getChildElementBySelector( - PromptPreviewModal.promptPreviewContent, - ); - public promptExportButton = this.getChildElementBySelector( - PromptPreviewModal.promptExportButton, - ); public promptDeleteButton = this.getChildElementBySelector( PromptPreviewModal.promptDeleteButton, ); public promptDuplicateButton = this.getChildElementBySelector( PromptPreviewModal.promptDuplicateButton, ); - public closeButton = this.getChildElementBySelector(IconSelectors.cancelIcon); public async duplicatePrompt({ isHttpMethodTriggered = true, From d941e1c9adc606eba5710f6d4dff9bbe1a4ac697 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Sat, 21 Dec 2024 16:35:52 +0100 Subject: [PATCH 04/12] fix(chat): fix slider overload and keyboard arrows navigation (Issue #2825) (#2862) --- .../src/components/Chat/TalkTo/TalkToCard.tsx | 22 +- .../components/Chat/TalkTo/TalkToModal.tsx | 458 +++--------------- .../components/Chat/TalkTo/TalkToSlider.tsx | 419 ++++++++++++++++ 3 files changed, 500 insertions(+), 399 deletions(-) create mode 100644 apps/chat/src/components/Chat/TalkTo/TalkToSlider.tsx diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx index 7d6004337..360405d99 100644 --- a/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx +++ b/apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx @@ -22,7 +22,6 @@ import { 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 { @@ -53,8 +52,7 @@ import { ApplicationTopic } from '@/src/components/Marketplace/ApplicationTopic' import { FunctionStatusIndicator } from '@/src/components/Marketplace/FunctionStatusIndicator'; import LoaderIcon from '@/public/images/icons/loader.svg'; -import UnpublishIcon from '@/public/images/icons/unpublish.svg'; -import { Feature, PublishActions } from '@epam/ai-dial-shared'; +import { Feature } from '@epam/ai-dial-shared'; const DESKTOP_ICON_SIZE = 80; const TABLET_ICON_SIZE = 48; @@ -82,7 +80,7 @@ interface ApplicationCardProps { disabled: boolean; isUnavailableModel: boolean; onClick: (entity: DialAIEntityModel) => void; - onPublish: (entity: DialAIEntityModel, action: PublishActions) => void; + onPublish: (entity: DialAIEntityModel) => void; onDelete: (entity: DialAIEntityModel) => void; onEdit: (entity: DialAIEntityModel) => void; onSelectVersion: (entity: DialAIEntityModel) => void; @@ -159,7 +157,7 @@ export const TalkToCard = ({ const handleSelectVersion = useCallback( (model: DialAIEntityModel) => { - onSelectVersion?.(model); + onSelectVersion(model); }, [onSelectVersion], ); @@ -215,17 +213,7 @@ export const TalkToCard = ({ Icon: IconWorldShare, onClick: (e: React.MouseEvent) => { e.stopPropagation(); - 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); + onPublish(entity); }, }, { @@ -326,7 +314,7 @@ export const TalkToCard = ({ )}
-
+
{!!versionsToSelect.length && (

{t('Version')}:

diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx index d9434e8b7..c84951111 100644 --- a/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx +++ b/apps/chat/src/components/Chat/TalkTo/TalkToModal.tsx @@ -1,9 +1,5 @@ -import { - IconCaretLeftFilled, - IconCaretRightFilled, - IconSearch, -} from '@tabler/icons-react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { IconSearch } from '@tabler/icons-react'; +import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useTranslation } from 'next-i18next'; @@ -11,9 +7,6 @@ import Link from 'next/link'; import classNames from 'classnames'; -import { useScreenState } from '@/src/hooks/useScreenState'; -import { useSwipe } from '@/src/hooks/useSwipe'; - import { getApplicationType } from '@/src/utils/app/application'; import { getConversationModelParams, @@ -21,10 +14,10 @@ import { } from '@/src/utils/app/conversation'; import { getFolderIdFromEntityId } from '@/src/utils/app/folders'; import { doesEntityContainSearchTerm } from '@/src/utils/app/search'; -import { ApiUtils, PseudoModel, isPseudoModel } from '@/src/utils/server/api'; +import { ApiUtils, PseudoModel } from '@/src/utils/server/api'; import { Conversation } from '@/src/types/chat'; -import { EntityType, ScreenState } from '@/src/types/common'; +import { EntityType } from '@/src/types/common'; import { ModalState } from '@/src/types/modal'; import { DialAIEntityModel } from '@/src/types/models'; import { SharingType } from '@/src/types/share'; @@ -44,121 +37,12 @@ import { PublishModal } from '@/src/components/Chat/Publish/PublishWizard'; import { ApplicationWizard } from '@/src/components/Common/ApplicationWizard/ApplicationWizard'; 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 { TalkToSlider } from './TalkToSlider'; 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'; - -const COMMON_GRID_TILES_GAP = 16; -const MOBILE_GRID_TILES_GAP = 12; -const getMaxChunksCountConfig = () => { - return { - [ScreenState.DESKTOP]: { - cardHeight: 166, - maxRows: 3, - cols: 3, - }, - [ScreenState.TABLET]: { - cardHeight: 160, - maxRows: 4, - cols: 2, - }, - [ScreenState.MOBILE]: { - cardHeight: 98, - maxRows: 5, - cols: 1, - }, - }; -}; -interface SliderModelsGroupProps { - modelsGroup: DialAIEntityModel[]; - conversation: Conversation; - screenState: ScreenState; - rowsCount: number; - onEditApplication: (entity: DialAIEntityModel) => void; - onDeleteApplication: (entity: DialAIEntityModel) => void; - onSetPublishEntity: ( - entity: DialAIEntityModel, - action: PublishActions, - ) => void; - onSelectModel: (entity: DialAIEntityModel) => void; - onOpenLogs: (entity: DialAIEntityModel) => void; -} -const SliderModelsGroup = ({ - modelsGroup, - conversation, - screenState, - rowsCount, - onEditApplication, - onDeleteApplication, - onSetPublishEntity, - onSelectModel, - onOpenLogs, -}: SliderModelsGroupProps) => { - const config = getMaxChunksCountConfig(); - - const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); - - return ( -
model.id).join('.')} - className="h-full min-w-full" - > -
- {modelsGroup.map((model) => { - const isNotPseudoModelSelected = - model.reference === conversation.model.id && - !conversation.playback?.isPlayback && - !conversation.replay?.replayAsIs; - const isPseudoModelSelected = - model.reference === PseudoModel.Playback || - (model.reference === REPLAY_AS_IS_MODEL && - !!conversation.replay?.replayAsIs); - - return ( - - ); - })} -
-
- ); -}; interface TalkToModalViewProps { conversation: Conversation; @@ -167,15 +51,6 @@ interface TalkToModalViewProps { onClose: () => void; } -const SLIDES_GAP = 16; -const calculateTranslateX = (activeSlide: number, clientWidth?: number) => { - if (!clientWidth) return 'none'; - - const offset = activeSlide * (clientWidth + SLIDES_GAP); - - return `translateX(-${offset}px)`; -}; - const TalkToModalView = ({ conversation, isCompareMode, @@ -186,6 +61,9 @@ const TalkToModalView = ({ const dispatch = useDispatch(); + const isMarketplaceEnabled = useAppSelector((state) => + SettingsSelectors.isFeatureEnabled(state, Feature.Marketplace), + ); const allModels = useAppSelector(ModelsSelectors.selectModels); const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); const addonsMap = useAppSelector(AddonsSelectors.selectAddonsMap); @@ -195,29 +73,17 @@ const TalkToModalView = ({ const recentModelIds = useAppSelector(ModelsSelectors.selectRecentModelsIds); const [searchTerm, setSearchTerm] = useState(''); - const [activeSlide, setActiveSlide] = useState(0); const [editModel, setEditModel] = useState(); const [deleteModel, setDeleteModel] = useState(); const [logModel, setLogModel] = useState(); - const [publishModel, setPublishModel] = useState<{ - entity: ShareEntity & { iconUrl?: string }; - action: PublishActions; - }>(); - const [sliderHeight, setSliderHeight] = useState(0); + const [publishModel, setPublishModel] = useState< + ShareEntity & { iconUrl?: string } + >(); const [sharedConversationNewModel, setSharedConversationNewModel] = useState(); - const [isOpenLogs, setIsOpenLogs] = useState(); - - const sliderRef = useRef(null); - - const screenState = useScreenState(); 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]; @@ -307,70 +173,6 @@ const TalkToModalView = ({ t, ]); - const sliderRowsCount = useMemo(() => { - const availableRows = - Math.floor(sliderHeight / config[screenState].cardHeight) || 1; - - const finalRows = - availableRows === 1 - ? availableRows - : Math.floor( - (sliderHeight - - (availableRows - 1) * - (screenState === ScreenState.MOBILE - ? MOBILE_GRID_TILES_GAP - : COMMON_GRID_TILES_GAP)) / - config[screenState].cardHeight, - ) || 1; - - return finalRows > config[screenState].maxRows - ? config[screenState].maxRows - : finalRows; - }, [config, screenState, sliderHeight]); - - const sliderGroups = useMemo(() => { - return chunk(displayedModels, sliderRowsCount * config[screenState].cols); - }, [config, displayedModels, screenState, sliderRowsCount]); - - const sliderDotsArray = range(0, sliderGroups.length); - - const swipeHandlers = useSwipe({ - onSwipedLeft: () => { - setActiveSlide((slide) => - slide >= sliderGroups.length - 1 ? sliderGroups.length - 1 : slide + 1, - ); - }, - onSwipedRight: () => { - setActiveSlide((slide) => (slide === 0 ? 0 : slide - 1)); - }, - }); - - useEffect(() => { - const handleResize = () => { - if (sliderRef.current) { - setSliderHeight(sliderRef.current.clientHeight); - } - }; - - const resizeObserver = new ResizeObserver(handleResize); - - if (sliderRef.current) { - resizeObserver.observe(sliderRef.current); - } - - return () => { - resizeObserver.disconnect(); - }; - }, []); - - useEffect(() => { - if (!sliderGroups.length) { - setActiveSlide(0); - } else if (activeSlide !== 0 && activeSlide > sliderGroups.length - 1) { - setActiveSlide(sliderGroups.length - 1); - } - }, [activeSlide, sliderGroups]); - const handleUpdateConversationModel = useCallback( (entity: DialAIEntityModel) => { const model = modelsMap[entity.reference]; @@ -400,6 +202,15 @@ const TalkToModalView = ({ [addonsMap, conversation, dispatch, modelsMap, onClose], ); + const handleCloseApplicationLogs = useCallback( + () => setLogModel(undefined), + [], + ); + + const handleOpenApplicationLogs = useCallback((entity: DialAIEntityModel) => { + setLogModel(entity); + }, []); + const handleSelectModel = useCallback( (entity: DialAIEntityModel) => { if (conversation.isShared && entity.reference !== conversation.model.id) { @@ -440,32 +251,17 @@ const TalkToModalView = ({ [deleteModel, dispatch], ); - 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 handleSetPublishEntity = useCallback((entity: DialAIEntityModel) => { + setPublishModel({ + name: entity.name, + id: ApiUtils.decodeApiUrl(entity.id), + folderId: getFolderIdFromEntityId(entity.id), + iconUrl: entity.iconUrl, + }); + }, []); 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); @@ -473,32 +269,6 @@ const TalkToModalView = ({ [setDeleteModel], ); - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'ArrowRight') { - setActiveSlide((activeSlide) => - activeSlide === sliderDotsArray.length - 1 - ? activeSlide - : activeSlide + 1, - ); - } else if (e.key === 'ArrowLeft') { - setActiveSlide((activeSlide) => - activeSlide === 0 ? activeSlide : activeSlide - 1, - ); - } - }, - [sliderDotsArray.length], - ); - - useEffect(() => { - if (isPlayback) { - window.addEventListener('keydown', handleKeyDown); - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - } - }, [handleKeyDown, isPlayback]); - return ( <>

@@ -519,110 +289,33 @@ const TalkToModalView = ({ data-qa="search-agents" />

-
-
+ + {isMarketplaceEnabled && ( + + conversation.playback?.isPlayback ? e.preventDefault() : null + } className={classNames( - 'flex size-full', - sliderGroups.length && 'transition duration-1000 ease-out', + 'm-auto mt-4 text-accent-primary md:absolute md:bottom-6 md:right-6', + conversation.playback?.isPlayback && 'cursor-not-allowed', )} - style={{ - transform: calculateTranslateX( - activeSlide, - sliderRef.current?.clientWidth, - ), - gap: `${SLIDES_GAP}px`, - }} + data-qa="go-to-my-workspace" > - {sliderGroups.length ? ( - sliderGroups.map((modelsGroup) => ( - model.id).join('.')} - modelsGroup={modelsGroup} - conversation={conversation} - screenState={screenState} - rowsCount={sliderRowsCount} - onEditApplication={handleEditApplication} - onDeleteApplication={handleDeleteApplication} - onSetPublishEntity={handleSetPublishEntity} - onSelectModel={handleSelectModel} - onOpenLogs={handleOpenApplicationLogs} - /> - )) - ) : ( -
- -
- )} -
-
-
-
-
- {sliderDotsArray.length <= 1 && - screenState === ScreenState.MOBILE && ( - - )} - {sliderDotsArray.length > 1 && ( - <> - - {sliderDotsArray.map((slideNumber) => ( - - ))} - - - )} -
- {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')} - - )} -
-
+ {t('Go to My workspace')} + + )} {editModel && ( )} - {logModel && isOpenLogs && ( + {logModel && ( )} - - { - if (result && sharedConversationNewModel) { - handleUpdateConversationModel(sharedConversationNewModel); + {sharedConversationNewModel && ( + { + if (result && sharedConversationNewModel) { + handleUpdateConversationModel(sharedConversationNewModel); + } - setSharedConversationNewModel(undefined); - }} - /> + setSharedConversationNewModel(undefined); + }} + /> + )} ); }; @@ -712,7 +406,7 @@ export const TalkToModal = ({ portalId="theme-main" state={ModalState.OPENED} dataQa="talk-to-agent" - containerClassName="flex xl:h-fit max-h-full flex-col rounded py-4 px-3 md:p-6 w-full grow items-start justify-center !bg-layer-2 md:w-[728px] md:max-w-[728px] xl:w-[1200px] xl:max-w-[1200px]" + containerClassName="flex xl:h-fit relative max-h-full flex-col rounded py-4 px-3 md:p-6 w-full grow items-start justify-center !bg-layer-2 md:w-[728px] md:max-w-[728px] xl:w-[1200px] xl:max-w-[1200px]" onClose={onClose} > { + if (!clientWidth) return 'none'; + + const offset = activeSlide * (clientWidth + SLIDES_GAP); + + return `translateX(-${offset}px)`; +}; + +const getDotSizeClass = ( + slideNumber: number, + activeSlide: number, + slidesCount: number, +) => { + if (slideNumber === activeSlide) { + return 'h-2 w-8'; + } + + if (slidesCount < 7) { + return 'size-2'; + } + + const offsetActive = activeSlide - slideNumber; + const offsetLast = slidesCount - activeSlide; + + if ( + (offsetActive === 3 && activeSlide > 2 && offsetLast > 3) || + (offsetLast === 3 && slideNumber < activeSlide - 3) || + (offsetLast === 2 && slideNumber < activeSlide - 4) || + (offsetLast === 1 && slideNumber < activeSlide - 5) || + (activeSlide <= 3 && slideNumber === 6) || + (activeSlide > 3 && slideNumber > activeSlide + 2) + ) { + return 'size-1'; + } + + if ( + (offsetActive === 2 && activeSlide > 1 && offsetLast > 3) || + (offsetLast === 3 && slideNumber < activeSlide - 2) || + (offsetLast === 2 && slideNumber < activeSlide - 3) || + (offsetLast === 1 && slideNumber < activeSlide - 4) || + (activeSlide <= 3 && slideNumber === 5) || + (activeSlide > 3 && slideNumber > activeSlide + 1) + ) { + return 'size-1.5'; + } + + return 'size-2'; +}; + +interface SliderModelsGroupProps { + modelsGroup: DialAIEntityModel[]; + conversation: Conversation; + screenState: ScreenState; + rowsCount: number; + onEdit: (entity: DialAIEntityModel) => void; + onDelete: (entity: DialAIEntityModel) => void; + onPublish: (entity: DialAIEntityModel) => void; + onSelectModel: (entity: DialAIEntityModel) => void; + onOpenLogs: (entity: DialAIEntityModel) => void; +} + +const SliderModelsGroup = ({ + modelsGroup, + conversation, + screenState, + rowsCount, + onSelectModel, + ...restProps +}: SliderModelsGroupProps) => { + const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); + + return ( +
model.id).join('.')} + className="h-full min-w-full" + > +
+ {modelsGroup.map((model) => { + const isNotPseudoModelSelected = + model.reference === conversation.model.id && + !conversation.playback?.isPlayback && + !conversation.replay?.replayAsIs; + const isPseudoModelSelected = + model.reference === PseudoModel.Playback || + (model.reference === REPLAY_AS_IS_MODEL && + !!conversation.replay?.replayAsIs); + + return ( + + ); + })} +
+
+ ); +}; + +interface Props { + conversation: Conversation; + items: DialAIEntityModel[]; + onEdit: (entity: DialAIEntityModel) => void; + onDelete: (entity: DialAIEntityModel) => void; + onPublish: (entity: DialAIEntityModel) => void; + onSelectModel: (entity: DialAIEntityModel) => void; + onOpenLogs: (entity: DialAIEntityModel) => void; +} + +export const TalkToSlider = ({ conversation, items, ...restProps }: Props) => { + const sliderRef = useRef(null); + + const [activeSlide, setActiveSlide] = useState(0); + const [sliderHeight, setSliderHeight] = useState(0); + + const screenState = useScreenState(); + + const sliderRowsCount = useMemo(() => { + const availableRows = + Math.floor(sliderHeight / maxChunksCountConfig[screenState].cardHeight) || + 1; + + const finalRows = + availableRows === 1 + ? availableRows + : Math.floor( + (sliderHeight - + (availableRows - 1) * + (screenState === ScreenState.MOBILE + ? MOBILE_GRID_TILES_GAP + : COMMON_GRID_TILES_GAP)) / + maxChunksCountConfig[screenState].cardHeight, + ) || 1; + + return finalRows > maxChunksCountConfig[screenState].maxRows + ? maxChunksCountConfig[screenState].maxRows + : finalRows; + }, [screenState, sliderHeight]); + + const sliderGroups = useMemo(() => { + return chunk( + items, + sliderRowsCount * maxChunksCountConfig[screenState].cols, + ); + }, [items, screenState, sliderRowsCount]); + + const sliderDotsArray = useMemo(() => { + return range(0, sliderGroups.length); + }, [sliderGroups.length]); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'ArrowRight') { + setActiveSlide((activeSlide) => + activeSlide === sliderDotsArray.length - 1 + ? activeSlide + : activeSlide + 1, + ); + } else if (e.key === 'ArrowLeft') { + setActiveSlide((activeSlide) => + activeSlide === 0 ? activeSlide : activeSlide - 1, + ); + } + }, + [sliderDotsArray.length], + ); + + useEffect(() => { + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [handleKeyDown]); + + const swipeHandlers = useSwipe({ + onSwipedLeft: () => { + setActiveSlide((slide) => + slide >= sliderGroups.length - 1 ? sliderGroups.length - 1 : slide + 1, + ); + }, + onSwipedRight: () => { + setActiveSlide((slide) => (slide === 0 ? 0 : slide - 1)); + }, + }); + + useEffect(() => { + const handleResize = () => { + if (sliderRef.current) { + setSliderHeight(sliderRef.current.clientHeight); + } + }; + + const resizeObserver = new ResizeObserver(handleResize); + + if (sliderRef.current) { + resizeObserver.observe(sliderRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + useEffect(() => { + const handleResize = () => { + if (sliderRef.current) { + setSliderHeight(sliderRef.current.clientHeight); + } + }; + + const resizeObserver = new ResizeObserver(handleResize); + + if (sliderRef.current) { + resizeObserver.observe(sliderRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + useEffect(() => { + if (!sliderGroups.length) { + setActiveSlide(0); + } else if (activeSlide !== 0 && activeSlide > sliderGroups.length - 1) { + setActiveSlide(sliderGroups.length - 1); + } + }, [activeSlide, sliderGroups]); + + const excessDots = sliderDotsArray.length - MAX_VISIBLE_SLIDER_DOTS; + const maxDotsTranslate = Math.max(0, excessDots * SLIDER_DOT_SIZE_WITH_GAPS); + const translateXValue = Math.max( + 0, + Math.min(maxDotsTranslate, (activeSlide - 3) * SLIDER_DOT_SIZE_WITH_GAPS), + ); + + return ( + <> +
+
+ {sliderGroups.length ? ( + sliderGroups.map((modelsGroup) => ( + model.id).join('.')} + modelsGroup={modelsGroup} + conversation={conversation} + screenState={screenState} + rowsCount={sliderRowsCount} + {...restProps} + /> + )) + ) : ( +
+ +
+ )} +
+
+
+
+
+ {sliderDotsArray.length <= 1 && + screenState === ScreenState.MOBILE && ( + + )} + {sliderDotsArray.length > 1 && ( + <> + +
+
+ {sliderDotsArray.map((slideNumber) => { + return ( +
setActiveSlide(slideNumber)} + className="flex min-w-2 items-center justify-center" + > +
+ ); + })} +
+
+ + + )} +
+
+
+ + ); +}; From d98273c8599cf46d37f4978812e2d041ff5189c5 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:19:13 +0100 Subject: [PATCH 05/12] fix(chat): fix settings modal opening delay (Issue #2863) (#2864) --- apps/chat/src/hooks/useTokenizer.ts | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/chat/src/hooks/useTokenizer.ts b/apps/chat/src/hooks/useTokenizer.ts index 875d9ec1e..a7efdf548 100644 --- a/apps/chat/src/hooks/useTokenizer.ts +++ b/apps/chat/src/hooks/useTokenizer.ts @@ -1,29 +1,40 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { DialAIEntityModel } from '../types/models'; import { Tiktoken, get_encoding } from 'tiktoken'; export const useTokenizer = (tokenizer: DialAIEntityModel['tokenizer']) => { - const [encoding, setEncoding] = useState(undefined); + const encodingRef = useRef(null); useEffect(() => { - if (tokenizer?.encoding) { - setEncoding(get_encoding(tokenizer.encoding)); + // clean up if tokenizer changed + if (encodingRef.current) { + encodingRef.current.free(); + encodingRef.current = null; } + + // use microtask to not block the thread and isMounted variable to prevent task execution if component unmounted + let isMounted = true; + Promise.resolve().then(() => { + if (isMounted && tokenizer?.encoding) { + encodingRef.current = get_encoding(tokenizer.encoding); + } + }); + + return () => { + isMounted = false; + if (encodingRef.current) { + encodingRef.current.free(); + encodingRef.current = null; + } + }; }, [tokenizer]); - useEffect(() => { - return () => encoding?.free(); + const getTokensLength = useCallback((str: string) => { + return encodingRef.current?.encode(str).length ?? new Blob([str]).size; }, []); - const getTokensLength = useCallback( - (str: string) => { - return encoding?.encode(str).length ?? new Blob([str]).size; - }, - [encoding], - ); - return { getTokensLength, }; From 53c780295c28c867f4b1802945687c2c1e8b0dcd Mon Sep 17 00:00:00 2001 From: Gimir Date: Mon, 23 Dec 2024 20:12:53 +0300 Subject: [PATCH 06/12] fix(chat): add confirmation dialog for code editor exit without saving (Issue #2734) (#2830) Co-authored-by: Magomed-Elbi Dzhukalaev --- .../components/Common/ApplicationDialog.tsx | 0 .../CodeAppView/CodeAppView.tsx | 132 +++++++++++++----- .../src/components/Common/OptionsDialog.tsx | 78 +++++++++++ .../src/components/Common/QuickAppDialog.tsx | 0 .../src/store/codeEditor/codeEditor.epics.ts | 29 ++++ .../store/codeEditor/codeEditor.reducer.ts | 3 +- .../store/codeEditor/codeEditor.selectors.ts | 5 + 7 files changed, 209 insertions(+), 38 deletions(-) delete mode 100644 apps/chat/src/components/Common/ApplicationDialog.tsx create mode 100644 apps/chat/src/components/Common/OptionsDialog.tsx delete mode 100644 apps/chat/src/components/Common/QuickAppDialog.tsx diff --git a/apps/chat/src/components/Common/ApplicationDialog.tsx b/apps/chat/src/components/Common/ApplicationDialog.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx index ba12794cd..65dcd4b72 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useMemo } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { useTranslation } from 'next-i18next'; @@ -15,7 +15,10 @@ import { import { Translation } from '@/src/types/translation'; import { ApplicationActions } from '@/src/store/application/application.reducers'; -import { CodeEditorActions } from '@/src/store/codeEditor/codeEditor.reducer'; +import { + CodeEditorActions, + CodeEditorSelectors, +} from '@/src/store/codeEditor/codeEditor.reducer'; import { FilesSelectors } from '@/src/store/files/files.reducers'; import { useAppDispatch, useAppSelector } from '@/src/store/hooks'; import { ModelsSelectors } from '@/src/store/models/models.reducers'; @@ -47,6 +50,7 @@ import { withErrorMessage } from '@/src/components/Common/Forms/FieldErrorMessag import { FieldTextArea } from '@/src/components/Common/Forms/FieldTextArea'; import { withLabel } from '@/src/components/Common/Forms/Label'; import { MultipleComboBox } from '@/src/components/Common/MultipleComboBox'; +import { OptionsDialog } from '@/src/components/Common/OptionsDialog'; import { CustomLogoSelect } from '@/src/components/Settings/CustomLogoSelect'; import { ViewProps } from '../view-props'; @@ -82,6 +86,9 @@ export const CodeAppView: FC = ({ ); const isAppDeployed = selectedApplication && isApplicationDeployed(selectedApplication); + const isCodeEditorDirty = useAppSelector(CodeEditorSelectors.selectIsDirty); + + const [editorConfirmation, setEditorConfirmation] = useState(); useEffect(() => { return () => { @@ -121,50 +128,94 @@ export const CodeAppView: FC = ({ [files], ); - const handleSubmit = (data: FormData) => { - const preparedData = getApplicationData(data, type); - - if (type === ApplicationType.CODE_APP) { - preparedData.functionStatus = selectedApplication?.functionStatus; - } - - if ( - isEdit && - selectedApplication?.name && - currentReference && - selectedApplication.id - ) { - const applicationData: CustomApplicationModel = { - ...preparedData, - reference: currentReference, - id: selectedApplication.id, - }; - - dispatch( - ApplicationActions.update({ - oldApplicationId: selectedApplication.id, - applicationData, - }), - ); - isAppDeployed && + const handleSubmit = useCallback( + (data: FormData) => { + const preparedData = getApplicationData(data, type); + + if (type === ApplicationType.CODE_APP) { + preparedData.functionStatus = selectedApplication?.functionStatus; + } + + if ( + isEdit && + selectedApplication?.name && + currentReference && + selectedApplication.id + ) { + const applicationData: CustomApplicationModel = { + ...preparedData, + reference: currentReference, + id: selectedApplication.id, + }; + dispatch( - UIActions.showWarningToast( - t('Saved changes will be applied during next deployment'), - ), + ApplicationActions.update({ + oldApplicationId: selectedApplication.id, + applicationData, + }), ); - } else { - dispatch(ApplicationActions.create(preparedData)); - } + isAppDeployed && + dispatch( + UIActions.showWarningToast( + t('Saved changes will be applied during next deployment'), + ), + ); + } else { + dispatch(ApplicationActions.create(preparedData)); + } + + onClose(true); + }, + [ + currentReference, + dispatch, + isAppDeployed, + isEdit, + onClose, + selectedApplication, + t, + type, + ], + ); - onClose(true); - }; + const handleSave = useCallback( + (data: FormData) => { + if (isCodeEditorDirty) setEditorConfirmation(data); + else handleSubmit(data); + }, + [handleSubmit, isCodeEditorDirty], + ); + + const modalOptions = useMemo( + () => [ + { + label: 'Save', + dataQa: 'save-option', + onClick: () => { + dispatch(CodeEditorActions.saveAllModifiedFiles()); + editorConfirmation && handleSubmit(editorConfirmation); + setEditorConfirmation(undefined); + }, + }, + { + label: "Don't save", + dataQa: 'not-save-option', + className: 'button-secondary', + onClick: () => { + editorConfirmation && handleSubmit(editorConfirmation); + setEditorConfirmation(undefined); + }, + }, + ], + [editorConfirmation, dispatch, handleSubmit], + ); register('sourceFiles', validators['sourceFiles']); const sources = watch('sources'); return (
@@ -313,6 +364,13 @@ export const CodeAppView: FC = ({ />
+ setEditorConfirmation(undefined)} + options={modalOptions} + /> + undefined; + +interface Props { + isOpen: boolean; + heading: string; + options: { + label: string; + dataQa: string; + className?: string; + onClick: () => void; + }[]; + description?: string; + headingClassName?: string; + onClose?: () => void; +} + +export const OptionsDialog = ({ + heading, + headingClassName, + description, + isOpen, + onClose, + options, +}: Props) => { + const descriptionId = useId(); + + return ( + +
+
+
+ {description && ( +

+ {description} +

+ )} +
+
+
+ {options.map((option) => ( + + ))} +
+
+
+ ); +}; diff --git a/apps/chat/src/components/Common/QuickAppDialog.tsx b/apps/chat/src/components/Common/QuickAppDialog.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/chat/src/store/codeEditor/codeEditor.epics.ts b/apps/chat/src/store/codeEditor/codeEditor.epics.ts index d37dd4766..be76e5d6c 100644 --- a/apps/chat/src/store/codeEditor/codeEditor.epics.ts +++ b/apps/chat/src/store/codeEditor/codeEditor.epics.ts @@ -28,6 +28,8 @@ import { FilesActions, FilesSelectors } from '../files/files.reducers'; import { UIActions, UISelectors } from '../ui/ui.reducers'; import { CodeEditorActions, CodeEditorSelectors } from './codeEditor.reducer'; +import { intersectionWith } from 'lodash-es'; + const initCodeEditorEpic: AppEpic = (action$, state$) => action$.pipe( filter(CodeEditorActions.initCodeEditor.match), @@ -216,10 +218,37 @@ const updateFileContentEpic: AppEpic = (action$, state$) => }), ); +const saveAllModifiedFilesEpic: AppEpic = (action$, state$) => + action$.pipe( + filter(CodeEditorActions.saveAllModifiedFiles.match), + switchMap(() => { + const modifiedFileIds = CodeEditorSelectors.selectModifiedFileIds( + state$.value, + ); + const filesContent = CodeEditorSelectors.selectFilesContent(state$.value); + + const changedFiles = intersectionWith( + filesContent, + modifiedFileIds, + (file, id) => file.id === id, + ); + + return concat( + changedFiles.map((file) => + CodeEditorActions.updateFileContent({ + id: file.id, + content: file.modifiedContent ?? file.content, + }), + ), + ); + }), + ); + export const CodeEditorEpics = combineEpics( initCodeEditorEpic, getFileTextContentEpic, setSelectedFileEpic, deleteFileEpic, updateFileContentEpic, + saveAllModifiedFilesEpic, ); diff --git a/apps/chat/src/store/codeEditor/codeEditor.reducer.ts b/apps/chat/src/store/codeEditor/codeEditor.reducer.ts index 973bfb75a..5eb800cbc 100644 --- a/apps/chat/src/store/codeEditor/codeEditor.reducer.ts +++ b/apps/chat/src/store/codeEditor/codeEditor.reducer.ts @@ -89,7 +89,7 @@ export const codeEditorSlice = createSlice({ }, deleteFile: ( state, - _state: PayloadAction<{ id: string; sourcesFolderId: string }>, + _action: PayloadAction<{ id: string; sourcesFolderId: string }>, ) => state, deleteFileSuccess: (state, { payload }: PayloadAction<{ id: string }>) => { state.filesContent = state.filesContent.filter( @@ -117,6 +117,7 @@ export const codeEditorSlice = createSlice({ return file; }); }, + saveAllModifiedFiles: (state) => state, }, }); diff --git a/apps/chat/src/store/codeEditor/codeEditor.selectors.ts b/apps/chat/src/store/codeEditor/codeEditor.selectors.ts index 040d7218b..50053866c 100644 --- a/apps/chat/src/store/codeEditor/codeEditor.selectors.ts +++ b/apps/chat/src/store/codeEditor/codeEditor.selectors.ts @@ -18,6 +18,11 @@ export const selectModifiedFileIds = createSelector( }, ); +export const selectIsDirty = createSelector( + [selectModifiedFileIds], + (ids) => !!ids.length, +); + export const selectFileContent = (fileId: string) => createSelector([selectFilesContent], (filesContents) => { return filesContents.find((file) => file.id === fileId); From 69b9dfd4ba548dcbcf5b90b11c64eac46672871e Mon Sep 17 00:00:00 2001 From: Denys_Kolomiitsev <143205282+denys-kolomiitsev@users.noreply.github.com> Date: Mon, 23 Dec 2024 18:30:42 +0100 Subject: [PATCH 07/12] fix(chat): add validation (#2866) Co-authored-by: Ilya Bondar --- .../src/pages/api/[entitytype]/[...slug].ts | 6 +---- apps/chat/src/pages/api/listing/multiple.ts | 23 ++++++++++++------- .../api/publication/[entitytype]/[...slug].ts | 6 +---- apps/chat/src/utils/server/api.ts | 5 ++++ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/chat/src/pages/api/[entitytype]/[...slug].ts b/apps/chat/src/pages/api/[entitytype]/[...slug].ts index f92bc025b..2f3dd110f 100644 --- a/apps/chat/src/pages/api/[entitytype]/[...slug].ts +++ b/apps/chat/src/pages/api/[entitytype]/[...slug].ts @@ -4,11 +4,11 @@ import { JWT, getToken } from 'next-auth/jwt'; import { constructPath } from '@/src/utils/app/file'; import { validateServerSession } from '@/src/utils/auth/session'; +import { isValidEntityApiType } from '@/src/utils/server/api'; import { getApiHeaders } from '@/src/utils/server/get-headers'; import { logger } from '@/src/utils/server/logger'; import { ServerUtils } from '@/src/utils/server/server'; -import { ApiKeys } from '@/src/types/common'; import { DialAIError } from '@/src/types/error'; import { HTTPMethod } from '@/src/types/http'; @@ -40,10 +40,6 @@ const getEntityUrlFromSlugs = ( ); }; -const isValidEntityApiType = (apiKey: string): boolean => { - return Object.values(ApiKeys).includes(apiKey as ApiKeys); -}; - const handler = async (req: NextApiRequest, res: NextApiResponse) => { const entityType = ServerUtils.getEntityTypeFromPath(req); if (!entityType || !isValidEntityApiType(entityType)) { diff --git a/apps/chat/src/pages/api/listing/multiple.ts b/apps/chat/src/pages/api/listing/multiple.ts index 92a457603..c58fafc5e 100644 --- a/apps/chat/src/pages/api/listing/multiple.ts +++ b/apps/chat/src/pages/api/listing/multiple.ts @@ -4,6 +4,7 @@ import { getServerSession } from 'next-auth/next'; import { constructPath } from '@/src/utils/app/file'; import { validateServerSession } from '@/src/utils/auth/session'; +import { isValidEntityApiType } from '@/src/utils/server/api'; import { getApiHeaders } from '@/src/utils/server/get-headers'; import { logger } from '@/src/utils/server/logger'; @@ -15,6 +16,7 @@ import { errorsMessages } from '@/src/constants/errors'; import { authOptions } from '@/src/pages/api/auth/[...nextauth]'; +import { sanitizeUri } from 'micromark-util-sanitize-uri'; import fetch from 'node-fetch'; const handler = async (req: NextApiRequest, res: NextApiResponse) => { @@ -37,14 +39,19 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }; const token = await getToken({ req }); - const apiUrls = body.urls.map( - (url) => - `${constructPath( - process.env.DIAL_API_HOST, - 'v1/metadata', - url, - )}/?limit=${limit}&recursive=${recursive}`, - ); + const apiUrls = body.urls + .map((url) => { + const entityType = url.split('/')[0]; + if (isValidEntityApiType(entityType)) { + return `${constructPath( + process.env.DIAL_API_HOST, + 'v1/metadata', + sanitizeUri(url), + )}/?limit=${limit}&recursive=${recursive}`; + } + return; + }) + .filter(Boolean) as string[]; const fetchPromises = apiUrls.map(async (url) => { const response = await fetch(url, { diff --git a/apps/chat/src/pages/api/publication/[entitytype]/[...slug].ts b/apps/chat/src/pages/api/publication/[entitytype]/[...slug].ts index c31e4bb87..d4867e014 100644 --- a/apps/chat/src/pages/api/publication/[entitytype]/[...slug].ts +++ b/apps/chat/src/pages/api/publication/[entitytype]/[...slug].ts @@ -4,11 +4,11 @@ import { getToken } from 'next-auth/jwt'; import { constructPath } from '@/src/utils/app/file'; import { validateServerSession } from '@/src/utils/auth/session'; +import { isValidEntityApiType } from '@/src/utils/server/api'; import { getApiHeaders } from '@/src/utils/server/get-headers'; import { logger } from '@/src/utils/server/logger'; import { ServerUtils } from '@/src/utils/server/server'; -import { ApiKeys } from '@/src/types/common'; import { DialAIError } from '@/src/types/error'; import { errorsMessages } from '@/src/constants/errors'; @@ -39,10 +39,6 @@ const getEntityUrlFromSlugs = ( ); }; -const isValidEntityApiType = (apiKey: string): boolean => { - return Object.values(ApiKeys).includes(apiKey as ApiKeys); -}; - const handler = async (req: NextApiRequest, res: NextApiResponse) => { const entityType = ServerUtils.getEntityTypeFromPath(req); if (!entityType || !isValidEntityApiType(entityType)) { diff --git a/apps/chat/src/utils/server/api.ts b/apps/chat/src/utils/server/api.ts index 705abd9d4..425dfe2d0 100644 --- a/apps/chat/src/utils/server/api.ts +++ b/apps/chat/src/utils/server/api.ts @@ -5,6 +5,7 @@ import { ServerUtils } from '@/src/utils/server/server'; import { ApplicationInfo } from '@/src/types/applications'; import { Conversation } from '@/src/types/chat'; +import { ApiKeys } from '@/src/types/common'; import { HTTPMethod } from '@/src/types/http'; import { PromptInfo } from '@/src/types/prompt'; @@ -283,3 +284,7 @@ export const getPublicItemIdWithoutVersion = (version: string, id: string) => { export const addVersionToId = (id: string, version: string) => [id, version].join(pathKeySeparator); + +export const isValidEntityApiType = (apiKey: string): boolean => { + return Object.values(ApiKeys).includes(apiKey as ApiKeys); +}; From f41850e4044b6cb52b3117a23582b24a04aabb36 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:08:37 +0100 Subject: [PATCH 08/12] fix(chat): show like/dislike/copy buttons only if content message hasn't error message or content isn't empty (Issue #125) (#2865) --- .../Chat/ChatMessage/MessageButtons.tsx | 139 +++++++++--------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx b/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx index 85e095fd0..0e7249dc1 100644 --- a/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx +++ b/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx @@ -154,81 +154,84 @@ export const MessageAssistantButtons = ({ )} - {messageCopied ? ( - - - - ) : ( - - - - )} + {(message.content.trim() || !message.errorMessage) && + (messageCopied ? ( + + + + ) : ( + + + + ))}
- {isLikesEnabled && !!message.responseId && ( - <> - {message.like !== LikeState.Disliked && ( - - - - )} - {message.like !== LikeState.Liked && ( - - + + )} + {message.like !== LikeState.Liked && ( + - - - - )} - - )} + + + )} + + )}
); From 4a368cf1901f658c7d1fb56447a05a4ecd662622 Mon Sep 17 00:00:00 2001 From: Ilya Bondar Date: Tue, 24 Dec 2024 10:35:53 +0100 Subject: [PATCH 09/12] fix(chat): fix downloading without extension and name of code block (Issue #2867, #2868) (#2869) --- apps/chat/src/components/Markdown/CodeBlock.tsx | 9 +++++---- .../src/store/conversations/conversations.selectors.ts | 2 +- apps/chat/src/utils/app/folders.ts | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/chat/src/components/Markdown/CodeBlock.tsx b/apps/chat/src/components/Markdown/CodeBlock.tsx index e51b4b9b0..da40fc8e0 100644 --- a/apps/chat/src/components/Markdown/CodeBlock.tsx +++ b/apps/chat/src/components/Markdown/CodeBlock.tsx @@ -60,8 +60,9 @@ export const CodeBlock: FC = memo( }, 2000); }); }, [value]); - - const displayLanguage = languageNameMapping[language] || language; + const lowercaseLanguage = language.toLowerCase(); + const displayLanguage = + languageNameMapping[lowercaseLanguage] || lowercaseLanguage; const downloadAsFile = useCallback(() => { // languageExtensionMapping allows set empty extension @@ -80,7 +81,7 @@ export const CodeBlock: FC = memo( return; } - const blob = new Blob([value], { type: 'text/plain' }); + const blob = new Blob([value], { type: 'attachment/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = fileName; @@ -107,7 +108,7 @@ export const CodeBlock: FC = memo( : 'border-secondary bg-layer-1', )} > - {displayLanguage} + {lowercaseLanguage} {!isLastMessageStreaming && (
state.conversations; export const selectConversations = (state: RootState): ConversationInfo[] => - state.conversations.conversations; + rootSelector(state).conversations; export const selectNotExternalConversations = createSelector( [selectConversations], diff --git a/apps/chat/src/utils/app/folders.ts b/apps/chat/src/utils/app/folders.ts index 188dc94f9..7284a5522 100644 --- a/apps/chat/src/utils/app/folders.ts +++ b/apps/chat/src/utils/app/folders.ts @@ -116,7 +116,7 @@ export const getNextDefaultName = ( parentFolderId?: string, ): string => { const prefix = `${defaultName} `; - const regex = new RegExp(`^${escapeRegExp(prefix)}(\\d+)$`); + const regex = new RegExp(`^${escapeRegExp(prefix)}(\\d{1,7})$`); if (!entities.length && !index) { return !startWithEmptyPostfix ? `${prefix}${1 + index}` : defaultName; @@ -165,7 +165,7 @@ export const generateNextName = ( entities: ShareEntity[], index = 0, ) => { - const regex = new RegExp(`^${defaultName} (\\d+)$`); + const regex = new RegExp(`^${defaultName} (\\d{1,7})$`); return currentName.match(regex) ? getNextDefaultName(defaultName, entities, index) : getNextDefaultName(currentName, entities, index, true); From 04e8295b7109449075d503ac73494ec317089168 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:30:31 +0100 Subject: [PATCH 10/12] fix(chat): speed up settings modal opening (Issue #2863) (#2872) --- apps/chat/src/hooks/useTokenizer.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/chat/src/hooks/useTokenizer.ts b/apps/chat/src/hooks/useTokenizer.ts index a7efdf548..9013efe81 100644 --- a/apps/chat/src/hooks/useTokenizer.ts +++ b/apps/chat/src/hooks/useTokenizer.ts @@ -14,20 +14,20 @@ export const useTokenizer = (tokenizer: DialAIEntityModel['tokenizer']) => { encodingRef.current = null; } - // use microtask to not block the thread and isMounted variable to prevent task execution if component unmounted - let isMounted = true; - Promise.resolve().then(() => { - if (isMounted && tokenizer?.encoding) { + // use macrotask to not block the thread + const timerId = setTimeout(() => { + if (tokenizer?.encoding) { encodingRef.current = get_encoding(tokenizer.encoding); } - }); + }, 0); return () => { - isMounted = false; if (encodingRef.current) { encodingRef.current.free(); encodingRef.current = null; } + + clearTimeout(timerId); }; }, [tokenizer]); From cea1900309baa3a0c243b86c5827a246963446e7 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:47:13 +0100 Subject: [PATCH 11/12] fix(chat): hide copy button if message hasn't content (Issue #125) (#2873) --- .../Chat/ChatMessage/ChatMessage.tsx | 7 +- .../Chat/ChatMessage/MessageButtons.tsx | 161 ++++++++++-------- 2 files changed, 91 insertions(+), 77 deletions(-) diff --git a/apps/chat/src/components/Chat/ChatMessage/ChatMessage.tsx b/apps/chat/src/components/Chat/ChatMessage/ChatMessage.tsx index 5a0106317..b171ddbbf 100644 --- a/apps/chat/src/components/Chat/ChatMessage/ChatMessage.tsx +++ b/apps/chat/src/components/Chat/ChatMessage/ChatMessage.tsx @@ -47,7 +47,7 @@ export const ChatMessage: FC = memo( onEdit, messageIndex, messagesLength, - ...props + isLikesEnabled, }) => { const { t } = useTranslation(Translation.Chat); @@ -130,7 +130,7 @@ export const ChatMessage: FC = memo( message={message} onRegenerate={onRegenerate} withButtons - {...props} + isLikesEnabled={isLikesEnabled} /> ) : ( = memo( : 0), ); }} - {...props} + isLikesEnabled={isLikesEnabled} /> } > @@ -179,6 +179,7 @@ export const ChatMessage: FC = memo( isMessageStreaming={!!conversation.isMessageStreaming} isLastMessage={isLastMessage} message={message} + isLikesEnabled={isLikesEnabled} onCopy={handleCopy} messageCopied={messageCopied} editDisabled={editDisabled} diff --git a/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx b/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx index 0e7249dc1..96815e395 100644 --- a/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx +++ b/apps/chat/src/components/Chat/ChatMessage/MessageButtons.tsx @@ -154,7 +154,7 @@ export const MessageAssistantButtons = ({ )} - {(message.content.trim() || !message.errorMessage) && + {message.content.trim() && (messageCopied ? ( @@ -249,6 +249,7 @@ interface MessageMobileButtonsProps { isEditTemplatesAvailable: boolean; onToggleTemplatesEditing: () => void; isLastMessage: boolean; + isLikesEnabled: boolean; isMessageStreaming: boolean; onRegenerate?: () => void; isConversationInvalid: boolean; @@ -258,6 +259,7 @@ export const MessageMobileButtons = ({ messageCopied, editDisabled, message, + isLikesEnabled, onLike, onCopy, onDelete, @@ -279,27 +281,28 @@ export const MessageMobileButtons = ({ !(isMessageStreaming && isLastMessage) && !isConversationInvalid && ( <> - {messageCopied ? ( - - -

{t('Copied')}

-
- } - /> - ) : ( - - - {t('Copy')} -
- } - onClick={onCopy} - /> - )} + {message.content.trim() && + (messageCopied ? ( + + +

{t('Copied')}

+
+ } + /> + ) : ( + + + {t('Copy')} +
+ } + onClick={onCopy} + /> + ))} {onRegenerate && ( )} - {message.like !== LikeState.Disliked && ( - - -

+ {message.like !== LikeState.Disliked && ( + - {message.like === LikeState.Liked ? t('Liked') : t('Like')} -

- - } - disabled={message.like === LikeState.Liked} - data-qa="like" - onClick={() => { - if (message.like !== LikeState.NoState) { - onLike(LikeState.Liked); - } - }} - /> - )} - {message.like !== LikeState.Liked && ( - - -

+ +

+ {message.like === LikeState.Liked + ? t('Liked') + : t('Like')} +

+ + } + disabled={message.like === LikeState.Liked} + data-qa="like" + onClick={() => { + if (message.like !== LikeState.NoState) { + onLike(LikeState.Liked); + } + }} + /> + )} + {message.like !== LikeState.Liked && ( + - {message.like === LikeState.Disliked - ? t('Disliked') - : t('Dislike')} -

- - } - onClick={() => { - if (message.like !== LikeState.NoState) { - onLike(LikeState.Disliked); - } - }} - /> - )} + data-qa="dislike" + item={ +
+ +

+ {message.like === LikeState.Disliked + ? t('Disliked') + : t('Dislike')} +

+
+ } + onClick={() => { + if (message.like !== LikeState.NoState) { + onLike(LikeState.Disliked); + } + }} + /> + )} + + )} ) ); From dae64f8ce0f87dec85e04f8231f386b772876ad7 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:50:16 +0100 Subject: [PATCH 12/12] fix(chat): hide slider arrows for mobiles and tablets (Issue #2825) (#2875) --- .../components/Chat/TalkTo/TalkToSlider.tsx | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/apps/chat/src/components/Chat/TalkTo/TalkToSlider.tsx b/apps/chat/src/components/Chat/TalkTo/TalkToSlider.tsx index 423680888..6b9b04a0b 100644 --- a/apps/chat/src/components/Chat/TalkTo/TalkToSlider.tsx +++ b/apps/chat/src/components/Chat/TalkTo/TalkToSlider.tsx @@ -308,6 +308,8 @@ export const TalkToSlider = ({ conversation, items, ...restProps }: Props) => { 0, Math.min(maxDotsTranslate, (activeSlide - 3) * SLIDER_DOT_SIZE_WITH_GAPS), ); + const isMobileOrTablet = + screenState === ScreenState.MOBILE || screenState === ScreenState.TABLET; return ( <> @@ -348,7 +350,7 @@ export const TalkToSlider = ({ conversation, items, ...restProps }: Props) => {
-
+
{sliderDotsArray.length <= 1 && screenState === ScreenState.MOBILE && ( @@ -356,17 +358,19 @@ export const TalkToSlider = ({ conversation, items, ...restProps }: Props) => { )} {sliderDotsArray.length > 1 && ( <> - + {!isMobileOrTablet && ( + + )}
{ })}
- + {!isMobileOrTablet && ( + + )} )}