From 5d6161cb96c3e32599a56f2d519cb04e383c672e Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 6 Feb 2025 12:36:18 -0700 Subject: [PATCH 01/80] remove base conversations --- .../assistant/assistant_overlay/index.tsx | 3 +- .../chat_send/use_chat_send.test.tsx | 2 - .../assistant/chat_send/use_chat_send.tsx | 2 - .../index.tsx | 7 +- .../impl/assistant/helpers.test.ts | 29 +++++--- .../impl/assistant/helpers.ts | 26 +++---- .../impl/assistant/index.tsx | 4 +- .../index.tsx | 7 +- .../use_settings_updater.test.tsx | 1 - .../assistant/use_assistant_overlay/index.tsx | 4 +- .../impl/assistant/use_data_stream_apis.tsx | 13 +--- .../impl/assistant_context/index.tsx | 5 -- .../impl/assistant_context/types.tsx | 5 +- .../mock/test_providers/test_providers.tsx | 1 - .../shared/kbn-elastic-assistant/index.ts | 2 +- .../public/assistant/provider.tsx | 3 - .../management_settings.test.tsx | 4 +- .../stack_management/management_settings.tsx | 7 +- .../use_assistant_telemetry/index.tsx | 21 +----- .../use_conversation_store/index.test.tsx | 74 ------------------- .../use_conversation_store/index.tsx | 28 ------- .../telemetry/events/ai_assistant/types.ts | 3 - .../common/mock/mock_assistant_provider.tsx | 2 - .../cards/assistant/assistant_card.tsx | 3 +- 24 files changed, 56 insertions(+), 200 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index c80145dcef5b9..539b54e245fd2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -47,7 +47,7 @@ export const AssistantOverlay = React.memo(() => { conversationTitle: cTitle, }: ShowAssistantOverlayProps) => { const conversationId = getLastConversationId(cTitle); - if (so) assistantTelemetry?.reportAssistantInvoked({ conversationId, invokedBy: 'click' }); + if (so) assistantTelemetry?.reportAssistantInvoked({ invokedBy: 'click' }); setIsModalVisible(so); setPromptContextId(pid); @@ -66,7 +66,6 @@ export const AssistantOverlay = React.memo(() => { setConversationTitle(getLastConversationId()); assistantTelemetry?.reportAssistantInvoked({ invokedBy: 'shortcut', - conversationId: getLastConversationId(), }); } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.test.tsx index 54fc610d405de..a9266f14ad69a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.test.tsx @@ -130,7 +130,6 @@ describe('use chat send', () => { await waitFor(() => { expect(reportAssistantMessageSent).toHaveBeenNthCalledWith(1, { - conversationId: testProps.currentConversation?.title, role: 'user', actionTypeId: '.gen-ai', model: undefined, @@ -138,7 +137,6 @@ describe('use chat send', () => { isEnabledKnowledgeBase: false, }); expect(reportAssistantMessageSent).toHaveBeenNthCalledWith(2, { - conversationId: testProps.currentConversation?.title, role: 'assistant', actionTypeId: '.gen-ai', model: undefined, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx index c240d5ac6b60b..6873cd317516b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx @@ -122,7 +122,6 @@ export const useChatSend = ({ }); assistantTelemetry?.reportAssistantMessageSent({ - conversationId: currentConversation.title, role: userMessage.role, actionTypeId: currentConversation.apiConfig.actionTypeId, model: currentConversation.apiConfig.model, @@ -138,7 +137,6 @@ export const useChatSend = ({ messages: [...updatedMessages, responseMessage], }); assistantTelemetry?.reportAssistantMessageSent({ - conversationId: currentConversation.title, role: responseMessage.role, actionTypeId: currentConversation.apiConfig.actionTypeId, model: currentConversation.apiConfig.model, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index 368b7d3d2c8b6..c4a38d415829d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -37,7 +37,7 @@ import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_conte import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { DEFAULT_PAGE_SIZE } from '../../settings/const'; import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater'; -import { mergeBaseWithPersistedConversations } from '../../helpers'; +import { formatPersistedConversations } from '../../helpers'; import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar'; interface Props { connectors: AIConnector[] | undefined; @@ -60,7 +60,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ const { actionTypeRegistry, assistantAvailability: { isAssistantEnabled }, - baseConversations, http, nameSpace, toasts, @@ -68,8 +67,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record => - mergeBaseWithPersistedConversations(baseConversations, conversationsData), - [baseConversations] + formatPersistedConversations(conversationsData), + [] ); const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts index 26609dea82164..d1b4fb8d8073d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts @@ -8,9 +8,10 @@ import { getDefaultConnector, getOptionalRequestParams, - mergeBaseWithPersistedConversations, + formatPersistedConversations, } from './helpers'; import { AIConnector } from '../connectorland/connector_selector'; +import { Conversation } from '../assistant_context/types'; describe('helpers', () => { describe('getDefaultConnector', () => { @@ -84,7 +85,7 @@ describe('helpers', () => { }); }); - describe('mergeBaseWithPersistedConversations', () => { + describe('formatPersistedConversations', () => { const messages = [ { content: 'Message 1', role: 'user' as const, timestamp: '2024-02-14T22:29:43.862Z' }, { content: 'Message 2', role: 'user' as const, timestamp: '2024-02-14T22:29:43.862Z' }, @@ -96,23 +97,24 @@ describe('helpers', () => { apiConfig: { actionTypeId: '.gen-ai', connectorId: '123' }, replacements: {}, }; - const baseConversations = { - conversation_1: { + const conversationArray = [ + { ...defaultProps, title: 'Conversation 1', id: 'conversation_1', }, - conversation_2: { + { ...defaultProps, title: 'Conversation 2', id: 'conversation_2', }, - }; + ]; + const conversationsData = { page: 1, per_page: 10, total: 2, - data: Object.values(baseConversations).map((c) => c), + data: conversationArray, }; it('should merge base conversations with user conversations when both are non-empty', () => { @@ -132,7 +134,7 @@ describe('helpers', () => { ], }; - const result = mergeBaseWithPersistedConversations(baseConversations, moreData); + const result = formatPersistedConversations(moreData); expect(result).toEqual({ conversation_1: { @@ -159,17 +161,22 @@ describe('helpers', () => { }); it('should return base conversations when user conversations are empty', () => { - const result = mergeBaseWithPersistedConversations(baseConversations, { + const result = formatPersistedConversations({ ...conversationsData, total: 0, data: [], }); - expect(result).toEqual(baseConversations); + expect(result).toEqual( + conversationArray.reduce((acc: Record, conversation) => { + acc[conversation.id] = conversation; + return acc; + }, {}) + ); }); it('should return user conversations when base conversations are empty', () => { - const result = mergeBaseWithPersistedConversations({}, conversationsData); + const result = formatPersistedConversations(conversationsData); expect(result).toEqual({ conversation_1: { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts index 9265cdd9d57ec..96b5b09e464ad 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts @@ -36,22 +36,22 @@ export const getMessageFromRawResponse = ( } }; -export const mergeBaseWithPersistedConversations = ( - baseConversations: Record, +export const formatPersistedConversations = ( conversationsData: FetchConversationsResponse ): Record => { - return [...(conversationsData?.data ?? []), ...Object.values(baseConversations)].reduce< - Record - >((transformed, conversation) => { - if (!isEmpty(conversation.id)) { - transformed[conversation.id] = conversation; - } else { - if (!some(Object.values(transformed), ['title', conversation.title])) { - transformed[conversation.title] = conversation; + return (conversationsData?.data ?? []).reduce>( + (transformed, conversation) => { + if (!isEmpty(conversation.id)) { + transformed[conversation.id] = conversation; + } else { + if (!some(Object.values(transformed), ['title', conversation.title])) { + transformed[conversation.title] = conversation; + } } - } - return transformed; - }, {}); + return transformed; + }, + {} + ); }; /** * Returns a default connector if there is only one connector diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index 399739099109c..0c9e2b8b3af27 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -86,7 +86,6 @@ const AssistantComponent: React.FC = ({ assistantAvailability: { isAssistantEnabled }, assistantTelemetry, augmentMessageCodeBlocks, - baseConversations, getComments, getLastConversationId, http, @@ -123,7 +122,7 @@ const AssistantComponent: React.FC = ({ refetchPrompts, refetchCurrentUserConversations, setIsStreaming, - } = useDataStreamApis({ http, baseConversations, isAssistantEnabled }); + } = useDataStreamApis({ http, isAssistantEnabled }); // Connector details const { data: connectors, isFetchedAfterMount: isFetchedConnectors } = useLoadConnectors({ @@ -440,7 +439,6 @@ const AssistantComponent: React.FC = ({ (promptTitle: string) => { if (currentConversation?.title) { assistantTelemetry?.reportAssistantQuickPrompt({ - conversationId: currentConversation?.title, promptTitle, }); } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index ac4488a50b79b..4e83b3321a82d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -20,7 +20,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; import { Conversation, - mergeBaseWithPersistedConversations, + formatPersistedConversations, useAssistantContext, useFetchCurrentUserConversations, } from '../../../../..'; @@ -51,14 +51,13 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector nameSpace, http, assistantAvailability: { isAssistantEnabled }, - baseConversations, toasts, } = useAssistantContext(); const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record => - mergeBaseWithPersistedConversations(baseConversations, conversationsData), - [baseConversations] + formatPersistedConversations(conversationsData), + [] ); const { data: allPrompts, refetch: refetchPrompts, isFetched: promptsLoaded } = useFetchPrompts(); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx index 8f68d99c3eaa5..7d629a62d712c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx @@ -51,7 +51,6 @@ const mockValues = { knowledgeBase: { latestAlerts: DEFAULT_LATEST_ALERTS, }, - baseConversations: {}, setKnowledgeBase: setKnowledgeBaseMock, http: mockHttp, anonymizationFieldsBulkActions: {}, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index ec93829ac6727..52bc32389b0b0 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -12,7 +12,7 @@ import { useAssistantContext } from '../../assistant_context'; import { getUniquePromptContextId } from '../../assistant_context/helpers'; import type { PromptContext } from '../prompt_context/types'; import { useConversation } from '../use_conversation'; -import { getDefaultConnector, mergeBaseWithPersistedConversations } from '../helpers'; +import { getDefaultConnector, formatPersistedConversations } from '../helpers'; import { getGenAiConfig } from '../../connectorland/helpers'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { FetchConversationsResponse, useFetchCurrentUserConversations } from '../api'; @@ -95,7 +95,7 @@ export const useAssistantOverlay = ( const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record => - mergeBaseWithPersistedConversations({}, conversationsData), + formatPersistedConversations(conversationsData), [] ); const { data: conversations, isLoading } = useFetchCurrentUserConversations({ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx index 4caf4918cee40..77a0508304861 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx @@ -14,12 +14,11 @@ import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetc import { FetchConversationsResponse, useFetchPrompts } from './api'; import { Conversation, - mergeBaseWithPersistedConversations, + formatPersistedConversations, useFetchCurrentUserConversations, } from '../..'; interface Props { - baseConversations: Record; http: HttpSetup; isAssistantEnabled: boolean; } @@ -45,16 +44,12 @@ export interface DataStreamApis { setIsStreaming: (isStreaming: boolean) => void; } -export const useDataStreamApis = ({ - http, - baseConversations, - isAssistantEnabled, -}: Props): DataStreamApis => { +export const useDataStreamApis = ({ http, isAssistantEnabled }: Props): DataStreamApis => { const [isStreaming, setIsStreaming] = useState(false); const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record => - mergeBaseWithPersistedConversations(baseConversations, conversationsData), - [baseConversations] + formatPersistedConversations(conversationsData), + [] ); const { data: conversations, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index 6ef7a5ef7f04b..fdbb463dfcf12 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -75,7 +75,6 @@ export interface AssistantProviderProps { getComments: GetAssistantMessages; http: HttpSetup; inferenceEnabled?: boolean; - baseConversations: Record; nameSpace?: string; navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise; title?: string; @@ -105,7 +104,6 @@ export interface UseAssistantContext { ) => CodeBlockDetails[][]; docLinks: Omit; basePath: string; - baseConversations: Record; currentUserAvatar?: UserAvatar; getComments: GetAssistantMessages; http: HttpSetup; @@ -159,7 +157,6 @@ export const AssistantProvider: React.FC = ({ getComments, http, inferenceEnabled = false, - baseConversations, navigateToApp, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, productDocBase, @@ -344,7 +341,6 @@ export const AssistantProvider: React.FC = ({ unRegisterPromptContext, getLastConversationId, setLastConversationId: setLocalStorageLastConversationId, - baseConversations, currentAppId, codeBlockRef, userProfileService, @@ -386,7 +382,6 @@ export const AssistantProvider: React.FC = ({ unRegisterPromptContext, getLastConversationId, setLocalStorageLastConversationId, - baseConversations, currentAppId, codeBlockRef, userProfileService, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index ed5b690e9aa57..c6f4a142ffbd7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -45,16 +45,15 @@ export interface Conversation { } export interface AssistantTelemetry { - reportAssistantInvoked: (params: { invokedBy: string; conversationId: string }) => void; + reportAssistantInvoked: (params: { invokedBy: string }) => void; reportAssistantMessageSent: (params: { - conversationId: string; role: string; actionTypeId: string; model?: string; provider?: string; isEnabledKnowledgeBase: boolean; }) => void; - reportAssistantQuickPrompt: (params: { conversationId: string; promptTitle: string }) => void; + reportAssistantQuickPrompt: (params: { promptTitle: string }) => void; reportAssistantSettingToggled: (params: { assistantStreamingEnabled?: boolean }) => void; } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index fa99679c0900f..a5e6920a4c9da 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -84,7 +84,6 @@ export const TestProvidersComponent: React.FC = ({ }} getComments={mockGetComments} http={mockHttp} - baseConversations={{}} navigateToApp={mockNavigateToApp} {...providerContext} currentAppId={'test'} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index 0bfaa45f51ba6..cb10c83f9523e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -149,7 +149,7 @@ export { useFetchCurrentUserConversations } from './impl/assistant/api/conversat export * from './impl/assistant/api/conversations/bulk_update_actions_conversations'; export { getConversationById } from './impl/assistant/api/conversations/conversations'; -export { mergeBaseWithPersistedConversations } from './impl/assistant/helpers'; +export { formatPersistedConversations } from './impl/assistant/helpers'; export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index 374ec85c4db65..37d0a33c86d78 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -30,7 +30,6 @@ import { useAssistantTelemetry } from './use_assistant_telemetry'; import { getComments } from './get_comments'; import { LOCAL_STORAGE_KEY, augmentMessageCodeBlocks } from './helpers'; import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts'; -import { useBaseConversations } from './use_conversation_store'; import { PROMPT_CONTEXTS } from './content/prompt_contexts'; import { useAssistantAvailability } from './use_assistant_availability'; import { useAppToasts } from '../common/hooks/use_app_toasts'; @@ -158,7 +157,6 @@ export const AssistantProvider: FC> = ({ children }) const basePath = useBasePath(); - const baseConversations = useBaseConversations(); const assistantAvailability = useAssistantAvailability(); const assistantTelemetry = useAssistantTelemetry(); const currentAppId = useObservable(currentAppId$, ''); @@ -231,7 +229,6 @@ export const AssistantProvider: FC> = ({ children }) docLinks={{ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }} basePath={basePath} basePromptContexts={Object.values(PROMPT_CONTEXTS)} - baseConversations={baseConversations} getComments={getComments} http={http} inferenceEnabled={inferenceEnabled} diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx index a3c14b9154c3f..ed13b06dd3f17 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx @@ -23,7 +23,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; jest.mock('@kbn/elastic-assistant', () => ({ useAssistantContext: jest.fn(), useFetchCurrentUserConversations: jest.fn(), - mergeBaseWithPersistedConversations: jest.fn(), + formatPersistedConversations: jest.fn(), WELCOME_CONVERSATION_TITLE: 'Welcome Conversation', })); jest.mock('@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management', () => ({ @@ -43,7 +43,6 @@ const useConversationMock = useConversation as jest.Mock; describe('ManagementSettings', () => { const queryClient = new QueryClient(); - const baseConversations = { base: 'conversation' }; const http = {}; const getDefaultConversation = jest.fn(); const setCurrentUserAvatar = jest.fn(); @@ -60,7 +59,6 @@ describe('ManagementSettings', () => { conversations: Record; }) => { useAssistantContextMock.mockReturnValue({ - baseConversations, http, assistantAvailability: { isAssistantEnabled }, setCurrentUserAvatar, diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 766bbffb5e9a7..325b6c29a1ab8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -11,7 +11,7 @@ import type { Conversation } from '@kbn/elastic-assistant'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { i18n } from '@kbn/i18n'; import { - mergeBaseWithPersistedConversations, + formatPersistedConversations, useAssistantContext, useFetchCurrentUserConversations, WELCOME_CONVERSATION_TITLE, @@ -27,7 +27,6 @@ const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE; export const ManagementSettings = React.memo(() => { const { - baseConversations, http, assistantAvailability: { isAssistantEnabled }, } = useAssistantContext(); @@ -46,8 +45,8 @@ export const ManagementSettings = React.memo(() => { const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record => - mergeBaseWithPersistedConversations(baseConversations, conversationsData), - [baseConversations] + formatPersistedConversations(conversationsData), + [] ); const { data: conversations } = useFetchCurrentUserConversations({ http, diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx index 04bfc8bdcd640..e56ba3d4eb524 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.tsx @@ -8,7 +8,6 @@ import { type AssistantTelemetry } from '@kbn/elastic-assistant'; import { useCallback } from 'react'; import { useKibana } from '../../common/lib/kibana'; -import { useBaseConversations } from '../use_conversation_store'; import type { ReportAssistantInvokedParams, ReportAssistantMessageSentParams, @@ -20,33 +19,19 @@ export const useAssistantTelemetry = (): AssistantTelemetry => { const { services: { telemetry }, } = useKibana(); - const baseConversations = useBaseConversations(); - - const getAnonymizedConversationTitle = useCallback( - async (title: string) => { - // With persistent storage for conversation replacing id to title, because id is UUID now - // and doesn't make any value for telemetry tracking - return baseConversations[title] ? title : 'Custom'; - }, - [baseConversations] - ); const reportTelemetry = useCallback( async ({ eventType, - params: { conversationId, ...rest }, + params, }: { eventType: AssistantEventTypes; params: | ReportAssistantInvokedParams | ReportAssistantMessageSentParams | ReportAssistantQuickPromptParams; - }) => - telemetry.reportEvent(eventType, { - ...rest, - conversationId: await getAnonymizedConversationTitle(conversationId), - }), - [getAnonymizedConversationTitle, telemetry] + }) => telemetry.reportEvent(eventType, params), + [telemetry] ); return { diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx deleted file mode 100644 index d93430e1552df..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react'; -import { useBaseConversations } from '.'; -import { useLinkAuthorized } from '../../common/links'; -import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__'; -import { useKibana } from '../../common/lib/kibana'; -import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations'; -import { unset } from 'lodash/fp'; -import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard'; - -const BASE_CONVERSATIONS_WITHOUT_DATA_QUALITY = unset( - DATA_QUALITY_DASHBOARD_CONVERSATION_ID, - BASE_SECURITY_CONVERSATIONS -); - -jest.mock('../../common/links', () => ({ - useLinkAuthorized: jest.fn(), -})); - -jest.mock('@kbn/elastic-assistant', () => ({ - useFetchCurrentUserConversations: jest.fn().mockReturnValue({ - data: {}, - isLoading: false, - isError: false, - }), -})); - -const mockedUseKibana = { - ...mockUseKibana(), - services: { - ...mockUseKibana().services, - storage: { - ...mockUseKibana().services.storage, - get: jest.fn(), - set: jest.fn(), - }, - }, -}; - -jest.mock('../../common/lib/kibana', () => { - return { - useKibana: jest.fn(), - }; -}); - -describe('useBaseConversations', () => { - beforeEach(() => { - jest.clearAllMocks(); - - (useKibana as jest.Mock).mockReturnValue(mockedUseKibana); - }); - - it('should return conversations with "Data Quality dashboard" conversation', () => { - (useLinkAuthorized as jest.Mock).mockReturnValue(true); - const { result } = renderHook(() => useBaseConversations()); - - expect(result.current).toEqual(expect.objectContaining(BASE_SECURITY_CONVERSATIONS)); - }); - - it('should return conversations Without "Data Quality dashboard" conversation', () => { - (useLinkAuthorized as jest.Mock).mockReturnValue(false); - const { result } = renderHook(() => useBaseConversations()); - - expect(result.current).toEqual( - expect.objectContaining(BASE_CONVERSATIONS_WITHOUT_DATA_QUALITY) - ); - }); -}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.tsx deleted file mode 100644 index 89fe80f4d29a3..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_conversation_store/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { type Conversation } from '@kbn/elastic-assistant'; - -import { unset } from 'lodash/fp'; -import { useMemo } from 'react'; -import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard'; - -import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations'; -import { useLinkAuthorized } from '../../common/links'; -import { SecurityPageName } from '../../../common'; - -export const useBaseConversations = (): Record => { - const isDataQualityDashboardPageExists = useLinkAuthorized(SecurityPageName.dataQuality); - const baseConversations = useMemo( - () => - isDataQualityDashboardPageExists - ? BASE_SECURITY_CONVERSATIONS - : unset(DATA_QUALITY_DASHBOARD_CONVERSATION_ID, BASE_SECURITY_CONVERSATIONS), - [isDataQualityDashboardPageExists] - ); - return baseConversations; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts b/x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts index 894494575f9af..b2527b7ef43ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts @@ -15,12 +15,10 @@ export enum AssistantEventTypes { } export interface ReportAssistantInvokedParams { - conversationId: string; invokedBy: string; } export interface ReportAssistantMessageSentParams { - conversationId: string; role: string; actionTypeId: string; provider?: string; @@ -29,7 +27,6 @@ export interface ReportAssistantMessageSentParams { } export interface ReportAssistantQuickPromptParams { - conversationId: string; promptTitle: string; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index c793f7722780a..9440c0dccde70 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -13,7 +13,6 @@ import { AssistantProvider } from '@kbn/elastic-assistant'; import type { UserProfileService } from '@kbn/core/public'; import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; -import { BASE_SECURITY_CONVERSATIONS } from '../../assistant/content/conversations'; interface Props { assistantAvailability?: AssistantAvailability; @@ -59,7 +58,6 @@ export const MockAssistantProviderComponent: React.FC = ({ getComments={jest.fn(() => [])} http={mockHttp} navigateToApp={mockNavigateToApp} - baseConversations={BASE_SECURITY_CONVERSATIONS} currentAppId={'test'} productDocBase={{ installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx index a8e8b12ee7085..3a07e869d96c2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx @@ -61,7 +61,6 @@ export const AssistantCard: OnboardingCardComponent = ({ const { http, assistantAvailability: { isAssistantEnabled }, - baseConversations, getLastConversationId, } = useAssistantContext(); const { @@ -70,7 +69,7 @@ export const AssistantCard: OnboardingCardComponent = ({ isFetchedCurrentUserConversations, isFetchedPrompts, refetchCurrentUserConversations, - } = useDataStreamApis({ http, baseConversations, isAssistantEnabled }); + } = useDataStreamApis({ http, isAssistantEnabled }); const { currentConversation, handleOnConversationSelected } = useCurrentConversation({ allSystemPrompts, From 4453b312cff0073eb90fc3412fe6de543ebbb04b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 6 Feb 2025 12:51:49 -0700 Subject: [PATCH 02/80] test fixing --- .../assistant_overlay/index.test.tsx | 1 - .../impl/assistant/helpers.test.ts | 61 +------------------ .../use_assistant_telemetry/index.test.tsx | 32 +--------- 3 files changed, 3 insertions(+), 91 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx index 9e6a9164607a3..4cd7b0fce58cc 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx @@ -57,7 +57,6 @@ describe('AssistantOverlay', () => { expect(reportAssistantInvoked).toHaveBeenCalledTimes(1); expect(reportAssistantInvoked).toHaveBeenCalledWith({ invokedBy: 'shortcut', - conversationId: 'Welcome', }); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); expect(reportAssistantInvoked).toHaveBeenCalledTimes(1); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts index d1b4fb8d8073d..469e8e33c0044 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts @@ -11,7 +11,6 @@ import { formatPersistedConversations, } from './helpers'; import { AIConnector } from '../connectorland/connector_selector'; -import { Conversation } from '../assistant_context/types'; describe('helpers', () => { describe('getDefaultConnector', () => { @@ -117,65 +116,7 @@ describe('helpers', () => { data: conversationArray, }; - it('should merge base conversations with user conversations when both are non-empty', () => { - const moreData = { - ...conversationsData, - data: [ - { - ...defaultProps, - title: 'Conversation 3', - id: 'conversation_3', - }, - { - ...defaultProps, - title: 'Conversation 4', - id: 'conversation_4', - }, - ], - }; - - const result = formatPersistedConversations(moreData); - - expect(result).toEqual({ - conversation_1: { - title: 'Conversation 1', - id: 'conversation_1', - ...defaultProps, - }, - conversation_2: { - title: 'Conversation 2', - id: 'conversation_2', - ...defaultProps, - }, - conversation_3: { - title: 'Conversation 3', - id: 'conversation_3', - ...defaultProps, - }, - conversation_4: { - title: 'Conversation 4', - id: 'conversation_4', - ...defaultProps, - }, - }); - }); - - it('should return base conversations when user conversations are empty', () => { - const result = formatPersistedConversations({ - ...conversationsData, - total: 0, - data: [], - }); - - expect(result).toEqual( - conversationArray.reduce((acc: Record, conversation) => { - acc[conversation.id] = conversation; - return acc; - }, {}) - ); - }); - - it('should return user conversations when base conversations are empty', () => { + it('should format user conversations', () => { const result = formatPersistedConversations(conversationsData); expect(result).toEqual({ diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx index 269d09bfed0fc..a756598e033e6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx @@ -67,42 +67,14 @@ describe('useAssistantTelemetry', () => { }); describe.each(trackingFns)('Handles %s id masking', (fn) => { - it('Should call tracking with appropriate id when tracking is called with an isDefault=true conversation id', async () => { + it('Should call tracking function', async () => { const { result } = renderHook(() => useAssistantTelemetry()); - const validId = Object.keys(mockedConversations)[0]; // @ts-ignore const trackingFn = result.current[fn.name]; - await trackingFn({ conversationId: validId, invokedBy: 'shortcut' }); + await trackingFn({ invokedBy: 'shortcut' }); // @ts-ignore const trackingMockedFn = mockedTelemetry.reportEvent; expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, { - conversationId: validId, - invokedBy: 'shortcut', - }); - }); - - it('Should call tracking with "Custom" id when tracking is called with an isDefault=false conversation id', async () => { - const { result } = renderHook(() => useAssistantTelemetry()); - // @ts-ignore - const trackingFn = result.current[fn.name]; - await trackingFn({ conversationId: customId, invokedBy: 'shortcut' }); - // @ts-ignore - const trackingMockedFn = mockedTelemetry.reportEvent; - expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, { - conversationId: 'Custom', - invokedBy: 'shortcut', - }); - }); - - it('Should call tracking with "Custom" id when tracking is called with an unknown conversation id', async () => { - const { result } = renderHook(() => useAssistantTelemetry()); - // @ts-ignore - const trackingFn = result.current[fn.name]; - await trackingFn({ conversationId: '123', invokedBy: 'shortcut' }); - // @ts-ignore - const trackingMockedFn = mockedTelemetry.reportEvent; - expect(trackingMockedFn).toHaveBeenCalledWith(fn.eventType, { - conversationId: 'Custom', invokedBy: 'shortcut', }); }); From 35933e4a5111c13c2588fa58b7a11dbf912d87aa Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 6 Feb 2025 14:26:32 -0700 Subject: [PATCH 03/80] rm isDefault param --- .../conversations/common_attributes.gen.ts | 8 -- .../common_attributes.schema.yaml | 6 -- .../find_conversations_route.gen.ts | 7 +- .../find_conversations_route.schema.yaml | 1 - .../impl/assistant/assistant_title/index.tsx | 2 +- .../inline_actions/index.tsx | 2 +- .../conversation_selector_settings/index.tsx | 73 +++++++++-------- .../conversation_selector_settings/types.ts | 12 --- .../index.tsx | 2 +- .../conversation_sidepanel/index.tsx | 1 - .../impl/assistant/use_conversation/index.tsx | 1 - .../impl/assistant_context/types.tsx | 1 - .../use_knowledge_base_table.tsx | 2 +- .../impl/mock/conversation.ts | 3 - .../__mocks__/conversations_schema.mock.ts | 3 - .../server/__mocks__/response.ts | 1 - .../conversations/create_conversation.test.ts | 2 - .../conversations/create_conversation.ts | 2 - .../conversations/field_maps_configuration.ts | 5 -- .../conversations/get_conversation.test.ts | 2 - .../conversations/index.test.ts | 2 - .../conversations/transforms.ts | 2 - .../conversations/types.ts | 2 - .../conversations/update_conversation.test.ts | 1 - .../ai_assistant_data_clients/find.test.ts | 2 - .../ai_assistant_data_clients/index.test.ts | 2 - .../user_conversations/find_route.test.ts | 2 +- .../user_conversations/update_route.test.ts | 11 --- .../assistant/content/conversations/index.tsx | 78 ------------------- .../content/conversations/translations.ts | 15 ---- .../public/assistant/provider.test.tsx | 6 -- .../use_assistant_telemetry/index.test.tsx | 10 --- 32 files changed, 41 insertions(+), 228 deletions(-) delete mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/index.tsx delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/translations.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts index 82a76f850e3bb..f5bca80302df8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts @@ -331,10 +331,6 @@ export const ConversationResponse = z.object({ * LLM API configuration. */ apiConfig: ApiConfig.optional(), - /** - * Is default conversation. - */ - isDefault: z.boolean().optional(), /** * excludeFromLastConversationStorage. */ @@ -394,10 +390,6 @@ export const ConversationCreateProps = z.object({ * LLM API configuration. */ apiConfig: ApiConfig.optional(), - /** - * Is default conversation. - */ - isDefault: z.boolean().optional(), /** * excludeFromLastConversationStorage. */ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml index b651ddd3ca660..180836eb00cd4 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml @@ -321,9 +321,6 @@ components: apiConfig: $ref: '#/components/schemas/ApiConfig' description: LLM API configuration. - isDefault: - description: Is default conversation. - type: boolean excludeFromLastConversationStorage: description: excludeFromLastConversationStorage. type: boolean @@ -382,9 +379,6 @@ components: apiConfig: $ref: '#/components/schemas/ApiConfig' description: LLM API configuration. - isDefault: - description: Is default conversation. - type: boolean excludeFromLastConversationStorage: description: excludeFromLastConversationStorage. type: boolean diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts index 244a88fa1daff..7354722ba6e38 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts @@ -21,12 +21,7 @@ import { SortOrder } from '../common_attributes.gen'; import { ConversationResponse } from './common_attributes.gen'; export type FindConversationsSortField = z.infer; -export const FindConversationsSortField = z.enum([ - 'created_at', - 'is_default', - 'title', - 'updated_at', -]); +export const FindConversationsSortField = z.enum(['created_at', 'title', 'updated_at']); export type FindConversationsSortFieldEnum = typeof FindConversationsSortField.enum; export const FindConversationsSortFieldEnum = FindConversationsSortField.enum; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml index 4b532c2cc7303..ccf2bd8e9cafe 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml @@ -98,6 +98,5 @@ components: type: string enum: - 'created_at' - - 'is_default' - 'title' - 'updated_at' diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx index 5c78ab2552ab4..531f940bd83e9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx @@ -65,7 +65,7 @@ export const AssistantTitle: React.FC<{ value={newTitle ?? NEW_CHAT} size="xs" isInvalid={!!newTitleError} - isReadOnly={isDisabled || selectedConversation?.isDefault} + isReadOnly={isDisabled} onChange={(e) => setNewTitle(e.currentTarget.nodeValue || '')} onCancel={() => setNewTitle(title)} onSave={handleUpdateTitle} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx index 7a2da0d22fc3e..8748d1bbe8e7d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx @@ -16,7 +16,7 @@ interface Props { onEdit?: (rowItem: T) => void; } -export const useInlineActions = () => { +export const useInlineActions = () => { const getInlineActions = useCallback( ({ isEditEnabled = () => false, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx index cf30f5ea935e9..dacbc079e6691 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx @@ -8,6 +8,7 @@ import { EuiButtonIcon, EuiComboBox, + EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -20,7 +21,6 @@ import { css } from '@emotion/react'; import { Conversation } from '../../../..'; import * as i18n from './translations'; import { SystemPromptSelectorOption } from '../../prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector'; -import { ConversationSelectorSettingsOption } from './types'; interface Props { conversations: Record; @@ -67,25 +67,24 @@ export const ConversationSelectorSettings: React.FC = React.memo( [conversations] ); - const [conversationOptions, setConversationOptions] = useState< - ConversationSelectorSettingsOption[] - >(() => { - return Object.values(conversations).map((conversation) => ({ - value: { isDefault: conversation.isDefault ?? false }, - label: conversation.title, - id: conversation.id, - 'data-test-subj': conversation.title, - })); - }); + const [conversationOptions, setConversationOptions] = useState( + () => { + return Object.values(conversations).map((conversation) => ({ + label: conversation.title, + id: conversation.id, + 'data-test-subj': conversation.title, + })); + } + ); - const selectedOptions = useMemo(() => { + const selectedOptions = useMemo(() => { return selectedConversationTitle ? conversationOptions.filter((c) => c.label === selectedConversationTitle) ?? [] : []; }, [conversationOptions, selectedConversationTitle]); const handleSelectionChange = useCallback( - (conversationSelectorSettingsOption: ConversationSelectorSettingsOption[]) => { + (conversationSelectorSettingsOption: EuiComboBoxOptionOption[]) => { const newConversation = conversationSelectorSettingsOption.length === 0 ? undefined @@ -129,7 +128,7 @@ export const ConversationSelectorSettings: React.FC = React.memo( // Callback for when a user selects a conversation const onChange = useCallback( - (newOptions: ConversationSelectorSettingsOption[]) => { + (newOptions: EuiComboBoxOptionOption[]) => { if (newOptions.length === 0) { handleSelectionChange([]); } else if (conversationOptions.findIndex((o) => o.label === newOptions?.[0].label) !== -1) { @@ -163,11 +162,11 @@ export const ConversationSelectorSettings: React.FC = React.memo( }, [conversationTitles, selectedConversationTitle, conversationOptions, handleSelectionChange]); const renderOption: ( - option: ConversationSelectorSettingsOption, + option: EuiComboBoxOptionOption, searchValue: string, OPTION_CONTENT_CLASSNAME: string ) => React.ReactNode = (option, searchValue) => { - const { label, value } = option; + const { label } = option; return ( = React.memo( {label} - {!value?.isDefault && ( - - - { - e.stopPropagation(); - onDelete(label); - }} - css={css` - visibility: hidden; - .parentFlexGroup:hover & { - visibility: visible; - } - `} - /> - - - )} + + + { + e.stopPropagation(); + onDelete(label); + }} + css={css` + visibility: hidden; + .parentFlexGroup:hover & { + visibility: visible; + } + `} + /> + + ); }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts deleted file mode 100644 index 548149ffe0c7b..0000000000000 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiComboBoxOptionOption } from '@elastic/eui'; - -export type ConversationSelectorSettingsOption = EuiComboBoxOptionOption<{ - isDefault: boolean; -}>; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index c4a38d415829d..a0a9685a43940 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -259,7 +259,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ const columns = useMemo( () => getColumns({ - isDeleteEnabled: (rowItem: ConversationTableItem) => rowItem.isDefault !== true, + isDeleteEnabled: () => true, isEditEnabled: () => true, onDeleteActionClicked, onEditActionClicked, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx index 5995222f42614..47d46d16078b2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx @@ -208,7 +208,6 @@ export const ConversationSidePanel = React.memo( onClick: () => setDeleteConversationItem(conversation), iconType: 'trash', iconSize: 's', - disabled: conversation.isDefault, 'aria-label': i18n.DELETE_CONVERSATION_ARIA_LABEL, 'data-test-subj': 'delete-option', }} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index fdc797009dea9..da5837154ec14 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -175,7 +175,6 @@ export const useConversation = (): UseConversation => { title: conversation.title, replacements: conversation.replacements, excludeFromLastConversationStorage: conversation.excludeFromLastConversationStorage, - isDefault: conversation.isDefault, id: '', messages: conversation.messages ?? [], }, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index c6f4a142ffbd7..0529e0d2a43b8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -40,7 +40,6 @@ export interface Conversation { updatedAt?: Date; createdAt?: Date; replacements: Replacements; - isDefault?: boolean; excludeFromLastConversationStorage?: boolean; } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx index cbdf97f116f7b..40fe1c7caf24a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/use_knowledge_base_table.tsx @@ -124,7 +124,7 @@ const NameColumn = ({ }; export const useKnowledgeBaseTable = () => { - const getActions = useInlineActions(); + const getActions = useInlineActions(); const getIconForEntry = (entry: KnowledgeBaseEntryResponse): string => { if (entry.type === DocumentEntryType.value) { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/conversation.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/conversation.ts index 5721e4788154b..fc5aa65490f51 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/conversation.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/conversation.ts @@ -12,7 +12,6 @@ export const alertConvo: Conversation = { id: '', title: 'Alert summary', category: 'assistant', - isDefault: true, messages: [ { content: @@ -51,7 +50,6 @@ export const emptyWelcomeConvo: Conversation = { id: '', title: 'Welcome', category: 'assistant', - isDefault: true, messages: [], replacements: {}, apiConfig: { @@ -88,7 +86,6 @@ export const customConvo: Conversation = { id: '', category: 'assistant', title: 'Custom option', - isDefault: false, messages: [], replacements: {}, apiConfig: { diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/conversations_schema.mock.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/conversations_schema.mock.ts index 278dfc9fe829b..a502f59a7ed42 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/conversations_schema.mock.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/conversations_schema.mock.ts @@ -44,7 +44,6 @@ export const getConversationSearchEsMock = () => { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', title: 'test', exclude_from_last_conversation_storage: true, - is_default: false, messages: [], replacements: [], users: [ @@ -71,7 +70,6 @@ export const getCreateConversationSchemaMock = ( model: 'model', }, excludeFromLastConversationStorage: false, - isDefault: true, messages: [ { content: 'test content', @@ -186,7 +184,6 @@ export const getQueryConversationParams = ( model: 'model', }, excludeFromLastConversationStorage: false, - isDefault: true, messages: [ { content: 'test content', diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/response.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/response.ts index b7ab289d0f270..eb34454579e32 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/response.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/response.ts @@ -87,7 +87,6 @@ export const getConversationResponseMock = ( replacements: {}, createdAt: timestamp, namespace: 'default', - isDefault: false, updatedAt: timestamp, timestamp, category: 'assistant', diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.test.ts index 0546ab39db592..e59116bd213b6 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.test.ts @@ -28,7 +28,6 @@ export const getCreateConversationMock = (): ConversationCreateProps => ({ provider: 'OpenAI', }, excludeFromLastConversationStorage: false, - isDefault: false, messages: [], replacements: {}, category: 'assistant', @@ -49,7 +48,6 @@ export const getConversationResponseMock = (): ConversationResponse => ({ replacements: {}, createdAt: '2024-01-28T04:20:02.394Z', namespace: 'test', - isDefault: false, updatedAt: '2024-01-28T04:20:02.394Z', timestamp: '2024-01-28T04:20:02.394Z', category: 'assistant', diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts index b98b8a1f4cc27..a10dae3ac015e 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts @@ -66,7 +66,6 @@ export const transformToCreateScheme = ( apiConfig, category, excludeFromLastConversationStorage, - isDefault, messages, replacements, }: ConversationCreateProps @@ -92,7 +91,6 @@ export const transformToCreateScheme = ( } : undefined, exclude_from_last_conversation_storage: excludeFromLastConversationStorage, - is_default: isDefault, messages: messages?.map((message) => ({ '@timestamp': message.timestamp, content: message.content, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/field_maps_configuration.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/field_maps_configuration.ts index 314c83728bcd0..22789e137349a 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/field_maps_configuration.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/field_maps_configuration.ts @@ -37,11 +37,6 @@ export const conversationsFieldMap: FieldMap = { array: false, required: true, }, - is_default: { - type: 'boolean', - array: false, - required: false, - }, updated_at: { type: 'date', array: false, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/get_conversation.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/get_conversation.test.ts index 43290c8a00293..5634eb4c994a9 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/get_conversation.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/get_conversation.test.ts @@ -21,7 +21,6 @@ export const getConversationResponseMock = (): ConversationResponse => ({ messages: [], id: '1', namespace: 'default', - isDefault: true, excludeFromLastConversationStorage: false, timestamp: '2020-04-20T15:25:31.830Z', apiConfig: { @@ -68,7 +67,6 @@ export const getSearchConversationMock = (): estypes.SearchResponse { }, ], title: 'Alert summary', - is_default: true, users: [ { name: 'elastic', @@ -113,7 +112,6 @@ describe('AIAssistantConversationsDataClient', () => { createdAt: '2024-01-25T01:32:37.649Z', excludeFromLastConversationStorage: undefined, id: undefined, - isDefault: true, messages: [ { content: diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/transforms.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/transforms.ts index c11d872e9d3ec..ac8464f4e674b 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/transforms.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/transforms.ts @@ -45,7 +45,6 @@ export const transformESSearchToConversations = ( : {}), excludeFromLastConversationStorage: conversationSchema.exclude_from_last_conversation_storage, - isDefault: conversationSchema.is_default, messages: // eslint-disable-next-line @typescript-eslint/no-explicit-any conversationSchema.messages?.map((message: Record) => ({ @@ -132,7 +131,6 @@ export const transformESToConversations = ( } : {}), excludeFromLastConversationStorage: conversationSchema.exclude_from_last_conversation_storage, - isDefault: conversationSchema.is_default, messages: // eslint-disable-next-line @typescript-eslint/no-explicit-any conversationSchema.messages?.map((message: Record) => ({ diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/types.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/types.ts index e520b25a5fe3f..81332bd9176ee 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/types.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/types.ts @@ -48,7 +48,6 @@ export interface EsConversationSchema { provider?: Provider; model?: string; }; - is_default?: boolean; exclude_from_last_conversation_storage?: boolean; replacements?: EsReplacementSchema[]; users?: Array<{ @@ -83,7 +82,6 @@ export interface CreateMessageSchema { provider?: Provider; model?: string; }; - is_default?: boolean; exclude_from_last_conversation_storage?: boolean; replacements?: EsReplacementSchema[]; users: Array<{ diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.test.ts index cdf630640b452..3bb84c4a5bffe 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.test.ts @@ -60,7 +60,6 @@ export const getConversationResponseMock = (): ConversationResponse => ({ replacements: {}, createdAt: '2020-04-20T15:25:31.830Z', namespace: 'default', - isDefault: false, updatedAt: '2020-04-20T15:25:31.830Z', timestamp: '2020-04-20T15:25:31.830Z', users: [ diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.test.ts index 8108376c843b4..a9ad152770040 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.test.ts @@ -34,7 +34,6 @@ export const getSearchConversationMock = (): estypes.SearchResponse { created_at: '2020-04-20T15:25:31.830Z', exclude_from_last_conversation_storage: false, id: '1', - is_default: true, messages: [], namespace: 'default', replacements: undefined, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/index.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/index.test.ts index 23e6e5e681d53..99dbf8e401c89 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/index.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/index.test.ts @@ -151,7 +151,6 @@ describe('AIAssistantDataClient', () => { }, ], title: 'Alert summary', - is_default: true, users: [ { name: 'elastic', @@ -185,7 +184,6 @@ describe('AIAssistantDataClient', () => { model: 'anthropic.claude-v2', }, created_at: '2024-01-25T01:32:37.649Z', - is_default: true, messages: [ { '@timestamp': '1/24/2024, 5:32:19 PM', diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.test.ts index 2b20ab03371f6..bc76d1f814c98 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.test.ts @@ -91,7 +91,7 @@ describe('Find user conversations route', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - `sort_field: Invalid enum value. Expected 'created_at' | 'is_default' | 'title' | 'updated_at', received 'name'` + `sort_field: Invalid enum value. Expected 'created_at' | 'title' | 'updated_at', received 'name'` ); }); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/update_route.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/update_route.test.ts index 138116398b818..a671e9872e2e8 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/update_route.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/update_route.test.ts @@ -96,17 +96,6 @@ describe('Update conversation route', () => { expect(response.badRequest).toHaveBeenCalled(); }); - test('rejects isDefault update', async () => { - const request = requestMock.create({ - method: 'put', - path: ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BY_ID, - body: { ...getUpdateConversationSchemaMock(), isDefault: false }, - }); - const result = await server.validate(request); - - expect(result.badRequest).toHaveBeenCalled(); - }); - test('allows title, excludeFromLastConversationStorage, apiConfig, replacements and message', async () => { const request = requestMock.create({ method: 'put', diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/index.tsx deleted file mode 100644 index 4332827c43ce1..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { WELCOME_CONVERSATION_TITLE } from '@kbn/elastic-assistant'; -import type { Conversation } from '@kbn/elastic-assistant'; -import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard'; - -import { DETECTION_RULES_CONVERSATION_ID } from '../../../detections/pages/detection_engine/rules/translations'; -import { DETECTION_RULES_CREATE_FORM_CONVERSATION_ID } from '../../../detections/pages/detection_engine/translations'; -import { - ALERT_SUMMARY_CONVERSATION_ID, - EVENT_SUMMARY_CONVERSATION_ID, -} from '../../../common/components/event_details/translations'; -import { TIMELINE_CONVERSATION_TITLE } from './translations'; - -export const BASE_SECURITY_CONVERSATIONS: Record = { - [ALERT_SUMMARY_CONVERSATION_ID]: { - id: '', - title: ALERT_SUMMARY_CONVERSATION_ID, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [DATA_QUALITY_DASHBOARD_CONVERSATION_ID]: { - id: '', - title: DATA_QUALITY_DASHBOARD_CONVERSATION_ID, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [DETECTION_RULES_CONVERSATION_ID]: { - id: '', - title: DETECTION_RULES_CONVERSATION_ID, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [DETECTION_RULES_CREATE_FORM_CONVERSATION_ID]: { - id: '', - title: DETECTION_RULES_CREATE_FORM_CONVERSATION_ID, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [EVENT_SUMMARY_CONVERSATION_ID]: { - id: '', - title: EVENT_SUMMARY_CONVERSATION_ID, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [TIMELINE_CONVERSATION_TITLE]: { - excludeFromLastConversationStorage: true, - id: '', - title: TIMELINE_CONVERSATION_TITLE, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, - [WELCOME_CONVERSATION_TITLE]: { - id: '', - title: WELCOME_CONVERSATION_TITLE, - category: 'assistant', - isDefault: true, - messages: [], - replacements: {}, - }, -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/translations.ts deleted file mode 100644 index 4cdb1b3f27e54..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/translations.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const TIMELINE_CONVERSATION_TITLE = i18n.translate( - 'xpack.securitySolution.assistant.conversations.timelineConversationTitle', - { - defaultMessage: 'Timeline', - } -); diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.test.tsx index 5108fb259cfb2..b935c17540dc3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.test.tsx @@ -53,7 +53,6 @@ export const mockConnectors = [ const conversations = { 'Alert summary': { id: 'Alert summary', - isDefault: true, apiConfig: { connectorId: 'my-bedrock', defaultSystemPromptId: 'default-system-prompt', @@ -83,7 +82,6 @@ const conversations = { }, 'Data Quality dashboard': { id: 'Data Quality dashboard', - isDefault: true, apiConfig: { connectorId: 'my-gen-ai', defaultSystemPromptId: 'default-system-prompt', @@ -107,26 +105,22 @@ const conversations = { }, 'Detection Rules': { id: 'Detection Rules', - isDefault: true, messages: [], apiConfig: {}, }, 'Event summary': { id: 'Event summary', - isDefault: true, messages: [], apiConfig: {}, }, Timeline: { excludeFromLastConversationStorage: true, id: 'Timeline', - isDefault: true, messages: [], apiConfig: {}, }, Welcome: { id: 'Welcome', - isDefault: true, theme: { title: 'Elastic AI Assistant', titleIcon: 'logoSecurity', diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx index a756598e033e6..72da1b1a3585f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_telemetry/index.test.tsx @@ -7,20 +7,10 @@ import { renderHook } from '@testing-library/react'; import { useAssistantTelemetry } from '.'; -import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations'; import { createTelemetryServiceMock } from '../../common/lib/telemetry/telemetry_service.mock'; import { AssistantEventTypes } from '../../common/lib/telemetry'; const customId = `My Convo`; -const mockedConversations = { - ...BASE_SECURITY_CONVERSATIONS, - [customId]: { - id: customId, - apiConfig: {}, - replacements: {}, - messages: [], - }, -}; const mockedTelemetry = { ...createTelemetryServiceMock(), From 840869fe6e89ff752904a203a55132cb4caf02a2 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 7 Feb 2025 10:24:40 -0700 Subject: [PATCH 04/80] more, working --- .../impl/assistant/assistant_title/index.tsx | 2 +- .../assistant/chat_send/use_chat_send.tsx | 45 ++-- .../index.tsx | 6 +- .../impl/assistant/index.test.tsx | 2 - .../impl/assistant/index.tsx | 14 +- .../system_prompt/index.test.tsx | 3 +- .../assistant_settings_management.tsx | 10 +- .../impl/assistant/use_conversation/index.tsx | 39 ---- .../use_conversation/sample_conversations.tsx | 3 +- .../use_conversation/translations.ts | 28 --- .../use_current_conversation/index.test.tsx | 1 - .../use_current_conversation/index.tsx | 201 ++++-------------- .../impl/assistant_context/index.tsx | 6 +- .../shared/kbn-elastic-assistant/index.ts | 5 - .../mock/test_providers/test_providers.tsx | 1 - .../scripts/create_conversations_script.ts | 1 - .../management_settings.test.tsx | 18 +- .../stack_management/management_settings.tsx | 15 -- .../telemetry/events/ai_assistant/index.ts | 21 -- 19 files changed, 95 insertions(+), 326 deletions(-) delete mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/translations.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx index 531f940bd83e9..c23f428cc0748 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx @@ -62,7 +62,7 @@ export const AssistantTitle: React.FC<{ data-test-subj="conversationTitle" heading="h2" inputAriaLabel="Edit text inline" - value={newTitle ?? NEW_CHAT} + value={newTitle || NEW_CHAT} size="xs" isInvalid={!!newTitleError} isReadOnly={isDisabled} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx index 6873cd317516b..9478a836fa704 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx @@ -60,7 +60,8 @@ export const useChatSend = ({ const [userPrompt, setUserPrompt] = useState(null); const { isLoading, sendMessage, abortStream } = useSendMessage(); - const { clearConversation, removeLastMessage } = useConversation(); + const { clearConversation, createConversation, getConversation, removeLastMessage } = + useConversation(); const { data: kbStatus } = useKnowledgeBaseStatus({ http, enabled: isAssistantEnabled }); const isSetupComplete = kbStatus?.elser_exists && @@ -82,15 +83,20 @@ export const useChatSend = ({ ); return; } - + const apiConfig = currentConversation.apiConfig; + let newConvo; + if (currentConversation.id === '') { + // create conversation with empty title, GENERATE_CHAT_TITLE graph step will properly title + newConvo = await createConversation(currentConversation); + } + const convo: Conversation = { ...currentConversation, ...(newConvo ?? {}) }; const userMessage = getCombinedMessage({ - currentReplacements: currentConversation.replacements, + currentReplacements: convo.replacements, promptText, selectedPromptContexts, }); - const baseReplacements: Replacements = - userMessage.replacements ?? currentConversation.replacements; + const baseReplacements: Replacements = userMessage.replacements ?? convo.replacements; const selectedPromptContextsReplacements = Object.values( selectedPromptContexts @@ -100,12 +106,12 @@ export const useChatSend = ({ ...baseReplacements, ...selectedPromptContextsReplacements, }; - const updatedMessages = [...currentConversation.messages, userMessage].map((m) => ({ + const updatedMessages = [...convo.messages, userMessage].map((m) => ({ ...m, content: m.content ?? '', })); setCurrentConversation({ - ...currentConversation, + ...convo, replacements, messages: updatedMessages, }); @@ -114,38 +120,41 @@ export const useChatSend = ({ setSelectedPromptContexts({}); const rawResponse = await sendMessage({ - apiConfig: currentConversation.apiConfig, + apiConfig, http, message: userMessage.content ?? '', - conversationId: currentConversation.id, + conversationId: convo.id, replacements, }); assistantTelemetry?.reportAssistantMessageSent({ role: userMessage.role, - actionTypeId: currentConversation.apiConfig.actionTypeId, - model: currentConversation.apiConfig.model, - provider: currentConversation.apiConfig.provider, + actionTypeId: apiConfig.actionTypeId, + model: apiConfig.model, + provider: apiConfig.provider, isEnabledKnowledgeBase: isSetupComplete ?? false, }); const responseMessage: ClientMessage = getMessageFromRawResponse(rawResponse); - + if (convo.title === '') { + convo.title = (await getConversation(convo.id))?.title ?? ''; + } setCurrentConversation({ - ...currentConversation, + ...convo, replacements, messages: [...updatedMessages, responseMessage], }); assistantTelemetry?.reportAssistantMessageSent({ role: responseMessage.role, - actionTypeId: currentConversation.apiConfig.actionTypeId, - model: currentConversation.apiConfig.model, - provider: currentConversation.apiConfig.provider, + actionTypeId: apiConfig.actionTypeId, + model: apiConfig.model, + provider: apiConfig.provider, isEnabledKnowledgeBase: isSetupComplete ?? false, }); }, [ assistantTelemetry, + createConversation, currentConversation, http, isSetupComplete, @@ -216,7 +225,7 @@ export const useChatSend = ({ const handleChatSend = useCallback( async (promptText: string) => { await handleSendMessage(promptText); - if (currentConversation?.title === NEW_CHAT) { + if (currentConversation?.title === NEW_CHAT || currentConversation?.title === '') { await refetchCurrentUserConversations(); } }, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index a0a9685a43940..12ead02156168 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -42,7 +42,6 @@ import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bo interface Props { connectors: AIConnector[] | undefined; defaultConnector?: AIConnector; - defaultSelectedConversation: Conversation; isDisabled?: boolean; } @@ -54,7 +53,6 @@ export const DEFAULT_TABLE_OPTIONS = { const ConversationSettingsManagementComponent: React.FC = ({ connectors, defaultConnector, - defaultSelectedConversation, isDisabled, }) => { const { @@ -139,9 +137,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ // Local state for saving previously selected items so tab switching is friendlier // Conversation Selection State - const [selectedConversation, setSelectedConversation] = useState(() => { - return conversationSettings[defaultSelectedConversation.title]; - }); + const [selectedConversation, setSelectedConversation] = useState(); const onSelectedConversationChange = useCallback((conversation?: Conversation) => { setSelectedConversation(conversation); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx index 2fc6a603d8a82..dd679f09bbed6 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -77,12 +77,10 @@ const renderAssistant = async (extraProps = {}) => { return assistant; }; const mockDeleteConvo = jest.fn(); -const mockGetDefaultConversation = jest.fn().mockReturnValue(mockData.welcome_id); const clearConversation = jest.fn(); const mockUseConversation = { clearConversation: clearConversation.mockResolvedValue(mockData.welcome_id), getConversation: jest.fn(), - getDefaultConversation: mockGetDefaultConversation, deleteConversation: mockDeleteConvo, setApiConfig: jest.fn().mockResolvedValue({}), }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index 0c9e2b8b3af27..f9c4e39c61f38 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -36,7 +36,6 @@ import { useCurrentConversation } from './use_current_conversation'; import { useDataStreamApis } from './use_data_stream_apis'; import { useChatSend } from './chat_send/use_chat_send'; import { ChatSend } from './chat_send'; -import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations'; import { getDefaultConnector } from './helpers'; import { useAssistantContext } from '../assistant_context'; @@ -145,10 +144,7 @@ const AssistantComponent: React.FC = ({ refetchCurrentUserConversations, conversationId: getLastConversationId(conversationTitle), mayUpdateConversations: - isFetchedConnectors && - isFetchedCurrentUserConversations && - isFetchedPrompts && - Object.keys(conversations).length > 0, + isFetchedConnectors && isFetchedCurrentUserConversations && isFetchedPrompts, }); const isInitialLoad = useMemo(() => { @@ -157,7 +153,7 @@ const AssistantComponent: React.FC = ({ } return ( (!isFetchedAnonymizationFields && !isFetchedCurrentUserConversations && !isFetchedPrompts) || - !(currentConversation && currentConversation?.id !== '') + !currentConversation ); }, [ currentConversation, @@ -192,13 +188,11 @@ const AssistantComponent: React.FC = ({ // Clear it if there is no connectors useEffect(() => { if (isFetchedConnectors && !connectors?.length) { - return setLastConversationId(WELCOME_CONVERSATION_TITLE); + return setLastConversationId(''); } if (!currentConversation?.excludeFromLastConversationStorage) { - setLastConversationId( - !isEmpty(currentConversation?.id) ? currentConversation?.id : currentConversation?.title - ); + setLastConversationId(!isEmpty(currentConversation?.id) ? currentConversation?.id : ''); } }, [ isFetchedConnectors, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx index 1363a29fc9671..9c70938978235 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx @@ -11,7 +11,6 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { mockSystemPrompt } from '../../../mock/system_prompt'; import { SystemPrompt } from '.'; import { Conversation } from '../../../..'; -import { DEFAULT_CONVERSATION_TITLE } from '../../use_conversation/translations'; import { TestProviders } from '../../../mock/test_providers/test_providers'; import { WELCOME_CONVERSATION } from '../../use_conversation/sample_conversations'; import { PromptResponse } from '@kbn/elastic-assistant-common'; @@ -28,7 +27,7 @@ const BASE_CONVERSATION: Conversation = { }; const mockConversations = { - [DEFAULT_CONVERSATION_TITLE]: BASE_CONVERSATION, + default: BASE_CONVERSATION, }; const mockSystemPrompts: PromptResponse[] = [mockSystemPrompt]; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx index c0dc904695257..7769b7f686723 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx @@ -9,7 +9,6 @@ import React, { useMemo } from 'react'; import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { Conversation } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; @@ -35,7 +34,6 @@ import { SettingsTabs } from './types'; interface Props { dataViews: DataViewsContract; - selectedConversation: Conversation; onTabChange?: (tabId: string) => void; currentTab: SettingsTabs; } @@ -45,12 +43,7 @@ interface Props { * anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag. */ export const AssistantSettingsManagement: React.FC = React.memo( - ({ - dataViews, - selectedConversation: defaultSelectedConversation, - onTabChange, - currentTab: selectedSettingsTab, - }) => { + ({ dataViews, onTabChange, currentTab: selectedSettingsTab }) => { const { assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, http, @@ -150,7 +143,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index da5837154ec14..daacfc4fbf7c2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -9,7 +9,6 @@ import { useCallback } from 'react'; import { ApiConfig } from '@kbn/elastic-assistant-common'; import { useAssistantContext } from '../../assistant_context'; import { Conversation, ClientMessage } from '../../assistant_context/types'; -import * as i18n from './translations'; import { getDefaultSystemPrompt } from './helpers'; import { createConversation as createConversationApi, @@ -17,24 +16,8 @@ import { getConversationById, updateConversation, } from '../api/conversations'; -import { WELCOME_CONVERSATION } from './sample_conversations'; import { useFetchPrompts } from '../api/prompts/use_fetch_prompts'; -export const DEFAULT_CONVERSATION_STATE: Conversation = { - id: '', - messages: [], - replacements: {}, - category: 'assistant', - title: i18n.DEFAULT_CONVERSATION_TITLE, -}; - -interface CreateConversationProps { - cTitle: string; - messages?: ClientMessage[]; - conversationIds?: string[]; - apiConfig?: Conversation['apiConfig']; -} - interface SetApiConfigProps { conversation: Conversation; apiConfig: ApiConfig; @@ -47,7 +30,6 @@ interface UpdateConversationTitleProps { export interface UseConversation { clearConversation: (conversation: Conversation) => Promise; - getDefaultConversation: ({ cTitle, messages }: CreateConversationProps) => Conversation; deleteConversation: (conversationId: string) => void; removeLastMessage: (conversationId: string) => Promise; setApiConfig: ({ @@ -121,26 +103,6 @@ export const useConversation = (): UseConversation => { [allPrompts, http, toasts] ); - /** - * Create a new conversation with the given conversationId, and optionally add messages - */ - const getDefaultConversation = useCallback( - ({ cTitle, messages }: CreateConversationProps): Conversation => { - const newConversation: Conversation = - cTitle === i18n.WELCOME_CONVERSATION_TITLE - ? WELCOME_CONVERSATION - : { - ...DEFAULT_CONVERSATION_STATE, - id: '', - title: cTitle, - messages: messages != null ? messages : [], - }; - - return newConversation; - }, - [] - ); - /** * Create a new conversation with the given conversation */ @@ -204,7 +166,6 @@ export const useConversation = (): UseConversation => { return { clearConversation, - getDefaultConversation, deleteConversation, removeLastMessage, setApiConfig, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/sample_conversations.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/sample_conversations.tsx index 8e7e297ca09b3..74f938d48baa7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/sample_conversations.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/sample_conversations.tsx @@ -6,11 +6,10 @@ */ import { Conversation } from '../../assistant_context/types'; -import { WELCOME_CONVERSATION_TITLE } from './translations'; export const WELCOME_CONVERSATION: Conversation = { id: '', - title: WELCOME_CONVERSATION_TITLE, + title: 'Welcome', category: 'assistant', messages: [], replacements: {}, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/translations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/translations.ts deleted file mode 100644 index 5a48ad53d044d..0000000000000 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/translations.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const WELCOME_CONVERSATION_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.useConversation.welcomeConversationTitle', - { - defaultMessage: 'Welcome', - } -); -export const DEFAULT_CONVERSATION_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.useConversation.defaultConversationTitle', - { - defaultMessage: 'Default', - } -); - -export const ELASTIC_AI_ASSISTANT_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantTitle', - { - defaultMessage: 'Elastic AI Assistant', - } -); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx index f5ea35983dd80..33839de380a85 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx @@ -44,7 +44,6 @@ describe('useCurrentConversation', () => { createConversation: jest.fn(), deleteConversation: jest.fn(), getConversation: jest.fn(), - getDefaultConversation: jest.fn(), setApiConfig: jest.fn(), }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index ab5e5532a19cb..b969de2f80040 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -8,15 +8,11 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; import { QueryObserverResult } from '@tanstack/react-query'; import { PromptResponse } from '@kbn/elastic-assistant-common'; -import { find } from 'lodash'; -import deepEqual from 'fast-deep-equal'; import { AIConnector } from '../../connectorland/connector_selector'; -import { getGenAiConfig } from '../../connectorland/helpers'; -import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations'; -import { getDefaultNewSystemPrompt, getDefaultSystemPrompt } from '../use_conversation/helpers'; +import { getDefaultSystemPrompt } from '../use_conversation/helpers'; import { useConversation } from '../use_conversation'; import { sleep } from '../helpers'; -import { Conversation, WELCOME_CONVERSATION_TITLE } from '../../..'; +import { Conversation } from '../../..'; export interface Props { allSystemPrompts: PromptResponse[]; @@ -57,20 +53,35 @@ export const useCurrentConversation = ({ mayUpdateConversations, refetchCurrentUserConversations, }: Props): UseCurrentConversation => { - const { - createConversation, - deleteConversation, - getConversation, - getDefaultConversation, - setApiConfig, - } = useConversation(); + const { deleteConversation, getConversation, setApiConfig } = useConversation(); const [currentConversation, setCurrentConversation] = useState(); - const [currentConversationId, setCurrentConversationId] = useState( - conversationId - ); useEffect(() => { - setCurrentConversationId(conversationId); - }, [conversationId]); + if (!mayUpdateConversations || !!currentConversation) return; + if (conversationId === '') { + return setCurrentConversation({ + apiConfig: defaultConnector + ? { + connectorId: defaultConnector.id ?? '', + actionTypeId: defaultConnector.actionTypeId ?? '', + } + : undefined, + id: '', + messages: [], + replacements: {}, + category: 'assistant', + title: '', + }); + } + if (conversations[conversationId]) { + setCurrentConversation(conversations[conversationId]); + } + }, [ + conversationId, + conversations, + currentConversation, + defaultConnector, + mayUpdateConversations, + ]); /** * START SYSTEM PROMPT */ @@ -116,10 +127,9 @@ export const useCurrentConversation = ({ cTitle, isStreamRefetch = false, }: { cId?: string; cTitle?: string; isStreamRefetch?: boolean } = {}) => { - if (cId === '' || (cTitle && !conversations[cTitle])) { + if (cId === '') { return; } - const cConversationId = cId ?? (cTitle && conversations[cTitle].id) ?? currentConversation?.id; @@ -151,101 +161,25 @@ export const useCurrentConversation = ({ [conversations, currentConversation?.id, getConversation] ); - const initializeDefaultConversationWithConnector = useCallback( - async (defaultConvo: Conversation): Promise => { - const apiConfig = getGenAiConfig(defaultConnector); - const updatedConvo = - (await setApiConfig({ - conversation: defaultConvo, - apiConfig: { - ...defaultConvo?.apiConfig, - connectorId: (defaultConnector?.id as string) ?? '', - actionTypeId: (defaultConnector?.actionTypeId as string) ?? '.gen-ai', - provider: apiConfig?.apiProvider, - model: apiConfig?.defaultModel, - defaultSystemPromptId: allSystemPrompts.find((sp) => sp.isNewConversationDefault)?.id, - }, - })) ?? defaultConvo; - await refetchCurrentUserConversations(); - return updatedConvo; - }, - [allSystemPrompts, defaultConnector, refetchCurrentUserConversations, setApiConfig] - ); - const handleOnConversationSelected = useCallback( async ({ cId, cTitle }: { cId: string; cTitle: string }) => { - const allConversations = await refetchCurrentUserConversations(); - - // This is a default conversation that has not yet been initialized - // add the default connector config - if (cId === '' && allConversations?.data?.[cTitle]) { - const updatedConvo = await initializeDefaultConversationWithConnector( - allConversations.data[cTitle] - ); - setCurrentConversationId(updatedConvo.id); - } else if (allConversations?.data?.[cId]) { - setCurrentConversationId(cId); + if (cId === '') { + return setCurrentConversation({ + // get apiConfig from currentConversation + ...currentConversation, + id: '', + messages: [], + replacements: {}, + category: 'assistant', + title: '', + }); } + // refetch will set the currentConversation + await refetchCurrentConversation({ cId, cTitle }); }, - [ - initializeDefaultConversationWithConnector, - refetchCurrentUserConversations, - setCurrentConversationId, - ] + [currentConversation, refetchCurrentConversation] ); - // update currentConversation when conversations or currentConversationId update - useEffect(() => { - if (!mayUpdateConversations) return; - const updateConversation = async () => { - const nextConversation: Conversation = - (currentConversationId && conversations[currentConversationId]) || - // if currentConversationId is not an id, it should be a title from a - // default conversation that has not yet been initialized - find(conversations, ['title', currentConversationId]) || - find(conversations, ['title', WELCOME_CONVERSATION_TITLE]) || - // if no Welcome convo exists, create one - getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE }); - - // on the off chance that the conversation is not found, return - if (!nextConversation) { - return; - } - - if (nextConversation && nextConversation.id === '') { - // This is a default conversation that has not yet been initialized - const conversation = await initializeDefaultConversationWithConnector(nextConversation); - return setCurrentConversation(conversation); - } - setCurrentConversation((prev) => { - if (deepEqual(prev, nextConversation)) return prev; - - if ( - prev && - prev.id === nextConversation.id && - // if the conversation id has not changed and the previous conversation has more messages - // it is because the local conversation has a readable stream running - // and it has not yet been persisted to the stored conversation - prev.messages.length > nextConversation.messages.length - ) { - return { - ...nextConversation, - messages: prev.messages, - }; - } - - return nextConversation; - }); - }; - updateConversation(); - }, [ - currentConversationId, - conversations, - getDefaultConversation, - initializeDefaultConversationWithConnector, - mayUpdateConversations, - ]); - const handleOnConversationDeleted = useCallback( async (cTitle: string) => { await deleteConversation(conversations[cTitle].id); @@ -255,52 +189,11 @@ export const useCurrentConversation = ({ ); const handleCreateConversation = useCallback(async () => { - const newChatExists = find(conversations, ['title', NEW_CHAT]); - if (newChatExists && !newChatExists.messages.length) { - handleOnConversationSelected({ - cId: newChatExists.id, - cTitle: newChatExists.title, - }); - return; - } - const newSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts); - - let conversation: Partial = {}; - if (currentConversation?.apiConfig) { - const { defaultSystemPromptId: _, ...restApiConfig } = currentConversation?.apiConfig; - conversation = - restApiConfig.actionTypeId != null - ? { - apiConfig: { - ...restApiConfig, - ...(newSystemPrompt?.id != null - ? { defaultSystemPromptId: newSystemPrompt.id } - : {}), - }, - } - : {}; - } - const newConversation = await createConversation({ - title: NEW_CHAT, - ...conversation, + handleOnConversationSelected({ + cId: '', + cTitle: '', }); - - if (newConversation) { - handleOnConversationSelected({ - cId: newConversation.id, - cTitle: newConversation.title, - }); - } else { - await refetchCurrentUserConversations(); - } - }, [ - allSystemPrompts, - conversations, - createConversation, - currentConversation?.apiConfig, - handleOnConversationSelected, - refetchCurrentUserConversations, - ]); + }, [handleOnConversationSelected]); return { currentConversation, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index fdbb463dfcf12..b3603734d7e86 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -44,7 +44,6 @@ import { TRACE_OPTIONS_SESSION_STORAGE_KEY, } from './constants'; import { useCapabilities } from '../assistant/api/capabilities/use_capabilities'; -import { WELCOME_CONVERSATION_TITLE } from '../assistant/use_conversation/translations'; import { SettingsTabs } from '../assistant/settings/types'; import { AssistantNavLink } from './assistant_nav_link'; @@ -285,9 +284,8 @@ export const AssistantProvider: React.FC = ({ const getLastConversationId = useCallback( // if a conversationId has been provided, use that // if not, check local storage - // last resort, go to welcome conversation - (conversationId?: string) => - conversationId ?? localStorageLastConversationId ?? WELCOME_CONVERSATION_TITLE, + // last resort, empty id + (conversationId?: string) => conversationId ?? localStorageLastConversationId ?? '', [localStorageLastConversationId] ); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index cb10c83f9523e..70f8676b48c83 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -98,11 +98,6 @@ export { export { useLoadConnectors } from './impl/connectorland/use_load_connectors'; -export { - ELASTIC_AI_ASSISTANT_TITLE, - WELCOME_CONVERSATION_TITLE, -} from './impl/assistant/use_conversation/translations'; - export type { /** for rendering results in a code block */ CodeBlockDetails, diff --git a/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 8aaf7012ebf17..94b81d0bc9cd6 100644 --- a/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -86,7 +86,6 @@ const TestExternalProvidersComponent: React.FC = ({ }} getComments={mockGetComments} http={mockHttp} - baseConversations={{}} navigateToApp={mockNavigateToApp} productDocBase={{ installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts index cae2e4d6185b4..198008328876a 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts @@ -125,7 +125,6 @@ const retryRequest = async ( const getMockConversationContent = (): Partial => ({ title: `A ${randomBytes(4).toString('hex')} title`, - isDefault: false, messages: [ { content: 'Hello robot', diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx index ed13b06dd3f17..62dd79c94d912 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx @@ -10,11 +10,7 @@ import '@testing-library/jest-dom'; import { MemoryRouter } from '@kbn/shared-ux-router'; import { ManagementSettings } from './management_settings'; import type { Conversation } from '@kbn/elastic-assistant'; -import { - useAssistantContext, - useFetchCurrentUserConversations, - WELCOME_CONVERSATION_TITLE, -} from '@kbn/elastic-assistant'; +import { useAssistantContext, useFetchCurrentUserConversations } from '@kbn/elastic-assistant'; import { useKibana } from '../../common/lib/kibana'; import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -24,7 +20,7 @@ jest.mock('@kbn/elastic-assistant', () => ({ useAssistantContext: jest.fn(), useFetchCurrentUserConversations: jest.fn(), formatPersistedConversations: jest.fn(), - WELCOME_CONVERSATION_TITLE: 'Welcome Conversation', + Welcome: 'Welcome Conversation', })); jest.mock('@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management', () => ({ AssistantSettingsManagement: jest.fn(() =>
), @@ -48,8 +44,14 @@ describe('ManagementSettings', () => { const setCurrentUserAvatar = jest.fn(); const navigateToApp = jest.fn(); const mockConversations = { - [WELCOME_CONVERSATION_TITLE]: { title: WELCOME_CONVERSATION_TITLE }, - } as Record; + Welcome: { + title: 'Welcome', + id: 'Welcome', + messages: [], + replacements: {}, + category: 'assistant', + }, + } as unknown as Record; const renderComponent = ({ isAssistantEnabled = true, diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 325b6c29a1ab8..6d3bd0ffac2de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -14,17 +14,12 @@ import { formatPersistedConversations, useAssistantContext, useFetchCurrentUserConversations, - WELCOME_CONVERSATION_TITLE, } from '@kbn/elastic-assistant'; -import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api'; import { SECURITY_AI_SETTINGS } from '@kbn/elastic-assistant/impl/assistant/settings/translations'; import { CONVERSATIONS_TAB } from '@kbn/elastic-assistant/impl/assistant/settings/const'; import type { SettingsTabs } from '@kbn/elastic-assistant/impl/assistant/settings/types'; import { useKibana } from '../../common/lib/kibana'; - -const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE; - export const ManagementSettings = React.memo(() => { const { http, @@ -54,15 +49,6 @@ export const ManagementSettings = React.memo(() => { isAssistantEnabled, }); - const { getDefaultConversation } = useConversation(); - - const currentConversation = useMemo( - () => - conversations?.[defaultSelectedConversationId] ?? - getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE }), - [conversations, getDefaultConversation] - ); - docTitle.change(SECURITY_AI_SETTINGS); const [searchParams] = useSearchParams(); @@ -131,7 +117,6 @@ export const ManagementSettings = React.memo(() => { if (conversations) { return ( Date: Mon, 10 Feb 2025 11:17:44 -0700 Subject: [PATCH 05/80] date category --- .../conversations/common_attributes.gen.ts | 6 +- .../common_attributes.schema.yaml | 5 +- .../conversation_sidepanel/date_utils.ts | 23 +++++ .../conversation_sidepanel/index.tsx | 96 ++++++++++++------- .../conversation_sidepanel/translations.ts | 27 ++++++ .../use_conversations_by_date.ts | 67 +++++++++++++ .../utils.test.tsx | 4 +- .../use_current_conversation/index.tsx | 29 +++--- .../impl/assistant_context/types.tsx | 4 +- .../scripts/create_conversations_script.ts | 73 +++++++++----- .../conversations/create_conversation.ts | 7 +- 11 files changed, 262 insertions(+), 79 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/date_utils.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/use_conversations_by_date.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts index f5bca80302df8..da332cbdb9389 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.gen.ts @@ -318,7 +318,7 @@ export const ConversationResponse = z.object({ */ updatedAt: z.string().optional(), /** - * The last time conversation was updated. + * The time conversation was created. */ createdAt: z.string(), replacements: Replacements.optional(), @@ -395,6 +395,10 @@ export const ConversationCreateProps = z.object({ */ excludeFromLastConversationStorage: z.boolean().optional(), replacements: Replacements.optional(), + /** + * Set the creation date. + */ + createdAt: z.string().optional(), }); export type ConversationMessageCreateProps = z.infer; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml index 180836eb00cd4..6c405d1e0db2d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/conversations/common_attributes.schema.yaml @@ -305,7 +305,7 @@ components: description: The last time conversation was updated. type: string createdAt: - description: The last time conversation was updated. + description: The time conversation was created. type: string replacements: $ref: '#/components/schemas/Replacements' @@ -384,6 +384,9 @@ components: type: boolean replacements: $ref: '#/components/schemas/Replacements' + createdAt: + description: Set the creation date. + type: string ConversationMessageCreateProps: type: object diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/date_utils.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/date_utils.ts new file mode 100644 index 0000000000000..474c5baf7c3a0 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/date_utils.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import datemath from '@kbn/datemath'; + +export const getAbsoluteTime = (range: string, opts: Parameters[1] = {}) => { + const parsed = datemath.parse(range, opts); + + if (parsed && parsed.isValid()) { + return parsed.valueOf(); + } + + return undefined; +}; + +export const isValidDateMath = (value: string): boolean => { + const parsedValue = datemath.parse(value); + return !!(parsedValue && parsedValue.isValid()); +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx index 47d46d16078b2..4811f0e60c82b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx @@ -13,12 +13,16 @@ import { EuiListGroupItem, EuiPanel, EuiConfirmModal, + EuiText, + EuiSpacer, + useEuiTheme, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import useEvent from 'react-use/lib/useEvent'; import { css } from '@emotion/react'; -import { isEmpty, findIndex, orderBy } from 'lodash'; +import { isEmpty, findIndex } from 'lodash'; +import { useConversationsByDate } from './use_conversations_by_date'; import { DataStreamApis } from '../../use_data_stream_apis'; import { Conversation } from '../../../..'; import * as i18n from './translations'; @@ -79,14 +83,19 @@ export const ConversationSidePanel = React.memo( onConversationDeleted, onConversationCreate, }) => { + const euiTheme = useEuiTheme(); + + const titleClassName = css` + text-transform: uppercase; + font-weight: ${euiTheme.euiTheme.font.weight.bold}; + `; const [deleteConversationItem, setDeleteConversationItem] = useState(null); + const conversationsCategorizedByDate = useConversationsByDate(Object.values(conversations)); + const conversationList = useMemo( - () => - orderBy(Object.values(conversations), 'updatedAt', 'desc').sort((a, b) => - a.id.length > b.id.length ? -1 : 1 - ), - [conversations] + () => Object.values(conversationsCategorizedByDate).flatMap((p) => p), + [conversationsCategorizedByDate] ); const conversationIds = useMemo(() => Object.keys(conversations), [conversations]); @@ -184,36 +193,51 @@ export const ConversationSidePanel = React.memo( `} > - - {conversationList.map((conversation) => ( - - onConversationSelected({ cId: conversation.id, cTitle: conversation.title }) - } - label={conversation.title} - data-test-subj={`conversation-select-${conversation.title}`} - isActive={ - !isEmpty(conversation.id) - ? conversation.id === currentConversation?.id - : conversation.title === currentConversation?.title - } - extraAction={{ - color: 'danger', - onClick: () => setDeleteConversationItem(conversation), - iconType: 'trash', - iconSize: 's', - 'aria-label': i18n.DELETE_CONVERSATION_ARIA_LABEL, - 'data-test-subj': 'delete-option', - }} - /> - ))} - + {Object.entries(conversationsCategorizedByDate).map(([category, convoList]) => + convoList.length ? ( + + + + {i18n.DATE_CATEGORY_LABELS[category]} + + + + {convoList.map((conversation) => ( + + onConversationSelected({ + cId: conversation.id, + cTitle: conversation.title, + }) + } + label={conversation.title} + data-test-subj={`conversation-select-${conversation.title}`} + isActive={ + !isEmpty(conversation.id) + ? conversation.id === currentConversation?.id + : conversation.title === currentConversation?.title + } + extraAction={{ + color: 'danger', + onClick: () => setDeleteConversationItem(conversation), + iconType: 'trash', + iconSize: 's', + 'aria-label': i18n.DELETE_CONVERSATION_ARIA_LABEL, + 'data-test-subj': 'delete-option', + }} + /> + ))} + + + + ) : null + )} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/translations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/translations.ts index 20a7075c8a727..3d08b00c6d32d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/translations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/translations.ts @@ -41,3 +41,30 @@ export const DELETE_BUTTON_TEXT = i18n.translate( defaultMessage: 'Delete', } ); + +export const DATE_CATEGORY_LABELS: Record = { + TODAY: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.today', { + defaultMessage: 'Today', + }), + YESTERDAY: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.yesterday', { + defaultMessage: 'Yesterday', + }), + THIS_WEEK: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.thisWeek', { + defaultMessage: 'This Week', + }), + LAST_WEEK: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.lastWeek', { + defaultMessage: 'Last Week', + }), + THIS_MONTH: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.thisMonth', { + defaultMessage: 'This Month', + }), + LAST_MONTH: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.lastMonth', { + defaultMessage: 'Last Month', + }), + THIS_YEAR: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.thisYear', { + defaultMessage: 'This Year', + }), + OLDER: i18n.translate('xpack.aiAssistant.conversationList.dateGroupTitle.older', { + defaultMessage: 'Older', + }), +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/use_conversations_by_date.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/use_conversations_by_date.ts new file mode 100644 index 0000000000000..b7a55a6715d83 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/use_conversations_by_date.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { Conversation } from '../../../..'; +import { getAbsoluteTime, isValidDateMath } from './date_utils'; + +export function useConversationsByDate(conversations: Conversation[] = []) { + return useMemo(() => { + const now = new Date(); + + const startOfToday = getAbsoluteTime('now/d', { forceNow: now }) ?? 0; + const startOfYesterday = getAbsoluteTime('now-1d/d', { forceNow: now }) ?? 0; + const startOfThisWeek = getAbsoluteTime('now/w', { forceNow: now }) ?? 0; + const startOfLastWeek = getAbsoluteTime('now-1w/w', { forceNow: now }) ?? 0; + const startOfThisMonth = getAbsoluteTime('now/M', { forceNow: now }) ?? 0; + const startOfLastMonth = getAbsoluteTime('now-1M/M', { forceNow: now }) ?? 0; + const startOfThisYear = getAbsoluteTime('now/y', { forceNow: now }) ?? 0; + + const categorizedConversations: Record = { + TODAY: [], + YESTERDAY: [], + THIS_WEEK: [], + LAST_WEEK: [], + THIS_MONTH: [], + LAST_MONTH: [], + THIS_YEAR: [], + OLDER: [], + }; + + conversations.forEach((conversation) => { + if (!conversation.updatedAt) { + return; + } + if (!isValidDateMath(conversation.updatedAt)) { + return; + } + const updatedAt = new Date(conversation.updatedAt).getTime(); + + const displayedConversation = conversation; + + if (updatedAt >= startOfToday) { + categorizedConversations.TODAY.push(displayedConversation); + } else if (updatedAt >= startOfYesterday) { + categorizedConversations.YESTERDAY.push(displayedConversation); + } else if (updatedAt >= startOfThisWeek) { + categorizedConversations.THIS_WEEK.push(displayedConversation); + } else if (updatedAt >= startOfLastWeek) { + categorizedConversations.LAST_WEEK.push(displayedConversation); + } else if (updatedAt >= startOfThisMonth) { + categorizedConversations.THIS_MONTH.push(displayedConversation); + } else if (updatedAt >= startOfLastMonth) { + categorizedConversations.LAST_MONTH.push(displayedConversation); + } else if (updatedAt >= startOfThisYear) { + categorizedConversations.THIS_YEAR.push(displayedConversation); + } else { + categorizedConversations.OLDER.push(displayedConversation); + } + }); + + return categorizedConversations; + }, [conversations]); +} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx index 4ea3943598501..041e00944eeb2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx @@ -11,7 +11,7 @@ describe('getSelectedConversations', () => { const conversationSettings = { '8f1e3218-0b02-480a-8791-78c1ed5f3708': { timestamp: '2024-06-25T12:33:26.779Z', - createdAt: '2024-06-25T12:33:26.779Z' as unknown as Date, + createdAt: '2024-06-25T12:33:26.779Z', users: [ { id: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', @@ -28,7 +28,7 @@ describe('getSelectedConversations', () => { model: 'gpt-4', }, messages: [], - updatedAt: '2024-06-25T18:35:28.217Z' as unknown as Date, + updatedAt: '2024-06-25T18:35:28.217Z', namespace: 'default', id: '8f1e3218-0b02-480a-8791-78c1ed5f3708', replacements: {}, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index b969de2f80040..03d3f122c578b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -57,23 +57,24 @@ export const useCurrentConversation = ({ const [currentConversation, setCurrentConversation] = useState(); useEffect(() => { if (!mayUpdateConversations || !!currentConversation) return; + const emptyConversation = { + apiConfig: defaultConnector + ? { + connectorId: defaultConnector.id ?? '', + actionTypeId: defaultConnector.actionTypeId ?? '', + } + : undefined, + id: '', + messages: [], + replacements: {}, + category: 'assistant', + title: '', + }; if (conversationId === '') { - return setCurrentConversation({ - apiConfig: defaultConnector - ? { - connectorId: defaultConnector.id ?? '', - actionTypeId: defaultConnector.actionTypeId ?? '', - } - : undefined, - id: '', - messages: [], - replacements: {}, - category: 'assistant', - title: '', - }); + return setCurrentConversation(emptyConversation); } if (conversations[conversationId]) { - setCurrentConversation(conversations[conversationId]); + setCurrentConversation(conversations[conversationId] ?? emptyConversation); } }, [ conversationId, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index 0529e0d2a43b8..003305ff61082 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -37,8 +37,8 @@ export interface Conversation { id: string; title: string; messages: ClientMessage[]; - updatedAt?: Date; - createdAt?: Date; + updatedAt?: string; + createdAt?: string; replacements: Replacements; excludeFromLastConversationStorage?: boolean; } diff --git a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts index 198008328876a..69d1d75efcf5d 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts @@ -35,7 +35,7 @@ export const create = async () => { .option('kibana', { type: 'string', description: 'Kibana url including auth', - default: `http://elastic:changeme@localhost:5601`, + default: `http://elastic:changeme@localhost:5601/kbn`, }) .parse(); const kibanaUrl = removeTrailingSlash(argv.kibana); @@ -122,30 +122,59 @@ const retryRequest = async ( throw e; // If retries are exhausted, throw the error } }; +const getRandomISODate = () => { + const now = new Date(); + const probabilities = [ + { chance: 3 / 10, daysAgo: Math.floor(Math.random() * 335) + 31 }, // Over a month ago + { chance: 1 / 10, daysAgo: 4 }, // In the last week + { chance: 1 / 10, daysAgo: 1 }, // Yesterday + { chance: 1 / 10, daysAgo: 0 }, // Today + { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 7) + 7 }, // Two weeks ago + { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 14) + 14 }, // Over two weeks ago + ]; -const getMockConversationContent = (): Partial => ({ - title: `A ${randomBytes(4).toString('hex')} title`, - messages: [ - { - content: 'Hello robot', - role: 'user', - timestamp: '2019-12-13T16:40:33.400Z', - traceData: { - traceId: '1', - transactionId: '2', + const rand = Math.random(); + let cumulativeProbability = 0; + + for (const { chance, daysAgo } of probabilities) { + cumulativeProbability += chance; + if (rand <= cumulativeProbability) { + const targetDate = new Date(now); + targetDate.setDate(now.getDate() - daysAgo); + return targetDate.toISOString(); + } + } + + // Fallback (should never be reached) + return now.toISOString(); +}; +const getMockConversationContent = (): Partial => { + const timestamp = getRandomISODate(); + return { + title: `A ${randomBytes(4).toString('hex')} title`, + createdAt: timestamp, + messages: [ + { + content: 'Hello robot', + role: 'user', + timestamp, + traceData: { + traceId: '1', + transactionId: '2', + }, }, - }, - { - content: 'Hello human', - role: 'assistant', - timestamp: '2019-12-13T16:41:33.400Z', - traceData: { - traceId: '3', - transactionId: '4', + { + content: 'Hello human', + role: 'assistant', + timestamp, + traceData: { + traceId: '3', + transactionId: '4', + }, }, - }, - ], -}); + ], + }; +}; export const AllowedActionTypeIds = ['.bedrock', '.gen-ai', '.gemini']; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts index a10dae3ac015e..b9857d4d3747e 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/create_conversation.ts @@ -34,7 +34,12 @@ export const createConversation = async ({ logger, }: CreateConversationParams): Promise => { const createdAt = new Date().toISOString(); - const body = transformToCreateScheme(createdAt, spaceId, user, conversation); + const body = transformToCreateScheme( + conversation.createdAt ?? createdAt, + spaceId, + user, + conversation + ); try { const response = await esClient.create({ body, From b4a9ad84c31d82709da2bd63342bf9dffde3968a Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 10 Feb 2025 13:19:35 -0700 Subject: [PATCH 06/80] minimal field fetching --- .../api/conversations/conversations.ts | 19 +++++++++----- .../use_fetch_current_user_conversations.ts | 25 ++++++++++++------- .../use_current_conversation/index.tsx | 14 ++++++----- .../shared/kbn-elastic-assistant/index.ts | 2 +- .../server/ai_assistant_data_clients/find.ts | 4 ++- .../routes/user_conversations/find_route.ts | 14 ++--------- .../public/assistant/provider.tsx | 6 ++--- 7 files changed, 46 insertions(+), 38 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/conversations.ts index 54bac7e563acc..d2a5c9d265cb1 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/conversations.ts @@ -61,16 +61,15 @@ export const getConversationById = async ({ }; /** - * API call for getting all user conversations. + * API call for determining whether any user conversations exist * - * @param {Object} options - The options object. * @param {HttpSetup} options.http - HttpSetup * @param {IToasts} [options.toasts] - IToasts * @param {AbortSignal} [options.signal] - AbortSignal * - * @returns {Promise} + * @returns {Promise} */ -export const getUserConversations = async ({ +export const getUserConversationsExist = async ({ http, signal, toasts, @@ -78,16 +77,24 @@ export const getUserConversations = async ({ http: HttpSetup; toasts?: IToasts; signal?: AbortSignal | undefined; -}) => { +}): Promise => { try { - return await http.fetch( + const conversation = await http.fetch( ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { method: 'GET', version: API_VERSIONS.public.v1, signal, + query: { + per_page: 1, + page: 1, + // one field to keep request as small as possible + fields: ['title'], + }, } ); + + return conversation.total > 0; } catch (error) { toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, { title: i18n.translate('xpack.elasticAssistant.conversations.getUserConversationsError', { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 9006ca387e086..08e4658261ef7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -22,6 +22,8 @@ export interface FetchConversationsResponse { export interface UseFetchCurrentUserConversationsParams { http: HttpSetup; + page?: number; + perPage?: number; onFetch: (result: FetchConversationsResponse) => Record; signal?: AbortSignal | undefined; refetchOnWindowFocus?: boolean; @@ -41,29 +43,34 @@ export interface UseFetchCurrentUserConversationsParams { const query = { page: 1, per_page: 99, + fields: ['id', 'title', 'apiConfig', 'updatedAt'], }; -export const CONVERSATIONS_QUERY_KEYS = [ - ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, - query.page, - query.per_page, - API_VERSIONS.public.v1, -]; - export const useFetchCurrentUserConversations = ({ http, onFetch, + page, + perPage, signal, refetchOnWindowFocus = true, isAssistantEnabled, }: UseFetchCurrentUserConversationsParams) => useQuery( - CONVERSATIONS_QUERY_KEYS, + [ + ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, + page ?? query.page, + perPage ?? query.per_page, + API_VERSIONS.public.v1, + ], async () => http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { method: 'GET', version: API_VERSIONS.public.v1, - query, + query: { + ...query, + page: page ?? query.page, + per_page: perPage ?? query.per_page, + }, signal, }), { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index 03d3f122c578b..bf1ba4e21f90b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -189,12 +189,14 @@ export const useCurrentConversation = ({ [conversations, deleteConversation, refetchCurrentUserConversations] ); - const handleCreateConversation = useCallback(async () => { - handleOnConversationSelected({ - cId: '', - cTitle: '', - }); - }, [handleOnConversationSelected]); + const handleCreateConversation = useCallback( + async () => + handleOnConversationSelected({ + cId: '', + cTitle: '', + }), + [handleOnConversationSelected] + ); return { currentConversation, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index 70f8676b48c83..01ac84a1c5359 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -147,7 +147,7 @@ export { getConversationById } from './impl/assistant/api/conversations/conversa export { formatPersistedConversations } from './impl/assistant/helpers'; export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; -export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; +export { getUserConversationsExist, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; export { /** A range slider component, typically used to configure the number of alerts sent as context */ diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts index 9f37e45250a9c..7f4b627fd69ed 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts @@ -41,6 +41,8 @@ export interface FindResponse { total: number; } +const camelToSnake = (camelStr: string): string => + camelStr.replace(/(?({ esClient, filter, @@ -74,7 +76,7 @@ export const findDocuments = async ({ track_total_hits: true, sort, }, - _source: true, + _source: fields?.length ? fields.map((f) => camelToSnake(f)) : true, from: (page - 1) * perPage, ignore_unavailable: true, index, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.ts index cc07747725ee7..972921b115b49 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/user_conversations/find_route.ts @@ -73,23 +73,13 @@ export const findUserConversationsRoute = (router: ElasticAssistantPluginRouter) ? `name: "${currentUser?.username}"` : `id: "${currentUser?.profile_uid}"`; - const MAX_CONVERSATION_TOTAL = query.per_page; - // TODO remove once we have pagination https://github.com/elastic/kibana/issues/192714 - // do a separate search for default conversations and non-default conversations to ensure defaults always get included - // MUST MATCH THE LENGTH OF BASE_SECURITY_CONVERSATIONS from 'x-pack/solutions/security/plugins/security_solution/public/assistant/content/conversations/index.tsx' - const MAX_DEFAULT_CONVERSATION_TOTAL = 7; - const nonDefaultSize = MAX_CONVERSATION_TOTAL - MAX_DEFAULT_CONVERSATION_TOTAL; const result = await dataClient?.findDocuments({ - perPage: nonDefaultSize, + perPage: query.per_page, page: query.page, sortField: query.sort_field, sortOrder: query.sort_order, - filter: `users:{ ${userFilter} }${additionalFilter} and not is_default: true`, + filter: `users:{ ${userFilter} }${additionalFilter}`, fields: query.fields, - mSearch: { - filter: `users:{ ${userFilter} }${additionalFilter} and is_default: true`, - perPage: MAX_DEFAULT_CONVERSATION_TOTAL, - }, }); if (result) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index 37d0a33c86d78..94baa67ea6e0e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -14,7 +14,7 @@ import type { Conversation } from '@kbn/elastic-assistant'; import { AssistantProvider as ElasticAssistantProvider, bulkUpdateConversations, - getUserConversations, + getUserConversationsExist, getPrompts, bulkUpdatePrompts, } from '@kbn/elastic-assistant'; @@ -168,10 +168,10 @@ export const AssistantProvider: FC> = ({ children }) assistantAvailability.isAssistantEnabled && assistantAvailability.hasAssistantPrivilege ) { - const res = await getUserConversations({ + const conversationsExist = await getUserConversationsExist({ http, }); - if (res.total === 0) { + if (!conversationsExist) { await createConversations(notifications, http, storage); } } From 768fcee382f68c7654785331e27841738e7f83ed Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 11 Feb 2025 14:40:52 -0700 Subject: [PATCH 07/80] infinite query --- .../use_fetch_current_user_conversations.ts | 156 +++++++++++++----- .../conversation_sidepanel/index.tsx | 84 +++++++--- .../impl/assistant/helpers.test.ts | 2 +- .../impl/assistant/index.test.tsx | 118 ++++++------- .../impl/assistant/index.tsx | 4 + .../use_assistant_overlay/index.test.tsx | 22 +-- .../use_current_conversation/index.tsx | 10 +- .../impl/assistant/use_data_stream_apis.tsx | 33 ++-- 8 files changed, 261 insertions(+), 168 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 08e4658261ef7..f3f15b30e8a41 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -6,16 +6,23 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { useQuery } from '@tanstack/react-query'; +import { + QueryObserverResult, + RefetchOptions, + RefetchQueryFilters, + useInfiniteQuery, +} from '@tanstack/react-query'; import { API_VERSIONS, ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, } from '@kbn/elastic-assistant-common'; +import { InfiniteData } from '@tanstack/query-core/src/types'; +import { useCallback, useEffect, useRef } from 'react'; import { Conversation } from '../../../assistant_context/types'; export interface FetchConversationsResponse { page: number; - per_page: number; + perPage: number; total: number; data: Conversation[]; } @@ -24,60 +31,125 @@ export interface UseFetchCurrentUserConversationsParams { http: HttpSetup; page?: number; perPage?: number; - onFetch: (result: FetchConversationsResponse) => Record; signal?: AbortSignal | undefined; refetchOnWindowFocus?: boolean; isAssistantEnabled: boolean; } -/** - * API call for fetching assistant conversations for the current user - * - * @param {Object} options - The options object. - * @param {HttpSetup} options.http - HttpSetup - * @param {Function} [options.onFetch] - transformation function for conversations fetch result - * @param {AbortSignal} [options.signal] - AbortSignal - * - * @returns {useQuery} hook for getting the status of the conversations - */ +export interface FetchCurrentUserConversations { + data: Record; + isLoading: boolean; + refetch: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise, unknown>>; + isFetched: boolean; + isFetching: boolean; + setPaginationObserver: (ref: HTMLDivElement) => void; +} + const query = { page: 1, - per_page: 99, + perPage: 28, fields: ['id', 'title', 'apiConfig', 'updatedAt'], }; +const formatFetchedData = (data: InfiniteData | undefined) => + data?.pages.reduce((acc, curr) => { + return { + ...acc, + ...curr.data.reduce( + (conversationsArr, conversation) => ({ + ...conversationsArr, + [conversation.id]: conversation, + }), + {} + ), + }; + }, {}); + +/** + * API call for fetching assistant conversations for the current user + */ export const useFetchCurrentUserConversations = ({ http, - onFetch, - page, - perPage, + page = query.page, + perPage = query.perPage, signal, refetchOnWindowFocus = true, isAssistantEnabled, -}: UseFetchCurrentUserConversationsParams) => - useQuery( - [ - ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, - page ?? query.page, - perPage ?? query.per_page, - API_VERSIONS.public.v1, - ], - async () => - http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { - method: 'GET', - version: API_VERSIONS.public.v1, - query: { - ...query, - page: page ?? query.page, - per_page: perPage ?? query.per_page, - }, - signal, - }), - { - select: (data) => onFetch(data), - keepPreviousData: true, - initialData: { ...query, total: 0, data: [] }, - refetchOnWindowFocus, - enabled: isAssistantEnabled, +}: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { + const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { + return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { + method: 'GET', + version: API_VERSIONS.public.v1, + query: { + ...query, + page: pageParam?.page ?? page, + per_page: pageParam?.perPage ?? perPage, + }, + signal, + }); + }; + + const getNextPageParam = (lastPage: FetchConversationsResponse) => { + const totalPages = Math.max(1, Math.ceil(lastPage.total / lastPage.perPage)); + if (totalPages === lastPage.page) { + return; } + return { + ...lastPage, + page: lastPage.page + 1, + }; + }; + + const { refetch, data, fetchNextPage, isLoading, isFetching, isFetched, hasNextPage } = + useInfiniteQuery( + [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], + queryFn, + { + enabled: isAssistantEnabled, + getNextPageParam, + refetchOnWindowFocus, + } + ); + const formatted = formatFetchedData(data); + + const observerRef = useRef(); + const fetchNext = useCallback( + async ([{ isIntersecting }]: IntersectionObserverEntry[]) => { + if (isIntersecting && hasNextPage && !isLoading && !isFetching) { + await fetchNextPage(); + observerRef.current?.disconnect(); + } + }, + [fetchNextPage, hasNextPage, isFetching, isLoading] ); + + useEffect(() => { + return () => observerRef.current?.disconnect(); + }, []); + + // Attaches an intersection observer to the last element + // to trigger a callback to paginate when the user scrolls to it + const setPaginationObserver = useCallback( + (ref: HTMLDivElement) => { + observerRef.current?.disconnect(); + observerRef.current = new IntersectionObserver(fetchNext, { + root: null, + rootMargin: '0px', + threshold: 0.1, + }); + observerRef.current?.observe(ref); + }, + [fetchNext] + ); + + return { + data: formatted ?? {}, + isLoading, + refetch, + isFetched, + isFetching, + setPaginationObserver, + }; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx index 4811f0e60c82b..3d07fe7d603ca 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx @@ -16,6 +16,7 @@ import { EuiText, EuiSpacer, useEuiTheme, + EuiLoadingSpinner, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import useEvent from 'react-use/lib/useEvent'; @@ -34,10 +35,12 @@ interface Props { onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; shouldDisableKeyboardShortcut?: () => boolean; isDisabled?: boolean; + isFetchingCurrentUserConversations: boolean; conversations: Record; onConversationDeleted: (conversationId: string) => void; onConversationCreate: () => void; refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations']; + setPaginationObserver: (ref: HTMLDivElement) => void; } const getCurrentConversationIndex = ( @@ -79,9 +82,11 @@ export const ConversationSidePanel = React.memo( onConversationSelected, shouldDisableKeyboardShortcut = () => false, isDisabled = false, + isFetchingCurrentUserConversations, conversations, onConversationDeleted, onConversationCreate, + setPaginationObserver, }) => { const euiTheme = useEuiTheme(); @@ -98,6 +103,8 @@ export const ConversationSidePanel = React.memo( [conversationsCategorizedByDate] ); + const lastConversationId = conversationList[conversationList.length - 1]?.id; + const conversationIds = useMemo(() => Object.keys(conversations), [conversations]); // Callback for when user deletes a conversation @@ -176,7 +183,6 @@ export const ConversationSidePanel = React.memo( }, [deleteConversationItem, onDelete]); useEvent('keydown', onKeyDown); - return ( <> ( padding: 0; `} > - {convoList.map((conversation) => ( - - onConversationSelected({ - cId: conversation.id, - cTitle: conversation.title, - }) + {convoList.map((conversation) => { + const internalSetObserver = (ref: HTMLDivElement | null) => { + if (conversation.id === lastConversationId && ref) { + setPaginationObserver(ref); } - label={conversation.title} - data-test-subj={`conversation-select-${conversation.title}`} - isActive={ - !isEmpty(conversation.id) - ? conversation.id === currentConversation?.id - : conversation.title === currentConversation?.title - } - extraAction={{ - color: 'danger', - onClick: () => setDeleteConversationItem(conversation), - iconType: 'trash', - iconSize: 's', - 'aria-label': i18n.DELETE_CONVERSATION_ARIA_LABEL, - 'data-test-subj': 'delete-option', - }} - /> - ))} + }; + return ( + + + onConversationSelected({ + cId: conversation.id, + cTitle: conversation.title, + }) + } + label={conversation.title} + data-test-subj={`conversation-select-${conversation.title}`} + isActive={ + !isEmpty(conversation.id) + ? conversation.id === currentConversation?.id + : conversation.title === currentConversation?.title + } + extraAction={{ + color: 'danger', + onClick: () => setDeleteConversationItem(conversation), + iconType: 'trash', + iconSize: 's', + 'aria-label': i18n.DELETE_CONVERSATION_ARIA_LABEL, + 'data-test-subj': 'delete-option', + }} + /> + {conversation.id === lastConversationId && ( +
+ )} + + ); + })} ) : null )} + {isFetchingCurrentUserConversations && ( + + )} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts index 469e8e33c0044..22dbd2bcf680c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts @@ -111,7 +111,7 @@ describe('helpers', () => { const conversationsData = { page: 1, - per_page: 10, + perPage: 10, total: 2, data: conversationArray, }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx index dd679f09bbed6..0883ca66d6458 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -13,14 +13,13 @@ import type { IHttpFetchError } from '@kbn/core/public'; import { useLoadConnectors } from '../connectorland/use_load_connectors'; -import { DefinedUseQueryResult, UseQueryResult } from '@tanstack/react-query'; +import { UseQueryResult } from '@tanstack/react-query'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import useSessionStorage from 'react-use/lib/useSessionStorage'; import { QuickPrompts } from './quick_prompts/quick_prompts'; import { TestProviders } from '../mock/test_providers/test_providers'; -import { useFetchCurrentUserConversations } from './api'; -import { Conversation } from '../assistant_context/types'; +import { FetchCurrentUserConversations, useFetchCurrentUserConversations } from './api'; import * as all from './chat_send/use_chat_send'; import { useConversation } from './use_conversation'; import { AIConnector } from '../connectorland/connector_selector'; @@ -86,7 +85,26 @@ const mockUseConversation = { }; const refetchResults = jest.fn(); - +const defaultFetchUserConversations = { + data: mockData, + isLoading: false, + refetch: refetchResults.mockResolvedValue({ + isLoading: false, + data: { + pages: [ + { + page: 1, + perPage: 28, + total: 150, + data: Object.values(mockData), + }, + ], + }, + }), + isFetched: true, + isFetching: false, + setPaginationObserver: jest.fn(), +}; describe('Assistant', () => { let persistToLocalStorage: jest.Mock; let persistToSessionStorage: jest.Mock; @@ -111,21 +129,9 @@ describe('Assistant', () => { data: connectors, } as unknown as UseQueryResult); - jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ - data: mockData, - isLoading: false, - refetch: refetchResults.mockResolvedValue({ - isLoading: false, - data: { - ...mockData, - welcome_id: { - ...mockData.welcome_id, - apiConfig: { newProp: true }, - }, - }, - }), - isFetched: true, - } as unknown as DefinedUseQueryResult, unknown>); + jest + .mocked(useFetchCurrentUserConversations) + .mockReturnValue(defaultFetchUserConversations as unknown as FetchCurrentUserConversations); jest .mocked(useLocalStorage) .mockReturnValue([undefined, persistToLocalStorage] as unknown as ReturnType< @@ -199,20 +205,25 @@ describe('Assistant', () => { getConversation, }); jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ + ...defaultFetchUserConversations, data: { ...mockData, electric_sheep_id: conversation, }, - isLoading: false, - refetch: jest.fn().mockResolvedValue({ + refetch: refetchResults.mockResolvedValue({ isLoading: false, data: { - ...mockData, - electric_sheep_id: conversation, + pages: [ + { + page: 1, + perPage: 28, + total: 150, + data: [...Object.values(mockData), conversation], + }, + ], }, }), - isFetched: true, - } as unknown as DefinedUseQueryResult, unknown>); + } as unknown as FetchCurrentUserConversations); const { findByText } = await renderAssistant(); @@ -233,20 +244,28 @@ describe('Assistant', () => { it('should fetch current conversation when id has value', async () => { const refetch = jest.fn(); jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ + ...defaultFetchUserConversations, data: { ...mockData, electric_sheep_id: { ...mockData.electric_sheep_id, title: 'updated title' }, }, - isLoading: false, - refetch: refetch.mockResolvedValue({ + refetch: refetchResults.mockResolvedValue({ isLoading: false, data: { - ...mockData, - electric_sheep_id: { ...mockData.electric_sheep_id, title: 'updated title' }, + pages: [ + { + page: 1, + perPage: 28, + total: 150, + data: [ + mockData.welcome_id, + { ...mockData.electric_sheep_id, title: 'updated title' }, + ], + }, + ], }, }), - isFetched: true, - } as unknown as DefinedUseQueryResult, unknown>); + } as unknown as FetchCurrentUserConversations); await renderAssistant(); const previousConversationButton = await screen.findByText('updated title'); @@ -258,43 +277,6 @@ describe('Assistant', () => { expect(persistToLocalStorage).toHaveBeenLastCalledWith('electric_sheep_id'); }); - it.skip('should refetch all conversations when id is empty', async () => { - const chatSendSpy = jest.spyOn(all, 'useChatSend'); - jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ - data: { - ...mockData, - 'electric sheep': { ...mockData.electric_sheep_id, id: '', apiConfig: { newProp: true } }, - }, - isLoading: false, - refetch: jest.fn().mockResolvedValue({ - isLoading: false, - data: { - ...mockData, - 'electric sheep': { - ...mockData.electric_sheep_id, - id: '', - apiConfig: { newProp: true }, - }, - }, - }), - isFetched: true, - } as unknown as DefinedUseQueryResult, unknown>); - await renderAssistant(); - - const previousConversationButton = screen.getByLabelText('Previous conversation'); - await act(async () => { - fireEvent.click(previousConversationButton); - }); - expect(chatSendSpy).toHaveBeenLastCalledWith( - expect.objectContaining({ - currentConversation: { - ...mockData.electric_sheep_id, - id: '', - apiConfig: { newProp: true }, - }, - }) - ); - }); }); describe('when no connectors are loaded', () => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index f9c4e39c61f38..bff79bed64f30 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -116,8 +116,10 @@ const AssistantComponent: React.FC = ({ isFetchedAnonymizationFields, isFetchedCurrentUserConversations, isFetchedPrompts, + isFetchingCurrentUserConversations, isLoadingAnonymizationFields, isLoadingCurrentUserConversations, + setPaginationObserver, refetchPrompts, refetchCurrentUserConversations, setIsStreaming, @@ -460,7 +462,9 @@ const AssistantComponent: React.FC = ({ conversations={conversations} onConversationDeleted={handleOnConversationDeleted} onConversationCreate={handleCreateConversation} + isFetchingCurrentUserConversations={isFetchingCurrentUserConversations} refetchCurrentUserConversations={refetchCurrentUserConversations} + setPaginationObserver={setPaginationObserver} /> )} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx index 7a142710d19ec..224ec264e95ee 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx @@ -5,12 +5,9 @@ * 2.0. */ -import { DefinedUseQueryResult } from '@tanstack/react-query'; - import { useAssistantOverlay } from '.'; import { waitFor, renderHook, act } from '@testing-library/react'; -import { useFetchCurrentUserConversations } from '../api'; -import { Conversation } from '../../assistant_context/types'; +import { FetchCurrentUserConversations, useFetchCurrentUserConversations } from '../api'; import { mockConnectors } from '../../mock/connectors'; const mockUseAssistantContext = { @@ -76,15 +73,20 @@ describe('useAssistantOverlay', () => { refetch: jest.fn().mockResolvedValue({ isLoading: false, data: { - ...mockData, - welcome_id: { - ...mockData.welcome_id, - apiConfig: { newProp: true }, - }, + pages: [ + { + page: 1, + perPage: 28, + total: 150, + data: Object.values(mockData), + }, + ], }, }), isFetched: true, - } as unknown as DefinedUseQueryResult, unknown>); + isFetching: false, + setPaginationObserver: jest.fn(), + } as unknown as FetchCurrentUserConversations); }); it('calls registerPromptContext with the expected context', async () => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index bf1ba4e21f90b..1c9f1929c714c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -6,8 +6,10 @@ */ import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; -import { QueryObserverResult } from '@tanstack/react-query'; +import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { InfiniteData } from '@tanstack/query-core/src/types'; +import { FetchConversationsResponse } from '../api'; import { AIConnector } from '../../connectorland/connector_selector'; import { getDefaultSystemPrompt } from '../use_conversation/helpers'; import { useConversation } from '../use_conversation'; @@ -20,9 +22,9 @@ export interface Props { conversations: Record; defaultConnector?: AIConnector; mayUpdateConversations: boolean; - refetchCurrentUserConversations: () => Promise< - QueryObserverResult, unknown> - >; + refetchCurrentUserConversations: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise, unknown>>; } interface UseCurrentConversation { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx index 77a0508304861..1be6acb05396d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_data_stream_apis.tsx @@ -5,18 +5,15 @@ * 2.0. */ -import { useCallback, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import type { HttpSetup } from '@kbn/core-http-browser'; import { PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common'; import type { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen'; import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query'; +import { InfiniteData } from '@tanstack/query-core/src/types'; import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetch_anonymization_fields'; import { FetchConversationsResponse, useFetchPrompts } from './api'; -import { - Conversation, - formatPersistedConversations, - useFetchCurrentUserConversations, -} from '../..'; +import { Conversation, useFetchCurrentUserConversations } from '../..'; interface Props { http: HttpSetup; @@ -31,6 +28,7 @@ export interface DataStreamApis { isErrorAnonymizationFields: boolean; isFetchedAnonymizationFields: boolean; isFetchedCurrentUserConversations: boolean; + isFetchingCurrentUserConversations: boolean; isLoadingAnonymizationFields: boolean; isLoadingCurrentUserConversations: boolean; isLoadingPrompts: boolean; @@ -38,27 +36,25 @@ export interface DataStreamApis { refetchPrompts: ( options?: RefetchOptions & RefetchQueryFilters ) => Promise>; - refetchCurrentUserConversations: () => Promise< - QueryObserverResult, unknown> - >; + refetchCurrentUserConversations: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise, unknown>>; setIsStreaming: (isStreaming: boolean) => void; + setPaginationObserver: (ref: HTMLDivElement) => void; } export const useDataStreamApis = ({ http, isAssistantEnabled }: Props): DataStreamApis => { const [isStreaming, setIsStreaming] = useState(false); - const onFetchedConversations = useCallback( - (conversationsData: FetchConversationsResponse): Record => - formatPersistedConversations(conversationsData), - [] - ); const { data: conversations, isLoading: isLoadingCurrentUserConversations, refetch: refetchCurrentUserConversations, + isFetching: isFetchingCurrentUserConversations, isFetched: isFetchedCurrentUserConversations, + setPaginationObserver, } = useFetchCurrentUserConversations({ http, - onFetch: onFetchedConversations, + perPage: 28, refetchOnWindowFocus: !isStreaming, isAssistantEnabled, }); @@ -82,6 +78,7 @@ export const useDataStreamApis = ({ http, isAssistantEnabled }: Props): DataStre } return []; }, [allPrompts, isLoadingPrompts]); + return { allPrompts, allSystemPrompts, @@ -90,12 +87,14 @@ export const useDataStreamApis = ({ http, isAssistantEnabled }: Props): DataStre isErrorAnonymizationFields, isFetchedAnonymizationFields, isFetchedCurrentUserConversations, + isFetchedPrompts, + isFetchingCurrentUserConversations, isLoadingAnonymizationFields, isLoadingCurrentUserConversations, isLoadingPrompts, - isFetchedPrompts, - refetchPrompts, refetchCurrentUserConversations, + refetchPrompts, setIsStreaming, + setPaginationObserver, }; }; From 2c4a5869590829bb65007be3ef30c34609a3a8e1 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 11 Feb 2025 15:09:39 -0700 Subject: [PATCH 08/80] type fixing --- .../index.tsx | 14 +---- .../impl/assistant/helpers.test.ts | 56 +------------------ .../impl/assistant/helpers.ts | 21 +------ .../index.tsx | 16 +----- .../assistant/use_assistant_overlay/index.tsx | 11 +--- .../stack_management/management_settings.tsx | 14 +---- 6 files changed, 8 insertions(+), 124 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index 12ead02156168..c86bd8b698019 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -21,11 +21,7 @@ import { ConversationStreamingSwitch } from '../conversation_settings/conversati import { AIConnector } from '../../../connectorland/connector_selector'; import * as i18n from './translations'; -import { - FetchConversationsResponse, - useFetchCurrentUserConversations, - useFetchPrompts, -} from '../../api'; +import { useFetchCurrentUserConversations, useFetchPrompts } from '../../api'; import { useAssistantContext } from '../../../assistant_context'; import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted'; import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; @@ -37,7 +33,6 @@ import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_conte import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { DEFAULT_PAGE_SIZE } from '../../settings/const'; import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater'; -import { formatPersistedConversations } from '../../helpers'; import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar'; interface Props { connectors: AIConnector[] | undefined; @@ -63,12 +58,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ toasts, } = useAssistantContext(); - const onFetchedConversations = useCallback( - (conversationsData: FetchConversationsResponse): Record => - formatPersistedConversations(conversationsData), - [] - ); - const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); const { @@ -77,7 +66,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ refetch: refetchConversations, } = useFetchCurrentUserConversations({ http, - onFetch: onFetchedConversations, isAssistantEnabled, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts index 22dbd2bcf680c..186a3f82691db 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - getDefaultConnector, - getOptionalRequestParams, - formatPersistedConversations, -} from './helpers'; +import { getDefaultConnector, getOptionalRequestParams } from './helpers'; import { AIConnector } from '../connectorland/connector_selector'; describe('helpers', () => { @@ -83,54 +79,4 @@ describe('helpers', () => { }); }); }); - - describe('formatPersistedConversations', () => { - const messages = [ - { content: 'Message 1', role: 'user' as const, timestamp: '2024-02-14T22:29:43.862Z' }, - { content: 'Message 2', role: 'user' as const, timestamp: '2024-02-14T22:29:43.862Z' }, - ]; - const defaultProps = { - messages, - category: 'assistant', - theme: {}, - apiConfig: { actionTypeId: '.gen-ai', connectorId: '123' }, - replacements: {}, - }; - const conversationArray = [ - { - ...defaultProps, - title: 'Conversation 1', - id: 'conversation_1', - }, - { - ...defaultProps, - title: 'Conversation 2', - id: 'conversation_2', - }, - ]; - - const conversationsData = { - page: 1, - perPage: 10, - total: 2, - data: conversationArray, - }; - - it('should format user conversations', () => { - const result = formatPersistedConversations(conversationsData); - - expect(result).toEqual({ - conversation_1: { - ...defaultProps, - title: 'Conversation 1', - id: 'conversation_1', - }, - conversation_2: { - ...defaultProps, - title: 'Conversation 2', - id: 'conversation_2', - }, - }); - }); - }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts index 96b5b09e464ad..eed016c4c044f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { isEmpty, some } from 'lodash'; import { AIConnector } from '../connectorland/connector_selector'; -import { FetchConnectorExecuteResponse, FetchConversationsResponse } from './api'; -import { Conversation } from '../..'; +import { FetchConnectorExecuteResponse } from './api'; import type { ClientMessage } from '../assistant_context/types'; export const getMessageFromRawResponse = ( @@ -36,23 +34,6 @@ export const getMessageFromRawResponse = ( } }; -export const formatPersistedConversations = ( - conversationsData: FetchConversationsResponse -): Record => { - return (conversationsData?.data ?? []).reduce>( - (transformed, conversation) => { - if (!isEmpty(conversation.id)) { - transformed[conversation.id] = conversation; - } else { - if (!some(Object.values(transformed), ['title', conversation.title])) { - transformed[conversation.title] = conversation; - } - } - return transformed; - }, - {} - ); -}; /** * Returns a default connector if there is only one connector * @param connectors diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 4e83b3321a82d..10e6fc946d536 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -18,15 +18,10 @@ import { import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; -import { - Conversation, - formatPersistedConversations, - useAssistantContext, - useFetchCurrentUserConversations, -} from '../../../../..'; +import { useAssistantContext, useFetchCurrentUserConversations } from '../../../../..'; import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants'; import { AIConnector } from '../../../../connectorland/connector_selector'; -import { FetchConversationsResponse, useFetchPrompts } from '../../../api'; +import { useFetchPrompts } from '../../../api'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { @@ -54,12 +49,6 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector toasts, } = useAssistantContext(); - const onFetchedConversations = useCallback( - (conversationsData: FetchConversationsResponse): Record => - formatPersistedConversations(conversationsData), - [] - ); - const { data: allPrompts, refetch: refetchPrompts, isFetched: promptsLoaded } = useFetchPrompts(); const { @@ -68,7 +57,6 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector refetch: refetchConversations, } = useFetchCurrentUserConversations({ http, - onFetch: onFetchedConversations, isAssistantEnabled, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index 52bc32389b0b0..30c56cc2635b0 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -12,11 +12,10 @@ import { useAssistantContext } from '../../assistant_context'; import { getUniquePromptContextId } from '../../assistant_context/helpers'; import type { PromptContext } from '../prompt_context/types'; import { useConversation } from '../use_conversation'; -import { getDefaultConnector, formatPersistedConversations } from '../helpers'; +import { getDefaultConnector } from '../helpers'; import { getGenAiConfig } from '../../connectorland/helpers'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; -import { FetchConversationsResponse, useFetchCurrentUserConversations } from '../api'; -import { Conversation } from '../../assistant_context/types'; +import { useFetchCurrentUserConversations } from '../api'; interface UseAssistantOverlay { showAssistantOverlay: (show: boolean, silent?: boolean) => void; @@ -93,14 +92,8 @@ export const useAssistantOverlay = ( const { createConversation } = useConversation(); - const onFetchedConversations = useCallback( - (conversationsData: FetchConversationsResponse): Record => - formatPersistedConversations(conversationsData), - [] - ); const { data: conversations, isLoading } = useFetchCurrentUserConversations({ http, - onFetch: onFetchedConversations, isAssistantEnabled, }); // memoize the props so that we can use them in the effect below: diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 6d3bd0ffac2de..2b50231135e51 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -7,15 +7,9 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { AssistantSettingsManagement } from '@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management'; -import type { Conversation } from '@kbn/elastic-assistant'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { i18n } from '@kbn/i18n'; -import { - formatPersistedConversations, - useAssistantContext, - useFetchCurrentUserConversations, -} from '@kbn/elastic-assistant'; -import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api'; +import { useAssistantContext, useFetchCurrentUserConversations } from '@kbn/elastic-assistant'; import { SECURITY_AI_SETTINGS } from '@kbn/elastic-assistant/impl/assistant/settings/translations'; import { CONVERSATIONS_TAB } from '@kbn/elastic-assistant/impl/assistant/settings/const'; import type { SettingsTabs } from '@kbn/elastic-assistant/impl/assistant/settings/types'; @@ -38,14 +32,8 @@ export const ManagementSettings = React.memo(() => { serverless, } = useKibana().services; - const onFetchedConversations = useCallback( - (conversationsData: FetchConversationsResponse): Record => - formatPersistedConversations(conversationsData), - [] - ); const { data: conversations } = useFetchCurrentUserConversations({ http, - onFetch: onFetchedConversations, isAssistantEnabled, }); From e2ddd2de56059b4f1efec20df8be3ca63d053784 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 12 Feb 2025 13:25:00 -0700 Subject: [PATCH 09/80] management wip --- .../use_fetch_current_user_conversations.ts | 40 +++++++---- .../pagination/use_session_pagination.ts | 66 +++++++++++++++---- .../index.tsx | 42 +++++------- .../use_settings_updater.tsx | 4 +- .../shared/kbn-elastic-assistant/index.ts | 2 - .../management_settings.test.tsx | 1 - 6 files changed, 102 insertions(+), 53 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index f3f15b30e8a41..ade7d6c8bd403 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -29,11 +29,13 @@ export interface FetchConversationsResponse { export interface UseFetchCurrentUserConversationsParams { http: HttpSetup; + fields?: string[]; page?: number; perPage?: number; signal?: AbortSignal | undefined; refetchOnWindowFocus?: boolean; isAssistantEnabled: boolean; + setTotalItemCount?: (total: number) => void; } export interface FetchCurrentUserConversations { @@ -72,18 +74,20 @@ const formatFetchedData = (data: InfiniteData | unde */ export const useFetchCurrentUserConversations = ({ http, + fields = query.fields, page = query.page, perPage = query.perPage, signal, refetchOnWindowFocus = true, isAssistantEnabled, + setTotalItemCount, }: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { method: 'GET', version: API_VERSIONS.public.v1, query: { - ...query, + fields, page: pageParam?.page ?? page, per_page: pageParam?.perPage ?? perPage, }, @@ -102,16 +106,30 @@ export const useFetchCurrentUserConversations = ({ }; }; - const { refetch, data, fetchNextPage, isLoading, isFetching, isFetched, hasNextPage } = - useInfiniteQuery( - [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], - queryFn, - { - enabled: isAssistantEnabled, - getNextPageParam, - refetchOnWindowFocus, - } - ); + const { + data, + fetchNextPage, + hasNextPage, + hasPreviousPage, + isFetched, + isFetching, + isLoading, + refetch, + } = useInfiniteQuery( + [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], + queryFn, + { + enabled: isAssistantEnabled, + getNextPageParam, + refetchOnWindowFocus, + } + ); + useEffect(() => { + if (setTotalItemCount && data?.pages?.length) { + setTotalItemCount(data?.pages[0].total ?? 0); + } + }, [data?.pages, setTotalItemCount]); + const formatted = formatFetchedData(data); const observerRef = useRef(); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index 3bf0a5e792089..a03d9b0a64e53 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -16,28 +16,67 @@ export const DEFAULT_TABLE_OPTIONS = { sort: { field: '', direction: 'asc' as const }, }; -export const useSessionPagination = ({ +interface InMemoryPagination { + initialPageSize: number; + pageSizeOptions: number[]; + pageIndex: number; +} + +interface ServerSidePagination { + totalItemCount: number; + pageSize: number; + pageSizeOptions: number[]; + pageIndex: number; +} + +interface UseSessionPaginationReturn { + onTableChange: ({ + page, + sort, + }: { + page: { size: number; index: number }; + sort: { field: string; direction: Direction }; + }) => void; + pagination: T extends true ? InMemoryPagination : ServerSidePagination; + sorting: { + sort: { field: string; direction: Direction }; + }; +} + +export const useSessionPagination = ({ defaultTableOptions, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, + inMemory = true as T, storageKey, + totalItemCount = 0, }: { defaultTableOptions: { page: { size: number; index: number }; sort: { field: string; direction: Direction }; }; + inMemory?: boolean; nameSpace?: string; storageKey: string; -}) => { + totalItemCount?: number; +}): UseSessionPaginationReturn => { const [sessionStorageTableOptions = defaultTableOptions, setSessionStorageTableOptions] = useSessionStorage(`${nameSpace}.${storageKey}`, defaultTableOptions); const pagination = useMemo( - () => ({ - initialPageSize: sessionStorageTableOptions.page.size, - pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], - pageIndex: sessionStorageTableOptions.page.index, - }), - [sessionStorageTableOptions] + () => + inMemory + ? ({ + initialPageSize: sessionStorageTableOptions.page.size, + pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], + pageIndex: sessionStorageTableOptions.page.index, + } as InMemoryPagination) + : ({ + totalItemCount, + pageSize: sessionStorageTableOptions.page.size ?? DEFAULT_PAGE_SIZE, + pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], + pageIndex: sessionStorageTableOptions.page.index, + } as ServerSidePagination), + [inMemory, sessionStorageTableOptions, totalItemCount] ); const sorting = useMemo( @@ -48,11 +87,12 @@ export const useSessionPagination = ({ ); const onTableChange = useCallback( - ({ - page, - sort, - }: // eslint-disable-next-line @typescript-eslint/no-explicit-any - any) => { + ( + args: // eslint-disable-next-line @typescript-eslint/no-explicit-any + any + ) => { + const { page, sort } = args; + console.log('onTableChange', args); setSessionStorageTableOptions({ page, sort, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index c86bd8b698019..7c5617ad271ab 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -9,11 +9,11 @@ import { EuiPanel, EuiSpacer, EuiConfirmModal, - EuiInMemoryTable, + EuiBasicTable, EuiTitle, EuiText, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Conversation } from '../../../assistant_context/types'; import { ConversationTableItem, useConversationsTable } from './use_conversations_table'; @@ -59,7 +59,14 @@ const ConversationSettingsManagementComponent: React.FC = ({ } = useAssistantContext(); const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); - + const [totalItemCount, setTotalItemCount] = useState(10); + const { onTableChange, pagination, sorting } = useSessionPagination({ + nameSpace, + storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, + defaultTableOptions: DEFAULT_TABLE_OPTIONS, + inMemory: false, + totalItemCount, + }); const { data: conversations, isFetched: conversationsLoaded, @@ -67,6 +74,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ } = useFetchCurrentUserConversations({ http, isAssistantEnabled, + // pagination index starts at 0, page starts as one + page: pagination.pageIndex + 1, + perPage: pagination.pageSize, + setTotalItemCount, }); const refetchAll = useCallback(() => { @@ -74,10 +85,11 @@ const ConversationSettingsManagementComponent: React.FC = ({ refetchConversations(); }, [refetchPrompts, refetchConversations]); + const conversationSettings = conversations; + const { systemPromptSettings: allSystemPrompts, assistantStreamingEnabled, - conversationSettings, conversationsSettingsBulkActions, resetSettings, saveSettings, @@ -131,18 +143,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ setSelectedConversation(conversation); }, []); - useEffect(() => { - if (selectedConversation != null) { - const newConversation = - conversationSettings[selectedConversation.id] || - conversationSettings[selectedConversation.title]; - setSelectedConversation( - // conversationSettings has title as key, sometime has id as key - newConversation - ); - } - }, [conversationSettings, selectedConversation]); - const { isFlyoutOpen: editFlyoutVisible, openFlyout: openEditFlyout, @@ -215,12 +215,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ const { getConversationsList, getColumns } = useConversationsTable(); - const { onTableChange, pagination, sorting } = useSessionPagination({ - nameSpace, - storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, - defaultTableOptions: DEFAULT_TABLE_OPTIONS, - }); - const conversationOptions = getConversationsList({ allSystemPrompts, actionTypeRegistry, @@ -281,12 +275,12 @@ const ConversationSettingsManagementComponent: React.FC = ({ {i18n.CONVERSATIONS_LIST_DESCRIPTION} - {editFlyoutVisible && ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx index 9786663a28b94..b86353112725a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx @@ -234,10 +234,10 @@ export const useSettingsUpdater = ( useEffect(() => { // Update conversation settings when conversations are loaded - if (conversationsLoaded) { + if (conversationsLoaded && Object.keys(conversationSettings).length === 0) { setConversationSettings(conversations); } - }, [conversations, conversationsLoaded]); + }, [conversationSettings, conversations, conversationsLoaded]); useEffect(() => { // Update quick prompts settings when prompts are loaded diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index 01ac84a1c5359..f30dd5a8b0ba7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -144,8 +144,6 @@ export { useFetchCurrentUserConversations } from './impl/assistant/api/conversat export * from './impl/assistant/api/conversations/bulk_update_actions_conversations'; export { getConversationById } from './impl/assistant/api/conversations/conversations'; -export { formatPersistedConversations } from './impl/assistant/helpers'; - export { UpgradeButtons } from './impl/upgrade/upgrade_buttons'; export { getUserConversationsExist, getPrompts, bulkUpdatePrompts } from './impl/assistant/api'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx index 62dd79c94d912..9a36bbac86c8a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx @@ -19,7 +19,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; jest.mock('@kbn/elastic-assistant', () => ({ useAssistantContext: jest.fn(), useFetchCurrentUserConversations: jest.fn(), - formatPersistedConversations: jest.fn(), Welcome: 'Welcome Conversation', })); jest.mock('@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management', () => ({ From bbc762c17ee438a5385f594307f6eb08f6aa801a Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 12 Feb 2025 18:39:28 -0700 Subject: [PATCH 10/80] progress --- .../impl/assistant/assistant_header/index.tsx | 12 +- .../impl/assistant/assistant_title/index.tsx | 9 +- .../pagination/use_session_pagination.ts | 1 - .../conversation_settings.tsx | 23 +- .../conversation_settings_editor.tsx | 226 ++++++------------ .../index.tsx | 35 ++- .../conversations/update_conversation.ts | 70 +++--- .../stack_management/management_settings.tsx | 30 +-- 8 files changed, 163 insertions(+), 243 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index 95085ac8ef8d6..e9047c2e324c8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -16,7 +16,7 @@ import { useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; -import { isEmpty } from 'lodash'; +import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations'; import { DataStreamApis } from '../use_data_stream_apis'; import { Conversation } from '../../..'; import { AssistantTitle } from '../assistant_title'; @@ -109,11 +109,7 @@ export const AssistantHeader: React.FC = ({ defaultConnector={defaultConnector} isDisabled={isDisabled} isSettingsModalVisible={isSettingsModalVisible} - selectedConversationId={ - !isEmpty(selectedConversation?.id) - ? selectedConversation?.id - : selectedConversation?.title - } + selectedConversationId={selectedConversation?.id} setIsSettingsModalVisible={setIsSettingsModalVisible} onConversationSelected={onConversationSelected} conversations={conversations} @@ -155,8 +151,8 @@ export const AssistantHeader: React.FC = ({ ) : ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx index c23f428cc0748..54fcd154c03be 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx @@ -23,7 +23,12 @@ export const AssistantTitle: React.FC<{ title?: string; selectedConversation: Conversation | undefined; refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations']; -}> = ({ title, selectedConversation, refetchCurrentUserConversations, isDisabled = false }) => { +}> = ({ + title = NEW_CHAT, + selectedConversation, + refetchCurrentUserConversations, + isDisabled = false, +}) => { const [newTitle, setNewTitle] = useState(title); const [newTitleError, setNewTitleError] = useState(false); const { updateConversationTitle } = useConversation(); @@ -62,7 +67,7 @@ export const AssistantTitle: React.FC<{ data-test-subj="conversationTitle" heading="h2" inputAriaLabel="Edit text inline" - value={newTitle || NEW_CHAT} + value={newTitle} size="xs" isInvalid={!!newTitleError} isReadOnly={isDisabled} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index a03d9b0a64e53..e384d098242df 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -92,7 +92,6 @@ export const useSessionPagination = ({ any ) => { const { page, sort } = args; - console.log('onTableChange', args); setSessionStorageTableOptions({ page, sort, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx index 852cd20882904..68810996909bb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx @@ -117,16 +117,19 @@ export const ConversationSettings: React.FC = React.m onConversationSelectionChange={onConversationSelectionChange} /> - + {/* TODO: double check this condition */} + {selectedConversationWithApiConfig && ( + + )} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx index ba18594836792..cd0e4390d2511 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -6,7 +6,7 @@ */ import { EuiFormRow, EuiLink } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { HttpSetup } from '@kbn/core-http-browser'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -31,7 +31,7 @@ export interface ConversationSettingsEditorProps { conversationsSettingsBulkActions: ConversationsBulkActions; http: HttpSetup; isDisabled?: boolean; - selectedConversation?: Conversation; + selectedConversation: Conversation; setConversationSettings: React.Dispatch>>; setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction @@ -55,209 +55,141 @@ export const ConversationSettingsEditor: React.FC(selectedConversation); const selectedSystemPrompt = useMemo(() => { - return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); - }, [allSystemPrompts, selectedConversation]); + return getDefaultSystemPrompt({ allSystemPrompts, conversation: conversationUpdates }); + }, [allSystemPrompts, conversationUpdates]); const handleOnSystemPromptSelectionChange = useCallback( (systemPromptId?: string | undefined) => { - if (selectedConversation != null && selectedConversation.apiConfig) { + if (conversationUpdates != null && conversationUpdates.apiConfig) { const updatedConversation = { - ...selectedConversation, + ...conversationUpdates, apiConfig: { - ...selectedConversation.apiConfig, + ...conversationUpdates.apiConfig, defaultSystemPromptId: systemPromptId, }, }; - setConversationSettings({ - ...conversationSettings, - [updatedConversation.id || updatedConversation.title]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update + setConversationUpdates(updatedConversation); + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - defaultSystemPromptId: systemPromptId, - }, + : {} + ).apiConfig ?? {}), + defaultSystemPromptId: systemPromptId, }, }, - }); - } else { - const createdConversation = { - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.title]: updatedConversation, - }, - }; - setConversationsSettingsBulkActions(createdConversation); - } + }, + }); } }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - ] + [conversationsSettingsBulkActions, conversationUpdates, setConversationsSettingsBulkActions] ); const selectedConnector = useMemo(() => { - const selectedConnectorId: string | undefined = selectedConversation?.apiConfig?.connectorId; + const selectedConnectorId: string | undefined = conversationUpdates?.apiConfig?.connectorId; if (areConnectorsFetched) { return connectors?.find((c) => c.id === selectedConnectorId); } return undefined; - }, [areConnectorsFetched, connectors, selectedConversation?.apiConfig?.connectorId]); + }, [areConnectorsFetched, connectors, conversationUpdates?.apiConfig?.connectorId]); const selectedProvider = useMemo( - () => selectedConversation?.apiConfig?.provider, - [selectedConversation?.apiConfig?.provider] + () => conversationUpdates?.apiConfig?.provider, + [conversationUpdates?.apiConfig?.provider] ); - const selectedConversationId = useMemo( - () => - selectedConversation?.id === '' - ? selectedConversation.title - : (selectedConversation?.id as string), - [selectedConversation] - ); const handleOnConnectorSelectionChange = useCallback( (connector: AIConnector) => { - if (selectedConversation != null) { + if (conversationUpdates != null) { const config = getGenAiConfig(connector); const updatedConversation = { - ...selectedConversation, + ...conversationUpdates, apiConfig: { - ...selectedConversation.apiConfig, + ...conversationUpdates.apiConfig, connectorId: connector.id, actionTypeId: connector.actionTypeId, provider: config?.apiProvider, model: config?.defaultModel, }, }; - setConversationSettings({ - ...conversationSettings, - [selectedConversationId]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id || updatedConversation.title]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update + setConversationUpdates(updatedConversation); + + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - connectorId: connector?.id, - actionTypeId: connector?.actionTypeId, - provider: config?.apiProvider, - model: config?.defaultModel, - }, + : {} + ).apiConfig ?? {}), + connectorId: connector?.id, + actionTypeId: connector?.actionTypeId, + provider: config?.apiProvider, + model: config?.defaultModel, }, }, - }); - } else { - const createdConversation = { - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.title || updatedConversation.id]: updatedConversation, - }, - }; - setConversationsSettingsBulkActions(createdConversation); - } + }, + }); } }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - selectedConversationId, - setConversationSettings, - setConversationsSettingsBulkActions, - ] + [conversationsSettingsBulkActions, conversationUpdates, setConversationsSettingsBulkActions] ); const selectedModel = useMemo(() => { const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel; // Prefer conversation configuration over connector default - return selectedConversation?.apiConfig?.model ?? connectorModel; - }, [selectedConnector, selectedConversation?.apiConfig?.model]); + return conversationUpdates?.apiConfig?.model ?? connectorModel; + }, [selectedConnector, conversationUpdates?.apiConfig?.model]); const handleOnModelSelectionChange = useCallback( (model?: string) => { - if (selectedConversation != null && selectedConversation.apiConfig) { + if (conversationUpdates != null && conversationUpdates.apiConfig) { const updatedConversation = { - ...selectedConversation, + ...conversationUpdates, apiConfig: { - ...selectedConversation.apiConfig, + ...conversationUpdates.apiConfig, model, }, }; - setConversationSettings({ - ...conversationSettings, - [updatedConversation.id || updatedConversation.title]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update + setConversationUpdates(updatedConversation); + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - model, - }, + : {} + ).apiConfig ?? {}), + model, }, }, - }); - } else { - const createdConversation = { - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id || updatedConversation.title]: updatedConversation, - }, - }; - setConversationsSettingsBulkActions(createdConversation); - } + }, + }); } }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - ] + [conversationsSettingsBulkActions, conversationUpdates, setConversationsSettingsBulkActions] ); return ( <> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index 7c5617ad271ab..b77996ca32687 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -28,7 +28,6 @@ import { useFlyoutModalVisibility } from '../../common/components/assistant_sett import { Flyout } from '../../common/components/assistant_settings_management/flyout'; import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; -import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { DEFAULT_PAGE_SIZE } from '../../settings/const'; @@ -59,7 +58,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ } = useAssistantContext(); const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); - const [totalItemCount, setTotalItemCount] = useState(10); + const [totalItemCount, setTotalItemCount] = useState(5); const { onTableChange, pagination, sorting } = useSessionPagination({ nameSpace, storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, @@ -84,12 +83,11 @@ const ConversationSettingsManagementComponent: React.FC = ({ refetchPrompts(); refetchConversations(); }, [refetchPrompts, refetchConversations]); - const conversationSettings = conversations; - const { systemPromptSettings: allSystemPrompts, assistantStreamingEnabled, + // conversationSettings, conversationsSettingsBulkActions, resetSettings, saveSettings, @@ -138,11 +136,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ // Local state for saving previously selected items so tab switching is friendlier // Conversation Selection State const [selectedConversation, setSelectedConversation] = useState(); - - const onSelectedConversationChange = useCallback((conversation?: Conversation) => { - setSelectedConversation(conversation); - }, []); - const { isFlyoutOpen: editFlyoutVisible, openFlyout: openEditFlyout, @@ -155,23 +148,23 @@ const ConversationSettingsManagementComponent: React.FC = ({ openFlyout: openConfirmModal, closeFlyout: closeConfirmModal, } = useFlyoutModalVisibility(); - - const onConversationSelectionChange = useConversationChanged({ - allSystemPrompts, - conversationSettings, - conversationsSettingsBulkActions, - defaultConnector, - setConversationSettings, - setConversationsSettingsBulkActions, - onSelectedConversationChange, - }); + // + // const onConversationSelectionChange = useConversationChanged({ + // allSystemPrompts, + // conversationSettings, + // conversationsSettingsBulkActions, + // defaultConnector, + // setConversationSettings, + // setConversationsSettingsBulkActions, + // onSelectedConversationChange, + // }); const onEditActionClicked = useCallback( (rowItem: ConversationTableItem) => { openEditFlyout(); - onConversationSelectionChange(rowItem); + setSelectedConversation(rowItem); }, - [onConversationSelectionChange, openEditFlyout] + [openEditFlyout] ); const onConversationDeleted = useConversationDeleted({ diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.ts index d8abaeaeb2595..391f72977c2de 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/update_conversation.ts @@ -119,7 +119,7 @@ export const transformToUpdateScheme = ( return { id, updated_at: updatedAt, - title, + ...(title ? { title } : {}), ...(apiConfig ? { api_config: { @@ -131,36 +131,42 @@ export const transformToUpdateScheme = ( }, } : {}), - exclude_from_last_conversation_storage: excludeFromLastConversationStorage, - replacements: replacements - ? Object.keys(replacements).map((key) => ({ - uuid: key, - value: replacements[key], - })) - : undefined, - messages: messages?.map((message) => ({ - '@timestamp': message.timestamp, - content: message.content, - is_error: message.isError, - reader: message.reader, - role: message.role, - ...(message.metadata - ? { - metadata: { - ...(message.metadata.contentReferences - ? { content_references: message.metadata.contentReferences } - : {}), - }, - } - : {}), - ...(message.traceData - ? { - trace_data: { - trace_id: message.traceData.traceId, - transaction_id: message.traceData.transactionId, - }, - } - : {}), - })), + exclude_from_last_conversation_storage: excludeFromLastConversationStorage ?? false, + ...(replacements + ? { + replacements: Object.keys(replacements).map((key) => ({ + uuid: key, + value: replacements[key], + })), + } + : {}), + ...(messages + ? { + messages: messages.map((message) => ({ + '@timestamp': message.timestamp, + content: message.content, + is_error: message.isError, + reader: message.reader, + role: message.role, + ...(message.metadata + ? { + metadata: { + ...(message.metadata.contentReferences + ? { content_references: message.metadata.contentReferences } + : {}), + }, + } + : {}), + ...(message.traceData + ? { + trace_data: { + trace_id: message.traceData.traceId, + transaction_id: message.traceData.transactionId, + }, + } + : {}), + })), + } + : {}), }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 2b50231135e51..0f84f003149f7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -9,17 +9,12 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { AssistantSettingsManagement } from '@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { i18n } from '@kbn/i18n'; -import { useAssistantContext, useFetchCurrentUserConversations } from '@kbn/elastic-assistant'; import { SECURITY_AI_SETTINGS } from '@kbn/elastic-assistant/impl/assistant/settings/translations'; import { CONVERSATIONS_TAB } from '@kbn/elastic-assistant/impl/assistant/settings/const'; import type { SettingsTabs } from '@kbn/elastic-assistant/impl/assistant/settings/types'; import { useKibana } from '../../common/lib/kibana'; -export const ManagementSettings = React.memo(() => { - const { - http, - assistantAvailability: { isAssistantEnabled }, - } = useAssistantContext(); +export const ManagementSettings = React.memo(() => { const { application: { navigateToApp, @@ -32,11 +27,6 @@ export const ManagementSettings = React.memo(() => { serverless, } = useKibana().services; - const { data: conversations } = useFetchCurrentUserConversations({ - http, - isAssistantEnabled, - }); - docTitle.change(SECURITY_AI_SETTINGS); const [searchParams] = useSearchParams(); @@ -102,17 +92,13 @@ export const ManagementSettings = React.memo(() => { navigateToApp('home'); } - if (conversations) { - return ( - - ); - } - - return <>; + return ( + + ); }); ManagementSettings.displayName = 'ManagementSettings'; From 584ef5ae5fc8316389f0388a0d91a7dd49291484 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 13 Feb 2025 11:39:26 -0700 Subject: [PATCH 11/80] fixes --- .../impl/assistant/assistant_body/index.tsx | 2 +- .../assistant_body/welcome_setup.tsx | 2 +- .../assistant/assistant_header/index.test.tsx | 1 - .../impl/assistant/assistant_header/index.tsx | 3 +- .../conversation_settings_editor.tsx | 10 +-- .../index.tsx | 44 ++++++------- .../conversation_sidepanel/index.tsx | 6 +- .../assistant/settings/assistant_settings.tsx | 3 +- .../settings/assistant_settings_modal.tsx | 2 +- .../use_current_conversation/index.test.tsx | 2 - .../use_current_conversation/index.tsx | 63 ++++++------------- .../connectorland/connector_setup/index.tsx | 3 +- 12 files changed, 55 insertions(+), 86 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx index 03a01cf0b5d3f..f93b62bbe67eb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx @@ -35,7 +35,7 @@ interface Props { comments: JSX.Element; currentConversation: Conversation | undefined; currentSystemPromptId: string | undefined; - handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; + handleOnConversationSelected: ({ cId }: { cId: string }) => Promise; isAssistantEnabled: boolean; isSettingsModalVisible: boolean; isWelcomeSetup: boolean; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/welcome_setup.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/welcome_setup.tsx index 9446454ad94cd..3ba0dc1783afe 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/welcome_setup.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/welcome_setup.tsx @@ -15,7 +15,7 @@ import * as i18n from '../translations'; interface Props { currentConversation: Conversation | undefined; - handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; + handleOnConversationSelected: ({ cId }: { cId: string }) => Promise; } export const WelcomeSetup: React.FC = ({ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx index fe4b0de4d2149..6c077eb29e0da 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx @@ -87,7 +87,6 @@ describe('AssistantHeader', () => { }); expect(onConversationSelected).toHaveBeenCalledWith({ cId: alertConvo.id, - cTitle: alertConvo.title, }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index e9047c2e324c8..ace2e97203494 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -38,7 +38,7 @@ interface OwnProps { onCloseFlyout?: () => void; chatHistoryVisible?: boolean; setChatHistoryVisible?: React.Dispatch>; - onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; + onConversationSelected: ({ cId }: { cId: string }) => void; conversations: Record; conversationsLoaded: boolean; refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations']; @@ -88,7 +88,6 @@ export const AssistantHeader: React.FC = ({ (updatedConversation: Conversation) => { onConversationSelected({ cId: updatedConversation.id, - cTitle: updatedConversation.title, }); }, [onConversationSelected] diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx index cd0e4390d2511..6aaaf7ea76594 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -44,12 +44,10 @@ export interface ConversationSettingsEditorProps { export const ConversationSettingsEditor: React.FC = React.memo( ({ allSystemPrompts, - conversationSettings, conversationsSettingsBulkActions, http, isDisabled = false, selectedConversation, - setConversationSettings, setConversationsSettingsBulkActions, }) => { const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ @@ -63,11 +61,15 @@ export const ConversationSettingsEditor: React.FC { if (conversationUpdates != null && conversationUpdates.apiConfig) { + const newSystemPromptId = + conversationUpdates.apiConfig.defaultSystemPromptId === systemPromptId + ? undefined + : systemPromptId; const updatedConversation = { ...conversationUpdates, apiConfig: { ...conversationUpdates.apiConfig, - defaultSystemPromptId: systemPromptId, + defaultSystemPromptId: newSystemPromptId, }, }; setConversationUpdates(updatedConversation); @@ -85,7 +87,7 @@ export const ConversationSettingsEditor: React.FC = ({ openFlyout: openConfirmModal, closeFlyout: closeConfirmModal, } = useFlyoutModalVisibility(); - // - // const onConversationSelectionChange = useConversationChanged({ - // allSystemPrompts, - // conversationSettings, - // conversationsSettingsBulkActions, - // defaultConnector, - // setConversationSettings, - // setConversationsSettingsBulkActions, - // onSelectedConversationChange, - // }); const onEditActionClicked = useCallback( (rowItem: ConversationTableItem) => { - openEditFlyout(); setSelectedConversation(rowItem); + openEditFlyout(); }, [openEditFlyout] ); @@ -287,16 +279,26 @@ const ConversationSettingsManagementComponent: React.FC = ({ selectedConversation?.title == null || selectedConversation?.title === '' } > - + {selectedConversation ? ( + + ) : ( + + )} )} {deleteConfirmModalVisibility && deletedConversation?.title && ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx index 3d07fe7d603ca..d5231b0cf6c3e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_sidepanel/index.tsx @@ -32,7 +32,7 @@ const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; interface Props { currentConversation?: Conversation; - onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; + onConversationSelected: ({ cId }: { cId: string }) => void; shouldDisableKeyboardShortcut?: () => boolean; isDisabled?: boolean; isFetchingCurrentUserConversations: boolean; @@ -114,7 +114,6 @@ export const ConversationSidePanel = React.memo( const previousConversation = getNextConversation(conversationList, conversation); onConversationSelected({ cId: previousConversation.id, - cTitle: previousConversation.title, }); } onConversationDeleted(conversation.id); @@ -127,14 +126,12 @@ export const ConversationSidePanel = React.memo( onConversationSelected({ cId: previousConversation.id, - cTitle: previousConversation.title, }); }, [conversationList, currentConversation, onConversationSelected]); const onArrowDownClick = useCallback(() => { const nextConversation = getNextConversation(conversationList, currentConversation); onConversationSelected({ cId: nextConversation.id, - cTitle: nextConversation.title, }); }, [conversationList, currentConversation, onConversationSelected]); @@ -226,7 +223,6 @@ export const ConversationSidePanel = React.memo( onClick={() => onConversationSelected({ cId: conversation.id, - cTitle: conversation.title, }) } label={conversation.title} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index 04c5818118728..05f854002608b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -56,7 +56,7 @@ interface Props { ) => void; onSave: (success: boolean) => Promise; selectedConversationId?: string; - onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; + onConversationSelected: ({ cId }: { cId: string }) => void; conversations: Record; conversationsLoaded: boolean; } @@ -170,7 +170,6 @@ export const AssistantSettings: React.FC = React.memo( if (isSelectedConversationDeleted && newSelectedConversation != null) { onConversationSelected({ cId: newSelectedConversation.id, - cTitle: newSelectedConversation.title, }); } const saveResult = await saveSettings(); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_modal.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_modal.tsx index 5f2d677adc9ee..53ba2e0d53f54 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_modal.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_modal.tsx @@ -19,7 +19,7 @@ interface Props { isSettingsModalVisible: boolean; selectedConversationId?: string; setIsSettingsModalVisible: React.Dispatch>; - onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; + onConversationSelected: ({ cId }: { cId: string }) => void; isDisabled?: boolean; conversations: Record; conversationsLoaded: boolean; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx index 33839de380a85..663100ddfc8db 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.test.tsx @@ -147,7 +147,6 @@ describe('useCurrentConversation', () => { await act(async () => { await result.current.handleOnConversationSelected({ cId: conversationId, - cTitle: conversationTitle, }); }); @@ -180,7 +179,6 @@ describe('useCurrentConversation', () => { await act(async () => { await result.current.handleOnConversationSelected({ cId: 'bad', - cTitle: 'bad', }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index 1c9f1929c714c..9c22eeaee398e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -32,10 +32,9 @@ interface UseCurrentConversation { currentSystemPrompt: PromptResponse | undefined; handleCreateConversation: () => Promise; handleOnConversationDeleted: (cTitle: string) => Promise; - handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; + handleOnConversationSelected: ({ cId }: { cId: string }) => Promise; refetchCurrentConversation: (options?: { cId?: string; - cTitle?: string; isStreamRefetch?: boolean; }) => Promise; setCurrentConversation: Dispatch>; @@ -57,34 +56,6 @@ export const useCurrentConversation = ({ }: Props): UseCurrentConversation => { const { deleteConversation, getConversation, setApiConfig } = useConversation(); const [currentConversation, setCurrentConversation] = useState(); - useEffect(() => { - if (!mayUpdateConversations || !!currentConversation) return; - const emptyConversation = { - apiConfig: defaultConnector - ? { - connectorId: defaultConnector.id ?? '', - actionTypeId: defaultConnector.actionTypeId ?? '', - } - : undefined, - id: '', - messages: [], - replacements: {}, - category: 'assistant', - title: '', - }; - if (conversationId === '') { - return setCurrentConversation(emptyConversation); - } - if (conversations[conversationId]) { - setCurrentConversation(conversations[conversationId] ?? emptyConversation); - } - }, [ - conversationId, - conversations, - currentConversation, - defaultConnector, - mayUpdateConversations, - ]); /** * START SYSTEM PROMPT */ @@ -125,16 +96,11 @@ export const useCurrentConversation = ({ * @param isStreamRefetch - Are we refetching because stream completed? If so retry several times to ensure the message has updated on the server */ const refetchCurrentConversation = useCallback( - async ({ - cId, - cTitle, - isStreamRefetch = false, - }: { cId?: string; cTitle?: string; isStreamRefetch?: boolean } = {}) => { + async ({ cId, isStreamRefetch = false }: { cId?: string; isStreamRefetch?: boolean } = {}) => { if (cId === '') { return; } - const cConversationId = - cId ?? (cTitle && conversations[cTitle].id) ?? currentConversation?.id; + const cConversationId = cId ?? currentConversation?.id; if (cConversationId) { let updatedConversation = await getConversation(cConversationId); @@ -161,15 +127,21 @@ export const useCurrentConversation = ({ return updatedConversation; } }, - [conversations, currentConversation?.id, getConversation] + [currentConversation?.id, getConversation] ); const handleOnConversationSelected = useCallback( - async ({ cId, cTitle }: { cId: string; cTitle: string }) => { + async ({ cId }: { cId: string }) => { if (cId === '') { return setCurrentConversation({ - // get apiConfig from currentConversation - ...currentConversation, + apiConfig: defaultConnector + ? { + connectorId: defaultConnector.id ?? '', + actionTypeId: defaultConnector.actionTypeId ?? '', + } + : undefined, + // get apiConfig from currentConversation if it exists + ...(currentConversation ?? {}), id: '', messages: [], replacements: {}, @@ -178,10 +150,14 @@ export const useCurrentConversation = ({ }); } // refetch will set the currentConversation - await refetchCurrentConversation({ cId, cTitle }); + await refetchCurrentConversation({ cId }); }, - [currentConversation, refetchCurrentConversation] + [currentConversation, defaultConnector, refetchCurrentConversation] ); + useEffect(() => { + if (!mayUpdateConversations || !!currentConversation) return; + handleOnConversationSelected({ cId: conversationId ?? '' }); + }, [conversationId, handleOnConversationSelected, currentConversation, mayUpdateConversations]); const handleOnConversationDeleted = useCallback( async (cTitle: string) => { @@ -195,7 +171,6 @@ export const useCurrentConversation = ({ async () => handleOnConversationSelected({ cId: '', - cTitle: '', }), [handleOnConversationSelected] ); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx index bde72752cc903..c7cf9a544899f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx @@ -20,7 +20,7 @@ import { getGenAiConfig } from '../helpers'; export interface ConnectorSetupProps { conversation?: Conversation; - onConversationUpdate?: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; + onConversationUpdate?: ({ cId }: { cId: string }) => Promise; updateConversationsOnSaveConnector?: boolean; } @@ -63,7 +63,6 @@ export const ConnectorSetup = ({ if (updatedConversation) { onConversationUpdate?.({ cId: updatedConversation.id, - cTitle: updatedConversation.title, }); refetchConnectors?.(); From 9af0a98eff5805e0dc734c287f82f3aced6faff5 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 13 Feb 2025 17:23:23 -0700 Subject: [PATCH 12/80] slowly, slowly --- .../use_fetch_current_user_conversations.ts | 31 ++-- ...ch_current_user_conversations_by_filter.ts | 133 ++++++++++++++++++ .../badges/index.tsx | 7 +- .../pagination/use_session_pagination.ts | 17 +-- .../index.tsx | 9 +- .../conversation_multi_selector.tsx | 15 +- .../system_prompt_editor.tsx | 16 ++- .../use_system_prompt_editor.tsx | 46 +++--- .../index.tsx | 39 +++-- .../use_system_prompt_table.tsx | 71 ++-------- .../use_settings_updater.tsx | 2 + .../use_system_prompt_updater.ts | 75 ++++++++++ .../scripts/create_conversations_script.ts | 18 ++- .../server/ai_assistant_data_clients/find.ts | 1 + 14 files changed, 327 insertions(+), 153 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index ade7d6c8bd403..6174f3bbc60f4 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -30,6 +30,7 @@ export interface FetchConversationsResponse { export interface UseFetchCurrentUserConversationsParams { http: HttpSetup; fields?: string[]; + filter?: string; page?: number; perPage?: number; signal?: AbortSignal | undefined; @@ -75,6 +76,7 @@ const formatFetchedData = (data: InfiniteData | unde export const useFetchCurrentUserConversations = ({ http, fields = query.fields, + filter, page = query.page, perPage = query.perPage, signal, @@ -88,6 +90,7 @@ export const useFetchCurrentUserConversations = ({ version: API_VERSIONS.public.v1, query: { fields, + filter, page: pageParam?.page ?? page, per_page: pageParam?.perPage ?? perPage, }, @@ -106,24 +109,16 @@ export const useFetchCurrentUserConversations = ({ }; }; - const { - data, - fetchNextPage, - hasNextPage, - hasPreviousPage, - isFetched, - isFetching, - isLoading, - refetch, - } = useInfiniteQuery( - [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], - queryFn, - { - enabled: isAssistantEnabled, - getNextPageParam, - refetchOnWindowFocus, - } - ); + const { data, fetchNextPage, hasNextPage, isFetched, isFetching, isLoading, refetch } = + useInfiniteQuery( + [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1, filter], + queryFn, + { + enabled: isAssistantEnabled, + getNextPageParam, + refetchOnWindowFocus, + } + ); useEffect(() => { if (setTotalItemCount && data?.pages?.length) { setTotalItemCount(data?.pages[0].total ?? 0); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts new file mode 100644 index 0000000000000..b0c2417ac8168 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { + QueryObserverResult, + RefetchOptions, + RefetchQueryFilters, + useQuery, +} from '@tanstack/react-query'; +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, +} from '@kbn/elastic-assistant-common'; +import { InfiniteData } from '@tanstack/query-core/src/types'; +import { useEffect } from 'react'; +import { Conversation } from '../../../assistant_context/types'; + +export interface FetchConversationsResponse { + page: number; + perPage: number; + total: number; + data: Conversation[]; +} + +export interface UseFetchCurrentUserConversationsParams { + http: HttpSetup; + fields?: string[]; + page?: number; + perPage?: number; + signal?: AbortSignal | undefined; + refetchOnWindowFocus?: boolean; + isAssistantEnabled: boolean; + setTotalItemCount?: (total: number) => void; +} + +export interface FetchCurrentUserConversations { + data: Record; + isLoading: boolean; + refetch: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise, unknown>>; + isFetched: boolean; + isFetching: boolean; + setPaginationObserver: (ref: HTMLDivElement) => void; +} + +const query = { + page: 1, + perPage: 28, + fields: ['id', 'title', 'apiConfig', 'updatedAt'], +}; + +const formatFetchedData = (data: InfiniteData | undefined) => + data?.pages.reduce((acc, curr) => { + return { + ...acc, + ...curr.data.reduce( + (conversationsArr, conversation) => ({ + ...conversationsArr, + [conversation.id]: conversation, + }), + {} + ), + }; + }, {}); + +/** + * API call for fetching assistant conversations for the current user + */ +export const useFetchCurrentUserConversationsByFilter = ({ + http, + fields = query.fields, + page = query.page, + perPage = query.perPage, + signal, + refetchOnWindowFocus = true, + isAssistantEnabled, + setTotalItemCount, +}: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { + const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { + return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { + method: 'GET', + version: API_VERSIONS.public.v1, + query: { + fields, + page: pageParam?.page ?? page, + per_page: pageParam?.perPage ?? perPage, + }, + signal, + }); + }; + + const getNextPageParam = (lastPage: FetchConversationsResponse) => { + const totalPages = Math.max(1, Math.ceil(lastPage.total / lastPage.perPage)); + if (totalPages === lastPage.page) { + return; + } + return { + ...lastPage, + page: lastPage.page + 1, + }; + }; + + const { data, isFetched, isFetching, isLoading, refetch } = useQuery( + [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], + queryFn, + { + enabled: isAssistantEnabled, + getNextPageParam, + refetchOnWindowFocus, + } + ); + useEffect(() => { + if (setTotalItemCount && data?.pages?.length) { + setTotalItemCount(data?.pages[0].total ?? 0); + } + }, [data?.pages, setTotalItemCount]); + + const formatted = formatFetchedData(data); + + return { + data: formatted ?? {}, + isLoading, + refetch, + isFetched, + isFetching, + }; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx index 0a44664bd5d34..69fe1c775d210 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx @@ -7,17 +7,18 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; +import { Conversation } from '../../../../../..'; export const BadgesColumn: React.FC<{ - items: string[] | null | undefined; + items: Conversation[] | null | undefined; prefix: string; color?: string; }> = React.memo(({ items, prefix, color = 'hollow' }) => items && items.length > 0 ? (
- {items.map((c, idx) => ( + {items.map(({ title }, idx) => ( - {c} + {title} ))}
diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index e384d098242df..6f14d9dc81050 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -64,18 +64,18 @@ export const useSessionPagination = ({ const pagination = useMemo( () => - inMemory - ? ({ + (inMemory + ? { initialPageSize: sessionStorageTableOptions.page.size, pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], pageIndex: sessionStorageTableOptions.page.index, - } as InMemoryPagination) - : ({ + } + : { totalItemCount, pageSize: sessionStorageTableOptions.page.size ?? DEFAULT_PAGE_SIZE, pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], pageIndex: sessionStorageTableOptions.page.index, - } as ServerSidePagination), + }) as T extends true ? InMemoryPagination : ServerSidePagination, [inMemory, sessionStorageTableOptions, totalItemCount] ); @@ -86,11 +86,8 @@ export const useSessionPagination = ({ [sessionStorageTableOptions.sort] ); - const onTableChange = useCallback( - ( - args: // eslint-disable-next-line @typescript-eslint/no-explicit-any - any - ) => { + const onTableChange: UseSessionPaginationReturn['onTableChange'] = useCallback( + (args) => { const { page, sort } = args; setSessionStorageTableOptions({ page, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index f8be9c9db4168..bfcdafc4dafb5 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -85,11 +85,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ refetchPrompts(); refetchConversations(); }, [refetchPrompts, refetchConversations]); - const conversationSettings = conversations; + const { systemPromptSettings: allSystemPrompts, assistantStreamingEnabled, - // conversationSettings, conversationsSettingsBulkActions, resetSettings, saveSettings, @@ -160,7 +159,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ ); const onConversationDeleted = useConversationDeleted({ - conversationSettings, + conversationSettings: conversations, conversationsSettingsBulkActions, setConversationSettings, setConversationsSettingsBulkActions, @@ -204,7 +203,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ allSystemPrompts, actionTypeRegistry, connectors, - conversations: conversationSettings, + conversations, defaultConnector, }); @@ -282,7 +281,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ {selectedConversation ? ( = React.memo( () => conversations.map((conversation) => ({ label: conversation.title ?? '', + id: conversation.id, 'data-test-subj': TEST_IDS.CONVERSATIONS_MULTISELECTOR_OPTION(conversation.title), })), [conversations] @@ -42,6 +43,7 @@ export const ConversationMultiSelector: React.FC = React.memo( return selectedConversations != null ? selectedConversations.map((conversation) => ({ label: conversation.title, + id: conversation.id, })) : []; }, [selectedConversations]); @@ -49,11 +51,17 @@ export const ConversationMultiSelector: React.FC = React.memo( const handleSelectionChange = useCallback( (conversationMultiSelectorOption: EuiComboBoxOptionOption[]) => { const newConversationSelection = conversations.filter((conversation) => - conversationMultiSelectorOption.some((cmso) => conversation.title === cmso.label) + conversationMultiSelectorOption.some((cmso) => conversation.id === cmso.id) ); + console.log('handleSelectionChange', { + conversations, + selectedOptions, + newConversationSelection, + conversationMultiSelectorOption, + }); onConversationSelectionChange(newConversationSelection); }, - [onConversationSelectionChange, conversations] + [conversations, selectedOptions, onConversationSelectionChange] ); // Callback for when user selects a conversation @@ -61,7 +69,8 @@ export const ConversationMultiSelector: React.FC = React.memo( (newOptions: EuiComboBoxOptionOption[]) => { if (newOptions.length === 0) { handleSelectionChange([]); - } else if (options.findIndex((o) => o.label === newOptions?.[0].label) !== -1) { + } else if (options.findIndex((o) => o.id === newOptions?.[0].id) !== -1) { + console.log('onChange', { newOptions, options }); handleSelectionChange(newOptions); } }, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx index aa507ab2cf8ab..20eec8e2e00e8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx @@ -141,6 +141,15 @@ export const SystemPromptEditorComponent: React.FC = ({ const conversationsWithApiConfig = Object.entries(conversationSettings).reduce< Record >((acc, [key, conversation]) => { + console.log('apiConfig', { + prevConfig: conversation.apiConfig, + newConfig: getConversationApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation, + defaultConnector, + }), + }); acc[key] = { ...conversation, ...getConversationApiConfig({ @@ -166,11 +175,9 @@ export const SystemPromptEditorComponent: React.FC = ({ const handleConversationSelectionChange = useCallback( (currentPromptConversations: Conversation[]) => { - const currentPromptConversationTitles = currentPromptConversations.map( - (convo) => convo.title - ); + const currentPromptConversationIds = currentPromptConversations.map((convo) => convo.id); const getDefaultSystemPromptId = (convo: Conversation) => - currentPromptConversationTitles.includes(convo.title) + currentPromptConversationIds.includes(convo.id) ? selectedSystemPrompt?.id : convo.apiConfig && convo.apiConfig.defaultSystemPromptId === selectedSystemPrompt?.id ? // remove the default System Prompt if it is assigned to a conversation @@ -232,6 +239,7 @@ export const SystemPromptEditorComponent: React.FC = ({ return {}; }; + // TODO when do we create a conversation from the system prompt editor? const createOperation = convo.id === '' ? { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx index 090925b7b8ca3..a897421c3cd03 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx @@ -28,9 +28,9 @@ export const useSystemPromptEditor = ({ const { currentAppId } = useAssistantContext(); // When top level system prompt selection changes const onSystemPromptSelectionChange = useCallback( - (systemPrompt?: PromptResponse | string) => { + (systemPrompt: PromptResponse | string) => { const isNew = typeof systemPrompt === 'string'; - const newSelectedSystemPrompt: PromptResponse | undefined = isNew + const newSelectedSystemPrompt: PromptResponse = isNew ? { id: systemPrompt ?? '', content: '', @@ -40,31 +40,29 @@ export const useSystemPromptEditor = ({ } : systemPrompt; - if (newSelectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev) => { - const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); + setUpdatedSystemPromptSettings((prev) => { + const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); - if (!alreadyExists) { - return [...prev, newSelectedSystemPrompt]; - } + if (!alreadyExists) { + return [...prev, newSelectedSystemPrompt]; + } - return prev; - }); + return prev; + }); - if (isNew) { - setPromptsBulkActions((prev) => { - const newBulkActions = { - ...prev, - create: [ - ...(promptsBulkActions.create ?? []), - { - ...newSelectedSystemPrompt, - }, - ], - }; - return newBulkActions; - }); - } + if (isNew) { + setPromptsBulkActions((prev) => { + const newBulkActions = { + ...prev, + create: [ + ...(promptsBulkActions.create ?? []), + { + ...newSelectedSystemPrompt, + }, + ], + }; + return newBulkActions; + }); } onSelectedSystemPromptChange(newSelectedSystemPrompt); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 10e6fc946d536..c415de18db64b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -18,6 +18,7 @@ import { import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { useSystemPromptUpdater } from '../../../settings/use_settings_updater/use_system_prompt_updater'; import { useAssistantContext, useFetchCurrentUserConversations } from '../../../../..'; import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -76,17 +77,22 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const { conversationSettings, - setConversationSettings, - systemPromptSettings, - setUpdatedSystemPromptSettings, conversationsSettingsBulkActions, - setConversationsSettingsBulkActions, + promptsBulkActions, resetSettings, saveSettings, - promptsBulkActions, + setConversationSettings, + setConversationsSettingsBulkActions, setPromptsBulkActions, + setUpdatedSystemPromptSettings, } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded); + const { systemPromptSettings, setSystemPromptSettings } = useSystemPromptUpdater({ + allPrompts, + http, + isAssistantEnabled, + }); + // System Prompt Selection State const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); @@ -95,9 +101,10 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }, []); useEffect(() => { - if (selectedSystemPrompt != null) { - setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id)); - } + // TODO can i delete this?? + // if (selectedSystemPrompt != null) { + // setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id)); + // } }, [selectedSystemPrompt, systemPromptSettings]); const handleSave = useCallback( @@ -135,7 +142,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const onEditActionClicked = useCallback( (prompt: PromptResponse) => { - onSystemPromptSelectionChange(prompt); + // onSystemPromptSelectionChange(prompt); openFlyout(); }, [onSystemPromptSelectionChange, openFlyout] @@ -183,7 +190,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const { getColumns, getSystemPromptsList } = useSystemPromptTable(); - const { onTableChange, pagination, sorting } = useSessionPagination({ + const { onTableChange, pagination, sorting } = useSessionPagination({ defaultTableOptions: DEFAULT_TABLE_OPTIONS, nameSpace, storageKey: SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY, @@ -200,16 +207,6 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }), [getColumns, isTableLoading, onEditActionClicked, onDeleteActionClicked] ); - const systemPromptListItems = useMemo( - () => - getSystemPromptsList({ - connectors, - conversationSettings, - defaultConnector, - systemPromptSettings, - }), - [getSystemPromptsList, connectors, conversationSettings, defaultConnector, systemPromptSettings] - ); return ( <> @@ -228,7 +225,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector { - const getActions = useInlineActions(); + const getActions = useInlineActions(); const getColumns = useCallback( ({ isActionsDisabled, @@ -31,16 +25,16 @@ export const useSystemPromptTable = () => { onDeleteActionClicked, }: { isActionsDisabled: boolean; - isDeleteEnabled: (conversation: SystemPromptTableItem) => boolean; - isEditEnabled: (conversation: SystemPromptTableItem) => boolean; - onEditActionClicked: (prompt: SystemPromptTableItem) => void; - onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; - }): Array> => [ + isDeleteEnabled: (conversation: SystemPromptSettings) => boolean; + isEditEnabled: (conversation: SystemPromptSettings) => boolean; + onEditActionClicked: (prompt: SystemPromptSettings) => void; + onDeleteActionClicked: (prompt: SystemPromptSettings) => void; + }): Array> => [ { align: 'left', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, truncateText: { lines: 3 }, - render: (prompt: SystemPromptTableItem) => + render: (prompt: SystemPromptSettings) => prompt?.name ? ( onEditActionClicked(prompt)} disabled={isActionsDisabled}> {prompt?.name} @@ -53,20 +47,20 @@ export const useSystemPromptTable = () => { )} ) : null, - sortable: ({ name }: SystemPromptTableItem) => name, + sortable: ({ name }: SystemPromptSettings) => name, }, { align: 'left', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: ({ defaultConversations, id }: SystemPromptTableItem) => ( - + render: ({ conversations, id }: SystemPromptSettings) => ( + ), }, { align: 'left', field: 'updatedAt', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DATE_UPDATED, - render: (updatedAt: SystemPromptTableItem['updatedAt']) => + render: (updatedAt: SystemPromptSettings['updatedAt']) => updatedAt ? ( { [getActions] ); - const getSystemPromptsList = ({ - connectors, - conversationSettings, - defaultConnector, - systemPromptSettings, - }: { - connectors: AIConnector[] | undefined; - conversationSettings: Record; - defaultConnector: AIConnector | undefined; - systemPromptSettings: PromptResponse[]; - }): SystemPromptTableItem[] => { - const conversationsWithApiConfig = Object.entries(conversationSettings).reduce< - Record - >((acc, [key, conversation]) => { - acc[key] = { - ...conversation, - ...getConversationApiConfig({ - allSystemPrompts: systemPromptSettings, - connectors, - conversation, - defaultConnector, - }), - }; - - return acc; - }, {}); - return systemPromptSettings.map((systemPrompt) => { - const defaultConversations = getSelectedConversations( - conversationsWithApiConfig, - systemPrompt?.id - ).map(({ title }) => title); - - return { - ...systemPrompt, - defaultConversations, - }; - }); - }; - - return { getColumns, getSystemPromptsList }; + return { getColumns }; }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx index b86353112725a..f091b7be992e6 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx @@ -97,6 +97,8 @@ export const useSettingsUpdater = ( const [systemPromptSettings, setUpdatedSystemPromptSettings] = useState( allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system) ); + console.log('systemPromptSettings', systemPromptSettings); + // Anonymization const [anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions] = useState({}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts new file mode 100644 index 0000000000000..a40d86c54ef95 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo, useState } from 'react'; +import { FindPromptsResponse, PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common'; +import { HttpSetup } from '@kbn/core-http-browser'; +import { Conversation, useFetchCurrentUserConversations } from '../../../..'; +interface Params { + allPrompts: FindPromptsResponse; + http: HttpSetup; + isAssistantEnabled: boolean; +} +export interface SystemPromptSettings extends PromptResponse { + conversations: Conversation[]; +} +export const useSystemPromptUpdater = ({ + allPrompts, + http, + isAssistantEnabled, +}: Params): { + systemPromptSettings: SystemPromptSettings[]; + setSystemPromptSettings: React.Dispatch>; +} => { + const [systemPromptSettings, setSystemPromptSettings] = useState([]); + + const systemPrompts = useMemo( + () => allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system), + [allPrompts.data] + ); + + const filter = useMemo(() => { + const systemPromptIds = systemPrompts.map((p) => p.id); + if (!Array.isArray(systemPromptIds) || systemPromptIds.length === 0) { + return ''; + } + + return systemPromptIds + .map((value) => `api_config.default_system_prompt_id: "${value}"`) + .join(' OR '); + }, [systemPrompts]); + + const { data } = useFetchCurrentUserConversations({ + http, + isAssistantEnabled: isAssistantEnabled && filter.length > 0, + filter, + }); + useEffect(() => { + if (!Object.keys(data).length) return; + + setSystemPromptSettings((prev) => { + const updatedSettings = systemPrompts.map((p) => { + const conversations = Object.values(data).filter( + (conversation) => conversation.apiConfig?.defaultSystemPromptId === p.id + ); + return { ...p, conversations }; + }); + + // Only update state if there's an actual change + if (JSON.stringify(prev) !== JSON.stringify(updatedSettings)) { + return updatedSettings; + } + return prev; + }); + }, [data, systemPrompts]); + + console.log('system prompt data', { systemPromptSettings, filter, data }); + return { + systemPromptSettings, + setSystemPromptSettings, + }; +}; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts index 69d1d75efcf5d..c60858157d4f8 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/scripts/create_conversations_script.ts @@ -70,9 +70,13 @@ export const create = async () => { conversationsCreateUrl, getCreateConversationSchemaMock({ ...getMockConversationContent(), + title: 'Very very old convo', apiConfig: { - actionTypeId: aiConnectors[0].connector_type_id, - connectorId: aiConnectors[0].id, + connectorId: '476563c9-47f8-4d20-9ebf-072521c8a748', + actionTypeId: '.gen-ai', + defaultSystemPromptId: '5dyRAJUBQBIRhhJMj7or', + model: 'gpt-4-turbo', + provider: 'OpenAI', }, }), { headers: requestHeaders } @@ -126,11 +130,11 @@ const getRandomISODate = () => { const now = new Date(); const probabilities = [ { chance: 3 / 10, daysAgo: Math.floor(Math.random() * 335) + 31 }, // Over a month ago - { chance: 1 / 10, daysAgo: 4 }, // In the last week - { chance: 1 / 10, daysAgo: 1 }, // Yesterday - { chance: 1 / 10, daysAgo: 0 }, // Today - { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 7) + 7 }, // Two weeks ago - { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 14) + 14 }, // Over two weeks ago + // { chance: 1 / 10, daysAgo: 4 }, // In the last week + // { chance: 1 / 10, daysAgo: 1 }, // Yesterday + // { chance: 1 / 10, daysAgo: 0 }, // Today + // { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 7) + 7 }, // Two weeks ago + // { chance: 2 / 10, daysAgo: Math.floor(Math.random() * 14) + 14 }, // Over two weeks ago ]; const rand = Math.random(); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts index 7f4b627fd69ed..3426d8d2a0681 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts @@ -57,6 +57,7 @@ export const findDocuments = async ({ mSearch, }: FindOptions): Promise> => { const query = getQueryFilter({ filter }); + console.log('find docs query:', JSON.stringify(query)); let sort: Sort | undefined; const ascOrDesc = sortOrder ?? ('asc' as const); if (sortField != null) { From 984805fefcafc4a30c6f53904368c385c556b29f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 14 Feb 2025 14:21:23 -0700 Subject: [PATCH 13/80] system prompt close --- .../conversation_multi_selector.tsx | 7 - .../system_prompt_editor.tsx | 368 +------------- .../system_prompt_selector.tsx | 8 +- .../index.tsx | 109 ++-- .../use_conversations_updater.ts | 109 ++++ .../use_settings_updater.tsx | 1 - .../use_system_prompt_updater.ts | 469 +++++++++++++++++- .../server/ai_assistant_data_clients/find.ts | 1 - 8 files changed, 644 insertions(+), 428 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_conversations_updater.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/conversation_multi_selector/conversation_multi_selector.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/conversation_multi_selector/conversation_multi_selector.tsx index b438fc581b50a..a396e789685c9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/conversation_multi_selector/conversation_multi_selector.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/conversation_multi_selector/conversation_multi_selector.tsx @@ -53,12 +53,6 @@ export const ConversationMultiSelector: React.FC = React.memo( const newConversationSelection = conversations.filter((conversation) => conversationMultiSelectorOption.some((cmso) => conversation.id === cmso.id) ); - console.log('handleSelectionChange', { - conversations, - selectedOptions, - newConversationSelection, - conversationMultiSelectorOption, - }); onConversationSelectionChange(newConversationSelection); }, [conversations, selectedOptions, onConversationSelectionChange] @@ -70,7 +64,6 @@ export const ConversationMultiSelector: React.FC = React.memo( if (newOptions.length === 0) { handleSelectionChange([]); } else if (options.findIndex((o) => o.id === newOptions?.[0].id) !== -1) { - console.log('onChange', { newOptions, options }); handleSelectionChange(newOptions); } }, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx index 20eec8e2e00e8..b89b2e6211a9d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { EuiFormRow, EuiTextArea, @@ -15,60 +15,39 @@ import { EuiFlexItem, } from '@elastic/eui'; -import { keyBy } from 'lodash/fp'; - import { css } from '@emotion/react'; -import { - PromptResponse, - PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody, -} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; -import { ApiConfig } from '@kbn/elastic-assistant-common'; -import { AIConnector } from '../../../../connectorland/connector_selector'; +import { SystemPromptSettings } from '../../../settings/use_settings_updater/use_system_prompt_updater'; import { Conversation } from '../../../../..'; import * as i18n from './translations'; import { ConversationMultiSelector } from './conversation_multi_selector/conversation_multi_selector'; import { SystemPromptSelector } from './system_prompt_selector/system_prompt_selector'; import { TEST_IDS } from '../../../constants'; -import { ConversationsBulkActions } from '../../../api'; -import { getSelectedConversations } from '../system_prompt_settings_management/utils'; -import { useSystemPromptEditor } from './use_system_prompt_editor'; -import { getConversationApiConfig } from '../../../use_conversation/helpers'; interface Props { - connectors: AIConnector[] | undefined; - conversationSettings: Record; - conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; - selectedSystemPrompt: PromptResponse | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; - setConversationSettings: React.Dispatch>>; - systemPromptSettings: PromptResponse[]; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; - defaultConnector?: AIConnector; + conversations: Record; + onConversationSelectionChange: (currentPromptConversations: Conversation[]) => void; + onNewConversationDefaultChange: (e: React.ChangeEvent) => void; + onPromptContentChange: (e: React.ChangeEvent) => void; + onSystemPromptDelete: (id: string) => void; + onSystemPromptSelect: (systemPrompt?: SystemPromptSettings | string) => void; resetSettings?: () => void; - promptsBulkActions: PromptsPerformBulkActionRequestBody; - setPromptsBulkActions: React.Dispatch>; + selectedSystemPrompt: SystemPromptSettings | undefined; + systemPromptSettings: SystemPromptSettings[]; } /** * Settings for adding/removing system prompts. Configure name, prompt and default conversations. */ export const SystemPromptEditorComponent: React.FC = ({ - connectors, - conversationSettings, - onSelectedSystemPromptChange, + conversations, + onConversationSelectionChange, + onNewConversationDefaultChange, + onPromptContentChange, + onSystemPromptDelete, + onSystemPromptSelect, + resetSettings, selectedSystemPrompt, - setUpdatedSystemPromptSettings, - setConversationSettings, systemPromptSettings, - conversationsSettingsBulkActions, - setConversationsSettingsBulkActions, - defaultConnector, - resetSettings, - promptsBulkActions, - setPromptsBulkActions, }) => { // Prompt const promptContent = useMemo( @@ -76,217 +55,12 @@ export const SystemPromptEditorComponent: React.FC = ({ () => systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt?.id)?.content ?? '', [selectedSystemPrompt?.id, systemPromptSettings] ); - - const handlePromptContentChange = useCallback( - (e: React.ChangeEvent) => { - if (selectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev): PromptResponse[] => { - const alreadyExists = prev.some((sp) => sp.id === selectedSystemPrompt.id); - - if (alreadyExists) { - return prev.map((sp): PromptResponse => { - if (sp.id === selectedSystemPrompt.id) { - return { - ...sp, - content: e.target.value, - }; - } - return sp; - }); - } - - return prev; - }); - const existingPrompt = systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt.id); - if (existingPrompt) { - const newBulkActions = { - ...promptsBulkActions, - ...(selectedSystemPrompt.name !== selectedSystemPrompt.id - ? { - update: [ - ...(promptsBulkActions.update ?? []).filter( - (p) => p.id !== selectedSystemPrompt.id - ), - { - ...selectedSystemPrompt, - content: e.target.value, - }, - ], - } - : { - create: [ - ...(promptsBulkActions.create ?? []).filter( - (p) => p.name !== selectedSystemPrompt.name - ), - { - ...selectedSystemPrompt, - content: e.target.value, - }, - ], - }), - }; - setPromptsBulkActions(newBulkActions); - } - } - }, - [ - promptsBulkActions, - selectedSystemPrompt, - setPromptsBulkActions, - setUpdatedSystemPromptSettings, - systemPromptSettings, - ] - ); - - const conversationsWithApiConfig = Object.entries(conversationSettings).reduce< - Record - >((acc, [key, conversation]) => { - console.log('apiConfig', { - prevConfig: conversation.apiConfig, - newConfig: getConversationApiConfig({ - allSystemPrompts: systemPromptSettings, - connectors, - conversation, - defaultConnector, - }), - }); - acc[key] = { - ...conversation, - ...getConversationApiConfig({ - allSystemPrompts: systemPromptSettings, - connectors, - conversation, - defaultConnector, - }), - }; - return acc; - }, {}); // Conversations this system prompt should be a default for - const conversationOptions = useMemo( - () => Object.values(conversationsWithApiConfig), - [conversationsWithApiConfig] - ); + const conversationOptions = useMemo(() => Object.values(conversations), [conversations]); const selectedConversations = useMemo(() => { - return selectedSystemPrompt != null - ? getSelectedConversations(conversationsWithApiConfig, selectedSystemPrompt.id) - : []; - }, [conversationsWithApiConfig, selectedSystemPrompt]); - - const handleConversationSelectionChange = useCallback( - (currentPromptConversations: Conversation[]) => { - const currentPromptConversationIds = currentPromptConversations.map((convo) => convo.id); - const getDefaultSystemPromptId = (convo: Conversation) => - currentPromptConversationIds.includes(convo.id) - ? selectedSystemPrompt?.id - : convo.apiConfig && convo.apiConfig.defaultSystemPromptId === selectedSystemPrompt?.id - ? // remove the default System Prompt if it is assigned to a conversation - // but that conversation is not in the currentPromptConversationList - // This means conversation was removed in the current transaction - undefined - : // leave it as it is .. if that conversation was neither added nor removed. - convo.apiConfig?.defaultSystemPromptId; - - if (selectedSystemPrompt != null) { - setConversationSettings((prev) => - keyBy( - 'id', - /* - * updatedConversationWithPrompts calculates the present of prompt for - * each conversation. Based on the values of selected conversation, it goes - * through each conversation adds/removed the selected prompt on each conversation. - * - * */ - Object.values(prev).map((convo) => { - const newConversationSetting = { - ...convo, - ...(convo.apiConfig - ? { - apiConfig: { - ...convo.apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - } - : { - apiConfig: { - defaultSystemPromptId: getDefaultSystemPromptId(convo), - connectorId: defaultConnector?.id ?? '', - actionTypeId: defaultConnector?.actionTypeId ?? '', - }, - }), - }; - return newConversationSetting; - }) - ) - ); - - let updatedConversationsSettingsBulkActions = { ...conversationsSettingsBulkActions }; - Object.values(conversationsWithApiConfig).forEach((convo) => { - const getApiConfigWithSelectedPrompt = (): ApiConfig | {} => { - if (convo.apiConfig) { - return { - apiConfig: { - ...getConversationApiConfig({ - allSystemPrompts: systemPromptSettings, - connectors, - conversation: convo, - defaultConnector, - }).apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - }; - } - - return {}; - }; - // TODO when do we create a conversation from the system prompt editor? - const createOperation = - convo.id === '' - ? { - create: { - ...(updatedConversationsSettingsBulkActions.create ?? {}), - [convo.title]: { - ...convo, - ...(convo.apiConfig ? getApiConfigWithSelectedPrompt() : {}), - }, - }, - } - : {}; - const updateOperation = - convo.id !== '' - ? { - update: { - ...(updatedConversationsSettingsBulkActions.update ?? {}), - [convo.id]: { - ...(updatedConversationsSettingsBulkActions.update - ? updatedConversationsSettingsBulkActions.update[convo.id] ?? {} - : {}), - ...getApiConfigWithSelectedPrompt(), - }, - }, - } - : {}; - - updatedConversationsSettingsBulkActions = { - ...updatedConversationsSettingsBulkActions, - ...createOperation, - ...updateOperation, - }; - }); - setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); - } - }, - [ - connectors, - conversationsSettingsBulkActions, - conversationsWithApiConfig, - defaultConnector, - selectedSystemPrompt, - setConversationSettings, - setConversationsSettingsBulkActions, - systemPromptSettings, - ] - ); + return selectedSystemPrompt != null ? selectedSystemPrompt.conversations : []; + }, [selectedSystemPrompt]); // Whether this system prompt should be the default for new conversations const isNewConversationDefault = useMemo( @@ -294,104 +68,12 @@ export const SystemPromptEditorComponent: React.FC = ({ [selectedSystemPrompt?.isNewConversationDefault] ); - const handleNewConversationDefaultChange = useCallback( - (e: React.ChangeEvent) => { - const isChecked = e.target.checked; - const defaultNewSystemPrompts = systemPromptSettings.filter( - (p) => p.isNewConversationDefault - ); - - const shouldCreateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => - sp?.name === sp?.id; // Prompts before preserving have the SAME name and id - - const shouldUpdateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => - sp?.name !== sp?.id; // Prompts after preserving have different name and id - - const shouldCreateSelectedSystemPrompt = - selectedSystemPrompt?.name === selectedSystemPrompt?.id; - - const shouldUpdateSelectedSystemPrompt = - selectedSystemPrompt?.name !== selectedSystemPrompt?.id; - - if (selectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev) => { - return prev.map((pp) => { - return { - ...pp, - isNewConversationDefault: selectedSystemPrompt.id === pp.id && isChecked, - }; - }); - }); - // Update and Create prompts can happen at the same time, as we have to unchecked the previous default prompt - // Each prompt can be updated or created - setPromptsBulkActions(() => { - const newBulkActions = { - update: [ - ...defaultNewSystemPrompts - .filter( - (p) => p.id !== selectedSystemPrompt.id && shouldUpdateNewDefaultSystemPrompts(p) - ) - .map((p) => ({ - ...p, - isNewConversationDefault: false, - })), - - ...(shouldUpdateSelectedSystemPrompt - ? [ - { - ...selectedSystemPrompt, - isNewConversationDefault: isChecked, - }, - ] - : []), - ], - create: [ - ...defaultNewSystemPrompts - .filter( - (p) => - p.name !== selectedSystemPrompt.name && shouldCreateNewDefaultSystemPrompts(p) - ) - .map((p) => ({ - ...p, - isNewConversationDefault: false, - })), - - ...(shouldCreateSelectedSystemPrompt - ? [ - { - ...selectedSystemPrompt, - isNewConversationDefault: isChecked, - }, - ] - : []), - ], - }; - - return newBulkActions; - }); - } - }, - [ - selectedSystemPrompt, - setPromptsBulkActions, - setUpdatedSystemPromptSettings, - systemPromptSettings, - ] - ); - - const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({ - setUpdatedSystemPromptSettings, - onSelectedSystemPromptChange, - promptsBulkActions, - setPromptsBulkActions, - }); - return ( <> = ({ = ({ @@ -439,7 +121,7 @@ export const SystemPromptEditorComponent: React.FC = ({ } checked={isNewConversationDefault} - onChange={handleNewConversationDefaultChange} + onChange={onNewConversationDefaultChange} /> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx index 15e047c599727..f560877791580 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; -import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { SystemPromptSettings } from '../../../../settings/use_settings_updater/use_system_prompt_updater'; import { TEST_IDS } from '../../../../constants'; import * as i18n from './translations'; import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../translations'; @@ -28,9 +28,9 @@ export const SYSTEM_PROMPT_SELECTOR_CLASSNAME = 'systemPromptSelector'; interface Props { autoFocus?: boolean; onSystemPromptDeleted: (systemPromptTitle: string) => void; - onSystemPromptSelectionChange: (systemPrompt?: PromptResponse | string) => void; - systemPrompts: PromptResponse[]; - selectedSystemPrompt?: PromptResponse; + onSystemPromptSelectionChange: (systemPrompt?: SystemPromptSettings | string) => void; + systemPrompts: SystemPromptSettings[]; + selectedSystemPrompt?: SystemPromptSettings; resetSettings?: () => void; } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index c415de18db64b..4e219d1fda616 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -15,10 +15,14 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; -import { useSystemPromptUpdater } from '../../../settings/use_settings_updater/use_system_prompt_updater'; +import { useConversationsUpdater } from '../../../settings/use_settings_updater/use_conversations_updater'; +import { + SystemPromptSettings, + useSystemPromptUpdater, +} from '../../../settings/use_settings_updater/use_system_prompt_updater'; import { useAssistantContext, useFetchCurrentUserConversations } from '../../../../..'; import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -30,20 +34,19 @@ import { useSessionPagination, } from '../../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../../settings/translations'; -import { useSettingsUpdater } from '../../../settings/use_settings_updater/use_settings_updater'; import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; import { SETTINGS_TITLE } from '../system_prompt_modal/translations'; -import { useSystemPromptEditor } from '../system_prompt_modal/use_system_prompt_editor'; import * as i18n from './translations'; import { useSystemPromptTable } from './use_system_prompt_table'; interface Props { - connectors: AIConnector[] | undefined; + connectors?: AIConnector[]; defaultConnector?: AIConnector; } const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }: Props) => { const { + currentAppId, nameSpace, http, assistantAvailability: { isAssistantEnabled }, @@ -76,85 +79,79 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const [deletedPrompt, setDeletedPrompt] = useState(); const { - conversationSettings, conversationsSettingsBulkActions, - promptsBulkActions, - resetSettings, - saveSettings, - setConversationSettings, + resetConversationsSettings, + saveConversationsSettings, setConversationsSettingsBulkActions, - setPromptsBulkActions, - setUpdatedSystemPromptSettings, - } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded); + } = useConversationsUpdater(conversations, conversationsLoaded); - const { systemPromptSettings, setSystemPromptSettings } = useSystemPromptUpdater({ + const { + onConversationSelectionChange, + onNewConversationDefaultChange, + onPromptContentChange, + onSystemPromptDelete, + onSystemPromptSelect, + refetchSystemPromptConversations, + resetSystemPromptSettings, + saveSystemPromptSettings, + selectedSystemPrompt, + systemPromptSettings, + } = useSystemPromptUpdater({ allPrompts, + connectors, + conversationsSettingsBulkActions, + currentAppId, + defaultConnector, http, isAssistantEnabled, + setConversationsSettingsBulkActions, }); - // System Prompt Selection State - const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); - - const onSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => { - setSelectedSystemPrompt(systemPrompt); - }, []); - - useEffect(() => { - // TODO can i delete this?? - // if (selectedSystemPrompt != null) { - // setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id)); - // } - }, [selectedSystemPrompt, systemPromptSettings]); - const handleSave = useCallback( async (param?: { callback?: () => void }) => { - await saveSettings(); + await saveSystemPromptSettings(); + await saveConversationsSettings(); + await refetchSystemPromptConversations(); toasts?.addSuccess({ iconType: 'check', title: SETTINGS_UPDATED_TOAST_TITLE, }); param?.callback?.(); }, - [saveSettings, toasts] + [saveConversationsSettings, saveSystemPromptSettings, toasts] ); const onCancelClick = useCallback(() => { - resetSettings(); - }, [resetSettings]); + resetConversationsSettings(); + resetSystemPromptSettings(); + }, [resetConversationsSettings, resetSystemPromptSettings]); const onCreate = useCallback(() => { - onSelectedSystemPromptChange({ + onSystemPromptSelect({ id: '', content: '', name: '', promptType: 'system', + conversations: [], }); openFlyout(); - }, [onSelectedSystemPromptChange, openFlyout]); - - const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({ - setUpdatedSystemPromptSettings, - onSelectedSystemPromptChange, - promptsBulkActions, - setPromptsBulkActions, - }); + }, [onSystemPromptSelect, openFlyout]); const onEditActionClicked = useCallback( - (prompt: PromptResponse) => { - // onSystemPromptSelectionChange(prompt); + (prompt: SystemPromptSettings) => { + onSystemPromptSelect(prompt); openFlyout(); }, - [onSystemPromptSelectionChange, openFlyout] + [onSystemPromptSelect, openFlyout] ); const onDeleteActionClicked = useCallback( (prompt: PromptResponse) => { setDeletedPrompt(prompt); - onSystemPromptDeleted(prompt.id); + onSystemPromptDelete(prompt.id); openConfirmModal(); }, - [onSystemPromptDeleted, openConfirmModal] + [onSystemPromptDelete, openConfirmModal] ); const onDeleteCancelled = useCallback(() => { @@ -188,7 +185,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector [deletedPrompt?.name] ); - const { getColumns, getSystemPromptsList } = useSystemPromptTable(); + const { getColumns } = useSystemPromptTable(); const { onTableChange, pagination, sorting } = useSessionPagination({ defaultTableOptions: DEFAULT_TABLE_OPTIONS, @@ -240,19 +237,15 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector saveButtonDisabled={selectedSystemPrompt?.name == null || selectedSystemPrompt?.name === ''} > {deleteConfirmModalVisibility && deletedPrompt?.name && ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_conversations_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_conversations_updater.ts new file mode 100644 index 0000000000000..c2d52ba4701c7 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_conversations_updater.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { Conversation } from '../../../..'; +import { useAssistantContext } from '../../../assistant_context'; +import { + ConversationsBulkActions, + bulkUpdateConversations, +} from '../../api/conversations/bulk_update_actions_conversations'; + +interface UseConversationsUpdater { + assistantStreamingEnabled: boolean; + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + resetConversationsSettings: () => void; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + setUpdatedAssistantStreamingEnabled: React.Dispatch>; + saveConversationsSettings: () => Promise; +} + +export const useConversationsUpdater = ( + conversations: Record, + conversationsLoaded: boolean +): UseConversationsUpdater => { + // Initial state from assistant context + const { + assistantTelemetry, + assistantStreamingEnabled, + setAssistantStreamingEnabled, + http, + toasts, + } = useAssistantContext(); + + const [conversationSettings, setConversationSettings] = + useState>(conversations); + const [conversationsSettingsBulkActions, setConversationsSettingsBulkActions] = + useState({}); + + const [updatedAssistantStreamingEnabled, setUpdatedAssistantStreamingEnabled] = + useState(assistantStreamingEnabled); + + const resetConversationsSettings = useCallback((): void => { + setConversationSettings(conversations); + setConversationsSettingsBulkActions({}); + + setUpdatedAssistantStreamingEnabled(assistantStreamingEnabled); + }, [assistantStreamingEnabled, conversations]); + + const hasBulkConversations = + conversationsSettingsBulkActions.create || + conversationsSettingsBulkActions.update || + conversationsSettingsBulkActions.delete; + + /** + * Save all pending settings + */ + const saveConversationsSettings = useCallback(async (): Promise => { + const bulkResult = hasBulkConversations + ? await bulkUpdateConversations(http, conversationsSettingsBulkActions, toasts) + : undefined; + const didUpdateAssistantStreamingEnabled = + assistantStreamingEnabled !== updatedAssistantStreamingEnabled; + + setAssistantStreamingEnabled(updatedAssistantStreamingEnabled); + + if (didUpdateAssistantStreamingEnabled) { + assistantTelemetry?.reportAssistantSettingToggled({ + assistantStreamingEnabled: updatedAssistantStreamingEnabled, + }); + } + setConversationsSettingsBulkActions({}); + return bulkResult?.success ?? true; + }, [ + http, + toasts, + hasBulkConversations, + conversationsSettingsBulkActions, + assistantStreamingEnabled, + updatedAssistantStreamingEnabled, + setAssistantStreamingEnabled, + assistantTelemetry, + ]); + + useEffect(() => { + // Update conversation settings when conversations are loaded + if (conversationsLoaded && Object.keys(conversationSettings).length === 0) { + setConversationSettings(conversations); + } + }, [conversationSettings, conversations, conversationsLoaded]); + + return { + assistantStreamingEnabled, + conversationSettings, + conversationsSettingsBulkActions, + resetConversationsSettings, + saveConversationsSettings, + setUpdatedAssistantStreamingEnabled, + setConversationSettings, + setConversationsSettingsBulkActions, + }; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx index f091b7be992e6..bcd754dbd30d5 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx @@ -97,7 +97,6 @@ export const useSettingsUpdater = ( const [systemPromptSettings, setUpdatedSystemPromptSettings] = useState( allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system) ); - console.log('systemPromptSettings', systemPromptSettings); // Anonymization const [anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions] = diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts index a40d86c54ef95..b0e1ecbc53b70 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_system_prompt_updater.ts @@ -5,27 +5,82 @@ * 2.0. */ -import { useEffect, useMemo, useState } from 'react'; -import { FindPromptsResponse, PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { + ApiConfig, + FindPromptsResponse, + PromptResponse, + PromptTypeEnum, +} from '@kbn/elastic-assistant-common'; import { HttpSetup } from '@kbn/core-http-browser'; -import { Conversation, useFetchCurrentUserConversations } from '../../../..'; +import { PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; +import { QueryObserverResult } from '@tanstack/react-query'; +import { InfiniteData } from '@tanstack/query-core/src/types'; +import { AIConnector } from '../../../connectorland/connector_selector'; +import { getConversationApiConfig } from '../../use_conversation/helpers'; +import { + bulkUpdatePrompts, + Conversation, + ConversationsBulkActions, + useFetchCurrentUserConversations, +} from '../../../..'; +import { FetchConversationsResponse } from '../../api'; interface Params { allPrompts: FindPromptsResponse; + connectors?: AIConnector[]; + conversationsSettingsBulkActions: ConversationsBulkActions; + currentAppId: string; + defaultConnector?: AIConnector; http: HttpSetup; isAssistantEnabled: boolean; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; } export interface SystemPromptSettings extends PromptResponse { conversations: Conversation[]; } + +interface SystemPromptUpdater { + onConversationSelectionChange: (currentPromptConversations: Conversation[]) => void; + onNewConversationDefaultChange: (e: React.ChangeEvent) => void; + onPromptContentChange: (e: React.ChangeEvent) => void; + onSystemPromptDelete: (id: string) => void; + onSystemPromptSelect: (systemPrompt?: SystemPromptSettings | string) => void; + refetchSystemPromptConversations: () => Promise< + QueryObserverResult, unknown> + >; + resetSystemPromptSettings: () => void; + saveSystemPromptSettings: () => Promise; + selectedSystemPrompt?: SystemPromptSettings; + systemPromptSettings: SystemPromptSettings[]; +} export const useSystemPromptUpdater = ({ allPrompts, + connectors, + conversationsSettingsBulkActions, + currentAppId, + defaultConnector, http, isAssistantEnabled, -}: Params): { - systemPromptSettings: SystemPromptSettings[]; - setSystemPromptSettings: React.Dispatch>; -} => { + setConversationsSettingsBulkActions, +}: Params): SystemPromptUpdater => { + // server equivalent const [systemPromptSettings, setSystemPromptSettings] = useState([]); + // local updates + const [systemPromptSettingsUpdates, setSystemPromptSettingsUpdates] = useState< + SystemPromptSettings[] + >([]); + const [promptsBulkActions, setPromptsBulkActions] = useState( + {} + ); + // System Prompt Selection State + const [selectedSystemPromptId, setSelectedSystemPromptId] = useState(); + + const selectedSystemPrompt: SystemPromptSettings | undefined = useMemo(() => { + const sip = systemPromptSettingsUpdates.find((sp) => sp.id === selectedSystemPromptId); + return sip; + }, [selectedSystemPromptId, systemPromptSettingsUpdates]); const systemPrompts = useMemo( () => allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system), @@ -43,15 +98,14 @@ export const useSystemPromptUpdater = ({ .join(' OR '); }, [systemPrompts]); - const { data } = useFetchCurrentUserConversations({ + const { data, refetch } = useFetchCurrentUserConversations({ http, isAssistantEnabled: isAssistantEnabled && filter.length > 0, filter, }); useEffect(() => { if (!Object.keys(data).length) return; - - setSystemPromptSettings((prev) => { + const updateSystemPromptSettings = (prev: SystemPromptSettings[]) => { const updatedSettings = systemPrompts.map((p) => { const conversations = Object.values(data).filter( (conversation) => conversation.apiConfig?.defaultSystemPromptId === p.id @@ -64,12 +118,399 @@ export const useSystemPromptUpdater = ({ return updatedSettings; } return prev; - }); + }; + + setSystemPromptSettings(updateSystemPromptSettings); }, [data, systemPrompts]); - console.log('system prompt data', { systemPromptSettings, filter, data }); + useEffect(() => { + if (systemPromptSettings.length) { + setSystemPromptSettingsUpdates(systemPromptSettings); + } + }, [systemPromptSettings]); + + const onSystemPromptSelect = useCallback( + (systemPrompt?: SystemPromptSettings | string) => { + if (systemPrompt == null) { + return setSelectedSystemPromptId(undefined); + } + const isNew = typeof systemPrompt === 'string'; + const newSelectedSystemPrompt: SystemPromptSettings = isNew + ? { + id: systemPrompt ?? '', + content: '', + name: systemPrompt ?? '', + promptType: 'system', + consumer: currentAppId, + conversations: [], + } + : systemPrompt; + + setSystemPromptSettingsUpdates((prev) => + !prev.some((sp) => sp.id === newSelectedSystemPrompt.id) + ? [...prev, newSelectedSystemPrompt] + : prev + ); + + if (isNew) { + setPromptsBulkActions((prev) => { + const newBulkActions = { + ...prev, + create: [ + ...(promptsBulkActions.create ?? []), + { + ...newSelectedSystemPrompt, + }, + ], + }; + return newBulkActions; + }); + } + + setSelectedSystemPromptId(newSelectedSystemPrompt.id); + }, + [currentAppId, promptsBulkActions.create] + ); + + const onSystemPromptDelete = useCallback( + (id: string) => { + setSystemPromptSettingsUpdates((prev) => prev.filter((sp) => sp.id !== id)); + setPromptsBulkActions({ + ...promptsBulkActions, + delete: { + ids: [...(promptsBulkActions.delete?.ids ?? []), id], + }, + }); + }, + [promptsBulkActions, setPromptsBulkActions] + ); + + const onPromptContentChange = useCallback( + (e: React.ChangeEvent) => { + if (selectedSystemPrompt != null) { + setSystemPromptSettingsUpdates((prev): SystemPromptSettings[] => + prev.map((sp): SystemPromptSettings => { + if (sp.id === selectedSystemPrompt.id) { + return { + ...sp, + content: e.target.value, + }; + } + return sp; + }) + ); + const existingPrompt = systemPromptSettingsUpdates.find( + (sp) => sp.id === selectedSystemPrompt.id + ); + if (existingPrompt) { + const newBulkActions = { + ...promptsBulkActions, + ...(selectedSystemPrompt.name !== selectedSystemPrompt.id + ? { + update: [ + ...(promptsBulkActions.update ?? []).filter( + (p) => p.id !== selectedSystemPrompt.id + ), + { + ...selectedSystemPrompt, + content: e.target.value, + }, + ], + } + : { + create: [ + ...(promptsBulkActions.create ?? []).filter( + (p) => p.name !== selectedSystemPrompt.name + ), + { + ...selectedSystemPrompt, + content: e.target.value, + }, + ], + }), + }; + setPromptsBulkActions(newBulkActions); + } + } + }, + [promptsBulkActions, selectedSystemPrompt, systemPromptSettingsUpdates] + ); + + const onNewConversationDefaultChange = useCallback( + (e: React.ChangeEvent) => { + const isChecked = e.target.checked; + const defaultNewSystemPrompts = systemPromptSettingsUpdates.filter( + (p) => p.isNewConversationDefault + ); + + const shouldCreateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => + sp?.name === sp?.id; // Prompts before preserving have the SAME name and id + + const shouldUpdateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => + sp?.name !== sp?.id; // Prompts after preserving have different name and id + + const shouldCreateSelectedSystemPrompt = + selectedSystemPrompt?.name === selectedSystemPrompt?.id; + + const shouldUpdateSelectedSystemPrompt = + selectedSystemPrompt?.name !== selectedSystemPrompt?.id; + + if (selectedSystemPrompt != null) { + setSystemPromptSettingsUpdates((prev) => { + return prev.map((pp) => { + return { + ...pp, + isNewConversationDefault: selectedSystemPrompt.id === pp.id && isChecked, + }; + }); + }); + // Update and Create prompts can happen at the same time, as we have to unchecked the previous default prompt + // Each prompt can be updated or created + setPromptsBulkActions(() => { + const newBulkActions = { + update: [ + ...defaultNewSystemPrompts + .filter( + (p) => p.id !== selectedSystemPrompt.id && shouldUpdateNewDefaultSystemPrompts(p) + ) + .map((p) => ({ + ...p, + isNewConversationDefault: false, + })), + + ...(shouldUpdateSelectedSystemPrompt + ? [ + { + ...selectedSystemPrompt, + isNewConversationDefault: isChecked, + }, + ] + : []), + ], + create: [ + ...defaultNewSystemPrompts + .filter( + (p) => + p.name !== selectedSystemPrompt.name && shouldCreateNewDefaultSystemPrompts(p) + ) + .map((p) => ({ + ...p, + isNewConversationDefault: false, + })), + + ...(shouldCreateSelectedSystemPrompt + ? [ + { + ...selectedSystemPrompt, + isNewConversationDefault: isChecked, + }, + ] + : []), + ], + }; + + return newBulkActions; + }); + } + }, + [selectedSystemPrompt, systemPromptSettingsUpdates] + ); + const [_conversationRemovals, setConversationRemovals] = useState([]); + + const onConversationSelectionChange = useCallback( + (currentPromptConversations: Conversation[]) => { + const originalSelectedSystemPrompt = systemPromptSettings.find( + (sp) => sp.id === selectedSystemPromptId + ); + const currentPromptConversationIds = currentPromptConversations.map((convo) => convo.id); + const removals = + originalSelectedSystemPrompt?.conversations.filter( + (c) => !currentPromptConversationIds.includes(c.id) + ) ?? []; + let cRemovals: Conversation[] = []; + setConversationRemovals((prev) => { + removals.forEach((r) => { + if (!prev.find((p) => p.id === r.id)) { + prev.push(r); + } + }); + cRemovals = prev.filter((c) => !currentPromptConversationIds.includes(c.id)); + return cRemovals; + }); + const getDefaultSystemPromptId = (convo: Conversation) => + currentPromptConversationIds.includes(convo.id) + ? selectedSystemPrompt?.id + : convo.apiConfig && convo.apiConfig.defaultSystemPromptId === selectedSystemPrompt?.id + ? // remove the default System Prompt if it is assigned to a conversation + // but that conversation is not in the currentPromptConversationList + // This means conversation was removed in the current transaction + undefined + : // leave it as it is .. if that conversation was neither added nor removed. + convo.apiConfig?.defaultSystemPromptId; + + if (selectedSystemPrompt != null) { + setSystemPromptSettingsUpdates((prev) => + prev.map((sp) => { + if (sp.id === selectedSystemPrompt.id) { + return { + ...sp, + conversations: currentPromptConversations, + }; + } + return sp; + }) + ); + let updatedConversationsSettingsBulkActions = { create: {}, update: {} }; + Object.values(currentPromptConversations).forEach((convo) => { + const getApiConfigWithSelectedPrompt = (): ApiConfig | {} => { + if (convo.apiConfig) { + return { + apiConfig: { + ...convo.apiConfig, + defaultSystemPromptId: getDefaultSystemPromptId(convo), + }, + }; + } + + return { + apiConfig: { + ...getConversationApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation: convo, + defaultConnector, + }).apiConfig, + defaultSystemPromptId: getDefaultSystemPromptId(convo), + }, + }; + }; + // TODO when do we create a conversation from the system prompt editor? + const createOperation = + convo.id === '' + ? { + create: { + ...(updatedConversationsSettingsBulkActions.create ?? {}), + [convo.title]: { + ...convo, + ...(convo.apiConfig ? getApiConfigWithSelectedPrompt() : {}), + }, + }, + } + : {}; + + const updateOperation = + convo.id !== '' + ? { + update: { + ...(updatedConversationsSettingsBulkActions.update ?? {}), + [convo.id]: { + ...getApiConfigWithSelectedPrompt(), + }, + }, + } + : {}; + + updatedConversationsSettingsBulkActions = { + ...updatedConversationsSettingsBulkActions, + ...createOperation, + ...updateOperation, + }; + }); + + cRemovals.forEach((convo) => { + const getApiConfigWithSelectedPrompt = (): ApiConfig | {} => { + if (convo.apiConfig) { + return { + apiConfig: { + ...convo.apiConfig, + defaultSystemPromptId: undefined, + }, + }; + } + + return { + apiConfig: { + ...getConversationApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation: convo, + defaultConnector, + }).apiConfig, + defaultSystemPromptId: undefined, + }, + }; + }; + // TODO when do we create a conversation from the system prompt editor? + const createOperation = + convo.id === '' + ? { + create: { + ...(updatedConversationsSettingsBulkActions.create ?? {}), + [convo.title]: { + ...convo, + ...(convo.apiConfig ? getApiConfigWithSelectedPrompt() : {}), + }, + }, + } + : {}; + + const updateOperation = + convo.id !== '' + ? { + update: { + ...(updatedConversationsSettingsBulkActions.update ?? {}), + [convo.id]: { + ...getApiConfigWithSelectedPrompt(), + }, + }, + } + : {}; + + updatedConversationsSettingsBulkActions = { + ...updatedConversationsSettingsBulkActions, + ...createOperation, + ...updateOperation, + }; + }); + setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); + } + }, + [ + systemPromptSettings, + selectedSystemPrompt, + selectedSystemPromptId, + conversationsSettingsBulkActions, + setConversationsSettingsBulkActions, + defaultConnector, + connectors, + ] + ); + + const resetSystemPromptSettings = useCallback((): void => { + setSystemPromptSettingsUpdates(systemPromptSettings); + setPromptsBulkActions({}); + }, [systemPromptSettings]); + + const saveSystemPromptSettings = useCallback(async (): Promise => { + const hasBulkPrompts = + promptsBulkActions.create || promptsBulkActions.update || promptsBulkActions.delete; + const bulkPromptsResult = hasBulkPrompts + ? // TODO add toasts? + await bulkUpdatePrompts(http, promptsBulkActions, undefined) + : undefined; + return bulkPromptsResult?.success ?? true; + }, [http, promptsBulkActions]); + return { - systemPromptSettings, - setSystemPromptSettings, + onConversationSelectionChange, + onNewConversationDefaultChange, + onPromptContentChange, + onSystemPromptDelete, + onSystemPromptSelect, + refetchSystemPromptConversations: refetch, + resetSystemPromptSettings, + saveSystemPromptSettings, + selectedSystemPrompt, + systemPromptSettings: systemPromptSettingsUpdates, }; }; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts index 3426d8d2a0681..7f4b627fd69ed 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/ai_assistant_data_clients/find.ts @@ -57,7 +57,6 @@ export const findDocuments = async ({ mSearch, }: FindOptions): Promise> => { const query = getQueryFilter({ filter }); - console.log('find docs query:', JSON.stringify(query)); let sort: Sort | undefined; const ascOrDesc = sortOrder ?? ('asc' as const); if (sortField != null) { From 3ae8d7719e2eed84c9b3d59b9fc99ddb16e66942 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 14 Feb 2025 16:39:08 -0700 Subject: [PATCH 14/80] better --- .../system_prompt_editor.tsx | 14 +++-- .../system_prompt_selector.tsx | 29 +++++---- .../index.tsx | 6 +- .../use_system_prompt_updater.ts | 2 +- .../assistant/use_conversation/helpers.ts | 2 +- .../impl/assistant/use_conversation/index.tsx | 15 +---- .../use_current_conversation/index.tsx | 60 ++++++++++++++----- 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx index b89b2e6211a9d..a339afdfc9d1b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx @@ -49,6 +49,10 @@ export const SystemPromptEditorComponent: React.FC = ({ selectedSystemPrompt, systemPromptSettings, }) => { + const disableFields = useMemo( + () => selectedSystemPrompt == null || selectedSystemPrompt.id === '', + [selectedSystemPrompt] + ); // Prompt const promptContent = useMemo( // Fixing Cursor Jump in text area @@ -59,8 +63,8 @@ export const SystemPromptEditorComponent: React.FC = ({ const conversationOptions = useMemo(() => Object.values(conversations), [conversations]); const selectedConversations = useMemo(() => { - return selectedSystemPrompt != null ? selectedSystemPrompt.conversations : []; - }, [selectedSystemPrompt]); + return !disableFields && selectedSystemPrompt ? selectedSystemPrompt.conversations : []; + }, [disableFields, selectedSystemPrompt]); // Whether this system prompt should be the default for new conversations const isNewConversationDefault = useMemo( @@ -82,7 +86,7 @@ export const SystemPromptEditorComponent: React.FC = ({ = ({ > @@ -110,7 +114,7 @@ export const SystemPromptEditorComponent: React.FC = ({ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx index f560877791580..0aa95fe2f7443 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx @@ -53,18 +53,27 @@ export const SystemPromptSelector: React.FC = React.memo( }) => { // Form options const [options, setOptions] = useState( - systemPrompts.map((sp) => ({ - value: { - isDefault: sp.isDefault ?? false, - isNewConversationDefault: sp.isNewConversationDefault ?? false, - }, - label: sp.name, - id: sp.id, - 'data-test-subj': `${TEST_IDS.SYSTEM_PROMPT_SELECTOR}-${sp.id}`, - })) + systemPrompts.reduce( + (acc: SystemPromptSelectorOption[], sp) => + sp.id !== '' + ? [ + ...acc, + { + value: { + isDefault: sp.isDefault ?? false, + isNewConversationDefault: sp.isNewConversationDefault ?? false, + }, + label: sp.name, + id: sp.id, + 'data-test-subj': `${TEST_IDS.SYSTEM_PROMPT_SELECTOR}-${sp.id}`, + }, + ] + : acc, + [] + ) ); const selectedOptions = useMemo(() => { - return selectedSystemPrompt + return selectedSystemPrompt && selectedSystemPrompt.id !== '' ? [ { value: { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 4e219d1fda616..2811207391a8f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -234,7 +234,11 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector onClose={onSaveCancelled} onSaveCancelled={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} - saveButtonDisabled={selectedSystemPrompt?.name == null || selectedSystemPrompt?.name === ''} + saveButtonDisabled={ + selectedSystemPrompt?.name == null || + selectedSystemPrompt?.name === '' || + selectedSystemPrompt?.content === '' + } > { }; /** - * Returns the new default system prompt, fallback to the default system prompt if not found + * Returns the new conversation default system prompt * * @param allSystemPrompts All available System Prompts */ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index daacfc4fbf7c2..f0276025a5e9e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -129,19 +129,8 @@ export const useConversation = (): UseConversation => { const setApiConfig = useCallback( async ({ conversation, apiConfig }: SetApiConfigProps) => { if (conversation.id === '') { - return createConversationApi({ - http, - conversation: { - apiConfig, - category: 'assistant', - title: conversation.title, - replacements: conversation.replacements, - excludeFromLastConversationStorage: conversation.excludeFromLastConversationStorage, - id: '', - messages: conversation.messages ?? [], - }, - toasts, - }); + // only developer should ever see this error + throw new Error('Conversation ID is required to set API config'); } else { return updateConversation({ http, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index 9c22eeaee398e..855b2679938c3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -11,7 +11,7 @@ import { PromptResponse } from '@kbn/elastic-assistant-common'; import { InfiniteData } from '@tanstack/query-core/src/types'; import { FetchConversationsResponse } from '../api'; import { AIConnector } from '../../connectorland/connector_selector'; -import { getDefaultSystemPrompt } from '../use_conversation/helpers'; +import { getDefaultNewSystemPrompt, getDefaultSystemPrompt } from '../use_conversation/helpers'; import { useConversation } from '../use_conversation'; import { sleep } from '../helpers'; import { Conversation } from '../../..'; @@ -56,9 +56,7 @@ export const useCurrentConversation = ({ }: Props): UseCurrentConversation => { const { deleteConversation, getConversation, setApiConfig } = useConversation(); const [currentConversation, setCurrentConversation] = useState(); - /** - * START SYSTEM PROMPT - */ + const currentSystemPrompt = useMemo( () => getDefaultSystemPrompt({ @@ -71,24 +69,40 @@ export const useCurrentConversation = ({ // Write the selected system prompt to the conversation config const setCurrentSystemPromptId = useCallback( async (promptId?: string) => { + if (currentConversation?.id === '') { + return setCurrentConversation({ + ...currentConversation, + apiConfig: currentConversation?.apiConfig + ? { + ...currentConversation?.apiConfig, + defaultSystemPromptId: promptId, + } + : undefined, + id: '', + messages: [], + replacements: {}, + category: 'assistant', + title: '', + }); + } if (currentConversation && currentConversation.apiConfig) { - await setApiConfig({ + const updatedConversation = await setApiConfig({ conversation: currentConversation, apiConfig: { ...currentConversation.apiConfig, defaultSystemPromptId: promptId, }, }); + + if (updatedConversation) { + setCurrentConversation(updatedConversation); + } await refetchCurrentUserConversations(); } }, [currentConversation, refetchCurrentUserConversations, setApiConfig] ); - /** - * END SYSTEM PROMPT - */ - /** * Refetches the current conversation, optionally by conversation ID or title. * @param cId - The conversation ID to refetch. @@ -133,15 +147,29 @@ export const useCurrentConversation = ({ const handleOnConversationSelected = useCallback( async ({ cId }: { cId: string }) => { if (cId === '') { + const apiConfig = defaultConnector + ? { + connectorId: defaultConnector.id ?? '', + actionTypeId: defaultConnector.actionTypeId ?? '', + } + : undefined; + + // Merge apiConfig from currentConversation if it exists + const mergedApiConfig = currentConversation?.apiConfig + ? currentConversation.apiConfig + : apiConfig; + const newConversationDefaultSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts); return setCurrentConversation({ - apiConfig: defaultConnector + ...(mergedApiConfig ? { - connectorId: defaultConnector.id ?? '', - actionTypeId: defaultConnector.actionTypeId ?? '', + apiConfig: { + ...mergedApiConfig, + ...(newConversationDefaultSystemPrompt?.id + ? { defaultSystemPromptId: newConversationDefaultSystemPrompt?.id } + : {}), + }, } - : undefined, - // get apiConfig from currentConversation if it exists - ...(currentConversation ?? {}), + : {}), id: '', messages: [], replacements: {}, @@ -152,7 +180,7 @@ export const useCurrentConversation = ({ // refetch will set the currentConversation await refetchCurrentConversation({ cId }); }, - [currentConversation, defaultConnector, refetchCurrentConversation] + [allSystemPrompts, currentConversation?.apiConfig, defaultConnector, refetchCurrentConversation] ); useEffect(() => { if (!mayUpdateConversations || !!currentConversation) return; From 734395de5ac49f9ad546fafbc7ae9ff592d60062 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 18 Feb 2025 09:05:57 -0700 Subject: [PATCH 15/80] type wrestling --- .../pagination/use_session_pagination.ts | 54 ++++++++----------- .../index.tsx | 15 +++--- .../index.tsx | 8 +-- .../index.tsx | 6 +-- .../context_editor/index.tsx | 25 +++------ 5 files changed, 42 insertions(+), 66 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index 6f14d9dc81050..40f5ed7e1b83f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { Direction } from '@elastic/eui'; +import { CriteriaWithPagination, EuiInMemoryTableProps, EuiTableSortingType } from '@elastic/eui'; import { useCallback, useMemo } from 'react'; import useSessionStorage from 'react-use/lib/useSessionStorage'; import { DEFAULT_ASSISTANT_NAMESPACE } from '../../../../../assistant_context/constants'; import { DEFAULT_PAGE_SIZE } from '../../../../settings/const'; -export const DEFAULT_TABLE_OPTIONS = { +export const getDefaultTableOptions = (sortField: keyof T) => ({ page: { size: DEFAULT_PAGE_SIZE, index: 0 }, - sort: { field: '', direction: 'asc' as const }, -}; + sort: { field: sortField, direction: 'asc' as const }, +}); interface InMemoryPagination { initialPageSize: number; @@ -29,38 +29,27 @@ interface ServerSidePagination { pageIndex: number; } -interface UseSessionPaginationReturn { - onTableChange: ({ - page, - sort, - }: { - page: { size: number; index: number }; - sort: { field: string; direction: Direction }; - }) => void; - pagination: T extends true ? InMemoryPagination : ServerSidePagination; - sorting: { - sort: { field: string; direction: Direction }; - }; +interface UseSessionPaginationReturn { + onTableChange: (criteria: CriteriaWithPagination) => void; + pagination: B extends true ? InMemoryPagination : ServerSidePagination; + sorting: B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType; } -export const useSessionPagination = ({ +export const useSessionPagination = ({ defaultTableOptions, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, - inMemory = true as T, + inMemory = true as B, storageKey, totalItemCount = 0, }: { - defaultTableOptions: { - page: { size: number; index: number }; - sort: { field: string; direction: Direction }; - }; + defaultTableOptions: CriteriaWithPagination; inMemory?: boolean; nameSpace?: string; storageKey: string; totalItemCount?: number; -}): UseSessionPaginationReturn => { +}): UseSessionPaginationReturn => { const [sessionStorageTableOptions = defaultTableOptions, setSessionStorageTableOptions] = - useSessionStorage(`${nameSpace}.${storageKey}`, defaultTableOptions); + useSessionStorage>(`${nameSpace}.${storageKey}`, defaultTableOptions); const pagination = useMemo( () => @@ -75,23 +64,24 @@ export const useSessionPagination = ({ pageSize: sessionStorageTableOptions.page.size ?? DEFAULT_PAGE_SIZE, pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], pageIndex: sessionStorageTableOptions.page.index, - }) as T extends true ? InMemoryPagination : ServerSidePagination, + }) as B extends true ? InMemoryPagination : ServerSidePagination, [inMemory, sessionStorageTableOptions, totalItemCount] ); const sorting = useMemo( - () => ({ - sort: sessionStorageTableOptions.sort, - }), - [sessionStorageTableOptions.sort] + () => + ({ + sort: sessionStorageTableOptions.sort ?? defaultTableOptions.sort, + } as B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType), + [defaultTableOptions.sort, sessionStorageTableOptions.sort] ); - const onTableChange: UseSessionPaginationReturn['onTableChange'] = useCallback( - (args) => { + const onTableChange: UseSessionPaginationReturn['onTableChange'] = useCallback( + (args: CriteriaWithPagination) => { const { page, sort } = args; setSessionStorageTableOptions({ page, - sort, + ...(sort ? { sort } : {}), }); }, [setSessionStorageTableOptions] diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index bfcdafc4dafb5..e698995900901 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -31,8 +31,10 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; -import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; -import { DEFAULT_PAGE_SIZE } from '../../settings/const'; +import { + getDefaultTableOptions, + useSessionPagination, +} from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater'; import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar'; interface Props { @@ -41,11 +43,6 @@ interface Props { isDisabled?: boolean; } -export const DEFAULT_TABLE_OPTIONS = { - page: { size: DEFAULT_PAGE_SIZE, index: 0 }, - sort: { field: 'createdAt', direction: 'desc' as const }, -}; - const ConversationSettingsManagementComponent: React.FC = ({ connectors, defaultConnector, @@ -61,10 +58,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); const [totalItemCount, setTotalItemCount] = useState(5); - const { onTableChange, pagination, sorting } = useSessionPagination({ + const { onTableChange, pagination, sorting } = useSessionPagination({ nameSpace, storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, - defaultTableOptions: DEFAULT_TABLE_OPTIONS, + defaultTableOptions: getDefaultTableOptions('createdAt'), inMemory: false, totalItemCount, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 2811207391a8f..3a38b4ce07832 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -30,7 +30,7 @@ import { useFetchPrompts } from '../../../api'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { - DEFAULT_TABLE_OPTIONS, + getDefaultTableOptions, useSessionPagination, } from '../../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../../settings/translations'; @@ -118,7 +118,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }); param?.callback?.(); }, - [saveConversationsSettings, saveSystemPromptSettings, toasts] + [refetchSystemPromptConversations, saveConversationsSettings, saveSystemPromptSettings, toasts] ); const onCancelClick = useCallback(() => { @@ -187,8 +187,8 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const { getColumns } = useSystemPromptTable(); - const { onTableChange, pagination, sorting } = useSessionPagination({ - defaultTableOptions: DEFAULT_TABLE_OPTIONS, + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: getDefaultTableOptions('createdAt'), nameSpace, storageKey: SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx index 16389b331175d..be2876ea36e4f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx @@ -24,7 +24,7 @@ import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/tra import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; import { - DEFAULT_TABLE_OPTIONS, + getDefaultTableOptions, useSessionPagination, } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; @@ -152,8 +152,8 @@ const QuickPromptSettingsManagementComponent = () => { isEditEnabled: () => true, }); - const { onTableChange, pagination, sorting } = useSessionPagination({ - defaultTableOptions: DEFAULT_TABLE_OPTIONS, + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: getDefaultTableOptions('createdAt'), nameSpace, storageKey: QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx index 2765e548671df..bd614f341178b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx @@ -15,31 +15,20 @@ import { getColumns } from './get_columns'; import { getRows } from './get_rows'; import { Toolbar } from './toolbar'; import * as i18n from './translations'; -import { BatchUpdateListItem, ContextEditorRow, FIELDS, SortConfig } from './types'; +import { BatchUpdateListItem, ContextEditorRow, FIELDS } from './types'; import { useAssistantContext } from '../../assistant_context'; -import { useSessionPagination } from '../../assistant/common/components/assistant_settings_management/pagination/use_session_pagination'; +import { + getDefaultTableOptions, + useSessionPagination, +} from '../../assistant/common/components/assistant_settings_management/pagination/use_session_pagination'; import { ANONYMIZATION_TABLE_SESSION_STORAGE_KEY } from '../../assistant_context/constants'; -const DEFAULT_PAGE_SIZE = 10; - const Wrapper = styled.div` > div > .euiSpacer { block-size: 16px; } `; -const defaultSort: SortConfig = { - sort: { - direction: 'asc', - field: FIELDS.FIELD, - }, -}; - -export const DEFAULT_TABLE_OPTIONS = { - page: { size: DEFAULT_PAGE_SIZE, index: 0 }, - ...defaultSort, -}; - export interface Props { anonymizationFields: FindAnonymizationFieldsResponse; compressed?: boolean; @@ -114,8 +103,8 @@ const ContextEditorComponent: React.FC = ({ setSelection(rows); }, [rows]); - const { onTableChange, pagination, sorting } = useSessionPagination({ - defaultTableOptions: DEFAULT_TABLE_OPTIONS, + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: getDefaultTableOptions('field'), nameSpace, storageKey: ANONYMIZATION_TABLE_SESSION_STORAGE_KEY, }); From adb6708a17b089600f35121664a804d8d25ce86f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 18 Feb 2025 09:40:21 -0700 Subject: [PATCH 16/80] conversations settings table fix --- .../use_fetch_current_user_conversations.ts | 20 +++++++++- .../pagination/use_session_pagination.ts | 17 +++++---- .../index.tsx | 38 ++++++++++++------- .../use_conversations_table.tsx | 4 +- .../index.tsx | 2 +- .../index.tsx | 2 +- 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 6174f3bbc60f4..1f4a3e7c2b61d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -34,6 +34,8 @@ export interface UseFetchCurrentUserConversationsParams { page?: number; perPage?: number; signal?: AbortSignal | undefined; + sortField?: string; + sortOrder?: string; refetchOnWindowFocus?: boolean; isAssistantEnabled: boolean; setTotalItemCount?: (total: number) => void; @@ -80,11 +82,17 @@ export const useFetchCurrentUserConversations = ({ page = query.page, perPage = query.perPage, signal, + sortField = 'updated_at', + sortOrder = 'desc', refetchOnWindowFocus = true, isAssistantEnabled, setTotalItemCount, }: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { + console.log('sorting', { + sortField, + sortOrder, + }); return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { method: 'GET', version: API_VERSIONS.public.v1, @@ -93,6 +101,8 @@ export const useFetchCurrentUserConversations = ({ filter, page: pageParam?.page ?? page, per_page: pageParam?.perPage ?? perPage, + sort_field: sortField, + sort_order: sortOrder, }, signal, }); @@ -111,7 +121,15 @@ export const useFetchCurrentUserConversations = ({ const { data, fetchNextPage, hasNextPage, isFetched, isFetching, isLoading, refetch } = useInfiniteQuery( - [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1, filter], + [ + ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, + page, + perPage, + API_VERSIONS.public.v1, + filter, + sortField, + sortOrder, + ], queryFn, { enabled: isAssistantEnabled, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index 40f5ed7e1b83f..adf78e31f838b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -13,7 +13,7 @@ import { DEFAULT_PAGE_SIZE } from '../../../../settings/const'; export const getDefaultTableOptions = (sortField: keyof T) => ({ page: { size: DEFAULT_PAGE_SIZE, index: 0 }, - sort: { field: sortField, direction: 'asc' as const }, + sort: { field: sortField, direction: 'desc' as const }, }); interface InMemoryPagination { @@ -68,17 +68,18 @@ export const useSessionPagination = ({ [inMemory, sessionStorageTableOptions, totalItemCount] ); - const sorting = useMemo( - () => - ({ - sort: sessionStorageTableOptions.sort ?? defaultTableOptions.sort, - } as B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType), - [defaultTableOptions.sort, sessionStorageTableOptions.sort] - ); + const sorting = useMemo(() => { + console.log('sessionStorage sort', sessionStorageTableOptions.sort); + console.log('defaultTableOptions sort', defaultTableOptions.sort); + return { + sort: sessionStorageTableOptions.sort ?? defaultTableOptions.sort, + } as B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType; + }, [defaultTableOptions.sort, sessionStorageTableOptions.sort]); const onTableChange: UseSessionPaginationReturn['onTableChange'] = useCallback( (args: CriteriaWithPagination) => { const { page, sort } = args; + console.log('setSessionStorageTableOptions sort', sort); setSessionStorageTableOptions({ page, ...(sort ? { sort } : {}), diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index e698995900901..f6f74e686d35d 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -17,6 +17,8 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import { css } from '@emotion/react'; +import { PromptTypeEnum } from '@kbn/elastic-assistant-common'; +import { useConversationsUpdater } from '../../settings/use_settings_updater/use_conversations_updater'; import { Conversation } from '../../../assistant_context/types'; import { ConversationTableItem, useConversationsTable } from './use_conversations_table'; import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch'; @@ -35,14 +37,14 @@ import { getDefaultTableOptions, useSessionPagination, } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; -import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater'; import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar'; interface Props { connectors: AIConnector[] | undefined; defaultConnector?: AIConnector; isDisabled?: boolean; } - +const camelToSnake = (camelStr: string): string => + camelStr.replace(/(? = ({ connectors, defaultConnector, @@ -56,15 +58,21 @@ const ConversationSettingsManagementComponent: React.FC = ({ toasts, } = useAssistantContext(); - const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); + const { data: allPrompts, refetch: refetchPrompts } = useFetchPrompts(); const [totalItemCount, setTotalItemCount] = useState(5); const { onTableChange, pagination, sorting } = useSessionPagination({ nameSpace, storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, - defaultTableOptions: getDefaultTableOptions('createdAt'), + defaultTableOptions: getDefaultTableOptions('updatedAt'), inMemory: false, totalItemCount, }); + + const allSystemPrompts = useMemo( + () => allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system), + [allPrompts.data] + ); + const { data: conversations, isFetched: conversationsLoaded, @@ -76,6 +84,11 @@ const ConversationSettingsManagementComponent: React.FC = ({ page: pagination.pageIndex + 1, perPage: pagination.pageSize, setTotalItemCount, + ...(sorting?.sort?.field + ? // @ts-ignore field can be Title field column gets entire conversation and is labeled Title + { sortField: sorting.sort.field === 'Title' ? 'title' : camelToSnake(sorting.sort.field) } + : {}), + ...(sorting?.sort?.direction ? { sortOrder: sorting.sort.direction } : {}), }); const refetchAll = useCallback(() => { @@ -84,21 +97,20 @@ const ConversationSettingsManagementComponent: React.FC = ({ }, [refetchPrompts, refetchConversations]); const { - systemPromptSettings: allSystemPrompts, assistantStreamingEnabled, conversationsSettingsBulkActions, - resetSettings, - saveSettings, + resetConversationsSettings, + saveConversationsSettings, setConversationSettings, setConversationsSettingsBulkActions, setUpdatedAssistantStreamingEnabled, - } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded); + } = useConversationsUpdater(conversations, conversationsLoaded); const [hasPendingChanges, setHasPendingChanges] = useState(false); const handleSave = useCallback( async (param?: { callback?: () => void }) => { - const isSuccess = await saveSettings(); + const isSuccess = await saveConversationsSettings(); if (isSuccess) { toasts?.addSuccess({ iconType: 'check', @@ -107,10 +119,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ setHasPendingChanges(false); param?.callback?.(); } else { - resetSettings(); + resetConversationsSettings(); } }, - [resetSettings, saveSettings, toasts] + [resetConversationsSettings, saveConversationsSettings, toasts] ); const setAssistantStreamingEnabled = useCallback( @@ -127,9 +139,9 @@ const ConversationSettingsManagementComponent: React.FC = ({ }, [handleSave, refetchAll]); const onCancelClick = useCallback(() => { - resetSettings(); + resetConversationsSettings(); setHasPendingChanges(false); - }, [resetSettings]); + }, [resetConversationsSettings]); // Local state for saving previously selected items so tab switching is friendlier // Conversation Selection State diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx index 001bdfd559003..2530befdc4477 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx @@ -64,7 +64,7 @@ export const useConversationsTable = () => { align: 'left', render: (systemPromptTitle: ConversationTableItem['systemPromptTitle']) => systemPromptTitle ? {systemPromptTitle} : null, - sortable: true, + sortable: false, }, { field: 'connectorTypeTitle', @@ -72,7 +72,7 @@ export const useConversationsTable = () => { align: 'left', render: (connectorTypeTitle: ConversationTableItem['connectorTypeTitle']) => connectorTypeTitle ? {connectorTypeTitle} : null, - sortable: true, + sortable: false, }, { field: 'updatedAt', diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 3a38b4ce07832..5d2341eaed846 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -188,7 +188,7 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector const { getColumns } = useSystemPromptTable(); const { onTableChange, pagination, sorting } = useSessionPagination({ - defaultTableOptions: getDefaultTableOptions('createdAt'), + defaultTableOptions: getDefaultTableOptions('updatedAt'), nameSpace, storageKey: SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY, }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx index be2876ea36e4f..138a87ef8e912 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx @@ -153,7 +153,7 @@ const QuickPromptSettingsManagementComponent = () => { }); const { onTableChange, pagination, sorting } = useSessionPagination({ - defaultTableOptions: getDefaultTableOptions('createdAt'), + defaultTableOptions: getDefaultTableOptions('updatedAt'), nameSpace, storageKey: QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY, }); From 46477973d5b7bed6681375f7471e5ae9d5efb248 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 18 Feb 2025 12:50:28 -0700 Subject: [PATCH 17/80] quick prompt management cleanup --- .../badges/index.tsx | 5 +- .../pagination/use_session_pagination.ts | 13 +- .../index.tsx | 9 +- .../use_system_prompt_table.tsx | 2 +- .../quick_prompt_editor.tsx | 232 ++----------- .../index.tsx | 91 ++--- .../use_quick_prompt_updater.ts | 316 ++++++++++++++++++ 7 files changed, 382 insertions(+), 286 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_quick_prompt_updater.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx index 69fe1c775d210..b84e5225c4d6f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx @@ -7,16 +7,15 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; -import { Conversation } from '../../../../../..'; export const BadgesColumn: React.FC<{ - items: Conversation[] | null | undefined; + items: string[] | null | undefined; prefix: string; color?: string; }> = React.memo(({ items, prefix, color = 'hollow' }) => items && items.length > 0 ? (
- {items.map(({ title }, idx) => ( + {items.map((title, idx) => ( {title} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts index adf78e31f838b..c1e010160c4f2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -35,15 +35,15 @@ interface UseSessionPaginationReturn { sorting: B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType; } -export const useSessionPagination = ({ +export const useSessionPagination = ({ defaultTableOptions, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, - inMemory = true as B, + inMemory, storageKey, totalItemCount = 0, }: { defaultTableOptions: CriteriaWithPagination; - inMemory?: boolean; + inMemory?: B; nameSpace?: string; storageKey: string; totalItemCount?: number; @@ -64,22 +64,19 @@ export const useSessionPagination = ({ pageSize: sessionStorageTableOptions.page.size ?? DEFAULT_PAGE_SIZE, pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], pageIndex: sessionStorageTableOptions.page.index, - }) as B extends true ? InMemoryPagination : ServerSidePagination, + }) as UseSessionPaginationReturn['pagination'], [inMemory, sessionStorageTableOptions, totalItemCount] ); const sorting = useMemo(() => { - console.log('sessionStorage sort', sessionStorageTableOptions.sort); - console.log('defaultTableOptions sort', defaultTableOptions.sort); return { sort: sessionStorageTableOptions.sort ?? defaultTableOptions.sort, - } as B extends true ? EuiInMemoryTableProps['sorting'] : EuiTableSortingType; + } as UseSessionPaginationReturn['sorting']; }, [defaultTableOptions.sort, sessionStorageTableOptions.sort]); const onTableChange: UseSessionPaginationReturn['onTableChange'] = useCallback( (args: CriteriaWithPagination) => { const { page, sort } = args; - console.log('setSessionStorageTableOptions sort', sort); setSessionStorageTableOptions({ page, ...(sort ? { sort } : {}), diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index 5d2341eaed846..be88b7cd576fc 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -127,13 +127,8 @@ const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }, [resetConversationsSettings, resetSystemPromptSettings]); const onCreate = useCallback(() => { - onSystemPromptSelect({ - id: '', - content: '', - name: '', - promptType: 'system', - conversations: [], - }); + // TODO test this change + onSystemPromptSelect(''); openFlyout(); }, [onSystemPromptSelect, openFlyout]); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx index ab3bb08ccda7d..4111b6002f1a7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx @@ -53,7 +53,7 @@ export const useSystemPromptTable = () => { align: 'left', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, render: ({ conversations, id }: SystemPromptSettings) => ( - + title)} prefix={id} /> ), }, { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx index f9705cedf2afb..88ead200d4b25 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -10,36 +10,34 @@ import { EuiFormRow, EuiColorPicker, EuiTextArea } from '@elastic/eui'; import { EuiSetColorMethod } from '@elastic/eui/src/services/color_picker/color_picker'; import { css } from '@emotion/react'; -import { - PromptResponse, - PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody, -} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; -import { getRandomEuiColor } from './helpers'; +import { PromptResponse } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; import { PromptContextTemplate } from '../../../..'; +import { getRandomEuiColor } from './helpers'; import * as i18n from './translations'; import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector'; import { PromptContextSelector } from '../prompt_context_selector/prompt_context_selector'; import { useAssistantContext } from '../../../assistant_context'; -import { useQuickPromptEditor } from './use_quick_prompt_editor'; interface Props { - onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void; - quickPromptSettings: PromptResponse[]; + onPromptContentChange: (e: React.ChangeEvent) => void; + onQuickPromptColorChange: EuiSetColorMethod; + onQuickPromptContextChange: (promptContexts: PromptContextTemplate[]) => void; + onQuickPromptDelete: (id: string) => void; + onQuickPromptSelect: (quickPrompt?: PromptResponse | string) => void; resetSettings?: () => void; selectedQuickPrompt: PromptResponse | undefined; - setUpdatedQuickPromptSettings: React.Dispatch>; - promptsBulkActions: PromptsPerformBulkActionRequestBody; - setPromptsBulkActions: React.Dispatch>; + quickPromptSettings: PromptResponse[]; } const QuickPromptSettingsEditorComponent = ({ - onSelectedQuickPromptChange, - quickPromptSettings, + onPromptContentChange, + onQuickPromptColorChange, + onQuickPromptContextChange, + onQuickPromptDelete, + onQuickPromptSelect, resetSettings, selectedQuickPrompt, - setUpdatedQuickPromptSettings, - promptsBulkActions, - setPromptsBulkActions, + quickPromptSettings, }: Props) => { const { basePromptContexts } = useAssistantContext(); @@ -50,131 +48,11 @@ const QuickPromptSettingsEditorComponent = ({ [selectedQuickPrompt?.id, quickPromptSettings] ); - const handlePromptChange = useCallback( - (e: React.ChangeEvent) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev): PromptResponse[] => { - const alreadyExists = prev.some((qp) => qp.id === selectedQuickPrompt.id); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.id === selectedQuickPrompt.id) { - return { - ...qp, - content: e.target.value, - }; - } - return qp; - }); - } - - return prev; - }); - - const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id); - if (existingPrompt) { - setPromptsBulkActions({ - ...promptsBulkActions, - ...(selectedQuickPrompt.name !== selectedQuickPrompt.id - ? { - update: [ - ...(promptsBulkActions.update ?? []).filter( - (p) => p.id !== selectedQuickPrompt.id - ), - { - ...selectedQuickPrompt, - content: e.target.value, - }, - ], - } - : { - create: [ - ...(promptsBulkActions.create ?? []).filter( - (p) => p.name !== selectedQuickPrompt.name - ), - { - ...selectedQuickPrompt, - content: e.target.value, - }, - ], - }), - }); - } - } - }, - [ - promptsBulkActions, - quickPromptSettings, - selectedQuickPrompt, - setPromptsBulkActions, - setUpdatedQuickPromptSettings, - ] - ); - - const handleColorChange = useCallback( - (color) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.name === selectedQuickPrompt.name) { - return { - ...qp, - color, - }; - } - return qp; - }); - } - return prev; - }); - const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id); - if (existingPrompt) { - setPromptsBulkActions({ - ...promptsBulkActions, - ...(selectedQuickPrompt.name !== selectedQuickPrompt.id - ? { - update: [ - ...(promptsBulkActions.update ?? []).filter( - (p) => p.id !== selectedQuickPrompt.id - ), - { - ...selectedQuickPrompt, - color, - }, - ], - } - : { - create: [ - ...(promptsBulkActions.create ?? []).filter( - (p) => p.name !== selectedQuickPrompt.name - ), - { - ...selectedQuickPrompt, - color, - }, - ], - }), - }); - } - } - }, - [ - promptsBulkActions, - quickPromptSettings, - selectedQuickPrompt, - setPromptsBulkActions, - setUpdatedQuickPromptSettings, - ] - ); - const setDefaultPromptColor = useCallback((): string => { const randomColor = getRandomEuiColor(); - handleColorChange(randomColor, { hex: randomColor, isValid: true }); + onQuickPromptColorChange(randomColor, { hex: randomColor, isValid: true }); return randomColor; - }, [handleColorChange]); + }, [onQuickPromptColorChange]); // Color const selectedColor = useMemo( @@ -190,80 +68,12 @@ const QuickPromptSettingsEditorComponent = ({ [basePromptContexts, selectedQuickPrompt?.categories] ); - const onPromptContextSelectionChange = useCallback( - (pc: PromptContextTemplate[]) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.name === selectedQuickPrompt.name) { - return { - ...qp, - categories: pc.map((p) => p.category), - }; - } - return qp; - }); - } - return prev; - }); - - const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id); - if (existingPrompt) { - setPromptsBulkActions({ - ...promptsBulkActions, - ...(selectedQuickPrompt.name !== selectedQuickPrompt.id - ? { - update: [ - ...(promptsBulkActions.update ?? []).filter( - (p) => p.id !== selectedQuickPrompt.id - ), - { - ...selectedQuickPrompt, - categories: pc.map((p) => p.category), - }, - ], - } - : { - create: [ - ...(promptsBulkActions.create ?? []).filter( - (p) => p.name !== selectedQuickPrompt.name - ), - { - ...selectedQuickPrompt, - categories: pc.map((p) => p.category), - }, - ], - }), - }); - } - } - }, - [ - promptsBulkActions, - quickPromptSettings, - selectedQuickPrompt, - setPromptsBulkActions, - setUpdatedQuickPromptSettings, - ] - ); - - // When top level quick prompt selection changes - const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({ - onSelectedQuickPromptChange, - setUpdatedQuickPromptSettings, - promptsBulkActions, - setPromptsBulkActions, - }); - return ( <> @@ -305,7 +115,7 @@ const QuickPromptSettingsEditorComponent = ({ color={selectedColor} compressed disabled={selectedQuickPrompt == null} - onChange={handleColorChange} + onChange={onQuickPromptColorChange} /> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx index 138a87ef8e912..bcde300173193 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiButton, EuiConfirmModal, @@ -16,12 +16,12 @@ import { EuiText, } from '@elastic/eui'; import { PromptResponse } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; +import { useQuickPromptUpdater } from '../../settings/use_settings_updater/use_quick_prompt_updater'; import { QuickPromptSettingsEditor } from '../quick_prompt_settings/quick_prompt_editor'; import * as i18n from './translations'; import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { Flyout } from '../../common/components/assistant_settings_management/flyout'; import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations'; -import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; import { getDefaultTableOptions, @@ -29,58 +29,45 @@ import { } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; import { useAssistantContext } from '../../../assistant_context'; -import { - DEFAULT_CONVERSATIONS, - useSettingsUpdater, -} from '../../settings/use_settings_updater/use_settings_updater'; import { useFetchPrompts } from '../../api'; const QuickPromptSettingsManagementComponent = () => { - const { nameSpace, basePromptContexts, toasts } = useAssistantContext(); + const { currentAppId, http, nameSpace, basePromptContexts, toasts } = useAssistantContext(); const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); const { - promptsBulkActions, + onPromptContentChange, + onQuickPromptColorChange, + onQuickPromptContextChange, + onQuickPromptDelete, + onQuickPromptSelect, quickPromptSettings, - resetSettings, - saveSettings, - setPromptsBulkActions, - setUpdatedQuickPromptSettings, - } = useSettingsUpdater( - DEFAULT_CONVERSATIONS, // Quick Prompt settings do not require conversations + resetQuickPromptSettings, + saveQuickPromptSettings, + selectedQuickPrompt, + } = useQuickPromptUpdater({ allPrompts, - false, // Quick Prompt settings do not require conversations - promptsLoaded - ); - - // Quick Prompt Selection State - const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); - const onSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => { - setSelectedQuickPrompt(quickPrompt); - }, []); - - useEffect(() => { - if (selectedQuickPrompt != null) { - setSelectedQuickPrompt(quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name)); - } - }, [quickPromptSettings, selectedQuickPrompt]); + currentAppId, + http, + promptsLoaded, + }); const handleSave = useCallback( async (param?: { callback?: () => void }) => { - await saveSettings(); + await saveQuickPromptSettings(); toasts?.addSuccess({ iconType: 'check', title: SETTINGS_UPDATED_TOAST_TITLE, }); param?.callback?.(); }, - [saveSettings, toasts] + [saveQuickPromptSettings, toasts] ); const onCancelClick = useCallback(() => { - resetSettings(); - }, [resetSettings]); + resetQuickPromptSettings(); + }, [resetQuickPromptSettings]); const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); @@ -90,28 +77,21 @@ const QuickPromptSettingsManagementComponent = () => { closeFlyout: closeConfirmModal, } = useFlyoutModalVisibility(); - const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({ - onSelectedQuickPromptChange, - setUpdatedQuickPromptSettings, - promptsBulkActions, - setPromptsBulkActions, - }); - const onEditActionClicked = useCallback( (prompt: PromptResponse) => { - onQuickPromptSelectionChange(prompt); + onQuickPromptSelect(prompt); openFlyout(); }, - [onQuickPromptSelectionChange, openFlyout] + [onQuickPromptSelect, openFlyout] ); const onDeleteActionClicked = useCallback( (prompt: PromptResponse) => { setDeletedQuickPrompt(prompt); - onQuickPromptDeleted(prompt.id); + onQuickPromptDelete(prompt.id); openConfirmModal(); }, - [onQuickPromptDeleted, openConfirmModal] + [onQuickPromptDelete, openConfirmModal] ); const onDeleteCancelled = useCallback(() => { @@ -126,21 +106,19 @@ const QuickPromptSettingsManagementComponent = () => { }, [closeConfirmModal, handleSave, refetchPrompts]); const onCreate = useCallback(() => { - onSelectedQuickPromptChange(); + onQuickPromptSelect(''); openFlyout(); - }, [onSelectedQuickPromptChange, openFlyout]); + }, [onQuickPromptSelect, openFlyout]); const onSaveCancelled = useCallback(() => { - onSelectedQuickPromptChange(); closeFlyout(); onCancelClick(); - }, [closeFlyout, onSelectedQuickPromptChange, onCancelClick]); + }, [closeFlyout, onCancelClick]); const onSaveConfirmed = useCallback(() => { handleSave({ callback: refetchPrompts }); - onSelectedQuickPromptChange(); closeFlyout(); - }, [closeFlyout, handleSave, onSelectedQuickPromptChange, refetchPrompts]); + }, [closeFlyout, handleSave, refetchPrompts]); const { getColumns } = useQuickPromptTable(); const columns = getColumns({ @@ -197,13 +175,14 @@ const QuickPromptSettingsManagementComponent = () => { saveButtonDisabled={selectedQuickPrompt?.name == null || selectedQuickPrompt?.name === ''} > {deleteConfirmModalVisibility && deletedQuickPrompt && ( diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_quick_prompt_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_quick_prompt_updater.ts new file mode 100644 index 0000000000000..2dd9978480cfc --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_quick_prompt_updater.ts @@ -0,0 +1,316 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { FindPromptsResponse, PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common'; +import { PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; +import { HttpSetup } from '@kbn/core-http-browser'; +import { EuiSetColorMethod } from '@elastic/eui/src/services/color_picker/color_picker'; +import { getRandomEuiColor } from '../../quick_prompts/quick_prompt_settings/helpers'; +import { bulkUpdatePrompts, PromptContextTemplate } from '../../../..'; + +interface Params { + allPrompts: FindPromptsResponse; + currentAppId: string; + http: HttpSetup; + promptsLoaded: boolean; +} +interface QuickPromptUpdater { + onPromptContentChange: (e: React.ChangeEvent) => void; + onQuickPromptColorChange: EuiSetColorMethod; + onQuickPromptContextChange: (promptContexts: PromptContextTemplate[]) => void; + onQuickPromptDelete: (id: string) => void; + onQuickPromptSelect: (quickPrompt?: PromptResponse | string) => void; + quickPromptSettings: PromptResponse[]; + resetQuickPromptSettings: () => void; + saveQuickPromptSettings: () => Promise; + selectedQuickPrompt?: PromptResponse; +} + +export const useQuickPromptUpdater = ({ + allPrompts, + currentAppId, + http, + promptsLoaded, +}: Params): QuickPromptUpdater => { + const [promptsBulkActions, setPromptsBulkActions] = useState( + {} + ); + const [selectedQuickPromptId, setSelectedQuickPromptId] = useState(); + const [quickPromptSettings, setUpdatedQuickPromptSettings] = useState( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick) + ); + + const selectedQuickPrompt: PromptResponse | undefined = useMemo(() => { + const sip = quickPromptSettings.find((qp) => qp.id === selectedQuickPromptId); + return sip; + }, [quickPromptSettings, selectedQuickPromptId]); + + useEffect(() => { + // Update quick prompts settings when prompts are loaded + if (promptsLoaded) { + setUpdatedQuickPromptSettings( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick) + ); + } + }, [allPrompts.data, promptsLoaded]); + + const onQuickPromptSelect = useCallback( + (quickPrompt?: PromptResponse | string, color?: string) => { + if (quickPrompt == null) { + return setSelectedQuickPromptId(undefined); + } + const isNew = typeof quickPrompt === 'string'; + const qpColor = color ? color : isNew ? getRandomEuiColor() : quickPrompt.color; + const newSelectedQuickPrompt: PromptResponse | undefined = isNew + ? { + name: quickPrompt, + id: quickPrompt, + content: '', + color: qpColor, + categories: [], + promptType: PromptTypeEnum.quick, + consumer: currentAppId, + } + : quickPrompt; + + setUpdatedQuickPromptSettings((prev) => + !prev.some((sp) => sp.id === newSelectedQuickPrompt.id) + ? [...prev, newSelectedQuickPrompt] + : prev + ); + + if (isNew) { + setPromptsBulkActions((prev) => { + const newBulkActions = { + ...prev, + create: [ + ...(promptsBulkActions.create ?? []), + { + ...newSelectedQuickPrompt, + }, + ], + }; + return newBulkActions; + }); + } + + setSelectedQuickPromptId(newSelectedQuickPrompt.id); + }, + [currentAppId, promptsBulkActions.create] + ); + + const onPromptContentChange = useCallback( + (e: React.ChangeEvent) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev): PromptResponse[] => + prev.map((sp): PromptResponse => { + if (sp.id === selectedQuickPrompt.id) { + return { + ...sp, + content: e.target.value, + }; + } + return sp; + }) + ); + const existingPrompt = quickPromptSettings.find((qp) => qp.id === selectedQuickPrompt.id); + if (existingPrompt) { + const newBulkActions = { + ...promptsBulkActions, + ...(selectedQuickPrompt.id !== '' + ? { + update: [ + ...(promptsBulkActions.update ?? []).filter( + (p) => p.id !== selectedQuickPrompt.id + ), + { + ...selectedQuickPrompt, + content: e.target.value, + }, + ], + } + : { + create: [ + ...(promptsBulkActions.create ?? []).filter( + (p) => p.name !== selectedQuickPrompt.name + ), + { + ...selectedQuickPrompt, + content: e.target.value, + }, + ], + }), + }; + setPromptsBulkActions(newBulkActions); + } + } + }, + [promptsBulkActions, selectedQuickPrompt, quickPromptSettings] + ); + + const onQuickPromptContextChange = useCallback( + (pc: PromptContextTemplate[]) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name); + + if (alreadyExists) { + return prev.map((qp) => { + if (qp.name === selectedQuickPrompt.name) { + return { + ...qp, + categories: pc.map((p) => p.category), + }; + } + return qp; + }); + } + return prev; + }); + + const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id); + if (existingPrompt) { + setPromptsBulkActions({ + ...promptsBulkActions, + ...(selectedQuickPrompt.name !== selectedQuickPrompt.id + ? { + update: [ + ...(promptsBulkActions.update ?? []).filter( + (p) => p.id !== selectedQuickPrompt.id + ), + { + ...selectedQuickPrompt, + categories: pc.map((p) => p.category), + }, + ], + } + : { + create: [ + ...(promptsBulkActions.create ?? []).filter( + (p) => p.name !== selectedQuickPrompt.name + ), + { + ...selectedQuickPrompt, + categories: pc.map((p) => p.category), + }, + ], + }), + }); + } + } + }, + [ + promptsBulkActions, + quickPromptSettings, + selectedQuickPrompt, + setPromptsBulkActions, + setUpdatedQuickPromptSettings, + ] + ); + + const onQuickPromptDelete = useCallback( + (id: string) => { + setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.id !== id)); + setPromptsBulkActions({ + ...promptsBulkActions, + delete: { + ids: [...(promptsBulkActions.delete?.ids ?? []), id], + }, + }); + }, + [promptsBulkActions, setPromptsBulkActions] + ); + + const onQuickPromptColorChange = useCallback( + (color) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name); + + if (alreadyExists) { + return prev.map((qp) => { + if (qp.name === selectedQuickPrompt.name) { + return { + ...qp, + color, + }; + } + return qp; + }); + } + return prev; + }); + const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id); + if (existingPrompt) { + setPromptsBulkActions({ + ...promptsBulkActions, + ...(selectedQuickPrompt.name !== selectedQuickPrompt.id + ? { + update: [ + ...(promptsBulkActions.update ?? []).filter( + (p) => p.id !== selectedQuickPrompt.id + ), + { + ...selectedQuickPrompt, + color, + }, + ], + } + : { + create: [ + ...(promptsBulkActions.create ?? []).filter( + (p) => p.name !== selectedQuickPrompt.name + ), + { + ...selectedQuickPrompt, + color, + }, + ], + }), + }); + } + } + }, + [ + promptsBulkActions, + quickPromptSettings, + selectedQuickPrompt, + setPromptsBulkActions, + setUpdatedQuickPromptSettings, + ] + ); + + const resetQuickPromptSettings = useCallback((): void => { + setPromptsBulkActions({}); + setUpdatedQuickPromptSettings( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick) + ); + }, [allPrompts]); + + const saveQuickPromptSettings = useCallback(async (): Promise => { + const hasBulkPrompts = + promptsBulkActions.create || promptsBulkActions.update || promptsBulkActions.delete; + const bulkPromptsResult = hasBulkPrompts + ? // TODO add toasts? + await bulkUpdatePrompts(http, promptsBulkActions, undefined) + : undefined; + return bulkPromptsResult?.success ?? true; + }, [http, promptsBulkActions]); + + return { + onPromptContentChange, + onQuickPromptColorChange, + onQuickPromptContextChange, + onQuickPromptDelete, + onQuickPromptSelect, + quickPromptSettings, + resetQuickPromptSettings, + saveQuickPromptSettings, + selectedQuickPrompt, + }; +}; From 6a0ff137ede0f131d439922da25395a134e5f3ae Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 18 Feb 2025 13:04:53 -0700 Subject: [PATCH 18/80] kb settings --- .../use_knowledge_base_updater.ts | 52 +++++++++++++++++++ .../impl/assistant_context/types.tsx | 5 +- .../index.tsx | 34 ++++++------ 3 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_knowledge_base_updater.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_knowledge_base_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_knowledge_base_updater.ts new file mode 100644 index 0000000000000..6c43b0f28b83b --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_knowledge_base_updater.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useState } from 'react'; +import { AssistantTelemetry } from '../../../..'; +import type { KnowledgeBaseConfig } from '../../types'; + +interface Params { + assistantTelemetry?: AssistantTelemetry; + setKnowledgeBase: React.Dispatch>; + knowledgeBase: KnowledgeBaseConfig; +} +interface KnowledgeBaseUpdater { + knowledgeBaseSettings: KnowledgeBaseConfig; + resetKnowledgeBaseSettings: () => void; + saveKnowledgeBaseSettings: () => boolean; + setUpdatedKnowledgeBaseSettings: React.Dispatch>; +} + +export const useKnowledgeBaseUpdater = ({ + assistantTelemetry, + knowledgeBase, + setKnowledgeBase, +}: Params): KnowledgeBaseUpdater => { + const [knowledgeBaseSettings, setUpdatedKnowledgeBaseSettings] = + useState(knowledgeBase); + + const resetKnowledgeBaseSettings = useCallback((): void => { + setUpdatedKnowledgeBaseSettings(knowledgeBase); + }, [knowledgeBase]); + + const saveKnowledgeBaseSettings = useCallback(() => { + const didUpdateAlertsCount = knowledgeBase.latestAlerts !== knowledgeBaseSettings.latestAlerts; + if (didUpdateAlertsCount) { + assistantTelemetry?.reportAssistantSettingToggled({ + alertsCountUpdated: didUpdateAlertsCount, + }); + } + setKnowledgeBase(knowledgeBaseSettings); + return true; + }, [assistantTelemetry, knowledgeBase.latestAlerts, knowledgeBaseSettings, setKnowledgeBase]); + return { + knowledgeBaseSettings, + resetKnowledgeBaseSettings, + saveKnowledgeBaseSettings, + setUpdatedKnowledgeBaseSettings, + }; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index 003305ff61082..0f1532734bf05 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -53,7 +53,10 @@ export interface AssistantTelemetry { isEnabledKnowledgeBase: boolean; }) => void; reportAssistantQuickPrompt: (params: { promptTitle: string }) => void; - reportAssistantSettingToggled: (params: { assistantStreamingEnabled?: boolean }) => void; + reportAssistantSettingToggled: (params: { + assistantStreamingEnabled?: boolean; + alertsCountUpdated?: boolean; + }) => void; } export interface AssistantAvailability { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx index 72a4b487794d6..1fea716041ecb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx @@ -32,6 +32,7 @@ import { css } from '@emotion/react'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import useAsync from 'react-use/lib/useAsync'; import { useSearchParams } from 'react-router-dom-v5-compat'; +import { useKnowledgeBaseUpdater } from '../../assistant/settings/use_settings_updater/use_knowledge_base_updater'; import { ProductDocumentationManagement } from '../../assistant/settings/product_documentation'; import { KnowledgeBaseTour } from '../../tour/knowledge_base'; import { AlertsSettingsManagement } from '../../assistant/settings/alerts_settings/alerts_settings_management'; @@ -39,11 +40,6 @@ import { useKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entr import { useAssistantContext } from '../../assistant_context'; import { useKnowledgeBaseTable } from './use_knowledge_base_table'; import { AssistantSettingsBottomBar } from '../../assistant/settings/assistant_settings_bottom_bar'; -import { - useSettingsUpdater, - DEFAULT_CONVERSATIONS, - DEFAULT_PROMPTS, -} from '../../assistant/settings/use_settings_updater/use_settings_updater'; import { AddEntryButton } from './add_entry_button'; import * as i18n from './translations'; import { Flyout } from '../../assistant/common/components/assistant_settings_management/flyout'; @@ -75,7 +71,10 @@ interface Params { export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ dataViews }) => { const { assistantAvailability: { hasManageGlobalKnowledgeBase, isAssistantEnabled }, + assistantTelemetry, http, + knowledgeBase, + setKnowledgeBase, toasts, } = useAssistantContext(); const [hasPendingChanges, setHasPendingChanges] = useState(false); @@ -99,13 +98,12 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ d ); // Only needed for legacy settings management - const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } = - useSettingsUpdater( - DEFAULT_CONVERSATIONS, // Knowledge Base settings do not require conversations - DEFAULT_PROMPTS, // Knowledge Base settings do not require prompts - false, // Knowledge Base settings do not require conversations - false // Knowledge Base settings do not require prompts - ); + const { + knowledgeBaseSettings, + resetKnowledgeBaseSettings, + saveKnowledgeBaseSettings, + setUpdatedKnowledgeBaseSettings, + } = useKnowledgeBaseUpdater({ assistantTelemetry, knowledgeBase, setKnowledgeBase }); const handleUpdateKnowledgeBaseSettings = useCallback< React.Dispatch> @@ -118,8 +116,8 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ d ); const handleSave = useCallback( - async (param?: { callback?: () => void }) => { - await saveSettings(); + (param?: { callback?: () => void }) => { + saveKnowledgeBaseSettings(); toasts?.addSuccess({ iconType: 'check', title: SETTINGS_UPDATED_TOAST_TITLE, @@ -127,13 +125,13 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ d setHasPendingChanges(false); param?.callback?.(); }, - [saveSettings, toasts] + [saveKnowledgeBaseSettings, toasts] ); const onCancelClick = useCallback(() => { - resetSettings(); + resetKnowledgeBaseSettings(); setHasPendingChanges(false); - }, [resetSettings]); + }, [resetKnowledgeBaseSettings]); const onSaveButtonClicked = useCallback(() => { handleSave(); @@ -396,7 +394,7 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ d Date: Tue, 18 Feb 2025 16:53:33 -0700 Subject: [PATCH 19/80] attack discovery invoke --- .../use_fetch_current_user_conversations.ts | 4 ---- .../kbn-elastic-assistant/impl/assistant/index.tsx | 6 ++++-- .../impl/assistant/use_assistant_overlay/index.tsx | 9 +++++---- .../impl/assistant/use_current_conversation/index.tsx | 5 ----- .../persistence/transforms/transforms.ts | 1 + 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 1f4a3e7c2b61d..9c43dd604b05e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -89,10 +89,6 @@ export const useFetchCurrentUserConversations = ({ setTotalItemCount, }: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { - console.log('sorting', { - sortField, - sortOrder, - }); return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { method: 'GET', version: API_VERSIONS.public.v1, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index bff79bed64f30..f2ec7048fcabd 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -308,7 +308,10 @@ const AssistantComponent: React.FC = ({ useEffect(() => { // Adding `conversationTitle !== selectedConversationTitle` to prevent auto-run still executing after changing selected conversation - if (currentConversation?.messages.length || conversationTitle !== currentConversation?.title) { + if ( + currentConversation?.messages.length || + (currentConversation && conversationTitle !== currentConversation?.title) + ) { return; } @@ -332,7 +335,6 @@ const AssistantComponent: React.FC = ({ anonymizationFields, promptContext, }); - setSelectedPromptContexts((prev) => ({ ...prev, [promptContext.id]: newSelectedPromptContext, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index 30c56cc2635b0..955a118954c89 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -94,7 +94,8 @@ export const useAssistantOverlay = ( const { data: conversations, isLoading } = useFetchCurrentUserConversations({ http, - isAssistantEnabled, + filter: `title:${conversationTitle}`, + isAssistantEnabled: conversationTitle != null ? isAssistantEnabled : false, }); // memoize the props so that we can use them in the effect below: const _category: PromptContext['category'] = useMemo(() => category, [category]); @@ -128,8 +129,8 @@ export const useAssistantOverlay = ( // non-default conversations that may need to be initialized async (showOverlay: boolean, shouldCreateConversation: boolean = false) => { if (promptContextId != null) { + let conversation; if (shouldCreateConversation) { - let conversation; if (!isLoading) { conversation = conversationTitle ? Object.values(conversations).find((conv) => conv.title === conversationTitle) @@ -138,7 +139,7 @@ export const useAssistantOverlay = ( if (isAssistantEnabled && !conversation && defaultConnector && !isLoading) { try { - await createConversation({ + conversation = await createConversation({ apiConfig: { ...apiConfig, actionTypeId: defaultConnector?.actionTypeId, @@ -155,7 +156,7 @@ export const useAssistantOverlay = ( assistantContextShowOverlay({ showOverlay, promptContextId, - conversationTitle: conversationTitle ?? undefined, + conversationTitle: conversation?.id ?? conversationTitle ?? undefined, }); } }, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index 855b2679938c3..c19030ff5a4d8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -78,11 +78,6 @@ export const useCurrentConversation = ({ defaultSystemPromptId: promptId, } : undefined, - id: '', - messages: [], - replacements: {}, - category: 'assistant', - title: '', }); } if (currentConversation && currentConversation.apiConfig) { diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts index 765d40f7a3226..4bcdb8a96f24e 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/attack_discovery/persistence/transforms/transforms.ts @@ -42,6 +42,7 @@ export const transformESSearchToAttackDiscovery = ( }, attackDiscoveries: adSchema.attack_discoveries.map((attackDiscovery) => ({ alertIds: attackDiscovery.alert_ids, + id: attackDiscovery.id, title: attackDiscovery.title, detailsMarkdown: attackDiscovery.details_markdown, entitySummaryMarkdown: attackDiscovery.entity_summary_markdown, From 624872c7a08774a9bd55e8afcbb3f27c5bbce44d Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 09:49:15 -0700 Subject: [PATCH 20/80] lastConversationId => lastConversation --- ...ch_current_user_conversations_by_filter.ts | 133 ------------------ .../impl/assistant/assistant_header/index.tsx | 15 +- .../assistant/assistant_overlay/index.tsx | 36 +++-- .../assistant/chat_send/use_chat_send.tsx | 6 + .../impl/assistant/index.tsx | 81 ++++++----- .../assistant/use_assistant_overlay/index.tsx | 50 ++----- .../impl/assistant/use_conversation/index.tsx | 1 + .../use_current_conversation/index.tsx | 74 +++++++--- .../impl/assistant_context/constants.tsx | 1 + .../impl/assistant_context/index.test.tsx | 33 +++-- .../impl/assistant_context/index.tsx | 102 +++++++++++--- .../connector_selector_inline.tsx | 34 +++-- .../impl/new_chat_by_title/index.tsx | 10 +- .../right/components/header_actions.tsx | 6 +- .../cards/assistant/assistant_card.tsx | 4 +- 15 files changed, 292 insertions(+), 294 deletions(-) delete mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts deleted file mode 100644 index b0c2417ac8168..0000000000000 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations_by_filter.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HttpSetup } from '@kbn/core/public'; -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, - useQuery, -} from '@tanstack/react-query'; -import { - API_VERSIONS, - ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, -} from '@kbn/elastic-assistant-common'; -import { InfiniteData } from '@tanstack/query-core/src/types'; -import { useEffect } from 'react'; -import { Conversation } from '../../../assistant_context/types'; - -export interface FetchConversationsResponse { - page: number; - perPage: number; - total: number; - data: Conversation[]; -} - -export interface UseFetchCurrentUserConversationsParams { - http: HttpSetup; - fields?: string[]; - page?: number; - perPage?: number; - signal?: AbortSignal | undefined; - refetchOnWindowFocus?: boolean; - isAssistantEnabled: boolean; - setTotalItemCount?: (total: number) => void; -} - -export interface FetchCurrentUserConversations { - data: Record; - isLoading: boolean; - refetch: ( - options?: RefetchOptions & RefetchQueryFilters - ) => Promise, unknown>>; - isFetched: boolean; - isFetching: boolean; - setPaginationObserver: (ref: HTMLDivElement) => void; -} - -const query = { - page: 1, - perPage: 28, - fields: ['id', 'title', 'apiConfig', 'updatedAt'], -}; - -const formatFetchedData = (data: InfiniteData | undefined) => - data?.pages.reduce((acc, curr) => { - return { - ...acc, - ...curr.data.reduce( - (conversationsArr, conversation) => ({ - ...conversationsArr, - [conversation.id]: conversation, - }), - {} - ), - }; - }, {}); - -/** - * API call for fetching assistant conversations for the current user - */ -export const useFetchCurrentUserConversationsByFilter = ({ - http, - fields = query.fields, - page = query.page, - perPage = query.perPage, - signal, - refetchOnWindowFocus = true, - isAssistantEnabled, - setTotalItemCount, -}: UseFetchCurrentUserConversationsParams): FetchCurrentUserConversations => { - const queryFn = async ({ pageParam }: { pageParam?: UseFetchCurrentUserConversationsParams }) => { - return http.fetch(ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, { - method: 'GET', - version: API_VERSIONS.public.v1, - query: { - fields, - page: pageParam?.page ?? page, - per_page: pageParam?.perPage ?? perPage, - }, - signal, - }); - }; - - const getNextPageParam = (lastPage: FetchConversationsResponse) => { - const totalPages = Math.max(1, Math.ceil(lastPage.total / lastPage.perPage)); - if (totalPages === lastPage.page) { - return; - } - return { - ...lastPage, - page: lastPage.page + 1, - }; - }; - - const { data, isFetched, isFetching, isLoading, refetch } = useQuery( - [ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND, page, perPage, API_VERSIONS.public.v1], - queryFn, - { - enabled: isAssistantEnabled, - getNextPageParam, - refetchOnWindowFocus, - } - ); - useEffect(() => { - if (setTotalItemCount && data?.pages?.length) { - setTotalItemCount(data?.pages[0].total ?? 0); - } - }, [data?.pages, setTotalItemCount]); - - const formatted = formatFetchedData(data); - - return { - data: formatted ?? {}, - isLoading, - refetch, - isFetched, - isFetching, - }; -}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index ace2e97203494..c139c995e53b0 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -16,6 +16,7 @@ import { useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; +import { ApiConfig } from '@kbn/elastic-assistant-common'; import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations'; import { DataStreamApis } from '../use_data_stream_apis'; import { Conversation } from '../../..'; @@ -38,7 +39,15 @@ interface OwnProps { onCloseFlyout?: () => void; chatHistoryVisible?: boolean; setChatHistoryVisible?: React.Dispatch>; - onConversationSelected: ({ cId }: { cId: string }) => void; + onConversationSelected: ({ + cId, + cTitle, + apiConfig, + }: { + apiConfig?: ApiConfig; + cId: string; + cTitle?: string; + }) => void; conversations: Record; conversationsLoaded: boolean; refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations']; @@ -85,9 +94,11 @@ export const AssistantHeader: React.FC = ({ ); const onConversationChange = useCallback( - (updatedConversation: Conversation) => { + (updatedConversation: Conversation, apiConfig?: ApiConfig) => { onConversationSelected({ cId: updatedConversation.id, + cTitle: updatedConversation.title, + ...(apiConfig ? { apiConfig } : {}), }); }, [onConversationSelected] diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index 539b54e245fd2..6bd4bf81df1c5 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -12,7 +12,11 @@ import useEvent from 'react-use/lib/useEvent'; import { css } from '@emotion/react'; import { createGlobalStyle } from 'styled-components'; -import { ShowAssistantOverlayProps, useAssistantContext } from '../../assistant_context'; +import { + LastConversation, + ShowAssistantOverlayProps, + useAssistantContext, +} from '../../assistant_context'; import { Assistant, CONVERSATION_SIDE_PANEL_WIDTH } from '..'; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; @@ -30,10 +34,12 @@ export const UnifiedTimelineGlobalStyles = createGlobalStyle` export const AssistantOverlay = React.memo(() => { const [isModalVisible, setIsModalVisible] = useState(false); - // Why is this named Title and not Id? - const [conversationTitle, setConversationTitle] = useState(undefined); + // id if the conversation exists in the data stream, title if it's a new conversation + const [lastConversation, setSelectedConversation] = useState( + undefined + ); const [promptContextId, setPromptContextId] = useState(); - const { assistantTelemetry, setShowAssistantOverlay, getLastConversationId } = + const { assistantTelemetry, setShowAssistantOverlay, getLastConversation } = useAssistantContext(); const [chatHistoryVisible, setChatHistoryVisible] = useState(false); @@ -44,16 +50,20 @@ export const AssistantOverlay = React.memo(() => { ({ showOverlay: so, promptContextId: pid, - conversationTitle: cTitle, + selectedConversation, }: ShowAssistantOverlayProps) => { - const conversationId = getLastConversationId(cTitle); + const nextConversation = getLastConversation(selectedConversation); if (so) assistantTelemetry?.reportAssistantInvoked({ invokedBy: 'click' }); setIsModalVisible(so); setPromptContextId(pid); - setConversationTitle(conversationId); + console.log('showOverlay', { + selectedConversation, + nextConversation, + }); + setSelectedConversation(nextConversation); }, - [assistantTelemetry, getLastConversationId] + [assistantTelemetry, getLastConversation] ); useEffect(() => { setShowAssistantOverlay(showOverlay); @@ -63,14 +73,14 @@ export const AssistantOverlay = React.memo(() => { const handleShortcutPress = useCallback(() => { // Try to restore the last conversation on shortcut pressed if (!isModalVisible) { - setConversationTitle(getLastConversationId()); + setSelectedConversation(getLastConversation()); assistantTelemetry?.reportAssistantInvoked({ invokedBy: 'shortcut', }); } setIsModalVisible(!isModalVisible); - }, [isModalVisible, getLastConversationId, assistantTelemetry]); + }, [isModalVisible, getLastConversation, assistantTelemetry]); // Register keyboard listener to show the modal when cmd + ; is pressed const onKeyDown = useCallback( @@ -88,8 +98,8 @@ export const AssistantOverlay = React.memo(() => { const cleanupAndCloseModal = useCallback(() => { setIsModalVisible(false); setPromptContextId(undefined); - setConversationTitle(conversationTitle); - }, [conversationTitle]); + setSelectedConversation(lastConversation); + }, [lastConversation]); const handleCloseModal = useCallback(() => { cleanupAndCloseModal(); @@ -131,7 +141,7 @@ export const AssistantOverlay = React.memo(() => { hideCloseButton > (null); @@ -88,6 +89,11 @@ export const useChatSend = ({ if (currentConversation.id === '') { // create conversation with empty title, GENERATE_CHAT_TITLE graph step will properly title newConvo = await createConversation(currentConversation); + if (newConvo?.id) { + setLastConversation({ + id: newConvo.id, + }); + } } const convo: Conversation = { ...currentConversation, ...(newConvo ?? {}) }; const userMessage = getCombinedMessage({ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index f2ec7048fcabd..4631f3b3188eb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -29,7 +29,6 @@ import { createPortal } from 'react-dom'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import { isEmpty } from 'lodash'; import useEvent from 'react-use/lib/useEvent'; import { AssistantBody } from './assistant_body'; import { useCurrentConversation } from './use_current_conversation'; @@ -38,7 +37,7 @@ import { useChatSend } from './chat_send/use_chat_send'; import { ChatSend } from './chat_send'; import { getDefaultConnector } from './helpers'; -import { useAssistantContext } from '../assistant_context'; +import { LastConversation, useAssistantContext } from '../assistant_context'; import { ContextPills } from './context_pills'; import { getNewSelectedPromptContext } from '../data_anonymization/get_new_selected_prompt_context'; import type { PromptContext, SelectedPromptContext } from './prompt_context/types'; @@ -61,7 +60,7 @@ const CommentContainer = styled('span')` export interface Props { chatHistoryVisible?: boolean; - conversationTitle?: string; + lastConversation?: LastConversation; onCloseFlyout?: () => void; promptContextId?: string; setChatHistoryVisible?: Dispatch>; @@ -74,7 +73,7 @@ export interface Props { */ const AssistantComponent: React.FC = ({ chatHistoryVisible, - conversationTitle, + lastConversation, onCloseFlyout, promptContextId = '', setChatHistoryVisible, @@ -86,11 +85,11 @@ const AssistantComponent: React.FC = ({ assistantTelemetry, augmentMessageCodeBlocks, getComments, - getLastConversationId, + getLastConversation, http, promptContexts, currentUserAvatar, - setLastConversationId, + setLastConversation, contentReferencesVisible, showAnonymizedValues, setContentReferencesVisible, @@ -144,9 +143,10 @@ const AssistantComponent: React.FC = ({ conversations, defaultConnector, refetchCurrentUserConversations, - conversationId: getLastConversationId(conversationTitle), + lastConversation: lastConversation ?? getLastConversation(lastConversation), mayUpdateConversations: isFetchedConnectors && isFetchedCurrentUserConversations && isFetchedPrompts, + setLastConversation, }); const isInitialLoad = useMemo(() => { @@ -186,24 +186,40 @@ const AssistantComponent: React.FC = ({ // Settings modal state (so it isn't shared between assistant instances like Timeline) const [isSettingsModalVisible, setIsSettingsModalVisible] = useState(false); - // Remember last selection for reuse after keyboard shortcut is pressed. - // Clear it if there is no connectors - useEffect(() => { - if (isFetchedConnectors && !connectors?.length) { - return setLastConversationId(''); - } - - if (!currentConversation?.excludeFromLastConversationStorage) { - setLastConversationId(!isEmpty(currentConversation?.id) ? currentConversation?.id : ''); - } - }, [ - isFetchedConnectors, - connectors?.length, - conversations, - currentConversation, - isLoadingCurrentUserConversations, - setLastConversationId, - ]); + // // Remember last selection for reuse after keyboard shortcut is pressed. + // // Clear it if there is no connectors + // useEffect(() => { + // if (isLoadingCurrentUserConversations) { + // console.log('useEffect 0'); + // return; + // } + // if (isFetchedConnectors && !connectors?.length) { + // console.log('useEffect 1'); + // return setLastConversation({ id: '' }); + // } + // + // if (!currentConversation?.excludeFromLastConversationStorage) { + // console.log('useEffect 2', { + // result: + // currentConversation && !isEmpty(currentConversation?.id) + // ? { id: currentConversation.id } + // : { id: '' }, + // currentConversation, + // }); + // setLastConversation( + // currentConversation && !isEmpty(currentConversation?.id) + // ? { id: currentConversation.id } + // : { id: '' } + // ); + // } + // }, [ + // isFetchedConnectors, + // connectors?.length, + // conversations, + // currentConversation, + // isLoadingCurrentUserConversations, + // setLastConversation, + // ]); const [autoPopulatedOnce, setAutoPopulatedOnce] = useState(false); @@ -307,18 +323,16 @@ const AssistantComponent: React.FC = ({ }); useEffect(() => { - // Adding `conversationTitle !== selectedConversationTitle` to prevent auto-run still executing after changing selected conversation + // Adding `conversationTitle !== selectedConversationTitle` to if ( currentConversation?.messages.length || - (currentConversation && conversationTitle !== currentConversation?.title) + // prevent auto-run still executing after changing selected conversation + (currentConversation && lastConversation?.title !== currentConversation?.title) || + autoPopulatedOnce ) { return; } - if (autoPopulatedOnce) { - return; - } - const promptContext: PromptContext | undefined = promptContexts[promptContextId]; if ( promptContext != null && @@ -349,11 +363,10 @@ const AssistantComponent: React.FC = ({ } } }, [ - currentConversation?.messages, promptContexts, promptContextId, - conversationTitle, - currentConversation?.title, + lastConversation, + currentConversation, selectedPromptContexts, autoPopulatedOnce, isLoadingAnonymizationFields, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index 955a118954c89..5a2cc6e1286ad 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -11,10 +11,6 @@ import { useCallback, useEffect, useMemo } from 'react'; import { useAssistantContext } from '../../assistant_context'; import { getUniquePromptContextId } from '../../assistant_context/helpers'; import type { PromptContext } from '../prompt_context/types'; -import { useConversation } from '../use_conversation'; -import { getDefaultConnector } from '../helpers'; -import { getGenAiConfig } from '../../connectorland/helpers'; -import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { useFetchCurrentUserConversations } from '../api'; interface UseAssistantOverlay { @@ -81,16 +77,7 @@ export const useAssistantOverlay = ( */ replacements?: Replacements | null ): UseAssistantOverlay => { - const { http, inferenceEnabled } = useAssistantContext(); - const { data: connectors } = useLoadConnectors({ - http, - inferenceEnabled, - }); - - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); - const apiConfig = useMemo(() => getGenAiConfig(defaultConnector), [defaultConnector]); - - const { createConversation } = useConversation(); + const { http } = useAssistantContext(); const { data: conversations, isLoading } = useFetchCurrentUserConversations({ http, @@ -136,41 +123,20 @@ export const useAssistantOverlay = ( ? Object.values(conversations).find((conv) => conv.title === conversationTitle) : undefined; } - - if (isAssistantEnabled && !conversation && defaultConnector && !isLoading) { - try { - conversation = await createConversation({ - apiConfig: { - ...apiConfig, - actionTypeId: defaultConnector?.actionTypeId, - connectorId: defaultConnector?.id, - }, - category: 'assistant', - title: conversationTitle ?? '', - }); - } catch (e) { - /* empty */ - } - } } assistantContextShowOverlay({ showOverlay, promptContextId, - conversationTitle: conversation?.id ?? conversationTitle ?? undefined, + // conversation with id exists in the data stream, title if it's a new conversation + selectedConversation: conversation?.id + ? { id: conversation.id } + : { + title: conversationTitle ?? '', + }, }); } }, - [ - apiConfig, - assistantContextShowOverlay, - conversationTitle, - conversations, - createConversation, - defaultConnector, - isAssistantEnabled, - isLoading, - promptContextId, - ] + [assistantContextShowOverlay, conversationTitle, conversations, isLoading, promptContextId] ); useEffect(() => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index f0276025a5e9e..2ce8157435ef1 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -128,6 +128,7 @@ export const useConversation = (): UseConversation => { */ const setApiConfig = useCallback( async ({ conversation, apiConfig }: SetApiConfigProps) => { + console.log('setApiConfig', conversation, apiConfig); if (conversation.id === '') { // only developer should ever see this error throw new Error('Conversation ID is required to set API config'); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx index c19030ff5a4d8..249627dbf3bde 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_current_conversation/index.tsx @@ -7,8 +7,9 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query'; -import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { ApiConfig, PromptResponse } from '@kbn/elastic-assistant-common'; import { InfiniteData } from '@tanstack/query-core/src/types'; +import { LastConversation } from '../../assistant_context'; import { FetchConversationsResponse } from '../api'; import { AIConnector } from '../../connectorland/connector_selector'; import { getDefaultNewSystemPrompt, getDefaultSystemPrompt } from '../use_conversation/helpers'; @@ -18,13 +19,14 @@ import { Conversation } from '../../..'; export interface Props { allSystemPrompts: PromptResponse[]; - conversationId: string; + lastConversation: LastConversation; conversations: Record; defaultConnector?: AIConnector; mayUpdateConversations: boolean; refetchCurrentUserConversations: ( options?: RefetchOptions & RefetchQueryFilters ) => Promise, unknown>>; + setLastConversation: Dispatch>; } interface UseCurrentConversation { @@ -32,7 +34,13 @@ interface UseCurrentConversation { currentSystemPrompt: PromptResponse | undefined; handleCreateConversation: () => Promise; handleOnConversationDeleted: (cTitle: string) => Promise; - handleOnConversationSelected: ({ cId }: { cId: string }) => Promise; + handleOnConversationSelected: ({ + cId, + cTitle, + }: { + cId: string; + cTitle?: string; + }) => Promise; refetchCurrentConversation: (options?: { cId?: string; isStreamRefetch?: boolean; @@ -48,11 +56,12 @@ interface UseCurrentConversation { */ export const useCurrentConversation = ({ allSystemPrompts, - conversationId, + lastConversation, conversations, defaultConnector, mayUpdateConversations, refetchCurrentUserConversations, + setLastConversation, }: Props): UseCurrentConversation => { const { deleteConversation, getConversation, setApiConfig } = useConversation(); const [currentConversation, setCurrentConversation] = useState(); @@ -140,25 +149,37 @@ export const useCurrentConversation = ({ ); const handleOnConversationSelected = useCallback( - async ({ cId }: { cId: string }) => { + async ({ + cId, + cTitle, + apiConfig: providedApiConfig, + }: { + apiConfig?: ApiConfig; + cId: string; + cTitle?: string; + }) => { if (cId === '') { - const apiConfig = defaultConnector - ? { - connectorId: defaultConnector.id ?? '', - actionTypeId: defaultConnector.actionTypeId ?? '', - } - : undefined; - - // Merge apiConfig from currentConversation if it exists - const mergedApiConfig = currentConversation?.apiConfig - ? currentConversation.apiConfig - : apiConfig; + const apiConfig = + providedApiConfig ?? + (currentConversation?.apiConfig + ? currentConversation.apiConfig + : defaultConnector + ? { + connectorId: defaultConnector.id ?? '', + actionTypeId: defaultConnector.actionTypeId ?? '', + } + : undefined); + const newConversationDefaultSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts); + setLastConversation({ + id: '', + title: cTitle ?? '', + }); return setCurrentConversation({ - ...(mergedApiConfig + ...(apiConfig ? { apiConfig: { - ...mergedApiConfig, + ...apiConfig, ...(newConversationDefaultSystemPrompt?.id ? { defaultSystemPromptId: newConversationDefaultSystemPrompt?.id } : {}), @@ -169,18 +190,27 @@ export const useCurrentConversation = ({ messages: [], replacements: {}, category: 'assistant', - title: '', + title: cTitle ?? '', }); } + setLastConversation({ + id: cId, + }); // refetch will set the currentConversation await refetchCurrentConversation({ cId }); }, - [allSystemPrompts, currentConversation?.apiConfig, defaultConnector, refetchCurrentConversation] + [ + allSystemPrompts, + currentConversation?.apiConfig, + defaultConnector, + refetchCurrentConversation, + setLastConversation, + ] ); useEffect(() => { if (!mayUpdateConversations || !!currentConversation) return; - handleOnConversationSelected({ cId: conversationId ?? '' }); - }, [conversationId, handleOnConversationSelected, currentConversation, mayUpdateConversations]); + handleOnConversationSelected({ cId: lastConversation.id, cTitle: lastConversation.title }); + }, [lastConversation, handleOnConversationSelected, currentConversation, mayUpdateConversations]); const handleOnConversationDeleted = useCallback( async (cTitle: string) => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/constants.tsx index f181b08fe7268..b4b5bc99015e1 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -12,6 +12,7 @@ export const DEFEND_INSIGHTS_STORAGE_KEY = 'defendInsights'; export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault'; export const END_LOCAL_STORAGE_KEY = 'end'; export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; +export const LAST_SELECTED_CONVERSATION_LOCAL_STORAGE_KEY = 'lastSelectedConversation'; export const FILTERS_LOCAL_STORAGE_KEY = 'filters'; export const MAX_ALERTS_LOCAL_STORAGE_KEY = 'maxAlerts'; export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.test.tsx index 94e7800450c5d..2e95fc0b1eeeb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.test.tsx @@ -11,8 +11,12 @@ import { useAssistantContext } from '.'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { TestProviders } from '../mock/test_providers/test_providers'; -jest.mock('react-use/lib/useLocalStorage', () => jest.fn().mockReturnValue(['456', jest.fn()])); -jest.mock('react-use/lib/useSessionStorage', () => jest.fn().mockReturnValue(['456', jest.fn()])); +jest.mock('react-use/lib/useLocalStorage', () => + jest.fn().mockReturnValue([{ id: '456' }, jest.fn()]) +); +jest.mock('react-use/lib/useSessionStorage', () => + jest.fn().mockReturnValue([{ id: '456' }, jest.fn()]) +); describe('AssistantContext', () => { beforeEach(() => jest.clearAllMocks()); @@ -32,22 +36,29 @@ describe('AssistantContext', () => { expect(result.current.http.fetch).toBeCalledWith(path); }); - test('getLastConversationId defaults to provided id', async () => { + test('getLastSelectedConversation defaults to provided id', async () => { const { result } = renderHook(useAssistantContext, { wrapper: TestProviders }); - const id = result.current.getLastConversationId('123'); - expect(id).toEqual('123'); + const id = result.current.getLastSelectedConversation({ id: '123' }); + expect(id).toEqual({ id: '123' }); }); - test('getLastConversationId uses local storage id when no id is provided ', async () => { + test('getLastSelectedConversation uses local storage id when no id is provided ', async () => { const { result } = renderHook(useAssistantContext, { wrapper: TestProviders }); - const id = result.current.getLastConversationId(); - expect(id).toEqual('456'); + const id = result.current.getLastSelectedConversation(); + expect(id).toEqual({ id: '456' }); }); - test('getLastConversationId defaults to Welcome when no local storage id and no id is provided ', async () => { + test('getLastSelectedConversation defaults to empty id when no local storage id and no id is provided ', async () => { (useLocalStorage as jest.Mock).mockReturnValue([undefined, jest.fn()]); const { result } = renderHook(useAssistantContext, { wrapper: TestProviders }); - const id = result.current.getLastConversationId(); - expect(id).toEqual('Welcome'); + const id = result.current.getLastSelectedConversation(); + expect(id).toEqual({ id: '' }); + }); + + test('getLastSelectedConversation defaults to empty id when title is provided and preserves title', async () => { + (useLocalStorage as jest.Mock).mockReturnValue([undefined, jest.fn()]); + const { result } = renderHook(useAssistantContext, { wrapper: TestProviders }); + const id = result.current.getLastSelectedConversation({ title: 'something' }); + expect(id).toEqual({ id: '', title: 'something' }); }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index b3603734d7e86..8141b9d4c489a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -39,6 +39,7 @@ import { DEFAULT_KNOWLEDGE_BASE_SETTINGS, KNOWLEDGE_BASE_LOCAL_STORAGE_KEY, LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY, + LAST_SELECTED_CONVERSATION_LOCAL_STORAGE_KEY, SHOW_ANONYMIZED_VALUES_LOCAL_STORAGE_KEY, STREAMING_LOCAL_STORAGE_KEY, TRACE_OPTIONS_SESSION_STORAGE_KEY, @@ -47,16 +48,23 @@ import { useCapabilities } from '../assistant/api/capabilities/use_capabilities' import { SettingsTabs } from '../assistant/settings/types'; import { AssistantNavLink } from './assistant_nav_link'; +export type SelectedConversation = { id: string } | { title: string }; +export interface LastConversation { + id: string; + title?: string; +} + export interface ShowAssistantOverlayProps { showOverlay: boolean; promptContextId?: string; - conversationTitle?: string; + // id if the conversation exists in the data stream, title if it's a new conversation + selectedConversation?: SelectedConversation; } type ShowAssistantOverlay = ({ showOverlay, promptContextId, - conversationTitle, + selectedConversation, }: ShowAssistantOverlayProps) => void; export interface AssistantProviderProps { actionTypeRegistry: ActionTypeRegistryContract; @@ -108,7 +116,7 @@ export interface UseAssistantContext { http: HttpSetup; inferenceEnabled: boolean; knowledgeBase: KnowledgeBaseConfig; - getLastConversationId: (conversationTitle?: string) => string; + getLastConversation: (selectedConversation?: SelectedConversation) => LastConversation; promptContexts: Record; navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise; nameSpace: string; @@ -120,7 +128,7 @@ export interface UseAssistantContext { setContentReferencesVisible: React.Dispatch>; setAssistantStreamingEnabled: React.Dispatch>; setKnowledgeBase: React.Dispatch>; - setLastConversationId: React.Dispatch>; + setLastConversation: React.Dispatch>; setSelectedSettingsTab: React.Dispatch>; setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void; showAssistantOverlay: ShowAssistantOverlay; @@ -179,8 +187,15 @@ export const AssistantProvider: React.FC = ({ defaultTraceOptions ); - const [localStorageLastConversationId, setLocalStorageLastConversationId] = - useLocalStorage(`${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}`); + // Legacy fallback: used only if the new storage value is not yet set + const [localStorageLastConversationId] = useLocalStorage( + `${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}` + ); + + const [localStorageLastConversation, setLocalStorageLastConversation] = + useLocalStorage( + `${nameSpace}.${LAST_SELECTED_CONVERSATION_LOCAL_STORAGE_KEY}` + ); /** * Local storage for knowledge base configuration, prefixed by assistant nameSpace @@ -281,12 +296,67 @@ export const AssistantProvider: React.FC = ({ */ const codeBlockRef = useRef(() => {}); - const getLastConversationId = useCallback( - // if a conversationId has been provided, use that - // if not, check local storage - // last resort, empty id - (conversationId?: string) => conversationId ?? localStorageLastConversationId ?? '', - [localStorageLastConversationId] + const getLastConversation = useCallback( + (selectedConversation?: SelectedConversation): LastConversation => { + console.log('getLastConversation', { + selectedConversation, + localStorageLastConversation, + localStorageLastConversationId, + }); + + let nextConversation: LastConversation = { id: '' }; + // Type guard to check if selectedConversation has a 'title' + if (selectedConversation && 'title' in selectedConversation) { + nextConversation = { + id: '', + title: selectedConversation.title, + }; + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 1', nextConversation); + return nextConversation; + } + + // If selectedConversation exists and has an 'id', return it with no 'title' + if (selectedConversation && 'id' in selectedConversation) { + nextConversation = { id: selectedConversation.id }; + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 2', nextConversation); + return nextConversation; + } + + // Check if localStorageLastConversation has a 'title' + if (localStorageLastConversation && 'title' in localStorageLastConversation) { + nextConversation = { + id: '', + title: localStorageLastConversation.title, + }; + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 3', nextConversation); + return nextConversation; + } + + // If localStorageLastConversation, return it + if (localStorageLastConversation && 'id' in localStorageLastConversation) { + nextConversation = localStorageLastConversation; + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 4', nextConversation); + return nextConversation; + } + + // If localStorageLastConversationId exists, use it as 'id' + if (localStorageLastConversationId) { + nextConversation = { id: localStorageLastConversationId }; + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 5', nextConversation); + return nextConversation; + } + + // Default to an empty 'id' + setLocalStorageLastConversation(nextConversation); + console.log('getLastConversation 1', nextConversation); + return nextConversation; + }, + [localStorageLastConversation, localStorageLastConversationId, setLocalStorageLastConversation] ); // Fetch assistant capabilities @@ -337,8 +407,8 @@ export const AssistantProvider: React.FC = ({ toasts, traceOptions: sessionStorageTraceOptions, unRegisterPromptContext, - getLastConversationId, - setLastConversationId: setLocalStorageLastConversationId, + getLastConversation, + setLastConversation: setLocalStorageLastConversation, currentAppId, codeBlockRef, userProfileService, @@ -378,8 +448,8 @@ export const AssistantProvider: React.FC = ({ toasts, sessionStorageTraceOptions, unRegisterPromptContext, - getLastConversationId, - setLocalStorageLastConversationId, + getLastConversation, + setLocalStorageLastConversation, currentAppId, codeBlockRef, userProfileService, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/connector_selector_inline.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/connector_selector_inline.tsx index 9dd36c13d7c14..110de63481271 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/connector_selector_inline.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector_inline/connector_selector_inline.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; import { css } from '@emotion/css'; -import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; +import type { ApiConfig, AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { AIConnector, ConnectorSelector } from '../connector_selector'; import { Conversation } from '../../..'; import { useAssistantContext } from '../../assistant_context'; @@ -23,7 +23,7 @@ interface Props { selectedConnectorId?: string; selectedConversation?: Conversation; onConnectorIdSelected?: (connectorId: string) => void; - onConnectorSelected?: (conversation: Conversation) => void; + onConnectorSelected?: (conversation: Conversation, apiConfig?: ApiConfig) => void; stats?: AttackDiscoveryStats | null; } @@ -81,20 +81,30 @@ export const ConnectorSelectorInline: React.FC = React.memo( setIsOpen(false); if (selectedConversation != null) { - const conversation = await setApiConfig({ - conversation: selectedConversation, - apiConfig: { + if (selectedConversation.id === '' && onConnectorSelected != null) { + onConnectorSelected(selectedConversation, { ...selectedConversation.apiConfig, - actionTypeId: connector.actionTypeId, connectorId, - // With the inline component, prefer config args to handle 'new connector' case + actionTypeId: connector.actionTypeId, provider: apiProvider, model, - }, - }); - - if (conversation && onConnectorSelected != null) { - onConnectorSelected(conversation); + }); + } else { + const conversation = await setApiConfig({ + conversation: selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + actionTypeId: connector.actionTypeId, + connectorId, + // With the inline component, prefer config args to handle 'new connector' case + provider: apiProvider, + model, + }, + }); + + if (conversation && onConnectorSelected != null) { + onConnectorSelected(conversation); + } } } diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index 3ea4007eeef51..106357b315f4f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -8,14 +8,14 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useAssistantContext } from '../assistant_context'; +import { SelectedConversation, useAssistantContext } from '../assistant_context'; import * as i18n from './translations'; export interface Props { children?: React.ReactNode; /** Optionally automatically add this context to a conversation when the assistant is shown */ - conversationTitle?: string; + selectedConversation?: SelectedConversation; /** Defaults to `discuss`. If null, the button will not have an icon */ iconType?: string | null; /** Optionally specify a well known ID, or default to a UUID */ @@ -26,7 +26,7 @@ export interface Props { const NewChatByTitleComponent: React.FC = ({ children = i18n.NEW_CHAT, - conversationTitle, + selectedConversation, iconType, promptContextId, iconOnly = false, @@ -36,11 +36,11 @@ const NewChatByTitleComponent: React.FC = ({ // proxy show / hide calls to assistant context, using our internal prompt context id: const showOverlay = useCallback(() => { showAssistantOverlay({ - conversationTitle, + selectedConversation, promptContextId, showOverlay: true, }); - }, [conversationTitle, promptContextId, showAssistantOverlay]); + }, [selectedConversation, promptContextId, showAssistantOverlay]); const icon = useMemo(() => { if (iconType === null) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx index 078f273ec28fa..058628ef6cbb6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx @@ -51,8 +51,10 @@ export const HeaderActions: VFC = memo(() => { {showAssistant && ( = ({ const { http, assistantAvailability: { isAssistantEnabled }, - getLastConversationId, + getLastConversation, } = useAssistantContext(); const { allSystemPrompts, @@ -76,7 +76,7 @@ export const AssistantCard: OnboardingCardComponent = ({ conversations, defaultConnector, refetchCurrentUserConversations, - conversationId: getLastConversationId(), + lastConversation: getLastConversation(), mayUpdateConversations: isFetchedCurrentUserConversations && isFetchedPrompts && From 49fce1c360f3ea652fd9d6e2481a9efb48095dd0 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 10:20:11 -0700 Subject: [PATCH 21/80] better --- .../assistant/assistant_overlay/index.tsx | 4 --- .../assistant/use_assistant_overlay/index.tsx | 26 ++++++++++--------- .../impl/assistant/use_conversation/index.tsx | 1 - .../impl/assistant_context/index.tsx | 20 +------------- .../use_view_in_ai_assistant.ts | 2 +- .../cards/assistant/assistant_card.tsx | 2 ++ 6 files changed, 18 insertions(+), 37 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index 6bd4bf81df1c5..5f8f9fa4c3809 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -57,10 +57,6 @@ export const AssistantOverlay = React.memo(() => { setIsModalVisible(so); setPromptContextId(pid); - console.log('showOverlay', { - selectedConversation, - nextConversation, - }); setSelectedConversation(nextConversation); }, [assistantTelemetry, getLastConversation] diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index 5a2cc6e1286ad..1940510fcf440 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -8,6 +8,7 @@ import { Replacements } from '@kbn/elastic-assistant-common'; import { useCallback, useEffect, useMemo } from 'react'; +import { Conversation } from '../../assistant_context/types'; import { useAssistantContext } from '../../assistant_context'; import { getUniquePromptContextId } from '../../assistant_context/helpers'; import type { PromptContext } from '../prompt_context/types'; @@ -79,10 +80,11 @@ export const useAssistantOverlay = ( ): UseAssistantOverlay => { const { http } = useAssistantContext(); - const { data: conversations, isLoading } = useFetchCurrentUserConversations({ + const { refetch } = useFetchCurrentUserConversations({ http, filter: `title:${conversationTitle}`, - isAssistantEnabled: conversationTitle != null ? isAssistantEnabled : false, + // prevent from running automatically + isAssistantEnabled: false, }); // memoize the props so that we can use them in the effect below: const _category: PromptContext['category'] = useMemo(() => category, [category]); @@ -114,16 +116,16 @@ export const useAssistantOverlay = ( const showAssistantOverlay = useCallback( // shouldCreateConversation should only be passed for // non-default conversations that may need to be initialized - async (showOverlay: boolean, shouldCreateConversation: boolean = false) => { + async (showOverlay: boolean) => { if (promptContextId != null) { - let conversation; - if (shouldCreateConversation) { - if (!isLoading) { - conversation = conversationTitle - ? Object.values(conversations).find((conv) => conv.title === conversationTitle) - : undefined; - } - } + const refetched = await refetch(); + const conversation = + refetched && conversationTitle + ? Object.values(refetched?.data?.pages[0].data ?? []).find( + (conv: Conversation) => conv.title === conversationTitle + ) + : undefined; + assistantContextShowOverlay({ showOverlay, promptContextId, @@ -136,7 +138,7 @@ export const useAssistantOverlay = ( }); } }, - [assistantContextShowOverlay, conversationTitle, conversations, isLoading, promptContextId] + [assistantContextShowOverlay, conversationTitle, promptContextId, refetch] ); useEffect(() => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index 2ce8157435ef1..f0276025a5e9e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -128,7 +128,6 @@ export const useConversation = (): UseConversation => { */ const setApiConfig = useCallback( async ({ conversation, apiConfig }: SetApiConfigProps) => { - console.log('setApiConfig', conversation, apiConfig); if (conversation.id === '') { // only developer should ever see this error throw new Error('Conversation ID is required to set API config'); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index 8141b9d4c489a..75baeb952baa2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -298,12 +298,6 @@ export const AssistantProvider: React.FC = ({ const getLastConversation = useCallback( (selectedConversation?: SelectedConversation): LastConversation => { - console.log('getLastConversation', { - selectedConversation, - localStorageLastConversation, - localStorageLastConversationId, - }); - let nextConversation: LastConversation = { id: '' }; // Type guard to check if selectedConversation has a 'title' if (selectedConversation && 'title' in selectedConversation) { @@ -311,16 +305,12 @@ export const AssistantProvider: React.FC = ({ id: '', title: selectedConversation.title, }; - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 1', nextConversation); return nextConversation; } // If selectedConversation exists and has an 'id', return it with no 'title' if (selectedConversation && 'id' in selectedConversation) { nextConversation = { id: selectedConversation.id }; - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 2', nextConversation); return nextConversation; } @@ -330,33 +320,25 @@ export const AssistantProvider: React.FC = ({ id: '', title: localStorageLastConversation.title, }; - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 3', nextConversation); return nextConversation; } // If localStorageLastConversation, return it if (localStorageLastConversation && 'id' in localStorageLastConversation) { nextConversation = localStorageLastConversation; - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 4', nextConversation); return nextConversation; } // If localStorageLastConversationId exists, use it as 'id' if (localStorageLastConversationId) { nextConversation = { id: localStorageLastConversationId }; - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 5', nextConversation); return nextConversation; } // Default to an empty 'id' - setLocalStorageLastConversation(nextConversation); - console.log('getLastConversation 1', nextConversation); return nextConversation; }, - [localStorageLastConversation, localStorageLastConversationId, setLocalStorageLastConversation] + [localStorageLastConversation, localStorageLastConversationId] ); // Fetch assistant capabilities diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/view_in_ai_assistant/use_view_in_ai_assistant.ts b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/view_in_ai_assistant/use_view_in_ai_assistant.ts index cf07259801c60..a481e577955cf 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/view_in_ai_assistant/use_view_in_ai_assistant.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/results/attack_discovery_panel/view_in_ai_assistant/use_view_in_ai_assistant.ts @@ -49,7 +49,7 @@ export const useViewInAiAssistant = ({ // proxy show / hide calls to assistant context, using our internal prompt context id: const showAssistantOverlay = useCallback(() => { - showOverlay(true, true); + showOverlay(true); }, [showOverlay]); const disabled = !hasAssistantPrivilege || promptContextId == null; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx index e8d5e46e7c1df..6bb2d24fe48f0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx @@ -62,6 +62,7 @@ export const AssistantCard: OnboardingCardComponent = ({ http, assistantAvailability: { isAssistantEnabled }, getLastConversation, + setLastConversation, } = useAssistantContext(); const { allSystemPrompts, @@ -81,6 +82,7 @@ export const AssistantCard: OnboardingCardComponent = ({ isFetchedCurrentUserConversations && isFetchedPrompts && Object.keys(conversations).length > 0, + setLastConversation, }); const onConversationChange = useCallback( From ec79d135447f9c46e449551736f3d677b63cf78f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 11:24:41 -0700 Subject: [PATCH 22/80] unique conversation names for alerts/events --- .../assistant/use_assistant_overlay/index.tsx | 6 ++-- .../impl/new_chat_by_title/index.tsx | 20 +++---------- .../right/components/header_actions.tsx | 16 ++--------- .../right/hooks/use_assistant.ts | 28 ++++++++++++++++--- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index 1940510fcf440..b855302b9f189 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -15,7 +15,7 @@ import type { PromptContext } from '../prompt_context/types'; import { useFetchCurrentUserConversations } from '../api'; interface UseAssistantOverlay { - showAssistantOverlay: (show: boolean, silent?: boolean) => void; + showAssistantOverlay: (show: boolean) => void; promptContextId: string; } @@ -117,7 +117,7 @@ export const useAssistantOverlay = ( // shouldCreateConversation should only be passed for // non-default conversations that may need to be initialized async (showOverlay: boolean) => { - if (promptContextId != null) { + if (promptContextId != null && isAssistantEnabled) { const refetched = await refetch(); const conversation = refetched && conversationTitle @@ -138,7 +138,7 @@ export const useAssistantOverlay = ( }); } }, - [assistantContextShowOverlay, conversationTitle, promptContextId, refetch] + [assistantContextShowOverlay, conversationTitle, isAssistantEnabled, promptContextId, refetch] ); useEffect(() => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index 106357b315f4f..643ff691bc355 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -8,39 +8,27 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { SelectedConversation, useAssistantContext } from '../assistant_context'; - import * as i18n from './translations'; export interface Props { children?: React.ReactNode; - /** Optionally automatically add this context to a conversation when the assistant is shown */ - selectedConversation?: SelectedConversation; + showAssistantOverlay: (show: boolean) => void; /** Defaults to `discuss`. If null, the button will not have an icon */ iconType?: string | null; - /** Optionally specify a well known ID, or default to a UUID */ - promptContextId?: string; /** Defaults to false. If true, shows icon button without text */ iconOnly?: boolean; } const NewChatByTitleComponent: React.FC = ({ children = i18n.NEW_CHAT, - selectedConversation, + showAssistantOverlay, iconType, - promptContextId, iconOnly = false, }) => { - const { showAssistantOverlay } = useAssistantContext(); - // proxy show / hide calls to assistant context, using our internal prompt context id: const showOverlay = useCallback(() => { - showAssistantOverlay({ - selectedConversation, - promptContextId, - showOverlay: true, - }); - }, [selectedConversation, promptContextId, showAssistantOverlay]); + showAssistantOverlay(true); + }, [showAssistantOverlay]); const icon = useMemo(() => { if (iconType === null) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx index 058628ef6cbb6..322b9cae1865a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx @@ -13,10 +13,6 @@ import { NewChatByTitle } from '@kbn/elastic-assistant'; import { useGetFlyoutLink } from '../hooks/use_get_flyout_link'; import { useBasicDataFromDetailsData } from '../../shared/hooks/use_basic_data_from_details_data'; import { useAssistant } from '../hooks/use_assistant'; -import { - ALERT_SUMMARY_CONVERSATION_ID, - EVENT_SUMMARY_CONVERSATION_ID, -} from '../../../../common/components/event_details/translations'; import { useDocumentDetailsContext } from '../../shared/context'; import { SHARE_BUTTON_TEST_ID } from './test_ids'; @@ -35,7 +31,7 @@ export const HeaderActions: VFC = memo(() => { const showShareAlertButton = isAlert && alertDetailsLink; - const { showAssistant, promptContextId } = useAssistant({ + const { showAssistant, showAssistantOverlay } = useAssistant({ dataFormattedForFieldBrowser, isAlert, }); @@ -50,15 +46,7 @@ export const HeaderActions: VFC = memo(() => { > {showAssistant && ( - + )} {showShareAlertButton && ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts index da65a684b026c..4059633c9be61 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts @@ -7,7 +7,7 @@ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { useAssistantOverlay } from '@kbn/elastic-assistant'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; import { getRawData } from '../../../../assistant/helpers'; @@ -29,7 +29,10 @@ const SUMMARY_VIEW = i18n.translate('xpack.securitySolution.eventDetails.summary defaultMessage: 'summary', }); -const useAssistantNoop = () => ({ promptContextId: undefined }); +const useAssistantNoop = () => ({ + promptContextId: undefined, + showAssistantOverlay: (show: boolean) => {}, +}); export interface UseAssistantParams { /** @@ -51,6 +54,10 @@ export interface UseAssistantResult { * Unique identifier for prompt context */ promptContextId: string; + /** + * Function to show assistant overlay + */ + showAssistantOverlay: (show: boolean) => void; } /** @@ -66,9 +73,21 @@ export const useAssistant = ({ async () => getRawData(dataFormattedForFieldBrowser ?? []), [dataFormattedForFieldBrowser] ); - const { promptContextId } = useAssistantHook( + + const uniqueName = useMemo(() => { + const ruleName: string = + dataFormattedForFieldBrowser.find((item) => item.field === 'rule.name')?.values?.[0] ?? + dataFormattedForFieldBrowser.find((item) => item.field === 'kibana.alert.rule.name') + ?.values?.[0] ?? + (isAlert ? ALERT_SUMMARY_CONVERSATION_ID : EVENT_SUMMARY_CONVERSATION_ID); + const timestamp: string = + dataFormattedForFieldBrowser.find((item) => item.field === '@timestamp')?.values?.[0] ?? ''; + return `${ruleName} - ${timestamp}`; + }, [dataFormattedForFieldBrowser, isAlert]); + + const { promptContextId, showAssistantOverlay } = useAssistantHook( isAlert ? 'alert' : 'event', - isAlert ? ALERT_SUMMARY_CONVERSATION_ID : EVENT_SUMMARY_CONVERSATION_ID, + uniqueName, isAlert ? ALERT_SUMMARY_CONTEXT_DESCRIPTION(SUMMARY_VIEW) : EVENT_SUMMARY_CONTEXT_DESCRIPTION(SUMMARY_VIEW), @@ -83,6 +102,7 @@ export const useAssistant = ({ return { showAssistant: hasAssistantPrivilege && promptContextId !== null, + showAssistantOverlay, promptContextId: promptContextId || '', }; }; From 85e319c2773a87f62e39c47a2699407672a8a63f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 14:02:18 -0700 Subject: [PATCH 23/80] prompt context fix --- .../impl/assistant/index.tsx | 29 +++++++++++-------- .../assistant/use_assistant_overlay/index.tsx | 4 +-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index 4631f3b3188eb..5395ae8c588e2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -323,13 +323,18 @@ const AssistantComponent: React.FC = ({ }); useEffect(() => { - // Adding `conversationTitle !== selectedConversationTitle` to + // reset PromptContexts state when conversation changes if ( - currentConversation?.messages.length || - // prevent auto-run still executing after changing selected conversation - (currentConversation && lastConversation?.title !== currentConversation?.title) || - autoPopulatedOnce + autoPopulatedOnce && + currentConversation && + lastConversation?.title !== currentConversation?.title ) { + setAutoPopulatedOnce(false); + setSelectedPromptContexts({}); + setUserPrompt(null); + return; + } + if (currentConversation?.messages.length || autoPopulatedOnce) { return; } @@ -363,16 +368,16 @@ const AssistantComponent: React.FC = ({ } } }, [ - promptContexts, - promptContextId, - lastConversation, - currentConversation, - selectedPromptContexts, + anonymizationFields, autoPopulatedOnce, - isLoadingAnonymizationFields, + currentConversation, isErrorAnonymizationFields, - anonymizationFields, isFetchedAnonymizationFields, + isLoadingAnonymizationFields, + lastConversation?.title, + promptContextId, + promptContexts, + selectedPromptContexts, setUserPrompt, ]); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index b855302b9f189..5df0d6cd1c8f8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -82,7 +82,7 @@ export const useAssistantOverlay = ( const { refetch } = useFetchCurrentUserConversations({ http, - filter: `title:${conversationTitle}`, + filter: `title:"${conversationTitle}"`, // prevent from running automatically isAssistantEnabled: false, }); @@ -117,7 +117,7 @@ export const useAssistantOverlay = ( // shouldCreateConversation should only be passed for // non-default conversations that may need to be initialized async (showOverlay: boolean) => { - if (promptContextId != null && isAssistantEnabled) { + if (promptContextId != null && isAssistantEnabled && conversationTitle != null) { const refetched = await refetch(); const conversation = refetched && conversationTitle From c6e83ee073694fca1237f9a38638168758d71bd3 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 14:59:41 -0700 Subject: [PATCH 24/80] DQD --- .../impl/new_chat_by_title/index.tsx | 11 +++++-- .../data_quality_panel/actions/chat/index.tsx | 30 +++++++++---------- .../impl/data_quality_panel/actions/index.tsx | 8 ++++- .../historical_check_fields/index.tsx | 2 ++ .../incompatible_tab/index.tsx | 9 ++++++ .../pattern/index_check_flyout/index.tsx | 1 + .../latest_results/index.tsx | 3 ++ .../latest_check_fields/index.tsx | 4 +++ .../sticky_actions/index.tsx | 3 ++ 9 files changed, 53 insertions(+), 18 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index 643ff691bc355..3cdfebaf0a118 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -5,12 +5,14 @@ * 2.0. */ -import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import * as i18n from './translations'; export interface Props { + /** Optionally render new chat as a link */ + asLink?: boolean; children?: React.ReactNode; showAssistantOverlay: (show: boolean) => void; /** Defaults to `discuss`. If null, the button will not have an icon */ @@ -20,6 +22,7 @@ export interface Props { } const NewChatByTitleComponent: React.FC = ({ + asLink = false, children = i18n.NEW_CHAT, showAssistantOverlay, iconType, @@ -40,7 +43,11 @@ const NewChatByTitleComponent: React.FC = ({ return useMemo( () => - iconOnly ? ( + asLink ? ( + + {children} + + ) : iconOnly ? ( { }; interface Props { + chatTitle?: string; markdownComment: string; indexName: string; } -const ChatActionComponent: FC = ({ indexName, markdownComment }) => { +const ChatActionComponent: FC = ({ indexName, markdownComment, chatTitle }) => { const styles = useStyles(); const { isAssistantEnabled } = useDataQualityContext(); const getPromptContext = useCallback(async () => markdownComment, [markdownComment]); - + const { showAssistantOverlay } = useAssistantOverlay( + 'data-quality-dashboard', + chatTitle ?? DATA_QUALITY_DASHBOARD_CONVERSATION_ID, + DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName), + getPromptContext, + null, + DATA_QUALITY_SUGGESTED_USER_PROMPT, + DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP, + isAssistantEnabled + ); return ( - + {ASK_ASSISTANT} - + ); }; diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/index.tsx index b1f4fa5911097..85111495970df 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/index.tsx @@ -13,6 +13,7 @@ import { CopyToClipboardAction } from './copy_to_clipboard'; import { AddToNewCaseAction } from './add_to_new_case'; export interface Props { + chatTitle?: string; markdownComment: string; indexName?: string; showAddToNewCaseAction?: boolean; @@ -21,6 +22,7 @@ export interface Props { } const ActionsComponent: React.FC = ({ + chatTitle, showAddToNewCaseAction, showCopyToClipboardAction, showChatAction, @@ -48,7 +50,11 @@ const ActionsComponent: React.FC = ({ {showChatAction && indexName && ( - + )} diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/historical_results/historical_results_list/historical_result/historical_check_fields/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/historical_results/historical_results_list/historical_result/historical_check_fields/index.tsx index d799418d54666..d34e6fbf2f72a 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/historical_results/historical_results_list/historical_result/historical_check_fields/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/historical_results/historical_results_list/historical_result/historical_check_fields/index.tsx @@ -36,6 +36,7 @@ const HistoricalCheckFieldsComponent: React.FC = ({ indexName, historical ecsFieldCount, customFieldCount, totalFieldCount, + checkedAt, } = historicalResult; const tabs = useMemo( @@ -47,6 +48,7 @@ const HistoricalCheckFieldsComponent: React.FC = ({ indexName, historical badgeCount: incompatibleFieldCount, content: ( = ({ + checkedAt, docsCount, ilmPhase, indexName, @@ -95,6 +98,10 @@ const IncompatibleTabComponent: React.FC = ({ ] ); + const chatTitle = useMemo(() => { + return `${indexName} - ${getFormattedCheckTime(checkedAt)}`; + }, [checkedAt, indexName]); + return (
{incompatibleFieldCount > 0 ? ( @@ -132,6 +139,7 @@ const IncompatibleTabComponent: React.FC = ({ {hasStickyActions ? ( = ({ /> ) : ( = ({ {selectedTabId === LATEST_CHECK_TAB_ID ? ( <> | null; indexName: string; patternRollup: PatternRollup | undefined; @@ -35,6 +36,7 @@ export interface Props { } const LatestResultsComponent: React.FC = ({ + checkedAt, indexName, patternRollup, stats, @@ -80,6 +82,7 @@ const LatestResultsComponent: React.FC = ({ = ({ + checkedAt, indexName, patternRollup, ilmPhase, @@ -95,6 +97,7 @@ const LatestCheckFieldsComponent: React.FC = ({ badgeCount: incompatibleFieldsCount, content: ( = ({ [ allFields, allFieldsCount, + checkedAt, customFields, customFieldsCount, docsCount, diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/latest_results/latest_check_fields/sticky_actions/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/latest_results/latest_check_fields/sticky_actions/index.tsx index 1664719fd6a4b..6eb20b2f584cc 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/latest_results/latest_check_fields/sticky_actions/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/latest_results/latest_check_fields/sticky_actions/index.tsx @@ -12,6 +12,7 @@ import { css } from '@emotion/react'; import { Actions } from '../../../../../../../actions'; interface Props { + chatTitle?: string; markdownComment: string; showAddToNewCaseAction?: boolean; showCopyToClipboardAction?: boolean; @@ -36,6 +37,7 @@ const useStyles = () => { }; const StickyActionsComponent: FC = ({ + chatTitle, indexName, markdownComment, showCopyToClipboardAction, @@ -47,6 +49,7 @@ const StickyActionsComponent: FC = ({ return (
Date: Wed, 19 Feb 2025 15:46:29 -0700 Subject: [PATCH 25/80] rule toolbar + rule status failed --- .../impl/new_chat_by_title/index.tsx | 8 +++-- .../data_quality_panel/actions/chat/index.tsx | 2 +- .../pages/rule_details/index.tsx | 1 + .../rules_table/rules_table_toolbar.tsx | 29 ++++++++++------ .../rule_status_failed_callout.tsx | 34 +++++++++++-------- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index 3cdfebaf0a118..d9f64ed9d083f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -14,6 +14,8 @@ export interface Props { /** Optionally render new chat as a link */ asLink?: boolean; children?: React.ReactNode; + /** Optionally specify color of empty button */ + color?: 'text' | 'accent' | 'primary' | 'success' | 'warning' | 'danger'; showAssistantOverlay: (show: boolean) => void; /** Defaults to `discuss`. If null, the button will not have an icon */ iconType?: string | null; @@ -24,6 +26,7 @@ export interface Props { const NewChatByTitleComponent: React.FC = ({ asLink = false, children = i18n.NEW_CHAT, + color = 'primary', showAssistantOverlay, iconType, iconOnly = false, @@ -44,7 +47,7 @@ const NewChatByTitleComponent: React.FC = ({ return useMemo( () => asLink ? ( - + {children} ) : iconOnly ? ( @@ -59,6 +62,7 @@ const NewChatByTitleComponent: React.FC = ({ ) : ( = ({ {children} ), - [children, icon, showOverlay, iconOnly] + [asLink, color, showOverlay, children, iconOnly, icon] ); }; diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx index 76199debddf44..67dc79ea7a507 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx @@ -52,7 +52,7 @@ const ChatActionComponent: FC = ({ indexName, markdownComment, chatTitle isAssistantEnabled ); return ( - + {ASK_ASSISTANT} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 5a5fa5e10795a..8fd01a090c919 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -433,6 +433,7 @@ const RuleDetailsPageComponent: React.FC = ({ ) : ( { () => rules.filter((rule) => selectedRuleIds.includes(rule.id)), [rules, selectedRuleIds] ); + + const selectedRuleNames = useMemo(() => selectedRules.map((rule) => rule.name), [selectedRules]); const getPromptContext = useCallback( async () => getPromptContextFromDetectionRules(selectedRules), [selectedRules] ); + const chatTitle = useMemo(() => { + return `${i18nAssistant.DETECTION_RULES_CONVERSATION_ID} - ${selectedRuleNames.join(', ')}`; + }, [selectedRuleNames]); + + const { showAssistantOverlay } = useAssistantOverlay( + 'detection-rules', + chatTitle, + i18nAssistant.RULE_MANAGEMENT_CONTEXT_DESCRIPTION, + getPromptContext, + null, + i18nAssistant.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS, + i18nAssistant.RULE_MANAGEMENT_CONTEXT_TOOLTIP, + isAssistantEnabled + ); + return ( @@ -98,15 +115,7 @@ export const RulesTableToolbar = React.memo(() => { {hasAssistantPrivilege && selectedRules.length > 0 && ( - + )} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx index 353a5bd0636f7..4602d2f56bb56 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx @@ -5,21 +5,21 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; -import { NewChat } from '@kbn/elastic-assistant'; +import { NewChatByTitle, useAssistantOverlay } from '@kbn/elastic-assistant'; import { FormattedDate } from '../../../../common/components/formatted_date'; import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring'; import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring'; import * as i18n from './translations'; -import * as i18nAssistant from '../../../pages/detection_engine/rules/translations'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; interface RuleStatusFailedCallOutProps { + ruleNameForChat: string; ruleName?: string | undefined; dataSources?: string[] | undefined; date: string; @@ -29,6 +29,7 @@ interface RuleStatusFailedCallOutProps { const RuleStatusFailedCallOutComponent: React.FC = ({ ruleName, + ruleNameForChat, dataSources, date, message, @@ -43,6 +44,20 @@ const RuleStatusFailedCallOutComponent: React.FC = : `Error message: ${message}`, [message, ruleName, dataSources] ); + + const chatTitle = useMemo(() => { + return `${ruleNameForChat} - ${title} ${date}`; + }, [date, title, ruleNameForChat]); + const { showAssistantOverlay } = useAssistantOverlay( + 'detection-rules', + chatTitle, + i18n.ASK_ASSISTANT_DESCRIPTION, + getPromptContext, + null, + i18n.ASK_ASSISTANT_USER_PROMPT, + i18n.ASK_ASSISTANT_TOOLTIP, + isAssistantEnabled + ); if (!shouldBeDisplayed) { return null; } @@ -77,18 +92,9 @@ const RuleStatusFailedCallOutComponent: React.FC = {message} {hasAssistantPrivilege && ( - + {i18n.ASK_ASSISTANT_ERROR_BUTTON} - + )}
From 65323ba8e92d7ad478b4ea7146f876454aadf9b2 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 16:05:26 -0700 Subject: [PATCH 26/80] better --- .../data_quality_panel/actions/chat/index.tsx | 26 +++++++++---------- .../components/ai_assistant/index.tsx | 9 +++++-- .../rules_table/rules_table_toolbar.tsx | 23 +++++++--------- .../rule_status_failed_callout.tsx | 26 +++++++++---------- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx index 67dc79ea7a507..6cdf688fd5172 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx @@ -6,7 +6,7 @@ */ import React, { FC, useCallback } from 'react'; -import { NewChatByTitle, useAssistantOverlay } from '@kbn/elastic-assistant'; +import { NewChat } from '@kbn/elastic-assistant'; import { css } from '@emotion/react'; import { useEuiTheme } from '@elastic/eui'; @@ -41,23 +41,23 @@ const ChatActionComponent: FC = ({ indexName, markdownComment, chatTitle const styles = useStyles(); const { isAssistantEnabled } = useDataQualityContext(); const getPromptContext = useCallback(async () => markdownComment, [markdownComment]); - const { showAssistantOverlay } = useAssistantOverlay( - 'data-quality-dashboard', - chatTitle ?? DATA_QUALITY_DASHBOARD_CONVERSATION_ID, - DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName), - getPromptContext, - null, - DATA_QUALITY_SUGGESTED_USER_PROMPT, - DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP, - isAssistantEnabled - ); return ( - + {ASK_ASSISTANT} - + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx index 8900e69254753..855591d5da792 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -91,6 +91,11 @@ Proposed solution should be valid and must not contain new line symbols (\\n)`; }, [getFields, setFieldValue] ); + const chatTitle = useMemo(() => { + const queryField = getFields().queryBar; + const { query } = (queryField.value as DefineStepRule['queryBar']).query; + return `${i18nAssistant.DETECTION_RULES_CREATE_FORM_CONVERSATION_ID} - ${query ?? 'query'}`; + }, [getFields]); if (!hasAssistantPrivilege) { return null; @@ -108,7 +113,7 @@ Proposed solution should be valid and must not contain new line symbols (\\n)`; { return `${i18nAssistant.DETECTION_RULES_CONVERSATION_ID} - ${selectedRuleNames.join(', ')}`; }, [selectedRuleNames]); - const { showAssistantOverlay } = useAssistantOverlay( - 'detection-rules', - chatTitle, - i18nAssistant.RULE_MANAGEMENT_CONTEXT_DESCRIPTION, - getPromptContext, - null, - i18nAssistant.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS, - i18nAssistant.RULE_MANAGEMENT_CONTEXT_TOOLTIP, - isAssistantEnabled - ); - return ( @@ -115,7 +104,15 @@ export const RulesTableToolbar = React.memo(() => { {hasAssistantPrivilege && selectedRules.length > 0 && ( - + )} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx index 4602d2f56bb56..0dcf5b1590114 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; -import { NewChatByTitle, useAssistantOverlay } from '@kbn/elastic-assistant'; +import { NewChat } from '@kbn/elastic-assistant'; import { FormattedDate } from '../../../../common/components/formatted_date'; import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring'; import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring'; @@ -48,16 +48,7 @@ const RuleStatusFailedCallOutComponent: React.FC = const chatTitle = useMemo(() => { return `${ruleNameForChat} - ${title} ${date}`; }, [date, title, ruleNameForChat]); - const { showAssistantOverlay } = useAssistantOverlay( - 'detection-rules', - chatTitle, - i18n.ASK_ASSISTANT_DESCRIPTION, - getPromptContext, - null, - i18n.ASK_ASSISTANT_USER_PROMPT, - i18n.ASK_ASSISTANT_TOOLTIP, - isAssistantEnabled - ); + if (!shouldBeDisplayed) { return null; } @@ -92,9 +83,18 @@ const RuleStatusFailedCallOutComponent: React.FC = {message} {hasAssistantPrivilege && ( - + {i18n.ASK_ASSISTANT_ERROR_BUTTON} - + )}
From 26c8f89d6637727c6bbd0495c1de9c82338272c5 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 16:06:49 -0700 Subject: [PATCH 27/80] revert new chat by title changes --- .../impl/new_chat_by_title/index.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index d9f64ed9d083f..3ea4007eeef51 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -5,36 +5,42 @@ * 2.0. */ -import { EuiButtonEmpty, EuiButtonIcon, EuiLink, EuiToolTip } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; +import { useAssistantContext } from '../assistant_context'; + import * as i18n from './translations'; export interface Props { - /** Optionally render new chat as a link */ - asLink?: boolean; children?: React.ReactNode; - /** Optionally specify color of empty button */ - color?: 'text' | 'accent' | 'primary' | 'success' | 'warning' | 'danger'; - showAssistantOverlay: (show: boolean) => void; + /** Optionally automatically add this context to a conversation when the assistant is shown */ + conversationTitle?: string; /** Defaults to `discuss`. If null, the button will not have an icon */ iconType?: string | null; + /** Optionally specify a well known ID, or default to a UUID */ + promptContextId?: string; /** Defaults to false. If true, shows icon button without text */ iconOnly?: boolean; } const NewChatByTitleComponent: React.FC = ({ - asLink = false, children = i18n.NEW_CHAT, - color = 'primary', - showAssistantOverlay, + conversationTitle, iconType, + promptContextId, iconOnly = false, }) => { + const { showAssistantOverlay } = useAssistantContext(); + // proxy show / hide calls to assistant context, using our internal prompt context id: const showOverlay = useCallback(() => { - showAssistantOverlay(true); - }, [showAssistantOverlay]); + showAssistantOverlay({ + conversationTitle, + promptContextId, + showOverlay: true, + }); + }, [conversationTitle, promptContextId, showAssistantOverlay]); const icon = useMemo(() => { if (iconType === null) { @@ -46,11 +52,7 @@ const NewChatByTitleComponent: React.FC = ({ return useMemo( () => - asLink ? ( - - {children} - - ) : iconOnly ? ( + iconOnly ? ( = ({ ) : ( = ({ {children} ), - [asLink, color, showOverlay, children, iconOnly, icon] + [children, icon, showOverlay, iconOnly] ); }; From b586e995ac7892edff1e2a4283768bc2fc276256 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 19 Feb 2025 16:10:18 -0700 Subject: [PATCH 28/80] unrevert --- .../impl/new_chat_by_title/index.tsx | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx index 3ea4007eeef51..9f21c5764fa1f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/new_chat_by_title/index.tsx @@ -8,39 +8,26 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useAssistantContext } from '../assistant_context'; - import * as i18n from './translations'; export interface Props { children?: React.ReactNode; - /** Optionally automatically add this context to a conversation when the assistant is shown */ - conversationTitle?: string; /** Defaults to `discuss`. If null, the button will not have an icon */ iconType?: string | null; - /** Optionally specify a well known ID, or default to a UUID */ - promptContextId?: string; + showAssistantOverlay: (show: boolean) => void; /** Defaults to false. If true, shows icon button without text */ iconOnly?: boolean; } const NewChatByTitleComponent: React.FC = ({ children = i18n.NEW_CHAT, - conversationTitle, iconType, - promptContextId, + showAssistantOverlay, iconOnly = false, }) => { - const { showAssistantOverlay } = useAssistantContext(); - - // proxy show / hide calls to assistant context, using our internal prompt context id: const showOverlay = useCallback(() => { - showAssistantOverlay({ - conversationTitle, - promptContextId, - showOverlay: true, - }); - }, [conversationTitle, promptContextId, showAssistantOverlay]); + showAssistantOverlay(true); + }, [showAssistantOverlay]); const icon = useMemo(() => { if (iconType === null) { From 8d59721704e957c644ad168ec7e982c1ddbd7d40 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 20 Feb 2025 09:57:42 -0700 Subject: [PATCH 29/80] anon updater --- .../use_anonymization_updater.ts | 120 ++++++++++++++++++ .../index.tsx | 93 ++++---------- 2 files changed, 144 insertions(+), 69 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts new file mode 100644 index 0000000000000..d4c6dc9cb2383 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen'; +import { useCallback, useEffect, useState } from 'react'; +import { PerformAnonymizationFieldsBulkActionRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import { BatchUpdateListItem } from '../../../data_anonymization_editor/context_editor/types'; +import { bulkUpdateAnonymizationFields } from '../../api/anonymization_fields/bulk_update_anonymization_fields'; + +const DEFAULT_ANONYMIZATION_FIELDS = { + page: 0, + perPage: 0, + total: 0, + data: [], +}; + +interface Params { + anonymizationFields: FindAnonymizationFieldsResponse; + http: HttpSetup; + toasts?: IToasts; +} + +interface AnonymizationUpdater { + hasPendingChanges: boolean; + onListUpdated: (updates: BatchUpdateListItem[]) => Promise; + resetAnonymizationSettings: () => void; + saveAnonymizationSettings: () => Promise; + updatedAnonymizationData: FindAnonymizationFieldsResponse; +} + +export const useAnonymizationUpdater = ({ + anonymizationFields = DEFAULT_ANONYMIZATION_FIELDS, + http, + toasts, +}: Params): AnonymizationUpdater => { + const [hasPendingChanges, setHasPendingChanges] = useState(false); + const [anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions] = + useState({}); + const [updatedAnonymizationData, setUpdatedAnonymizationData] = + useState(anonymizationFields); + + useEffect(() => { + if ( + !( + anonymizationFieldsBulkActions.create?.length || + anonymizationFieldsBulkActions.update?.length || + anonymizationFieldsBulkActions.delete?.ids?.length + ) + ) + setUpdatedAnonymizationData(anonymizationFields); + }, [ + anonymizationFields, + anonymizationFieldsBulkActions.create?.length, + anonymizationFieldsBulkActions.delete?.ids?.length, + anonymizationFieldsBulkActions.update?.length, + ]); + const resetAnonymizationSettings = useCallback(() => { + setHasPendingChanges(false); + setUpdatedAnonymizationData(anonymizationFields); + }, [anonymizationFields]); + + const saveAnonymizationSettings = useCallback(async (): Promise => { + const hasBulkAnonymizationFields = + anonymizationFieldsBulkActions.create || + anonymizationFieldsBulkActions.update || + anonymizationFieldsBulkActions.delete; + const bulkAnonymizationFieldsResult = hasBulkAnonymizationFields + ? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts) + : undefined; + + setHasPendingChanges(false); + return bulkAnonymizationFieldsResult?.success ?? true; + }, [anonymizationFieldsBulkActions, http, toasts]); + + const onListUpdated = useCallback( + async (updates: BatchUpdateListItem[]) => { + const updatedFieldsKeys = updates.map((u) => u.field); + const updatedFields = updates.map((u) => ({ + ...(updatedAnonymizationData.data.find((f) => f.field === u.field) ?? { + id: '', + field: '', + }), + ...(u.update === 'allow' || u.update === 'defaultAllow' + ? { allowed: u.operation === 'add' } + : {}), + ...(u.update === 'allowReplacement' || u.update === 'defaultAllowReplacement' + ? { anonymized: u.operation === 'add' } + : {}), + })); + setHasPendingChanges(true); + setAnonymizationFieldsBulkActions({ + ...anonymizationFieldsBulkActions, + // Only update makes sense now, as long as we don't have an add new field design/UX + update: [...(anonymizationFieldsBulkActions?.update ?? []), ...updatedFields], + }); + setUpdatedAnonymizationData({ + ...updatedAnonymizationData, + data: [ + ...updatedAnonymizationData.data.filter((f) => !updatedFieldsKeys.includes(f.field)), + ...updatedFields, + ], + }); + }, + [updatedAnonymizationData, anonymizationFieldsBulkActions] + ); + + return { + hasPendingChanges, + onListUpdated, + resetAnonymizationSettings, + saveAnonymizationSettings, + updatedAnonymizationData, + }; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx index 48f34eeab29b6..88adab5d78c8e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx @@ -19,21 +19,12 @@ import { EuiText, useEuiTheme, } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; +import { useAnonymizationUpdater } from '../../../assistant/settings/use_settings_updater/use_anonymization_updater'; import { Stats } from '../../../data_anonymization_editor/stats'; import { ContextEditor } from '../../../data_anonymization_editor/context_editor'; import * as i18n from '../anonymization_settings/translations'; -import { - useAnonymizationListUpdate, - UseAnonymizationListUpdateProps, -} from '../anonymization_settings/use_anonymization_list_update'; -import { - DEFAULT_ANONYMIZATION_FIELDS, - DEFAULT_CONVERSATIONS, - DEFAULT_PROMPTS, - useSettingsUpdater, -} from '../../../assistant/settings/use_settings_updater/use_settings_updater'; import { useFetchAnonymizationFields } from '../../../assistant/api/anonymization_fields/use_fetch_anonymization_fields'; import { AssistantSettingsBottomBar } from '../../../assistant/settings/assistant_settings_bottom_bar'; import { useAssistantContext } from '../../../assistant_context'; @@ -53,76 +44,40 @@ const AnonymizationSettingsManagementComponent: React.FC = ({ onClose, }) => { const { euiTheme } = useEuiTheme(); - const { toasts } = useAssistantContext(); - const { data: anonymizationFields } = useFetchAnonymizationFields(); - const [hasPendingChanges, setHasPendingChanges] = useState(false); + const { http, toasts } = useAssistantContext(); + const { data: anonymizationFields, refetch } = useFetchAnonymizationFields(); const { - anonymizationFieldsBulkActions, - setAnonymizationFieldsBulkActions, - setUpdatedAnonymizationData, - resetSettings, - saveSettings, + hasPendingChanges, + onListUpdated, + resetAnonymizationSettings, + saveAnonymizationSettings, updatedAnonymizationData, - } = useSettingsUpdater( - DEFAULT_CONVERSATIONS, // Anonymization settings do not require conversations - DEFAULT_PROMPTS, // Anonymization settings do not require prompts - false, // Anonymization settings do not require conversations - false, // Anonymization settings do not require prompts - anonymizationFields ?? DEFAULT_ANONYMIZATION_FIELDS - ); + } = useAnonymizationUpdater({ + anonymizationFields, + http, + toasts, + }); const onCancelClick = useCallback(() => { onClose?.(); - resetSettings(); - setHasPendingChanges(false); - }, [onClose, resetSettings]); - - const handleSave = useCallback( - async (param?: { callback?: () => void }) => { - await saveSettings(); - toasts?.addSuccess({ - iconType: 'check', - title: SETTINGS_UPDATED_TOAST_TITLE, - }); - setHasPendingChanges(false); - param?.callback?.(); - }, - [saveSettings, toasts] - ); + resetAnonymizationSettings(); + }, [onClose, resetAnonymizationSettings]); + + const handleSave = useCallback(async () => { + await saveAnonymizationSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + await refetch(); + }, [refetch, saveAnonymizationSettings, toasts]); const onSaveButtonClicked = useCallback(() => { handleSave(); onClose?.(); }, [handleSave, onClose]); - const handleAnonymizationFieldsBulkActions = useCallback< - UseAnonymizationListUpdateProps['setAnonymizationFieldsBulkActions'] - >( - (value) => { - setHasPendingChanges(true); - setAnonymizationFieldsBulkActions(value); - }, - [setAnonymizationFieldsBulkActions] - ); - - const handleUpdatedAnonymizationData = useCallback< - UseAnonymizationListUpdateProps['setUpdatedAnonymizationData'] - >( - (value) => { - setHasPendingChanges(true); - setUpdatedAnonymizationData(value); - }, - [setUpdatedAnonymizationData] - ); - - const onListUpdated = useAnonymizationListUpdate({ - anonymizationFields: updatedAnonymizationData, - anonymizationFieldsBulkActions, - setAnonymizationFieldsBulkActions: handleAnonymizationFieldsBulkActions, - setUpdatedAnonymizationData: handleUpdatedAnonymizationData, - }); - if (modalMode) { return ( From ca473245d533c287a5262f523ab62f91e897e610 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 20 Feb 2025 11:05:01 -0700 Subject: [PATCH 30/80] anon updater --- .../use_anonymization_updater.test.ts | 230 ++++++++++++++++++ .../use_anonymization_updater.ts | 4 +- .../context_editor/types.ts | 10 - 3 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.test.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.test.ts new file mode 100644 index 0000000000000..1dbbd36b1eed4 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.test.ts @@ -0,0 +1,230 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, act } from '@testing-library/react'; +import { useAnonymizationUpdater } from './use_anonymization_updater'; // Adjust the import path +import { bulkUpdateAnonymizationFields } from '../../api/anonymization_fields/bulk_update_anonymization_fields'; +import { HttpSetup } from '@kbn/core-http-browser'; +import { IToasts } from '@kbn/core-notifications-browser'; +import { BatchUpdateListItem } from '../../../data_anonymization_editor/context_editor/types'; + +jest.mock('../../api/anonymization_fields/bulk_update_anonymization_fields', () => ({ + bulkUpdateAnonymizationFields: jest.fn(), +})); +const mockField = { + timestamp: '2025-02-04T16:47:17.791Z', + createdAt: '2025-02-04T16:47:17.791Z', + field: 'user.name', + allowed: true, + anonymized: false, + updatedAt: '2025-02-20T16:56:58.086Z', + namespace: 'default', + id: 'blnb0ZQBQBIRhhJM6eMi', +}; +const mockField2 = { + timestamp: '2025-02-04T16:47:17.791Z', + createdAt: '2025-02-04T16:47:17.791Z', + field: 'host.name', + allowed: true, + anonymized: false, + updatedAt: '2025-02-14T16:56:58.086Z', + namespace: 'default', + id: 'blnb0ZQBQBIRhhJM6egi', +}; +const mockAnonymizationFields = { + perPage: 1000, + page: 1, + total: 2, + data: [mockField, mockField2], +}; + +const mockHttp = {} as HttpSetup; +const mockToasts = { + addSuccess: jest.fn(), + addDanger: jest.fn(), +} as unknown as IToasts; + +describe('useAnonymizationUpdater', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should initialize with default values', () => { + const { result } = renderHook(() => + useAnonymizationUpdater({ + anonymizationFields: mockAnonymizationFields, + http: mockHttp, + toasts: mockToasts, + }) + ); + + expect(result.current.hasPendingChanges).toBe(false); + expect(result.current.updatedAnonymizationData).toEqual(mockAnonymizationFields); + }); + + it('should update hasPendingChanges and updatedAnonymizationData on onListUpdated', async () => { + const { result } = renderHook(() => + useAnonymizationUpdater({ + anonymizationFields: mockAnonymizationFields, + http: mockHttp, + toasts: mockToasts, + }) + ); + + const updates: BatchUpdateListItem[] = [ + { + field: 'user.name', + update: 'allow', + operation: 'add', + }, + ]; + + await act(async () => { + await result.current.onListUpdated(updates); + }); + + expect(result.current.hasPendingChanges).toBe(true); + expect(result.current.updatedAnonymizationData.data[0].allowed).toBe(true); + }); + + it('should make updates to multiple fields and resets on cancel', async () => { + const mockBulkUpdate = bulkUpdateAnonymizationFields as jest.Mock; + mockBulkUpdate.mockResolvedValueOnce({ + success: true, + }); + + const { result } = renderHook(() => + useAnonymizationUpdater({ + anonymizationFields: mockAnonymizationFields, + http: mockHttp, + toasts: mockToasts, + }) + ); + + const update1: BatchUpdateListItem[] = [ + { + field: 'user.name', + update: 'allowReplacement', + operation: 'add', + }, + ]; + + const update2: BatchUpdateListItem[] = [ + { + field: 'host.name', + update: 'allowReplacement', + operation: 'add', + }, + ]; + + // allowed: true, + // anonymized: false, + await act(async () => { + await result.current.onListUpdated(update1); + }); + expect(result.current.hasPendingChanges).toBe(true); + expect(result.current.updatedAnonymizationData).toEqual({ + ...mockAnonymizationFields, + data: [mockField2, { ...mockField, anonymized: true }], + }); + await act(async () => { + await result.current.onListUpdated(update2); + }); + expect(result.current.updatedAnonymizationData).toEqual({ + ...mockAnonymizationFields, + data: [ + { ...mockField, anonymized: true }, + { ...mockField2, anonymized: true }, + ], + }); + + act(() => { + result.current.resetAnonymizationSettings(); + }); + + expect(result.current.hasPendingChanges).toBe(false); + expect(result.current.updatedAnonymizationData).toEqual(mockAnonymizationFields); + }); + + it('should call bulkUpdateAnonymizationFields on saveAnonymizationSettings', async () => { + const mockBulkUpdate = bulkUpdateAnonymizationFields as jest.Mock; + mockBulkUpdate.mockResolvedValueOnce({ + success: true, + }); + + const { result } = renderHook(() => + useAnonymizationUpdater({ + anonymizationFields: mockAnonymizationFields, + http: mockHttp, + toasts: mockToasts, + }) + ); + + const updates: BatchUpdateListItem[] = [ + { + field: 'user.name', + update: 'allow', + operation: 'remove', + }, + ]; + + await act(async () => { + await result.current.onListUpdated(updates); + }); + await act(async () => { + const success = await result.current.saveAnonymizationSettings(); + expect(success).toBe(true); + }); + + expect(mockBulkUpdate).toHaveBeenCalledWith( + mockHttp, + { + update: [ + { + ...mockField, + allowed: false, + anonymized: false, + }, + ], + }, + mockToasts + ); + expect(result.current.hasPendingChanges).toBe(false); + }); + + it('should handle failure in saveAnonymizationSettings', async () => { + (bulkUpdateAnonymizationFields as jest.Mock).mockResolvedValueOnce({ + success: false, + }); + + const { result } = renderHook(() => + useAnonymizationUpdater({ + anonymizationFields: mockAnonymizationFields, + http: mockHttp, + toasts: mockToasts, + }) + ); + + const updates: BatchUpdateListItem[] = [ + { + field: 'user.name', + update: 'allow', + operation: 'add', + }, + ]; + + await act(async () => { + await result.current.onListUpdated(updates); + }); + await act(async () => { + const success = await result.current.saveAnonymizationSettings(); + expect(success).toBe(false); + }); + + expect(bulkUpdateAnonymizationFields).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts index d4c6dc9cb2383..18f709260ffd8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_anonymization_updater.ts @@ -52,8 +52,9 @@ export const useAnonymizationUpdater = ({ anonymizationFieldsBulkActions.update?.length || anonymizationFieldsBulkActions.delete?.ids?.length ) - ) + ) { setUpdatedAnonymizationData(anonymizationFields); + } }, [ anonymizationFields, anonymizationFieldsBulkActions.create?.length, @@ -70,6 +71,7 @@ export const useAnonymizationUpdater = ({ anonymizationFieldsBulkActions.create || anonymizationFieldsBulkActions.update || anonymizationFieldsBulkActions.delete; + const bulkAnonymizationFieldsResult = hasBulkAnonymizationFields ? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts) : undefined; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/types.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/types.ts index baeaaf7267018..35425538c6275 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/types.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/types.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { Direction } from '@elastic/eui'; - export interface ContextEditorRow { /** Is the field is allowed to be included in the context sent to the assistant */ allowed: boolean; @@ -29,14 +27,6 @@ export const FIELDS = { ID: 'id', RAW_VALUES: 'rawValues', }; - -export interface SortConfig { - sort: { - direction: Direction; - field: string; - }; -} - /** The `field` in the specified `list` will be added or removed, as specified by the `operation` */ export interface BatchUpdateListItem { field: string; From 37cc51ac4ddcd781f6e3c92db011d8cb5cfbbb40 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 20 Feb 2025 11:27:14 -0700 Subject: [PATCH 31/80] fix prompt context --- .../impl/assistant/assistant_header/index.tsx | 2 +- .../impl/assistant/chat_send/index.tsx | 4 ---- .../kbn-elastic-assistant/impl/assistant/index.tsx | 12 +++++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index c139c995e53b0..8ce5db28c9276 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -161,7 +161,7 @@ export const AssistantHeader: React.FC = ({ ) : ( = ({ useAutosizeTextArea(promptTextAreaRef?.current, promptValue); - useEffect(() => { - setUserPrompt(promptValue); - }, [setUserPrompt, promptValue]); - return ( = ({ }); useEffect(() => { - // reset PromptContexts state when conversation changes if ( autoPopulatedOnce && currentConversation && lastConversation?.title !== currentConversation?.title ) { + // reset PromptContexts state when conversation changes setAutoPopulatedOnce(false); setSelectedPromptContexts({}); setUserPrompt(null); - return; } - if (currentConversation?.messages.length || autoPopulatedOnce) { + }, [autoPopulatedOnce, currentConversation, lastConversation?.title, setUserPrompt]); + + useEffect(() => { + if ( + (currentConversation && lastConversation?.title !== currentConversation?.title) || + currentConversation?.messages.length || + autoPopulatedOnce + ) { return; } From eea73d1f114c276c4c9eb66f1dcccd256b397315 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 20 Feb 2025 11:42:19 -0700 Subject: [PATCH 32/80] alert settings modal --- .../alerts_settings/alerts_settings_modal.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx index 4e362a4bec8be..3f2109db8e3f2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings_modal.tsx @@ -15,12 +15,9 @@ import { EuiModalHeader, EuiModalHeaderTitle, } from '@elastic/eui'; +import { useAssistantContext } from '../../../..'; +import { useKnowledgeBaseUpdater } from '../use_settings_updater/use_knowledge_base_updater'; import { ALERTS_LABEL } from '../../../knowledge_base/translations'; -import { - DEFAULT_CONVERSATIONS, - DEFAULT_PROMPTS, - useSettingsUpdater, -} from '../use_settings_updater/use_settings_updater'; import { AlertsSettings } from './alerts_settings'; import { CANCEL, SAVE } from '../translations'; @@ -29,17 +26,15 @@ interface AlertSettingsModalProps { } export const AlertsSettingsModal = ({ onClose }: AlertSettingsModalProps) => { - const { knowledgeBase, setUpdatedKnowledgeBaseSettings, saveSettings } = useSettingsUpdater( - DEFAULT_CONVERSATIONS, // Alerts settings do not require conversations - DEFAULT_PROMPTS, // Alerts settings do not require prompts - false, // Alerts settings do not require conversations - false // Alerts settings do not require prompts - ); + const { assistantTelemetry, knowledgeBase, setKnowledgeBase } = useAssistantContext(); + + const { knowledgeBaseSettings, saveKnowledgeBaseSettings, setUpdatedKnowledgeBaseSettings } = + useKnowledgeBaseUpdater({ assistantTelemetry, knowledgeBase, setKnowledgeBase }); const handleSave = useCallback(() => { - saveSettings(); + saveKnowledgeBaseSettings(); onClose(); - }, [onClose, saveSettings]); + }, [onClose, saveKnowledgeBaseSettings]); return ( @@ -48,7 +43,7 @@ export const AlertsSettingsModal = ({ onClose }: AlertSettingsModalProps) => { From d2f00858aa5174973e11fb971aeeb6795df9285c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 20 Feb 2025 14:42:38 -0700 Subject: [PATCH 33/80] modal refactor --- .../impl/assistant/assistant_header/index.tsx | 1 - .../conversation_settings.test.tsx | 343 ------------------ .../conversation_settings.tsx | 153 -------- .../conversation_settings_editor.tsx | 9 +- .../system_prompt_settings.tsx | 34 +- .../system_prompt_modal/types.ts | 31 +- .../quick_prompt_settings.tsx | 43 ++- .../assistant/settings/assistant_settings.tsx | 259 +++++-------- .../settings/assistant_settings_modal.tsx | 2 - .../impl/assistant/settings/index.tsx | 2 - .../impl/assistant/settings/types.ts | 18 +- .../use_conversations_updater.ts | 4 +- .../connector_missing_callout/index.tsx | 87 ++--- .../settings/anonymization_settings/index.tsx | 21 +- 14 files changed, 185 insertions(+), 822 deletions(-) delete mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx delete mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index 8ce5db28c9276..0fbcb02f1e39a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -119,7 +119,6 @@ export const AssistantHeader: React.FC = ({ defaultConnector={defaultConnector} isDisabled={isDisabled} isSettingsModalVisible={isSettingsModalVisible} - selectedConversationId={selectedConversation?.id} setIsSettingsModalVisible={setIsSettingsModalVisible} onConversationSelected={onConversationSelected} conversations={conversations} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx deleted file mode 100644 index 9897fc9dc3d97..0000000000000 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; -import { ConversationSettings, ConversationSettingsProps } from './conversation_settings'; -import { TestProviders } from '../../../mock/test_providers/test_providers'; -import { alertConvo, customConvo, welcomeConvo } from '../../../mock/conversation'; -import { mockSystemPrompts } from '../../../mock/system_prompt'; -import { mockConnectors } from '../../../mock/connectors'; -import { HttpSetup } from '@kbn/core/public'; - -const mockConvos = { - '1234': { ...welcomeConvo, id: '1234' }, - '12345': { ...alertConvo, id: '12345' }, - '123': { ...customConvo, id: '123' }, -}; -const onSelectedConversationChange = jest.fn(); - -const setConversationSettings = jest.fn(); -const setConversationsSettingsBulkActions = jest.fn(); - -const testProps: ConversationSettingsProps = { - allSystemPrompts: mockSystemPrompts, - assistantStreamingEnabled: false, - connectors: mockConnectors, - conversationSettings: mockConvos, - conversationsSettingsBulkActions: {}, - http: { basePath: { get: jest.fn() } } as unknown as HttpSetup, - onSelectedConversationChange, - selectedConversation: mockConvos['1234'], - setAssistantStreamingEnabled: jest.fn(), - setConversationSettings, - setConversationsSettingsBulkActions, -}; - -jest.mock('../../../connectorland/use_load_connectors', () => ({ - useLoadConnectors: () => ({ - data: mockConnectors, - error: null, - isSuccess: true, - }), -})); - -const mockConvo = mockConvos['12345']; -jest.mock('../conversation_selector_settings', () => ({ - // @ts-ignore - ConversationSelectorSettings: ({ onConversationDeleted, onConversationSelectionChange }) => ( - <> -