feat: edit chat_dashboard style logic

This commit is contained in:
wb-lh513319
2024-08-13 15:05:26 +08:00
parent 20db9550e6
commit 650156367c
7 changed files with 325 additions and 310 deletions

View File

@@ -15,7 +15,7 @@ export const ChatEn = {
copy_success: 'Copy success',
copy_failed: 'Copy failed',
file_tip: 'File cannot be changed after upload',
assistant: 'DataFun Assistant',
assistant: 'Platform Assistant', // DataFun Assistant
model_tip: 'Model selection is not supported for the current application',
temperature_tip: 'The current application does not support temperature configuration',
extend_tip: 'Extended configuration is not supported for the current application',

View File

@@ -24,7 +24,7 @@ export const ChatZh: Resources['translation'] = {
copy_nothing: '内容复制为空',
file_tip: '文件上传后无法更改',
chat_online: '在线对话',
assistant: '灵数平台小助手',
assistant: '平台小助手', // 灵数平台小助手
model_tip: '当前应用暂不支持模型选择',
temperature_tip: '当前应用暂不支持温度配置',
extend_tip: '当前应用暂不支持拓展配置',

View File

@@ -0,0 +1,298 @@
import ChatContentContainer from '@/ant-components/chat/ChatContentContainer';
import ChatDefault from '@/ant-components/chat/content/ChatDefault';
import ChatInputPanel from '@/ant-components/chat/input/ChatInputPanel';
import ChatSider from '@/ant-components/chat/sider/ChatSider';
import { ChatContext } from '@/app/chat-context';
import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList } from '@/client/api';
import useChat from '@/hooks/use-chat';
import { IApp } from '@/types/app';
import { ChartData, ChatHistoryResponse, IChatDialogueSchema } from '@/types/chat';
import { getInitMessage } from '@/utils';
import { useAsyncEffect, useRequest } from 'ahooks';
import { Flex, Layout, Spin } from 'antd';
import { useSearchParams } from 'next/navigation';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
const { Content } = Layout;
interface ChatContentProps {
history: ChatHistoryResponse; // 会话记录列表
replyLoading: boolean; // 对话回复loading
scrollRef: React.RefObject<HTMLDivElement>; // 会话内容可滚动dom
canAbort: boolean; // 是否能中断回复
chartsData: ChartData[];
agent: string;
currentDialogue: IChatDialogueSchema; // 当前选择的会话
appInfo: IApp;
temperatureValue: any;
resourceValue: any;
modelValue: string;
setModelValue: React.Dispatch<React.SetStateAction<string>>;
setTemperatureValue: React.Dispatch<React.SetStateAction<any>>;
setResourceValue: React.Dispatch<React.SetStateAction<any>>;
setAppInfo: React.Dispatch<React.SetStateAction<IApp>>;
setAgent: React.Dispatch<React.SetStateAction<string>>;
setCanAbort: React.Dispatch<React.SetStateAction<boolean>>;
setReplyLoading: React.Dispatch<React.SetStateAction<boolean>>;
handleChat: (content: string, data?: Record<string, any>) => Promise<void>; // 处理会话请求逻辑函数
refreshDialogList: () => void;
refreshHistory: () => void;
refreshAppInfo: () => void;
setHistory: React.Dispatch<React.SetStateAction<ChatHistoryResponse>>;
}
export const ChatContentContext = createContext<ChatContentProps>({
history: [],
replyLoading: false,
scrollRef: { current: null },
canAbort: false,
chartsData: [],
agent: '',
currentDialogue: {} as any,
appInfo: {} as any,
temperatureValue: 0.5,
resourceValue: {},
modelValue: '',
setModelValue: () => {},
setResourceValue: () => {},
setTemperatureValue: () => {},
setAppInfo: () => {},
setAgent: () => {},
setCanAbort: () => {},
setReplyLoading: () => {},
refreshDialogList: () => {},
refreshHistory: () => {},
refreshAppInfo: () => {},
setHistory: () => {},
handleChat: () => Promise.resolve(),
});
const Chat: React.FC = () => {
const { model, currentDialogInfo } = useContext(ChatContext);
const { chat, ctrl } = useChat({ app_code: currentDialogInfo.app_code || '' });
const searchParams = useSearchParams();
const chatId = searchParams?.get('id') ?? '';
const scene = searchParams?.get('scene') ?? '';
const knowledgeId = searchParams?.get('knowledge_id') ?? '';
const dbName = searchParams?.get('db_name') ?? '';
const scrollRef = useRef<HTMLDivElement>(null);
const order = useRef<number>(1);
const [history, setHistory] = useState<ChatHistoryResponse>([]);
const [chartsData, setChartsData] = useState<Array<ChartData>>();
const [replyLoading, setReplyLoading] = useState<boolean>(false);
const [canAbort, setCanAbort] = useState<boolean>(false);
const [agent, setAgent] = useState<string>('');
const [appInfo, setAppInfo] = useState<IApp>({} as IApp);
const [temperatureValue, setTemperatureValue] = useState();
const [resourceValue, setResourceValue] = useState<any>();
const [modelValue, setModelValue] = useState<string>('');
useEffect(() => {
setTemperatureValue(appInfo?.param_need?.filter((item) => item.type === 'temperature')[0]?.value || 0.5);
setModelValue(appInfo?.param_need?.filter((item) => item.type === 'model')[0]?.value || model);
setResourceValue(Number(knowledgeId) || dbName || appInfo?.param_need?.filter((item) => item.type === 'resource')[0]?.bind_value);
}, [appInfo, dbName, knowledgeId, model]);
// 是否是默认小助手
const isChatDefault = useMemo(() => {
return !chatId && !scene;
}, [chatId, scene]);
// 获取会话列表
const {
data: dialogueList = [],
refresh: refreshDialogList,
loading: listLoading,
} = useRequest(async () => {
return await apiInterceptors(getDialogueList());
});
// 获取应用详情
const { run: queryAppInfo, refresh: refreshAppInfo } = useRequest(
async () =>
await apiInterceptors(
getAppInfo({
...currentDialogInfo,
}),
),
{
manual: true,
onSuccess: (data) => {
const [, res] = data;
setAppInfo(res || ({} as IApp));
},
},
);
// 列表当前活跃对话
const currentDialogue = useMemo(() => {
const [, list] = dialogueList;
return list?.find((item) => item.conv_uid === chatId) || ({} as IChatDialogueSchema);
}, [chatId, dialogueList]);
useEffect(() => {
const initMessage = getInitMessage();
if (currentDialogInfo.chat_scene === scene && !isChatDefault && !(initMessage && initMessage.message)) {
queryAppInfo();
}
}, [chatId, currentDialogInfo, isChatDefault, queryAppInfo, scene]);
// 获取会话历史记录
const {
run: getHistory,
loading: historyLoading,
refresh: refreshHistory,
} = useRequest(async () => await apiInterceptors(getChatHistory(chatId)), {
manual: true,
onSuccess: (data) => {
const [, res] = data;
const viewList = res?.filter((item) => item.role === 'view');
if (viewList && viewList.length > 0) {
order.current = viewList[viewList.length - 1].order + 1;
}
setHistory(res || []);
},
});
// 会话提问
const handleChat = useCallback(
(content: string, data?: Record<string, any>) => {
return new Promise<void>((resolve) => {
const initMessage = getInitMessage();
const ctrl = new AbortController();
setReplyLoading(true);
if (history && history.length > 0) {
const viewList = history?.filter((item) => item.role === 'view');
const humanList = history?.filter((item) => item.role === 'human');
order.current = (viewList[viewList.length - 1]?.order || humanList[humanList.length - 1]?.order) + 1;
}
const tempHistory: ChatHistoryResponse = [
...(initMessage && initMessage.id === chatId ? [] : history),
{ role: 'human', context: content, model_name: data?.model_name || modelValue, order: order.current, time_stamp: 0 },
{ role: 'view', context: '', model_name: data?.model_name || modelValue, order: order.current, time_stamp: 0, thinking: true },
];
const index = tempHistory.length - 1;
setHistory([...tempHistory]);
chat({
data: {
chat_mode: scene,
model_name: modelValue,
user_input: content,
...data,
},
ctrl,
chatId,
onMessage: (message) => {
setCanAbort(true);
if (data?.incremental) {
tempHistory[index].context += message;
tempHistory[index].thinking = false;
} else {
tempHistory[index].context = message;
tempHistory[index].thinking = false;
}
setHistory([...tempHistory]);
},
onDone: () => {
setReplyLoading(false);
setCanAbort(false);
resolve();
},
onClose: () => {
setReplyLoading(false);
setCanAbort(false);
resolve();
},
onError: (message) => {
setReplyLoading(false);
setCanAbort(false);
tempHistory[index].context = message;
tempHistory[index].thinking = false;
setHistory([...tempHistory]);
resolve();
},
});
});
},
[chatId, history, modelValue, chat, scene],
);
useAsyncEffect(async () => {
// 如果是默认小助手,不获取历史记录
if (isChatDefault) {
return;
}
const initMessage = getInitMessage();
if (initMessage && initMessage.id === chatId) {
return;
}
await getHistory();
}, [chatId, scene, getHistory]);
useEffect(() => {
if (isChatDefault) {
order.current = 1;
setHistory([]);
}
}, [isChatDefault]);
return (
<ChatContentContext.Provider
value={{
history,
replyLoading,
scrollRef,
canAbort,
chartsData: chartsData || [],
agent,
currentDialogue,
appInfo,
temperatureValue,
resourceValue,
modelValue,
setModelValue,
setResourceValue,
setTemperatureValue,
setAppInfo,
setAgent,
setCanAbort,
setReplyLoading,
handleChat,
refreshDialogList,
refreshHistory,
refreshAppInfo,
setHistory,
}}
>
<Flex flex={1}>
<Layout className="bg-gradient-light bg-cover bg-center dark:bg-gradient-dark">
<ChatSider
refresh={refreshDialogList}
dialogueList={dialogueList}
listLoading={listLoading}
historyLoading={historyLoading}
order={order}
/>
<Layout className="bg-transparent">
{isChatDefault ? (
<Content>
<ChatDefault />
</Content>
) : (
<Spin spinning={historyLoading} className="w-full h-full m-auto">
<Content className="flex flex-col h-screen">
<ChatContentContainer ref={scrollRef} />
<ChatInputPanel ctrl={ctrl} />
</Content>
</Spin>
)}
</Layout>
</Layout>
</Flex>
</ChatContentContext.Provider>
);
};
export default Chat;

View File

@@ -1,298 +1,27 @@
import ChatContentContainer from '@/ant-components/chat/ChatContentContainer';
import ChatDefault from '@/ant-components/chat/content/ChatDefault';
import ChatInputPanel from '@/ant-components/chat/input/ChatInputPanel';
import ChatSider from '@/ant-components/chat/sider/ChatSider';
import React, { useContext, useEffect } from 'react';
import { useRouter } from 'next/router';
import { ChatContext } from '@/app/chat-context';
import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList } from '@/client/api';
import useChat from '@/hooks/use-chat';
import { IApp } from '@/types/app';
import { ChartData, ChatHistoryResponse, IChatDialogueSchema } from '@/types/chat';
import { getInitMessage } from '@/utils';
import { useAsyncEffect, useRequest } from 'ahooks';
import { Flex, Layout, Spin } from 'antd';
import { useSearchParams } from 'next/navigation';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import dynamic from 'next/dynamic';
const { Content } = Layout;
const DbEditor = dynamic(() => import('@/components/chat/db-editor'), { ssr: false });
const ChatContainer = dynamic(() => import('@/components/chat/chat-container'), { ssr: false });
interface ChatContentProps {
history: ChatHistoryResponse; // 会话记录列表
replyLoading: boolean; // 对话回复loading
scrollRef: React.RefObject<HTMLDivElement>; // 会话内容可滚动dom
canAbort: boolean; // 是否能中断回复
chartsData: ChartData[];
agent: string;
currentDialogue: IChatDialogueSchema; // 当前选择的会话
appInfo: IApp;
temperatureValue: any;
resourceValue: any;
modelValue: string;
setModelValue: React.Dispatch<React.SetStateAction<string>>;
setTemperatureValue: React.Dispatch<React.SetStateAction<any>>;
setResourceValue: React.Dispatch<React.SetStateAction<any>>;
setAppInfo: React.Dispatch<React.SetStateAction<IApp>>;
setAgent: React.Dispatch<React.SetStateAction<string>>;
setCanAbort: React.Dispatch<React.SetStateAction<boolean>>;
setReplyLoading: React.Dispatch<React.SetStateAction<boolean>>;
handleChat: (content: string, data?: Record<string, any>) => Promise<void>; // 处理会话请求逻辑函数
refreshDialogList: () => void;
refreshHistory: () => void;
refreshAppInfo: () => void;
setHistory: React.Dispatch<React.SetStateAction<ChatHistoryResponse>>;
function Chat() {
const {
query: { id, scene },
} = useRouter();
const { isContract, setIsContract, setIsMenuExpand } = useContext(ChatContext);
useEffect(() => {
// 仅初始化执行防止dashboard页面无法切换状态
setIsMenuExpand(scene !== 'chat_dashboard');
// 路由变了要取消Editor模式再进来是默认的Preview模式
if (id && scene) {
setIsContract(false);
}
}, [id, scene]);
return <>{isContract ? <DbEditor /> : <ChatContainer />}</>;
}
export const ChatContentContext = createContext<ChatContentProps>({
history: [],
replyLoading: false,
scrollRef: { current: null },
canAbort: false,
chartsData: [],
agent: '',
currentDialogue: {} as any,
appInfo: {} as any,
temperatureValue: 0.5,
resourceValue: {},
modelValue: '',
setModelValue: () => {},
setResourceValue: () => {},
setTemperatureValue: () => {},
setAppInfo: () => {},
setAgent: () => {},
setCanAbort: () => {},
setReplyLoading: () => {},
refreshDialogList: () => {},
refreshHistory: () => {},
refreshAppInfo: () => {},
setHistory: () => {},
handleChat: () => Promise.resolve(),
});
const Chat: React.FC = () => {
const { model, currentDialogInfo } = useContext(ChatContext);
const { chat, ctrl } = useChat({ app_code: currentDialogInfo.app_code || '' });
const searchParams = useSearchParams();
const chatId = searchParams?.get('id') ?? '';
const scene = searchParams?.get('scene') ?? '';
const knowledgeId = searchParams?.get('knowledge_id') ?? '';
const dbName = searchParams?.get('db_name') ?? '';
const scrollRef = useRef<HTMLDivElement>(null);
const order = useRef<number>(1);
const [history, setHistory] = useState<ChatHistoryResponse>([]);
const [chartsData, setChartsData] = useState<Array<ChartData>>();
const [replyLoading, setReplyLoading] = useState<boolean>(false);
const [canAbort, setCanAbort] = useState<boolean>(false);
const [agent, setAgent] = useState<string>('');
const [appInfo, setAppInfo] = useState<IApp>({} as IApp);
const [temperatureValue, setTemperatureValue] = useState();
const [resourceValue, setResourceValue] = useState<any>();
const [modelValue, setModelValue] = useState<string>('');
useEffect(() => {
setTemperatureValue(appInfo?.param_need?.filter((item) => item.type === 'temperature')[0]?.value || 0.5);
setModelValue(appInfo?.param_need?.filter((item) => item.type === 'model')[0]?.value || model);
setResourceValue(Number(knowledgeId) || dbName || appInfo?.param_need?.filter((item) => item.type === 'resource')[0]?.bind_value);
}, [appInfo, dbName, knowledgeId, model]);
// 是否是默认小助手
const isChatDefault = useMemo(() => {
return !chatId && !scene;
}, [chatId, scene]);
// 获取会话列表
const {
data: dialogueList = [],
refresh: refreshDialogList,
loading: listLoading,
} = useRequest(async () => {
return await apiInterceptors(getDialogueList());
});
// 获取应用详情
const { run: queryAppInfo, refresh: refreshAppInfo } = useRequest(
async () =>
await apiInterceptors(
getAppInfo({
...currentDialogInfo,
}),
),
{
manual: true,
onSuccess: (data) => {
const [, res] = data;
setAppInfo(res || ({} as IApp));
},
},
);
// 列表当前活跃对话
const currentDialogue = useMemo(() => {
const [, list] = dialogueList;
return list?.find((item) => item.conv_uid === chatId) || ({} as IChatDialogueSchema);
}, [chatId, dialogueList]);
useEffect(() => {
const initMessage = getInitMessage();
if (currentDialogInfo.chat_scene === scene && !isChatDefault && !(initMessage && initMessage.message)) {
queryAppInfo();
}
}, [chatId, currentDialogInfo, isChatDefault, queryAppInfo, scene]);
// 获取会话历史记录
const {
run: getHistory,
loading: historyLoading,
refresh: refreshHistory,
} = useRequest(async () => await apiInterceptors(getChatHistory(chatId)), {
manual: true,
onSuccess: (data) => {
const [, res] = data;
const viewList = res?.filter((item) => item.role === 'view');
if (viewList && viewList.length > 0) {
order.current = viewList[viewList.length - 1].order + 1;
}
setHistory(res || []);
},
});
// 会话提问
const handleChat = useCallback(
(content: string, data?: Record<string, any>) => {
return new Promise<void>((resolve) => {
const initMessage = getInitMessage();
const ctrl = new AbortController();
setReplyLoading(true);
if (history && history.length > 0) {
const viewList = history?.filter((item) => item.role === 'view');
const humanList = history?.filter((item) => item.role === 'human');
order.current = (viewList[viewList.length - 1]?.order || humanList[humanList.length - 1]?.order) + 1;
}
const tempHistory: ChatHistoryResponse = [
...(initMessage && initMessage.id === chatId ? [] : history),
{ role: 'human', context: content, model_name: data?.model_name || modelValue, order: order.current, time_stamp: 0 },
{ role: 'view', context: '', model_name: data?.model_name || modelValue, order: order.current, time_stamp: 0, thinking: true },
];
const index = tempHistory.length - 1;
setHistory([...tempHistory]);
chat({
data: {
chat_mode: scene,
model_name: modelValue,
user_input: content,
...data,
},
ctrl,
chatId,
onMessage: (message) => {
setCanAbort(true);
if (data?.incremental) {
tempHistory[index].context += message;
tempHistory[index].thinking = false;
} else {
tempHistory[index].context = message;
tempHistory[index].thinking = false;
}
setHistory([...tempHistory]);
},
onDone: () => {
setReplyLoading(false);
setCanAbort(false);
resolve();
},
onClose: () => {
setReplyLoading(false);
setCanAbort(false);
resolve();
},
onError: (message) => {
setReplyLoading(false);
setCanAbort(false);
tempHistory[index].context = message;
tempHistory[index].thinking = false;
setHistory([...tempHistory]);
resolve();
},
});
});
},
[chatId, history, modelValue, chat, scene],
);
useAsyncEffect(async () => {
// 如果是默认小助手,不获取历史记录
if (isChatDefault) {
return;
}
const initMessage = getInitMessage();
if (initMessage && initMessage.id === chatId) {
return;
}
await getHistory();
}, [chatId, scene, getHistory]);
useEffect(() => {
if (isChatDefault) {
order.current = 1;
setHistory([]);
}
}, [isChatDefault]);
return (
<ChatContentContext.Provider
value={{
history,
replyLoading,
scrollRef,
canAbort,
chartsData: chartsData || [],
agent,
currentDialogue,
appInfo,
temperatureValue,
resourceValue,
modelValue,
setModelValue,
setResourceValue,
setTemperatureValue,
setAppInfo,
setAgent,
setCanAbort,
setReplyLoading,
handleChat,
refreshDialogList,
refreshHistory,
refreshAppInfo,
setHistory,
}}
>
<Flex flex={1}>
<Layout className="bg-gradient-light bg-cover bg-center dark:bg-gradient-dark">
<ChatSider
refresh={refreshDialogList}
dialogueList={dialogueList}
listLoading={listLoading}
historyLoading={historyLoading}
order={order}
/>
<Layout className="bg-transparent">
{isChatDefault ? (
<Content>
<ChatDefault />
</Content>
) : (
<Spin spinning={historyLoading} className="w-full h-full m-auto">
<Content className="flex flex-col h-screen">
<ChatContentContainer ref={scrollRef} />
<ChatInputPanel ctrl={ctrl} />
</Content>
</Spin>
)}
</Layout>
</Layout>
</Flex>
</ChatContentContext.Provider>
);
};
export default Chat;

View File

@@ -381,21 +381,6 @@ export default function AppContent() {
</span>
),
},
{
key: 'admin',
label: (
<span
className="flex flex-1"
onClick={(e) => {
e.stopPropagation();
setAdminOpen(true);
setCurApp(item);
}}
>
</span>
),
},
{
key: 'del',
label: (

View File

@@ -270,7 +270,9 @@ function Flow() {
rightTopHover={false}
Tags={
<div>
<Tag color="green">{flow.source}</Tag>
<Tag color={flow.source === 'DBGPT-WEB' ? 'green' : 'blue'}>{flow.source}</Tag>
<Tag color={flow.editable ? 'green' : 'gray'}>{flow.editable ? 'Editable' : 'Can not Edit'}</Tag>
<Tag color={flow.state === 'load_failed' ? 'red' : flow.state === 'running' ? 'green' : 'blue'}>{flow.state}</Tag>
</div>
}
LeftBottom={

View File

@@ -257,6 +257,7 @@ const AddOrEditPrompt: React.FC = () => {
},
onmessage: (event) => {
let message = event.data;
if (!message) return;
try {
message = JSON.parse(message).vis;
} catch (e) {