Skip to content

Commit

Permalink
Merge pull request elastic#16 from yansavitski/ai-playground-sidebar
Browse files Browse the repository at this point in the history
Ai playground sidebar
  • Loading branch information
yansavitski authored Feb 20, 2024
2 parents beebf84 + 13342e1 commit 1cf4e33
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 181 deletions.
226 changes: 91 additions & 135 deletions packages/kbn-ai-playground/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import React, { useMemo } from 'react';

import { Controller, useForm } from 'react-hook-form';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import {
EuiButtonIcon,
EuiFlexGroup,
Expand All @@ -27,25 +27,24 @@ import { ChatForm, ChatFormFields, MessageRole } from '../types';

import { MessageList } from './message_list/message_list';
import { QuestionInput } from './question_input';
import { OpenAIKeyField } from './open_ai_key_field';
import { InstructionsField } from './instructions_field';
import { IncludeCitationsField } from './include_citations_field';
import { SourcesPanelSidebar } from './sources_panel/sources_panel_sidebar';

import { TelegramIcon } from './telegram_icon';

import { transformFromChatMessages } from '../utils/transformToMessages';
import { IndexName } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ChatSidebar } from '@kbn/ai-playground/components/chat_sidebar';

export const Chat = () => {
const { euiTheme } = useEuiTheme();
const form = useForm<ChatForm>();
const {
control,
watch,
formState: { isValid, isSubmitting },
resetField,
handleSubmit,
} = useForm<ChatForm>();
} = form;
const { messages, append, stop: stopRequest } = useChat();
const selectedIndicesCount = watch(ChatFormFields.indices, []).length;

