Skip to content

Commit 30c6e69

Browse files
committed
Apply suggestions from code review
1 parent 557a483 commit 30c6e69

File tree

25 files changed

+166
-6410
lines changed

25 files changed

+166
-6410
lines changed

packages/backend.ai-ui/src/hooks/useGetAvailableFolderName.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const useGetAvailableFolderName = () => {
3232
.catch(() => 0);
3333

3434
const hash = generateRandomString(5);
35-
35+
3636
return count === 0 ? targetName : `${targetName.substring(0, 58)}_${hash}`;
3737
};
3838
};

react/src/components/Chat/CopyButton.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CopyConfig } from 'antd/es/typography/Base';
33
import { CheckIcon, CopyIcon } from 'lucide-react';
44
import React, { useEffect, useState } from 'react';
55
import { CopyToClipboard } from 'react-copy-to-clipboard';
6+
import { useTranslation } from 'react-i18next';
67

78
interface CopyButtonProps extends ButtonProps {
89
copyable?: Omit<CopyConfig, 'text'> & { text: string };
@@ -14,6 +15,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
1415
...props
1516
}) => {
1617
const [isCopied, setIsCopied] = useState(false);
18+
const { t } = useTranslation();
1719

1820
const handleCopy = async () => {
1921
setIsCopied(true);
@@ -30,7 +32,13 @@ const CopyButton: React.FC<CopyButtonProps> = ({
3032

3133
return (
3234
<Tooltip
33-
title={isCopied ? 'Copied!' : 'Copy'}
35+
title={
36+
props.disabled
37+
? undefined
38+
: isCopied
39+
? t('sourceCodeViewer.Copied')
40+
: t('sourceCodeViewer.Copy')
41+
}
3442
open={isCopied ? true : undefined}
3543
>
3644
<div>

react/src/components/ImportNotebook.tsx

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { useSuspendedBackendaiClient, useWebUINavigate } from '../hooks';
2+
import CopyButton from './Chat/CopyButton';
23
import { PrimaryAppOption } from './ComputeSessionNodeItems/SessionActionButtons';
34
import { CloudDownloadOutlined } from '@ant-design/icons';
4-
import { App, Form, FormInstance, FormProps, Input } from 'antd';
5+
import {
6+
App,
7+
Divider,
8+
Form,
9+
FormInstance,
10+
FormProps,
11+
Input,
12+
Typography,
13+
} from 'antd';
514
import {
615
BAIButton,
16+
BAIFlex,
717
generateRandomString,
818
useErrorMessageResolver,
919
} from 'backend.ai-ui';
@@ -19,6 +29,8 @@ const regularizeGithubURL = (url: string) => {
1929
url = url.replace('github.com', 'raw.githubusercontent.com');
2030
return url;
2131
};
32+
33+
const notebookURLPattern = new RegExp('^(https?)://([\\w./-]{1,}).ipynb$');
2234
const ImportNotebook: React.FC<FormProps> = (props) => {
2335
const formRef = useRef<FormInstance<{
2436
url: string;
@@ -78,7 +90,7 @@ const ImportNotebook: React.FC<FormProps> = (props) => {
7890
required: true,
7991
},
8092
{
81-
pattern: new RegExp('^(https?)://([\\w./-]{1,}).ipynb$'),
93+
pattern: notebookURLPattern,
8294
message: t('import.InvalidNotebookURL'),
8395
},
8496
{
@@ -107,6 +119,78 @@ const ImportNotebook: React.FC<FormProps> = (props) => {
107119
>
108120
{t('import.GetAndRunNotebook')}
109121
</BAIButton>
122+
<BAIFlex
123+
direction="column"
124+
wrap="wrap"
125+
data-testid="panel-notebook-badge-code-section"
126+
>
127+
<Divider />
128+
<Typography.Paragraph>
129+
{t('import.YouCanCreateNotebookCode')}
130+
</Typography.Paragraph>
131+
<Form.Item dependencies={[['url']]}>
132+
{({ getFieldValue }) => {
133+
const url = getFieldValue('url') || '';
134+
const rawURL = regularizeGithubURL(url);
135+
const badgeURL = rawURL.replace(
136+
'https://raw.githubusercontent.com/',
137+
'',
138+
);
139+
let baseURL = '';
140+
141+
if (globalThis.isElectron) {
142+
baseURL = 'https://cloud.backend.ai/github?';
143+
} else {
144+
baseURL =
145+
window.location.protocol + '//' + window.location.hostname;
146+
if (window.location.port) {
147+
baseURL = baseURL + ':' + window.location.port;
148+
}
149+
baseURL = baseURL + '/github?';
150+
}
151+
const fullText = `<a href="${
152+
baseURL + badgeURL
153+
}"><img src="https://www.backend.ai/assets/badge.svg" /></a>`;
154+
const fullTextMarkdown = `[![Run on Backend.AI](https://www.backend.ai/assets/badge.svg)](${
155+
baseURL + badgeURL
156+
})`;
157+
158+
const isValidURL =
159+
notebookURLPattern.test(url) && url.length <= 2048;
160+
const isButtonDisabled = !url || !isValidURL;
161+
162+
return (
163+
<BAIFlex justify="start" gap={'sm'} wrap="wrap">
164+
<img
165+
src="/resources/badge.svg"
166+
style={{ marginTop: 5, marginBottom: 5 }}
167+
width="147"
168+
/>
169+
<BAIFlex gap={'sm'}>
170+
<CopyButton
171+
size="small"
172+
copyable={{
173+
text: fullText,
174+
}}
175+
disabled={isButtonDisabled}
176+
>
177+
{t('import.NotebookBadgeCodeHTML')}
178+
</CopyButton>
179+
<CopyButton
180+
size="small"
181+
copyable={{
182+
text: fullTextMarkdown,
183+
}}
184+
disabled={isButtonDisabled}
185+
>
186+
{t('import.NotebookBadgeCodeMarkdown')}
187+
</CopyButton>
188+
</BAIFlex>
189+
</BAIFlex>
190+
);
191+
}}
192+
</Form.Item>
193+
</BAIFlex>
110194
</Form>
111195
);
112196
};

react/src/components/ImportRepoForm.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { useSuspendedBackendaiClient } from '../hooks';
22
import StorageSelect from './StorageSelect';
33
import { CloudDownloadOutlined } from '@ant-design/icons';
4-
import { App, Form, FormInstance, FormProps, Input, message } from 'antd';
4+
import {
5+
App,
6+
Form,
7+
FormInstance,
8+
FormProps,
9+
Input,
10+
message,
11+
Radio,
12+
} from 'antd';
513
import {
614
BAIButton,
715
useErrorMessageResolver,
@@ -18,10 +26,12 @@ import {
1826

1927
type URLType = 'github' | 'gitlab';
2028

29+
type VFolderUsageModeForImport = 'general' | 'model';
2130
interface ImportFromURLFormValues {
2231
url: string;
2332
storageHost: string;
2433
gitlabBranch?: string;
34+
vfolder_usage_mode?: VFolderUsageModeForImport;
2535
}
2636

2737
interface ImportFromURLFormProps extends FormProps {
@@ -33,19 +43,24 @@ const createRepoBootstrapScript = (
3343
folderName: string,
3444
extractedDirectory?: string,
3545
) => {
46+
// Helper function to escape shell arguments
47+
const escapeShellArg = (arg: string): string => {
48+
return `'${arg.replace(/'/g, "'\\''")}'`;
49+
};
50+
3651
const scriptLines = [
3752
'#!/bin/sh',
3853
'# Create folder and download repository',
39-
`curl -o /tmp/repo.zip ${archiveUrl}`,
40-
`mkdir -p /home/work/${folderName}`,
41-
`cd /home/work/${folderName}`,
54+
`curl -o /tmp/repo.zip ${escapeShellArg(archiveUrl)}`,
55+
`mkdir -p /home/work/${escapeShellArg(folderName)}`,
56+
`cd /home/work/${escapeShellArg(folderName)}`,
4257
'unzip -o /tmp/repo.zip',
4358
extractedDirectory
4459
? [
4560
'# Move contents if in subfolder',
46-
`if [ -d "${extractedDirectory}" ]; then`,
47-
` mv ${extractedDirectory}/* .`,
48-
` rm -rf ${extractedDirectory}`,
61+
`if [ -d ${escapeShellArg(extractedDirectory)} ]; then`,
62+
` mv ${escapeShellArg(extractedDirectory)}/* .`,
63+
` rm -rf ${escapeShellArg(extractedDirectory)}`,
4964
'fi',
5065
].join('\n')
5166
: undefined,
@@ -201,7 +216,7 @@ const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
201216
folderName,
202217
values.storageHost,
203218
'', // group
204-
'general', // usage mode
219+
values.vfolder_usage_mode ?? 'general', // usage mode
205220
'rw', // permission
206221
);
207222

@@ -251,7 +266,16 @@ const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
251266
};
252267

253268
return (
254-
<Form ref={formRef} layout="vertical" {...formProps}>
269+
<Form
270+
ref={formRef}
271+
layout="vertical"
272+
{...formProps}
273+
initialValues={
274+
{
275+
vfolder_usage_mode: 'general',
276+
} as ImportFromURLFormValues
277+
}
278+
>
255279
<Form.Item>
256280
{urlType === 'github'
257281
? t('import.RepoWillBeFolder')
@@ -267,7 +291,7 @@ const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
267291
{ type: 'string', max: 2048 },
268292
{
269293
pattern:
270-
urlType === 'github' ? t('import.GitHubURL') : t('import.GitlabURL')
294+
urlType === 'github'
271295
? /^(https?):\/\/github\.com\/([\w./-]{1,})$/
272296
: /^(https?):\/\/gitlab\.com\/([\w./-]{1,})$/,
273297
message: t('import.WrongURLType'),
@@ -288,6 +312,21 @@ const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
288312
</>
289313
)}
290314

315+
<Form.Item
316+
label={t('data.UsageMode')}
317+
name={'vfolder_usage_mode'}
318+
hidden={baiClient._config.enableModelFolders !== true}
319+
required
320+
>
321+
<Radio.Group>
322+
<Radio value={'general'} data-testid="general-usage-mode">
323+
{t('data.General')}
324+
</Radio>
325+
<Radio value={'model'} data-testid="model-usage-mode">
326+
{t('data.Models')}
327+
</Radio>
328+
</Radio.Group>
329+
</Form.Item>
291330
<Form.Item
292331
name="storageHost"
293332
label={t('import.StorageHost')}

resources/i18n/de.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@
757757
"RepositoryNotFound": "Repository nicht gefunden.",
758758
"StorageHost": "Speicherhost",
759759
"WrongURLType": "Falscher URL-Typ",
760-
"YouCanCreateNotebookCode": "Geben Sie die Notebook-URL ein, um einen Badge-Code wie unten zu erstellen, den Sie direkt auf Backend.AI ausführen können."
760+
"YouCanCreateNotebookCode": "Dieses Abzeichen ermöglicht die direkte Ausführung Ihres Notebooks auf Backend.AI. Kopieren Sie den Code, um ihn überall zu verwenden."
761761
},
762762
"information": {
763763
"APIVersion": "API-Version",

resources/i18n/el.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@
754754
"RepositoryNotFound": "Το αποθετήριο δεν βρέθηκε.",
755755
"StorageHost": "Διακομιστής αποθήκευσης",
756756
"WrongURLType": "Λάθος τύπος διεύθυνσης URL",
757-
"YouCanCreateNotebookCode": "Εισαγάγετε τη διεύθυνση URL του σημειωματάριου για να δημιουργήσετε έναν κωδικό σήματος όπως παρακάτω, τον οποίο μπορείτε να εκτελέσετε απευθείας στο Backend.AI."
757+
"YouCanCreateNotebookCode": "Αυτό το σήμα επιτρέπει την άμεση εκτέλεση του σημειωματάριού σας στο Backend.AI. Αντιγράψτε τον κώδικα για να τον χρησιμοποιήσετε οπουδήποτε."
758758
},
759759
"information": {
760760
"APIVersion": "Έκδοση API",

resources/i18n/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@
762762
"RepositoryNotFound": "Repository Not Found.",
763763
"StorageHost": "Storage Host",
764764
"WrongURLType": "Wrong URL Type",
765-
"YouCanCreateNotebookCode": "Enter the notebook URL to create a badge code like below that you can run directly on Backend.AI."
765+
"YouCanCreateNotebookCode": "This badge enables direct execution of your notebook on Backend.AI. Copy the code to use it anywhere."
766766
},
767767
"information": {
768768
"APIVersion": "API version",

resources/i18n/es.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@
757757
"RepositoryNotFound": "Repositorio no encontrado.",
758758
"StorageHost": "Host de almacenamiento",
759759
"WrongURLType": "Tipo de URL incorrecto",
760-
"YouCanCreateNotebookCode": "Introduce la URL del cuaderno para crear un código de insignia como el siguiente que puedes ejecutar directamente en Backend.AI."
760+
"YouCanCreateNotebookCode": "Esta insignia habilita la ejecución directa de tu notebook en Backend.AI. Copia el código para usarlo en cualquier lugar."
761761
},
762762
"information": {
763763
"APIVersion": "Versión API",

resources/i18n/fi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@
757757
"RepositoryNotFound": "Repositoriota ei löytynyt.",
758758
"StorageHost": "Tallennuspalvelin",
759759
"WrongURLType": "Väärä URL-tyyppi",
760-
"YouCanCreateNotebookCode": "Kirjoita muistikirjan URL-osoite luodaksesi alla olevan kaltaisen merkkikoodin, jonka voit suorittaa suoraan Backend.AI:ssa."
760+
"YouCanCreateNotebookCode": "Tämä badge mahdollistaa muistikirjasi suorittamisen suoraan Backend.AI:ssä. Kopioi koodi käyttääksesi sitä missä tahansa."
761761
},
762762
"information": {
763763
"APIVersion": "API-versio",

0 commit comments

Comments
 (0)