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> {
|
VariantProps<typeof textAreaVariants> {
|
||||||
autoResize?: AutoResizeOptions;
|
autoResize?: AutoResizeOptions;
|
||||||
onPressMetaEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
onPressMetaEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
|
onPressEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
|
export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
|
||||||
|
@ -49,6 +50,7 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
|
||||||
rows = 1,
|
rows = 1,
|
||||||
rounding = 'default',
|
rounding = 'default',
|
||||||
onPressMetaEnter,
|
onPressMetaEnter,
|
||||||
|
onPressEnter,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
|
@ -120,9 +122,14 @@ export const InputTextArea = React.forwardRef<HTMLTextAreaElement, InputTextArea
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleKeyDown = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
if (e.key === 'Enter') {
|
||||||
|
if ((e.metaKey || e.ctrlKey) && onPressMetaEnter) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onPressMetaEnter?.(e);
|
onPressMetaEnter(e);
|
||||||
|
} else if (!e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
onPressEnter?.(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
props.onKeyDown?.(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 { InputTextArea, InputTextAreaProps } from './InputTextArea';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
|
@ -6,7 +6,6 @@ import { Button } from '../buttons/Button';
|
||||||
import { ArrowUp } from '../icons/NucleoIconOutlined';
|
import { ArrowUp } from '../icons/NucleoIconOutlined';
|
||||||
import { ShapeSquare } from '../icons/NucleoIconFilled';
|
import { ShapeSquare } from '../icons/NucleoIconFilled';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Tooltip } from '../tooltip';
|
|
||||||
|
|
||||||
const inputTextAreaButtonVariants = cva(
|
const inputTextAreaButtonVariants = cva(
|
||||||
'relative flex w-full items-center overflow-visible rounded-xl border border-border transition-all duration-200',
|
'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);
|
onSubmit(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onPressMetaEnter = useMemoizedFn(() => {
|
|
||||||
onSubmitPreflight();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
@ -74,7 +69,8 @@ export const InputTextAreaButton = forwardRef<HTMLTextAreaElement, InputTextArea
|
||||||
)}
|
)}
|
||||||
autoResize={autoResize}
|
autoResize={autoResize}
|
||||||
rounding="xl"
|
rounding="xl"
|
||||||
onPressMetaEnter={onPressMetaEnter}
|
onPressMetaEnter={onSubmitPreflight}
|
||||||
|
onPressEnter={onSubmitPreflight}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,7 @@ import type {
|
||||||
} from '@/api/asset_interfaces';
|
} from '@/api/asset_interfaces';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import sample from 'lodash/sample';
|
import sample from 'lodash/sample';
|
||||||
import last from 'lodash/last';
|
|
||||||
import { useBusterChatContextSelector } from '../ChatProvider';
|
import { useBusterChatContextSelector } from '../ChatProvider';
|
||||||
import { timeout } from '@/lib/timeout';
|
|
||||||
import random from 'lodash/random';
|
|
||||||
|
|
||||||
export const useAutoAppendThought = () => {
|
export const useAutoAppendThought = () => {
|
||||||
const onUpdateChatMessage = useBusterChatContextSelector((x) => x.onUpdateChatMessage);
|
const onUpdateChatMessage = useBusterChatContextSelector((x) => x.onUpdateChatMessage);
|
||||||
|
|
|
@ -1,90 +1,57 @@
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { ChangeEvent, useMemo, useRef, useState } from 'react';
|
||||||
import { Input } from 'antd';
|
|
||||||
import { createStyles } from 'antd-style';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { inputHasText } from '@/lib';
|
import { inputHasText } from '@/lib/text';
|
||||||
import { AIWarning } from './AIWarning';
|
import { AIWarning } from './AIWarning';
|
||||||
import { SubmitButton } from './SubmitButton';
|
|
||||||
import { useChatInputFlow } from './useChatInputFlow';
|
import { useChatInputFlow } from './useChatInputFlow';
|
||||||
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
|
||||||
import { useChatIndividualContextSelector } from '../../../ChatContext';
|
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(({}) => {
|
export const ChatInput: React.FC<{}> = React.memo(({}) => {
|
||||||
const { styles, cx } = useStyles();
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const inputRef = useRef<TextAreaRef>(null);
|
|
||||||
|
|
||||||
const loading = useChatIndividualContextSelector((x) => x.isLoading);
|
const loading = useChatIndividualContextSelector((x) => x.isLoading);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
|
||||||
|
|
||||||
const disableSendButton = useMemo(() => {
|
const disableSubmit = useMemo(() => {
|
||||||
return !inputHasText(inputValue);
|
return !inputHasText(inputValue);
|
||||||
}, [inputValue]);
|
}, [inputValue]);
|
||||||
|
|
||||||
const { onSubmitPreflight } = useChatInputFlow({
|
const { onSubmitPreflight } = useChatInputFlow({
|
||||||
disableSendButton,
|
disableSubmit,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
loading,
|
loading,
|
||||||
inputRef
|
textAreaRef
|
||||||
});
|
});
|
||||||
|
|
||||||
const onPressEnter = useMemoizedFn((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const onChange = useMemoizedFn((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.metaKey && e.key === 'Enter') {
|
|
||||||
onSubmitPreflight();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onChangeInput = useMemoizedFn((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
setInputValue(e.target.value);
|
setInputValue(e.target.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onBlurInput = useMemoizedFn(() => {
|
const onStop = useMemoizedFn(() => {
|
||||||
setIsFocused(false);
|
// setInputValue('');
|
||||||
});
|
|
||||||
|
|
||||||
const onFocusInput = useMemoizedFn(() => {
|
|
||||||
setIsFocused(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cn(
|
||||||
styles.inputCard,
|
'flex flex-col space-y-1.5',
|
||||||
'z-10 mx-3 mt-0.5 flex min-h-fit flex-col items-center space-y-1.5 overflow-hidden pb-2'
|
'z-10 mx-3 mt-0.5 mb-2 flex min-h-fit flex-col items-center overflow-hidden',
|
||||||
|
'shadow-lg'
|
||||||
)}>
|
)}>
|
||||||
<div
|
<InputTextAreaButton
|
||||||
className={cx(
|
placeholder="Ask Buster a question..."
|
||||||
styles.inputContainer,
|
autoResize={autoResizeConfig}
|
||||||
isFocused && 'focused',
|
onSubmit={onSubmitPreflight}
|
||||||
loading && 'loading',
|
onChange={onChange}
|
||||||
'relative flex w-full items-center overflow-hidden'
|
onStop={onStop}
|
||||||
)}>
|
|
||||||
<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}
|
loading={loading}
|
||||||
onSubmitPreflight={onSubmitPreflight}
|
disabledSubmit={disableSubmit}
|
||||||
|
autoFocus
|
||||||
|
ref={textAreaRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AIWarning />
|
<AIWarning />
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,28 +59,3 @@ export const ChatInput: React.FC<{}> = React.memo(({}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ChatInput.displayName = 'ChatInput';
|
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 React, { useMemo } from 'react';
|
||||||
import { useChatIndividualContextSelector } from '../../../ChatContext';
|
import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { useBusterNewChatContextSelector } from '@/context/Chats';
|
import { useBusterNewChatContextSelector } from '@/context/Chats';
|
||||||
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
|
||||||
|
|
||||||
type FlowType = 'followup-chat' | 'followup-metric' | 'followup-dashboard' | 'new';
|
type FlowType = 'followup-chat' | 'followup-metric' | 'followup-dashboard' | 'new';
|
||||||
|
|
||||||
export const useChatInputFlow = ({
|
export const useChatInputFlow = ({
|
||||||
disableSendButton,
|
disableSubmit,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
inputRef,
|
textAreaRef,
|
||||||
loading
|
loading
|
||||||
}: {
|
}: {
|
||||||
disableSendButton: boolean;
|
disableSubmit: boolean;
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
setInputValue: (value: string) => void;
|
setInputValue: (value: string) => void;
|
||||||
inputRef: React.RefObject<TextAreaRef>;
|
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const hasChat = useChatIndividualContextSelector((x) => x.hasChat);
|
const hasChat = useChatIndividualContextSelector((x) => x.hasChat);
|
||||||
|
@ -37,7 +36,7 @@ export const useChatInputFlow = ({
|
||||||
}, [hasChat, selectedFileType, selectedFileId]);
|
}, [hasChat, selectedFileType, selectedFileId]);
|
||||||
|
|
||||||
const onSubmitPreflight = useMemoizedFn(async () => {
|
const onSubmitPreflight = useMemoizedFn(async () => {
|
||||||
if (disableSendButton || !chatId || !currentMessageId) return;
|
if (disableSubmit || !chatId || !currentMessageId) return;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
onStopChat({ chatId: chatId!, messageId: currentMessageId });
|
onStopChat({ chatId: chatId!, messageId: currentMessageId });
|
||||||
|
@ -76,7 +75,7 @@ export const useChatInputFlow = ({
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inputRef.current?.focus();
|
textAreaRef.current?.focus();
|
||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue