mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Observability AI Assistant] Save/open Lens embeddable (#165542)
This commit is contained in:
parent
aab1f8a04a
commit
1c53191972
3 changed files with 93 additions and 47 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { last } from 'lodash';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -104,49 +104,40 @@ export function ChatBody({
|
|||
: '100%'};
|
||||
`;
|
||||
|
||||
const [stickToBottom, setStickToBottom] = useState(true);
|
||||
|
||||
const isAtBottom = (parent: HTMLElement) =>
|
||||
parent.scrollTop + parent.clientHeight >= parent.scrollHeight;
|
||||
|
||||
useEffect(() => {
|
||||
const parent = timelineContainerRef.current?.parentElement;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rafId: number | undefined;
|
||||
|
||||
const isAtBottom = () => parent.scrollTop >= parent.scrollHeight - parent.offsetHeight;
|
||||
|
||||
const stick = () => {
|
||||
if (!isAtBottom()) {
|
||||
parent.scrollTop = parent.scrollHeight - parent.offsetHeight;
|
||||
}
|
||||
rafId = requestAnimationFrame(stick);
|
||||
};
|
||||
|
||||
const unstick = () => {
|
||||
if (rafId) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const onScroll = (event: Event) => {
|
||||
if (isAtBottom()) {
|
||||
stick();
|
||||
} else {
|
||||
unstick();
|
||||
}
|
||||
};
|
||||
function onScroll() {
|
||||
setStickToBottom(isAtBottom(parent!));
|
||||
}
|
||||
|
||||
parent.addEventListener('scroll', onScroll);
|
||||
|
||||
stick();
|
||||
|
||||
return () => {
|
||||
unstick();
|
||||
parent.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timelineContainerRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
const parent = timelineContainerRef.current?.parentElement;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stickToBottom) {
|
||||
parent.scrollTop = parent.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
const handleCopyConversation = () => {
|
||||
const content = JSON.stringify({ title, messages });
|
||||
|
||||
|
@ -210,7 +201,10 @@ export function ChatBody({
|
|||
<ChatPromptEditor
|
||||
loading={isLoading}
|
||||
disabled={!connectors.selectedConnector || !hasCorrectLicense}
|
||||
onSubmit={timeline.onSubmit}
|
||||
onSubmit={(message) => {
|
||||
setStickToBottom(true);
|
||||
return timeline.onSubmit(message);
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiPanel>
|
||||
|
|
|
@ -16,7 +16,6 @@ import classNames from 'classnames';
|
|||
import type { Code, InlineCode, Parent, Text } from 'mdast';
|
||||
import React, { useMemo } from 'react';
|
||||
import type { Node } from 'unist';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
|
@ -48,7 +47,10 @@ const cursorCss = css`
|
|||
|
||||
const Cursor = () => <span key="cursor" className={classNames(cursorCss, 'cursor')} />;
|
||||
|
||||
const CURSOR = `{{${v4()}}}`;
|
||||
// a weird combination of different whitespace chars to make sure it stays
|
||||
// invisible even when we cannot properly parse the text while still being
|
||||
// unique
|
||||
const CURSOR = ` `;
|
||||
|
||||
const loadingCursorPlugin = () => {
|
||||
const visitor = (node: Node, parent?: Parent) => {
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { LensAttributesBuilder, XYChart, XYDataLayer } from '@kbn/lens-embeddable-utils';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import React from 'react';
|
||||
import type { LensEmbeddableInput, LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import React, { useState } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Assign } from 'utility-types';
|
||||
import type { RegisterFunctionDefinition } from '../../common/types';
|
||||
import type {
|
||||
ObservabilityAIAssistantPluginStartDependencies,
|
||||
|
@ -56,6 +58,8 @@ function Lens({
|
|||
});
|
||||
}, [indexPattern]);
|
||||
|
||||
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
|
||||
|
||||
if (!formulaAsync.value || !dataViewAsync.value) {
|
||||
return <EuiLoadingSpinner />;
|
||||
}
|
||||
|
@ -68,19 +72,65 @@ function Lens({
|
|||
}),
|
||||
}).build();
|
||||
|
||||
const lensEmbeddableInput: Assign<LensEmbeddableInput, { attributes: typeof attributes }> = {
|
||||
id: indexPattern,
|
||||
attributes,
|
||||
timeRange: {
|
||||
from: start,
|
||||
to: end,
|
||||
mode: 'relative' as const,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<lens.EmbeddableComponent
|
||||
id={indexPattern}
|
||||
attributes={attributes}
|
||||
timeRange={{
|
||||
from: start,
|
||||
to: end,
|
||||
mode: 'relative',
|
||||
}}
|
||||
style={{
|
||||
height: 240,
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="lensApp"
|
||||
onClick={() => {
|
||||
lens.navigateToPrefilledEditor(lensEmbeddableInput);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.lensFunction.openInLens', {
|
||||
defaultMessage: 'Open in Lens',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="save"
|
||||
onClick={() => {
|
||||
setIsSaveModalOpen(() => true);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.lensFunction.save', {
|
||||
defaultMessage: 'Save',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<lens.EmbeddableComponent
|
||||
{...lensEmbeddableInput}
|
||||
style={{
|
||||
height: 240,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isSaveModalOpen ? (
|
||||
<lens.SaveModalComponent
|
||||
initialInput={lensEmbeddableInput}
|
||||
onClose={() => {
|
||||
setIsSaveModalOpen(() => false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue