Fix/chat box in mobile (#7595)

* fix(plugin-ai): chat box layout issues in mobile layout

* feat(plugin-ai): add minimize button for chat box in mobile layout
This commit is contained in:
Drol
2025-10-15 00:21:07 +08:00
committed by GitHub
parent 90b3667fe5
commit dd828aeca2
5 changed files with 94 additions and 27 deletions
@@ -7,8 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useEffect, useRef, useState } from 'react';
import { Layout, Card, Button, Divider, Tooltip } from 'antd';
import React, { useEffect, useRef } from 'react';
import { Layout, Card, Button, Divider, Tooltip, notification, Avatar, Flex, Typography } from 'antd';
import {
CloseOutlined,
ExpandOutlined,
@@ -17,6 +17,7 @@ import {
MenuFoldOutlined,
ShrinkOutlined,
CodeOutlined,
CompressOutlined,
} from '@ant-design/icons';
import { useMobileLayout, useToken } from '@nocobase/client';
const { Header, Footer, Sider } = Layout;
@@ -28,11 +29,13 @@ import { useT } from '../../locale';
import { UserPrompt } from './UserPrompt';
import { useChatBoxStore } from './stores/chat-box';
import { useChatBoxActions } from './hooks/useChatBoxActions';
import { aiSelection } from '../stores/ai-selection';
import { observer } from '@formily/react';
import { dialogController } from '../stores/dialog-controller';
import { CodeHistory } from '../ai-coding/CodeHistory';
import { isEngineer } from '../built-in/utils';
import { avatars } from '../avatars';
const { Text } = Typography;
export const ChatBox: React.FC = () => {
const chatBoxRef = useRef<HTMLDivElement | null>(null);
@@ -41,6 +44,7 @@ export const ChatBox: React.FC = () => {
const currentEmployee = useChatBoxStore.use.currentEmployee();
const expanded = useChatBoxStore.use.expanded();
const setExpanded = useChatBoxStore.use.setExpanded();
const setMinimize = useChatBoxStore.use.setMinimize();
const showConversations = useChatBoxStore.use.showConversations();
const setShowConversations = useChatBoxStore.use.setShowConversations();
const setShowCodeHistory = useChatBoxStore.use.setShowCodeHistory();
@@ -140,7 +144,15 @@ export const ChatBox: React.FC = () => {
<Divider type="vertical" />
</>
) : null}
{!isMobileLayout && (
{isMobileLayout ? (
<Button
icon={<CompressOutlined />}
type="text"
onClick={() => {
setMinimize(true);
}}
/>
) : (
<Button
icon={!expanded ? <ExpandOutlined /> : <ShrinkOutlined />}
type="text"
@@ -188,34 +200,83 @@ const ExpandChatBox: React.FC = observer(() => {
);
});
const MobileLayoutChatBox: React.FC<{ showConversations: boolean }> = ({ showConversations }) => {
return (
<div
style={{
position: 'fixed',
left: '0',
top: '0',
width: showConversations ? '800px' : '100%',
height: '100%',
zIndex: dialogController.shouldHide ? -1 : 1100,
backgroundColor: 'white',
}}
>
<ChatBox />
</div>
);
const MobileLayoutChatBox: React.FC<{ showConversations: boolean; minimize: boolean }> = observer(
({ showConversations, minimize }) => {
return (
<div
style={{
position: 'fixed',
left: '0',
top: '0',
width: showConversations ? '800px' : '100%',
height: '100%',
zIndex: dialogController.shouldHide ? -1 : 1100,
backgroundColor: 'white',
display: minimize ? 'none' : 'block',
}}
>
<ChatBox />
<ChatBoxMinimizeControl />
</div>
);
},
);
export const ChatBoxMinimizeControl: React.FC = () => {
const currentEmployee = useChatBoxStore.use.currentEmployee();
const minimize = useChatBoxStore.use.minimize();
const setMinimize = useChatBoxStore.use.setMinimize();
const setOpen = useChatBoxStore.use.setOpen();
const t = useT();
const [api, contextHolder] = notification.useNotification();
const key = useRef(`ai-chat-box-minimize--control-${new Date().getTime()}`);
if (minimize === true) {
api.open({
key: key.current,
closeIcon: false,
message: (
<Flex justify="space-between" align="center">
<Avatar shape="circle" size={35} src={avatars(currentEmployee.avatar)} />
<Text ellipsis>{t('Conversation')}</Text>
<Button
type="text"
icon={<CloseOutlined />}
onClick={(e) => {
e.stopPropagation();
setOpen(false);
setMinimize(false);
}}
></Button>
</Flex>
),
duration: 0,
placement: 'top',
style: {
width: 200,
},
onClick() {
setMinimize(false);
},
});
} else {
api.destroy(key.current);
}
return <>{contextHolder}</>;
};
export const ChatBoxWrapper: React.FC = () => {
const expanded = useChatBoxStore.use.expanded();
const minimize = useChatBoxStore.use.minimize();
const showConversations = useChatBoxStore.use.showConversations();
const showCodeHistory = useChatBoxStore.use.showCodeHistory();
const { isMobileLayout } = useMobileLayout();
return expanded ? (
return isMobileLayout ? (
<MobileLayoutChatBox showConversations={showConversations} minimize={minimize} />
) : expanded ? (
<ExpandChatBox />
) : isMobileLayout ? (
<MobileLayoutChatBox showConversations={showConversations} />
) : (
<div
style={{
@@ -7,8 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useContext } from 'react';
import { CurrentUserContext, useMobileLayout } from '@nocobase/client';
import React from 'react';
import { useMobileLayout } from '@nocobase/client';
import { ChatBoxWrapper } from './ChatBox';
import { Helmet } from 'react-helmet';
import { ChatButton } from './ChatButton';
@@ -19,6 +19,7 @@ interface ChatBoxState {
open: boolean;
expanded: boolean;
showConversations: boolean;
minimize: boolean;
currentEmployee?: AIEmployee;
senderValue: string;
@@ -41,6 +42,7 @@ interface ChatBoxActions {
setOpen: (open: boolean) => void;
setExpanded: (expanded: boolean) => void;
setShowConversations: (show: boolean) => void;
setMinimize: (minus: boolean) => void;
setCurrentEmployee: (aiEmployee?: AIEmployee | ((prev: AIEmployee) => AIEmployee)) => void;
setSenderValue: (value: string) => void;
@@ -61,6 +63,7 @@ const store = create<ChatBoxState & ChatBoxActions>()((set) => ({
open: false,
expanded: false,
showConversations: false,
minimize: false,
currentEmployee: null,
senderValue: '',
@@ -82,6 +85,7 @@ const store = create<ChatBoxState & ChatBoxActions>()((set) => ({
setOpen: (open) => set({ open }),
setExpanded: (expanded) => set({ expanded }),
setShowConversations: (show) => set({ showConversations: show }),
setMinimize: (minus) => set({ minimize: minus }),
setCurrentEmployee: (employee: AIEmployee | ((prev: AIEmployee) => AIEmployee)) =>
set((state) => ({
@@ -181,5 +181,6 @@
"Cite {{count}} pieces of information as references": "Cite {{count}} pieces of information as references",
"references {{index}}": "references {{index}}",
"Enable search": "Enable search",
"Disable search": "Disable search"
"Disable search": "Disable search",
"Conversation": "Conversation"
}
@@ -182,5 +182,6 @@
"Cite {{count}} pieces of information as references": "引用 {{count}} 篇资料作为参考",
"references {{index}}": "参考资料 {{index}}",
"Enable search": "启用搜索",
"Disable search": "禁用搜索"
"Disable search": "禁用搜索",
"Conversation": "当前会话"
}