mirror of https://github.com/buster-so/buster.git
input chat
This commit is contained in:
parent
ce4a188e1f
commit
be76c32f69
|
@ -37,6 +37,7 @@ export interface InputTextAreaProps
|
|||
VariantProps<typeof textAreaVariants> {
|
||||
autoResize?: AutoResizeOptions;
|
||||
onPressMetaEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
onPressEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
|
||||
|
@ -49,6 +50,7 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
|
|||
rows = 1,
|
||||
rounding = 'default',
|
||||
onPressMetaEnter,
|
||||
onPressEnter,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
|
@ -120,9 +122,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
|
|||
});
|
||||
|
||||
const handleKeyDown = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
onPressMetaEnter?.(e);
|
||||
if (e.key === 'Enter') {
|
||||
if ((e.metaKey || e.ctrlKey) && onPressMetaEnter) {
|
||||
e.preventDefault();
|
||||
onPressMetaEnter(e);
|
||||
} else if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
onPressEnter?.(e);
|
||||
}
|
||||
}
|
||||
props.onKeyDown?.(e);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo, useRef, forwardRef } from 'react';
|
||||
import React, { useMemo, forwardRef } from 'react';
|
||||
import { InputTextArea, InputTextAreaProps } from './InputTextArea';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
@ -6,7 +6,6 @@ import { Button } from '../buttons/Button';
|
|||
import { ArrowUp } from '../icons/NucleoIconOutlined';
|
||||
import { ShapeSquare } from '../icons/NucleoIconFilled';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Tooltip } from '../tooltip';
|
||||
|
||||
const inputTextAreaButtonVariants = cva(
|
||||
'relative flex w-full items-center overflow-visible rounded-xl border border-border transition-all duration-200',
|
||||
|
@ -53,10 +52,6 @@ export const InputTextAreaButton = forwardRef<HTMLTextAreaElement, InputTextArea
|
|||
onSubmit(text);
|
||||
});
|
||||
|
||||
const onPressMetaEnter = useMemoizedFn(() => {
|
||||
onSubmitPreflight();
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
@ -74,7 +69,8 @@ export const InputTextAreaButton = forwardRef<HTMLTextAreaElement, InputTextArea
|
|||
)}
|
||||
autoResize={autoResize}
|
||||
rounding="xl"
|
||||
onPressMetaEnter={onPressMetaEnter}
|
||||
onPressMetaEnter={onSubmitPreflight}
|
||||
onPressEnter={onSubmitPreflight}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
|
|
|
@ -4,10 +4,7 @@ import type {
|
|||
} from '@/api/asset_interfaces';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import sample from 'lodash/sample';
|
||||
import last from 'lodash/last';
|
||||
import { useBusterChatContextSelector } from '../ChatProvider';
|
||||
import { timeout } from '@/lib/timeout';
|
||||
import random from 'lodash/random';
|
||||
|
||||
export const useAutoAppendThought = () => {
|
||||
const onUpdateChatMessage = useBusterChatContextSelector((x) => x.onUpdateChatMessage);
|
||||
|
|
|
@ -1,90 +1,57 @@
|
|||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { ChangeEvent, useMemo, useRef, useState } from 'react';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { inputHasText } from '@/lib';
|
||||
import { inputHasText } from '@/lib/text';
|
||||
import { AIWarning } from './AIWarning';
|
||||
import { SubmitButton } from './SubmitButton';
|
||||
import { useChatInputFlow } from './useChatInputFlow';
|
||||
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
||||
import { useChatIndividualContextSelector } from '../../../ChatContext';
|
||||
import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
|
||||
const autoSize = { minRows: 3, maxRows: 16 };
|
||||
const autoResizeConfig = { minRows: 3, maxRows: 16 };
|
||||
|
||||
export const ChatInput: React.FC<{}> = React.memo(({}) => {
|
||||
const { styles, cx } = useStyles();
|
||||
const inputRef = useRef<TextAreaRef>(null);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const loading = useChatIndividualContextSelector((x) => x.isLoading);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const disableSendButton = useMemo(() => {
|
||||
const disableSubmit = useMemo(() => {
|
||||
return !inputHasText(inputValue);
|
||||
}, [inputValue]);
|
||||
|
||||
const { onSubmitPreflight } = useChatInputFlow({
|
||||
disableSendButton,
|
||||
disableSubmit,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
loading,
|
||||
inputRef
|
||||
textAreaRef
|
||||
});
|
||||
|
||||
const onPressEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.metaKey && e.key === 'Enter') {
|
||||
onSubmitPreflight();
|
||||
}
|
||||
});
|
||||
|
||||
const onChangeInput = useMemoizedFn((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const onChange = useMemoizedFn((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputValue(e.target.value);
|
||||
});
|
||||
|
||||
const onBlurInput = useMemoizedFn(() => {
|
||||
setIsFocused(false);
|
||||
});
|
||||
|
||||
const onFocusInput = useMemoizedFn(() => {
|
||||
setIsFocused(true);
|
||||
const onStop = useMemoizedFn(() => {
|
||||
// setInputValue('');
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
styles.inputCard,
|
||||
'z-10 mx-3 mt-0.5 flex min-h-fit flex-col items-center space-y-1.5 overflow-hidden pb-2'
|
||||
className={cn(
|
||||
'flex flex-col space-y-1.5',
|
||||
'z-10 mx-3 mt-0.5 mb-2 flex min-h-fit flex-col items-center overflow-hidden',
|
||||
'shadow-lg'
|
||||
)}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.inputContainer,
|
||||
isFocused && 'focused',
|
||||
loading && 'loading',
|
||||
'relative flex w-full items-center overflow-hidden'
|
||||
)}>
|
||||
<Input.TextArea
|
||||
ref={inputRef}
|
||||
variant="borderless"
|
||||
onBlur={onBlurInput}
|
||||
onFocus={onFocusInput}
|
||||
className="inline-block w-full pt-2! pr-9! pb-2! pl-3.5! align-middle"
|
||||
placeholder="Ask a follow up..."
|
||||
value={inputValue}
|
||||
autoFocus={true}
|
||||
onChange={onChangeInput}
|
||||
onPressEnter={onPressEnter}
|
||||
disabled={loading}
|
||||
autoSize={autoSize}
|
||||
/>
|
||||
|
||||
<div className="absolute right-2 bottom-2">
|
||||
<SubmitButton
|
||||
disableSendButton={disableSendButton}
|
||||
loading={loading}
|
||||
onSubmitPreflight={onSubmitPreflight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InputTextAreaButton
|
||||
placeholder="Ask Buster a question..."
|
||||
autoResize={autoResizeConfig}
|
||||
onSubmit={onSubmitPreflight}
|
||||
onChange={onChange}
|
||||
onStop={onStop}
|
||||
loading={loading}
|
||||
disabledSubmit={disableSubmit}
|
||||
autoFocus
|
||||
ref={textAreaRef}
|
||||
/>
|
||||
|
||||
<AIWarning />
|
||||
</div>
|
||||
|
@ -92,28 +59,3 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => {
|
|||
});
|
||||
|
||||
ChatInput.displayName = 'ChatInput';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
inputCard: css`
|
||||
box-shadow: 0px -10px 18px 10px ${token.colorBgContainerDisabled};
|
||||
`,
|
||||
inputContainer: css`
|
||||
background: ${token.colorBgBase};
|
||||
border-radius: ${token.borderRadius}px;
|
||||
border: 0.5px solid ${token.colorBorder};
|
||||
transition: border-color 0.2s;
|
||||
min-height: 40px;
|
||||
&:hover {
|
||||
border-color: ${token.colorPrimaryHover};
|
||||
}
|
||||
&.focused {
|
||||
border-color: ${token.colorPrimary};
|
||||
}
|
||||
&.loading {
|
||||
border-color: ${token.colorText};
|
||||
textarea {
|
||||
background: ${token.colorBgContainerDisabled};
|
||||
}
|
||||
}
|
||||
`
|
||||
}));
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { useChatIndividualContextSelector } from '../../../ChatContext';
|
||||
import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useBusterNewChatContextSelector } from '@/context/Chats';
|
||||
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
||||
|
||||
type FlowType = 'followup-chat' | 'followup-metric' | 'followup-dashboard' | 'new';
|
||||
|
||||
export const useChatInputFlow = ({
|
||||
disableSendButton,
|
||||
disableSubmit,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
inputRef,
|
||||
textAreaRef,
|
||||
loading
|
||||
}: {
|
||||
disableSendButton: boolean;
|
||||
disableSubmit: boolean;
|
||||
inputValue: string;
|
||||
setInputValue: (value: string) => void;
|
||||
inputRef: React.RefObject<TextAreaRef>;
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
loading: boolean;
|
||||
}) => {
|
||||
const hasChat = useChatIndividualContextSelector((x) => x.hasChat);
|
||||
|
@ -37,7 +36,7 @@ export const useChatInputFlow = ({
|
|||
}, [hasChat, selectedFileType, selectedFileId]);
|
||||
|
||||
const onSubmitPreflight = useMemoizedFn(async () => {
|
||||
if (disableSendButton || !chatId || !currentMessageId) return;
|
||||
if (disableSubmit || !chatId || !currentMessageId) return;
|
||||
|
||||
if (loading) {
|
||||
onStopChat({ chatId: chatId!, messageId: currentMessageId });
|
||||
|
@ -76,7 +75,7 @@ export const useChatInputFlow = ({
|
|||
setInputValue('');
|
||||
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
textAreaRef.current?.focus();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue