-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[OneChat] Simple Chat UI #222816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[OneChat] Simple Chat UI #222816
Changes from all commits
48ec5fa
9d80f2c
ab154d2
4be24c4
7ae836f
2bb4f99
d0453c9
5936550
b1c22d5
58df7b1
6c5016f
f08e338
0515651
0445f30
af28709
093140b
6c10bf8
3f5ba3e
384c1ff
7d857a1
8e5bc8b
4b3a35d
ae6c28b
b0115e4
2b80594
d4cbb4e
90a5193
37f117b
5cfd707
1a7a368
1f2b5ec
8fb41d1
d4c6b88
d144a5a
fed1912
4a4ab48
60213aa
3b09815
0aa5890
a5d6af1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 React, { useCallback, useRef, useEffect } from 'react'; | ||
| import { css } from '@emotion/css'; | ||
| import { EuiFlexItem, EuiPanel, useEuiTheme, euiScrollBarStyles } from '@elastic/eui'; | ||
| import { useChat } from '../../hooks/use_chat'; | ||
| import { useConversation } from '../../hooks/use_conversation'; | ||
| import { useStickToBottom } from '../../hooks/use_stick_to_bottom'; | ||
| import { ConversationInputForm } from './conversation_input_form'; | ||
| import { ConversationRounds } from './conversation_rounds/conversation_rounds'; | ||
| import { NewConversationPrompt } from './new_conversation_prompt'; | ||
|
|
||
| const fullHeightClassName = css` | ||
| height: 100%; | ||
| `; | ||
|
|
||
| const conversationPanelClass = css` | ||
| min-height: 100%; | ||
| max-width: 850px; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we try not to hardcode widths here. You should be able to reach the euiTheme via hook and get the spacing values.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Styling will likely be changing anyways in follow up |
||
| margin-left: auto; | ||
| margin-right: auto; | ||
| `; | ||
|
|
||
| const scrollContainerClassName = (scrollBarStyles: string) => css` | ||
| overflow-y: auto; | ||
| ${scrollBarStyles} | ||
| `; | ||
|
|
||
| interface ConversationProps { | ||
| agentId: string; | ||
| conversationId: string | undefined; | ||
| } | ||
|
|
||
| export const Conversation: React.FC<ConversationProps> = ({ agentId, conversationId }) => { | ||
| const { conversation } = useConversation({ conversationId }); | ||
| const { sendMessage } = useChat({ | ||
| conversationId, | ||
| agentId, | ||
| }); | ||
|
|
||
| const theme = useEuiTheme(); | ||
| const scrollBarStyles = euiScrollBarStyles(theme); | ||
|
|
||
| const scrollContainerRef = useRef<HTMLDivElement | null>(null); | ||
|
|
||
| const { setStickToBottom } = useStickToBottom({ | ||
| defaultState: true, | ||
| scrollContainer: scrollContainerRef.current, | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| setStickToBottom(true); | ||
| }, [conversationId, setStickToBottom]); | ||
|
|
||
| const onSubmit = useCallback( | ||
| (message: string) => { | ||
| setStickToBottom(true); | ||
| sendMessage(message); | ||
| }, | ||
| [sendMessage, setStickToBottom] | ||
| ); | ||
|
|
||
| if (!conversationId && (!conversation || conversation.rounds.length === 0)) { | ||
| return <NewConversationPrompt onSubmit={onSubmit} />; | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <EuiFlexItem grow className={scrollContainerClassName(scrollBarStyles)}> | ||
| <div ref={scrollContainerRef} className={fullHeightClassName}> | ||
| <EuiPanel hasBorder={false} hasShadow={false} className={conversationPanelClass}> | ||
| <ConversationRounds conversationRounds={conversation?.rounds ?? []} /> | ||
| </EuiPanel> | ||
| </div> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem grow={false}> | ||
| <ConversationInputForm disabled={!agentId} loading={false} onSubmit={onSubmit} /> | ||
| </EuiFlexItem> | ||
| </> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /* | ||
| * 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 React from 'react'; | ||
| import { css } from '@emotion/css'; | ||
| import { | ||
| EuiTitle, | ||
| EuiFlexGroup, | ||
| EuiFlexItem, | ||
| EuiPanel, | ||
| EuiSkeletonTitle, | ||
| useEuiTheme, | ||
| useEuiFontSize, | ||
| } from '@elastic/eui'; | ||
| import { useConversation } from '../../hooks/use_conversation'; | ||
| import { chatCommonLabels } from './i18n'; | ||
|
|
||
| interface ConversationHeaderProps { | ||
| conversationId: string | undefined; | ||
| } | ||
|
|
||
| export const ConversationHeader: React.FC<ConversationHeaderProps> = ({ conversationId }) => { | ||
| const { conversation, isLoading: isConvLoading } = useConversation({ conversationId }); | ||
|
|
||
| const { euiTheme } = useEuiTheme(); | ||
|
|
||
| const containerClass = css` | ||
| padding: ${euiTheme.size.s} ${euiTheme.size.m}; | ||
| border-bottom: solid ${euiTheme.border.width.thin} ${euiTheme.border.color}; | ||
| `; | ||
|
|
||
| const conversationTitleClass = css` | ||
| font-weight: ${euiTheme.font.weight.semiBold}; | ||
| font-size: ${useEuiFontSize('m').fontSize}; | ||
| `; | ||
|
|
||
| return ( | ||
| <EuiFlexItem grow={false}> | ||
| <EuiPanel | ||
| hasBorder={false} | ||
| hasShadow={false} | ||
| borderRadius="none" | ||
| color="subdued" | ||
| className={containerClass} | ||
| > | ||
| <EuiFlexGroup alignItems="center"> | ||
| <EuiFlexItem grow> | ||
| <EuiTitle> | ||
| <EuiSkeletonTitle size="m" isLoading={conversationId !== undefined && isConvLoading}> | ||
| <h3 className={conversationTitleClass}> | ||
| {conversation?.title || chatCommonLabels.chat.conversations.newConversationLabel} | ||
| </h3> | ||
| </EuiSkeletonTitle> | ||
| </EuiTitle> | ||
| </EuiFlexItem> | ||
| </EuiFlexGroup> | ||
| </EuiPanel> | ||
| </EuiFlexItem> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| /* | ||
| * 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 React, { useCallback, useState, KeyboardEvent } from 'react'; | ||
| import { css } from '@emotion/css'; | ||
| import { | ||
| EuiButtonIcon, | ||
| EuiFlexGroup, | ||
| EuiFlexItem, | ||
| EuiTextArea, | ||
| keys, | ||
| useEuiTheme, | ||
| } from '@elastic/eui'; | ||
| import { chatCommonLabels } from './i18n'; | ||
|
|
||
| interface ConversationInputFormProps { | ||
| disabled: boolean; | ||
| loading: boolean; | ||
| onSubmit: (message: string) => void; | ||
| } | ||
|
|
||
| export const ConversationInputForm: React.FC<ConversationInputFormProps> = ({ | ||
| disabled, | ||
| loading, | ||
| onSubmit, | ||
| }) => { | ||
| const [message, setMessage] = useState<string>(''); | ||
| const { euiTheme } = useEuiTheme(); | ||
|
|
||
| const handleSubmit = useCallback(() => { | ||
| if (loading || !message.trim()) { | ||
| return; | ||
| } | ||
|
|
||
| onSubmit(message); | ||
| setMessage(''); | ||
| }, [message, loading, onSubmit]); | ||
|
|
||
| const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
| setMessage(event.currentTarget.value); | ||
| }, []); | ||
|
|
||
| const handleTextAreaKeyDown = useCallback( | ||
| (event: KeyboardEvent<HTMLTextAreaElement>) => { | ||
| if (!event.shiftKey && event.key === keys.ENTER) { | ||
| event.preventDefault(); | ||
| handleSubmit(); | ||
| } | ||
| }, | ||
| [handleSubmit] | ||
| ); | ||
|
|
||
| const topContainerClass = css` | ||
| padding-bottom: ${euiTheme.size.m}; | ||
| `; | ||
|
|
||
| const inputFlexItemClass = css` | ||
| max-width: 900px; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here with hardcoded width |
||
| `; | ||
|
|
||
| return ( | ||
| <EuiFlexGroup | ||
| gutterSize="s" | ||
| responsive={false} | ||
| alignItems="center" | ||
| justifyContent="center" | ||
| className={topContainerClass} | ||
| > | ||
| <EuiFlexItem className={inputFlexItemClass}> | ||
| <EuiTextArea | ||
| data-test-subj="onechatAppConversationInputFormTextArea" | ||
| fullWidth | ||
| rows={1} | ||
| value={message} | ||
| onChange={handleChange} | ||
| onKeyDown={handleTextAreaKeyDown} | ||
| placeholder={chatCommonLabels.userInputBox.placeholder} | ||
| /> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem grow={false}> | ||
| <EuiButtonIcon | ||
| aria-label="Submit" | ||
| data-test-subj="onechatAppConversationInputFormSubmitButton" | ||
| iconType="kqlFunction" | ||
| display="fill" | ||
| size="m" | ||
| onClick={handleSubmit} | ||
| disabled={loading || disabled} | ||
| isLoading={loading} | ||
| /> | ||
| </EuiFlexItem> | ||
| </EuiFlexGroup> | ||
| ); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for starting this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, it was Oren's suggestion