mirror of
https://github.com/csunny/DB-GPT.git
synced 2026-07-17 10:16:49 +00:00
Co-authored-by: lusain <lusain1990@gmail.com> Co-authored-by: alan.cl <1165243776@qq.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
250 lines
7.4 KiB
TypeScript
250 lines
7.4 KiB
TypeScript
import { ChatContentContext } from '@/pages/chat';
|
|
import { LoadingOutlined } from '@ant-design/icons';
|
|
import { Button, Input, Spin } from 'antd';
|
|
import classNames from 'classnames';
|
|
import { useSearchParams } from 'next/navigation';
|
|
import React, { forwardRef, useContext, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import { UserChatContent } from '@/types/chat';
|
|
import { parseResourceValue } from '@/utils';
|
|
import { SlashCommand } from './CommandPopover';
|
|
import EnhancedChatInput, { ContentPart, EnhancedChatInputRef } from './EnhancedChatInput';
|
|
import ToolsBar from './ToolsBar';
|
|
|
|
const USE_ENHANCED_INPUT = false;
|
|
|
|
const defaultCommands: SlashCommand[] = [
|
|
{ id: 'clear', trigger: 'clear', title: 'Clear chat history', type: 'builtin' },
|
|
{ id: 'help', trigger: 'help', title: 'Show available commands', type: 'builtin' },
|
|
{ id: 'model', trigger: 'model', title: 'Switch AI model', type: 'builtin' },
|
|
];
|
|
|
|
const ChatInputPanel: React.ForwardRefRenderFunction<any, { ctrl: AbortController }> = ({ ctrl }, ref) => {
|
|
const { t } = useTranslation();
|
|
const {
|
|
replyLoading,
|
|
handleChat,
|
|
appInfo,
|
|
currentDialogue,
|
|
temperatureValue,
|
|
maxNewTokensValue,
|
|
resourceValue,
|
|
knowledgeValue,
|
|
setResourceValue,
|
|
refreshDialogList,
|
|
} = useContext(ChatContentContext);
|
|
|
|
const searchParams = useSearchParams();
|
|
const scene = searchParams?.get('scene') ?? '';
|
|
const select_param = searchParams?.get('select_param') ?? '';
|
|
|
|
const [userInput, setUserInput] = useState<string>('');
|
|
const [isFocus, setIsFocus] = useState<boolean>(false);
|
|
const [isZhInput, setIsZhInput] = useState<boolean>(false);
|
|
|
|
const enhancedInputRef = useRef<EnhancedChatInputRef>(null);
|
|
const submitCountRef = useRef(0);
|
|
|
|
const paramKey: string[] = useMemo(() => {
|
|
return appInfo.param_need?.map(i => i.type) || [];
|
|
}, [appInfo.param_need]);
|
|
|
|
const buildChatParams = () => ({
|
|
app_code: appInfo.app_code || '',
|
|
...(paramKey.includes('temperature') && { temperature: temperatureValue }),
|
|
...(paramKey.includes('max_new_tokens') && { max_new_tokens: maxNewTokensValue }),
|
|
select_param,
|
|
...(paramKey.includes('resource') && {
|
|
select_param:
|
|
typeof resourceValue === 'string'
|
|
? resourceValue
|
|
: JSON.stringify(resourceValue) || currentDialogue.select_param,
|
|
}),
|
|
// Include knowledge space in ext_info for RAG
|
|
...(knowledgeValue && {
|
|
ext_info: { knowledge_space: knowledgeValue },
|
|
}),
|
|
});
|
|
|
|
const handleEnhancedSubmit = async (text: string, parts: ContentPart[]) => {
|
|
submitCountRef.current++;
|
|
|
|
const resources = parseResourceValue(resourceValue);
|
|
let newUserInput: UserChatContent;
|
|
|
|
const imageParts = parts.filter(p => p.type === 'image');
|
|
const hasImages = imageParts.length > 0;
|
|
const hasResources = resources.length > 0;
|
|
|
|
if (hasResources || hasImages) {
|
|
if (scene !== 'chat_excel') {
|
|
setResourceValue(null);
|
|
}
|
|
|
|
const messages = [...resources];
|
|
|
|
imageParts.forEach(img => {
|
|
if (img.dataUrl) {
|
|
messages.push({
|
|
type: 'image_url',
|
|
image_url: {
|
|
url: img.dataUrl,
|
|
fileName: img.filename || 'image',
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
messages.push({
|
|
type: 'text',
|
|
text: text,
|
|
});
|
|
|
|
newUserInput = {
|
|
role: 'user',
|
|
content: messages,
|
|
};
|
|
} else {
|
|
newUserInput = text;
|
|
}
|
|
|
|
const params = buildChatParams();
|
|
await handleChat(newUserInput, params);
|
|
|
|
if (submitCountRef.current === 1) {
|
|
await refreshDialogList();
|
|
}
|
|
};
|
|
|
|
const onLegacySubmit = async () => {
|
|
submitCountRef.current++;
|
|
setUserInput('');
|
|
const resources = parseResourceValue(resourceValue);
|
|
let newUserInput: UserChatContent;
|
|
if (resources.length > 0) {
|
|
if (scene !== 'chat_excel') {
|
|
setResourceValue(null);
|
|
}
|
|
const messages = [...resources];
|
|
messages.push({
|
|
type: 'text',
|
|
text: userInput,
|
|
});
|
|
newUserInput = {
|
|
role: 'user',
|
|
content: messages,
|
|
};
|
|
} else {
|
|
newUserInput = userInput;
|
|
}
|
|
|
|
const params = buildChatParams();
|
|
await handleChat(newUserInput, params);
|
|
|
|
if (submitCountRef.current === 1) {
|
|
await refreshDialogList();
|
|
}
|
|
};
|
|
|
|
const handleCommandSelect = (command: SlashCommand) => {
|
|
console.log('Command selected:', command);
|
|
};
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
setUserInput: (value: string) => {
|
|
if (USE_ENHANCED_INPUT) {
|
|
enhancedInputRef.current?.setValue(value);
|
|
} else {
|
|
setUserInput(value);
|
|
}
|
|
},
|
|
}));
|
|
|
|
if (USE_ENHANCED_INPUT) {
|
|
return (
|
|
<div className='flex flex-col w-5/6 mx-auto pt-4 pb-6 bg-transparent'>
|
|
<div className='flex flex-col bg-white dark:bg-[rgba(255,255,255,0.16)] px-5 py-4 pt-2 rounded-xl relative'>
|
|
<ToolsBar ctrl={ctrl} />
|
|
<EnhancedChatInput
|
|
ref={enhancedInputRef}
|
|
onSubmit={handleEnhancedSubmit}
|
|
disabled={replyLoading}
|
|
loading={replyLoading}
|
|
placeholder={t('input_tips')}
|
|
commands={defaultCommands}
|
|
onCommandSelect={handleCommandSelect}
|
|
className='border-0 bg-transparent'
|
|
maxHeight={150}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='flex flex-col w-5/6 mx-auto pt-4 pb-6 bg-transparent'>
|
|
<div
|
|
className={`flex flex-1 flex-col bg-white dark:bg-[rgba(255,255,255,0.16)] px-5 py-4 pt-2 rounded-xl relative border-t border-b border-l border-r dark:border-[rgba(255,255,255,0.6)] ${
|
|
isFocus ? 'border-[#0c75fc]' : ''
|
|
}`}
|
|
id='input-panel'
|
|
>
|
|
<ToolsBar ctrl={ctrl} />
|
|
<Input.TextArea
|
|
placeholder={t('input_tips')}
|
|
className='w-full h-20 resize-none border-0 p-0 focus:shadow-none dark:bg-transparent'
|
|
value={userInput}
|
|
onKeyDown={e => {
|
|
if (e.key === 'Enter') {
|
|
if (e.shiftKey) {
|
|
return;
|
|
}
|
|
if (isZhInput) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
if (!userInput.trim() || replyLoading) {
|
|
return;
|
|
}
|
|
onLegacySubmit();
|
|
}
|
|
}}
|
|
onChange={e => {
|
|
setUserInput(e.target.value);
|
|
}}
|
|
onFocus={() => {
|
|
setIsFocus(true);
|
|
}}
|
|
onBlur={() => setIsFocus(false)}
|
|
onCompositionStart={() => setIsZhInput(true)}
|
|
onCompositionEnd={() => setIsZhInput(false)}
|
|
/>
|
|
<Button
|
|
type='primary'
|
|
className={classNames(
|
|
'flex items-center justify-center w-14 h-8 rounded-lg text-sm absolute right-4 bottom-3 bg-button-gradient border-0',
|
|
{
|
|
'cursor-not-allowed': !userInput.trim(),
|
|
},
|
|
)}
|
|
onClick={() => {
|
|
if (replyLoading || !userInput.trim()) {
|
|
return;
|
|
}
|
|
onLegacySubmit();
|
|
}}
|
|
>
|
|
{replyLoading ? (
|
|
<Spin spinning={replyLoading} indicator={<LoadingOutlined className='text-white' />} />
|
|
) : (
|
|
t('sent')
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default forwardRef(ChatInputPanel);
|