input chat

This commit is contained in:
Nate Kelley 2025-03-05 10:36:31 -07:00
parent ce4a188e1f
commit be76c32f69
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 47 additions and 106 deletions

View File

@ -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);
});

View File

@ -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}
/>

View File

@ -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);

View File

@ -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};
}
}
`
}));

View File

@ -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);
});