From f7ce6a7ff9cd7ab38438d4dbbe253b82d547dad5 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Thu, 10 Aug 2023 16:55:22 +0200 Subject: [PATCH] Tweaking the timeline --- .../common/types.ts | 2 +- .../public/components/chat/chat_item.tsx | 4 +- .../components/chat/chat_prompt_editor.tsx | 222 +++++++++--------- .../public/components/chat/chat_timeline.tsx | 4 +- .../get_timeline_items_from_conversation.tsx | 73 +++--- 5 files changed, 157 insertions(+), 148 deletions(-) diff --git a/x-pack/plugins/observability_ai_assistant/common/types.ts b/x-pack/plugins/observability_ai_assistant/common/types.ts index 59e0b85cdc5d58..8ef052cb8e5174 100644 --- a/x-pack/plugins/observability_ai_assistant/common/types.ts +++ b/x-pack/plugins/observability_ai_assistant/common/types.ts @@ -28,7 +28,7 @@ export interface Message { function_call?: { name: string; arguments?: string; - trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic; + trigger: MessageRole; }; data?: string; }; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx index e66a32035a0d5f..f7ef7b338009f4 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx @@ -113,8 +113,8 @@ export function ChatItem({ }; const handleToggleEdit = () => { - if (collapsed) { - setExpanded(!expanded); + if (collapsed && !expanded) { + setExpanded(true); } setEditing(!editing); }; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx index 8d1559c34c1983..827251990f4294 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_prompt_editor.tsx @@ -15,6 +15,7 @@ import { EuiSpacer, EuiPanel, keys, + EuiFocusTrap, } from '@elastic/eui'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import { i18n } from '@kbn/i18n'; @@ -43,6 +44,8 @@ export function ChatPromptEditor({ const { getFunctions } = useObservabilityAIAssistant(); const functions = getFunctions(); + const isFocusTrapEnabled = Boolean(initialPrompt); + const [prompt, setPrompt] = useState(initialPrompt); const [selectedFunctionName, setSelectedFunctionName] = useState( @@ -103,7 +106,8 @@ export function ChatPromptEditor({ await onSubmit({ '@timestamp': new Date().toISOString(), message: { - role: MessageRole.User, + role: MessageRole.Assistant, + content: '', function_call: { name: selectedFunctionName, trigger: MessageRole.User, @@ -128,7 +132,7 @@ export function ChatPromptEditor({ useEffect(() => { const keyboardListener = (event: KeyboardEvent) => { - if (!event.shiftKey && event.key === keys.ENTER) { + if (!event.shiftKey && event.key === keys.ENTER && (prompt || selectedFunctionName)) { event.preventDefault(); handleSubmit(); } @@ -139,7 +143,7 @@ export function ChatPromptEditor({ return () => { window.removeEventListener('keypress', keyboardListener); }; - }, [handleSubmit]); + }, [handleSubmit, prompt, selectedFunctionName]); useEffect(() => { const textarea = textAreaRef.current; @@ -155,112 +159,114 @@ export function ChatPromptEditor({ }); return ( - - - - - - - - - - {selectedFunctionName ? ( - - {i18n.translate('xpack.observabilityAiAssistant.prompt.emptySelection', { - defaultMessage: 'Empty selection', - })} - - ) : null} - - - - - {selectedFunctionName ? ( - - + + + + + + + + + + {selectedFunctionName ? ( + + {i18n.translate('xpack.observabilityAiAssistant.prompt.emptySelection', { + defaultMessage: 'Empty selection', + })} + + ) : null} + + + + + {selectedFunctionName ? ( + + { + editor.focus(); + }} + options={{ + accessibilitySupport: 'off', + acceptSuggestionOnEnter: 'on', + automaticLayout: true, + autoClosingQuotes: 'always', + autoIndent: 'full', + contextmenu: true, + fontSize: 12, + formatOnPaste: true, + formatOnType: true, + inlineHints: { enabled: true }, + lineNumbers: 'on', + minimap: { enabled: false }, + model, + overviewRulerBorder: false, + quickSuggestions: true, + scrollbar: { alwaysConsumeMouseWheel: false }, + scrollBeyondLastLine: false, + suggestOnTriggerCharacters: true, + tabSize: 2, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + transparentBackground + value={functionPayload || ''} + onChange={handleChangeFunctionPayload} + /> + + ) : ( + { - editor.focus(); - }} - options={{ - accessibilitySupport: 'off', - acceptSuggestionOnEnter: 'on', - automaticLayout: true, - autoClosingQuotes: 'always', - autoIndent: 'full', - contextmenu: true, - fontSize: 12, - formatOnPaste: true, - formatOnType: true, - inlineHints: { enabled: true }, - lineNumbers: 'on', - minimap: { enabled: false }, - model, - overviewRulerBorder: false, - quickSuggestions: true, - scrollbar: { alwaysConsumeMouseWheel: false }, - scrollBeyondLastLine: false, - suggestOnTriggerCharacters: true, - tabSize: 2, - wordWrap: 'on', - wrappingIndent: 'indent', - }} - transparentBackground - value={functionPayload || ''} - onChange={handleChangeFunctionPayload} + inputRef={textAreaRef} + placeholder={i18n.translate('xpack.observabilityAiAssistant.prompt.placeholder', { + defaultMessage: 'Press ‘$’ for function recommendations', + })} + resize="vertical" + rows={1} + value={prompt} + onChange={handleChange} /> - - ) : ( - - )} - - - - - - - - + )} + + + + + + + + + ); } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx index 45d1dac7888710..c953ce91867959 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx @@ -7,7 +7,7 @@ import { EuiCommentList } from '@elastic/eui'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { type Message } from '../../../common'; import type { Feedback } from '../feedback_buttons'; import { ChatItem } from './chat_item'; @@ -16,7 +16,7 @@ export interface ChatTimelineItem extends Pick, Pick { id: string; - title: string; + title: ReactNode; loading: boolean; canCopy: boolean; canEdit: boolean; diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx index a030c3d74d96db..6bd60f081445fd 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/utils/get_timeline_items_from_conversation.tsx @@ -14,7 +14,8 @@ import { type Message, MessageRole } from '../../common'; import type { FunctionDefinition } from '../../common/types'; function convertFunctionParamsToMarkdownCodeBlock(object: Record) { - return `\`\`\` + return ` +\`\`\` ${JSON.stringify(object, null, 4)} \`\`\``; } @@ -49,15 +50,16 @@ export function getTimelineItemsfromConversation({ }, ...messages.map((message, index) => { const id = v4(); - const role = message.message.role; let title: string = ''; let content: string | undefined; let element: React.ReactNode | undefined; - const functionCall = message.message.name - ? messages[index - 1].message.function_call - : message.message.function_call; + const role = message.message.name ? message.message.role : message.message.role; + const functionCall = + message.message.name && messages[index - 1] && messages[index - 1].message.function_call + ? messages[index - 1].message.function_call + : message.message.function_call; let canCopy: boolean = false; let canEdit: boolean = false; @@ -72,41 +74,26 @@ export function getTimelineItemsfromConversation({ break; case MessageRole.User: - // is a prompt by the user - if (!message.message.name) { - title = ''; - content = message.message.content; - - canCopy = true; - canEdit = hasConnector; - canGiveFeedback = false; - canRegenerate = false; - collapsed = false; - hide = false; - } else { - // user has executed a function - const prevMessage = messages[index - 1]; - if (!prevMessage || !prevMessage.message.function_call) { - throw new Error('Could not find preceding message with function_call'); - } - + // User executed a function: + if (functionCall) { title = i18n.translate('xpack.observabilityAiAssistant.executedFunctionEvent', { defaultMessage: 'executed the function {functionName}', values: { - functionName: prevMessage.message.function_call!.name, + functionName: functionCall.name, }, }); - content = convertFunctionParamsToMarkdownCodeBlock( - JSON.parse(message.message.content || '{}') - ); + content = convertFunctionParamsToMarkdownCodeBlock({ + name: functionCall.name, + arguments: JSON.parse(functionCall.arguments || '{}'), + }); const fn = functions.find((func) => func.options.name === message.message.name); element = fn?.render ? ( ) : null; @@ -117,22 +104,38 @@ export function getTimelineItemsfromConversation({ canRegenerate = hasConnector; collapsed = !Boolean(element); hide = false; + } else { + // is a prompt by the user + title = ''; + content = message.message.content; + + canCopy = true; + canEdit = hasConnector; + canGiveFeedback = false; + canRegenerate = false; + collapsed = false; + hide = false; } + break; case MessageRole.Assistant: // is a function suggestion by the assistant - if (!!message.message.function_call?.name) { + if (!!functionCall?.name) { title = i18n.translate('xpack.observabilityAiAssistant.suggestedFunctionEvent', { defaultMessage: 'suggested to use function {functionName}', values: { - functionName: message.message.function_call!.name, + functionName: functionCall!.name, }, }); - content = convertFunctionParamsToMarkdownCodeBlock({ - name: message.message.function_call!.name, - arguments: JSON.parse(message.message.function_call?.arguments || '{}'), - }); + content = + i18n.translate('xpack.observabilityAiAssistant.responseWas', { + defaultMessage: 'Suggested the payload: ', + }) + + convertFunctionParamsToMarkdownCodeBlock({ + name: functionCall!.name, + arguments: JSON.parse(functionCall?.arguments || '{}'), + }); canCopy = true; canEdit = false;