more-compute 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. frontend/.DS_Store +0 -0
  2. frontend/.gitignore +41 -0
  3. frontend/README.md +36 -0
  4. frontend/__init__.py +1 -0
  5. frontend/app/favicon.ico +0 -0
  6. frontend/app/globals.css +1537 -0
  7. frontend/app/layout.tsx +173 -0
  8. frontend/app/page.tsx +11 -0
  9. frontend/components/AddCellButton.tsx +42 -0
  10. frontend/components/Cell.tsx +244 -0
  11. frontend/components/CellButton.tsx +58 -0
  12. frontend/components/CellOutput.tsx +70 -0
  13. frontend/components/ErrorDisplay.tsx +208 -0
  14. frontend/components/ErrorModal.tsx +154 -0
  15. frontend/components/MarkdownRenderer.tsx +84 -0
  16. frontend/components/Notebook.tsx +520 -0
  17. frontend/components/Sidebar.tsx +46 -0
  18. frontend/components/popups/ComputePopup.tsx +879 -0
  19. frontend/components/popups/FilterPopup.tsx +427 -0
  20. frontend/components/popups/FolderPopup.tsx +171 -0
  21. frontend/components/popups/MetricsPopup.tsx +168 -0
  22. frontend/components/popups/PackagesPopup.tsx +112 -0
  23. frontend/components/popups/PythonPopup.tsx +292 -0
  24. frontend/components/popups/SettingsPopup.tsx +68 -0
  25. frontend/eslint.config.mjs +25 -0
  26. frontend/lib/api.ts +469 -0
  27. frontend/lib/settings.ts +87 -0
  28. frontend/lib/websocket-native.ts +202 -0
  29. frontend/lib/websocket.ts +134 -0
  30. frontend/next-env.d.ts +6 -0
  31. frontend/next.config.mjs +17 -0
  32. frontend/next.config.ts +7 -0
  33. frontend/package-lock.json +5676 -0
  34. frontend/package.json +41 -0
  35. frontend/postcss.config.mjs +5 -0
  36. frontend/public/assets/icons/add.svg +1 -0
  37. frontend/public/assets/icons/check.svg +1 -0
  38. frontend/public/assets/icons/copy.svg +1 -0
  39. frontend/public/assets/icons/folder.svg +1 -0
  40. frontend/public/assets/icons/metric.svg +1 -0
  41. frontend/public/assets/icons/packages.svg +1 -0
  42. frontend/public/assets/icons/play.svg +1 -0
  43. frontend/public/assets/icons/python.svg +265 -0
  44. frontend/public/assets/icons/setting.svg +1 -0
  45. frontend/public/assets/icons/stop.svg +1 -0
  46. frontend/public/assets/icons/trash.svg +1 -0
  47. frontend/public/assets/icons/up-down.svg +1 -0
  48. frontend/public/assets/icons/x.svg +1 -0
  49. frontend/public/file.svg +1 -0
  50. frontend/public/fonts/Fira.ttf +0 -0
  51. frontend/public/fonts/Tiempos.woff2 +0 -0
  52. frontend/public/fonts/VeraMono.ttf +0 -0
  53. frontend/public/globe.svg +1 -0
  54. frontend/public/next.svg +1 -0
  55. frontend/public/vercel.svg +1 -0
  56. frontend/public/window.svg +1 -0
  57. frontend/tailwind.config.ts +29 -0
  58. frontend/tsconfig.json +27 -0
  59. frontend/types/notebook.ts +58 -0
  60. kernel_run.py +7 -0
  61. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/METADATA +1 -1
  62. more_compute-0.1.4.dist-info/RECORD +86 -0
  63. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/top_level.txt +1 -0
  64. morecompute/__version__.py +1 -0
  65. more_compute-0.1.2.dist-info/RECORD +0 -26
  66. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/WHEEL +0 -0
  67. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/entry_points.txt +0 -0
  68. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,427 @@
