Skip to content

Commit 1aeaa1b

Browse files
committed
feat: implementing import and delete for revision
1 parent 5cabe48 commit 1aeaa1b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2968
-1009
lines changed

packages/backend.ai-ui/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,16 @@
105105
"@types/react-copy-to-clipboard": "^5.0.7",
106106
"@types/react-dom": "^19.0.3",
107107
"@types/react-relay": "^18.2.1",
108+
"@types/relay-runtime": "^19.0.2",
108109
"@types/react-resizable": "^3.0.8",
109110
"@types/relay-test-utils": "^19.0.0",
110111
"@vitejs/plugin-react": "^4.5.0",
111-
"ahooks": "^3.8.4",
112-
"dayjs": "^1.11.13",
113112
"eslint": "^8.57.1",
114113
"eslint-config-react-app": "^7.0.1",
115114
"eslint-plugin-import": "^2.31.0",
116115
"eslint-plugin-react": "^7.37.4",
117116
"eslint-plugin-storybook": "^9.1.1",
118117
"fast-glob": "^3.3.3",
119-
"graphql": "^16.10.0",
120118
"jest": "^29.7.0",
121119
"jest-environment-jsdom": "^29.7.0",
122120
"storybook": "^9.1.1",

packages/backend.ai-ui/src/components/BAIGraphQLPropertyFilter.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,25 @@ interface FilterCondition {
136136
}
137137

