mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.12] Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175) (#173450)
# Backport This will backport the following commits from `main` to `8.12`: - [Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175)](https://github.com/elastic/kibana/pull/173175) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Coen Warmer","email":"coen.warmer@gmail.com"},"sourceCommit":{"committedDate":"2023-12-15T09:21:20Z","message":"Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.12.0","v8.13.0"],"number":173175,"url":"https://github.com/elastic/kibana/pull/173175","mergeCommit":{"message":"Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173175","number":173175,"mergeCommit":{"message":"Adds EBT event for User Prompts, refactor existing EBT event tracking to be typesafe (#173175)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"13ad0ef510ec23a380335935b4557efc01f01f2d"}}]}] BACKPORT--> Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
parent
1b761f16b0
commit
ba5f326a3f
18 changed files with 416 additions and 147 deletions
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser';
|
||||
import type { Message } from '../../common';
|
||||
import {
|
||||
eventType as chatFeedbackEventType,
|
||||
chatFeedbackEventSchema,
|
||||
ChatFeedback,
|
||||
} from './schemas/chat_feedback';
|
||||
import {
|
||||
eventType as insightFeedbackEventType,
|
||||
insightFeedbackEventSchema,
|
||||
InsightFeedback,
|
||||
} from './schemas/insight_feedback';
|
||||
import {
|
||||
eventType as userSentPromptEventType,
|
||||
userSentPromptEventSchema,
|
||||
} from './schemas/user_sent_prompt';
|
||||
|
||||
const schemas = [chatFeedbackEventSchema, insightFeedbackEventSchema, userSentPromptEventSchema];
|
||||
|
||||
export const TELEMETRY = {
|
||||
[chatFeedbackEventType]: chatFeedbackEventType,
|
||||
[insightFeedbackEventType]: insightFeedbackEventType,
|
||||
[userSentPromptEventType]: userSentPromptEventType,
|
||||
} as const;
|
||||
|
||||
export type TelemetryEventTypeWithPayload =
|
||||
| { type: typeof chatFeedbackEventType; payload: ChatFeedback }
|
||||
| { type: typeof insightFeedbackEventType; payload: InsightFeedback }
|
||||
| { type: typeof userSentPromptEventType; payload: Message };
|
||||
|
||||
export const registerTelemetryEventTypes = (analytics: AnalyticsServiceSetup) => {
|
||||
schemas.forEach((schema) => {
|
||||
analytics.registerEventType<{}>(schema);
|
||||
});
|
||||
};
|
||||
|
||||
export function sendEvent(
|
||||
analytics: AnalyticsServiceStart,
|
||||
eventType: TelemetryEventTypeWithPayload
|
||||
): void {
|
||||
analytics.reportEvent(eventType.type, eventType.payload);
|
||||
}
|
|
@ -1,95 +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 { RootSchema } from '@kbn/analytics-client';
|
||||
import { Message } from '../../common';
|
||||
import type { Feedback } from '../components/feedback_buttons';
|
||||
|
||||
export const MESSAGE_FEEDBACK = 'observability_ai_assistant_chat_message_feedback' as const;
|
||||
export const INSIGHT_FEEDBACK = 'observability_ai_assistant_chat_insight_feedback' as const;
|
||||
|
||||
export interface MessageFeedback extends Message {
|
||||
feedback: Feedback;
|
||||
}
|
||||
|
||||
export interface TelemetryEvent {
|
||||
eventType: typeof MESSAGE_FEEDBACK | typeof INSIGHT_FEEDBACK;
|
||||
schema: RootSchema<MessageFeedback>;
|
||||
}
|
||||
|
||||
export const MESSAGE_FEEDBACK_SCHEMA: TelemetryEvent = {
|
||||
eventType: MESSAGE_FEEDBACK,
|
||||
schema: {
|
||||
'@timestamp': {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The timestamp of the message.',
|
||||
},
|
||||
},
|
||||
feedback: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'Whether the user has deemed this response useful or not',
|
||||
},
|
||||
},
|
||||
message: {
|
||||
properties: {
|
||||
content: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The response generated by the LLM.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The name of the function that was executed.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
role: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The actor that generated the response.',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: '',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
function_call: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The name of the function that was executed.',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
arguments: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The arguments that were used when executing the function.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The actor which triggered the execution of this function.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 { EventTypeOpts } from '@kbn/analytics-client';
|
||||
import type { Message, Conversation } from '../../../common';
|
||||
import type { Feedback } from '../../components/feedback_buttons';
|
||||
import { messageSchema } from './common';
|
||||
|
||||
export interface ChatFeedback {
|
||||
messageWithFeedback: {
|
||||
message: Message;
|
||||
feedback: Feedback;
|
||||
};
|
||||
conversation: Conversation;
|
||||
}
|
||||
|
||||
export const eventType = 'observability_ai_assistant_chat_feedback';
|
||||
|
||||
export const chatFeedbackEventSchema: EventTypeOpts<ChatFeedback> = {
|
||||
eventType,
|
||||
schema: {
|
||||
messageWithFeedback: {
|
||||
properties: {
|
||||
message: {
|
||||
properties: messageSchema,
|
||||
},
|
||||
feedback: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'Whether the user has deemed this response useful or not',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
conversation: {
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The timestamp of the conversation.',
|
||||
},
|
||||
},
|
||||
user: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The id of the user.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The name of the user.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
conversation: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The id of the conversation.',
|
||||
},
|
||||
},
|
||||
title: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The title of the conversation.',
|
||||
},
|
||||
},
|
||||
last_updated: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The timestamp of the last message in the conversation.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
type: 'array',
|
||||
items: {
|
||||
properties: messageSchema,
|
||||
},
|
||||
_meta: {
|
||||
description: 'The messages in the conversation.',
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
type: 'pass_through',
|
||||
_meta: {
|
||||
description: 'The labels of the conversation.',
|
||||
},
|
||||
},
|
||||
numeric_labels: {
|
||||
type: 'pass_through',
|
||||
_meta: {
|
||||
description: 'The numeric labels of the conversation.',
|
||||
},
|
||||
},
|
||||
namespace: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The namespace of the conversation.',
|
||||
},
|
||||
},
|
||||
public: {
|
||||
type: 'boolean',
|
||||
_meta: {
|
||||
description: 'Whether the conversation is public or not.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 { RootSchema } from '@kbn/analytics-client';
|
||||
import type { Message } from '../../../common';
|
||||
|
||||
export const messageSchema: RootSchema<Message> = {
|
||||
'@timestamp': {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The timestamp of the message.',
|
||||
},
|
||||
},
|
||||
message: {
|
||||
properties: {
|
||||
content: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The response generated by the LLM.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The name of the function that was executed.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
role: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The actor that generated the response.',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: '',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
function_call: {
|
||||
_meta: {
|
||||
description: 'The function call that was executed.',
|
||||
optional: true,
|
||||
},
|
||||
properties: {
|
||||
name: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The name of the function that was executed.',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
arguments: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The arguments that were used when executing the function.',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'The actor which triggered the execution of this function.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { EventTypeOpts } from '@kbn/analytics-client';
|
||||
import type { Message } from '../../../common';
|
||||
import type { Feedback } from '../../components/feedback_buttons';
|
||||
import { messageSchema } from './common';
|
||||
|
||||
export interface InsightFeedback {
|
||||
feedback: Feedback;
|
||||
message: Message;
|
||||
}
|
||||
|
||||
export const eventType = 'observability_ai_assistant_insight_feedback';
|
||||
|
||||
export const insightFeedbackEventSchema: EventTypeOpts<InsightFeedback> = {
|
||||
eventType,
|
||||
schema: {
|
||||
feedback: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'Whether the user has deemed this response useful or not',
|
||||
},
|
||||
},
|
||||
message: {
|
||||
properties: messageSchema,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { EventTypeOpts } from '@kbn/analytics-client';
|
||||
import type { Message } from '../../../common';
|
||||
import { messageSchema } from './common';
|
||||
|
||||
export const eventType = 'observability_ai_assistant_user_sent_prompt_in_chat';
|
||||
export const userSentPromptEventSchema: EventTypeOpts<Message> = {
|
||||
eventType,
|
||||
schema: messageSchema,
|
||||
};
|
|
@ -32,9 +32,9 @@ import { Feedback } from '../feedback_buttons';
|
|||
import { IncorrectLicensePanel } from './incorrect_license_panel';
|
||||
import { WelcomeMessage } from './welcome_message';
|
||||
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
|
||||
import { MESSAGE_FEEDBACK } from '../../analytics/schema';
|
||||
import { ChatActionClickType } from './types';
|
||||
import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
import { TELEMETRY, sendEvent } from '../../analytics';
|
||||
|
||||
const fullHeightClassName = css`
|
||||
height: 100%;
|
||||
|
@ -140,8 +140,15 @@ export function ChatBody({
|
|||
parent.scrollTop + parent.clientHeight >= parent.scrollHeight;
|
||||
|
||||
const handleFeedback = (message: Message, feedback: Feedback) => {
|
||||
const feedbackEvent = { ...message, feedback };
|
||||
chatService.analytics.reportEvent(MESSAGE_FEEDBACK, feedbackEvent);
|
||||
if (conversation.value?.conversation && 'user' in conversation.value.conversation) {
|
||||
sendEvent(chatService.analytics, {
|
||||
type: TELEMETRY.observability_ai_assistant_chat_feedback,
|
||||
payload: {
|
||||
messageWithFeedback: { message, feedback },
|
||||
conversation: conversation.value.conversation,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -196,6 +203,9 @@ export function ChatBody({
|
|||
onSubmit={(message) => {
|
||||
next(messages.concat(message));
|
||||
}}
|
||||
onSendTelemetry={(eventWithPayload) =>
|
||||
sendEvent(chatService.analytics, eventWithPayload)
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiPanel>
|
||||
|
@ -236,6 +246,9 @@ export function ChatBody({
|
|||
const indexOf = messages.indexOf(message);
|
||||
next(messages.slice(0, indexOf));
|
||||
}}
|
||||
onSendTelemetry={(eventWithPayload) =>
|
||||
sendEvent(chatService.analytics, eventWithPayload)
|
||||
}
|
||||
onStopGenerating={() => {
|
||||
stop();
|
||||
}}
|
||||
|
@ -283,8 +296,11 @@ export function ChatBody({
|
|||
className={promptEditorContainerClassName}
|
||||
>
|
||||
<ChatPromptEditor
|
||||
loading={isLoading}
|
||||
disabled={!connectors.selectedConnector || !hasCorrectLicense}
|
||||
loading={isLoading}
|
||||
onSendTelemetry={(eventWithPayload) =>
|
||||
sendEvent(chatService.analytics, eventWithPayload)
|
||||
}
|
||||
onSubmit={(message) => {
|
||||
setStickToBottom(true);
|
||||
return next(messages.concat(message));
|
||||
|
|
|
@ -49,18 +49,20 @@ const noPanelStyle = css`
|
|||
|
||||
export function ChatConsolidatedItems({
|
||||
consolidatedItem,
|
||||
onActionClick,
|
||||
onEditSubmit,
|
||||
onFeedback,
|
||||
onRegenerate,
|
||||
onEditSubmit,
|
||||
onSendTelemetry,
|
||||
onStopGenerating,
|
||||
onActionClick,
|
||||
}: {
|
||||
consolidatedItem: ChatTimelineItem[];
|
||||
onActionClick: ChatTimelineProps['onActionClick'];
|
||||
onEditSubmit: ChatTimelineProps['onEdit'];
|
||||
onFeedback: ChatTimelineProps['onFeedback'];
|
||||
onRegenerate: ChatTimelineProps['onRegenerate'];
|
||||
onEditSubmit: ChatTimelineProps['onEdit'];
|
||||
onSendTelemetry: ChatTimelineProps['onSendTelemetry'];
|
||||
onStopGenerating: ChatTimelineProps['onStopGenerating'];
|
||||
onActionClick: ChatTimelineProps['onActionClick'];
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
|
@ -122,15 +124,16 @@ export function ChatConsolidatedItems({
|
|||
// use index, not id to prevent unmounting of component when message is persisted
|
||||
key={index}
|
||||
{...item}
|
||||
onActionClick={onActionClick}
|
||||
onEditSubmit={(message) => onEditSubmit(item.message, message)}
|
||||
onFeedbackClick={(feedback) => {
|
||||
onFeedback(item.message, feedback);
|
||||
}}
|
||||
onRegenerateClick={() => {
|
||||
onRegenerate(item.message);
|
||||
}}
|
||||
onEditSubmit={(message) => onEditSubmit(item.message, message)}
|
||||
onSendTelemetry={onSendTelemetry}
|
||||
onStopGeneratingClick={onStopGenerating}
|
||||
onActionClick={onActionClick}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
|
|
|
@ -21,17 +21,19 @@ import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_pr
|
|||
import { ChatItemControls } from './chat_item_controls';
|
||||
import { ChatTimelineItem } from './chat_timeline';
|
||||
import { getRoleTranslation } from '../../utils/get_role_translation';
|
||||
import type { Feedback } from '../feedback_buttons';
|
||||
import { Message } from '../../../common';
|
||||
import { FailedToLoadResponse } from '../message_panel/failed_to_load_response';
|
||||
import { ChatActionClickHandler } from './types';
|
||||
import type { Message } from '../../../common';
|
||||
import type { Feedback } from '../feedback_buttons';
|
||||
import type { ChatActionClickHandler } from './types';
|
||||
import type { TelemetryEventTypeWithPayload } from '../../analytics';
|
||||
|
||||
export interface ChatItemProps extends ChatTimelineItem {
|
||||
onActionClick: ChatActionClickHandler;
|
||||
onEditSubmit: (message: Message) => void;
|
||||
onFeedbackClick: (feedback: Feedback) => void;
|
||||
onRegenerateClick: () => void;
|
||||
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
|
||||
onStopGeneratingClick: () => void;
|
||||
onActionClick: ChatActionClickHandler;
|
||||
}
|
||||
|
||||
const normalMessageClassName = css`
|
||||
|
@ -66,21 +68,22 @@ const noPanelMessageClassName = css`
|
|||
|
||||
export function ChatItem({
|
||||
actions: { canCopy, canEdit, canGiveFeedback, canRegenerate },
|
||||
display: { collapsed },
|
||||
message: {
|
||||
message: { function_call: functionCall, role },
|
||||
},
|
||||
content,
|
||||
currentUser,
|
||||
display: { collapsed },
|
||||
element,
|
||||
error,
|
||||
loading,
|
||||
message: {
|
||||
message: { function_call: functionCall, role },
|
||||
},
|
||||
title,
|
||||
onActionClick,
|
||||
onEditSubmit,
|
||||
onFeedbackClick,
|
||||
onRegenerateClick,
|
||||
onSendTelemetry,
|
||||
onStopGeneratingClick,
|
||||
onActionClick,
|
||||
}: ChatItemProps) {
|
||||
const accordionId = useGeneratedHtmlId({ prefix: 'chat' });
|
||||
|
||||
|
@ -130,6 +133,7 @@ export function ChatItem({
|
|||
loading={loading}
|
||||
onSubmit={handleInlineEditSubmit}
|
||||
onActionClick={onActionClick}
|
||||
onSendTelemetry={onSendTelemetry}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
import React from 'react';
|
||||
import { MessageText } from '../message_panel/message_text';
|
||||
import { ChatPromptEditor } from './chat_prompt_editor';
|
||||
import { MessageRole, type Message } from '../../../common';
|
||||
import { ChatActionClickHandler } from './types';
|
||||
import { type Message, MessageRole } from '../../../common';
|
||||
import type { ChatActionClickHandler } from './types';
|
||||
import type { TelemetryEventTypeWithPayload } from '../../analytics';
|
||||
|
||||
interface Props {
|
||||
content: string | undefined;
|
||||
|
@ -22,16 +23,18 @@ interface Props {
|
|||
| undefined;
|
||||
loading: boolean;
|
||||
editing: boolean;
|
||||
onSubmit: (message: Message) => void;
|
||||
onActionClick: ChatActionClickHandler;
|
||||
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
|
||||
onSubmit: (message: Message) => void;
|
||||
}
|
||||
export function ChatItemContentInlinePromptEditor({
|
||||
content,
|
||||
functionCall,
|
||||
editing,
|
||||
loading,
|
||||
onSubmit,
|
||||
onActionClick,
|
||||
onSendTelemetry,
|
||||
onSubmit,
|
||||
}: Props) {
|
||||
return !editing ? (
|
||||
<MessageText content={content || ''} loading={loading} onActionClick={onActionClick} />
|
||||
|
@ -44,6 +47,7 @@ export function ChatItemContentInlinePromptEditor({
|
|||
initialSelectedFunctionName={functionCall?.name}
|
||||
trigger={functionCall?.trigger}
|
||||
onSubmit={onSubmit}
|
||||
onSendTelemetry={onSendTelemetry}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -46,14 +46,14 @@ export function ChatItemControls({
|
|||
controls = <StopGeneratingButton onClick={onStopGeneratingClick} />;
|
||||
} else if (displayFeedback || displayRegenerate) {
|
||||
controls = (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
{displayFeedback ? (
|
||||
<EuiFlexItem grow={true}>
|
||||
<FeedbackButtons onClickFeedback={onFeedbackClick} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
{displayRegenerate ? (
|
||||
<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end' }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RegenerateResponseButton onClick={onRegenerateClick} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
|
@ -16,12 +18,11 @@ import {
|
|||
EuiTextArea,
|
||||
keys,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CodeEditor } from '@kbn/kibana-react-plugin/public';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { MessageRole, type Message } from '../../../common';
|
||||
import { useJsonEditorModel } from '../../hooks/use_json_editor_model';
|
||||
import { FunctionListPopover } from './function_list_popover';
|
||||
import { useJsonEditorModel } from '../../hooks/use_json_editor_model';
|
||||
import { TelemetryEventTypeWithPayload, TELEMETRY } from '../../analytics';
|
||||
|
||||
export interface ChatPromptEditorProps {
|
||||
disabled: boolean;
|
||||
|
@ -31,6 +32,7 @@ export interface ChatPromptEditorProps {
|
|||
initialFunctionPayload?: string;
|
||||
trigger?: MessageRole;
|
||||
onSubmit: (message: Message) => void;
|
||||
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
|
||||
}
|
||||
|
||||
export function ChatPromptEditor({
|
||||
|
@ -40,6 +42,7 @@ export function ChatPromptEditor({
|
|||
initialSelectedFunctionName,
|
||||
initialFunctionPayload,
|
||||
onSubmit,
|
||||
onSendTelemetry,
|
||||
}: ChatPromptEditorProps) {
|
||||
const isFocusTrapEnabled = Boolean(initialPrompt);
|
||||
|
||||
|
@ -112,9 +115,11 @@ export function ChatPromptEditor({
|
|||
setFunctionPayload(undefined);
|
||||
handleResetTextArea();
|
||||
|
||||
let message: Message;
|
||||
|
||||
try {
|
||||
if (selectedFunctionName) {
|
||||
await onSubmit({
|
||||
message = {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
message: {
|
||||
role: MessageRole.Assistant,
|
||||
|
@ -125,20 +130,27 @@ export function ChatPromptEditor({
|
|||
arguments: currentPayload,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
onSubmit(message);
|
||||
|
||||
setFunctionPayload(undefined);
|
||||
setSelectedFunctionName(undefined);
|
||||
} else {
|
||||
await onSubmit({
|
||||
message = {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
message: { role: MessageRole.User, content: currentPrompt },
|
||||
});
|
||||
};
|
||||
onSubmit(message);
|
||||
}
|
||||
|
||||
onSendTelemetry({
|
||||
type: TELEMETRY.observability_ai_assistant_user_sent_prompt_in_chat,
|
||||
payload: message,
|
||||
});
|
||||
} catch (_) {
|
||||
setPrompt(currentPrompt);
|
||||
}
|
||||
}, [functionPayload, loading, onSubmit, prompt, selectedFunctionName]);
|
||||
}, [functionPayload, loading, onSendTelemetry, onSubmit, prompt, selectedFunctionName]);
|
||||
|
||||
useEffect(() => {
|
||||
setFunctionPayload(initialJsonString);
|
||||
|
|
|
@ -119,11 +119,12 @@ const defaultProps: ComponentProps<typeof Component> = {
|
|||
},
|
||||
}),
|
||||
],
|
||||
onActionClick: async () => {},
|
||||
onEdit: async () => {},
|
||||
onFeedback: () => {},
|
||||
onRegenerate: () => {},
|
||||
onSendTelemetry: () => {},
|
||||
onStopGenerating: () => {},
|
||||
onActionClick: async () => {},
|
||||
};
|
||||
|
||||
export const ChatTimeline = Template.bind({});
|
||||
|
|
|
@ -14,6 +14,7 @@ import type { Message } from '../../../common';
|
|||
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
import type { ChatActionClickHandler } from './types';
|
||||
import type { ObservabilityAIAssistantChatService } from '../../types';
|
||||
import type { TelemetryEventTypeWithPayload } from '../../analytics';
|
||||
import { ChatItem } from './chat_item';
|
||||
import { ChatConsolidatedItems } from './chat_consolidated_items';
|
||||
import { ChatState } from '../../hooks/use_chat';
|
||||
|
@ -54,13 +55,13 @@ export interface ChatTimelineProps {
|
|||
onEdit: (message: Message, messageAfterEdit: Message) => void;
|
||||
onFeedback: (message: Message, feedback: Feedback) => void;
|
||||
onRegenerate: (message: Message) => void;
|
||||
onSendTelemetry: (eventWithPayload: TelemetryEventTypeWithPayload) => void;
|
||||
onStopGenerating: () => void;
|
||||
onActionClick: ChatActionClickHandler;
|
||||
}
|
||||
|
||||
export function ChatTimeline({
|
||||
messages,
|
||||
knowledgeBase,
|
||||
chatService,
|
||||
hasConnector,
|
||||
currentUser,
|
||||
|
@ -68,6 +69,7 @@ export function ChatTimeline({
|
|||
onEdit,
|
||||
onFeedback,
|
||||
onRegenerate,
|
||||
onSendTelemetry,
|
||||
onStopGenerating,
|
||||
onActionClick,
|
||||
chatState,
|
||||
|
@ -115,17 +117,19 @@ export function ChatTimeline({
|
|||
<ChatConsolidatedItems
|
||||
key={index}
|
||||
consolidatedItem={item}
|
||||
onActionClick={onActionClick}
|
||||
onFeedback={onFeedback}
|
||||
onRegenerate={onRegenerate}
|
||||
onEditSubmit={onEdit}
|
||||
onSendTelemetry={onSendTelemetry}
|
||||
onStopGenerating={onStopGenerating}
|
||||
onActionClick={onActionClick}
|
||||
/>
|
||||
) : (
|
||||
<ChatItem
|
||||
// use index, not id to prevent unmounting of component when message is persisted
|
||||
key={index}
|
||||
{...item}
|
||||
onActionClick={onActionClick}
|
||||
onFeedbackClick={(feedback) => {
|
||||
onFeedback(item.message, feedback);
|
||||
}}
|
||||
|
@ -135,8 +139,8 @@ export function ChatTimeline({
|
|||
onEditSubmit={(message) => {
|
||||
onEdit(item.message, message);
|
||||
}}
|
||||
onSendTelemetry={onSendTelemetry}
|
||||
onStopGeneratingClick={onStopGenerating}
|
||||
onActionClick={onActionClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { useKibana } from '../hooks/use_kibana';
|
||||
|
||||
export type Feedback = 'positive' | 'negative';
|
||||
|
||||
|
@ -16,6 +17,22 @@ interface FeedbackButtonsProps {
|
|||
}
|
||||
|
||||
export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
|
||||
const { notifications } = useKibana().services;
|
||||
|
||||
const [hasBeenClicked, setHasBeenClicked] = useState(false);
|
||||
|
||||
const handleClickPositive = () => {
|
||||
onClickFeedback('positive');
|
||||
setHasBeenClicked(true);
|
||||
notifications.toasts.addSuccess('Thanks for your feedback!');
|
||||
};
|
||||
|
||||
const handleClickNegative = () => {
|
||||
onClickFeedback('negative');
|
||||
setHasBeenClicked(true);
|
||||
notifications.toasts.addSuccess('Thanks for your feedback!');
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup responsive={false} direction="row" alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -34,9 +51,10 @@ export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="observabilityAiAssistantFeedbackButtonsPositiveButton"
|
||||
color="success"
|
||||
disabled={hasBeenClicked}
|
||||
iconType="faceHappy"
|
||||
size="s"
|
||||
onClick={() => onClickFeedback('positive')}
|
||||
onClick={handleClickPositive}
|
||||
>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.insight.feedbackButtons.positive', {
|
||||
defaultMessage: 'Yes',
|
||||
|
@ -48,9 +66,10 @@ export function FeedbackButtons({ onClickFeedback }: FeedbackButtonsProps) {
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="observabilityAiAssistantFeedbackButtonsNegativeButton"
|
||||
color="danger"
|
||||
disabled={hasBeenClicked}
|
||||
iconType="faceSad"
|
||||
size="s"
|
||||
onClick={() => onClickFeedback('negative')}
|
||||
onClick={handleClickNegative}
|
||||
>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.insight.feedbackButtons.negative', {
|
||||
defaultMessage: 'No',
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { last, noop } from 'lodash';
|
||||
import { last } from 'lodash';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { MessageRole, type Message } from '../../../common/types';
|
||||
import { INSIGHT_FEEDBACK } from '../../analytics/schema';
|
||||
import { sendEvent, TELEMETRY } from '../../analytics';
|
||||
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
|
||||
import { useAbortableAsync } from '../../hooks/use_abortable_async';
|
||||
import { ChatState, useChat } from '../../hooks/use_chat';
|
||||
|
@ -79,14 +79,17 @@ function ChatContent({
|
|||
) : (
|
||||
<EuiFlexGroup direction="row">
|
||||
<FeedbackButtons
|
||||
onClickFeedback={(feedback) =>
|
||||
lastAssistantResponse
|
||||
? chatService.analytics.reportEvent(INSIGHT_FEEDBACK, {
|
||||
onClickFeedback={(feedback) => {
|
||||
if (lastAssistantResponse) {
|
||||
sendEvent(chatService.analytics, {
|
||||
type: TELEMETRY.observability_ai_assistant_insight_feedback,
|
||||
payload: {
|
||||
feedback,
|
||||
...lastAssistantResponse,
|
||||
})
|
||||
: noop
|
||||
}
|
||||
message: lastAssistantResponse,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RegenerateResponseButton
|
||||
|
|
|
@ -27,7 +27,7 @@ import type {
|
|||
ObservabilityAIAssistantPluginStartDependencies,
|
||||
ObservabilityAIAssistantService,
|
||||
} from './types';
|
||||
import { MessageFeedback, MESSAGE_FEEDBACK_SCHEMA } from './analytics/schema';
|
||||
import { registerTelemetryEventTypes } from './analytics';
|
||||
|
||||
export class ObservabilityAIAssistantPlugin
|
||||
implements
|
||||
|
@ -89,7 +89,7 @@ export class ObservabilityAIAssistantPlugin
|
|||
},
|
||||
});
|
||||
|
||||
coreSetup.analytics.registerEventType<MessageFeedback>(MESSAGE_FEEDBACK_SCHEMA);
|
||||
registerTelemetryEventTypes(coreSetup.analytics);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -52,7 +52,8 @@
|
|||
"@kbn/analytics-client",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/babel-register",
|
||||
"@kbn/dev-cli-runner"
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/core-analytics-browser"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue