From 1226c3efb77af388b6cdd85f244bf7a3dd77a4f8 Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Fri, 12 May 2023 00:09:10 +0800 Subject: [PATCH] feat: chat ui --- public/docs/chatProblem.md | 5 +- src/components/Layout/index.tsx | 2 + src/components/Markdown/index.module.scss | 6 +- src/components/Markdown/index.tsx | 2 +- src/pages/chat/index.tsx | 280 ++++++++++++++-------- src/pages/index.tsx | 4 +- 6 files changed, 195 insertions(+), 104 deletions(-) diff --git a/public/docs/chatProblem.md b/public/docs/chatProblem.md index 3b19dd651..edbb5e31a 100644 --- a/public/docs/chatProblem.md +++ b/public/docs/chatProblem.md @@ -11,5 +11,6 @@ chatgpt 上下文最长 4096 tokens, 会自动截取上下文,超过 4096 部 服务器代理不稳定,可以过一会儿再尝试。 或者可以访问国外服务器: [FastGpt](https://fastgpt.run/) **其他问题** 请 WX 联系: fastgpt123 -![](/imgs/wxqun300.jpg) -![](/imgs/wx300.jpg) +| 交流群 | 小助手 | +| ----------------------- | -------------------- | +| ![](/imgs/wxqun300.jpg) | ![](/imgs/wx300.jpg) | diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index eb25fe639..0495e0787 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -9,9 +9,11 @@ import Navbar from './navbar'; import NavbarPhone from './navbarPhone'; const pcUnShowLayoutRoute: Record = { + '/': true, '/login': true }; const phoneUnShowLayoutRoute: Record = { + '/': true, '/login': true }; diff --git a/src/components/Markdown/index.module.scss b/src/components/Markdown/index.module.scss index 32da9ef0a..a62ce39c3 100644 --- a/src/components/Markdown/index.module.scss +++ b/src/components/Markdown/index.module.scss @@ -337,10 +337,10 @@ } .markdown { text-align: justify; - overflow-y: hidden; tab-size: 4; word-spacing: normal; word-break: break-all; + width: 100%; p { white-space: pre-line; @@ -353,13 +353,13 @@ margin: 0; border: none; border-radius: 0; - background-color: #222 !important; + background-color: #292b33 !important; overflow-x: auto; color: #fff; } pre code { - background-color: #222 !important; + background-color: #292b33 !important; width: 100%; } diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index a7368c496..0386c9167 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -29,7 +29,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: const code = String(children); return !inline || match ? ( - + (null); const TextareaDom = useRef(null); + const ContextMenuRef = useRef(null); + const PhoneContextShow = useRef(false); // 中断请求 const controller = useRef(new AbortController()); @@ -83,6 +88,12 @@ const Chat = ({ const [inputVal, setInputVal] = useState(''); // user input prompt const [showSystemPrompt, setShowSystemPrompt] = useState(''); + const [messageContextMenuData, setMessageContextMenuData] = useState<{ + // message messageContextMenuData + left: number; + top: number; + message: ChatSiteItemType; + }>(); const { lastChatModelId, @@ -107,6 +118,24 @@ const Chat = ({ const { Loading, setIsLoading } = useLoading(); const { userInfo } = useUserStore(); const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure(); + // close contextMenu + useOutsideClick({ + ref: ContextMenuRef, + handler: () => { + // 移动端长按后会将其设置为true,松手时候也会触发一次,松手的时候需要忽略一次。 + if (PhoneContextShow.current) { + PhoneContextShow.current = false; + } else { + messageContextMenuData && + setTimeout(() => { + setMessageContextMenuData(undefined); + window.getSelection?.()?.empty?.(); + window.getSelection?.()?.removeAllRanges?.(); + document?.getSelection()?.empty(); + }); + } + } + }); // 滚动到底部 const scrollToBottom = useCallback((behavior: 'smooth' | 'auto' = 'smooth') => { @@ -234,6 +263,9 @@ const Chat = ({ // refresh history loadHistory({ pageNum: 1, init: true }); + setTimeout(() => { + generatingMessage(); + }, 100); }, [ chatId, @@ -327,24 +359,26 @@ const Chat = ({ ]); // 删除一句话 - const delChatRecord = useCallback( - async (index: number, id: string) => { - setIsLoading(true); - try { - // 删除数据库最后一句 - await delChatRecordByIndex(chatId, id); + const delChatRecord = useCallback(async () => { + if (!messageContextMenuData) return; + setIsLoading(true); + const index = chatData.history.findIndex( + (item) => item._id === messageContextMenuData.message._id + ); - setChatData((state) => ({ - ...state, - history: state.history.filter((_, i) => i !== index) - })); - } catch (err) { - console.log(err); - } - setIsLoading(false); - }, - [chatId, setChatData, setIsLoading] - ); + try { + // 删除数据库最后一句 + await delChatRecordByIndex(chatId, messageContextMenuData.message._id); + + setChatData((state) => ({ + ...state, + history: state.history.filter((_, i) => i !== index) + })); + } catch (err) { + console.log(err); + } + setIsLoading(false); + }, [chatData.history, chatId, messageContextMenuData, setChatData, setIsLoading]); // 复制内容 const onclickCopy = useCallback( @@ -433,6 +467,34 @@ const Chat = ({ [loadHistory] ); + // onclick chat message context + const onclickContextMenu = useCallback( + (e: MouseEvent, message: ChatSiteItemType) => { + e.preventDefault(); // 阻止默认右键菜单 + + // select all text + const range = document.createRange(); + range.selectNodeContents(e.currentTarget as HTMLDivElement); + window.getSelection()?.removeAllRanges(); + window.getSelection()?.addRange(range); + + navigator.vibrate?.(50); // 震动 50 毫秒 + + if (!isPcDevice) { + PhoneContextShow.current = true; + } + + setMessageContextMenuData({ + left: e.clientX, + top: e.clientY, + message + }); + + return false; + }, + [isPcDevice] + ); + // 获取对话信息 const loadChatInfo = useCallback( async ({ @@ -511,6 +573,7 @@ const Chat = ({ }); }); + // abort stream useEffect(() => { return () => { isLeavePage.current = true; @@ -522,7 +585,7 @@ const Chat = ({ {/* pc always show history. phone is only show when modelId is present */} {isPc || !modelId ? ( @@ -605,6 +668,7 @@ const Chat = ({ position={'relative'} h={[0, '100%']} w={['100%', 0]} + pt={[2, 4]} flex={'1 0 0'} flexDirection={'column'} > @@ -617,20 +681,25 @@ const Chat = ({ w={'100%'} overflow={'overlay'} > - {chatData.history.map((item, index) => ( - - - - + + {chatData.history.map((item, index) => ( + + {item.obj === 'Human' && } + {/* avatar */} + router.push(`/model?modelId=${chatData.modelId}`) + } + : { + order: 3, + ml: ['6px', 4] + })} + > + avatar - - - {chatData.model.canUse && item.obj === 'AI' && ( - router.push(`/model?modelId=${chatData.modelId}`)}> - AI助手详情 - - )} - onclickCopy(item.value)}>复制 - delChatRecord(index, item._id)}>删除该行 - - - + + + {/* message */} + {item.obj === 'AI' ? ( - <> - - {item.systemPrompt && ( - + + {isPc && ( + + {chatData.model.name} + )} - + onclickContextMenu(e, item)} + > + + {item.systemPrompt && ( + + )} + + ) : ( - - {item.value} + + {isPc && ( + + Human + + )} + onclickContextMenu(e, item)} + > + {item.value} + )} - - {/* copy and clear icon */} - {isPc && ( - - - onclickCopy(item.value)} - /> - - delChatRecord(index, item._id)} - /> - - )} + - - ))} - {chatData.history.length === 0 && } + ))} + {chatData.history.length === 0 && } + {/* 发送区 */} {chatData.model.canUse ? ( @@ -715,7 +782,7 @@ const Chat = ({ { // 触发快捷发送 - if (isPc && e.keyCode === 13 && !e.shiftKey) { + if (isPcDevice && e.keyCode === 13 && !e.shiftKey) { sendPrompt(); e.preventDefault(); } @@ -817,6 +884,25 @@ const Chat = ({ } + {/* context menu */} + {messageContextMenuData && ( + + + + + onclickCopy(messageContextMenuData.message.value)}> + 复制 + + 删除 + + + + )} ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3061b1fc5..fa92c221f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -141,6 +141,7 @@ const Home = () => { flexDirection={'column'} alignItems={'center'} pt={'20vh'} + overflow={'overlay'} > {''} @@ -162,7 +163,8 @@ const Home = () => {