Skip to content

Commit c5a90ea

Browse files
committed
feat(FR-1494): improve UX for session creation related to enqueueOnly and App launcher (#4353)
# Improved session creation notification flow to show session status updates This PR adds a new `BAINotificationItem` component to the UI library and enhances the notification system to support node-specific notifications. It also upgrades the Ant Design library from 5.24.5 to 5.27.4. Key changes: - Added `BAINotificationItem` component to the UI library for consistent notification styling - Created specialized notification components for different types: - `BAIGeneralNotificationItem` for standard notifications - `BAINodeNotificationItem` for node-specific notifications (sessions, folders) - `BAISessionNotificationItem` for session-specific notifications - Enhanced `SessionActionButtons` to support hiding specific buttons and improved small size display - Updated notification drawer to use the appropriate notification item based on content type - Fixed React Router provider structure to ensure consistent context availability
1 parent e89449c commit c5a90ea

17 files changed

+793
-359
lines changed

packages/backend.ai-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"@ant-design/icons": "^5.6.1",
5858
"@tanstack/react-query": "^5.69.0",
5959
"ahooks": "^3.8.4",
60-
"antd": "^5.24.5",
60+
"antd": "^5.27.4",
6161
"antd-style": "^3.7.1",
6262
"graphql": "^16.10.0",
6363
"i18next": "^24.2.3",
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import BAIFlex from './BAIFlex';
2+
import { List, Typography, theme } from 'antd';
3+
import React, { ReactNode } from 'react';
4+
5+
export interface NotificationItemTemplateStyles {
6+
title?: React.CSSProperties;
7+
description?: React.CSSProperties;
8+
action?: React.CSSProperties;
9+
footer?: React.CSSProperties;
10+
}
11+
12+
export interface NotificationItemTemplateProps {
13+
title?: ReactNode;
14+
description?: ReactNode;
15+
action?: ReactNode;
16+
footer?: ReactNode;
17+
styles?: NotificationItemTemplateStyles;
18+
}
19+
20+
const isPrimitiveContent = (
21+
value: ReactNode,
22+
): value is string | number | bigint => {
23+
const valueType = typeof value;
24+
return (
25+
valueType === 'string' || valueType === 'number' || valueType === 'bigint'
26+
);
27+
};
28+
29+
const NotificationItemTemplate: React.FC<NotificationItemTemplateProps> = ({
30+
title,
31+
description,
32+
action,
33+
footer,
34+
styles,
35+
}) => {
36+
const { token } = theme.useToken();
37+
38+
const renderTextContent = (
39+
content: ReactNode,
40+
typographyStyle?: React.CSSProperties,
41+
) => {
42+
return isPrimitiveContent(content) ? (
43+
<Typography.Text style={typographyStyle}>{content}</Typography.Text>
44+
) : (
45+
content
46+
);
47+
};
48+
49+
return (
50+
<List.Item>
51+
<BAIFlex direction="column" align="stretch" gap="xxs">
52+
{title && (
53+
<div
54+
style={{
55+
fontWeight: 500,
56+
marginRight: 22,
57+
marginBottom: token.marginSM,
58+
...styles?.title,
59+
}}
60+
>
61+
{renderTextContent(title)}
62+
</div>
63+
)}
64+
65+
{description && (
66+
<div style={styles?.description}>
67+
{renderTextContent(description)}
68+
</div>
69+
)}
70+
71+
{action && (
72+
<BAIFlex
73+
direction="row"
74+
align="end"
75+
justify="end"
76+
gap="xxs"
77+
style={styles?.action}
78+
>
79+
{action}
80+
</BAIFlex>
81+
)}
82+
83+
{footer && (
84+
<div
85+
style={{
86+
alignSelf: 'flex-end',
87+
color: token.colorTextSecondary,
88+
...styles?.footer,
89+
}}
90+
>
91+
{renderTextContent(footer)}
92+
</div>
93+
)}
94+
</BAIFlex>
95+
</List.Item>
96+
);
97+
};
98+
99+
export { NotificationItemTemplate };
100+
101+
export default NotificationItemTemplate;

packages/backend.ai-ui/src/components/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ export { default as BAISelect } from './BAISelect';
4343
export type { BAISelectProps } from './BAISelect';
4444
export { default as BAIImportFromHuggingFaceModal } from './BAIImportFromHuggingFaceModal';
4545
export type { BAIImportFromHuggingFaceModalProps } from './BAIImportFromHuggingFaceModal';
46+
export {
47+
default as BAINotificationItem,
48+
NotificationItemTemplate,
49+
} from './BAINotificationItem';
50+
export type {
51+
NotificationItemTemplateProps,
52+
NotificationItemTemplateStyles,
53+
} from './BAINotificationItem';
4654
export * from './Table';
4755
export * from './fragments';
4856
export * from './provider';

pnpm-lock.yaml

Lines changed: 126 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"ahooks": "^3.8.4",
2929
"ai": "^4.3.15",
3030
"ansi_up": "^6.0.2",
31-
"antd": "^5.24.9",
31+
"antd": "^5.27.4",
3232
"antd-style": "^3.7.1",
3333
"backend.ai-ui": "workspace:*",
3434
"big.js": "^7.0.1",

react/src/App.tsx

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,32 +104,36 @@ const router = createBrowserRouter([
104104
path: '/interactive-login',
105105
errorElement: <ErrorView />,
106106
element: (
107-
<QueryParamProvider adapter={ReactRouter6Adapter}>
108-
<InteractiveLoginPage />
109-
</QueryParamProvider>
107+
<DefaultProvidersForReactRoot>
108+
<QueryParamProvider adapter={ReactRouter6Adapter}>
109+
<InteractiveLoginPage />
110+
</QueryParamProvider>
111+
</DefaultProvidersForReactRoot>
110112
),
111113
},
112114
{
113115
path: '/',
114116
errorElement: <ErrorView />,
115117
element: (
116-
<QueryParamProvider
117-
adapter={ReactRouter6Adapter}
118-
options={
119-
{
120-
// searchStringToObject: queryString.parse,
121-
// objectToSearchString: queryString.stringify,
118+
<DefaultProvidersForReactRoot>
119+
<QueryParamProvider
120+
adapter={ReactRouter6Adapter}
121+
options={
122+
{
123+
// searchStringToObject: queryString.parse,
124+
// objectToSearchString: queryString.stringify,
125+
}
122126
}
123-
}
124-
>
125-
<MainLayout />
126-
<RoutingEventHandler />
127-
<Suspense>
128-
<FolderExplorerOpener />
129-
<FolderInvitationResponseModalOpener />
130-
<FileUploadManager />
131-
</Suspense>
132-
</QueryParamProvider>
127+
>
128+
<MainLayout />
129+
<RoutingEventHandler />
130+
<Suspense>
131+
<FolderExplorerOpener />
132+
<FolderInvitationResponseModalOpener />
133+
<FileUploadManager />
134+
</Suspense>
135+
</QueryParamProvider>
136+
</DefaultProvidersForReactRoot>
133137
),
134138
children: [
135139
{
@@ -584,11 +588,7 @@ const router = createBrowserRouter([
584588
]);
585589

586590
const App: FC = () => {
587-
return (
588-
<DefaultProvidersForReactRoot>
589-
<RouterProvider router={router} />
590-
</DefaultProvidersForReactRoot>
591-
);
591+
return <RouterProvider router={router} />;
592592
};
593593

594594
export default App;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { NotificationState } from '../hooks/useBAINotification';
2+
import {
3+
CheckCircleOutlined,
4+
ClockCircleOutlined,
5+
CloseCircleOutlined,
6+
} from '@ant-design/icons';
7+
import { Button, Card, List, Progress, Typography, theme } from 'antd';
8+
import { BAIFlex } from 'backend.ai-ui';
9+
import dayjs from 'dayjs';
10+
import _ from 'lodash';
11+
import { FolderIcon } from 'lucide-react';
12+
import React, { useState } from 'react';
13+
import { useTranslation } from 'react-i18next';
14+
15+
const BAIGeneralNotificationItem: React.FC<{
16+
notification: NotificationState;
17+
onClickAction?: (
18+
e: React.MouseEvent,
19+
notification: NotificationState,
20+
) => void;
21+
showDate?: boolean;
22+
}> = ({ notification, onClickAction, showDate }) => {
23+
const { t } = useTranslation();
24+
const { token } = theme.useToken();
25+
const [showExtraDescription, setShowExtraDescription] = useState(false);
26+
27+
const explicitIcon = notification.icon === 'folder' ? <FolderIcon /> : null;
28+
const icon =
29+
explicitIcon ||
30+
(notification.backgroundTask &&
31+
{
32+
pending: <ClockCircleOutlined style={{ color: token.colorInfo }} />,
33+
resolved: <CheckCircleOutlined style={{ color: token.colorSuccess }} />,
34+
rejected: <CloseCircleOutlined style={{ color: token.colorError }} />,
35+
}[notification.backgroundTask.status]) ||
36+
(notification.type === 'error' ? (
37+
<CloseCircleOutlined style={{ color: token.colorError }} />
38+
) : notification.type === 'success' ? (
39+
<CheckCircleOutlined style={{ color: token.colorSuccess }} />
40+
) : null);
41+
42+
return (
43+
<>
44+
<List.Item>
45+
<BAIFlex direction="column" align="stretch" gap={'xxs'}>
46+
<BAIFlex
47+
direction="row"
48+
align="start"
49+
gap={'xs'}
50+
style={{
51+
paddingRight: token.paddingMD,
52+
}}
53+
>
54+
{icon && <BAIFlex style={{ height: 22 }}>{icon}</BAIFlex>}
55+
<Typography.Paragraph
56+
style={{
57+
fontWeight: 500,
58+
}}
59+
>
60+
{_.isString(notification.message)
61+
? _.truncate(notification.message, {
62+
length: 200,
63+
})
64+
: notification.message}
65+
</Typography.Paragraph>
66+
</BAIFlex>
67+
<BAIFlex direction="row" align="end" gap={'xxs'} justify="between">
68+
<Typography.Paragraph>
69+
{_.isString(notification.description)
70+
? _.truncate(notification.description, {
71+
length: 300,
72+
})
73+
: notification.description}
74+
</Typography.Paragraph>
75+
{notification.to ? (
76+
<BAIFlex>
77+
<Typography.Link
78+
onClick={(e) => {
79+
onClickAction && onClickAction(e, notification);
80+
}}
81+
>
82+
{notification.toText ??
83+
notification.toTextKey ??
84+
t('notification.SeeDetail')}
85+
</Typography.Link>
86+
</BAIFlex>
87+
) : null}
88+
{notification?.onCancel ? (
89+
<BAIFlex>
90+
<Button type="link" onClick={notification.onCancel}>
91+
{t('button.Cancel')}
92+
</Button>
93+
</BAIFlex>
94+
) : null}
95+
{notification.extraDescription && !notification?.onCancel ? (
96+
<BAIFlex>
97+
<Typography.Link
98+
onClick={(e) => {
99+
// onClickAction && onClickAction(e, notification);
100+
setShowExtraDescription(!showExtraDescription);
101+
}}
102+
>
103+
{notification.toTextKey
104+
? t(notification.toTextKey)
105+
: t('notification.SeeDetail')}
106+
</Typography.Link>
107+
</BAIFlex>
108+
) : null}
109+
</BAIFlex>
110+
{notification.extraDescription && showExtraDescription ? (
111+
<Card size="small">
112+
<Typography.Text type="secondary" copyable>
113+
{notification.extraDescription}
114+
</Typography.Text>
115+
</Card>
116+
) : null}
117+
118+
<BAIFlex direction="row" align="center" justify="end" gap={'sm'}>
119+
{notification.backgroundTask &&
120+
_.isNumber(notification.backgroundTask.percent) ? (
121+
<Progress
122+
size="small"
123+
showInfo={false}
124+
percent={notification.backgroundTask.percent}
125+
strokeColor={
126+
notification.backgroundTask.status === 'rejected'
127+
? token.colorTextDisabled
128+
: undefined
129+
}
130+
style={{
131+
margin: 0,
132+
opacity:
133+
notification.backgroundTask.status === 'resolved' &&
134+
showDate
135+
? 0
136+
: 1,
137+
}}
138+
139+
// status={item.progressStatus}
140+
/>
141+
) : null}
142+
{showDate ? (
143+
<BAIFlex>
144+
<Typography.Text type="secondary">
145+
{dayjs(notification.created).format('lll')}
146+
</Typography.Text>
147+
</BAIFlex>
148+
) : null}
149+
</BAIFlex>
150+
</BAIFlex>
151+
</List.Item>
152+
</>
153+
);
154+
};
155+
156+
export default BAIGeneralNotificationItem;

0 commit comments

Comments
 (0)