DB-GPT/web/new-components/chat/input/ToolsBar.tsx

199 lines
6.1 KiB
TypeScript

import { apiInterceptors, clearChatHistory } from '@/client/api';
import { ChatContentContext } from '@/pages/chat';
import { ClearOutlined, LoadingOutlined, PauseCircleOutlined, RedoOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd';
import { Spin, Tooltip } from 'antd';
import classNames from 'classnames';
import Image from 'next/image';
import React, { useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import MaxNewTokens from './MaxNewTokens';
import ModelSwitcher from './ModelSwitcher';
import Resource from './Resource';
import Temperature from './Temperature';
interface ToolsConfig {
icon: React.ReactNode;
can_use: boolean;
key: string;
tip?: string;
onClick?: () => void;
}
const ToolsBar: React.FC<{
ctrl: AbortController;
}> = ({ ctrl }) => {
const { t } = useTranslation();
const {
history,
scrollRef,
canAbort,
replyLoading,
currentDialogue,
appInfo,
temperatureValue,
maxNewTokensValue,
resourceValue,
setTemperatureValue,
setMaxNewTokensValue,
refreshHistory,
setCanAbort,
setReplyLoading,
handleChat,
} = useContext(ChatContentContext);
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [clsLoading, setClsLoading] = useState<boolean>(false);
// 左边工具栏动态可用key
const paramKey: string[] = useMemo(() => {
return appInfo.param_need?.map(i => i.type) || [];
}, [appInfo.param_need]);
const rightToolsConfig: ToolsConfig[] = useMemo(() => {
return [
{
tip: t('stop_replying'),
icon: <PauseCircleOutlined className={classNames({ 'text-[#0c75fc]': canAbort })} />,
can_use: canAbort,
key: 'abort',
onClick: () => {
if (!canAbort) {
return;
}
ctrl.abort();
setTimeout(() => {
setCanAbort(false);
setReplyLoading(false);
}, 100);
},
},
{
tip: t('answer_again'),
icon: <RedoOutlined />,
can_use: !replyLoading && history.length > 0,
key: 'redo',
onClick: async () => {
const lastHuman = history.filter(i => i.role === 'human')?.slice(-1)?.[0];
handleChat(lastHuman?.context || '', {
app_code: appInfo.app_code,
...(paramKey.includes('temperature') && { temperature: temperatureValue }),
...(paramKey.includes('max_new_tokens') && { max_new_tokens: maxNewTokensValue }),
...(paramKey.includes('resource') && {
select_param:
typeof resourceValue === 'string'
? resourceValue
: JSON.stringify(resourceValue) || currentDialogue.select_param,
}),
});
setTimeout(() => {
scrollRef.current?.scrollTo({
top: scrollRef.current?.scrollHeight,
behavior: 'smooth',
});
}, 0);
},
},
{
tip: t('erase_memory'),
icon: clsLoading ? (
<Spin spinning={clsLoading} indicator={<LoadingOutlined style={{ fontSize: 20 }} />} />
) : (
<ClearOutlined />
),
can_use: history.length > 0,
key: 'clear',
onClick: async () => {
if (clsLoading) {
return;
}
setClsLoading(true);
await apiInterceptors(clearChatHistory(currentDialogue.conv_uid)).finally(async () => {
await refreshHistory();
setClsLoading(false);
});
},
},
];
}, [
t,
canAbort,
replyLoading,
history,
clsLoading,
ctrl,
setCanAbort,
setReplyLoading,
handleChat,
appInfo.app_code,
paramKey,
temperatureValue,
resourceValue,
currentDialogue.select_param,
currentDialogue.conv_uid,
scrollRef,
refreshHistory,
]);
const returnTools = (config: ToolsConfig[]) => {
return (
<>
{config.map(item => (
<Tooltip key={item.key} title={item.tip} arrow={false} placement='bottom'>
<div
className={`flex w-8 h-8 items-center justify-center rounded-md hover:bg-[rgb(221,221,221,0.6)] text-lg ${
item.can_use ? 'cursor-pointer' : 'opacity-30 cursor-not-allowed'
}`}
onClick={() => {
item.onClick?.();
}}
>
{item.icon}
</div>
</Tooltip>
))}
</>
);
};
const fileName = useMemo(() => {
try {
return JSON.parse(currentDialogue.select_param).file_name;
} catch {
return '';
}
}, [currentDialogue.select_param]);
return (
<div className='flex flex-col mb-2'>
<div className='flex items-center justify-between h-full w-full'>
<div className='flex gap-3 text-lg'>
<ModelSwitcher />
<Resource fileList={fileList} setFileList={setFileList} setLoading={setLoading} fileName={fileName} />
<Temperature temperatureValue={temperatureValue} setTemperatureValue={setTemperatureValue} />
<MaxNewTokens maxNewTokensValue={maxNewTokensValue} setMaxNewTokensValue={setMaxNewTokensValue} />
</div>
<div className='flex gap-1'>{returnTools(rightToolsConfig)}</div>
</div>
{(fileName || fileList[0]?.name) && (
<div className='group/item flex mt-2'>
<div className='flex items-center justify-between w-64 border border-[#e3e4e6] dark:border-[rgba(255,255,255,0.6)] rounded-lg p-2'>
<div className='flex items-center'>
<Image src={`/icons/chat/excel.png`} width={20} height={20} alt='file-icon' className='mr-2' />
<span className='text-sm text-[#1c2533] dark:text-white line-clamp-1'>
{fileName || fileList[0]?.name}
</span>
</div>
<Spin spinning={loading} indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
</div>
</div>
)}
</div>
);
};
export default ToolsBar;