diff --git a/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
index 680a92f559f0a..5be7a75449970 100644
--- a/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
+++ b/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
@@ -14,11 +14,13 @@ import {
EuiText,
EuiTextArea,
EuiCallOut,
+ EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { cloneDeep, isArray, isEmpty, last, once } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
+import dedent from 'dedent';
import { ILicense } from '@kbn/licensing-plugin/public';
import { MessageRole, type Message } from '../../../common/types';
import { ObservabilityAIAssistantChatServiceContext } from '../../context/observability_ai_assistant_chat_service_context';
@@ -41,6 +43,10 @@ import { InsightBase } from './insight_base';
import { ActionsMenu } from './actions_menu';
import { ObservabilityAIAssistantTelemetryEventType } from '../../analytics/telemetry_event_type';
+// defined and used in x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
+const CONTEXT_INSTRUCTION =
+ 'The following contextual information is available to help you understand the alert';
+
function getLastMessageOfType(messages: Message[], role: MessageRole) {
return last(messages.filter((msg) => msg.message.role === role));
}
@@ -288,16 +294,61 @@ export function Insight({
[service]
);
+ const getPromptToEdit = () => {
+ const clonedMessages = cloneDeep(messages.messages);
+ const lastUserPrompt = getLastMessageOfType(clonedMessages, MessageRole.User)?.message.content;
+
+ if (!lastUserPrompt) {
+ return '';
+ }
+
+ try {
+ const { instructions = '' } = JSON.parse(lastUserPrompt);
+
+ // instructions is a large multiline string with contextual information appended to it.
+ // We'll split at CONTEXT_INSTRUCTION so that we can extract the instructions the user needs to edit
+ const [beforeContextInstruction] = instructions.split(CONTEXT_INSTRUCTION);
+ return beforeContextInstruction.trim();
+ } catch (e) {
+ return '';
+ }
+ };
+
const onEditPrompt = (newPrompt: string) => {
const clonedMessages = cloneDeep(messages.messages);
const userMessage = getLastMessageOfType(clonedMessages, MessageRole.User);
if (!userMessage) return false;
- userMessage.message.content = newPrompt;
- setIsPromptUpdated(true);
- setMessages({ messages: clonedMessages, status: FETCH_STATUS.SUCCESS });
- setEditingPrompt(false);
- return true;
+ try {
+ const parsedContent = JSON.parse(userMessage.message.content || '');
+
+ if (!parsedContent.instructions) {
+ return false;
+ }
+
+ const [_beforeContextInstruction, afterContextInstruction] =
+ parsedContent.instructions.split(CONTEXT_INSTRUCTION);
+
+ // Rebuild instructions with the new prompt, preserving everything after the delimiter
+ const updatedInstructions = dedent(
+ `${newPrompt}
+ ${CONTEXT_INSTRUCTION}
+ ${afterContextInstruction}`
+ );
+
+ // Assign the updated instructions
+ parsedContent.instructions = updatedInstructions;
+ userMessage.message.content = JSON.stringify(parsedContent);
+
+ setIsPromptUpdated(true);
+ setMessages({ messages: clonedMessages, status: FETCH_STATUS.SUCCESS });
+ setEditingPrompt(false);
+ return true;
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Failed to edit prompt:', e);
+ return false;
+ }
};
const handleCancel = () => {
@@ -372,15 +423,19 @@ export function Insight({
>
);
} else if (isEditingPrompt) {
- children = (
-
- );
+ const instructionToEdit = getPromptToEdit();
+
+ if (messages.status === FETCH_STATUS.SUCCESS && instructionToEdit) {
+ children = (
+
+ );
+ } else {
+ children = ;
+ }
} else if (!connectors.loading && !connectors.connectors?.length) {
children = (
diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
index 17f7d335624d1..d63fd5d06eccd 100644
--- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
+++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx
@@ -13,6 +13,10 @@ import { type AlertDetailsContextualInsight } from '../../../server/services';
import { useKibana } from '../../utils/kibana_react';
import { AlertData } from '../../hooks/use_fetch_alert_detail';
+// used in x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
+const CONTEXT_INSTRUCTION =
+ 'The following contextual information is available to help you understand the alert';
+
export function AlertDetailContextualInsights({ alert }: { alert: AlertData | null }) {
const {
services: { observabilityAIAssistant, http },
@@ -62,7 +66,7 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu
instructions: dedent(
`I'm an SRE. I am looking at an alert that was triggered. I want to understand why it was triggered, what it means, and what I should do next.
- The following contextual information is available to help you understand the alert:
+ ${CONTEXT_INSTRUCTION}:
${obsAlertContext}
The user already know the alert reason so do not repeat this: ${alert.formatted.reason}