1
+ import React from "react";
2
+ import { GpuAvailabilityParams } from "@/lib/api";
3
+
4
+ interface FilterPopupProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ filters: GpuAvailabilityParams;
8
+ onFiltersChange: (filters: GpuAvailabilityParams) => void;
9
+ onApply: () => void;
10
+ }
11
+
12
+ const GPU_TYPES = [
13
+ { value: "H100_80GB", label: "H100 80GB" },
14
+ { value: "H200_96GB", label: "H200 96GB" },
15
+ { value: "GH200_96GB", label: "GH200 96GB" },
16
+ { value: "H200_141GB", label: "H200 141GB" },
17
+ { value: "B200_180GB", label: "B200 180GB" },
18
+ { value: "A100_80GB", label: "A100 80GB" },
19
+ { value: "A100_40GB", label: "A100 40GB" },
20
+ { value: "A10_24GB", label: "A10 24GB" },
21
+ { value: "A30_24GB", label: "A30 24GB" },
22
+ { value: "A40_48GB", label: "A40 48GB" },
23
+ { value: "RTX4090_24GB", label: "RTX 4090 24GB" },
24
+ { value: "RTX5090_32GB", label: "RTX 5090 32GB" },
25
+ { value: "RTX4080_16GB", label: "RTX 4080 16GB" },
26
+ { value: "RTX4080Ti_16GB", label: "RTX 4080 Ti 16GB" },
27
+ { value: "RTX4070Ti_12GB", label: "RTX 4070 Ti 12GB" },
28
+ { value: "RTX3090_24GB", label: "RTX 3090 24GB" },
29
+ { value: "RTX3090Ti_24GB", label: "RTX 3090 Ti 24GB" },
30
+ { value: "RTX3080_10GB", label: "RTX 3080 10GB" },
31
+ { value: "RTX3080Ti_12GB", label: "RTX 3080 Ti 12GB" },
32
+ { value: "RTX3070_8GB", label: "RTX 3070 8GB" },
33
+ { value: "L40S_48GB", label: "L40S 48GB" },
34
+ { value: "L40_48GB", label: "L40 48GB" },
35
+ { value: "L4_24GB", label: "L4 24GB" },
36
+ { value: "V100_32GB", label: "V100 32GB" },
37
+ { value: "V100_16GB", label: "V100 16GB" },
38
+ { value: "T4_16GB", label: "T4 16GB" },
39
+ { value: "P100_16GB", label: "P100 16GB" },
40
+ { value: "A6000_48GB", label: "A6000 48GB" },
41
+ { value: "A5000_24GB", label: "A5000 24GB" },
42
+ { value: "A4000_16GB", label: "A4000 16GB" },
43
+ { value: "RTX6000Ada_48GB", label: "RTX 6000 Ada 48GB" },
44
+ { value: "RTX5000Ada_32GB", label: "RTX 5000 Ada 32GB" },
45
+ { value: "RTX4000Ada_20GB", label: "RTX 4000 Ada 20GB" },
46
+ ];
47
+
48
+ const FilterPopup: React.FC<FilterPopupProps> = ({
49
+ isOpen,
50
+ onClose,
51
+ filters,
52
+ onFiltersChange,
53
+ onApply,
54
+ }) => {
55
+ const [filterCategory, setFilterCategory] = React.useState<string>("gpu_type");
56
+ const [filterSearch, setFilterSearch] = React.useState<string>("");
57
+
58
+ if (!isOpen) return null;
59
+
60
+ const handleClearAll = () => {
61
+ onFiltersChange({});
62
+ setFilterSearch("");
63
+ };
64
+
65
+ return (
66
+ <>
67
+ {/* Backdrop */}
68
+ <div
69
+ onClick={onClose}
70
+ style={{
71
+ position: "fixed",
72
+ top: 0,
73
+ left: 0,
74
+ right: 0,
75
+ bottom: 0,
76
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
77
+ zIndex: 9998,
78
+ }}
79
+ />
80
+ {/* Filter Popup */}
81
+ <div
82
+ style={{
83
+ position: "fixed",
84
+ top: "50%",
85
+ left: "50%",
86
+ transform: "translate(-50%, -50%)",
87
+ backgroundColor: "white",
88
+ border: "1px solid #d1d5db",
89
+ borderRadius: "8px",
90
+ padding: "16px",
91
+ width: "320px",
92
+ maxHeight: "480px",
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ boxShadow: "0 10px 25px rgba(0, 0, 0, 0.2)",
96
+ zIndex: 9999,
97
+ }}
98
+ >
99
+ {/* Header */}
100
+ <div
101
+ style={{
102
+ display: "flex",
103
+ justifyContent: "space-between",
104
+ alignItems: "center",
105
+ marginBottom: "16px",
106
+ }}
107
+ >
108
+ <h4
109
+ style={{
110
+ fontSize: "14px",
111
+ fontWeight: 600,
112
+ margin: 0,
113
+ color: "#111827",
114
+ }}
115
+ >
116
+ Filter
117
+ </h4>
118
+ <button
119
+ onClick={handleClearAll}
120
+ style={{
121
+ fontSize: "11px",
122
+ color: "#3b82f6",
123
+ background: "none",
124
+ border: "none",
125
+ cursor: "pointer",
126
+ padding: "4px 8px",
127
+ fontWeight: 500,
128
+ }}
129
+ >
130
+ Clear All
131
+ </button>
132
+ </div>
133
+
134
+ {/* Category Dropdown */}
135
+ <select
136
+ value={filterCategory}
137
+ onChange={(e) => {
138
+ setFilterCategory(e.target.value);
139
+ setFilterSearch("");
140
+ }}
141
+ style={{
142
+ width: "100%",
143
+ padding: "8px 10px",
144
+ borderRadius: "6px",
145
+ border: "1px solid #d1d5db",
146
+ backgroundColor: "white",
147
+ color: "#111827",
148
+ fontSize: "12px",
149
+ marginBottom: "12px",
150
+ cursor: "pointer",
151
+ }}
152
+ >
153
+ <option value="gpu_type">GPU Type</option>
154
+ <option value="gpu_count">GPU Count</option>
155
+ <option value="security">Security</option>
156
+ <option value="socket">Socket</option>
157
+ </select>
158
+
159
+ {/* Search within category */}
160
+ <input
161
+ type="text"
162
+ placeholder="Search"
163
+ value={filterSearch}
164
+ onChange={(e) => setFilterSearch(e.target.value)}
165
+ style={{
166
+ width: "100%",
167
+ padding: "8px 10px",
168
+ borderRadius: "6px",
169
+ border: "1px solid #d1d5db",
170
+ backgroundColor: "white",
171
+ color: "#111827",
172
+ fontSize: "12px",
173
+ marginBottom: "12px",
174
+ boxSizing: "border-box",
175
+ }}
176
+ />
177
+
178
+ {/* Options List */}
179
+ <div
180
+ style={{
181
+ flex: 1,
182
+ overflowY: "auto",
183
+ marginBottom: "16px",
184
+ maxHeight: "240px",
185
+ border: "1px solid #e5e7eb",
186
+ borderRadius: "6px",
187
+ padding: "4px",
188
+ }}
189
+ >
190
+ {filterCategory === "gpu_type" && (
191
+ <>
192
+ {GPU_TYPES.filter((gpu) =>
193
+ gpu.label.toLowerCase().includes(filterSearch.toLowerCase())
194
+ ).map((gpu) => (
195
+ <label
196
+ key={gpu.value}
197
+ style={{
198
+ display: "flex",
199
+ alignItems: "center",
200
+ padding: "8px 6px",
201
+ cursor: "pointer",
202
+ fontSize: "12px",
203
+ color: "#374151",
204
+ borderRadius: "4px",
205
+ transition: "background-color 0.15s",
206
+ }}
207
+ onMouseEnter={(e) =>
208
+ (e.currentTarget.style.backgroundColor = "#f3f4f6")
209
+ }
210
+ onMouseLeave={(e) =>
211
+ (e.currentTarget.style.backgroundColor = "transparent")
212
+ }
213
+ >
214
+ <input
215
+ type="radio"
216
+ checked={filters.gpu_type === gpu.value}
217
+ onChange={() =>
218
+ onFiltersChange({
219
+ ...filters,
220
+ gpu_type: gpu.value,
221
+ })
222
+ }
223
+ style={{ marginRight: "10px", cursor: "pointer" }}
224
+ />
225
+ {gpu.label}
226
+ </label>
227
+ ))}
228
+ </>
229
+ )}
230
+
231
+ {filterCategory === "gpu_count" && (
232
+ <>
233
+ {[
234
+ { value: "", label: "Any" },
235
+ { value: "1", label: "1 GPU" },
236
+ { value: "2", label: "2 GPUs" },
237
+ { value: "4", label: "4 GPUs" },
238
+ { value: "8", label: "8 GPUs" },
239
+ ]
240
+ .filter((option) =>
241
+ option.label.toLowerCase().includes(filterSearch.toLowerCase())
242
+ )
243
+ .map((option) => (
244
+ <label
245
+ key={option.value}
246
+ style={{
247
+ display: "flex",
248
+ alignItems: "center",
249
+ padding: "8px 6px",
250
+ cursor: "pointer",
251
+ fontSize: "12px",
252
+ color: "#374151",
253
+ borderRadius: "4px",
254
+ transition: "background-color 0.15s",
255
+ }}
256
+ onMouseEnter={(e) =>
257
+ (e.currentTarget.style.backgroundColor = "#f3f4f6")
258
+ }
259
+ onMouseLeave={(e) =>
260
+ (e.currentTarget.style.backgroundColor = "transparent")
261
+ }
262
+ >
263
+ <input
264
+ type="radio"
265
+ checked={
266
+ (filters.gpu_count?.toString() || "") === option.value
267
+ }
268
+ onChange={() =>
269
+ onFiltersChange({
270
+ ...filters,
271
+ gpu_count: option.value
272
+ ? parseInt(option.value)
273
+ : undefined,
274
+ })
275
+ }
276
+ style={{ marginRight: "10px", cursor: "pointer" }}
277
+ />
278
+ {option.label}
279
+ </label>
280
+ ))}
281
+ </>
282
+ )}
283
+
284
+ {filterCategory === "security" && (
285
+ <>
286
+ {[
287
+ { value: "", label: "All" },
288
+ { value: "secure_cloud", label: "Secure Cloud" },
289
+ {
290
+ value: "community_cloud",
291
+ label: "Community Cloud",
292
+ },
293
+ ]
294
+ .filter((option) =>
295
+ option.label.toLowerCase().includes(filterSearch.toLowerCase())
296
+ )
297
+ .map((option) => (
298
+ <label
299
+ key={option.value}
300
+ style={{
301
+ display: "flex",
302
+ alignItems: "center",
303
+ padding: "8px 6px",
304
+ cursor: "pointer",
305
+ fontSize: "12px",
306
+ color: "#374151",
307
+ borderRadius: "4px",
308
+ transition: "background-color 0.15s",
309
+ }}
310
+ onMouseEnter={(e) =>
311
+ (e.currentTarget.style.backgroundColor = "#f3f4f6")
312
+ }
313
+ onMouseLeave={(e) =>
314
+ (e.currentTarget.style.backgroundColor = "transparent")
315
+ }
316
+ >
317
+ <input
318
+ type="radio"
319
+ checked={(filters.security || "") === option.value}
320
+ onChange={() =>
321
+ onFiltersChange({
322
+ ...filters,
323
+ security: option.value || undefined,
324
+ })
325
+ }
326
+ style={{ marginRight: "10px", cursor: "pointer" }}
327
+ />
328
+ {option.label}
329
+ </label>
330
+ ))}
331
+ </>
332
+ )}
333
+
334
+ {filterCategory === "socket" && (
335
+ <>
336
+ {[
337
+ { value: "", label: "All" },
338
+ { value: "PCIe", label: "PCIe" },
339
+ { value: "SXM4", label: "SXM4" },
340
+ { value: "SXM5", label: "SXM5" },
341
+ { value: "SXM6", label: "SXM6" },
342
+ ]
343
+ .filter((option) =>
344
+ option.label.toLowerCase().includes(filterSearch.toLowerCase())
345
+ )
346
+ .map((option) => (
347
+ <label
348
+ key={option.value}
349
+ style={{
350
+ display: "flex",
351
+ alignItems: "center",
352
+ padding: "8px 6px",
353
+ cursor: "pointer",
354
+ fontSize: "12px",
355
+ color: "#374151",
356
+ borderRadius: "4px",
357
+ transition: "background-color 0.15s",
358
+ }}
359
+ onMouseEnter={(e) =>
360
+ (e.currentTarget.style.backgroundColor = "#f3f4f6")
361
+ }
362
+ onMouseLeave={(e) =>
363
+ (e.currentTarget.style.backgroundColor = "transparent")
364
+ }
365
+ >
366
+ <input
367
+ type="radio"
368
+ checked={(filters.socket || "") === option.value}
369
+ onChange={() =>
370
+ onFiltersChange({
371
+ ...filters,
372
+ socket: option.value || undefined,
373
+ })
374
+ }
375
+ style={{ marginRight: "10px", cursor: "pointer" }}
376
+ />
377
+ {option.label}
378
+ </label>
379
+ ))}
380
+ </>
381
+ )}
382
+ </div>
383
+
384
+ {/* Action Buttons */}
385
+ <div style={{ display: "flex", gap: "10px" }}>
386
+ <button
387
+ onClick={onClose}
388
+ style={{
389
+ flex: 1,
390
+ padding: "8px 16px",
391
+ fontSize: "12px",
392
+ borderRadius: "6px",
393
+ border: "1px solid #d1d5db",
394
+ backgroundColor: "white",
395
+ color: "#374151",
396
+ cursor: "pointer",
397
+ fontWeight: 500,
398
+ }}
399
+ >
400
+ Cancel
401
+ </button>
402
+ <button
403
+ onClick={() => {
404
+ onApply();
405
+ onClose();
406
+ }}
407
+ style={{
408
+ flex: 1,
409
+ padding: "8px 16px",
410
+ fontSize: "12px",
411
+ borderRadius: "6px",
412
+ border: "none",
413
+ backgroundColor: "#3b82f6",
414
+ color: "white",
415
+ cursor: "pointer",
416
+ fontWeight: 500,
417
+ }}
418
+ >
419
+ Apply
420
+ </button>
421
+ </div>
422
+ </div>
423
+ </>
424
+ );
425
+ };
426
+
427
+ export default FilterPopup;
@@ -0,0 +1,171 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Folder as FolderIcon, File as FileIcon, ArrowLeft, ArrowUp } from 'lucide-react';
3
+ import { fetchFileTree, fetchFilePreview, type FileTreeItem } from '@/lib/api';
4
+
5
+ interface FolderPopupProps {
6
+ onClose?: () => void;
7
+ }
8
+
9
+ type ViewState = 'list' | 'preview';
10
+
11
+ const FolderPopup: React.FC<FolderPopupProps> = () => {
12
+ const [currentPath, setCurrentPath] = useState<string>('.');
13
+ const [rootPath, setRootPath] = useState<string>('');
14
+ const [items, setItems] = useState<FileTreeItem[]>([]);
15
+ const [loading, setLoading] = useState<boolean>(true);
16
+ const [error, setError] = useState<string | null>(null);
17
+ const [viewState, setViewState] = useState<ViewState>('list');
18
+ const [selectedFile, setSelectedFile] = useState<FileTreeItem | null>(null);
19
+ const [filePreview, setFilePreview] = useState<string>('');
20
+ const [previewLoading, setPreviewLoading] = useState<boolean>(false);
21
+ const [previewError, setPreviewError] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ void loadDirectory(currentPath);
25
+ }, [currentPath]);
26
+
27
+ const loadDirectory = async (path: string) => {
28
+ setLoading(true);
29
+ setError(null);
30
+ setViewState('list');
31
+ try {
32
+ const data = await fetchFileTree(path);
33
+ setRootPath(data.root);
34
+ setCurrentPath(data.path);
35
+ setItems(data.items);
36
+ } catch (err: any) {
37
+ console.error('Failed to load files:', err);
38
+ setError(err.message || 'Failed to load files');
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ const handleItemClick = async (item: FileTreeItem) => {
45
+ if (item.type === 'directory') {
46
+ setSelectedFile(null);
47
+ setFilePreview('');
48
+ setPreviewError(null);
49
+ await loadDirectory(item.path);
50
+ } else {
51
+ await openFile(item);
52
+ }
53
+ };
54
+
55
+ const openFile = async (item: FileTreeItem) => {
56
+ setSelectedFile(item);
57
+ setViewState('preview');
58
+ setPreviewLoading(true);
59
+ setPreviewError(null);
60
+ try {
61
+ const text = await fetchFilePreview(item.path);
62
+ setFilePreview(text);
63
+ } catch (err: any) {
64
+ console.error('Failed to load file:', err);
65
+ setPreviewError(err.message || 'Failed to load file');
66
+ } finally {
67
+ setPreviewLoading(false);
68
+ }
69
+ };
70
+
71
+ const navigateUp = () => {
72
+ if (currentPath === '.' || currentPath === '') {
73
+ return;
74
+ }
75
+ const parts = currentPath.split('/');
76
+ parts.pop();
77
+ const parent = parts.join('/') || '.';
78
+ setCurrentPath(parent);
79
+ };
80
+
81
+ const renderToolbar = () => (
82
+ <div className="file-toolbar">
83
+ {viewState === 'preview' && (
84
+ <button type="button" className="file-toolbar-btn" onClick={() => setViewState('list')} aria-label="Back to files">
85
+ <ArrowLeft size={16} />
86
+ </button>
87
+ )}
88
+ {!(currentPath === '.' || currentPath === '') && (
89
+ <button type="button" className="file-toolbar-btn" onClick={navigateUp} aria-label="Up one directory">
90
+ <ArrowUp size={16} />
91
+ </button>
92
+ )}
93
+ </div>
94
+ );
95
+
96
+ const renderList = () => {
97
+ if (loading) {
98
+ return <div className="file-tree">Loading…</div>;
99
+ }
100
+
101
+ if (error) {
102
+ return (
103
+ <div className="error">
104
+ <p>{error}</p>
105
+ <button type="button" onClick={() => void loadDirectory(currentPath)}>Retry</button>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ if (!items.length) {
111
+ return <div className="file-tree empty">No files found in this directory.</div>;
112
+ }
113
+
114
+ return (
115
+ <div className="file-tree">
116
+ <div className="file-list">
117
+ {items.map((item) => (
118
+ <div
119
+ key={item.path}
120
+ className={`file-item ${item.type}`}
121
+ onClick={() => void handleItemClick(item)}
122
+ >
123
+ {item.type === 'directory' ? (
124
+ <FolderIcon className="file-icon" size={18} />
125
+ ) : (
126
+ <FileIcon className="file-icon" size={18} />
127
+ )}
128
+ <div className="file-meta">
129
+ <span className="file-name">{item.name}</span>
130
+ <span className="file-details">
131
+ {item.type === 'directory' ? 'Folder' : 'File'}
132
+ {item.size !== undefined ? ` · ${item.size} bytes` : ''}
133
+ {item.modified ? ` · ${new Date(item.modified).toLocaleString()}` : ''}
134
+ </span>
135
+ </div>
136
+ </div>
137
+ ))}
138
+ </div>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ const renderPreview = () => {
144
+ if (!selectedFile) return null;
145
+
146
+ return (
147
+ <div className="file-preview">
148
+ <div className="file-preview-body">
149
+ {previewLoading ? (
150
+ <div className="file-preview-loading">Loading preview…</div>
151
+ ) : previewError ? (
152
+ <div className="error">{previewError}</div>
153
+ ) : (
154
+ <pre className="file-preview-content">{filePreview}</pre>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ return (
162
+ <div className="file-browser">
163
+ {renderToolbar()}
164
+ <div className="file-tree-container">
165
+ {viewState === 'list' ? renderList() : renderPreview()}
166
+ </div>
167
+ </div>
168
+ );
169
+ };
170
+
171
+ export default FolderPopup;