mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
## Summary Resolves System Prompt not sending issues: https://github.com/elastic/kibana/issues/161809 Also resolves: - [X] Not being able to delete really long Conversation, System Prompt, and Quick Prompt names - [X] Fix user/all System Prompts being overridden on refresh - [X] Conversation without default System Prompt not healed if it is initial conversation when Assistant opens (Timeline) - [X] New conversation created from Conversations Settings not getting a connector by default - [X] Current conversation not selected by default when settings gear is clicked (and other assistant instances exist) - [X] Sent to Timeline action sends anonymized values instead of actual plaintext - [X] Clicking Submit does not clear the text area - [X] Remove System Prompt Tooltip - [X] Fixes confusion when System or Quick Prompt is empty by adding a placeholder value - [X] Shows (empty prompt) in System Prompt selector when the Prompt content is empty - [X] Fixes connector error callout flashing on initial load - [X] Shows `(empty prompt)` text within Prompt Editor when prompt content is empty to prevent confusion ### Checklist Delete any items that are not applicable to this PR. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
321 lines
11 KiB
TypeScript
321 lines
11 KiB
TypeScript
/*
|
|
* 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 { EuiCommentProps } from '@elastic/eui';
|
|
import type { HttpSetup } from '@kbn/core-http-browser';
|
|
import { omit, uniq } from 'lodash/fp';
|
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
|
import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
|
import { useLocalStorage } from 'react-use';
|
|
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
|
import { updatePromptContexts } from './helpers';
|
|
import type {
|
|
PromptContext,
|
|
RegisterPromptContext,
|
|
UnRegisterPromptContext,
|
|
} from '../assistant/prompt_context/types';
|
|
import type { Conversation } from './types';
|
|
import { DEFAULT_ASSISTANT_TITLE } from '../assistant/translations';
|
|
import { CodeBlockDetails } from '../assistant/use_conversation/helpers';
|
|
import { PromptContextTemplate } from '../assistant/prompt_context/types';
|
|
import { QuickPrompt } from '../assistant/quick_prompts/types';
|
|
import { Prompt } from '../assistant/types';
|
|
import { BASE_SYSTEM_PROMPTS } from '../content/prompts/system';
|
|
import {
|
|
DEFAULT_ASSISTANT_NAMESPACE,
|
|
LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY,
|
|
QUICK_PROMPT_LOCAL_STORAGE_KEY,
|
|
SYSTEM_PROMPT_LOCAL_STORAGE_KEY,
|
|
} from './constants';
|
|
import { CONVERSATIONS_TAB, SettingsTabs } from '../assistant/settings/assistant_settings';
|
|
|
|
export interface ShowAssistantOverlayProps {
|
|
showOverlay: boolean;
|
|
promptContextId?: string;
|
|
conversationId?: string;
|
|
}
|
|
|
|
type ShowAssistantOverlay = ({
|
|
showOverlay,
|
|
promptContextId,
|
|
conversationId,
|
|
}: ShowAssistantOverlayProps) => void;
|
|
interface AssistantProviderProps {
|
|
actionTypeRegistry: ActionTypeRegistryContract;
|
|
augmentMessageCodeBlocks: (currentConversation: Conversation) => CodeBlockDetails[][];
|
|
baseAllow: string[];
|
|
baseAllowReplacement: string[];
|
|
defaultAllow: string[];
|
|
defaultAllowReplacement: string[];
|
|
basePromptContexts?: PromptContextTemplate[];
|
|
baseQuickPrompts?: QuickPrompt[];
|
|
baseSystemPrompts?: Prompt[];
|
|
docLinks: Omit<DocLinksStart, 'links'>;
|
|
children: React.ReactNode;
|
|
getComments: ({
|
|
currentConversation,
|
|
lastCommentRef,
|
|
showAnonymizedValues,
|
|
}: {
|
|
currentConversation: Conversation;
|
|
lastCommentRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
showAnonymizedValues: boolean;
|
|
}) => EuiCommentProps[];
|
|
http: HttpSetup;
|
|
getInitialConversations: () => Record<string, Conversation>;
|
|
nameSpace?: string;
|
|
setConversations: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
|
setDefaultAllow: React.Dispatch<React.SetStateAction<string[]>>;
|
|
setDefaultAllowReplacement: React.Dispatch<React.SetStateAction<string[]>>;
|
|
title?: string;
|
|
}
|
|
|
|
export interface UseAssistantContext {
|
|
actionTypeRegistry: ActionTypeRegistryContract;
|
|
augmentMessageCodeBlocks: (currentConversation: Conversation) => CodeBlockDetails[][];
|
|
allQuickPrompts: QuickPrompt[];
|
|
allSystemPrompts: Prompt[];
|
|
baseAllow: string[];
|
|
baseAllowReplacement: string[];
|
|
docLinks: Omit<DocLinksStart, 'links'>;
|
|
defaultAllow: string[];
|
|
defaultAllowReplacement: string[];
|
|
basePromptContexts: PromptContextTemplate[];
|
|
baseQuickPrompts: QuickPrompt[];
|
|
baseSystemPrompts: Prompt[];
|
|
conversationIds: string[];
|
|
conversations: Record<string, Conversation>;
|
|
getComments: ({
|
|
currentConversation,
|
|
lastCommentRef,
|
|
showAnonymizedValues,
|
|
}: {
|
|
currentConversation: Conversation;
|
|
lastCommentRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
|
|
showAnonymizedValues: boolean;
|
|
}) => EuiCommentProps[];
|
|
http: HttpSetup;
|
|
localStorageLastConversationId: string | undefined;
|
|
promptContexts: Record<string, PromptContext>;
|
|
nameSpace: string;
|
|
registerPromptContext: RegisterPromptContext;
|
|
selectedSettingsTab: SettingsTabs;
|
|
setAllQuickPrompts: React.Dispatch<React.SetStateAction<QuickPrompt[] | undefined>>;
|
|
setAllSystemPrompts: React.Dispatch<React.SetStateAction<Prompt[] | undefined>>;
|
|
setConversations: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
|
setDefaultAllow: React.Dispatch<React.SetStateAction<string[]>>;
|
|
setDefaultAllowReplacement: React.Dispatch<React.SetStateAction<string[]>>;
|
|
setLastConversationId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
setSelectedSettingsTab: React.Dispatch<React.SetStateAction<SettingsTabs>>;
|
|
setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void;
|
|
showAssistantOverlay: ShowAssistantOverlay;
|
|
title: string;
|
|
unRegisterPromptContext: UnRegisterPromptContext;
|
|
}
|
|
|
|
const AssistantContext = React.createContext<UseAssistantContext | undefined>(undefined);
|
|
|
|
export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|
actionTypeRegistry,
|
|
augmentMessageCodeBlocks,
|
|
baseAllow,
|
|
baseAllowReplacement,
|
|
defaultAllow,
|
|
defaultAllowReplacement,
|
|
docLinks,
|
|
basePromptContexts = [],
|
|
baseQuickPrompts = [],
|
|
baseSystemPrompts = BASE_SYSTEM_PROMPTS,
|
|
children,
|
|
getComments,
|
|
http,
|
|
getInitialConversations,
|
|
nameSpace = DEFAULT_ASSISTANT_NAMESPACE,
|
|
setConversations,
|
|
setDefaultAllow,
|
|
setDefaultAllowReplacement,
|
|
title = DEFAULT_ASSISTANT_TITLE,
|
|
}) => {
|
|
/**
|
|
* Local storage for all quick prompts, prefixed by assistant nameSpace
|
|
*/
|
|
const [localStorageQuickPrompts, setLocalStorageQuickPrompts] = useLocalStorage(
|
|
`${nameSpace}.${QUICK_PROMPT_LOCAL_STORAGE_KEY}`,
|
|
baseQuickPrompts
|
|
);
|
|
|
|
/**
|
|
* Local storage for all system prompts, prefixed by assistant nameSpace
|
|
*/
|
|
const [localStorageSystemPrompts, setLocalStorageSystemPrompts] = useLocalStorage(
|
|
`${nameSpace}.${SYSTEM_PROMPT_LOCAL_STORAGE_KEY}`,
|
|
baseSystemPrompts
|
|
);
|
|
|
|
const [localStorageLastConversationId, setLocalStorageLastConversationId] =
|
|
useLocalStorage<string>(`${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}`);
|
|
|
|
/**
|
|
* Prompt contexts are used to provide components a way to register and make their data available to the assistant.
|
|
*/
|
|
const [promptContexts, setPromptContexts] = useState<Record<string, PromptContext>>({});
|
|
|
|
const registerPromptContext: RegisterPromptContext = useCallback(
|
|
(promptContext: PromptContext) => {
|
|
setPromptContexts((prevPromptContexts) => {
|
|
if (promptContext != null && prevPromptContexts[promptContext.id] == null) {
|
|
return updatePromptContexts({
|
|
prevPromptContexts,
|
|
promptContext,
|
|
});
|
|
} else {
|
|
return prevPromptContexts;
|
|
}
|
|
});
|
|
},
|
|
[]
|
|
);
|
|
|
|
const unRegisterPromptContext: UnRegisterPromptContext = useCallback(
|
|
(queryContextId: string) =>
|
|
setPromptContexts((prevPromptContexts) => {
|
|
if (prevPromptContexts[queryContextId] == null) {
|
|
return prevPromptContexts;
|
|
} else {
|
|
return omit(queryContextId, prevPromptContexts);
|
|
}
|
|
}),
|
|
[]
|
|
);
|
|
|
|
/**
|
|
* Global Assistant Overlay actions
|
|
*/
|
|
const [showAssistantOverlay, setShowAssistantOverlay] = useState<ShowAssistantOverlay>(
|
|
(showAssistant) => {}
|
|
);
|
|
|
|
/**
|
|
* Settings State
|
|
*/
|
|
const [selectedSettingsTab, setSelectedSettingsTab] = useState<SettingsTabs>(CONVERSATIONS_TAB);
|
|
|
|
const [conversations, setConversationsInternal] = useState(getInitialConversations());
|
|
const conversationIds = useMemo(() => Object.keys(conversations).sort(), [conversations]);
|
|
|
|
// TODO: This is a fix for conversations not loading out of localstorage. Also re-introduces our cascading render issue (as it loops back in localstorage)
|
|
useEffect(() => {
|
|
setConversationsInternal(getInitialConversations());
|
|
}, [getInitialConversations]);
|
|
|
|
const onConversationsUpdated = useCallback<
|
|
React.Dispatch<React.SetStateAction<Record<string, Conversation>>>
|
|
>(
|
|
(
|
|
newConversations:
|
|
| Record<string, Conversation>
|
|
| ((prev: Record<string, Conversation>) => Record<string, Conversation>)
|
|
) => {
|
|
if (typeof newConversations === 'function') {
|
|
const updater = newConversations;
|
|
setConversationsInternal((prevValue) => {
|
|
const newValue = updater(prevValue);
|
|
setConversations(newValue);
|
|
return newValue;
|
|
});
|
|
} else {
|
|
setConversations(newConversations);
|
|
setConversationsInternal(newConversations);
|
|
}
|
|
},
|
|
[setConversations]
|
|
);
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
actionTypeRegistry,
|
|
augmentMessageCodeBlocks,
|
|
allQuickPrompts: localStorageQuickPrompts ?? [],
|
|
allSystemPrompts: localStorageSystemPrompts ?? [],
|
|
baseAllow: uniq(baseAllow),
|
|
baseAllowReplacement: uniq(baseAllowReplacement),
|
|
basePromptContexts,
|
|
baseQuickPrompts,
|
|
baseSystemPrompts,
|
|
conversationIds,
|
|
conversations,
|
|
defaultAllow: uniq(defaultAllow),
|
|
defaultAllowReplacement: uniq(defaultAllowReplacement),
|
|
docLinks,
|
|
getComments,
|
|
http,
|
|
promptContexts,
|
|
nameSpace,
|
|
registerPromptContext,
|
|
selectedSettingsTab,
|
|
setAllQuickPrompts: setLocalStorageQuickPrompts,
|
|
setAllSystemPrompts: setLocalStorageSystemPrompts,
|
|
setConversations: onConversationsUpdated,
|
|
setDefaultAllow,
|
|
setDefaultAllowReplacement,
|
|
setSelectedSettingsTab,
|
|
setShowAssistantOverlay,
|
|
showAssistantOverlay,
|
|
title,
|
|
unRegisterPromptContext,
|
|
localStorageLastConversationId,
|
|
setLastConversationId: setLocalStorageLastConversationId,
|
|
}),
|
|
[
|
|
actionTypeRegistry,
|
|
augmentMessageCodeBlocks,
|
|
baseAllow,
|
|
baseAllowReplacement,
|
|
basePromptContexts,
|
|
baseQuickPrompts,
|
|
baseSystemPrompts,
|
|
conversationIds,
|
|
conversations,
|
|
defaultAllow,
|
|
defaultAllowReplacement,
|
|
docLinks,
|
|
getComments,
|
|
http,
|
|
localStorageLastConversationId,
|
|
localStorageQuickPrompts,
|
|
localStorageSystemPrompts,
|
|
nameSpace,
|
|
onConversationsUpdated,
|
|
promptContexts,
|
|
registerPromptContext,
|
|
selectedSettingsTab,
|
|
setDefaultAllow,
|
|
setDefaultAllowReplacement,
|
|
setLocalStorageLastConversationId,
|
|
setLocalStorageQuickPrompts,
|
|
setLocalStorageSystemPrompts,
|
|
setSelectedSettingsTab,
|
|
showAssistantOverlay,
|
|
title,
|
|
unRegisterPromptContext,
|
|
]
|
|
);
|
|
|
|
return <AssistantContext.Provider value={value}>{children}</AssistantContext.Provider>;
|
|
};
|
|
|
|
export const useAssistantContext = () => {
|
|
const context = React.useContext(AssistantContext);
|
|
|
|
if (context == null) {
|
|
throw new Error('useAssistantContext must be used within a AssistantProvider');
|
|
}
|
|
|
|
return context;
|
|
};
|