138138
const OPERATORS_BY_TYPE: Record<FilterPropertyType, FilterOperator[]> = {
139-
string: ['equals', 'notEquals', 'contains', 'startsWith', 'endsWith', 'in', 'notIn'],
140-
number: ['equals', 'notEquals', 'greaterThan', 'greaterOrEqual', 'lessThan', 'lessOrEqual', 'in', 'notIn'],
139+
string: [
140+
'equals',
141+
'notEquals',
142+
'contains',
143+
'startsWith',
144+
'endsWith',
145+
'in',
146+
'notIn',
147+
],
148+
number: [
149+
'equals',
150+
'notEquals',
151+
'greaterThan',
152+
'greaterOrEqual',
153+
'lessThan',
154+
'lessOrEqual',
155+
'in',
156+
'notIn',
157+
],
141158
boolean: ['equals'],
142159
enum: ['equals', 'notEquals', 'in', 'notIn'],
143160
};
@@ -488,7 +505,9 @@ const BAIGraphQLPropertyFilter: React.FC<BAIGraphQLPropertyFilterProps> = ({
488505
options={propertyOptions}
489506
value={selectedProperty.key}
490507
onChange={(_value, options) => {
508+
console.log(options);
491509
const property = _.castArray(options)[0].filter;
510+
console.log(property);
492511
setSelectedProperty(property);
493512
const mode =
494513
property.valueMode ||
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { BAIImportFromHuggingFaceModalScanArtifactModelsMutation } from '../__generated__/BAIImportFromHuggingFaceModalScanArtifactModelsMutation.graphql';
2+
import BAIFlex from './BAIFlex';
3+
import BAIText from './BAIText';
4+
import BAIUnmountAfterClose from './BAIUnmountAfterClose';
5+
import { App, Form, Input, Modal, ModalProps } from 'antd';
6+
import { useTranslation } from 'react-i18next';
7+
import { graphql, useMutation } from 'react-relay';
8+
9+
export interface BAIImportFromHuggingFaceModalProps
10+
extends Omit<ModalProps, 'onOk'> {
11+
onOk: (e: React.MouseEvent<HTMLElement>, artifactId: string) => void;
12+
}
13+
14+
const BAIImportFromHuggingFaceModal = ({
15+
onOk,
16+
onCancel,
17+
...modalProps
18+
}: BAIImportFromHuggingFaceModalProps) => {
19+
const { t } = useTranslation();
20+
const [form] = Form.useForm();
21+
const { message } = App.useApp();
22+
23+
const [scanArtifactModels, isInflightScanArtifactModels] =
24+
useMutation<BAIImportFromHuggingFaceModalScanArtifactModelsMutation>(
25+
graphql`
26+
mutation BAIImportFromHuggingFaceModalScanArtifactModelsMutation(
27+
$input: ScanArtifactModelsInput!
28+
) {
29+
scanArtifactModels(input: $input) {
30+
artifactRevision {
31+
count
32+
edges {
33+
node {
34+
artifact {
35+
id
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+
`,
43+
);
44+
return (
45+
<BAIUnmountAfterClose>
46+
<Modal
47+
title={t('comp:BAIImportFromHuggingFaceModal.ModalTitle')}
48+
okText={t('comp:BAIImportFromHuggingFaceModal.Import')}
49+
centered
50+
cancelText={t('general.button.Close')}
51+
{...modalProps}
52+
okButtonProps={{
53+
loading: isInflightScanArtifactModels,
54+
disabled: isInflightScanArtifactModels,
55+
}}
56+
onOk={(e) => {
57+
form
58+
.validateFields()
59+
.then((values) => {
60+
scanArtifactModels({
61+
variables: {
62+
input: {
63+
models: [
64+
{
65+
modelId: values.modelId,
66+
revision: values.revision ?? null,
67+
},
68+
],
69+
},
70+
},
71+
onCompleted: (res, errors) => {
72+
if (errors && errors.length > 0) {
73+
errors.forEach((err) =>
74+
message.error(
75+
err.message ??
76+
t(
77+
'comp:BAIImportFromHuggingFaceModal.Failed to import model from hugging face',
78+
),
79+
),
80+
);
81+
return;
82+
}
83+
message.success(
84+
t(
85+
'comp:BAIImportFromHuggingFaceModal.SuccessfullyImportedModelFromHuggingFace',
86+
),
87+
);
88+
onOk(
89+
e,
90+
res.scanArtifactModels.artifactRevision.edges[0].node
91+
.artifact.id,
92+
);
93+
},
94+
onError: (error) => {
95+
message.error(
96+
error.message ??
97+
t(
98+
'comp:BAIImportFromHuggingFaceModal.Failed to import model from hugging face',
99+
),
100+
);
101+
},
102+
});
103+
})
104+
.catch(() => {});
105+
}}
106+
onCancel={(e) => {
107+
onCancel?.(e);
108+
}}
109+
afterClose={() => {
110+
form.resetFields();
111+
}}
112+
>
113+
<Form form={form} layout="vertical">
114+
<BAIFlex direction="column" gap="md" align="stretch">
115+
<BAIText>
116+
{t('comp:BAIImportFromHuggingFaceModal.ModalDescription')}
117+
</BAIText>
118+
<Form.Item
119+
label={t('comp:BAIImportFromHuggingFaceModal.ModelID')}
120+
name="modelId"
121+
required
122+
rules={[{ required: true }]}
123+
style={{
124+
marginBottom: 0,
125+
}}
126+
>
127+
<Input
128+
placeholder={t(
129+
'comp:BAIImportFromHuggingFaceModal.EnterAModelID',
130+
)}
131+
/>
132+
</Form.Item>
133+
<Form.Item
134+
label={t('comp:BAIImportFromHuggingFaceModal.Version')}
135+
name="revision"
136+
>
137+
<Input
138+
placeholder={t(
139+
'comp:BAIImportFromHuggingFaceModal.EnterAVersion',
140+
)}
141+
/>
142+
</Form.Item>
143+
</BAIFlex>
144+
</Form>
145+
</Modal>
146+
</BAIUnmountAfterClose>
147+
);
148+
};
149+
150+
export default BAIImportFromHuggingFaceModal;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import BAIFlex from './BAIFlex';
2+
import { Divider, Select, SelectProps, theme, Tooltip, Typography } from 'antd';
3+
import { createStyles } from 'antd-style';
4+
import { BaseOptionType, DefaultOptionType } from 'antd/es/select';
5+
import { GetRef } from 'antd/lib';
6+
import classNames from 'classnames';
7+
import _ from 'lodash';
8+
import React, { useLayoutEffect, useRef } from 'react';
9+
10+
const useStyles = createStyles(({ css, token }) => ({
11+
ghostSelect: css`
12+
&.ant-select {
13+
.ant-select-selector {
14+
background-color: transparent;
15+
border-color: ${token.colorBgBase} !important;
16+
/* box-shadow: none; */
17+
color: ${token.colorBgBase};
18+
/* transition: color 0.3s, border-color 0.3s; */
19+
}
20+
21+
&:hover .ant-select-selector {
22+
background-color: rgb(255 255 255 / 10%);
23+
}
24+
25+
&:active .ant-select-selector {
26+
background-color: rgb(255 255 255 / 10%);
27+
}
28+
29+
.ant-select-arrow {
30+
color: ${token.colorBgBase};
31+
}
32+
33+
&:hover .ant-select-arrow {
34+
color: ${token.colorBgBase};
35+
}
36+
37+
&:active .ant-select-arrow {
38+
color: ${token.colorBgBase};
39+
}
40+
}
41+
`,
42+
}));
43+
44+
export interface BAISelectProps<
45+
ValueType = any,
46+
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
47+
> extends SelectProps<ValueType, OptionType> {
48+
ref?: React.RefObject<GetRef<typeof Select<ValueType, OptionType>> | null>;
49+
ghost?: boolean;
50+
autoSelectOption?:
51+
| boolean
52+
| ((options: SelectProps<ValueType, OptionType>['options']) => ValueType);
53+
tooltip?: string;
54+
atBottomThreshold?: number;
55+
atBottomStateChange?: (atBottom: boolean) => void;
56+
bottomLoading?: boolean;
57+
footer?: React.ReactNode;
58+
endReached?: () => void; // New prop for endReached
59+
}
60+
61+
function BAISelect<
62+
ValueType = any,
63+
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
64+
>({
65+
ref,
66+
autoSelectOption,
67+
ghost,
68+
tooltip = '',
69+
atBottomThreshold = 30,
70+
atBottomStateChange,
71+
bottomLoading,
72+
footer,
73+
endReached, // Destructure the new prop
74+
...selectProps
75+
}: BAISelectProps<ValueType, OptionType>): React.ReactElement {
76+
const { value, options, onChange } = selectProps;
77+
const { styles } = useStyles();
78+
// const dropdownRef = useRef<HTMLDivElement | null>(null);
79+
const lastScrollTop = useRef<number>(0);
80+
const isAtBottom = useRef<boolean>(false);
81+
const { token } = theme.useToken();
82+
83+
useLayoutEffect(() => {
84+
if (autoSelectOption && _.isEmpty(value) && options?.[0]) {
85+
if (_.isBoolean(autoSelectOption)) {
86+
onChange?.(options?.[0].value || options?.[0], options?.[0]);
87+
} else if (_.isFunction(autoSelectOption)) {
88+
onChange?.(autoSelectOption(options), options?.[0]);
89+
}
90+
}
91+
}, [value, options, onChange, autoSelectOption]);
92+
93+
// Function to check if the scroll has reached the bottom
94+
const handlePopupScroll = (e: React.UIEvent<HTMLDivElement>) => {
95+
if (!atBottomStateChange && !endReached) return; // Check for endReached
96+
97+
const target = e.target as HTMLElement;
98+
const scrollTop = target.scrollTop;
99+
// const scrollDirection = scrollTop > lastScrollTop.current ? 'down' : 'up';
100+
lastScrollTop.current = scrollTop;
101+
102+
const isAtBottomNow =
103+
target.scrollHeight - scrollTop - target.clientHeight <=
104+
atBottomThreshold;
105+
106+
// Only notify when the state changes
107+
// ~~or when scrolling down at the bottom~~
108+
if (
109+
isAtBottomNow !== isAtBottom.current
110+
// ||
111+
// (isAtBottomNow && scrollDirection === 'down')
112+
) {
113+
isAtBottom.current = isAtBottomNow;
114+
atBottomStateChange?.(isAtBottomNow);
115+
116+
if (isAtBottomNow) {
117+
endReached?.(); // Call endReached when at the bottom
118+
}
119+
}
120+
};
121+
122+
return (
123+
<Tooltip title={tooltip}>
124+
<Select<ValueType, OptionType>
125+
{...selectProps}
126+
ref={ref}
127+
className={
128+
ghost
129+
? classNames(styles.ghostSelect, selectProps.className)
130+
: selectProps.className
131+
}
132+
onPopupScroll={(e) => {
133+
if (atBottomStateChange || endReached) handlePopupScroll(e);
134+
selectProps.onPopupScroll?.(e);
135+
}}
136+
dropdownRender={
137+
footer
138+
? (menu) => {
139+
// Process with custom dropdownRender if provided
140+
// const renderedMenu = selectProps.dropdownRender
141+
// ? selectProps.dropdownRender(menu)
142+
// : menu;
143+
144+
return (
145+
<BAIFlex direction="column" align="stretch">
146+
{menu}
147+
<Divider
148+
style={{
149+
margin: 0,
150+
marginBottom: token.paddingXS,
151+
}}
152+
/>
153+
<BAIFlex
154+
direction="column"
155+
align="end"
156+
gap={'xs'}
157+
style={{
158+
paddingBottom: token.paddingXXS,
159+
paddingInline: token.paddingSM,
160+
}}
161+
>
162+
{_.isString(footer) ? (
163+
<Typography.Text type="secondary">
164+
{footer}
165+
</Typography.Text>
166+
) : (
167+
footer
168+
)}
169+
</BAIFlex>
170+
</BAIFlex>
171+
);
172+
}
173+
: undefined
174+
}
175+
/>
176+
</Tooltip>
177+
);
178+
}
179+
180+
export default BAISelect;

0 commit comments

Comments
 (0)