mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-17 23:18:20 +00:00
feat(ChatExcel): Better Chat Excel (#2423)
This commit is contained in:
@@ -63,6 +63,15 @@ export const newDialogue = (data: NewDialogueParam) => {
|
||||
);
|
||||
};
|
||||
|
||||
const buildUrl = (baseUrl: string, params: any) => {
|
||||
const queryString = Object.keys(params)
|
||||
.filter(key => params[key] !== undefined) //
|
||||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||
.join('&');
|
||||
|
||||
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||
};
|
||||
|
||||
export const addUser = (data: UserParam) => {
|
||||
return POST<UserParam, UserParamResponse>('/api/v1/user/add', data);
|
||||
};
|
||||
@@ -112,6 +121,8 @@ export const postChatModeParamsFileLoad = ({
|
||||
data,
|
||||
config,
|
||||
model,
|
||||
temperatureValue,
|
||||
maxNewTokensValue,
|
||||
userName,
|
||||
sysCode,
|
||||
}: {
|
||||
@@ -119,20 +130,30 @@ export const postChatModeParamsFileLoad = ({
|
||||
chatMode: string;
|
||||
data: FormData;
|
||||
model: string;
|
||||
temperatureValue?: number;
|
||||
maxNewTokensValue?: number;
|
||||
userName?: string;
|
||||
sysCode?: string;
|
||||
config?: Omit<AxiosRequestConfig, 'headers'>;
|
||||
}) => {
|
||||
return POST<FormData, any>(
|
||||
`/api/v1/resource/file/upload?conv_uid=${convUid}&chat_mode=${chatMode}&model_name=${model}&user_name=${userName}&sys_code=${sysCode}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
...config,
|
||||
const baseUrl = `/api/v1/resource/file/upload`;
|
||||
const params = {
|
||||
conv_uid: convUid,
|
||||
chat_mode: chatMode,
|
||||
model_name: model,
|
||||
user_name: userName,
|
||||
sys_code: sysCode,
|
||||
temperature: temperatureValue,
|
||||
max_new_tokens: maxNewTokensValue,
|
||||
};
|
||||
|
||||
const url = buildUrl(baseUrl, params);
|
||||
return POST<FormData, any>(url, data, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
);
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const clearChatHistory = (conUid: string) => {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, postChatModeParamsFileLoad } from '@/client/api';
|
||||
import { ChatContentContext } from '@/pages/chat';
|
||||
import { LinkOutlined, SelectOutlined, UploadOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip, Upload, UploadFile, UploadProps, message } from 'antd';
|
||||
import { PropsWithChildren, useContext, useState } from 'react';
|
||||
@@ -17,6 +18,8 @@ function ExcelUpload({ convUid, chatMode, onComplete, ...props }: PropsWithChild
|
||||
const [percent, setPercent] = useState<number>();
|
||||
const { model } = useContext(ChatContext);
|
||||
|
||||
const { temperatureValue, maxNewTokensValue } = useContext(ChatContentContext);
|
||||
|
||||
const onChange: UploadProps['onChange'] = async info => {
|
||||
if (!info) {
|
||||
message.error('Please select the *.(csv|xlsx|xls) file');
|
||||
@@ -42,6 +45,8 @@ function ExcelUpload({ convUid, chatMode, onComplete, ...props }: PropsWithChild
|
||||
chatMode,
|
||||
data: formData,
|
||||
model,
|
||||
temperatureValue,
|
||||
maxNewTokensValue,
|
||||
config: {
|
||||
/** timeout 1h */
|
||||
timeout: 1000 * 60 * 60,
|
||||
|
@@ -341,6 +341,9 @@ export const CommonEn = {
|
||||
All: 'All',
|
||||
Please_input_prompt_name: 'Please input prompt name',
|
||||
Copy_Btn: 'Copy',
|
||||
copy_to_clipboard: 'Copy to clipboard',
|
||||
copy_to_clipboard_success: 'Copy to clipboard success',
|
||||
copy_to_clipboard_failed: 'Copy to clipboard failed',
|
||||
Delete_Btn: 'Delete',
|
||||
publish: 'Publish',
|
||||
unpublish: 'Unpublished',
|
||||
|
@@ -347,6 +347,9 @@ export const CommonZh: Resources['translation'] = {
|
||||
Please_input_prompt_name: '请输入prompt名称',
|
||||
Copy_Btn: '复制',
|
||||
Delete_Btn: '删除',
|
||||
copy_to_clipboard: '复制到剪贴板',
|
||||
copy_to_clipboard_success: '复制到剪贴板成功',
|
||||
copy_to_clipboard_failed: '复制到剪贴板失败',
|
||||
publish: '发布',
|
||||
unpublish: '取消发布',
|
||||
publish_desc: '您确认发布该应用吗?',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import ChatHeader from '@/new-components/chat/header/ChatHeader';
|
||||
import { VerticalAlignBottomOutlined, VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
@@ -8,34 +9,103 @@ const ChatCompletion = dynamic(() => import('@/new-components/chat/content/ChatC
|
||||
const ChatContentContainer = ({}, ref: React.ForwardedRef<any>) => {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [isScrollToTop, setIsScrollToTop] = useState<boolean>(false);
|
||||
const [showScrollButtons, setShowScrollButtons] = useState<boolean>(false);
|
||||
const [isAtTop, setIsAtTop] = useState<boolean>(true);
|
||||
const [isAtBottom, setIsAtBottom] = useState<boolean>(false);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return scrollRef.current;
|
||||
});
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!scrollRef.current) return;
|
||||
|
||||
const container = scrollRef.current;
|
||||
const scrollTop = container.scrollTop;
|
||||
const scrollHeight = container.scrollHeight;
|
||||
const clientHeight = container.clientHeight;
|
||||
const buffer = 20; // Small buffer for better UX
|
||||
|
||||
// Check if we're at the top
|
||||
setIsAtTop(scrollTop <= buffer);
|
||||
|
||||
// Check if we're at the bottom
|
||||
setIsAtBottom(scrollTop + clientHeight >= scrollHeight - buffer);
|
||||
|
||||
// Header visibility
|
||||
if (scrollTop >= 42 + 32) {
|
||||
setIsScrollToTop(true);
|
||||
} else {
|
||||
setIsScrollToTop(false);
|
||||
}
|
||||
|
||||
// Show scroll buttons when content is scrollable
|
||||
const isScrollable = scrollHeight > clientHeight;
|
||||
setShowScrollButtons(isScrollable);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.addEventListener('scroll', () => {
|
||||
const st = scrollRef.current?.scrollTop || 0;
|
||||
if (st >= 42 + 32) {
|
||||
setIsScrollToTop(true);
|
||||
} else {
|
||||
setIsScrollToTop(false);
|
||||
}
|
||||
});
|
||||
scrollRef.current.addEventListener('scroll', handleScroll);
|
||||
|
||||
// Check initially if content is scrollable
|
||||
const isScrollable = scrollRef.current.scrollHeight > scrollRef.current.clientHeight;
|
||||
setShowScrollButtons(isScrollable);
|
||||
}
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
scrollRef.current && scrollRef.current.removeEventListener('scroll', () => {});
|
||||
scrollRef.current && scrollRef.current.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const scrollToTop = () => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTo({
|
||||
top: scrollRef.current.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 overflow-hidden relative'>
|
||||
<div ref={scrollRef} className='h-full w-full mx-auto overflow-y-auto'>
|
||||
<ChatHeader isScrollToTop={isScrollToTop} />
|
||||
<ChatCompletion />
|
||||
</div>
|
||||
|
||||
{showScrollButtons && (
|
||||
<div className='absolute right-6 bottom-24 flex flex-col gap-2'>
|
||||
{!isAtTop && (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className='w-10 h-10 bg-white dark:bg-[rgba(255,255,255,0.2)] border border-gray-200 dark:border-[rgba(255,255,255,0.2)] rounded-full flex items-center justify-center shadow-md hover:shadow-lg transition-shadow'
|
||||
aria-label='Scroll to top'
|
||||
>
|
||||
<VerticalAlignTopOutlined className='text-[#525964] dark:text-[rgba(255,255,255,0.85)]' />
|
||||
</button>
|
||||
)}
|
||||
{!isAtBottom && (
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className='w-10 h-10 bg-white dark:bg-[rgba(255,255,255,0.2)] border border-gray-200 dark:border-[rgba(255,255,255,0.2)] rounded-full flex items-center justify-center shadow-md hover:shadow-lg transition-shadow'
|
||||
aria-label='Scroll to bottom'
|
||||
>
|
||||
<VerticalAlignBottomOutlined className='text-[#525964] dark:text-[rgba(255,255,255,0.85)]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -56,12 +56,12 @@ const ChatCompletion: React.FC = () => {
|
||||
if (res) {
|
||||
const paramKey: string[] = res?.param_need?.map(i => i.type) || [];
|
||||
const resModel = res?.param_need?.filter(item => item.type === 'model')[0]?.value || model;
|
||||
const temperature = res?.param_need?.filter(item => item.type === 'temperature')[0]?.value || 0.5;
|
||||
const maxNewTokens = res?.param_need?.filter(item => item.type === 'max_new_tokens')[0]?.value || 2048;
|
||||
const temperature = res?.param_need?.filter(item => item.type === 'temperature')[0]?.value || 0.6;
|
||||
const maxNewTokens = res?.param_need?.filter(item => item.type === 'max_new_tokens')[0]?.value || 4000;
|
||||
const resource = res?.param_need?.filter(item => item.type === 'resource')[0]?.bind_value;
|
||||
setAppInfo(res || ({} as IApp));
|
||||
setTemperatureValue(temperature || 0.5);
|
||||
setMaxNewTokensValue(maxNewTokens || 2048);
|
||||
setTemperatureValue(temperature || 0.6);
|
||||
setMaxNewTokensValue(maxNewTokens || 4000);
|
||||
setModelValue(resModel);
|
||||
setResourceValue(resource);
|
||||
await handleChat(initMessage.message, {
|
||||
|
@@ -1,8 +1,16 @@
|
||||
import markdownComponents from '@/components/chat/chat-content/config';
|
||||
import { IChatDialogueMessageSchema } from '@/types/chat';
|
||||
import { STORAGE_USERINFO_KEY } from '@/utils/constants/index';
|
||||
import { CheckOutlined, ClockCircleOutlined, CloseOutlined, CodeOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
CheckOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseOutlined,
|
||||
CodeOutlined,
|
||||
CopyOutlined,
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { GPTVis } from '@antv/gpt-vis';
|
||||
import { message } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import Image from 'next/image';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
@@ -173,8 +181,36 @@ const ChatContent: React.FC<{
|
||||
<div className={`flex ${scene === 'chat_agent' && !thinking ? 'flex-1' : ''} overflow-hidden`}>
|
||||
{/* 用户提问 */}
|
||||
{!isRobot && (
|
||||
<div className='flex flex-1 items-center text-sm text-[#1c2533] dark:text-white'>
|
||||
{typeof context === 'string' && context}
|
||||
<div className='flex flex-1 relative group'>
|
||||
<div
|
||||
className='flex-1 text-sm text-[#1c2533] dark:text-white'
|
||||
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}
|
||||
>
|
||||
{typeof context === 'string' && context}
|
||||
</div>
|
||||
{typeof context === 'string' && context.trim() && (
|
||||
<div className='absolute right-0 top-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200'>
|
||||
<button
|
||||
className='flex items-center justify-center w-8 h-8 text-[#525964] dark:text-[rgba(255,255,255,0.6)] hover:text-[#1677ff] dark:hover:text-white transition-colors'
|
||||
onClick={() => {
|
||||
if (typeof context === 'string') {
|
||||
navigator.clipboard
|
||||
.writeText(context)
|
||||
.then(() => {
|
||||
message.success(t('copy_to_clipboard_success'));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(t('copy_to_clipboard_failed'), err);
|
||||
message.error(t('copy_to_clipboard_failed'));
|
||||
});
|
||||
}
|
||||
}}
|
||||
title={t('copy_to_clipboard')}
|
||||
>
|
||||
<CopyOutlined />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* ai回答 */}
|
||||
|
@@ -52,7 +52,7 @@ const MaxNewTokens: React.FC<{
|
||||
max={20480}
|
||||
step={1}
|
||||
onChange={handleSliderChange}
|
||||
value={typeof maxNewTokensValue === 'number' ? maxNewTokensValue : 2048}
|
||||
value={typeof maxNewTokensValue === 'number' ? maxNewTokensValue : 4000}
|
||||
/>
|
||||
<InputNumber
|
||||
size='small'
|
||||
|
@@ -21,6 +21,7 @@ const Resource: React.FC<{
|
||||
const { setResourceValue, appInfo, refreshHistory, refreshDialogList, modelValue, resourceValue } =
|
||||
useContext(ChatContentContext);
|
||||
|
||||
const { temperatureValue, maxNewTokensValue } = useContext(ChatContentContext);
|
||||
const searchParams = useSearchParams();
|
||||
const scene = searchParams?.get('scene') ?? '';
|
||||
const chatId = searchParams?.get('id') ?? '';
|
||||
@@ -97,6 +98,8 @@ const Resource: React.FC<{
|
||||
chatMode: scene,
|
||||
data: formData,
|
||||
model: modelValue,
|
||||
temperatureValue,
|
||||
maxNewTokensValue,
|
||||
config: {
|
||||
timeout: 1000 * 60 * 60,
|
||||
},
|
||||
|
@@ -104,8 +104,8 @@ const Chat: React.FC = () => {
|
||||
const [modelValue, setModelValue] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setTemperatureValue(appInfo?.param_need?.filter(item => item.type === 'temperature')[0]?.value || 0.5);
|
||||
setMaxNewTokensValue(appInfo?.param_need?.filter(item => item.type === 'max_new_tokens')[0]?.value || 2048);
|
||||
setTemperatureValue(appInfo?.param_need?.filter(item => item.type === 'temperature')[0]?.value || 0.6);
|
||||
setMaxNewTokensValue(appInfo?.param_need?.filter(item => item.type === 'max_new_tokens')[0]?.value || 4000);
|
||||
setModelValue(appInfo?.param_need?.filter(item => item.type === 'model')[0]?.value || model);
|
||||
setResourceValue(
|
||||
knowledgeId || dbName || appInfo?.param_need?.filter(item => item.type === 'resource')[0]?.bind_value,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { apiInterceptors, postChatModeParamsFileLoad } from '@/client/api';
|
||||
import { ChatContentContext } from '@/pages/chat';
|
||||
import { dbMapper } from '@/utils';
|
||||
import { FolderAddOutlined, LoadingOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import { useRequest } from 'ahooks';
|
||||
@@ -12,6 +13,7 @@ const Resource: React.FC = () => {
|
||||
const { appInfo, resourceList, scene, model, conv_uid, getChatHistoryRun, setResource, resource } =
|
||||
useContext(MobileChatContext);
|
||||
|
||||
const { temperatureValue, maxNewTokensValue } = useContext(ChatContentContext);
|
||||
const [selectedVal, setSelectedVal] = useState<any>(null);
|
||||
|
||||
// 资源类型
|
||||
@@ -51,6 +53,8 @@ const Resource: React.FC = () => {
|
||||
chatMode: scene,
|
||||
data: formData,
|
||||
model,
|
||||
temperatureValue,
|
||||
maxNewTokensValue,
|
||||
config: {
|
||||
timeout: 1000 * 60 * 60,
|
||||
},
|
||||
|
Reference in New Issue
Block a user