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([]); const [loading, setLoading] = useState(false); const [clsLoading, setClsLoading] = useState(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: , can_use: canAbort, key: 'abort', onClick: () => { if (!canAbort) { return; } ctrl.abort(); setTimeout(() => { setCanAbort(false); setReplyLoading(false); }, 100); }, }, { tip: t('answer_again'), icon: , 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 ? ( } /> ) : ( ), 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 => (
{ item.onClick?.(); }} > {item.icon}
))} ); }; const fileName = useMemo(() => { try { return JSON.parse(currentDialogue.select_param).file_name; } catch { return ''; } }, [currentDialogue.select_param]); return (
{returnTools(rightToolsConfig)}
{(fileName || fileList[0]?.name) && (
file-icon {fileName || fileList[0]?.name}
} />
)}
); }; export default ToolsBar;