const onSubmit = async (data: ChatForm) => {
await append(
Expand Down Expand Up @@ -75,133 +74,90 @@ export const Chat = () => {
);

return (
<EuiForm
component="form"
css={{ display: 'flex', flexGrow: 1 }}
onSubmit={handleSubmit(onSubmit)}
>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem
grow={2}
css={{
borderRight: euiTheme.border.thin,
paddingTop: euiTheme.size.l,
paddingBottom: euiTheme.size.l,
}}
>
<EuiFlexGroup direction="column" className="eui-fullHeight">
{/* // Set scroll at the border of parent element*/}
<EuiFlexItem
grow={1}
className="eui-yScroll"
css={{ paddingLeft: euiTheme.size.l, paddingRight: euiTheme.size.l }}
tabIndex={0}
>
<MessageList messages={chatMessages} />
</EuiFlexItem>

<EuiFlexItem
grow={false}
css={{ paddingLeft: euiTheme.size.l, paddingRight: euiTheme.size.l }}
>
<EuiHorizontalRule margin="none" />

<EuiSpacer size="m" />

<Controller
name={ChatFormFields.question}
control={control}
defaultValue=""
rules={{
required: true,
validate: (rule) => !!rule?.trim(),
}}
render={({ field }) => (
<QuestionInput
value={field.value}
onChange={field.onChange}
isDisabled={isSubmitting}
button={
isSubmitting ? (
<EuiButtonIcon
aria-label={i18n.translate('aiPlayground.chat.stopButtonAriaLabel', {
defaultMessage: 'Stop request',
})}
display="base"
size="s"
iconType="stop"
onClick={stopRequest}
/>
) : (
<EuiButtonIcon
aria-label={i18n.translate('aiPlayground.chat.sendButtonAriaLabel', {
defaultMessage: 'Send a question',
})}
display={isValid ? 'base' : 'empty'}
size="s"
type="submit"
isLoading={isSubmitting}
isDisabled={!isValid}
iconType={TelegramIcon}
/>
)
}
/>
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

<EuiFlexItem
grow={1}
css={{
padding: euiTheme.size.l,
}}
>
<Controller
name={ChatFormFields.openAIKey}
control={control}
defaultValue=""
render={({ field }) => <OpenAIKeyField apiKey={field.value} onSave={field.onChange} />}
/>

<Controller
name={ChatFormFields.prompt}
control={control}
defaultValue=""
render={({ field }) => (
<InstructionsField value={field.value} onChange={field.onChange} />
)}
/>

<Controller
name={ChatFormFields.citations}
control={control}
defaultValue={true}
render={({ field }) => (
<IncludeCitationsField checked={field.value} onChange={field.onChange} />
)}
/>

<Controller
name={ChatFormFields.indices}
control={control}
defaultValue={[]}
render={({ field }) => (
<SourcesPanelSidebar
selectedIndices={field.value}
addIndex={(newIndex: IndexName) => {
field.onChange([...field.value, newIndex]);
}}
removeIndex={(index: IndexName) => {
field.onChange(field.value.filter((indexName) => indexName !== index));
}}
/>
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
<FormProvider {...form}>
<EuiForm
component="form"
css={{ display: 'flex', flexGrow: 1 }}
onSubmit={handleSubmit(onSubmit)}
>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem
grow={2}
css={{
borderRight: euiTheme.border.thin,
paddingTop: euiTheme.size.l,
paddingBottom: euiTheme.size.l,
}}
>
<EuiFlexGroup direction="column" className="eui-fullHeight">
{/* // Set scroll at the border of parent element*/}
<EuiFlexItem
grow={1}
className="eui-yScroll"
css={{ paddingLeft: euiTheme.size.l, paddingRight: euiTheme.size.l }}
tabIndex={0}
>
<MessageList messages={chatMessages} />
</EuiFlexItem>

<EuiFlexItem
grow={false}
css={{ paddingLeft: euiTheme.size.l, paddingRight: euiTheme.size.l }}
>
<EuiHorizontalRule margin="none" />

<EuiSpacer size="m" />

<Controller
name={ChatFormFields.question}
control={control}
defaultValue=""
rules={{
required: true,
validate: (rule) => !!rule?.trim(),
}}
render={({ field }) => (
<QuestionInput
value={field.value}
onChange={field.onChange}
isDisabled={isSubmitting}
button={
isSubmitting ? (
<EuiButtonIcon
aria-label={i18n.translate('aiPlayground.chat.stopButtonAriaLabel', {
defaultMessage: 'Stop request',
})}
display="base"
size="s"
iconType="stop"
onClick={stopRequest}
/>
) : (
<EuiButtonIcon
aria-label={i18n.translate('aiPlayground.chat.sendButtonAriaLabel', {
defaultMessage: 'Send a question',
})}
display={isValid ? 'base' : 'empty'}
size="s"
type="submit"
isLoading={isSubmitting}
isDisabled={!isValid}
iconType={TelegramIcon}
/>
)
}
/>
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

<EuiFlexItem grow={1}>
<ChatSidebar selectedIndicesCount={selectedIndicesCount} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
</FormProvider>
);
};
86 changes: 86 additions & 0 deletions packages/kbn-ai-playground/components/chat_sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 {
EuiAccordion,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
EuiTitle,
useEuiTheme,
useGeneratedHtmlId,
} from '@elastic/eui';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { SourcesPanelSidebar } from './sources_panel/sources_panel_sidebar';
import { SummarizationPanel } from './summarization_panel/summarization_panel';

interface ChatSidebarProps {
selectedIndicesCount: number;
}

export const ChatSidebar: React.FC<ChatSidebarProps> = ({ selectedIndicesCount }) => {
const { euiTheme } = useEuiTheme();
const accordions = [
{
id: useGeneratedHtmlId({ prefix: 'summarizationAccordion' }),
title: i18n.translate('aiPlayground.sidebar.summarizationTitle', {
defaultMessage: 'Summarization',
}),
children: <SummarizationPanel />,
},
{
id: useGeneratedHtmlId({ prefix: 'sourcesAccordion' }),
title: i18n.translate('aiPlayground.sidebar.sourceTitle', { defaultMessage: 'Sources' }),
extraAction: !!selectedIndicesCount && (
<EuiText size="xs">
<p>
{i18n.translate('aiPlayground.sidebar.sourceIndicesCount', {
defaultMessage: '{count, number} {count, plural, one {Index} other {Indices}}',
values: { count: Number(selectedIndicesCount) },
})}
</p>
</EuiText>
),
children: <SourcesPanelSidebar />,
},
];
const [openAccordionId, setOpenAccordionId] = useState(accordions[0].id);

return (
<EuiFlexGroup direction="column" gutterSize="none">
{accordions.map(({ id, title, extraAction, children }, index) => (
<EuiFlexItem
key={id}
css={{
borderBottom: index === accordions.length - 1 ? 'none' : euiTheme.border.thin,
padding: `0 ${euiTheme.size.l}`,
flexGrow: openAccordionId === id ? 1 : 0,
transition: `${euiTheme.animation.normal} ease-in-out`,
}}
>
<EuiAccordion
id={id}
buttonContent={
<EuiTitle size="xs">
<h5>{title}</h5>
</EuiTitle>
}
extraAction={extraAction}
buttonProps={{ paddingSize: 'l' }}
forceState={openAccordionId === id ? 'open' : 'closed'}
onToggle={() => setOpenAccordionId(openAccordionId === id ? '' : id)}
>
{children}
<EuiSpacer size="l" />
</EuiAccordion>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};
Loading

0 comments on commit 1cf4e33

Please sign in to comment.