completion-input.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { ChatContext } from '@/app/chat-context';
  2. import { apiInterceptors, getDocumentList } from '@/client/api';
  3. import { IDocument } from '@/types/knowledge';
  4. import { SendOutlined } from '@ant-design/icons';
  5. import { Button, Input } from 'antd';
  6. import { PropsWithChildren, useContext, useEffect, useMemo, useRef, useState } from 'react';
  7. import DocList from '../chat/doc-list';
  8. import DocUpload from '../chat/doc-upload';
  9. import PromptBot from './prompt-bot';
  10. type TextAreaProps = Omit<Parameters<typeof Input.TextArea>[0], 'value' | 'onPressEnter' | 'onChange' | 'onSubmit'>;
  11. interface Props {
  12. onSubmit: (val: string) => void;
  13. handleFinish?: (val: boolean) => void;
  14. loading?: boolean;
  15. placeholder?: string;
  16. }
  17. function CompletionInput({
  18. children,
  19. loading,
  20. onSubmit,
  21. handleFinish,
  22. placeholder,
  23. ...props
  24. }: PropsWithChildren<Props & TextAreaProps>) {
  25. const { dbParam, scene } = useContext(ChatContext);
  26. const [userInput, setUserInput] = useState('');
  27. const showUpload = useMemo(() => scene === 'chat_knowledge', [scene]);
  28. const [documents, setDocuments] = useState<IDocument[]>([]);
  29. const uploadCountRef = useRef(0);
  30. useEffect(() => {
  31. showUpload && fetchDocuments();
  32. }, [dbParam]);
  33. async function fetchDocuments() {
  34. if (!dbParam) {
  35. return null;
  36. }
  37. const [_, data] = await apiInterceptors(
  38. getDocumentList(dbParam, {
  39. page: 1,
  40. page_size: uploadCountRef.current,
  41. }),
  42. );
  43. setDocuments(data?.data || []);
  44. }
  45. const onUploadFinish = async () => {
  46. uploadCountRef.current += 1;
  47. await fetchDocuments();
  48. };
  49. return (
  50. <div className='flex-1 relative'>
  51. <DocList documents={documents} dbParam={dbParam} />
  52. {showUpload && (
  53. <DocUpload handleFinish={handleFinish} onUploadFinish={onUploadFinish} className='absolute z-10 top-2 left-2' />
  54. )}
  55. <Input.TextArea
  56. className={`flex-1 ${showUpload ? 'pl-10' : ''} pr-10`}
  57. size='large'
  58. value={userInput}
  59. autoSize={{ minRows: 1, maxRows: 4 }}
  60. {...props}
  61. onPressEnter={e => {
  62. if (!userInput.trim()) return;
  63. if (e.keyCode === 13) {
  64. if (e.shiftKey) {
  65. e.preventDefault();
  66. setUserInput(state => state + '\n');
  67. return;
  68. }
  69. onSubmit(userInput);
  70. setTimeout(() => {
  71. setUserInput('');
  72. }, 0);
  73. }
  74. }}
  75. onChange={e => {
  76. if (typeof props.maxLength === 'number') {
  77. setUserInput(e.target.value.substring(0, props.maxLength));
  78. return;
  79. }
  80. setUserInput(e.target.value);
  81. }}
  82. placeholder={placeholder}
  83. />
  84. <Button
  85. className='ml-2 flex items-center justify-center absolute right-0 bottom-0'
  86. size='large'
  87. type='text'
  88. loading={loading}
  89. icon={<SendOutlined />}
  90. onClick={() => {
  91. onSubmit(userInput);
  92. }}
  93. />
  94. <PromptBot
  95. submit={prompt => {
  96. setUserInput(userInput + prompt);
  97. }}
  98. />
  99. {children}
  100. </div>
  101. );
  102. }
  103. export default CompletionInput;