From 79c38ca7c077cfb1789599b8846a4e510a1fe444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Lidue=C3=B1a?= Date: Tue, 4 Mar 2025 23:15:37 +0100 Subject: [PATCH] [Observability AI Assistant] duplicate conversations (#208044) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #209382 ### Summary: #### Duplicate Conversation - **Readonly** → Public conversations can only be modified by the owner. - Duplicated conversations are **owned** by the user who duplicates them. - Duplicated conversations are **private** by default `public: false`. https://github.com/user-attachments/assets/9a2d1727-aa0d-4d8f-a886-727c0ce1578c UPDATE: https://github.com/user-attachments/assets/ee3282e8-5ae8-445d-9368-928dd59cfb75 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) (cherry picked from commit b331fa1c53f817c0e9c372ab4e5939551550ab9c) --- .../src/chat/chat_actions_menu.tsx | 12 + .../kbn-ai-assistant/src/chat/chat_body.tsx | 119 ++++++-- .../src/chat/chat_consolidated_items.tsx | 3 + .../kbn-ai-assistant/src/chat/chat_flyout.tsx | 12 +- .../kbn-ai-assistant/src/chat/chat_header.tsx | 8 +- .../kbn-ai-assistant/src/chat/chat_item.tsx | 8 +- .../src/chat/chat_timeline.tsx | 19 +- .../src/conversation/conversation_view.tsx | 7 + .../src/hooks/use_conversation.test.tsx | 8 + .../src/hooks/use_conversation.ts | 64 +++- .../src/utils/get_role_translation.ts | 18 +- ..._timeline_items_from_conversation.test.tsx | 8 + .../get_timeline_items_from_conversation.tsx | 20 +- .../public/hooks/use_chat.ts | 5 +- .../server/routes/conversations/route.ts | 26 ++ .../server/service/client/index.test.ts | 9 + .../server/service/client/index.ts | 208 ++++++++----- .../conversations/conversations.spec.ts | 285 ++++++++++++++++++ 18 files changed, 707 insertions(+), 132 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_actions_menu.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_actions_menu.tsx index 4a19272e8938b..45646c701ced7 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_actions_menu.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_actions_menu.tsx @@ -25,11 +25,13 @@ export function ChatActionsMenu({ conversationId, disabled, onCopyConversationClick, + onDuplicateConversationClick, }: { connectors: UseGenAIConnectorsResult; conversationId?: string; disabled: boolean; onCopyConversationClick: () => void; + onDuplicateConversationClick: () => void; }) { const { application, http } = useKibana().services; const knowledgeBase = useKnowledgeBase(); @@ -141,6 +143,16 @@ export function ChatActionsMenu({ onCopyConversationClick(); }, }, + { + name: i18n.translate('xpack.aiAssistant.chatHeader.actions.duplicateConversation', { + defaultMessage: 'Duplicate', + }), + disabled: !conversationId, + onClick: () => { + toggleActionsMenu(); + onDuplicateConversationClick(); + }, + }, ], }, { diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx index 693ff8d63687d..97098f335bb30 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx @@ -6,13 +6,16 @@ */ import { + EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiIcon, EuiPanel, euiScrollBarStyles, EuiSpacer, + EuiText, useEuiTheme, } from '@elastic/eui'; import { css, keyframes } from '@emotion/css'; @@ -45,8 +48,8 @@ import { SimulatedFunctionCallingCallout } from './simulated_function_calling_ca import { WelcomeMessage } from './welcome_message'; import { useLicense } from '../hooks/use_license'; import { PromptEditor } from '../prompt_editor/prompt_editor'; -import { deserializeMessage } from '../utils/deserialize_message'; import { useKibana } from '../hooks/use_kibana'; +import { deserializeMessage } from '../utils/deserialize_message'; const fullHeightClassName = css` height: 100%; @@ -113,9 +116,10 @@ export function ChatBody({ onConversationUpdate, onToggleFlyoutPositionMode, navigateToConversation, + onConversationDuplicate, }: { connectors: ReturnType; - currentUser?: Pick; + currentUser?: Pick; flyoutPositionMode?: FlyoutPositionMode; initialTitle?: string; initialMessages?: Message[]; @@ -123,6 +127,7 @@ export function ChatBody({ knowledgeBase: UseKnowledgeBaseResult; showLinkToConversationsApp: boolean; onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void; + onConversationDuplicate: (conversation: Conversation) => void; onToggleFlyoutPositionMode?: (flyoutPositionMode: FlyoutPositionMode) => void; navigateToConversation?: (conversationId?: string) => void; }) { @@ -142,13 +147,26 @@ export function ChatBody({ false ); - const { conversation, messages, next, state, stop, saveTitle } = useConversation({ + const { + conversation, + conversationId, + messages, + next, + state, + stop, + saveTitle, + duplicateConversation, + isConversationOwnedByCurrentUser, + user: conversationUser, + } = useConversation({ + currentUser, initialConversationId, initialMessages, initialTitle, chatService, connectorId: connectors.selectedConnector, onConversationUpdate, + onConversationDuplicate, }); const timelineContainerRef = useRef(null); @@ -385,28 +403,65 @@ export function ChatBody({ } /> ) : ( - { - setStickToBottom(true); - const indexOf = messages.indexOf(editedMessage); - next(messages.slice(0, indexOf).concat(newMessage)); - }} - onFeedback={handleFeedback} - onRegenerate={(message) => { - next(reverseToLastUserMessage(messages, message)); - }} - onSendTelemetry={(eventWithPayload) => - chatService.sendAnalyticsEvent(eventWithPayload) - } - onStopGenerating={stop} - onActionClick={handleActionClick} - /> + <> + { + setStickToBottom(true); + const indexOf = messages.indexOf(editedMessage); + next(messages.slice(0, indexOf).concat(newMessage)); + }} + onFeedback={handleFeedback} + onRegenerate={(message) => { + next(reverseToLastUserMessage(messages, message)); + }} + onSendTelemetry={(eventWithPayload) => + chatService.sendAnalyticsEvent(eventWithPayload) + } + onStopGenerating={stop} + onActionClick={handleActionClick} + /> + {conversationId && !isConversationOwnedByCurrentUser ? ( + <> + + + + + + + +

+ {i18n.translate('xpack.aiAssistant.sharedBanner.title', { + defaultMessage: 'This conversation is shared with your team.', + })} +

+

+ {i18n.translate('xpack.aiAssistant.sharedBanner.description', { + defaultMessage: `You can’t edit or continue this conversation, but you can duplicate + it into a new private conversation. The original conversation will + remain unchanged.`, + })} +

+ + {i18n.translate('xpack.aiAssistant.duplicateButton', { + defaultMessage: 'Duplicate', + })} + +
+
+
+
+ + + ) : null} + )} @@ -432,7 +487,11 @@ export function ChatBody({ className={promptEditorContainerClassName} >