mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-07-23 12:21:08 +00:00
199 lines
6.1 KiB
TypeScript
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;
|