From 8711c27b787130ee711fcbd6ce6527bfdc4d29f7 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 13 Apr 2026 15:18:08 +0800 Subject: [PATCH] chore: replace components with core-ui, upgrade eslint --- .eslintignore | 1 - .eslintrc.js | 16 - eslint.config.js | 77 + package.json | 13 +- src/components/alert-info/block.tsx | 182 -- src/components/alert-info/index.tsx | 49 - src/components/audio-animation/index.less | 19 - src/components/audio-animation/index.tsx | 192 -- src/components/audio-player/audio-element.tsx | 22 - src/components/audio-player/config/type.ts | 39 - src/components/audio-player/index.less | 103 -- src/components/audio-player/index.tsx | 311 ---- .../audio-player/raw-audio-player.tsx | 197 -- src/components/audio-player/simple-audio.tsx | 406 ----- src/components/auto-image/index.less | 21 - src/components/auto-image/index.tsx | 131 -- src/components/auto-image/progress-line.less | 44 - src/components/auto-image/single-image.less | 144 -- src/components/auto-image/single-image.tsx | 209 --- src/components/auto-tooltip/index.tsx | 151 -- src/components/auto-tooltip/title-tip.tsx | 28 - src/components/bibtex-viewer/index.tsx | 47 - src/components/buttons/more.tsx | 46 - src/components/card-wrapper/index.tsx | 16 - src/components/card-wrapper/simple-card.tsx | 118 -- src/components/check-buttons/index.tsx | 43 - .../collapse-container/headless-collapse.tsx | 57 - src/components/collapse-container/index.tsx | 219 --- src/components/content-wrapper/index.less | 17 - src/components/content-wrapper/index.tsx | 24 - src/components/copy-button/index.tsx | 156 -- src/components/delete-modal/index.tsx | 224 --- src/components/divider-line/index.less | 20 - src/components/divider-line/index.tsx | 6 - src/components/drop-down-actions/index.tsx | 40 - src/components/drop-down-buttons/index.less | 4 - src/components/drop-down-buttons/index.tsx | 150 -- .../dynamic-form/components/field-item.tsx | 19 - .../dynamic-form/components/form-widget.tsx | 59 - .../dynamic-form/components/list-map.tsx | 175 -- src/components/dynamic-form/config/types.ts | 39 - .../dynamic-form/hooks/use-parse-fields.ts | 33 - .../dynamic-form/hooks/use-validate-fields.ts | 61 - src/components/dynamic-form/index.tsx | 26 - src/components/echarts/bar-chart.tsx | 107 -- src/components/echarts/chart-tooltip.tsx | 75 - src/components/echarts/chart.tsx | 150 -- src/components/echarts/config.ts | 247 --- src/components/echarts/gauge.tsx | 97 - src/components/echarts/h-bar.tsx | 185 -- src/components/echarts/index.ts | 60 - src/components/echarts/line-chart.tsx | 172 -- src/components/echarts/mix-line-bar.tsx | 176 -- src/components/echarts/scatter.tsx | 234 --- src/components/echarts/types.ts | 45 - src/components/editor-wrap/index.less | 24 - src/components/editor-wrap/index.tsx | 73 - src/components/editor/index.tsx | 109 -- src/components/empty-data/index.tsx | 34 - src/components/form-buttons/index.tsx | 44 - .../highlight-code/code-viewer-dark.tsx | 43 - .../highlight-code/code-viewer-light.tsx | 44 - src/components/highlight-code/code-viewer.tsx | 172 -- src/components/highlight-code/index.tsx | 63 - .../highlight-code/styles/dark.less | 106 -- .../highlight-code/styles/index.less | 84 - .../highlight-code/styles/light.less | 106 -- src/components/highlight-code/utils.ts | 8 - .../icon-font/iconfont/iconfont.css | 915 ---------- src/components/icon-font/iconfont/iconfont.js | 1 - .../icon-font/iconfont/iconfont.json | 1584 ----------------- .../icon-font/iconfont/iconfont.ttf | Bin 67840 -> 0 bytes .../icon-font/iconfont/iconfont.woff | Bin 42984 -> 0 bytes .../icon-font/iconfont/iconfont.woff2 | Bin 36380 -> 0 bytes src/components/icon-font/icons.ts | 71 - src/components/icon-font/index.tsx | 8 - .../image-editor/extract-image-colors.ts | 149 -- .../image-editor/hooks/use-drawing.ts | 437 ----- src/components/image-editor/hooks/use-zoom.ts | 128 -- src/components/image-editor/index.less | 39 - src/components/image-editor/index.tsx | 645 ------- src/components/image-editor/invert-worker.ts | 54 - .../image-editor/offscreen-worker.ts | 111 -- src/components/image-editor/tools-bar.tsx | 200 --- src/components/label-cell/index.tsx | 32 - .../label-selector/autocomplete-item.tsx | 144 -- src/components/label-selector/context.ts | 26 - src/components/label-selector/index.tsx | 119 -- src/components/label-selector/inner.tsx | 121 -- src/components/label-selector/label-item.tsx | 129 -- .../label-selector/styles/label-item.less | 29 - src/components/label-selector/wrapper.tsx | 94 - src/components/list-input/hint-input.tsx | 109 -- src/components/list-input/index.tsx | 169 -- src/components/list-input/list-item.tsx | 82 - .../list-input/style/list-item.less | 15 - .../list-input/styles/list-item.less | 15 - src/components/logs-viewer/config.ts | 33 - src/components/logs-viewer/logs-list.tsx | 155 -- .../logs-viewer/logs-pagination.tsx | 109 -- src/components/logs-viewer/parse-worker.ts | 315 ---- src/components/logs-viewer/styles/index.less | 126 -- .../logs-viewer/styles/logs-list.less | 44 - .../logs-viewer/styles/pagination.less | 25 - .../logs-viewer/styles/xterm-viewer.less | 50 - .../logs-viewer/use-logs-pagination.ts | 41 - src/components/logs-viewer/use-size.ts | 34 - .../logs-viewer/virtual-log-list.tsx | 345 ---- src/components/logs-viewer/xterm-viewer.tsx | 178 -- .../markdown-viewer/code-viewer.tsx | 0 .../markdown-viewer/full-markdown.tsx | 67 - src/components/markdown-viewer/index.less | 136 -- src/components/markdown-viewer/index.tsx | 232 --- src/components/markdown-viewer/utils.ts | 55 - src/components/metadata-list/index.tsx | 83 - src/components/modal-footer/index.tsx | 82 - .../overlay-scroller/index.module.less | 6 - src/components/overlay-scroller/index.tsx | 112 -- src/components/page-tools/filters-button.less | 47 - src/components/page-tools/index.less | 18 - src/components/page-tools/index.tsx | 278 --- src/components/popover/index.less | 27 - src/components/popover/index.tsx | 25 - src/components/progress-bar/index.tsx | 85 - src/components/radio-buttons/index.less | 21 - src/components/radio-buttons/index.tsx | 29 - src/components/resize-container/index.tsx | 35 - .../resize-container/use-responsive.ts | 23 - src/components/scroller-modal/gs-drawer.tsx | 72 - src/components/scroller-modal/index.tsx | 98 - .../scroller-modal/use-scroller-context.ts | 11 - src/components/seal-form/auto-complete.tsx | 159 -- src/components/seal-form/base/select.tsx | 64 - src/components/seal-form/base/styles.less | 7 - src/components/seal-form/checkbox-field.tsx | 29 - .../seal-form/components/label-info.less | 22 - .../seal-form/components/label-info.tsx | 50 - .../components/not-found-content.tsx | 47 - src/components/seal-form/config/components.ts | 28 - src/components/seal-form/config/index.ts | 15 - src/components/seal-form/field-component.tsx | 48 - .../seal-form/hooks/use-select-render.tsx | 38 - src/components/seal-form/input-number.tsx | 92 - src/components/seal-form/input-search.tsx | 94 - src/components/seal-form/multiple-select.tsx | 121 -- src/components/seal-form/password.tsx | 88 - src/components/seal-form/row-textarea.tsx | 251 --- src/components/seal-form/seal-cascader.tsx | 220 --- src/components/seal-form/seal-checkbox.tsx | 74 - src/components/seal-form/seal-input.tsx | 118 -- src/components/seal-form/seal-select.tsx | 135 -- src/components/seal-form/seal-slider.tsx | 115 -- src/components/seal-form/seal-switch.tsx | 73 - src/components/seal-form/seal-textarea.tsx | 131 -- src/components/seal-form/simple-select.tsx | 308 ---- .../seal-form/styles/row-textarea.less | 138 -- src/components/seal-form/styles/slider.less | 24 - src/components/seal-form/switch-input.tsx | 75 - src/components/seal-form/types.ts | 15 - .../seal-form/wrapper/auto-complete-label.ts | 28 - src/components/seal-form/wrapper/index.tsx | 257 --- src/components/seal-form/wrapper/input.ts | 187 -- src/components/seal-form/wrapper/select.ts | 213 --- src/components/seal-form/wrapper/slider.ts | 57 - .../seal-table/components/cell-content.tsx | 182 -- .../seal-table/components/header-prefix.tsx | 131 -- .../seal-table/components/header.tsx | 56 - .../seal-table/components/layout.tsx | 28 - .../seal-table/components/pagination.tsx | 7 - .../seal-table/components/row-children.tsx | 9 - .../seal-table/components/row-prefix.tsx | 101 -- .../seal-table/components/table-body.tsx | 75 - .../seal-table/components/table-cell.tsx | 52 - .../seal-table/components/table-header.tsx | 125 -- .../seal-table/components/table-row.tsx | 230 --- .../seal-table/components/table-skeleton.tsx | 32 - src/components/seal-table/index.tsx | 216 --- src/components/seal-table/row-context.ts | 13 - src/components/seal-table/styles/cell.less | 28 - src/components/seal-table/styles/header.less | 89 - src/components/seal-table/styles/index.less | 92 - .../seal-table/styles/row-children.less | 13 - .../seal-table/styles/skeleton.less | 13 - src/components/seal-table/table-context.ts | 5 - src/components/seal-table/types.ts | 114 -- src/components/seal-table/use-sorter.ts | 90 - src/components/segment-line/index.tsx | 128 -- src/components/short-cuts/index.less | 42 - src/components/short-cuts/index.tsx | 94 - src/components/short-cuts/keymap.ts | 60 - src/components/simple-overlay/index.tsx | 78 - src/components/simple-table/cell.tsx | 0 src/components/simple-table/header.tsx | 28 - src/components/simple-table/index.less | 50 - src/components/simple-table/index.tsx | 76 - src/components/simple-table/info-column.tsx | 56 - src/components/simple-table/row.tsx | 89 - .../speech-content/audio-player.tsx | 222 --- src/components/speech-content/index.tsx | 37 - src/components/speech-content/speech-item.tsx | 305 ---- .../speech-content/styles/index.less | 105 -- .../styles/slider-progress.less | 21 - src/components/status-icon/error.tsx | 26 - src/components/status-tag/copy-btn.less | 27 - src/components/status-tag/index.less | 53 - src/components/status-tag/index.tsx | 197 -- src/components/tags-wrapper/index.less | 33 - src/components/tags-wrapper/index.tsx | 136 -- src/components/tags-wrapper/more-dropdown.tsx | 29 - src/components/tags-wrapper/theme-tag.tsx | 42 - src/components/templates/card-list.tsx | 12 +- src/components/templates/card-skelton.tsx | 42 - src/components/templates/card.tsx | 142 -- .../theme-toggle/theme-drop-actions.tsx | 2 +- src/components/tooltip-list/index.tsx | 53 - src/components/transition/index.less | 45 - src/components/transition/index.tsx | 88 - src/components/type-word-effect/index.tsx | 22 - src/components/upload-audio/index.tsx | 90 - src/components/util-bar/index.less | 17 - src/components/util-bar/index.tsx | 52 - src/components/x-terminal/index.tsx | 294 --- src/hooks/use-chunk-request.ts | 2 +- src/hooks/use-download-logs.tsx | 2 +- src/layouts/error-boundary.tsx | 3 +- src/layouts/extraRender.tsx | 11 +- src/layouts/index.tsx | 143 +- src/layouts/sider-menu.tsx | 2 +- src/pages/_components/category-select.tsx | 2 +- .../_components/collapse-panel/index.tsx | 2 +- src/pages/_components/column-settings.tsx | 2 +- .../_components/command-viewer/index.tsx | 84 - src/pages/_components/doc-link.tsx | 2 +- src/pages/_components/filter-form/index.tsx | 2 +- src/pages/_components/form-drawer.tsx | 4 +- src/pages/_components/model-tag.tsx | 2 +- .../_components/page-breadcrumb/index.tsx | 2 +- .../_components/pill-button-group/index.tsx | 3 +- .../_components/scroll-spy-tabs/index.tsx | 2 +- src/pages/_components/select-panel/index.tsx | 224 --- src/pages/_components/select-panel/list.tsx | 70 - .../select-panel/selected-list.tsx | 60 - src/pages/_components/terminal-tabs/tabs.tsx | 2 +- src/pages/_components/yaml-editor/editor.tsx | 2 +- .../add-apikey-modal/allow-models.tsx | 2 +- .../components/add-apikey-modal/form.tsx | 16 +- .../components/add-apikey-modal/index.tsx | 18 +- src/pages/api-keys/hooks/use-keys-columns.tsx | 6 +- src/pages/api-keys/index.tsx | 6 +- .../community/add-community-modal.tsx | 3 +- .../backends/community/backend-detail.tsx | 4 +- .../backends/community/commnity-backends.tsx | 3 +- src/pages/backends/components/add-modal.tsx | 12 +- .../backends/components/backend-card.tsx | 25 +- .../backends/components/backend-list.tsx | 14 +- src/pages/backends/components/import-yaml.tsx | 2 +- .../components/version-info-modal.tsx | 2 +- src/pages/backends/config/index.ts | 2 +- src/pages/backends/forms/basic.tsx | 24 +- src/pages/backends/forms/version-info.tsx | 11 +- src/pages/backends/forms/versions-config.tsx | 33 +- .../backends/hooks/use-create-backend.tsx | 2 +- src/pages/backends/index.tsx | 4 +- .../components/add-benchmark-modal.tsx | 2 +- .../benchmark/components/detail-content.tsx | 2 +- .../benchmark/components/detail-modal.tsx | 2 +- .../components/environment/index.tsx | 12 +- .../environment/use-gpu-columns.tsx | 3 +- .../environment/use-worker-columns.tsx | 3 +- .../benchmark/components/left-actions.tsx | 2 +- src/pages/benchmark/components/logs/index.tsx | 2 +- .../benchmark/components/right-actions.tsx | 3 +- .../benchmark/components/row-actions.tsx | 5 +- .../benchmark/components/summary/instance.tsx | 3 +- .../benchmark/components/summary/section.tsx | 3 +- .../benchmark/components/view-logs-modal.tsx | 2 +- src/pages/benchmark/details.tsx | 4 +- src/pages/benchmark/filters/index.tsx | 4 +- src/pages/benchmark/forms/basic.tsx | 17 +- src/pages/benchmark/forms/dataset.tsx | 4 +- src/pages/benchmark/forms/index.tsx | 6 +- src/pages/benchmark/forms/labels.tsx | 2 +- src/pages/benchmark/forms/model-instance.tsx | 2 +- src/pages/benchmark/forms/random-settings.tsx | 26 +- .../benchmark/hooks/use-benchmark-columns.tsx | 3 +- .../benchmark/hooks/use-column-settings.tsx | 11 +- src/pages/benchmark/index.tsx | 4 +- .../cluster-management/cluster-create.tsx | 2 +- .../cluster-management/cluster-detail.tsx | 2 +- .../cluster-management/cluster-modal.tsx | 2 +- src/pages/cluster-management/clusters.tsx | 18 +- .../components/add-cluster.tsx | 2 +- .../components/add-credential.tsx | 15 +- .../components/add-pool.tsx | 2 +- .../components/add-worker-command.tsx | 2 +- .../components/add-worker/index.tsx | 3 +- .../components/add-worker/select-cluster.tsx | 3 +- .../add-worker/specify-arguments.tsx | 2 +- .../components/add-worker/step-collapse.tsx | 6 +- .../components/add-worker/vendor-notes.tsx | 6 +- .../components/check-env-command.tsx | 2 +- .../components/cloud-options.tsx | 11 +- .../components/cloud-provider-form.tsx | 7 +- .../components/cluster-detail-modal.tsx | 2 +- .../components/cluster-form.tsx | 12 +- .../components/cluster-metrics.tsx | 19 +- .../components/detail/cluster-basic.tsx | 4 +- .../components/detail/cluster-system-load.tsx | 2 +- .../components/k8s-provider-form.tsx | 2 +- .../components/k8s-volume-mount.tsx | 35 +- .../components/pool-form.tsx | 41 +- .../components/pool-rows.tsx | 14 +- .../components/provider-catalog.tsx | 7 +- .../components/register-cluster-inner.tsx | 2 +- .../components/support-gpus.tsx | 2 +- .../components/trend-chart.tsx | 2 +- .../components/volumes-config.tsx | 6 +- .../components/worker-pools.tsx | 3 +- .../config/cloud-options-config.ts | 2 +- src/pages/cluster-management/config/index.ts | 2 +- .../cluster-management/config/providers.ts | 2 +- src/pages/cluster-management/credentials.tsx | 4 +- .../hooks/use-cluster-columns.tsx | 15 +- .../hooks/use-credential-columns.tsx | 4 +- .../hooks/use-pools-columns.tsx | 8 +- .../step-forms/advance-config.tsx | 6 +- .../step-forms/worker-pools-form.tsx | 2 +- .../dashboard/components/active-table.tsx | 4 +- .../components/resource-utilization.tsx | 2 +- .../dashboard/components/system-load.tsx | 11 +- .../components/usage-inner/export-data.tsx | 5 +- .../components/usage-inner/filter-bar.tsx | 9 +- .../usage-inner/request-token-inner.tsx | 8 +- .../components/usage-inner/top-user.tsx | 7 +- .../dashboard/hooks/use-add-resource.tsx | 3 +- src/pages/llmodels/catalog.tsx | 12 +- .../components/access-control-modal/form.tsx | 8 +- .../components/access-control-modal/index.tsx | 4 +- .../llmodels/components/api-access-info.tsx | 11 +- .../components/catalog/catalog-item.tsx | 5 +- .../components/catalog/catalog-list.tsx | 12 +- .../llmodels/components/compatible-alert.tsx | 2 +- .../deployment/deploy-builtin-modal.tsx | 4 +- .../components/deployment/deploy-modal.tsx | 7 +- .../components/deployment/update-modal.tsx | 4 +- .../llmodels/components/download/index.tsx | 3 +- .../components/download/target-form.tsx | 18 +- src/pages/llmodels/components/gpu-card.tsx | 3 +- .../components/incompatiable-info.tsx | 2 +- .../instance-cells/actions-cell.tsx | 10 +- .../instance-cells/cpu-offloading-cell.tsx | 3 +- .../instance-cells/distribute-info-cell.tsx | 13 +- .../downloading-status-cell.tsx | 12 +- .../instance-cells/instance-status-cell.tsx | 2 +- .../components/instance-cells/name-cell.tsx | 4 +- .../components/instance/instance-item.tsx | 4 +- .../llmodels/components/left-filters.tsx | 2 +- src/pages/llmodels/components/model-item.tsx | 20 +- .../components/model-source/hf-model-file.tsx | 4 +- .../components/model-source/hf-model-item.tsx | 2 +- .../components/model-source/model-card.tsx | 12 +- .../model-source/model-file-item.tsx | 3 +- .../components/model-source/search-input.tsx | 2 +- .../components/model-source/search-model.tsx | 51 +- .../components/model-source/search-result.tsx | 2 +- src/pages/llmodels/components/scaling.tsx | 146 -- src/pages/llmodels/components/table-list.tsx | 45 +- .../llmodels/components/view-logs-modal.tsx | 2 +- src/pages/llmodels/config/button-actions.ts | 2 +- src/pages/llmodels/deployments.tsx | 10 +- src/pages/llmodels/filters/index.tsx | 4 +- src/pages/llmodels/forms/advance-config.tsx | 8 +- .../forms/backend-parameters-list.tsx | 2 +- src/pages/llmodels/forms/backend.tsx | 3 +- src/pages/llmodels/forms/basic.tsx | 21 +- src/pages/llmodels/forms/catalog.tsx | 6 +- src/pages/llmodels/forms/custom-backend.tsx | 7 +- .../llmodels/forms/envs-override-popover.tsx | 9 +- src/pages/llmodels/forms/index.tsx | 8 +- src/pages/llmodels/forms/kv-cache.tsx | 9 +- .../llmodels/forms/local-path-source.tsx | 7 +- src/pages/llmodels/forms/mode-field.tsx | 4 +- src/pages/llmodels/forms/online-source.tsx | 18 +- src/pages/llmodels/forms/schedule-type.tsx | 18 +- .../llmodels/forms/speculative-decode.tsx | 22 +- .../llmodels/hooks/use-generic-proxy.tsx | 2 +- .../llmodels/hooks/use-models-columns.tsx | 11 +- .../llmodels/hooks/use-no-resource-result.tsx | 3 +- .../llmodels/hooks/use-view-instance-logs.ts | 2 +- src/pages/llmodels/index.tsx | 2 +- src/pages/llmodels/instance-view/index.tsx | 5 +- .../llmodels/instance-view/left-filters.tsx | 3 +- .../instance-view/use-instance-columns.tsx | 4 +- src/pages/llmodels/user-models.tsx | 23 +- .../login/components/local-user-form.tsx | 7 +- src/pages/login/components/password-form.tsx | 6 +- .../components/add-provider-modal.tsx | 2 +- .../components/provider-models.tsx | 4 +- src/pages/maas-provider/config/index.ts | 2 +- .../maas-provider/forms/advance-config.tsx | 6 +- src/pages/maas-provider/forms/api-keys.tsx | 4 +- src/pages/maas-provider/forms/basic.tsx | 15 +- src/pages/maas-provider/forms/index.tsx | 10 +- src/pages/maas-provider/forms/model-item.tsx | 3 +- .../maas-provider/forms/provider-configs.tsx | 8 +- .../maas-provider/forms/proxy-config.tsx | 11 +- .../maas-provider/forms/supported-models.tsx | 2 +- .../hooks/use-provider-columns.tsx | 4 +- .../hooks/use-query-provider-models.tsx | 10 +- src/pages/maas-provider/index.tsx | 4 +- .../components/add-route-modal.tsx | 4 +- .../model-routes/components/route-targets.tsx | 11 +- src/pages/model-routes/config/index.ts | 2 +- src/pages/model-routes/forms/basic.tsx | 9 +- src/pages/model-routes/forms/index.tsx | 12 +- src/pages/model-routes/forms/meta-data.tsx | 30 +- src/pages/model-routes/forms/targets.tsx | 11 +- .../model-routes/hooks/use-routes-columns.tsx | 8 +- src/pages/model-routes/index.tsx | 18 +- src/pages/playground/components/file-list.tsx | 3 +- .../playground/components/input-list.tsx | 2 +- .../playground/components/message-input.tsx | 6 +- .../playground/components/model-select.tsx | 2 +- .../components/multiple-chat/content-item.tsx | 2 +- .../multiple-chat/message-actions.tsx | 3 +- .../components/multiple-chat/message-body.tsx | 5 +- .../components/multiple-chat/model-item.tsx | 11 +- .../multiple-chat/think-content.tsx | 2 +- .../components/multiple-chat/think-parser.ts | 4 +- .../playground/components/params-fields.tsx | 2 +- src/pages/playground/components/thumb-img.tsx | 2 +- .../components/view-code-buttons.tsx | 2 +- .../components/view-common-code.tsx | 2 +- .../embedding/forms/advance-config.tsx | 6 +- src/pages/playground/embedding/page.tsx | 18 +- src/pages/playground/hooks/use-add-image.tsx | 6 +- src/pages/playground/hooks/use-text-image.ts | 4 +- src/pages/playground/images/create.tsx | 3 +- src/pages/playground/images/edit.tsx | 12 +- src/pages/playground/images/forms/index.tsx | 7 +- .../rerank/forms/advance-config.tsx | 6 +- src/pages/playground/rerank/page.tsx | 2 +- .../playground/speech/forms/stt-form.tsx | 3 +- .../playground/speech/forms/tts-advance.tsx | 22 +- .../playground/speech/forms/tts-form.tsx | 10 +- src/pages/playground/speech/index.tsx | 2 +- src/pages/playground/speech/stt.tsx | 14 +- src/pages/playground/speech/tts.tsx | 4 +- src/pages/playground/video/page.tsx | 3 +- src/pages/playground/view-code/image.ts | 2 +- src/pages/playground/view-code/video.ts | 2 +- src/pages/profile/components/appearance.tsx | 3 +- .../profile/components/modify-password.tsx | 13 +- src/pages/resources/components/gpus.tsx | 4 +- .../resources/components/model-files.tsx | 7 +- .../resources/components/script-install.tsx | 2 +- .../resources/components/update-labels.tsx | 7 +- .../components/worker-detail-content.tsx | 2 +- .../components/worker-detail-modal.tsx | 2 +- src/pages/resources/components/workers.tsx | 3 +- .../resources/hooks/use-files-columns.tsx | 10 +- src/pages/resources/hooks/use-gpu-columns.tsx | 5 +- .../resources/hooks/use-grafana-link.tsx | 2 +- .../resources/hooks/use-instances-columns.tsx | 20 +- .../resources/hooks/use-worker-columns.tsx | 21 +- .../hooks/use-worker-maintenance.tsx | 11 +- src/pages/users/components/add-modal.tsx | 22 +- src/pages/users/hooks/use-users-columns.tsx | 7 +- src/pages/users/index.tsx | 4 +- tsconfig.json | 15 +- 470 files changed, 1017 insertions(+), 24093 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js delete mode 100644 src/components/alert-info/block.tsx delete mode 100644 src/components/alert-info/index.tsx delete mode 100644 src/components/audio-animation/index.less delete mode 100644 src/components/audio-animation/index.tsx delete mode 100644 src/components/audio-player/audio-element.tsx delete mode 100644 src/components/audio-player/config/type.ts delete mode 100644 src/components/audio-player/index.less delete mode 100644 src/components/audio-player/index.tsx delete mode 100644 src/components/audio-player/raw-audio-player.tsx delete mode 100644 src/components/audio-player/simple-audio.tsx delete mode 100644 src/components/auto-image/index.less delete mode 100644 src/components/auto-image/index.tsx delete mode 100644 src/components/auto-image/progress-line.less delete mode 100644 src/components/auto-image/single-image.less delete mode 100644 src/components/auto-image/single-image.tsx delete mode 100644 src/components/auto-tooltip/index.tsx delete mode 100644 src/components/auto-tooltip/title-tip.tsx delete mode 100644 src/components/bibtex-viewer/index.tsx delete mode 100644 src/components/buttons/more.tsx delete mode 100644 src/components/card-wrapper/index.tsx delete mode 100644 src/components/card-wrapper/simple-card.tsx delete mode 100644 src/components/check-buttons/index.tsx delete mode 100644 src/components/collapse-container/headless-collapse.tsx delete mode 100644 src/components/collapse-container/index.tsx delete mode 100644 src/components/content-wrapper/index.less delete mode 100644 src/components/content-wrapper/index.tsx delete mode 100644 src/components/copy-button/index.tsx delete mode 100644 src/components/delete-modal/index.tsx delete mode 100644 src/components/divider-line/index.less delete mode 100644 src/components/divider-line/index.tsx delete mode 100644 src/components/drop-down-actions/index.tsx delete mode 100644 src/components/drop-down-buttons/index.less delete mode 100644 src/components/drop-down-buttons/index.tsx delete mode 100644 src/components/dynamic-form/components/field-item.tsx delete mode 100644 src/components/dynamic-form/components/form-widget.tsx delete mode 100644 src/components/dynamic-form/components/list-map.tsx delete mode 100644 src/components/dynamic-form/config/types.ts delete mode 100644 src/components/dynamic-form/hooks/use-parse-fields.ts delete mode 100644 src/components/dynamic-form/hooks/use-validate-fields.ts delete mode 100644 src/components/dynamic-form/index.tsx delete mode 100644 src/components/echarts/bar-chart.tsx delete mode 100644 src/components/echarts/chart-tooltip.tsx delete mode 100644 src/components/echarts/chart.tsx delete mode 100644 src/components/echarts/config.ts delete mode 100644 src/components/echarts/gauge.tsx delete mode 100644 src/components/echarts/h-bar.tsx delete mode 100644 src/components/echarts/index.ts delete mode 100644 src/components/echarts/line-chart.tsx delete mode 100644 src/components/echarts/mix-line-bar.tsx delete mode 100644 src/components/echarts/scatter.tsx delete mode 100644 src/components/echarts/types.ts delete mode 100644 src/components/editor-wrap/index.less delete mode 100644 src/components/editor-wrap/index.tsx delete mode 100644 src/components/editor/index.tsx delete mode 100644 src/components/empty-data/index.tsx delete mode 100644 src/components/form-buttons/index.tsx delete mode 100644 src/components/highlight-code/code-viewer-dark.tsx delete mode 100644 src/components/highlight-code/code-viewer-light.tsx delete mode 100644 src/components/highlight-code/code-viewer.tsx delete mode 100644 src/components/highlight-code/index.tsx delete mode 100644 src/components/highlight-code/styles/dark.less delete mode 100644 src/components/highlight-code/styles/index.less delete mode 100644 src/components/highlight-code/styles/light.less delete mode 100644 src/components/highlight-code/utils.ts delete mode 100644 src/components/icon-font/iconfont/iconfont.css delete mode 100644 src/components/icon-font/iconfont/iconfont.js delete mode 100644 src/components/icon-font/iconfont/iconfont.json delete mode 100644 src/components/icon-font/iconfont/iconfont.ttf delete mode 100644 src/components/icon-font/iconfont/iconfont.woff delete mode 100644 src/components/icon-font/iconfont/iconfont.woff2 delete mode 100644 src/components/icon-font/icons.ts delete mode 100644 src/components/icon-font/index.tsx delete mode 100644 src/components/image-editor/extract-image-colors.ts delete mode 100644 src/components/image-editor/hooks/use-drawing.ts delete mode 100644 src/components/image-editor/hooks/use-zoom.ts delete mode 100644 src/components/image-editor/index.less delete mode 100644 src/components/image-editor/index.tsx delete mode 100644 src/components/image-editor/invert-worker.ts delete mode 100644 src/components/image-editor/offscreen-worker.ts delete mode 100644 src/components/image-editor/tools-bar.tsx delete mode 100644 src/components/label-cell/index.tsx delete mode 100644 src/components/label-selector/autocomplete-item.tsx delete mode 100644 src/components/label-selector/context.ts delete mode 100644 src/components/label-selector/index.tsx delete mode 100644 src/components/label-selector/inner.tsx delete mode 100644 src/components/label-selector/label-item.tsx delete mode 100644 src/components/label-selector/styles/label-item.less delete mode 100644 src/components/label-selector/wrapper.tsx delete mode 100644 src/components/list-input/hint-input.tsx delete mode 100644 src/components/list-input/index.tsx delete mode 100644 src/components/list-input/list-item.tsx delete mode 100644 src/components/list-input/style/list-item.less delete mode 100644 src/components/list-input/styles/list-item.less delete mode 100644 src/components/logs-viewer/config.ts delete mode 100644 src/components/logs-viewer/logs-list.tsx delete mode 100644 src/components/logs-viewer/logs-pagination.tsx delete mode 100644 src/components/logs-viewer/parse-worker.ts delete mode 100644 src/components/logs-viewer/styles/index.less delete mode 100644 src/components/logs-viewer/styles/logs-list.less delete mode 100644 src/components/logs-viewer/styles/pagination.less delete mode 100644 src/components/logs-viewer/styles/xterm-viewer.less delete mode 100644 src/components/logs-viewer/use-logs-pagination.ts delete mode 100644 src/components/logs-viewer/use-size.ts delete mode 100644 src/components/logs-viewer/virtual-log-list.tsx delete mode 100644 src/components/logs-viewer/xterm-viewer.tsx delete mode 100644 src/components/markdown-viewer/code-viewer.tsx delete mode 100644 src/components/markdown-viewer/full-markdown.tsx delete mode 100644 src/components/markdown-viewer/index.less delete mode 100644 src/components/markdown-viewer/index.tsx delete mode 100644 src/components/markdown-viewer/utils.ts delete mode 100644 src/components/metadata-list/index.tsx delete mode 100644 src/components/modal-footer/index.tsx delete mode 100644 src/components/overlay-scroller/index.module.less delete mode 100644 src/components/overlay-scroller/index.tsx delete mode 100644 src/components/page-tools/filters-button.less delete mode 100644 src/components/page-tools/index.less delete mode 100644 src/components/page-tools/index.tsx delete mode 100644 src/components/popover/index.less delete mode 100644 src/components/popover/index.tsx delete mode 100644 src/components/progress-bar/index.tsx delete mode 100644 src/components/radio-buttons/index.less delete mode 100644 src/components/radio-buttons/index.tsx delete mode 100644 src/components/resize-container/index.tsx delete mode 100644 src/components/resize-container/use-responsive.ts delete mode 100644 src/components/scroller-modal/gs-drawer.tsx delete mode 100644 src/components/scroller-modal/index.tsx delete mode 100644 src/components/scroller-modal/use-scroller-context.ts delete mode 100644 src/components/seal-form/auto-complete.tsx delete mode 100644 src/components/seal-form/base/select.tsx delete mode 100644 src/components/seal-form/base/styles.less delete mode 100644 src/components/seal-form/checkbox-field.tsx delete mode 100644 src/components/seal-form/components/label-info.less delete mode 100644 src/components/seal-form/components/label-info.tsx delete mode 100644 src/components/seal-form/components/not-found-content.tsx delete mode 100644 src/components/seal-form/config/components.ts delete mode 100644 src/components/seal-form/config/index.ts delete mode 100644 src/components/seal-form/field-component.tsx delete mode 100644 src/components/seal-form/hooks/use-select-render.tsx delete mode 100644 src/components/seal-form/input-number.tsx delete mode 100644 src/components/seal-form/input-search.tsx delete mode 100644 src/components/seal-form/multiple-select.tsx delete mode 100644 src/components/seal-form/password.tsx delete mode 100644 src/components/seal-form/row-textarea.tsx delete mode 100644 src/components/seal-form/seal-cascader.tsx delete mode 100644 src/components/seal-form/seal-checkbox.tsx delete mode 100644 src/components/seal-form/seal-input.tsx delete mode 100644 src/components/seal-form/seal-select.tsx delete mode 100644 src/components/seal-form/seal-slider.tsx delete mode 100644 src/components/seal-form/seal-switch.tsx delete mode 100644 src/components/seal-form/seal-textarea.tsx delete mode 100644 src/components/seal-form/simple-select.tsx delete mode 100644 src/components/seal-form/styles/row-textarea.less delete mode 100644 src/components/seal-form/styles/slider.less delete mode 100644 src/components/seal-form/switch-input.tsx delete mode 100644 src/components/seal-form/types.ts delete mode 100644 src/components/seal-form/wrapper/auto-complete-label.ts delete mode 100644 src/components/seal-form/wrapper/index.tsx delete mode 100644 src/components/seal-form/wrapper/input.ts delete mode 100644 src/components/seal-form/wrapper/select.ts delete mode 100644 src/components/seal-form/wrapper/slider.ts delete mode 100644 src/components/seal-table/components/cell-content.tsx delete mode 100644 src/components/seal-table/components/header-prefix.tsx delete mode 100644 src/components/seal-table/components/header.tsx delete mode 100644 src/components/seal-table/components/layout.tsx delete mode 100644 src/components/seal-table/components/pagination.tsx delete mode 100644 src/components/seal-table/components/row-children.tsx delete mode 100644 src/components/seal-table/components/row-prefix.tsx delete mode 100644 src/components/seal-table/components/table-body.tsx delete mode 100644 src/components/seal-table/components/table-cell.tsx delete mode 100644 src/components/seal-table/components/table-header.tsx delete mode 100644 src/components/seal-table/components/table-row.tsx delete mode 100644 src/components/seal-table/components/table-skeleton.tsx delete mode 100644 src/components/seal-table/index.tsx delete mode 100644 src/components/seal-table/row-context.ts delete mode 100644 src/components/seal-table/styles/cell.less delete mode 100644 src/components/seal-table/styles/header.less delete mode 100644 src/components/seal-table/styles/index.less delete mode 100644 src/components/seal-table/styles/row-children.less delete mode 100644 src/components/seal-table/styles/skeleton.less delete mode 100644 src/components/seal-table/table-context.ts delete mode 100644 src/components/seal-table/types.ts delete mode 100644 src/components/seal-table/use-sorter.ts delete mode 100644 src/components/segment-line/index.tsx delete mode 100644 src/components/short-cuts/index.less delete mode 100644 src/components/short-cuts/index.tsx delete mode 100644 src/components/short-cuts/keymap.ts delete mode 100644 src/components/simple-overlay/index.tsx delete mode 100644 src/components/simple-table/cell.tsx delete mode 100644 src/components/simple-table/header.tsx delete mode 100644 src/components/simple-table/index.less delete mode 100644 src/components/simple-table/index.tsx delete mode 100644 src/components/simple-table/info-column.tsx delete mode 100644 src/components/simple-table/row.tsx delete mode 100644 src/components/speech-content/audio-player.tsx delete mode 100644 src/components/speech-content/index.tsx delete mode 100644 src/components/speech-content/speech-item.tsx delete mode 100644 src/components/speech-content/styles/index.less delete mode 100644 src/components/speech-content/styles/slider-progress.less delete mode 100644 src/components/status-icon/error.tsx delete mode 100644 src/components/status-tag/copy-btn.less delete mode 100644 src/components/status-tag/index.less delete mode 100644 src/components/status-tag/index.tsx delete mode 100644 src/components/tags-wrapper/index.less delete mode 100644 src/components/tags-wrapper/index.tsx delete mode 100644 src/components/tags-wrapper/more-dropdown.tsx delete mode 100644 src/components/tags-wrapper/theme-tag.tsx delete mode 100644 src/components/templates/card-skelton.tsx delete mode 100644 src/components/templates/card.tsx delete mode 100644 src/components/tooltip-list/index.tsx delete mode 100644 src/components/transition/index.less delete mode 100644 src/components/transition/index.tsx delete mode 100644 src/components/type-word-effect/index.tsx delete mode 100644 src/components/upload-audio/index.tsx delete mode 100644 src/components/util-bar/index.less delete mode 100644 src/components/util-bar/index.tsx delete mode 100644 src/components/x-terminal/index.tsx delete mode 100644 src/pages/_components/command-viewer/index.tsx delete mode 100644 src/pages/_components/select-panel/index.tsx delete mode 100644 src/pages/_components/select-panel/list.tsx delete mode 100644 src/pages/_components/select-panel/selected-list.tsx delete mode 100644 src/pages/llmodels/components/scaling.tsx diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index bf4091e3..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/components/icon-font/iconfont/iconfont.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 555e77a0..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - extends: require.resolve('@umijs/max/eslint'), - rules: { - 'react/no-unstable-nested-components': 1, - 'no-unused-vars': 'off', - 'no-undef': 'error', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/class-name-casing': 'off' - }, - globals: { - Global: 'readonly', - React: 'readonly', - JSX: 'readonly' - }, - ignorePatterns: ['public/static/'] -}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..768677e4 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,77 @@ +import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; +import importPlugin from 'eslint-plugin-import'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import unusedImports from 'eslint-plugin-unused-imports'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default defineConfig([ + globalIgnores([ + 'public/static/', + 'dist', + 'src/.umi/', + 'src/.umi-production/', + 'src/.umi-test/' + ]), + { + files: ['**/*.{ts,tsx,js,jsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + prettier + ], + plugins: { + react: reactPlugin, + import: importPlugin, + 'unused-imports': unusedImports + }, + settings: { + react: { + version: 'detect' + }, + 'import/resolver': { + node: true, + typescript: true + } + }, + languageOptions: { + ecmaVersion: 2020, + globals: { + ...globals.browser, + ...globals.node, + Global: 'readonly', + React: 'readonly', + JSX: 'readonly' + } + }, + rules: { + 'react/no-unstable-nested-components': 'warn', + 'no-unused-vars': 'off', + 'no-undef': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unnecessary-type-constraint': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': 'off', + 'import/no-unresolved': 'off', + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/preserve-manual-memoization': 'off', + 'react-hooks/set-state-in-effect': 'off', + 'react-hooks/refs': 'off', + 'react-hooks/use-memo': 'off', + 'react-hooks/immutability': 'off', + 'no-unsafe-optional-chaining': 'off', + 'no-empty': 'off', + 'no-constant-condition': 'off', + 'no-prototype-builtins': 'off', + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0, maxBOF: 0 }] + } + } +]); diff --git a/package.json b/package.json index 6a4a9f3b..803af5d4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@ant-design/icons": "^6.1.0", "@ant-design/pro-components": "3.1.0-0", "@braintree/sanitize-url": "^7.1.1", + "@gpustack/core-ui": "workspace:*", "@huggingface/gguf": "^0.1.7", "@huggingface/hub": "^0.15.1", "@huggingface/tasks": "^0.11.6", @@ -86,6 +87,7 @@ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { + "@eslint/js": "^9.39.4", "@types/node": "^25.0.3", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", @@ -96,8 +98,14 @@ "case-sensitive-paths-webpack-plugin": "^2.4.0", "compression-webpack-plugin": "^11.1.0", "css-loader": "^7.1.2", - "eslint": "^8.56.0", - "eslint-plugin-unused-imports": "^3.2.0", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-unused-imports": "^4.4.1", + "globals": "^17.4.0", "extract-css-loader": "^0.0.1", "file-loader": "^6.2.0", "husky": "^9.0.11", @@ -111,6 +119,7 @@ "prettier-plugin-two-style-order": "^1.0.1", "tsx": "^4.19.3", "typescript": "^5.4.5", + "typescript-eslint": "^8.58.0", "url-loader": "^4.1.1", "webpack-bundle-analyzer": "^4.10.2", "worker-loader": "^3.0.8" diff --git a/src/components/alert-info/block.tsx b/src/components/alert-info/block.tsx deleted file mode 100644 index e5c15584..00000000 --- a/src/components/alert-info/block.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { - CheckCircleFilled, - LoadingOutlined, - WarningFilled -} from '@ant-design/icons'; -import { Typography } from 'antd'; -import { createStyles } from 'antd-style'; -import classNames from 'classnames'; -import React from 'react'; -import styled from 'styled-components'; -import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller'; -interface AlertInfoProps { - type: Global.MessageType; - message: React.ReactNode; - rows?: number; - icon?: React.ReactNode; - ellipsis?: boolean; - style?: React.CSSProperties; - contentStyle?: React.CSSProperties; - title?: React.ReactNode; - maxHeight?: number; - overlayScrollerProps?: OverlayScrollerOptions; -} - -const useStyles = createStyles(({ token, css }) => { - return { - alertBlockInfo: css` - padding-block: 6px; - padding-inline: 10px 16px; - position: relative; - padding-left: 32px; - text-align: left; - border-radius: ${token.borderRadiusLG}px; - margin: 0; - border: 1px solid transparent; - .ant-typography { - margin-bottom: 0; - } - - &.danger { - border-color: ${token.colorErrorBorder}; - background-color: ${token.colorErrorBg}; - } - - &.warning { - border-color: ${token.colorWarningBorder}; - background-color: ${token.colorWarningBg}; - } - - &.transition { - color: ${token.geekblue7}; - background: ${token.geekblue1}; - border-color: ${token.geekblue3}; - } - - &.success { - border: 1px solid ${token.colorSuccess}; - color: ${token.colorSuccessText}; - background: ${token.colorSuccessBg}; - - .content.success { - font-weight: var(--font-weight-normal); - } - } - .title { - position: absolute; - left: 0; - top: 0; - display: flex; - height: 32px; - padding: 5px 10px; - border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0; - - .info-icon { - &.danger { - color: ${token.colorErrorText}; - } - - &.warning { - color: ${token.colorWarningText}; - } - - &.transition { - color: ${token.geekblue7}; - } - - &.success { - color: ${token.colorSuccessText}; - } - } - - .text { - font-weight: var(--font-weight-bold); - } - } - ` - }; -}); - -const TitleWrapper = styled.div` - font-weight: 700; - color: var(--ant-color-text); -`; - -const ContentWrapper = styled.div<{ $hasTitle: boolean }>` - word-break: break-word; - color: ${(props) => - props.$hasTitle - ? 'var(--ant-color-text-secondary)' - : 'var(--ant-color-text)'}; - font-weight: var(--font-weight-500); - white-space: pre-line; -`; - -const AlertInfo: React.FC = (props) => { - const { - message, - type, - rows = 1, - ellipsis, - style, - title, - contentStyle, - icon, - maxHeight = 86, - overlayScrollerProps = {} - } = props; - const { styles } = useStyles(); - - const renderIcon = () => { - if (type === 'transition') { - return ; - } - if (type === 'success') { - return ; - } - return ; - }; - - return ( - <> - {message ? ( -
- -
- - {icon ?? renderIcon()} - -
- {title && ( - {title} - )} - - - {message} - - -
-
- ) : null} - - ); -}; - -export default AlertInfo; diff --git a/src/components/alert-info/index.tsx b/src/components/alert-info/index.tsx deleted file mode 100644 index 644a7a49..00000000 --- a/src/components/alert-info/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { WarningOutlined } from '@ant-design/icons'; -import { Typography } from 'antd'; -import React from 'react'; - -interface AlertInfoProps { - type: 'danger' | 'warning'; - message: string; - rows?: number; - icon?: React.ReactNode; - ellipsis?: boolean; - style?: React.CSSProperties; -} - -const AlertInfo: React.FC = (props) => { - const { message, type, rows = 1, ellipsis, style } = props; - - return ( - <> - {message ? ( - - - {message} - - ) : null} - - ); -}; - -export default AlertInfo; diff --git a/src/components/audio-animation/index.less b/src/components/audio-animation/index.less deleted file mode 100644 index d4828cd2..00000000 --- a/src/components/audio-animation/index.less +++ /dev/null @@ -1,19 +0,0 @@ -.canvas-wrap { - display: flex; - justify-content: center; - align-items: center; - text-align: center; - width: 100%; - - canvas { - display: block; - width: 100%; - image-rendering: crisp-edges; - } -} - -.scroller-wrapper { - width: 100%; - height: 100%; - overflow: hidden; -} diff --git a/src/components/audio-animation/index.tsx b/src/components/audio-animation/index.tsx deleted file mode 100644 index 2a297c68..00000000 --- a/src/components/audio-animation/index.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import useResizeObserver from '@/components/logs-viewer/use-size'; -import React, { useEffect, useState } from 'react'; -import './index.less'; - -interface AudioAnimationProps { - width: number; - height: number; - maxWidth?: number; - scaleFactor?: number; - maxBarCount?: number; - amplitude?: number; - fixedHeight?: boolean; - analyserData: { - data: Uint8Array; - analyser: any; - }; -} - -const AudioAnimation: React.FC = (props) => { - const { - scaleFactor = 1.2, - maxBarCount = 128, - amplitude = 40, - maxWidth, - fixedHeight = true, - analyserData, - width: initialWidth, - height: initialHeight - } = props; - const canvasRef = React.useRef(null); - const animationId = React.useRef(0); - const isScaled = React.useRef(false); - const oscillationOffset = React.useRef(0); - const direction = React.useRef(1); - const scrollerRef = React.useRef(null); - const [width, setWidth] = useState(initialWidth); - const [height, setHeight] = useState(initialHeight); - const containerRef = React.useRef(null); - - const size = useResizeObserver(scrollerRef); - - const calculateJitter = ( - i: number, - timestamp: number, - baseHeight: number, - minJitter: number, - jitterAmplitude: number - ) => { - // - const jitterFactor = Math.sin(timestamp / 200 + i) * 0.5 + 0.5; - const jitter = - minJitter + - jitterFactor * (jitterAmplitude - minJitter) * (baseHeight / maxBarCount); - return jitter; - }; - - const startAudioVisualization = () => { - if (!canvasRef.current || !analyserData.data?.length) return; - - const canvas = canvasRef.current; - const canvasCtx = canvas.getContext('2d'); - if (!canvasCtx) return; - - const WIDTH = (canvas.width = width * 2); - const HEIGHT = (canvas.height = height * 2); - - if (!isScaled.current) { - canvasCtx.scale(2, 2); - isScaled.current = true; - } - - const barWidth = 4; - const barSpacing = 6; - const centerLine = Math.floor(HEIGHT / 2); - - const jitterAmplitude = amplitude; - const minJitter = 10; - - let lastFrameTime = 0; - - const gradient = canvasCtx.createLinearGradient(0, 0, 0, HEIGHT); - gradient.addColorStop(0, '#007BFF'); - gradient.addColorStop(1, '#0069DA'); - canvasCtx.fillStyle = gradient; - - const draw = (timestamp: number) => { - const elapsed = timestamp - lastFrameTime; - if (elapsed < 16) { - animationId.current = requestAnimationFrame(draw); - return; - } - lastFrameTime = timestamp; - - analyserData.analyser?.current?.getByteFrequencyData(analyserData.data); - canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); - - const barCount = Math.min(maxBarCount, analyserData.data.length); - const totalWidth = barCount * (barWidth + barSpacing) - barSpacing; - let x = WIDTH / 2 - totalWidth / 2 + oscillationOffset.current; - - oscillationOffset.current += direction.current; - if (Math.abs(oscillationOffset.current) > 20) { - direction.current *= -1; - } - - for (let i = 0; i < barCount; i++) { - const baseHeight = Math.floor(analyserData.data[i] / 2) * scaleFactor; - - const jitter = calculateJitter( - i, - timestamp, - baseHeight, - minJitter, - jitterAmplitude - ); - const barHeight = baseHeight + Math.round(jitter); - - const topY = Math.round(centerLine - barHeight / 2); - const bottomY = Math.round(centerLine + barHeight / 2); - - canvasCtx.beginPath(); - canvasCtx.moveTo(x, bottomY); - canvasCtx.lineTo(x, topY + 2); - canvasCtx.arcTo(x + barWidth, topY + 2, x + barWidth, bottomY, 2); - canvasCtx.lineTo(x + barWidth, bottomY); - canvasCtx.closePath(); - canvasCtx.fill(); - - x += barWidth + barSpacing; - } - - animationId.current = requestAnimationFrame(draw); - }; - - draw(performance.now()); - }; - - React.useEffect(() => { - if (size) { - if (maxWidth) { - setWidth(Math.min(size.width, maxWidth)); - } else { - setWidth(size?.width || 0); - } - if (!fixedHeight) { - setHeight(size?.height || 0); - } - } - }, [size, maxWidth]); - - useEffect(() => { - if (!canvasRef.current) return; - const clearCanvas = () => { - if (canvasRef.current) { - const ctx = canvasRef.current.getContext('2d'); - if (ctx) ctx.clearRect(0, 0, width * 2, height * 2); - } - }; - - if (!analyserData.data?.length || !analyserData.analyser?.current) { - clearCanvas(); - cancelAnimationFrame(animationId.current); - animationId.current = 0; - return; - } - - startAudioVisualization(); - - return () => { - cancelAnimationFrame(animationId.current); - clearCanvas(); - }; - }, [analyserData, width, height]); - - return ( -
-
- -
-
- ); -}; - -export default AudioAnimation; diff --git a/src/components/audio-player/audio-element.tsx b/src/components/audio-player/audio-element.tsx deleted file mode 100644 index 9601b9eb..00000000 --- a/src/components/audio-player/audio-element.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -const AudioWrapper = styled.div` - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: flex-start; -`; - -const AudioElement: React.FC = (props) => { - return ( -
- - - -
- ); -}; - -export default AudioElement; diff --git a/src/components/audio-player/config/type.ts b/src/components/audio-player/config/type.ts deleted file mode 100644 index 210fb88c..00000000 --- a/src/components/audio-player/config/type.ts +++ /dev/null @@ -1,39 +0,0 @@ -export type AudioEvent = - | 'play' - | 'playing' - | 'pause' - | 'timeupdate' - | 'ended' - | 'loadedmetadata' - | 'audioprocess' - | 'canplay' - | 'ended' - | 'loadeddata' - | 'seeked' - | 'seeking' - | 'volumechange'; - -export interface AudioPlayerProps { - controls?: boolean; - autoplay?: boolean; - url: string; - speed?: number; - ref?: any; - height?: number; - width?: number; - duration?: number; - onPlay?: () => void; - onPlaying?: () => void; - onPause?: () => void; - onTimeUpdate?: () => void; - onEnded?: () => void; - onLoadedMetadata?: (duration: number) => void; - onAudioProcess?: (current: number) => void; - onCanPlay?: () => void; - onLoadedData?: () => void; - onSeeked?: () => void; - onSeeking?: () => void; - onVolumeChange?: () => void; - onReady?: (duration: number) => void; - onAnalyse?: (analyseData: any, frequencyBinCount: any) => void; -} diff --git a/src/components/audio-player/index.less b/src/components/audio-player/index.less deleted file mode 100644 index 25668b32..00000000 --- a/src/components/audio-player/index.less +++ /dev/null @@ -1,103 +0,0 @@ -.player-wrap { - width: 100%; - display: flex; - // background-color: var(--ant-color-fill-quaternary); - border-radius: 6px; - - .player-ui { - padding: 8px 16px; - flex: 1; - display: flex; - justify-content: flex-start; - align-items: center; - } - - .controls { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - } - - .play-btn { - margin-inline: 30px; - height: 22px; - width: 22px; - - .ant-btn { - height: 22px; - width: 22px; - } - } - - .backward, - .forward { - background: none !important; - } - - .slider { - display: flex; - justify-content: center; - align-items: center; - - .slider-inner { - flex: 1; - } - } - - .play-content { - display: flex; - justify-content: center; - align-items: center; - flex: 1; - } - - .time { - width: 52px; - text-align: right; - - &.current { - text-align: left; - } - } - - .progress-bar { - margin-inline: 10px; - flex: 1; - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - - .slider { - width: 100%; - } - - .file-name { - margin-bottom: 6px; - line-height: 20px; - height: 20px; - display: flex; - align-items: center; - align-self: center; - justify-content: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - - .ant-slider-horizontal { - margin-block: 5px; - } - } - - .speaker { - margin-left: 10px; - position: relative; - - .volume-slider { - position: absolute; - bottom: 30px; - } - } -} diff --git a/src/components/audio-player/index.tsx b/src/components/audio-player/index.tsx deleted file mode 100644 index ae315ff8..00000000 --- a/src/components/audio-player/index.tsx +++ /dev/null @@ -1,311 +0,0 @@ -import { formatTime } from '@/utils/index'; -import { - FastBackwardOutlined, - FastForwardOutlined, - PauseCircleFilled, - PlayCircleFilled -} from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Slider, Tooltip } from 'antd'; -import { round } from 'lodash'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle -} from 'react'; -import './index.less'; - -interface AudioPlayerProps { - autoplay?: boolean; - url: string; - speed?: number; - ref?: any; - name: string; - height?: number; - width?: number; - duration?: number; - extra?: React.ReactNode; -} - -const speedOptions = [ - { label: '1x', value: 1 }, - { label: '2x', value: 2 }, - { label: '3x', value: 3 }, - { label: '4x', value: 4 } -]; - -const speedConfig = { - min: 0.5, - max: 2, - step: 0.25 -}; - -const AudioPlayer: React.FC = forwardRef((props, ref) => { - const intl = useIntl(); - const { autoplay = false, speed: defaultSpeed = 1, extra } = props; - const audioRef = React.useRef(null); - const [audioState, setAudioState] = React.useState<{ - currentTime: number; - duration: number; - }>({ - currentTime: 0, - duration: 0 - }); - const [playOn, setPlayOn] = React.useState(false); - const [speakerOn, setSpeakerOn] = React.useState(false); - const [volume, setVolume] = React.useState(1); - const [speed, setSpeed] = React.useState(defaultSpeed); - const timer = React.useRef(null); - - useImperativeHandle(ref, () => ({ - play: () => { - audioRef.current?.play(); - }, - pause: () => { - audioRef.current?.pause(); - } - })); - const handleShowVolume = useCallback(() => { - setSpeakerOn(!speakerOn); - }, [speakerOn]); - - const handleSeepdChange = useCallback((value: number | string) => { - setSpeed(value as number); - audioRef.current!.playbackRate = value as number; - }, []); - - const handleAudioOnPlay = useCallback(() => { - console.log('audio play'); - timer.current = setInterval(() => { - setAudioState((prestate) => { - return { - currentTime: Math.ceil(audioRef.current?.currentTime || 0), - duration: - prestate.duration || Math.ceil(audioRef.current?.duration || 0) - }; - }); - - if (audioRef.current?.paused || audioRef.current?.ended) { - clearInterval(timer.current); - setPlayOn(false); - setAudioState((prestate: any) => { - return { - currentTime: audioRef.current?.ended ? 0 : prestate.currentTime, - duration: prestate.duration - }; - }); - } - }, 500); - }, []); - - const handlePlay = useCallback(() => { - setPlayOn(!playOn); - if (playOn) { - audioRef.current?.pause(); - } else { - audioRef.current?.play(); - } - }, [playOn]); - - const handleFormatVolume = (val?: number) => { - if (val === undefined) { - return `${round(volume * 100)}%`; - } - return `${round(val * 100)}%`; - }; - - const handleVolumeChange = useCallback((value: number) => { - audioRef.current!.volume = round(value, 2); - setVolume(round(value, 2)); - }, []); - - const initPlayerConfig = () => { - if (audioRef.current) { - audioRef.current!.volume = volume; - audioRef.current!.playbackRate = speed; - } - }; - - const handleLoadedMetadata = useCallback( - (data: any) => { - const duration = Math.ceil(audioRef.current?.duration || 0); - setAudioState({ - currentTime: 0, - duration: - duration && duration !== Infinity ? duration : props.duration || 0 - }); - setPlayOn(autoplay); - }, - [autoplay, props.duration] - ); - - const handleCurrentChange = useCallback((val: number) => { - audioRef.current!.currentTime = val; - setAudioState((prestate) => { - return { - currentTime: val, - duration: prestate.duration - }; - }); - }, []); - - const handleReduceSpeed = () => { - setSpeed((pre) => { - if (pre - speedConfig.step < speedConfig.min) { - return speedConfig.min; - } - const next = pre - speedConfig.step; - audioRef.current!.playbackRate = next; - return next; - }); - }; - - const handleAddSpeed = () => { - setSpeed((pre) => { - if (pre + speedConfig.step > speedConfig.max) { - return speedConfig.max; - } - const next = pre + speedConfig.step; - audioRef.current!.playbackRate = next; - return next; - }); - }; - - const handleOnLoad = (e: any) => { - console.log('onload', e); - }; - - const onDownload = useCallback(() => { - const url = props.url || ''; - const filename = props.name; - - const link = document.createElement('a'); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - link.remove(); - }, [props.url, props.name]); - - useEffect(() => { - if (audioRef.current) { - initPlayerConfig(); - } - }, [audioRef.current]); - - useEffect(() => { - return () => { - clearInterval(timer.current); - }; - }, []); - - return ( -
-
-
-
- {props.name} -
- {/* - {' '} - {formatTime(audioState.currentTime)} - */} -
- -
- {/* {formatTime(audioState.duration)} */} -
-
-
- - {' '} - {formatTime(audioState.currentTime)} - - - - - - - - - - - {formatTime(audioState.duration)} -
- {extra} -
-
-
-
- -
- ); -}); - -export default React.memo(AudioPlayer); diff --git a/src/components/audio-player/raw-audio-player.tsx b/src/components/audio-player/raw-audio-player.tsx deleted file mode 100644 index 69a1fa76..00000000 --- a/src/components/audio-player/raw-audio-player.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef -} from 'react'; -import { AudioPlayerProps } from './config/type'; - -const RawAudioPlayer: React.FC = forwardRef((props, ref) => { - const { autoplay = false } = props; - const audioRef = React.useRef(null); - - // =================== audio context ====================== - const audioContext = useRef(null); - const analyser = useRef(null); - const dataArray = useRef(null); - // ======================================================== - - const initAudioContext = useCallback(() => { - audioContext.current = new ( - window.AudioContext || (window as any).webkitAudioContext - )(); - - analyser.current = audioContext.current.createAnalyser(); - analyser.current.fftSize = 512; - dataArray.current = new Uint8Array(analyser.current.frequencyBinCount); - }, []); - - const generateVisualData = useCallback(() => { - const source = audioContext.current.createMediaElementSource( - audioRef.current - ); - source.connect(analyser.current); - analyser.current.connect(audioContext.current.destination); - }, []); - - const initEnvents = () => { - if (!audioRef.current) { - return; - } - - audioRef.current.addEventListener('complete', () => {}); - - audioRef.current.addEventListener('play', () => { - props.onAnalyse?.(dataArray.current, analyser); - props.onPlay?.(); - }); - - audioRef.current.addEventListener('pause', () => { - props.onAnalyse?.(dataArray.current, analyser); - props.onPause?.(); - }); - - audioRef.current.addEventListener('timeupdate', () => { - const current = audioRef.current?.currentTime || 0; - props.onTimeUpdate?.(); - props.onAudioProcess?.(current); - }); - - audioRef.current.addEventListener('ended', () => { - props.onEnded?.(); - }); - // add all other events - - audioRef.current.addEventListener('canplay', () => { - props.onCanPlay?.(); - }); - - audioRef.current.addEventListener('loadeddata', () => { - initEnvents(); - if (!audioContext.current) { - initAudioContext(); - generateVisualData(); - } - props.onLoadedData?.(); - }); - - audioRef.current.addEventListener('seeked', () => { - props.onSeeked?.(); - }); - - audioRef.current.addEventListener('seeking', () => { - props.onSeeking?.(); - }); - - audioRef.current.addEventListener('volumechange', () => { - props.onVolumeChange?.(); - }); - - audioRef.current.addEventListener('playing', () => { - props.onPlaying?.(); - }); - - audioRef.current.addEventListener('loadedmetadata', () => { - const duration = audioRef.current?.duration || 0; - props.onLoadedMetadata?.(duration); - props.onReady?.(duration); - }); - - audioRef.current.addEventListener('ended', () => { - props.onEnded?.(); - }); - - audioRef.current.addEventListener('loadeddata', () => { - props.onLoadedData?.(); - }); - }; - - useImperativeHandle(ref, () => ({ - play: () => { - if (audioRef.current) { - // If playback has ended, reset to beginning - if (audioRef.current.currentTime >= audioRef.current.duration) { - audioRef.current.currentTime = 0; - } - audioRef.current.play(); - } - }, - pause: () => { - audioRef.current?.pause(); - }, - seekTo: (ratio: number) => { - if (audioRef.current && audioRef.current.duration) { - audioRef.current.currentTime = ratio * audioRef.current.duration; - } - }, - // Compatibility layer for wavesurfer interface - wavesurfer: { - current: { - play: () => { - if (audioRef.current) { - // If playback has ended, reset to beginning - if (audioRef.current.currentTime >= audioRef.current.duration) { - audioRef.current.currentTime = 0; - } - return audioRef.current.play(); - } - return Promise.resolve(); - }, - isPlaying: () => { - return audioRef.current ? !audioRef.current?.paused : false; - } - } - } - })); - - useEffect(() => { - if (audioRef.current) { - initEnvents(); - } - return () => { - if (audioContext.current) { - audioContext.current.close(); - } - // remove all events - - audioRef.current?.removeEventListener('play', () => {}); - audioRef.current?.removeEventListener('pause', () => {}); - audioRef.current?.removeEventListener('timeupdate', () => {}); - audioRef.current?.removeEventListener('ended', () => {}); - audioRef.current?.removeEventListener('canplay', () => {}); - audioRef.current?.removeEventListener('loadeddata', () => {}); - audioRef.current?.removeEventListener('seeked', () => {}); - audioRef.current?.removeEventListener('seeking', () => {}); - audioRef.current?.removeEventListener('volumechange', () => {}); - audioRef.current?.removeEventListener('playing', () => {}); - audioRef.current?.removeEventListener('loadedmetadata', () => {}); - audioRef.current?.removeEventListener('ended', () => {}); - audioRef.current?.removeEventListener('loadeddata', () => {}); - }; - }, [audioRef.current]); - - // Reload audio when URL changes - useEffect(() => { - if (audioRef.current && props.url) { - audioRef.current.load(); - } - }, [props.url]); - - return ( - - ); -}); - -export default RawAudioPlayer; diff --git a/src/components/audio-player/simple-audio.tsx b/src/components/audio-player/simple-audio.tsx deleted file mode 100644 index 9016cc69..00000000 --- a/src/components/audio-player/simple-audio.tsx +++ /dev/null @@ -1,406 +0,0 @@ -import { formatTime } from '@/utils/index'; -import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Dropdown, Slider, type MenuProps } from 'antd'; -import { createStyles } from 'antd-style'; -import { round } from 'lodash'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useMemo -} from 'react'; -import styled from 'styled-components'; -import AutoTooltip from '../auto-tooltip'; -import IconFont from '../icon-font'; - -type ActionItem = 'download' | 'delete' | 'speed'; - -interface AudioPlayerProps { - autoplay?: boolean; - url: string; - speed?: number; - ref?: any; - name: string; - height?: number; - width?: number; - duration?: number; - actions?: ActionItem[]; - onDelete?: () => void; -} - -const SliderWrapper = styled.div` - width: 100%; - display: flex; - align-items: center; - justify-content: flex-start; - gap: 8px; - .ant-slider { - flex: 1; - } - .time { - color: var(--ant-color-text-tertiary); - } -`; - -const useStyles = createStyles(({ css, token }) => { - // @ts-ignore - const isDarkMode = token.darkMode as boolean; - return { - wrapper: css` - position: relative; - min-width: 360px; - height: 54px; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 8px 10px; - background-color: ${isDarkMode - ? 'var(--ant-color-fill-secondary)' - : '#F1F3F4'}; - border-radius: 28px; - .inner { - width: 100%; - display: flex; - align-items: center; - justify-content: flex-start; - flex: 1; - gap: 8px; - .slider { - display: flex; - flex-direction: column; - justify-content: center; - flex: 1; - .ant-slider { - margin: 0; - } - &:hover { - .ant-slider-handle { - opacity: 1; - transition: opacity 0.3s ease-in-out; - } - } - &:focus-within { - .ant-slider-handle { - opacity: 1; - } - } - } - - .ant-slider-handle { - opacity: 0; - &::before { - background-color: var(--ant-color-bg-spotlight); - border-radius: 50%; - } - &::after { - display: none; - } - } - } - ` - }; -}); - -const sliderStyles = { - rail: { - borderRadius: '4px', - backgroundColor: 'var(--ant-color-fill-secondary)' - }, - track: { - borderRadius: '4px', - backgroundColor: 'var(--ant-color-bg-spotlight)' - } -}; - -const speedOptions = [ - { label: '1x', value: 1 }, - { label: '2x', value: 2 }, - { label: '3x', value: 3 }, - { label: '4x', value: 4 } -]; - -const speedConfig = { - min: 0.5, - max: 2, - step: 0.25 -}; - -const AudioPlayer: React.FC = forwardRef((props, ref) => { - const intl = useIntl(); - const { styles } = useStyles(); - const { - autoplay = false, - speed: defaultSpeed = 1, - actions = ['delete'], - name, - onDelete - } = props; - const audioRef = React.useRef(null); - const [audioState, setAudioState] = React.useState<{ - currentTime: number; - duration: number; - }>({ - currentTime: 0, - duration: 0 - }); - console.log('audioState', name); - const [playOn, setPlayOn] = React.useState(false); - const [speakerOn, setSpeakerOn] = React.useState(false); - const [volume, setVolume] = React.useState(1); - const [speed, setSpeed] = React.useState(defaultSpeed); - const timer = React.useRef(null); - - useImperativeHandle(ref, () => ({ - play: () => { - audioRef.current?.play(); - }, - pause: () => { - audioRef.current?.pause(); - } - })); - const handleShowVolume = useCallback(() => { - setSpeakerOn(!speakerOn); - }, [speakerOn]); - - const handleSeepdChange = useCallback((value: number | string) => { - setSpeed(value as number); - audioRef.current!.playbackRate = value as number; - }, []); - - const handleAudioOnPlay = useCallback(() => { - timer.current = setInterval(() => { - setAudioState((prestate) => { - return { - currentTime: Math.ceil(audioRef.current?.currentTime || 0), - duration: - prestate.duration || Math.ceil(audioRef.current?.duration || 0) - }; - }); - - if (audioRef.current?.paused || audioRef.current?.ended) { - clearInterval(timer.current); - setPlayOn(false); - setAudioState((prestate: any) => { - return { - currentTime: audioRef.current?.ended ? 0 : prestate.currentTime, - duration: prestate.duration - }; - }); - } - }, 500); - }, []); - - const handlePlay = useCallback(() => { - setPlayOn(!playOn); - if (playOn) { - audioRef.current?.pause(); - } else { - audioRef.current?.play(); - } - }, [playOn]); - - const handleFormatVolume = (val?: number) => { - if (val === undefined) { - return `${round(volume * 100)}%`; - } - return `${round(val * 100)}%`; - }; - - const handleVolumeChange = useCallback((value: number) => { - audioRef.current!.volume = round(value, 2); - setVolume(round(value, 2)); - }, []); - - const initPlayerConfig = () => { - if (audioRef.current) { - audioRef.current!.volume = volume; - audioRef.current!.playbackRate = speed; - } - }; - - const handleLoadedMetadata = useCallback( - (data: any) => { - const duration = Math.ceil(audioRef.current?.duration || 0); - setAudioState({ - currentTime: 0, - duration: - duration && duration !== Infinity ? duration : props.duration || 0 - }); - setPlayOn(autoplay); - }, - [autoplay, props.duration] - ); - - const handleCurrentChange = useCallback((val: number) => { - audioRef.current!.currentTime = val; - setAudioState((prestate) => { - return { - currentTime: val, - duration: prestate.duration - }; - }); - }, []); - - const handleReduceSpeed = () => { - setSpeed((pre) => { - if (pre - speedConfig.step < speedConfig.min) { - return speedConfig.min; - } - const next = pre - speedConfig.step; - audioRef.current!.playbackRate = next; - return next; - }); - }; - - const handleAddSpeed = () => { - setSpeed((pre) => { - if (pre + speedConfig.step > speedConfig.max) { - return speedConfig.max; - } - const next = pre + speedConfig.step; - audioRef.current!.playbackRate = next; - return next; - }); - }; - - const handleOnLoad = (e: any) => { - console.log('onload', e); - }; - - const onDownload = useCallback(() => { - const url = props.url || ''; - const filename = props.name; - - const link = document.createElement('a'); - link.href = url; - link.download = filename || 'audio.mp3'; // Default filename - document.body.appendChild(link); - link.click(); - link.remove(); - }, [props.url, props.name]); - - const items: MenuProps['items'] = useMemo(() => { - return [ - { - key: 'download', - label: intl.formatMessage({ id: 'common.button.download' }), - icon: , - onClick: onDownload - }, - { - key: 'speed', - label: intl.formatMessage({ id: 'playground.params.speed' }), - icon: , - children: speedOptions.map((item) => ({ - key: item.value, - label: item.label, - onClick: () => handleSeepdChange(item.value) - })) - }, - { - key: 'delete', - label: intl.formatMessage({ id: 'common.button.delete' }), - icon: , - danger: true, - onClick: () => { - if (audioRef.current) { - audioRef.current.pause(); - audioRef.current.src = ''; - audioRef.current.load(); - } - setAudioState({ currentTime: 0, duration: 0 }); - setPlayOn(false); - onDelete?.(); - } - } - ].filter((item) => actions.includes(item.key as ActionItem)); - }, [actions, intl, onDownload, onDelete, handleSeepdChange]); - - useEffect(() => { - if (audioRef.current) { - initPlayerConfig(); - } - }, [audioRef.current]); - - useEffect(() => { - return () => { - clearInterval(timer.current); - }; - }, []); - - return ( -
-
- - -
-
- - {name} - -
- - {formatTime(audioState.currentTime)} - - -
- - - -
- -
- ); -}); - -export default React.memo(AudioPlayer); diff --git a/src/components/auto-image/index.less b/src/components/auto-image/index.less deleted file mode 100644 index 9298a78a..00000000 --- a/src/components/auto-image/index.less +++ /dev/null @@ -1,21 +0,0 @@ -.toolbar-wrapper { - padding: 0 24px; - color: rgba(255, 255, 255, 65%); - font-size: 16px; - background-color: rgba(0, 0, 0, 10%); - border-radius: 100px; -} - -.toolbar-wrapper .anticon { - padding: 12px; - cursor: pointer; -} - -.toolbar-wrapper .anticon[disabled] { - cursor: not-allowed; - opacity: 0.3; -} - -.toolbar-wrapper .anticon:hover { - opacity: 0.3; -} diff --git a/src/components/auto-image/index.tsx b/src/components/auto-image/index.tsx deleted file mode 100644 index 0ead6666..00000000 --- a/src/components/auto-image/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import fallbackImg from '@/assets/images/img_fallback.png'; -import { - DownloadOutlined, - EyeOutlined, - RotateLeftOutlined, - RotateRightOutlined, - SwapOutlined, - UndoOutlined, - ZoomInOutlined, - ZoomOutOutlined -} from '@ant-design/icons'; -import { Image as AntImage, ImageProps, Space } from 'antd'; -import { round } from 'lodash'; -import React, { useCallback, useEffect, useState } from 'react'; -import './index.less'; - -const AutoImage: React.FC< - ImageProps & { - height: number | string; - width?: number | string; - autoSize?: boolean; - preview?: boolean; - onLoad?: () => void; - } -> = (props) => { - const { height = 100, width: w, autoSize, preview = true, ...rest } = props; - const [width, setWidth] = useState(w || 0); - const [isError, setIsError] = useState(false); - - const getImgRatio = useCallback((url: string): Promise<{ ratio: number }> => { - return new Promise((resolve) => { - const img = new Image(); - img.onload = () => { - resolve({ ratio: round(img.width / img.height, 2) }); - }; - img.onerror = () => { - resolve({ ratio: 1 }); - }; - img.src = url; - }); - }, []); - - const handleOnLoad = useCallback(async () => { - if (autoSize) { - return; - } - const { ratio } = await getImgRatio(props.src || ''); - - if (typeof height === 'number') { - setWidth(height * ratio); - } else { - throw new Error('Height must be a number'); - } - }, [getImgRatio, height, props.src]); - - const onDownload = useCallback(() => { - const url = props.src || ''; - const filename = Date.now() + ''; - - const link = document.createElement('a'); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - link.remove(); - }, [props.src]); - - const handleImgLoad = useCallback(() => { - props.onLoad?.(); - setIsError(false); - }, [props.onLoad]); - - const handleOnError = useCallback((e: any) => { - setIsError(true); - e.target.src = fallbackImg; - }, []); - - useEffect(() => { - handleOnLoad(); - }, [handleOnLoad]); - - useEffect(() => { - setWidth(w || 0); - }, [w]); - - return ( - , - actionsRender: ( - _, - { - transform: { scale }, - actions: { - onFlipY, - onFlipX, - onRotateLeft, - onRotateRight, - onZoomOut, - onZoomIn, - onReset - } - } - ) => ( - - - - - - - - - - - ) - } - } - /> - ); -}; - -export default AutoImage; diff --git a/src/components/auto-image/progress-line.less b/src/components/auto-image/progress-line.less deleted file mode 100644 index 3e49983d..00000000 --- a/src/components/auto-image/progress-line.less +++ /dev/null @@ -1,44 +0,0 @@ -.img-wrapper { - position: relative; - display: inline-block; -} - -.img-wrapper .auto-image { - display: block; -} - -.img-wrapper .progress-wrapper { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; -} - -.progress-square { - width: 100%; - height: 100%; - transform: rotate(0deg); -} - -.progress-square-bg { - fill: none; -} - -.progress-square-fg { - fill: none; - stroke-linecap: square; - stroke-dasharray: 400; - stroke-dashoffset: 400; - transition: stroke-dashoffset 0.3s ease; -} - -.progress-text { - position: absolute; - color: black; - font-size: 20px; - font-weight: bold; -} diff --git a/src/components/auto-image/single-image.less b/src/components/auto-image/single-image.less deleted file mode 100644 index 28b32101..00000000 --- a/src/components/auto-image/single-image.less +++ /dev/null @@ -1,144 +0,0 @@ -.thumb-img { - position: relative; - display: flex; - max-width: 100%; - max-height: 100%; - justify-content: center; - align-items: center; - border-radius: var(--border-radius-base); - overflow: hidden; - - .label { - position: absolute; - top: 4px; - left: 4px; - height: 20px; - line-height: 20px; - border-radius: 12px; - padding: 0 8px; - background-color: var(--ant-geekblue-1); - z-index: 10; - transform: scale(0.9); - } - - .progress-wrapper { - position: absolute; - bottom: 20px; - left: 20px; - right: 20px; - } - - .small-progress-wrap { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: flex; - justify-content: center; - align-items: center; - background-color: rgba(0, 0, 0, 30%); - - .ant-progress-text { - color: var(--color-white-secondary); - } - } - - .img { - display: flex; - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; - overflow: hidden; - border-radius: var(--border-radius-base); - cursor: pointer; - justify-content: center; - align-items: center; - } - - .del { - position: absolute; - top: 2px; - right: 2px; - font-size: var(--font-size-middle); - cursor: pointer; - background-color: var(--color-white-1); - display: none; - border-radius: 50%; - height: 16px; - width: 16px; - overflow: hidden; - pointer-events: all; - } - - &:hover { - .ant-image .ant-image-mask { - opacity: 1; - transition: opacity var(--ant-motion-duration-slow); - } - - .del { - display: flex; - justify-content: center; - align-items: center; - } - } -} - -.single-image { - // height: 100%; - width: inherit; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - border-radius: var(--border-radius-base); - - &.loading { - width: 100%; - height: 100%; - } - - &.auto-bg-color { - position: relative; - - &::before { - content: ''; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - filter: blur(100px); - backdrop-filter: blur(100px); - z-index: 5; - } - - .thumb-img { - position: relative; - z-index: 10; - border-radius: 0; - - .img { - border-radius: 0; - } - } - - .ant-image { - border-radius: 0; - } - - .mask { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - bottom: 0; - right: 0; - z-index: 1; - } - } -} diff --git a/src/components/auto-image/single-image.tsx b/src/components/auto-image/single-image.tsx deleted file mode 100644 index 8f763eba..00000000 --- a/src/components/auto-image/single-image.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'; -import { Progress, ProgressProps, Spin } from 'antd'; -import classNames from 'classnames'; -import { round } from 'lodash'; -import ResizeObserver from 'rc-resize-observer'; -import React, { useCallback } from 'react'; -import AutoImage from './index'; -import './single-image.less'; -interface SingleImageProps { - loading?: boolean; - width?: number; - height?: number; - progress?: number; - maxHeight?: number; - maxWidth?: number; - dataUrl: string; - label?: React.ReactNode; - uid: number | string; - preview?: boolean; - autoSize?: boolean; - onDelete: (uid: number | string) => void; - onClick?: (item: any) => void; - autoBgColor?: boolean; - editable?: boolean; - style?: React.CSSProperties; - loadingSize?: ProgressProps['size']; - progressType?: 'line' | 'circle' | 'dashboard'; - progressColor?: string; - progressWidth?: number; -} - -const SingleImage: React.FC = (props) => { - const { - editable, - onDelete, - onClick, - autoSize, - uid, - loading, - width, - height, - progress, - maxHeight, - maxWidth, - dataUrl = '', - label, - style, - autoBgColor, - preview = true, - loadingSize = 'default' - } = props; - - const imgWrapper = React.useRef(null); - const [imgSize, setImgSize] = React.useState({ - width: width, - height: height - }); - - const thumImgWrapStyle = React.useMemo(() => { - return loading ? { width: '100%', height: '100%' } : {}; - }, [loading, imgSize]); - - const handleOnClick = useCallback(() => { - onClick?.(props); - }, [onClick, props]); - - const handleResize = useCallback( - (size: { width: number; height: number }) => { - if (!autoSize || !size.width || !size.height) return; - - const { width: containerWidth, height: containerHeight } = size; - const { width: originalWidth, height: originalHeight } = props; - - if (!originalWidth || !originalHeight) return; - - const widthRatio = containerWidth / originalWidth; - const heightRatio = containerHeight / originalHeight; - - const scale = Math.min(widthRatio, heightRatio, 1); - - const newWidth = originalWidth * scale; - const newHeight = originalHeight * scale; - - if (newWidth === imgSize.width && newHeight === imgSize.height) { - return; - } - setImgSize({ - width: newWidth, - height: newHeight - }); - }, - [autoSize, props.width, props.height] - ); - - const handleOnLoad = React.useCallback(async () => {}, []); - - const handleOnDelete = (uid: number | string, e: any) => { - e.stopPropagation(); - onDelete(uid); - }; - - const renderProgress = () => { - {round(progress, 0)}%} - railColor="var(--ant-color-fill-secondary)" - />; - }; - - return ( - -
- {autoBgColor && ( -
- )} - - <> - {label &&
{label}
} - {loading ? ( - - } - /> - - ) : ( - - - {progress && progress < 100 && ( - - ( - - {round(progress, 0)}% - - )} - strokeColor="var(--color-white-secondary)" - railColor="var(--ant-color-fill-secondary)" - /> - - )} - - )} - - - {editable && ( - handleOnDelete(uid, e)}> - - - )} -
-
-
- ); -}; - -export default SingleImage; diff --git a/src/components/auto-tooltip/index.tsx b/src/components/auto-tooltip/index.tsx deleted file mode 100644 index e7fae224..00000000 --- a/src/components/auto-tooltip/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { CloseOutlined } from '@ant-design/icons'; -import { Tag, Tooltip, type TagProps } from 'antd'; -import { throttle } from 'lodash'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react'; -import styled from 'styled-components'; -import { TooltipOverlayScroller } from '../overlay-scroller'; - -// type TagProps = React.ComponentProps; - -interface AutoTooltipProps extends Omit { - children: React.ReactNode; - maxWidth?: number | string; - minWidth?: number | string; - color?: string; - style?: React.CSSProperties; - ghost?: boolean; - title?: React.ReactNode; - showTitle?: boolean; - closable?: boolean; - radius?: number | string; - filled?: boolean; - tooltipProps?: React.ComponentProps; -} - -const StyledTag = styled(Tag)` - margin: 0; - &.tag-filled { - border: none; - background-color: var(--ant-color-fill-secondary); - } -`; - -const AutoTooltip: React.FC = ({ - children, - maxWidth = '100%', - minWidth, - ghost = false, - title, - showTitle = false, - tooltipProps, - radius = 12, - filled = false, - ...tagProps -}) => { - const contentRef = useRef(null); - const [isOverflowing, setIsOverflowing] = useState(false); - const resizeObserver = useRef(null); - - const checkOverflow = useCallback(() => { - if (contentRef.current) { - const { scrollWidth, clientWidth } = contentRef.current; - setIsOverflowing(scrollWidth > clientWidth); - } - }, [contentRef.current]); - - useEffect(() => { - const element = contentRef.current; - if (!element) return; - resizeObserver.current?.disconnect(); - resizeObserver.current = new ResizeObserver(() => { - checkOverflow(); - }); - - resizeObserver.current?.observe(element); - - // Initial check - checkOverflow(); - - return () => { - resizeObserver.current?.disconnect(); - resizeObserver.current = null; - }; - }, [checkOverflow]); - - useEffect(() => { - const debouncedCheckOverflow = throttle(checkOverflow, 200); - window.addEventListener('resize', debouncedCheckOverflow); - return () => { - window.removeEventListener('resize', debouncedCheckOverflow); - debouncedCheckOverflow.cancel(); - }; - }, [checkOverflow]); - - useEffect(() => { - checkOverflow(); - }, [children, checkOverflow]); - - const tagStyle = useMemo( - () => ({ - maxWidth, - minWidth, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' as const, - ...tagProps.style - }), - [maxWidth, tagProps.style] - ); - - return ( - - {ghost ? ( -
- {children} -
- ) : ( - - ) : ( - false - ) - } - > - {children} - - )} -
- ); -}; - -export default AutoTooltip; diff --git a/src/components/auto-tooltip/title-tip.tsx b/src/components/auto-tooltip/title-tip.tsx deleted file mode 100644 index 71c38de4..00000000 --- a/src/components/auto-tooltip/title-tip.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { OverlayScroller } from '@/components/overlay-scroller'; -import React from 'react'; - -interface TitleTipProps { - isOverflowing: boolean; - showTitle: boolean; - title: React.ReactNode; - children: React.ReactNode; -} - -const TitleTip: React.FC = (props) => { - const { isOverflowing, showTitle, title, children } = props; - - return ( - -
- {isOverflowing || showTitle ? title || children : ''} -
-
- ); -}; - -export default React.memo(TitleTip); diff --git a/src/components/bibtex-viewer/index.tsx b/src/components/bibtex-viewer/index.tsx deleted file mode 100644 index 372c955c..00000000 --- a/src/components/bibtex-viewer/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import bibtexParse from '@orcid/bibtex-parse-js'; -import { Typography } from 'antd'; -import React from 'react'; - -/* - @inproceedings{Lysenko:2010:GMC:1839778.1839781,\ - author = {Lysenko, Mikola and Nelaturi, Saigopal and Shapiro, Vadim},\ - title = {Group morphology with convolution algebras},\ - booktitle = {Proceedings of the 14th ACM Symposium on Solid and Physical Modeling},\ - series = {SPM '10},\ - year = {2010},\ - isbn = {978-1-60558-984-8},\ - location = {Haifa, Israel},\ - pages = {11--22},\ - numpages = {12},\ - url = {http://doi.acm.org/10.1145/1839778.1839781},\ - doi = {10.1145/1839778.1839781},\ - acmid = {1839781},\ - publisher = {ACM},\ - address = {New York, NY, USA},\ - } -*/ - -const BibTeXViewer: React.FC<{ data: string }> = ({ data }) => { - if (!data) { - return null; - } - const dataList = bibtexParse.toJSON(data); - return ( -
    - {dataList.map((item: any, index: number) => ( -
  1. - - {item.entryTags?.title}.{' '} - - {item.entryTags?.author}. - [{item.entryTags?.year}] - {item.entryTags?.journal && ( - .({item.entryTags?.journal}) - )} -
  2. - ))} -
- ); -}; - -export default BibTeXViewer; diff --git a/src/components/buttons/more.tsx b/src/components/buttons/more.tsx deleted file mode 100644 index c5847e8d..00000000 --- a/src/components/buttons/more.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { DoubleRightOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -interface MoreButtonProps { - show: boolean; - loadMore: () => void; - loading?: boolean; -} - -const MoreWrapper = styled.div` - display: flex; - justify-content: center; - margin-block: 16px; - opacity: 1; - transition: opacity 0.3s; - &.loading { - opacity: 0; - transition: opacity 0.3s ease-in-out; - } -`; - -const MoreButton: React.FC = (props) => { - const { show, loading, loadMore } = props; - const intl = useIntl(); - return ( - <> - {show ? ( - - - - ) : null} - - ); -}; - -export default MoreButton; diff --git a/src/components/card-wrapper/index.tsx b/src/components/card-wrapper/index.tsx deleted file mode 100644 index 95a0e7ed..00000000 --- a/src/components/card-wrapper/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import styled from 'styled-components'; - -const Wrapper = styled.div` - border-radius: var(--border-radius-lg); - background-color: var(--ant-color-bg-container); - box-shadow: none; - padding: 8px 16px; - border: 1px solid var(--ant-color-border); -`; - -const CardWrapper = (props: any) => { - const { children, style } = props; - return {children}; -}; - -export default CardWrapper; diff --git a/src/components/card-wrapper/simple-card.tsx b/src/components/card-wrapper/simple-card.tsx deleted file mode 100644 index d0d4214b..00000000 --- a/src/components/card-wrapper/simple-card.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { createStyles } from 'antd-style'; -import React from 'react'; -import styled from 'styled-components'; - -const SimpleCardItemWrapper = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - height: 100%; - gap: 16px; -`; - -const useStyles = createStyles(({ css, token }) => ({ - wrapper: css` - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - background: ${token.colorBgContainer}; - border-radius: ${token.borderRadius}px; - padding: ${token.padding}px; - justify-content: center; - align-items: center; - gap: ${token.padding}px; - &.bordered { - border: 1px solid ${token.colorBorder}; - } - .title { - font-size: ${token.fontSize}px; - font-weight: var(--font-weight-medium); - } - .content { - display: flex; - justify-content: center; - align-items: center; - font-size: ${token.fontSize}px; - color: ${token.colorTextSecondary}; - gap: 8px; - .icon { - display: inline-block; - width: 10px; - height: 10px; - gap: 10px; - &.roundRect { - border-radius: 2px; - } - &.circle { - border-radius: 50%; - } - } - } - ` -})); -export const SimpleCardItem: React.FC<{ - title?: string; - content?: React.ReactNode; - style?: React.CSSProperties; - bordered?: boolean; - color?: string; - iconType?: string; -}> = (props) => { - const { styles, cx } = useStyles(); - - const { title, content, style, bordered, iconType, color } = props; - - return ( -
-
{title}
-
- {iconType && ( - - )} - {content} -
-
- ); -}; - -export const SimpleCard: React.FC<{ - dataList: { - label: string; - value: React.ReactNode; - color: string; - iconType?: string; - }[]; - height?: string | number; - bordered?: boolean; - styles?: { - wrapper?: React.CSSProperties; - item?: React.CSSProperties; - }; -}> = (props) => { - const { dataList, bordered, styles } = props; - - return ( - - {dataList.map((item, index) => ( - - ))} - - ); -}; diff --git a/src/components/check-buttons/index.tsx b/src/components/check-buttons/index.tsx deleted file mode 100644 index 559c872c..00000000 --- a/src/components/check-buttons/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Button } from 'antd'; -import React from 'react'; - -interface CheckButtonsProps { - options: Global.BaseOption[]; - onChange: (value: string | number) => void; - cancelable?: boolean; - size?: 'small' | 'middle' | 'large'; - type?: 'text' | 'primary' | 'default' | 'dashed' | 'link' | undefined; -} - -const CheckButtons: React.FC = (props) => { - const [type, setType] = React.useState(props.type || 'text'); - const [active, setActive] = React.useState(null); - const handleChange = (value: string | number) => { - props.onChange(value); - if (props.cancelable && active === value) { - setActive(null); - } else { - setActive(value); - } - }; - return ( -
- {props.options?.map?.((option, index) => { - return ( - - ); - })} -
- ); -}; - -export default React.memo(CheckButtons); diff --git a/src/components/collapse-container/headless-collapse.tsx b/src/components/collapse-container/headless-collapse.tsx deleted file mode 100644 index a41e644b..00000000 --- a/src/components/collapse-container/headless-collapse.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; - -interface CollapseProps { - open: boolean; - children: React.ReactNode; - duration?: number; - minHeight?: number; -} - -export default function Collapse({ - open, - children, - minHeight = 0, - duration = 200 -}: CollapseProps) { - const ref = useRef(null); - const [height, setHeight] = useState(minHeight); - - useEffect(() => { - const el = ref.current; - if (!el) return; - - if (open) { - const h = el.scrollHeight; - setHeight(h); - - const timer = setTimeout(() => { - setHeight('auto'); - }, duration); - - return () => clearTimeout(timer); - } else { - const h = el.scrollHeight; - setHeight(h); - - requestAnimationFrame(() => { - requestAnimationFrame(() => { - setHeight(0); - }); - }); - } - return undefined; - }, [open, duration]); - - return ( -
- {children} -
- ); -} diff --git a/src/components/collapse-container/index.tsx b/src/components/collapse-container/index.tsx deleted file mode 100644 index aa8aee47..00000000 --- a/src/components/collapse-container/index.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import IconFont from '@/components/icon-font'; -import { Card } from 'antd'; -import { createStyles } from 'antd-style'; -import classNames from 'classnames'; -import React, { useEffect, useRef, useState } from 'react'; -import styled from 'styled-components'; - -const CardStyled = styled(Card)` - box-shadow: none !important; - background-color: none; - &.isOpen { - .ant-card-head { - border-bottom: 1px solid var(--ant-color-border-secondary); - border-radius: var(--ant-border-radius) var(--ant-border-radius) 0 0; - } - } - .ant-card-head { - cursor: pointer; - background-color: var(--ant-color-fill-quaternary); - border-bottom: none; - border-radius: var(--ant-border-radius); - padding: 0 16px; - &:hover { - background-color: var(--ant-color-fill-secondary); - .del-btn { - display: block; - } - } - } - &.disabled { - .ant-card-head { - cursor: not-allowed; - background-color: var(--ant-color-fill-quaternary) !important; - } - } -`; - -const useStyles = createStyles(({ css, token }) => { - return { - title: css` - font-weight: 400; - min-height: 56px; - font-size: var(--font-size-base); - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - `, - expandIcon: css` - display: flex; - align-items: center; - gap: 8px; - `, - subtitle: css` - font-size: 14px; - color: ${token.colorTextSecondary}; - `, - content: css` - padding-top: 8px; - `, - left: css` - flex: 1; - `, - right: css` - display: flex; - align-items: center; - gap: 8px; - .del-btn { - display: none; - } - ` - }; -}); - -export interface CollapsibleContainerProps { - title?: React.ReactNode; - subtitle?: React.ReactNode; - right?: React.ReactNode; - deleteBtn?: React.ReactNode; - defaultOpen?: boolean; - open?: boolean; - collapsible?: boolean; - showExpandIcon?: boolean; - onToggle?: (open: boolean) => void; - disabled?: boolean; - variant?: 'outlined' | 'borderless' | undefined; - iconPlacement?: 'left' | 'right'; - className?: string; - children?: React.ReactNode; - styles?: { - root?: React.CSSProperties; - body?: React.CSSProperties; - header?: React.CSSProperties; - content?: React.CSSProperties; - }; -} - -export default function CollapsibleContainer({ - title, - subtitle, - right, - deleteBtn, - defaultOpen = true, - open, - onToggle, - disabled = false, - showExpandIcon = true, - variant = 'borderless', - className = '', - collapsible, - iconPlacement = 'left', - styles: cardStyles, - children -}: CollapsibleContainerProps) { - const { styles } = useStyles(); - const isControlled = typeof open === 'boolean'; - const [internalOpen, setInternalOpen] = useState(defaultOpen); - const isOpen = collapsible - ? isControlled - ? (open as boolean) - : internalOpen - : true; - - const toggle = () => { - if (disabled || !collapsible) return; - const next = !isOpen; - if (!isControlled) setInternalOpen(next); - onToggle?.(next); - }; - - const contentRef = useRef(null); - const [height, setHeight] = useState(isOpen ? 'auto' : '0px'); - - const renderIcon = () => { - if (showExpandIcon) { - return ( - - ); - } - return null; - }; - - const renderTitle = () => { - if (!collapsible) { - return null; - } - return ( -
-
-
- {iconPlacement === 'left' && renderIcon()} - {title &&
{title}
} -
- {subtitle &&
{subtitle}
} -
-
- {right && {right}} - {deleteBtn && {deleteBtn}} - {iconPlacement === 'right' && renderIcon()} -
-
- ); - }; - - useEffect(() => { - if (!collapsible) { - setHeight('auto'); - return; - } - if (isOpen) { - const scrollHeight = contentRef.current?.scrollHeight || 0; - setHeight(scrollHeight + 'px'); - const timer = setTimeout(() => setHeight('auto'), 200); - return () => clearTimeout(timer); - } else { - const scrollHeight = contentRef.current?.scrollHeight || 0; - setHeight(scrollHeight + 'px'); - requestAnimationFrame(() => setHeight('0px')); - return () => {}; - } - }, [isOpen, collapsible]); - - return ( - -
-
{children}
-
-
- ); -} diff --git a/src/components/content-wrapper/index.less b/src/components/content-wrapper/index.less deleted file mode 100644 index 47d6a555..00000000 --- a/src/components/content-wrapper/index.less +++ /dev/null @@ -1,17 +0,0 @@ -.content-wrapper { - .content { - padding-block-start: 0; - padding-block-end: 32px; - padding-inline: 40px; - } - - .title { - font-size: var(--font-size-large); - font-weight: 600; - line-height: 32px; - padding-block-start: 8px; - padding-block-end: 16px; - padding-inline-start: 40px; - padding-inline-end: 40px; - } -} diff --git a/src/components/content-wrapper/index.tsx b/src/components/content-wrapper/index.tsx deleted file mode 100644 index 8a210057..00000000 --- a/src/components/content-wrapper/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import './index.less'; - -const ContentWrapper: React.FC<{ - children: React.ReactNode; - title: React.ReactNode; - titleStyle?: React.CSSProperties; - contentStyle?: React.CSSProperties; -}> = ({ children, title = false, titleStyle, contentStyle }) => { - return ( -
- {title && ( -
- {title} -
- )} -
- {children} -
-
- ); -}; - -export default ContentWrapper; diff --git a/src/components/copy-button/index.tsx b/src/components/copy-button/index.tsx deleted file mode 100644 index 81062b1e..00000000 --- a/src/components/copy-button/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, message, Tooltip } from 'antd'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import AutoTooltip from '../auto-tooltip'; - -type CopyButtonProps = { - children?: React.ReactNode; - text: string; - fontSize?: string; - type?: 'text' | 'primary' | 'dashed' | 'link' | 'default'; - size?: 'small' | 'middle' | 'large'; - shape?: 'circle' | 'round' | 'default'; - tips?: string; - placement?: - | 'top' - | 'left' - | 'right' - | 'bottom' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight'; - btnStyle?: React.CSSProperties; - style?: React.CSSProperties; -}; - -const CopyButton: React.FC = ({ - children, - tips, - text, - type = 'text', - shape = 'default', - fontSize = '14px', - style, - btnStyle, - placement, - size = 'small' -}) => { - const intl = useIntl(); - const [copied, setCopied] = useState(false); - const timerRef = useRef(); - - const resetCopied = () => { - window.clearTimeout(timerRef.current); - timerRef.current = window.setTimeout(() => { - setCopied(false); - }, 3000); - }; - - /** - * Modern clipboard API (works in secure contexts: HTTPS or localhost) - */ - const asyncCopy = async (value: string): Promise => { - try { - await navigator.clipboard.writeText(value); - return true; - } catch (error) { - return false; - } - }; - - /** - * Fallback: execCommand with copy event listener - * More reliable than textarea selection method - */ - const execCopy = (value: string): boolean => { - let copySuccess = false; - - const onCopy = (event: ClipboardEvent) => { - event.stopPropagation(); - event.preventDefault(); - event.clipboardData?.clearData(); - event.clipboardData?.setData('text/plain', value); - copySuccess = true; - }; - - try { - document.addEventListener('copy', onCopy, { capture: true }); - document.execCommand('copy'); - return copySuccess; - } catch (error) { - return false; - } finally { - document.removeEventListener('copy', onCopy, { capture: true }); - } - }; - - const handleCopy = async () => { - try { - // Try modern clipboard API first - if (await asyncCopy(text)) { - setCopied(true); - return; - } - - // Fallback to execCommand method - if (execCopy(text)) { - setCopied(true); - return; - } - - // Both methods failed - throw new Error('Copy failed'); - } catch (error) { - message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string); - } - }; - - const tipTitle = useMemo(() => { - if (copied) { - return intl.formatMessage({ id: 'common.button.copied' }); - } - return tips ?? intl.formatMessage({ id: 'common.button.copy' }); - }, [copied, tips, intl]); - - useEffect(() => { - resetCopied(); - }, [copied]); - - return ( -
- {children && ( - - {children} - - )} - - - - - -
- ); -}; - -export default CopyButton; diff --git a/src/components/delete-modal/index.tsx b/src/components/delete-modal/index.tsx deleted file mode 100644 index 690723e7..00000000 --- a/src/components/delete-modal/index.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import useBodyScroll from '@/hooks/use-body-scroll'; -import { ExclamationCircleFilled } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { - Button, - Checkbox, - Modal, - Space, - message, - type ModalFuncProps -} from 'antd'; -import { createStyles } from 'antd-style'; -import { forwardRef, useImperativeHandle, useState } from 'react'; -import styled from 'styled-components'; - -const useStyles = createStyles(({ css }) => ({ - 'delete-modal-content': css` - display: flex; - font-size: var(--font-size-middle); - .anticon { - font-size: 20px; - margin-right: 10px; - color: var(--ant-color-warning); - } - .title { - display: flex; - align-items: center; - font-weight: var(--font-weight-500); - } - `, - content: css` - padding-top: 15px; - padding-left: 30px; - color: var(--ant-color-text-secondary); - white-space: pre-line; - word-break: break-all; - span { - color: var(--ant-color-text); - display: flex; - margin-top: 8px; - } - ` -})); - -const CheckboxWrapper = styled.div` - margin-top: 20px; - margin-left: 30px; - display: flex; - justify-content: flex-start; - align-items: center; - .check-text { - font-weight: 700; - color: var(--ant-color-warning); - } -`; - -interface DataOptions { - content?: string; - selection?: boolean; - name?: string; - okText?: string; - cancelText?: string; - title?: string; - operation: string; - checkConfig?: { - checkText: string; - defautlChecked: boolean; - }; -} - -interface Configuration { - checked: boolean; -} - -// default need to pass content and operation -const DeleteModal = forwardRef((props, ref) => { - const intl = useIntl(); - const { styles } = useStyles(); - const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); - const [visible, setVisible] = useState(false); - const [configuration, setConfiguration] = useState({ - checked: false - }); - const [delLoading, setDelLoading] = useState(false); - const [config, setConfig] = useState({} as any); - - const show = (data: ModalFuncProps & DataOptions) => { - saveScrollHeight(); - setConfig(data); - setConfiguration({ - checked: data.checkConfig?.defautlChecked || false - }); - setVisible(true); - }; - - const hide = () => { - setVisible(false); - restoreScrollHeight(); - }; - - const handleCancel = () => { - setVisible(false); - config.onCancel?.(); - restoreScrollHeight(); - }; - - const handleOk = async () => { - try { - setDelLoading(true); - const res = await config.onOk?.(); - const isArray = Array.isArray(res); - if (isArray) { - const allSuccess = res.every( - (item: any) => item?.status === 'fulfilled' - ); - if (allSuccess) { - message.success(intl.formatMessage({ id: 'common.message.success' })); - } - } else { - message.success(intl.formatMessage({ id: 'common.message.success' })); - } - } catch (error) { - // Handle error if needed - } finally { - setVisible(false); - setDelLoading(false); - restoreScrollHeight(); - } - }; - - useImperativeHandle(ref, () => ({ - show, - hide, - configuration - })); - - return ( - - - - - } - > -
- - - - {config.title - ? intl.formatMessage({ id: config.title }) - : intl.formatMessage({ id: 'common.title.delete.confirm' })} - - -
-
- {config.checkConfig && ( - - - setConfiguration({ - checked: e.target.checked - }) - } - > - - {intl.formatMessage({ id: config.checkConfig?.checkText })} - - - - )} -
- ); -}); - -export default DeleteModal; diff --git a/src/components/divider-line/index.less b/src/components/divider-line/index.less deleted file mode 100644 index dde6eafa..00000000 --- a/src/components/divider-line/index.less +++ /dev/null @@ -1,20 +0,0 @@ -.divider-line { - height: 8px; - // border-radius: 4px; - width: 100%; - // background-color: var(--color-fill-1); - z-index: 100; - margin: 0; - position: relative; - &::after { - content: ''; - position: absolute; - top: 0; - left: -9px; - bottom: 0; - right: 0; - height: 100%; - background: var(--color-fill-1); - // border-radius: 4px; - } -} diff --git a/src/components/divider-line/index.tsx b/src/components/divider-line/index.tsx deleted file mode 100644 index c15be85a..00000000 --- a/src/components/divider-line/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import styles from './index.less'; -const DividerLine: React.FC = () => { - return
; -}; - -export default DividerLine; diff --git a/src/components/drop-down-actions/index.tsx b/src/components/drop-down-actions/index.tsx deleted file mode 100644 index 68b35f64..00000000 --- a/src/components/drop-down-actions/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useIntl } from '@umijs/max'; -import { Dropdown, DropDownProps } from 'antd'; -import _ from 'lodash'; -import React, { useMemo } from 'react'; - -const DropDownActions: React.FC = (props) => { - const { - menu, - trigger = ['hover'], - placement = 'bottomRight', - children, - ...rest - } = props; - const intl = useIntl(); - - const items = useMemo(() => { - return menu?.items?.map((item: any) => ({ - ..._.omit(item, 'locale'), - icon: item.icon - ? React.cloneElement(item.icon, { style: { fontSize: 14 } }) - : null, - label: item.locale ? intl.formatMessage({ id: item.label }) : item.label - })); - }, [menu?.items, intl]); - return ( - - {children} - - ); -}; - -export default DropDownActions; diff --git a/src/components/drop-down-buttons/index.less b/src/components/drop-down-buttons/index.less deleted file mode 100644 index fdefa3d3..00000000 --- a/src/components/drop-down-buttons/index.less +++ /dev/null @@ -1,4 +0,0 @@ -.dropdown-button.middle { - height: 28px; - width: 28px; -} diff --git a/src/components/drop-down-buttons/index.tsx b/src/components/drop-down-buttons/index.tsx deleted file mode 100644 index 8cf7af7b..00000000 --- a/src/components/drop-down-buttons/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { MoreOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Dropdown, Space, Tooltip, type MenuProps } from 'antd'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import './index.less'; - -type Trigger = 'click' | 'hover'; -interface DropdownButtonsProps { - items: MenuProps['items']; - size?: 'small' | 'middle' | 'large'; - trigger?: Trigger[]; - showText?: boolean; - disabled?: boolean; - variant?: 'filled' | 'outlined'; - color?: string; - extra?: React.ReactNode; - onSelect: (val: any, item?: any) => void; -} - -const DropdownWrapper = styled.div` - display: flex; - flex-direction: column; - background-color: var(--ant-color-bg-elevated); - padding: 5px; - align-items: flex-start; - border-radius: var(--border-radius-base); - box-shadow: var(--ant-box-shadow-secondary); - min-width: 160px; -`; - -const DropdownButtons: React.FC< - DropdownButtonsProps & { items: MenuProps['items'] } -> = ({ - items, - size = 'middle', - trigger = ['hover'], - showText, - disabled, - variant, - color, - extra, - onSelect -}) => { - const headItem = _.head(items); - const intl = useIntl(); - - const handleMenuClick = (item: any) => { - const selectItem = _.find(items, { key: item.key }); - onSelect(item.key, selectItem); - }; - - const handleButtonClick = (e: any) => { - const headItem = _.head(items); - onSelect(headItem.key, headItem); - }; - - if (!items?.length) { - return ; - } - - return ( - <> - {items?.length === 1 ? ( - - - - ) : ( - - <> - {showText ? ( - - ) : ( - - - - )} - - ({ - ..._.omit(item, ['label', 'locale']), - ...item.props, - label: - item.locale || item.locale === undefined - ? intl.formatMessage({ id: item.label }) - : item.label - })) - }} - > - - - - )} - - ); -}; - -export default DropdownButtons; diff --git a/src/components/dynamic-form/components/field-item.tsx b/src/components/dynamic-form/components/field-item.tsx deleted file mode 100644 index 24966ab4..00000000 --- a/src/components/dynamic-form/components/field-item.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import ComponentsMap from '@/components/seal-form/config/components'; -import { SealFormItemProps } from '@/components/seal-form/types'; -import { Form } from 'antd'; -import React from 'react'; - -interface FieldItemProps extends SealFormItemProps { - widget: keyof typeof ComponentsMap; - name: string; -} - -const FieldItem: React.FC = (props) => { - const { name, widget, required = [], ...rest } = props; - - const Component = ComponentsMap[widget]; - - return ; -}; - -export default FieldItem; diff --git a/src/components/dynamic-form/components/form-widget.tsx b/src/components/dynamic-form/components/form-widget.tsx deleted file mode 100644 index d741dfa2..00000000 --- a/src/components/dynamic-form/components/form-widget.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import ComponentsMap from '@/components/seal-form/config/components'; -import { FormWidgetProps } from '../config/types'; - -const FormWidget: React.FC< - FormWidgetProps & { - onChange?: (data: any) => void; - disabled?: boolean; - } -> = ({ - widget, - title: label, - required, - placeholder, - options, - description, - enum: enumValues, - style, - value, - min, - max, - status, - checked, - isInFormItems, - disabled, - onChange -}) => { - const Component = ComponentsMap[widget]; - - const optionList = enumValues?.map((item: string | number) => ({ - label: item, - value: item - })); - - return Component ? ( - - ) : null; -}; - -export default FormWidget; diff --git a/src/components/dynamic-form/components/list-map.tsx b/src/components/dynamic-form/components/list-map.tsx deleted file mode 100644 index 93785bd5..00000000 --- a/src/components/dynamic-form/components/list-map.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import Wrapper from '@/components/label-selector/wrapper'; -import { MinusOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; -import React, { useEffect, useMemo } from 'react'; -import styled from 'styled-components'; -import { statusType } from '../config/types'; -import FormWidget from './form-widget'; - -const RowWrapper = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; -`; - -const WidgetBox = styled.div` - display: flex; - align-items: center; - gap: 8px; - width: 100%; -`; -interface ListMapProps { - minItems?: number; - dataList: any[]; - label?: React.ReactNode; - btnText?: string; - requiredFields?: string[]; - validateStatusList?: Record[]; - properties: Record; - disabled?: boolean; - onAdd?: (data: any[]) => void; - onDelete?: (deletedItem: any, data: any[]) => void; - onChange?: (data: any) => void; -} - -interface ListItemProps { - schemaList: any[]; - data: Record; - disabled?: boolean; - validateStatus?: Record; - onChange?: (data: any) => void; -} - -const ListItem: React.FC = ({ - schemaList, - data, - onChange, - validateStatus, - disabled -}) => { - const handleValueChange = (name: string, target: any) => { - if (target?.target?.type === 'checkbox') { - const checked = target.target?.checked; - onChange?.({ [name]: checked }); - } else { - const value = target?.target ? target.target.value : target; - onChange?.({ [name]: value }); - } - }; - return ( - <> - {schemaList.map((schema: any) => ( - handleValueChange(schema.name, target)} - /> - ))} - - ); -}; - -const ListMap: React.FC = ({ - dataList = [], - label, - btnText, - properties = {}, - requiredFields = [], - minItems = 0, - validateStatusList = [], - disabled, - onAdd, - onDelete, - onChange -}) => { - const [items, setItems] = React.useState(dataList || []); - - const schemaList = useMemo(() => { - const list = Object.entries(properties).map(([key, value]) => ({ - ...value, - required: requiredFields.includes(key), - name: key - })); - return list; - }, [properties, requiredFields]); - - const handleOnAdd = () => { - const keys = Object.keys(properties); - const newItems = [ - ...items, - { ...keys.reduce((acc, key) => ({ ...acc, [key]: '' }), {}) } - ]; - setItems(newItems); - onAdd?.(newItems); - }; - - const handleDelete = (index: number) => { - const deleteItem = items[index]; - const newItems = items.filter((_, i) => i !== index); - setItems(newItems); - onDelete?.(deleteItem, newItems); - }; - - const handleItemChange = (index: number, data: { [key: string]: any }) => { - const newItems = [...items]; - newItems[index] = { ...newItems[index], ...data }; - setItems(newItems); - onChange?.(newItems); - }; - - useEffect(() => { - if (!dataList.length && minItems > 0) { - handleOnAdd(); - } - }, []); - - useEffect(() => { - setItems(dataList); - }, [dataList]); - - return ( - - {items.map((item, index) => ( - - - handleItemChange(index, value)} - /> - - {!disabled && ( - - )} - {showCancel && ( - - )} - - ); -}; - -export default FormButtons; diff --git a/src/components/highlight-code/code-viewer-dark.tsx b/src/components/highlight-code/code-viewer-dark.tsx deleted file mode 100644 index 1d5b8bb0..00000000 --- a/src/components/highlight-code/code-viewer-dark.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import CodeViewer from './code-viewer'; -import './styles/dark.less'; - -interface CodeViewerProps { - code: string; - copyValue?: string; - lang: string; - autodetect?: boolean; - ignoreIllegals?: boolean; - copyable?: boolean; - height?: string | number; - style?: React.CSSProperties; - xScrollable?: boolean; -} -const DarkViewer: React.FC = (props) => { - const { - code, - copyValue, - lang, - autodetect, - ignoreIllegals, - copyable, - height = 'auto', - xScrollable = false - } = props || {}; - - return ( - - ); -}; - -export default DarkViewer; diff --git a/src/components/highlight-code/code-viewer-light.tsx b/src/components/highlight-code/code-viewer-light.tsx deleted file mode 100644 index 7de78b8e..00000000 --- a/src/components/highlight-code/code-viewer-light.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import CodeViewer from './code-viewer'; -import './styles/light.less'; - -interface CodeViewerProps { - code: string; - copyValue?: string; - lang: string; - autodetect?: boolean; - ignoreIllegals?: boolean; - copyable?: boolean; - height?: string | number; - style?: React.CSSProperties; - xScrollable?: boolean; -} -const LightViewer: React.FC = (props) => { - const { - code, - copyValue, - lang, - autodetect, - ignoreIllegals, - copyable, - style, - height = 'auto', - xScrollable = false - } = props || {}; - - return ( - - ); -}; - -export default LightViewer; diff --git a/src/components/highlight-code/code-viewer.tsx b/src/components/highlight-code/code-viewer.tsx deleted file mode 100644 index 8aa5ec18..00000000 --- a/src/components/highlight-code/code-viewer.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import classNames from 'classnames'; -import hljs from 'highlight.js'; -import { useMemo } from 'react'; -import styled from 'styled-components'; -import CopyButton from '../copy-button'; -import { escapeHtml } from './utils'; - -interface CodeViewerProps { - code: string; - copyValue?: string; - lang: string; - autodetect?: boolean; - ignoreIllegals?: boolean; - copyable?: boolean; - height?: string | number; - theme?: 'light' | 'dark'; - style?: React.CSSProperties; - xScrollable?: boolean; -} - -interface CodeHeaderProps { - copyValue: string; - copyable: boolean; - lang: string; - theme: 'light' | 'dark'; -} - -const CodeHeaderWrapper = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - height: 32px; - padding: 0 12px; - font-size: 12px; - color: var(--ant-color-text-tertiary); - background-color: #fafafa; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - &.dark { - background-color: var(--color-editor-header-bg); - color: rgba(255, 255, 255, 0.65); - } -`; - -const Wrapper = styled.div` - border-radius: var(--border-radius-mini); - &:hover { - .custome-scrollbar { - &::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar-thumb); - border-radius: 4px; - } - } - } -`; - -const CodeHeader: React.FC = ({ - copyValue, - lang, - theme, - copyable -}) => { - if (!copyable) { - return null; - } - return ( - - {lang} - - - ); -}; - -const CodeViewer: React.FC = (props) => { - const { - code = '', - copyValue, - lang, - autodetect = true, - ignoreIllegals = true, - copyable = true, - height = 'auto', - style, - xScrollable = false - } = props || {}; - - const highlightedCode = useMemo(() => { - const autodetectLang = autodetect && !lang; - const cannotDetectLanguage = !autodetectLang && !hljs.getLanguage(lang); - let className = ''; - - if (!cannotDetectLanguage) { - className = `hljs ${lang}`; - } - - // No idea what language to use, return raw code - if (cannotDetectLanguage) { - console.warn(`The language "${lang}" you specified could not be found.`); - return { - value: escapeHtml(code), - className: className - }; - } - - if (autodetectLang) { - const result = hljs.highlightAuto(code); - return { - value: result.value, - className: className - }; - } - const result = hljs.highlight(code, { - language: lang, - ignoreIllegals: ignoreIllegals - }); - return { - value: result.value, - className: className - }; - }, [code, lang, autodetect, ignoreIllegals]); - - return ( - - -
-        
-      
-
- ); -}; - -export default CodeViewer; diff --git a/src/components/highlight-code/index.tsx b/src/components/highlight-code/index.tsx deleted file mode 100644 index c731f63e..00000000 --- a/src/components/highlight-code/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import useUserSettings from '@/hooks/use-user-settings'; -import React from 'react'; -import CodeViewerDark from './code-viewer-dark'; -import CodeViewerLight from './code-viewer-light'; -import './styles/index.less'; - -const HighlightCode: React.FC<{ - code: string; - lang?: string; - copyable?: boolean; - theme?: 'light' | 'dark'; - fixedTheme?: 'light' | 'dark'; - xScrollable?: boolean; - height?: string | number; - style?: React.CSSProperties; - copyValue?: string; -}> = (props) => { - const { - style, - code, - copyValue, - lang = 'bash', - copyable = true, - theme, - height = 'auto', - xScrollable = false - } = props; - - const { userSettings } = useUserSettings(); - - const currentTheme = React.useMemo(() => { - const res = theme || userSettings.theme === 'realDark' ? 'dark' : 'light'; - return res; - }, [theme, userSettings.theme]); - - return ( -
- {currentTheme === 'dark' ? ( - - ) : ( - - )} -
- ); -}; - -export default HighlightCode; diff --git a/src/components/highlight-code/styles/dark.less b/src/components/highlight-code/styles/dark.less deleted file mode 100644 index dae651b5..00000000 --- a/src/components/highlight-code/styles/dark.less +++ /dev/null @@ -1,106 +0,0 @@ -// @ts-ingore -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em; -} - -code.hljs { - padding: 3px 5px; -} - -/* - -Atom One Dark by Daniel Gamage -Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax - -base: #282c34 -mono-1: #abb2bf -mono-2: #818896 -mono-3: #5c6370 -hue-1: #56b6c2 -hue-2: #61aeee -hue-3: #c678dd -hue-4: #98c379 -hue-5: #e06c75 -hue-5-2: #be5046 -hue-6: #d19a66 -hue-6-2: #e6c07b - -*/ -.code-pre.dark { - .hljs { - color: #abb2bf; - background: var(--color-editor-dark); - } - - .hljs-comment, - .hljs-quote { - color: #5c6370; - font-style: italic; - } - - .hljs-doctag, - .hljs-keyword, - .hljs-formula { - color: #c678dd; - } - - .hljs-section, - .hljs-name, - .hljs-selector-tag, - .hljs-deletion, - .hljs-subst { - color: #e06c75; - } - - .hljs-literal { - color: #56b6c2; - } - - .hljs-string, - .hljs-regexp, - .hljs-addition, - .hljs-attribute, - .hljs-meta .hljs-string { - color: #98c379; - } - - .hljs-attr, - .hljs-variable, - .hljs-template-variable, - .hljs-type, - .hljs-selector-class, - .hljs-selector-attr, - .hljs-selector-pseudo, - .hljs-number { - color: #d19a66; - } - - .hljs-symbol, - .hljs-bullet, - .hljs-link, - .hljs-meta, - .hljs-selector-id, - .hljs-title { - color: #61aeee; - } - - .hljs-built_in, - .hljs-title.class_, - .hljs-class .hljs-title { - color: #e6c07b; - } - - .hljs-emphasis { - font-style: italic; - } - - .hljs-strong { - font-weight: bold; - } - - .hljs-link { - text-decoration: underline; - } -} diff --git a/src/components/highlight-code/styles/index.less b/src/components/highlight-code/styles/index.less deleted file mode 100644 index 59d75006..00000000 --- a/src/components/highlight-code/styles/index.less +++ /dev/null @@ -1,84 +0,0 @@ -.high-light-wrapper { - text-align: left; - font-size: var(--font-size-code); - - .hljs { - font-weight: var(--font-weight-normal); - padding-inline: 0; - padding-block: 1.2em; - - &::-webkit-scrollbar { - height: var(--scrollbar-size); - } - - &::-webkit-scrollbar-thumb { - background-color: transparent; - border-radius: 4px; - } - - &::-webkit-scrollbar-track { - background-color: transparent; - } - - &.light { - &:hover { - &::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar-thumb); - border-radius: 4px; - } - } - } - - &.dark { - &:hover { - &::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-handle-light-bg); - border-radius: 4px; - } - } - } - } - - .code-pre { - padding-inline: 12px 12px; - border-radius: 0 0 var(--border-radius-mini) var(--border-radius-mini); - position: relative; - white-space: pre-wrap; - - code { - line-height: 1.6; - } - - &.copyable { - padding-inline: 12px 32px; - } - - .copy-button { - position: absolute; - top: 6px; - right: 6px; - } - - &.dark { - background-color: var(--color-editor-dark); - - &:hover { - &::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-handle-light-bg); - border-radius: 4px; - } - } - } - - &.light { - background-color: rgb(250, 250, 250); - - &:hover { - &::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar-thumb); - border-radius: 4px; - } - } - } - } -} diff --git a/src/components/highlight-code/styles/light.less b/src/components/highlight-code/styles/light.less deleted file mode 100644 index d0702775..00000000 --- a/src/components/highlight-code/styles/light.less +++ /dev/null @@ -1,106 +0,0 @@ -// @ts-ingore -pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em; -} - -code.hljs { - padding: 3px 5px; -} - -/* - -Atom One Light by Daniel Gamage -Original One Light Syntax theme from https://github.com/atom/one-light-syntax - -base: #fafafa -mono-1: #383a42 -mono-2: #686b77 -mono-3: #a0a1a7 -hue-1: #0184bb -hue-2: #4078f2 -hue-3: #a626a4 -hue-4: #50a14f -hue-5: #e45649 -hue-5-2: #c91243 -hue-6: #986801 -hue-6-2: #c18401 - -*/ -.code-pre.light { - .hljs { - color: #383a42; - background: var(--color-editor-light); - } - - .hljs-comment, - .hljs-quote { - color: #a0a1a7; - font-style: italic; - } - - .hljs-doctag, - .hljs-keyword, - .hljs-formula { - color: #a626a4; - } - - .hljs-section, - .hljs-name, - .hljs-selector-tag, - .hljs-deletion, - .hljs-subst { - color: #e45649; - } - - .hljs-literal { - color: #0184bb; - } - - .hljs-string, - .hljs-regexp, - .hljs-addition, - .hljs-attribute, - .hljs-meta .hljs-string { - color: #50a14f; - } - - .hljs-attr, - .hljs-variable, - .hljs-template-variable, - .hljs-type, - .hljs-selector-class, - .hljs-selector-attr, - .hljs-selector-pseudo, - .hljs-number { - color: #986801; - } - - .hljs-symbol, - .hljs-bullet, - .hljs-link, - .hljs-meta, - .hljs-selector-id, - .hljs-title { - color: #4078f2; - } - - .hljs-built_in, - .hljs-title.class_, - .hljs-class .hljs-title { - color: #c18401; - } - - .hljs-emphasis { - font-style: italic; - } - - .hljs-strong { - font-weight: bold; - } - - .hljs-link { - text-decoration: underline; - } -} diff --git a/src/components/highlight-code/utils.ts b/src/components/highlight-code/utils.ts deleted file mode 100644 index 76fbce35..00000000 --- a/src/components/highlight-code/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function escapeHtml(value: string): string { - return value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} diff --git a/src/components/icon-font/iconfont/iconfont.css b/src/components/icon-font/iconfont/iconfont.css deleted file mode 100644 index 11df1d0d..00000000 --- a/src/components/icon-font/iconfont/iconfont.css +++ /dev/null @@ -1,915 +0,0 @@ -@font-face { - font-family: iconfont; /* Project id 4613488 */ - src: url('iconfont.woff2?t=1770985961668') format('woff2'), - url('iconfont.woff?t=1770985961668') format('woff'), - url('iconfont.ttf?t=1770985961668') format('truetype'); -} - -.iconfont { - font-family: iconfont !important; - font-size: 16px; - font-style: normal; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-grafana::before { - content: "\e61e"; -} - -.icon-chart::before { - content: "\e6da"; -} - -.icon-monitor-02::before { - content: "\e6d9"; -} - -.icon-monitor::before { - content: "\e6d8"; -} - -.icon-metrics::before { - content: "\e6d6"; -} - -.icon-export::before { - content: "\e6d7"; -} - -.icon-license::before { - content: "\e6d5"; -} - -.icon-community::before { - content: "\e6d3"; -} - -.icon-person::before { - content: "\e6d4"; -} - -.icon-public::before { - content: "\e6d2"; -} - -.icon-charger::before { - content: "\e6d0"; -} - -.icon-disabled::before { - content: "\e6d1"; -} - -.icon-source::before { - content: "\e6cf"; -} - -.icon-Serviceprovider::before { - content: "\e630"; -} - -.icon-database::before { - content: "\e6ce"; -} - -.icon-filters::before { - content: "\e6cc"; -} - -.icon-speed-filled::before { - content: "\e6cb"; -} - -.icon-shield::before { - content: "\e6c9"; -} - -.icon-shield-filled::before { - content: "\e6ca"; -} - -.icon-openai::before { - content: "\e61d"; -} - -.icon-anthropic::before { - content: "\e74d"; -} - -.icon-doubao::before { - content: "\e618"; -} - -.icon-qwen::before { - content: "\e8b2"; -} - -.icon-deepseek::before { - content: "\e61c"; -} - -.icon-extension-outline::before { - content: "\e6c7"; -} - -.icon-extension-filled::before { - content: "\e6c8"; -} - -.icon-thead::before { - content: "\e617"; -} - -.icon-video-filled02::before { - content: "\e6c5"; -} - -.icon-video-outline::before { - content: "\e6c6"; -} - -.icon-video::before { - content: "\e6c3"; -} - -.icon-video-filled::before { - content: "\e6c4"; -} - -.icon-refresh::before { - content: "\e6c2"; -} - -.icon-settings-02::before { - content: "\e6bf"; -} - -.icon-arrow_forward::before { - content: "\e6c0"; -} - -.icon-logout::before { - content: "\e6c1"; -} - -.icon-amd-logo::before { - content: "\e6be"; -} - -.icon-cloud::before { - content: "\e6bc"; -} - -.icon-server02::before { - content: "\e6bd"; -} - -.icon-drag_handle::before { - content: "\e6b9"; -} - -.icon-basic::before { - content: "\e6bb"; -} - -.icon-settings::before { - content: "\e6b8"; -} - -.icon-speed::before { - content: "\e6ba"; -} - -.icon-permission::before { - content: "\e6b6"; -} - -.icon-captive_portal::before { - content: "\e6b7"; -} - -.icon-lock_open_right::before { - content: "\e6b3"; -} - -.icon-lock_person::before { - content: "\e6b4"; -} - -.icon-lock_open::before { - content: "\e6b5"; -} - -.icon-question::before { - content: "\e6b2"; -} - -.icon-aws::before { - content: "\e616"; -} - -.icon-aws1::before { - content: "\e62f"; -} - -.icon-manage_user::before { - content: "\e6b1"; -} - -.icon-private::before { - content: "\e6b0"; -} - -.icon-stop3::before { - content: "\e6af"; -} - -.icon-version::before { - content: "\e6ae"; -} - -.icon-edit-content::before { - content: "\e6ab"; -} - -.icon-code_block::before { - content: "\e6ac"; -} - -.icon-parameters::before { - content: "\e6ad"; -} - -.icon-backend-filled::before { - content: "\e6a9"; -} - -.icon-backend::before { - content: "\e6aa"; -} - -.icon-down2::before { - content: "\e6a7"; -} - -.icon-nvidia2::before { - content: "\e60d"; -} - -.icon-centos::before { - content: "\e6cd"; -} - -.icon-redhat::before { - content: "\ec7b"; -} - -.icon-ubuntu::before { - content: "\edd3"; -} - -.icon-debian::before { - content: "\eb74"; -} - -.icon-alma-linux::before { - content: "\e6a8"; -} - -.icon-fedora::before { - content: "\e61a"; -} - -.icon-rocky-linux::before { - content: "\e620"; -} - -.icon-nvidia1::before { - content: "\e980"; -} - -.icon-nvidia::before { - content: "\e60c"; -} - -.icon-amd::before { - content: "\e6a6"; -} - -.icon-huawei::before { - content: "\e615"; -} - -.icon-metax::before { - content: "\e6a4"; -} - -.icon-ascend::before { - content: "\e6a3"; -} - -.icon-huaweicloud::before { - content: "\e614"; -} - -.icon-alicloud::before { - content: "\e784"; -} - -.icon-tencentcloud::before { - content: "\e609"; -} - -.icon-cluster2-outline::before { - content: "\e6a1"; -} - -.icon-cluster2-filled::before { - content: "\e6a2"; -} - -.icon-cluster-filled::before { - content: "\e69f"; -} - -.icon-cluster-outline::before { - content: "\e6a0"; -} - -.icon-admin-user::before { - content: "\e69d"; -} - -.icon-user::before { - content: "\e69e"; -} - -.icon-detail-info::before { - content: "\e69b"; -} - -.icon-docker::before { - content: "\e69c"; -} - -.icon-digitalocean::before { - content: "\eb79"; -} - -.icon-rocket-launch1::before { - content: "\e699"; -} - -.icon-rocket-launch-fill::before { - content: "\e69a"; -} - -.icon-credential-filled::before { - content: "\e697"; -} - -.icon-credential-outline::before { - content: "\e698"; -} - -.icon-k8s-filled::before { - content: "\e63e"; -} - -.icon-k8s-outline::before { - content: "\e64b"; -} - -.icon-huggingface1::before { - content: "\e605"; -} - -.icon-modelscope_light::before { - content: "\e696"; -} - -.icon-catalog1::before { - content: "\e691"; -} - -.icon-chat-filled::before { - content: "\e692"; -} - -.icon-chat::before { - content: "\e693"; -} - -.icon-files-filled::before { - content: "\e694"; -} - -.icon-files::before { - content: "\e695"; -} - -.icon-models-filled::before { - content: "\e682"; -} - -.icon-image-filled::before { - content: "\e683"; -} - -.icon-audio1::before { - content: "\e684"; -} - -.icon-image1::before { - content: "\e685"; -} - -.icon-audio-filled::before { - content: "\e686"; -} - -.icon-reranker-filled::before { - content: "\e687"; -} - -.icon-embedding-filled::before { - content: "\e68a"; -} - -.icon-models::before { - content: "\e68b"; -} - -.icon-reranker::before { - content: "\e68c"; -} - -.icon-embedding::before { - content: "\e68d"; -} - -.icon-gpu-filled::before { - content: "\e68e"; -} - -.icon-catalog-filled::before { - content: "\e68f"; -} - -.icon-gpu1::before { - content: "\e690"; -} - -.icon-language::before { - content: "\e679"; -} - -.icon-help::before { - content: "\e67a"; -} - -.icon-key-filled::before { - content: "\e67b"; -} - -.icon-key::before { - content: "\e67d"; -} - -.icon-resources::before { - content: "\e67e"; -} - -.icon-users::before { - content: "\e67f"; -} - -.icon-resources-filled::before { - content: "\e680"; -} - -.icon-users-filled::before { - content: "\e681"; -} - -.icon-model::before { - content: "\e676"; -} - -.icon-model-filled::before { - content: "\e678"; -} - -.icon-layers-filled::before { - content: "\e671"; -} - -.icon-layers::before { - content: "\e673"; -} - -.icon-experiment::before { - content: "\e674"; -} - -.icon-experiment-filled::before { - content: "\e675"; -} - -.icon-dashboard::before { - content: "\e66d"; -} - -.icon-dashboard-filled::before { - content: "\e66e"; -} - -.icon-expand-left::before { - content: "\ea48"; -} - -.icon-expand-right::before { - content: "\ea49"; -} - -.icon-users-fill::before { - content: "\e677"; -} - -.icon-layers-fill::before { - content: "\e89d"; -} - -.icon-key-fill::before { - content: "\e80e"; -} - -.icon-server-fill::before { - content: "\e7a3"; -} - -.icon-model-fill::before { - content: "\e7b7"; -} - -.icon-left_panel_close::before { - content: "\e668"; -} - -.icon-left_panel_open::before { - content: "\e669"; -} - -.icon-playcircle-fill::before { - content: "\e665"; -} - -.icon-stopcircle-fill::before { - content: "\e666"; -} - -.icon-play-speed::before { - content: "\e856"; -} - -.icon-more::before { - content: "\e62e"; -} - -.icon-play::before { - content: "\e9f9"; -} - -.icon-pause::before { - content: "\e713"; -} - -.icon-dark_theme::before { - content: "\e646"; -} - -.icon-theme-auto-1::before { - content: "\e660"; -} - -.icon-theme-auto::before { - content: "\e663"; -} - -.icon-auto-theme-1::before { - content: "\e664"; -} - -.icon-auto-theme::before { - content: "\e662"; -} - -.icon-cols_3::before { - content: "\e65c"; -} - -.icon-cols_6::before { - content: "\e65d"; -} - -.icon-cols_2::before { - content: "\e65e"; -} - -.icon-cols_4::before { - content: "\e65f"; -} - -.icon-a-save1::before { - content: "\e65b"; -} - -.icon-uncollapse_all::before { - content: "\e657"; -} - -.icon-collapse::before { - content: "\e656"; -} - -.icon-rocket-launch::before { - content: "\e689"; -} - -.icon-user-filled::before { - content: "\e625"; -} - -.icon-assistant::before { - content: "\e62d"; -} - -.icon-assistant-filled::before { - content: "\e847"; -} - -.icon-save3::before { - content: "\e655"; -} - -.icon-fankuifaqs::before { - content: "\e7bf"; -} - -.icon-issues::before { - content: "\e816"; -} - -.icon-neicun::before { - content: "\e688"; -} - -.icon-collapse_all::before { - content: "\e66f"; -} - -.icon-down::before { - content: "\e654"; -} - -.icon-fenxiang::before { - content: "\e604"; -} - -.icon-mosaic-2::before { - content: "\e64f"; -} - -.icon-stars::before { - content: "\e8a8"; -} - -.icon-mosaic::before { - content: "\e636"; -} - -.icon-outline-play::before { - content: "\e653"; -} - -.icon-SelectionInverse::before { - content: "\eace"; -} - -.icon-justice1::before { - content: "\e652"; -} - -.icon-New_img::before { - content: "\e733"; -} - -.icon-new_release_outlined::before { - content: "\e66c"; -} - -.icon-new-releases::before { - content: "\e60f"; -} - -.icon-catalog::before { - content: "\e62b"; -} - -.icon-save2::before { - content: "\e635"; -} - -.icon-left-template::before { - content: "\e62a"; -} - -.icon-logs::before { - content: "\e6ec"; -} - -.icon-gpu::before { - content: "\e71e"; -} - -.icon-filled-gpu::before { - content: "\e6de"; -} - -.icon-outline-gpu::before { - content: "\e641"; -} - -.icon-ts-tubiao_webserver::before { - content: "\e716"; -} - -.icon-server::before { - content: "\e66a"; -} - -.icon-host::before { - content: "\e7c6"; -} - -.icon-playcircle::before { - content: "\e80f"; -} - -.icon-recreate::before { - content: "\e6a5"; -} - -.icon-save1::before { - content: "\e647"; -} - -.icon-save::before { - content: "\e67c"; -} - -.icon-upload_image::before { - content: "\e613"; -} - -.icon-sound-wave::before { - content: "\e619"; -} - -.icon-rank1::before { - content: "\e7cb"; -} - -.icon-cube::before { - content: "\e769"; -} - -.icon-speaker-slash::before { - content: "\ebb6"; -} - -.icon-random::before { - content: "\e603"; -} - -.icon-suijisenlin::before { - content: "\e60e"; -} - -.icon-stop2::before { - content: "\e8db"; -} - -.icon-stop::before { - content: "\e60b"; -} - -.icon-image::before { - content: "\e62c"; -} - -.icon-SpeakerSlash::before { - content: "\e661"; -} - -.icon-SpeakerHigh::before { - content: "\e670"; -} - -.icon-user_voice::before { - content: "\e667"; -} - -.icon-audio::before { - content: "\e985"; -} - -.icon-hard-disk::before { - content: "\eb1d"; -} - -.icon-new::before { - content: "\e612"; -} - -.icon-tu2::before { - content: "\e607"; -} - -.icon-robot::before { - content: "\e634"; -} - -.icon-robot1::before { - content: "\e602"; -} - -.icon-aizhineng::before { - content: "\e672"; -} - -.icon-copy::before { - content: "\e720"; -} - -.icon-AIzhineng::before { - content: "\e608"; -} - -.icon-clear::before { - content: "\e60a"; -} - -.icon-keyboard::before { - content: "\e61b"; -} - -.icon-networkerror::before { - content: "\e624"; -} - -.icon-external-link::before { - content: "\e66b"; -} - -.icon-huggingface::before { - content: "\e7d1"; -} - -.icon-ollama::before { - content: "\e601"; -} - -.icon-a-layout6-line::before { - content: "\e9ef"; -} - -.icon-a-Layout5::before { - content: "\e610"; -} - -.icon-English::before { - content: "\e8b3"; -} - -.icon-chinese::before { - content: "\e611"; -} - -.icon-yingguo::before { - content: "\e606"; -} - -.icon-code::before { - content: "\e84f"; -} - -.icon-stop1::before { - content: "\e783"; -} - -.icon-command::before { - content: "\e600"; -} - diff --git a/src/components/icon-font/iconfont/iconfont.js b/src/components/icon-font/iconfont/iconfont.js deleted file mode 100644 index 275cb03f..00000000 --- a/src/components/icon-font/iconfont/iconfont.js +++ /dev/null @@ -1 +0,0 @@ -window._iconfont_svg_string_4613488='',(a=>{var l=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var v,c,z,t,m,o=function(l,h){h.parentNode.insertBefore(l,h)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}v=function(){var l,h=document.createElement("div");h.innerHTML=a._iconfont_svg_string_4613488,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(l=document.body).firstChild?o(h,l.firstChild):l.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(v,0):(c=function(){document.removeEventListener("DOMContentLoaded",c,!1),v()},document.addEventListener("DOMContentLoaded",c,!1)):document.attachEvent&&(z=v,t=a.document,m=!1,i(),t.onreadystatechange=function(){"complete"==t.readyState&&(t.onreadystatechange=null,q())})}function q(){m||(m=!0,z())}function i(){try{t.documentElement.doScroll("left")}catch(l){return void setTimeout(i,50)}q()}})(window); \ No newline at end of file diff --git a/src/components/icon-font/iconfont/iconfont.json b/src/components/icon-font/iconfont/iconfont.json deleted file mode 100644 index 9d53aee0..00000000 --- a/src/components/icon-font/iconfont/iconfont.json +++ /dev/null @@ -1,1584 +0,0 @@ -{ - "id": "4613488", - "name": "gpustack", - "font_family": "iconfont", - "css_prefix_text": "icon-", - "description": "", - "glyphs": [ - { - "icon_id": "31689363", - "name": "grafana (2)", - "font_class": "grafana", - "unicode": "e61e", - "unicode_decimal": 58910 - }, - { - "icon_id": "46845894", - "name": "chart", - "font_class": "chart", - "unicode": "e6da", - "unicode_decimal": 59098 - }, - { - "icon_id": "46844385", - "name": "monitor", - "font_class": "monitor-02", - "unicode": "e6d9", - "unicode_decimal": 59097 - }, - { - "icon_id": "46842822", - "name": "monitor", - "font_class": "monitor", - "unicode": "e6d8", - "unicode_decimal": 59096 - }, - { - "icon_id": "46821022", - "name": "metrics", - "font_class": "metrics", - "unicode": "e6d6", - "unicode_decimal": 59094 - }, - { - "icon_id": "46812763", - "name": "export", - "font_class": "export", - "unicode": "e6d7", - "unicode_decimal": 59095 - }, - { - "icon_id": "46802651", - "name": "license", - "font_class": "license", - "unicode": "e6d5", - "unicode_decimal": 59093 - }, - { - "icon_id": "46790594", - "name": "crowdsource_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "community", - "unicode": "e6d3", - "unicode_decimal": 59091 - }, - { - "icon_id": "46790593", - "name": "person_celebrate_24dp_1F1F1F_FILL0_wght300_GRAD0_o", - "font_class": "person", - "unicode": "e6d4", - "unicode_decimal": 59092 - }, - { - "icon_id": "46790615", - "name": "public_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "public", - "unicode": "e6d2", - "unicode_decimal": 59090 - }, - { - "icon_id": "46783187", - "name": "charger", - "font_class": "charger", - "unicode": "e6d0", - "unicode_decimal": 59088 - }, - { - "icon_id": "46783186", - "name": "disabled", - "font_class": "disabled", - "unicode": "e6d1", - "unicode_decimal": 59089 - }, - { - "icon_id": "46775558", - "name": "source", - "font_class": "source", - "unicode": "e6cf", - "unicode_decimal": 59087 - }, - { - "icon_id": "2735079", - "name": "Service provider", - "font_class": "Serviceprovider", - "unicode": "e630", - "unicode_decimal": 58928 - }, - { - "icon_id": "46719213", - "name": "database_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "database", - "unicode": "e6ce", - "unicode_decimal": 59086 - }, - { - "icon_id": "46714731", - "name": "manage_search_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz", - "font_class": "filters", - "unicode": "e6cc", - "unicode_decimal": 59084 - }, - { - "icon_id": "46703055", - "name": "speed_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz24", - "font_class": "speed-filled", - "unicode": "e6cb", - "unicode_decimal": 59083 - }, - { - "icon_id": "46702137", - "name": "shield_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "shield", - "unicode": "e6c9", - "unicode_decimal": 59081 - }, - { - "icon_id": "46702136", - "name": "shield_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz24", - "font_class": "shield-filled", - "unicode": "e6ca", - "unicode_decimal": 59082 - }, - { - "icon_id": "42735715", - "name": "openai", - "font_class": "openai", - "unicode": "e61d", - "unicode_decimal": 58909 - }, - { - "icon_id": "39934600", - "name": "anthropic", - "font_class": "anthropic", - "unicode": "e74d", - "unicode_decimal": 59213 - }, - { - "icon_id": "40532962", - "name": "doubao", - "font_class": "doubao", - "unicode": "e618", - "unicode_decimal": 58904 - }, - { - "icon_id": "43558740", - "name": "QWen", - "font_class": "qwen", - "unicode": "e8b2", - "unicode_decimal": 59570 - }, - { - "icon_id": "43616544", - "name": "deepseek-copy", - "font_class": "deepseek", - "unicode": "e61c", - "unicode_decimal": 58908 - }, - { - "icon_id": "46650646", - "name": "extension_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "extension-outline", - "unicode": "e6c7", - "unicode_decimal": 59079 - }, - { - "icon_id": "46650645", - "name": "extension_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz24", - "font_class": "extension-filled", - "unicode": "e6c8", - "unicode_decimal": 59080 - }, - { - "icon_id": "43960229", - "name": "thead", - "font_class": "thead", - "unicode": "e617", - "unicode_decimal": 58903 - }, - { - "icon_id": "46626616", - "name": "video-filled02", - "font_class": "video-filled02", - "unicode": "e6c5", - "unicode_decimal": 59077 - }, - { - "icon_id": "46626615", - "name": "video-outline", - "font_class": "video-outline", - "unicode": "e6c6", - "unicode_decimal": 59078 - }, - { - "icon_id": "46616487", - "name": "video", - "font_class": "video", - "unicode": "e6c3", - "unicode_decimal": 59075 - }, - { - "icon_id": "46616486", - "name": "video-filled", - "font_class": "video-filled", - "unicode": "e6c4", - "unicode_decimal": 59076 - }, - { - "icon_id": "46569108", - "name": "refresh", - "font_class": "refresh", - "unicode": "e6c2", - "unicode_decimal": 59074 - }, - { - "icon_id": "46569091", - "name": "settings", - "font_class": "settings-02", - "unicode": "e6bf", - "unicode_decimal": 59071 - }, - { - "icon_id": "46569092", - "name": "arrow_forward", - "font_class": "arrow_forward", - "unicode": "e6c0", - "unicode_decimal": 59072 - }, - { - "icon_id": "46569093", - "name": "logout", - "font_class": "logout", - "unicode": "e6c1", - "unicode_decimal": 59073 - }, - { - "icon_id": "46548884", - "name": "amd-logo", - "font_class": "amd-logo", - "unicode": "e6be", - "unicode_decimal": 59070 - }, - { - "icon_id": "46296038", - "name": "cloud", - "font_class": "cloud", - "unicode": "e6bc", - "unicode_decimal": 59068 - }, - { - "icon_id": "46296037", - "name": "server", - "font_class": "server02", - "unicode": "e6bd", - "unicode_decimal": 59069 - }, - { - "icon_id": "46272083", - "name": "drag_handle", - "font_class": "drag_handle", - "unicode": "e6b9", - "unicode_decimal": 59065 - }, - { - "icon_id": "46055620", - "name": "basic", - "font_class": "basic", - "unicode": "e6bb", - "unicode_decimal": 59067 - }, - { - "icon_id": "46055265", - "name": "settings", - "font_class": "settings", - "unicode": "e6b8", - "unicode_decimal": 59064 - }, - { - "icon_id": "46055263", - "name": "speed", - "font_class": "speed", - "unicode": "e6ba", - "unicode_decimal": 59066 - }, - { - "icon_id": "46039838", - "name": "permission", - "font_class": "permission", - "unicode": "e6b6", - "unicode_decimal": 59062 - }, - { - "icon_id": "46039837", - "name": "captive_portal", - "font_class": "captive_portal", - "unicode": "e6b7", - "unicode_decimal": 59063 - }, - { - "icon_id": "46001737", - "name": "lock_open_right", - "font_class": "lock_open_right", - "unicode": "e6b3", - "unicode_decimal": 59059 - }, - { - "icon_id": "46001736", - "name": "lock_person", - "font_class": "lock_person", - "unicode": "e6b4", - "unicode_decimal": 59060 - }, - { - "icon_id": "46001735", - "name": "lock_open", - "font_class": "lock_open", - "unicode": "e6b5", - "unicode_decimal": 59061 - }, - { - "icon_id": "45937227", - "name": "question_mark_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz", - "font_class": "question", - "unicode": "e6b2", - "unicode_decimal": 59058 - }, - { - "icon_id": "10786885", - "name": "aws", - "font_class": "aws", - "unicode": "e616", - "unicode_decimal": 58902 - }, - { - "icon_id": "12014352", - "name": "aws", - "font_class": "aws1", - "unicode": "e62f", - "unicode_decimal": 58927 - }, - { - "icon_id": "45704693", - "name": "manage_accounts_24dp_1F1F1F_FILL1_wght300_GRAD0_op", - "font_class": "manage_user", - "unicode": "e6b1", - "unicode_decimal": 59057 - }, - { - "icon_id": "45702717", - "name": "private", - "font_class": "private", - "unicode": "e6b0", - "unicode_decimal": 59056 - }, - { - "icon_id": "45702697", - "name": "stop", - "font_class": "stop3", - "unicode": "e6af", - "unicode_decimal": 59055 - }, - { - "icon_id": "45688107", - "name": "version", - "font_class": "version", - "unicode": "e6ae", - "unicode_decimal": 59054 - }, - { - "icon_id": "45631011", - "name": "contract_edit_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz", - "font_class": "edit-content", - "unicode": "e6ab", - "unicode_decimal": 59051 - }, - { - "icon_id": "45631010", - "name": "code_blocks_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "code_block", - "unicode": "e6ac", - "unicode_decimal": 59052 - }, - { - "icon_id": "45631009", - "name": "settings_applications_24dp_1F1F1F_FILL0_wght300_GR", - "font_class": "parameters", - "unicode": "e6ad", - "unicode_decimal": 59053 - }, - { - "icon_id": "45623375", - "name": "bolt_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz24", - "font_class": "backend-filled", - "unicode": "e6a9", - "unicode_decimal": 59049 - }, - { - "icon_id": "45623374", - "name": "bolt_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24", - "font_class": "backend", - "unicode": "e6aa", - "unicode_decimal": 59050 - }, - { - "icon_id": "45554217", - "name": "arrow_down", - "font_class": "down2", - "unicode": "e6a7", - "unicode_decimal": 59047 - }, - { - "icon_id": "11155543", - "name": "nvidia", - "font_class": "nvidia2", - "unicode": "e60d", - "unicode_decimal": 58893 - }, - { - "icon_id": "11425971", - "name": "centos", - "font_class": "centos", - "unicode": "e6cd", - "unicode_decimal": 59085 - }, - { - "icon_id": "15378743", - "name": "redhat", - "font_class": "redhat", - "unicode": "ec7b", - "unicode_decimal": 60539 - }, - { - "icon_id": "6808499", - "name": "Ubuntu", - "font_class": "ubuntu", - "unicode": "edd3", - "unicode_decimal": 60883 - }, - { - "icon_id": "15378275", - "name": "debian", - "font_class": "debian", - "unicode": "eb74", - "unicode_decimal": 60276 - }, - { - "icon_id": "38395859", - "name": "almalinux", - "font_class": "alma-linux", - "unicode": "e6a8", - "unicode_decimal": 59048 - }, - { - "icon_id": "43280042", - "name": "linux-fedora", - "font_class": "fedora", - "unicode": "e61a", - "unicode_decimal": 58906 - }, - { - "icon_id": "43280143", - "name": "linux-rocky_linux", - "font_class": "rocky-linux", - "unicode": "e620", - "unicode_decimal": 58912 - }, - { - "icon_id": "36227448", - "name": "nvidia", - "font_class": "nvidia1", - "unicode": "e980", - "unicode_decimal": 59776 - }, - { - "icon_id": "40723469", - "name": "nvidia", - "font_class": "nvidia", - "unicode": "e60c", - "unicode_decimal": 58892 - }, - { - "icon_id": "45463937", - "name": "amd", - "font_class": "amd", - "unicode": "e6a6", - "unicode_decimal": 59046 - }, - { - "icon_id": "24164616", - "name": "华为", - "font_class": "huawei", - "unicode": "e615", - "unicode_decimal": 58901 - }, - { - "icon_id": "45461362", - "name": "metax", - "font_class": "metax", - "unicode": "e6a4", - "unicode_decimal": 59044 - }, - { - "icon_id": "45459906", - "name": "ascend", - "font_class": "ascend", - "unicode": "e6a3", - "unicode_decimal": 59043 - }, - { - "icon_id": "8152996", - "name": "HUAWEI", - "font_class": "huaweicloud", - "unicode": "e614", - "unicode_decimal": 58900 - }, - { - "icon_id": "34290284", - "name": "alicloud", - "font_class": "alicloud", - "unicode": "e784", - "unicode_decimal": 59268 - }, - { - "icon_id": "36654764", - "name": "tencent", - "font_class": "tencentcloud", - "unicode": "e609", - "unicode_decimal": 58889 - }, - { - "icon_id": "45310241", - "name": "cluster2-outline", - "font_class": "cluster2-outline", - "unicode": "e6a1", - "unicode_decimal": 59041 - }, - { - "icon_id": "45310242", - "name": "cluster2-filled", - "font_class": "cluster2-filled", - "unicode": "e6a2", - "unicode_decimal": 59042 - }, - { - "icon_id": "45310070", - "name": "cluster-filled", - "font_class": "cluster-filled", - "unicode": "e69f", - "unicode_decimal": 59039 - }, - { - "icon_id": "45310069", - "name": "cluster-outline", - "font_class": "cluster-outline", - "unicode": "e6a0", - "unicode_decimal": 59040 - }, - { - "icon_id": "45240470", - "name": "admin-user", - "font_class": "admin-user", - "unicode": "e69d", - "unicode_decimal": 59037 - }, - { - "icon_id": "45240469", - "name": "user", - "font_class": "user", - "unicode": "e69e", - "unicode_decimal": 59038 - }, - { - "icon_id": "45221182", - "name": "detail-info", - "font_class": "detail-info", - "unicode": "e69b", - "unicode_decimal": 59035 - }, - { - "icon_id": "45102077", - "name": "Docker", - "font_class": "docker", - "unicode": "e69c", - "unicode_decimal": 59036 - }, - { - "icon_id": "15378290", - "name": "digitalocean", - "font_class": "digitalocean", - "unicode": "eb79", - "unicode_decimal": 60281 - }, - { - "icon_id": "45040302", - "name": "rocket-launch", - "font_class": "rocket-launch1", - "unicode": "e699", - "unicode_decimal": 59033 - }, - { - "icon_id": "45040301", - "name": "rocket-launch-fill", - "font_class": "rocket-launch-fill", - "unicode": "e69a", - "unicode_decimal": 59034 - }, - { - "icon_id": "45028316", - "name": "credential", - "font_class": "credential-filled", - "unicode": "e697", - "unicode_decimal": 59031 - }, - { - "icon_id": "45028315", - "name": "credential", - "font_class": "credential-outline", - "unicode": "e698", - "unicode_decimal": 59032 - }, - { - "icon_id": "37601405", - "name": "k8s", - "font_class": "k8s-filled", - "unicode": "e63e", - "unicode_decimal": 58942 - }, - { - "icon_id": "41673174", - "name": "k8s", - "font_class": "k8s-outline", - "unicode": "e64b", - "unicode_decimal": 58955 - }, - { - "icon_id": "40865292", - "name": "huggingface", - "font_class": "huggingface1", - "unicode": "e605", - "unicode_decimal": 58885 - }, - { - "icon_id": "43616582", - "name": "modelscope_light", - "font_class": "modelscope_light", - "unicode": "e696", - "unicode_decimal": 59030 - }, - { - "icon_id": "44776845", - "name": "catalog", - "font_class": "catalog1", - "unicode": "e691", - "unicode_decimal": 59025 - }, - { - "icon_id": "44776848", - "name": "chat-filled", - "font_class": "chat-filled", - "unicode": "e692", - "unicode_decimal": 59026 - }, - { - "icon_id": "44776846", - "name": "chat", - "font_class": "chat", - "unicode": "e693", - "unicode_decimal": 59027 - }, - { - "icon_id": "44776841", - "name": "files-filled", - "font_class": "files-filled", - "unicode": "e694", - "unicode_decimal": 59028 - }, - { - "icon_id": "44776840", - "name": "files", - "font_class": "files", - "unicode": "e695", - "unicode_decimal": 59029 - }, - { - "icon_id": "44776851", - "name": "models-filled", - "font_class": "models-filled", - "unicode": "e682", - "unicode_decimal": 59010 - }, - { - "icon_id": "44776857", - "name": "image-filled", - "font_class": "image-filled", - "unicode": "e683", - "unicode_decimal": 59011 - }, - { - "icon_id": "44776856", - "name": "audio", - "font_class": "audio1", - "unicode": "e684", - "unicode_decimal": 59012 - }, - { - "icon_id": "44776855", - "name": "image", - "font_class": "image1", - "unicode": "e685", - "unicode_decimal": 59013 - }, - { - "icon_id": "44776850", - "name": "audio-filled", - "font_class": "audio-filled", - "unicode": "e686", - "unicode_decimal": 59014 - }, - { - "icon_id": "44776849", - "name": "reranker-filled", - "font_class": "reranker-filled", - "unicode": "e687", - "unicode_decimal": 59015 - }, - { - "icon_id": "44776852", - "name": "embedding-filled", - "font_class": "embedding-filled", - "unicode": "e68a", - "unicode_decimal": 59018 - }, - { - "icon_id": "44776853", - "name": "models", - "font_class": "models", - "unicode": "e68b", - "unicode_decimal": 59019 - }, - { - "icon_id": "44776844", - "name": "reranker", - "font_class": "reranker", - "unicode": "e68c", - "unicode_decimal": 59020 - }, - { - "icon_id": "44776854", - "name": "embedding", - "font_class": "embedding", - "unicode": "e68d", - "unicode_decimal": 59021 - }, - { - "icon_id": "44776843", - "name": "gpu-filled", - "font_class": "gpu-filled", - "unicode": "e68e", - "unicode_decimal": 59022 - }, - { - "icon_id": "44776847", - "name": "catalog-filled", - "font_class": "catalog-filled", - "unicode": "e68f", - "unicode_decimal": 59023 - }, - { - "icon_id": "44776842", - "name": "gpu", - "font_class": "gpu1", - "unicode": "e690", - "unicode_decimal": 59024 - }, - { - "icon_id": "44685190", - "name": "language", - "font_class": "language", - "unicode": "e679", - "unicode_decimal": 59001 - }, - { - "icon_id": "44685189", - "name": "help", - "font_class": "help", - "unicode": "e67a", - "unicode_decimal": 59002 - }, - { - "icon_id": "44684306", - "name": "key-filled", - "font_class": "key-filled", - "unicode": "e67b", - "unicode_decimal": 59003 - }, - { - "icon_id": "44684302", - "name": "key", - "font_class": "key", - "unicode": "e67d", - "unicode_decimal": 59005 - }, - { - "icon_id": "44684304", - "name": "resources", - "font_class": "resources", - "unicode": "e67e", - "unicode_decimal": 59006 - }, - { - "icon_id": "44684305", - "name": "users", - "font_class": "users", - "unicode": "e67f", - "unicode_decimal": 59007 - }, - { - "icon_id": "44684303", - "name": "resources-filled", - "font_class": "resources-filled", - "unicode": "e680", - "unicode_decimal": 59008 - }, - { - "icon_id": "44684301", - "name": "users-filled", - "font_class": "users-filled", - "unicode": "e681", - "unicode_decimal": 59009 - }, - { - "icon_id": "44684150", - "name": "model", - "font_class": "model", - "unicode": "e676", - "unicode_decimal": 58998 - }, - { - "icon_id": "44684149", - "name": "model-filled", - "font_class": "model-filled", - "unicode": "e678", - "unicode_decimal": 59000 - }, - { - "icon_id": "44684090", - "name": "layers-filled", - "font_class": "layers-filled", - "unicode": "e671", - "unicode_decimal": 58993 - }, - { - "icon_id": "44684091", - "name": "layers", - "font_class": "layers", - "unicode": "e673", - "unicode_decimal": 58995 - }, - { - "icon_id": "44684086", - "name": "experiment", - "font_class": "experiment", - "unicode": "e674", - "unicode_decimal": 58996 - }, - { - "icon_id": "44684085", - "name": "experiment", - "font_class": "experiment-filled", - "unicode": "e675", - "unicode_decimal": 58997 - }, - { - "icon_id": "44684021", - "name": "dashboard", - "font_class": "dashboard", - "unicode": "e66d", - "unicode_decimal": 58989 - }, - { - "icon_id": "44684020", - "name": "dashboard-filled", - "font_class": "dashboard-filled", - "unicode": "e66e", - "unicode_decimal": 58990 - }, - { - "icon_id": "40068272", - "name": "expand-left", - "font_class": "expand-left", - "unicode": "ea48", - "unicode_decimal": 59976 - }, - { - "icon_id": "40068278", - "name": "expand-right", - "font_class": "expand-right", - "unicode": "ea49", - "unicode_decimal": 59977 - }, - { - "icon_id": "1350497", - "name": "sq-users", - "font_class": "users-fill", - "unicode": "e677", - "unicode_decimal": 58999 - }, - { - "icon_id": "13691715", - "name": "layers", - "font_class": "layers-fill", - "unicode": "e89d", - "unicode_decimal": 59549 - }, - { - "icon_id": "14267903", - "name": "key-skeleton-alt", - "font_class": "key-fill", - "unicode": "e80e", - "unicode_decimal": 59406 - }, - { - "icon_id": "14341823", - "name": "cloud-server-solid", - "font_class": "server-fill", - "unicode": "e7a3", - "unicode_decimal": 59299 - }, - { - "icon_id": "40764011", - "name": "icon-model-selected", - "font_class": "model-fill", - "unicode": "e7b7", - "unicode_decimal": 59319 - }, - { - "icon_id": "44668359", - "name": "left_panel_close_36dp_1F1F1F_FILL0_wght400_GRAD0_o", - "font_class": "left_panel_close", - "unicode": "e668", - "unicode_decimal": 58984 - }, - { - "icon_id": "44668360", - "name": "left_panel_open_36dp_1F1F1F_FILL0_wght400_GRAD0_op", - "font_class": "left_panel_open", - "unicode": "e669", - "unicode_decimal": 58985 - }, - { - "icon_id": "44650419", - "name": "play circle-fill", - "font_class": "playcircle-fill", - "unicode": "e665", - "unicode_decimal": 58981 - }, - { - "icon_id": "44650418", - "name": "stop circle-fill", - "font_class": "stopcircle-fill", - "unicode": "e666", - "unicode_decimal": 58982 - }, - { - "icon_id": "8592899", - "name": "play-speed", - "font_class": "play-speed", - "unicode": "e856", - "unicode_decimal": 59478 - }, - { - "icon_id": "10941162", - "name": "more", - "font_class": "more", - "unicode": "e62e", - "unicode_decimal": 58926 - }, - { - "icon_id": "703373", - "name": "play", - "font_class": "play", - "unicode": "e9f9", - "unicode_decimal": 59897 - }, - { - "icon_id": "736882", - "name": "pause", - "font_class": "pause", - "unicode": "e713", - "unicode_decimal": 59155 - }, - { - "icon_id": "44028047", - "name": "dark_theme", - "font_class": "dark_theme", - "unicode": "e646", - "unicode_decimal": 58950 - }, - { - "icon_id": "44094552", - "name": "theme-auto-1", - "font_class": "theme-auto-1", - "unicode": "e660", - "unicode_decimal": 58976 - }, - { - "icon_id": "44094551", - "name": "theme-auto", - "font_class": "theme-auto", - "unicode": "e663", - "unicode_decimal": 58979 - }, - { - "icon_id": "44094550", - "name": "theme", - "font_class": "auto-theme-1", - "unicode": "e664", - "unicode_decimal": 58980 - }, - { - "icon_id": "44092227", - "name": "routine_36dp_1F1F1F_FILL0_wght400_GRAD0_opsz24", - "font_class": "auto-theme", - "unicode": "e662", - "unicode_decimal": 58978 - }, - { - "icon_id": "44086334", - "name": "cols_3", - "font_class": "cols_3", - "unicode": "e65c", - "unicode_decimal": 58972 - }, - { - "icon_id": "44086333", - "name": "cols_6", - "font_class": "cols_6", - "unicode": "e65d", - "unicode_decimal": 58973 - }, - { - "icon_id": "44086332", - "name": "cols_2", - "font_class": "cols_2", - "unicode": "e65e", - "unicode_decimal": 58974 - }, - { - "icon_id": "44086331", - "name": "cols_4", - "font_class": "cols_4", - "unicode": "e65f", - "unicode_decimal": 58975 - }, - { - "icon_id": "44065884", - "name": "save", - "font_class": "a-save1", - "unicode": "e65b", - "unicode_decimal": 58971 - }, - { - "icon_id": "43808249", - "name": "uncollapse_all", - "font_class": "uncollapse_all", - "unicode": "e657", - "unicode_decimal": 58967 - }, - { - "icon_id": "43807590", - "name": "collapse", - "font_class": "collapse", - "unicode": "e656", - "unicode_decimal": 58966 - }, - { - "icon_id": "22575690", - "name": "rocket-launch", - "font_class": "rocket-launch", - "unicode": "e689", - "unicode_decimal": 59017 - }, - { - "icon_id": "6050653", - "name": "user", - "font_class": "user-filled", - "unicode": "e625", - "unicode_decimal": 58917 - }, - { - "icon_id": "37486588", - "name": "assistant", - "font_class": "assistant", - "unicode": "e62d", - "unicode_decimal": 58925 - }, - { - "icon_id": "43220044", - "name": "AIAssistant", - "font_class": "assistant-filled", - "unicode": "e847", - "unicode_decimal": 59463 - }, - { - "icon_id": "43626126", - "name": "save", - "font_class": "save3", - "unicode": "e655", - "unicode_decimal": 58965 - }, - { - "icon_id": "20186129", - "name": "反馈faqs", - "font_class": "fankuifaqs", - "unicode": "e7bf", - "unicode_decimal": 59327 - }, - { - "icon_id": "42800361", - "name": "issues", - "font_class": "issues", - "unicode": "e816", - "unicode_decimal": 59414 - }, - { - "icon_id": "8211955", - "name": "ram", - "font_class": "neicun", - "unicode": "e688", - "unicode_decimal": 59016 - }, - { - "icon_id": "37125227", - "name": "collapse_all", - "font_class": "collapse_all", - "unicode": "e66f", - "unicode_decimal": 58991 - }, - { - "icon_id": "43363249", - "name": "down", - "font_class": "down", - "unicode": "e654", - "unicode_decimal": 58964 - }, - { - "icon_id": "159932", - "name": "search", - "font_class": "fenxiang", - "unicode": "e604", - "unicode_decimal": 58884 - }, - { - "icon_id": "40678285", - "name": "mosaic", - "font_class": "mosaic-2", - "unicode": "e64f", - "unicode_decimal": 58959 - }, - { - "icon_id": "22859935", - "name": "stars", - "font_class": "stars", - "unicode": "e8a8", - "unicode_decimal": 59560 - }, - { - "icon_id": "39566959", - "name": "mosaic", - "font_class": "mosaic", - "unicode": "e636", - "unicode_decimal": 58934 - }, - { - "icon_id": "43256197", - "name": "play circle", - "font_class": "outline-play", - "unicode": "e653", - "unicode_decimal": 58963 - }, - { - "icon_id": "27116743", - "name": "SelectionInverse", - "font_class": "SelectionInverse", - "unicode": "eace", - "unicode_decimal": 60110 - }, - { - "icon_id": "43035036", - "name": "justice", - "font_class": "justice1", - "unicode": "e652", - "unicode_decimal": 58962 - }, - { - "icon_id": "1740853", - "name": "New_img", - "font_class": "New_img", - "unicode": "e733", - "unicode_decimal": 59187 - }, - { - "icon_id": "9745732", - "name": "new_release_outlined", - "font_class": "new_release_outlined", - "unicode": "e66c", - "unicode_decimal": 58988 - }, - { - "icon_id": "40667024", - "name": "new-releases", - "font_class": "new-releases", - "unicode": "e60f", - "unicode_decimal": 58895 - }, - { - "icon_id": "41920256", - "name": "catalog", - "font_class": "catalog", - "unicode": "e62b", - "unicode_decimal": 58923 - }, - { - "icon_id": "13851416", - "name": "save", - "font_class": "save2", - "unicode": "e635", - "unicode_decimal": 58933 - }, - { - "icon_id": "3238366", - "name": "模板", - "font_class": "left-template", - "unicode": "e62a", - "unicode_decimal": 58922 - }, - { - "icon_id": "26746089", - "name": "logs", - "font_class": "logs", - "unicode": "e6ec", - "unicode_decimal": 59116 - }, - { - "icon_id": "1132892", - "name": "gpu", - "font_class": "gpu", - "unicode": "e71e", - "unicode_decimal": 59166 - }, - { - "icon_id": "38418656", - "name": "GPUxCPU", - "font_class": "filled-gpu", - "unicode": "e6de", - "unicode_decimal": 59102 - }, - { - "icon_id": "7556336", - "name": "gpu", - "font_class": "outline-gpu", - "unicode": "e641", - "unicode_decimal": 58945 - }, - { - "icon_id": "8040181", - "name": "webserver", - "font_class": "ts-tubiao_webserver", - "unicode": "e716", - "unicode_decimal": 59158 - }, - { - "icon_id": "6587064", - "name": "server", - "font_class": "server", - "unicode": "e66a", - "unicode_decimal": 58986 - }, - { - "icon_id": "40493325", - "name": "host", - "font_class": "host", - "unicode": "e7c6", - "unicode_decimal": 59334 - }, - { - "icon_id": "6151262", - "name": "play circle", - "font_class": "playcircle", - "unicode": "e80f", - "unicode_decimal": 59407 - }, - { - "icon_id": "25197526", - "name": "recreate", - "font_class": "recreate", - "unicode": "e6a5", - "unicode_decimal": 59045 - }, - { - "icon_id": "12689724", - "name": "save", - "font_class": "save1", - "unicode": "e647", - "unicode_decimal": 58951 - }, - { - "icon_id": "15838524", - "name": "save", - "font_class": "save", - "unicode": "e67c", - "unicode_decimal": 59004 - }, - { - "icon_id": "8782480", - "name": "upload_image", - "font_class": "upload_image", - "unicode": "e613", - "unicode_decimal": 58899 - }, - { - "icon_id": "14464883", - "name": "sound-wave", - "font_class": "sound-wave", - "unicode": "e619", - "unicode_decimal": 58905 - }, - { - "icon_id": "37200543", - "name": "rank", - "font_class": "rank1", - "unicode": "e7cb", - "unicode_decimal": 59339 - }, - { - "icon_id": "5127275", - "name": "cube", - "font_class": "cube", - "unicode": "e769", - "unicode_decimal": 59241 - }, - { - "icon_id": "28326047", - "name": "speaker-slash", - "font_class": "speaker-slash", - "unicode": "ebb6", - "unicode_decimal": 60342 - }, - { - "icon_id": "13059398", - "name": "随机", - "font_class": "random", - "unicode": "e603", - "unicode_decimal": 58883 - }, - { - "icon_id": "5978743", - "name": "random-forest", - "font_class": "suijisenlin", - "unicode": "e60e", - "unicode_decimal": 58894 - }, - { - "icon_id": "15617444", - "name": "stop", - "font_class": "stop2", - "unicode": "e8db", - "unicode_decimal": 59611 - }, - { - "icon_id": "8327181", - "name": "stop", - "font_class": "stop", - "unicode": "e60b", - "unicode_decimal": 58891 - }, - { - "icon_id": "714031", - "name": "image", - "font_class": "image", - "unicode": "e62c", - "unicode_decimal": 58924 - }, - { - "icon_id": "23563264", - "name": "SpeakerSlash", - "font_class": "SpeakerSlash", - "unicode": "e661", - "unicode_decimal": 58977 - }, - { - "icon_id": "23563318", - "name": "SpeakerHigh", - "font_class": "SpeakerHigh", - "unicode": "e670", - "unicode_decimal": 58992 - }, - { - "icon_id": "41955357", - "name": " user_voice", - "font_class": "user_voice", - "unicode": "e667", - "unicode_decimal": 58983 - }, - { - "icon_id": "29174196", - "name": "audio", - "font_class": "audio", - "unicode": "e985", - "unicode_decimal": 59781 - }, - { - "icon_id": "40073465", - "name": "hard-disk", - "font_class": "hard-disk", - "unicode": "eb1d", - "unicode_decimal": 60189 - }, - { - "icon_id": "12717507", - "name": "new", - "font_class": "new", - "unicode": "e612", - "unicode_decimal": 58898 - }, - { - "icon_id": "36135335", - "name": "mdp-template-modelscope", - "font_class": "tu2", - "unicode": "e607", - "unicode_decimal": 58887 - }, - { - "icon_id": "10710692", - "name": "robot", - "font_class": "robot", - "unicode": "e634", - "unicode_decimal": 58932 - }, - { - "icon_id": "12070718", - "name": "robot", - "font_class": "robot1", - "unicode": "e602", - "unicode_decimal": 58882 - }, - { - "icon_id": "10791813", - "name": "ai智能", - "font_class": "aizhineng", - "unicode": "e672", - "unicode_decimal": 58994 - }, - { - "icon_id": "19418384", - "name": "copy", - "font_class": "copy", - "unicode": "e720", - "unicode_decimal": 59168 - }, - { - "icon_id": "40757798", - "name": "AI智能", - "font_class": "AIzhineng", - "unicode": "e608", - "unicode_decimal": 58888 - }, - { - "icon_id": "36263088", - "name": "clear", - "font_class": "clear", - "unicode": "e60a", - "unicode_decimal": 58890 - }, - { - "icon_id": "5297059", - "name": "keyboard", - "font_class": "keyboard", - "unicode": "e61b", - "unicode_decimal": 58907 - }, - { - "icon_id": "11539157", - "name": "network error", - "font_class": "networkerror", - "unicode": "e624", - "unicode_decimal": 58916 - }, - { - "icon_id": "14465073", - "name": "external-link", - "font_class": "external-link", - "unicode": "e66b", - "unicode_decimal": 58987 - }, - { - "icon_id": "38465464", - "name": "huggingface", - "font_class": "huggingface", - "unicode": "e7d1", - "unicode_decimal": 59345 - }, - { - "icon_id": "40568411", - "name": "ollama", - "font_class": "ollama", - "unicode": "e601", - "unicode_decimal": 58881 - }, - { - "icon_id": "24341846", - "name": "layout 6-line", - "font_class": "a-layout6-line", - "unicode": "e9ef", - "unicode_decimal": 59887 - }, - { - "icon_id": "31345353", - "name": "a-Layout5", - "font_class": "a-Layout5", - "unicode": "e610", - "unicode_decimal": 58896 - }, - { - "icon_id": "10031665", - "name": "English", - "font_class": "English", - "unicode": "e8b3", - "unicode_decimal": 59571 - }, - { - "icon_id": "9134244", - "name": "chinese", - "font_class": "chinese", - "unicode": "e611", - "unicode_decimal": 58897 - }, - { - "icon_id": "12176725", - "name": "english", - "font_class": "yingguo", - "unicode": "e606", - "unicode_decimal": 58886 - }, - { - "icon_id": "34453176", - "name": "code", - "font_class": "code", - "unicode": "e84f", - "unicode_decimal": 59471 - }, - { - "icon_id": "12579515", - "name": "stop", - "font_class": "stop1", - "unicode": "e783", - "unicode_decimal": 59267 - }, - { - "icon_id": "1842082", - "name": "command", - "font_class": "command", - "unicode": "e600", - "unicode_decimal": 58880 - } - ] -} diff --git a/src/components/icon-font/iconfont/iconfont.ttf b/src/components/icon-font/iconfont/iconfont.ttf deleted file mode 100644 index f29f715f442b562f13bdf42d1adeac0c6058a5f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67840 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

xN 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 diff --git a/src/components/icon-font/iconfont/iconfont.woff b/src/components/icon-font/iconfont/iconfont.woff deleted file mode 100644 index 9ce077cdc9b68730d207422032739085a22fc00b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42984 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 diff --git a/src/components/icon-font/iconfont/iconfont.woff2 b/src/components/icon-font/iconfont/iconfont.woff2 deleted file mode 100644 index b67f3b75373ff8a54af52c15ba1cc80c03ede7f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36380 zcmV(~K+nH-Pew8T0RR910FE303jhEB0SN#A0FBE40RR9100000000000000000000 z0000SR0d!Go-hi7%NT;{F99|JBm;*43xh%c1Rw>3X9txZ8xqzhRIeF_Lv{nAxM^)q zoKXmD8~~^c2WMrlaR5Y+N3;L`KP#z>v3<~f4w|55WdwO3&`5e8+m3wqJ{*Z-Pnm|u zChXoCd=gKy5{`*8>`pE>PHrP2lSC%&;gD$X1HI#;w2JCpanR!`dK0mQb5;|Td7fpw z;L#@1#GHJq20~OKl9(Wgg%DMH@lkM5x@Um9$Qe8g;Xm4s{h6IR`yA8e+ANbuBqFWT3VdnJOCACMuGQ3b6!jqg z5O6SG!r%i`1Dpp_e-3~lm|HhiEPypIdaz(4M%1W9F=B%S6@$^Gw1n}3iiCv)3fdT$ zC|EDPk^dG}@kYL{49s&OH%cc}aN_2MAy)BX1)%(XshaP=kQ5xISJCi%lz;AWZaFL9 z5yS}u=v_eJALX>FYDvj%m0F4y5MRr$J>W?i?4WHq_8`Ujbl?8E?|z1%T~6kRAmgOD z+n3+#EPR7LLL)|KL^{|M*~1~G(pQwux#(RLE}F&|+D5)NG(ibNP-+OWOSVuD((Um!F_OSG*m_SW3j~q;V-#5pv0cr26%vwE3X z8N>!WJudFaJA}4}l0@u_rAW!~Pj9NdEtcsZ=>qj*<_zU1IrMT&on3S;$`=+O-dik) zw*V+CNJ?0MlDMD(-hz^_faFh7VIcCS6!abUfuarzO5a%U0zhfbAa!>`IqNE?m@7AB z$YpomRmh#kMOT@i&2cWSFCkAj#S;KuH>SnJ;zsy!|Z4K)!F zNl014p5MKW_8-?UHv2y|-Q`|+vC_uIC{RH#*#5tRf!>9}f=hlX))cwpw`XVZg^0aj zt*zs)O*3J8N5$H-Ouc&=GUDkjP{wU0eB}G~p|L^6C?X&(y|gaX($VMre_hiJz3_*< z_1?yD0RbGG{H)i^dhp5lS)F9lUp-TS=HXpYq3pc2Wsi=_R|o-_a7wX|?mvI<{Ot=O z6l}4_i%8lrk`?UX2&Z3Md~x-q+1nLmRtIdi9-Hgo!n@e|G(J8sV{n}(dc;*0?w8F<2p$DG#xkV6l9 zuICX)9(DBYd+fQ_-uv9Tu>F4fA8_D72k*4=F1zlw<2KuFwe^%OCQY6&aoqS#Hh!ke zYQI(P+kULeJdIsHG;LMXWtOK=oP>VhdX~M_=wgT_s=}g}3`V_Hrx7Y~#iy;?O|jb! zS8Wsvc&y@RV=T4TVYiL4&N53pGSNtD#Mw?Lb|^gF@qb3WN74EIy%d*j(Z9DMi5R6cHOJ5>}_kc$K2y zK}E$k6b;`|bi79~@VsJTQ^mqYij6T82eVUL%uMm{g5qOcCBSEtu=b=)1T#`%44|Y~ zPbu*^rNRwLk4KaN4=E$wpiHI+_+nXv7L(H36;jhDuZWL z0o$tz?o@T$ry96NHF3LY;TqM(b*h7F)u61~hH!-%;Yu~eRceB()fAVg8Q!GkxLhr8 zsaoPPwZge-jq}t7Z&O>Gt#&v^?eUa4;0x-A2h<5CsWVPi7u=?ICaNI)C0$> zCr(f=oT%P7MtyLa`r=si!y)RALp1=0X&|1{ARMm2I6^~kq=w=s4a3nIj@>l^duSx~ z)F|ww(b!vKu#d*#R*l2;8jpQ70sCnp_SYmFpvgE;Q*e-`;$Tg~PMVILH3Pe7CU(^< z?55e+QFE}3=3-mT!&aJ)t+fDCv=Ccp5hiIdCTj^MXelOY8OCWj#%l#O(MoKrRd`0L zv7y#rHCl^xv<|D#daOztup({5O0)?p(`KwdTd+KB#d5R_%hGl%Lp!h}?Zi^F3ro{( zEKYl{1ntG5v=0l@ek?`@um~N*f^-NA(P1n=M=&oP#e8%O^V4z6Lnkm7oy6R93UknD z%t>c3E1kt`bPm(gdCWi;FdbdQv~&q;&}B?RS1=V_#VERl(R3Xn=>~?=O^l#h7)G}- zgzjJ{-Nj(Khe3271L-jySAdUrls{wOBL0kxC;2mRo_Gu=B|gSEi2HFRVkX>t@8B=Q*Z5Bk7xGV8aSAR+oQf}VxR-wu ziFa{NVr|@y*b?vN@G}1mXdi+{`*YCG@C5J+06Ut9mgg(L+mXnr8=F5dMw*5*n8B?9 zY2iCQE?pDSb(XPfm=d*%U1N({m;$k|L28WxRWja%Rz%p&H8OuT+!^&Y%($A|S`yjp zD(IAVGIgah%#qVWqtojQx;Eral3PYbeFw3rJLIT1<8On5q3E8*RwRyG8Hk@}cZH*f%M= z-h1*#)l|jG3Y@83UAIDBZBi2bMYbUv_s31n(~c$w(VH7$ZQgsOGOhP(=9KVtND z>hV$cOk)Q{Q>|=OHpDD9)|z>ab!0D(ZDyDI5`c5y^~4-BA1OrNaJ@dpRZ*D#Z56rm z%;BkLp1M9=v8;-|nAeXT&@|ryXP%hPb2~oT)GG%tdv7hAg^l9NUw|>-EoLiBU(2m- zrM!2sEv31qq8?jaU2g5}0KaF4^XB0q#uPsD!$UE}i&IW`Gz=oJX|j%?7(uxBQztOi z>*oQ-iU%}Xy;8t%msNf1m9A$#fBOYscR~HM8V1AAScg=$tV*W3i$tbDa~9cSRyEHG zS>~i*cZV!1ilKlIV_L$>YT3qTz!Pd18%D{7E+Przp^08|4b{}c1E_?P-5DGt)`5l& z)~@?$uqr}{u>F+xEzJw7fK}6SQI?8GVNa0z-KlW_$ClBI=!-H`_>A63^!qu86TLdb zXm{3j0S%jMr@(Gn}A*iIwi^F!t!77Qi8aKKl}cZw@Qxn z;-har-}fnU`Dd+ttFfPi!(GUWPg@URBzl%(WJU$~M1tO-&q&hp_diY>!Pdpv%bjOy z4`vTd-iI?4+ye}7eP(AoG^>kF#m>$tR=MNlB7S9VEd;5@JpcY@CUVZZNA|4m7Ori$ z(6_el0zjolOjugHT4=t_;h%fp$avb4E|eY>0?8r_*$J%8{}$y1L0aaHd`z-_(0;G- z?|)IU&)qV7i&;XXdp|<0uPo`%Y;(U^7(MY@e_R@$Q(${9JhR!6@+ewd<`Hb3FX?ZO zX0EY(fO!m2X+{^#2t-Oug4)2>lBrO|tn(#nuO%^wBkvP}5ZPScJNH|XeDAgPYhT8r z=bGe_wtJxMOyQ8x-FuqEoN4>iHDK_AqFpwf5QzIAa`c706Vq@bX)Z)i5Ky3^IyUlx z`p_@Y{+{}Ei@0Kp)Zs$x6S8mp9+YWs;hjoD}ZW){Ay1p?Jf28l!r&Oq@ zdT)!s*s?Y_7&X-(5tH-4H~Ojj26Yq)ia*C`^vjaQAXm>vH+lO2=Z=ox-c;{QaqSwo zS}41c_AH0n_P4yGX3fp0cI)fEIQ9LmZ~KsYndGzr-AtvlNA1?O^a z5V+{`$5O_a1CG}=#wlrX%O{`ciRHQav4nFc>OUlQs$=-FLtWbH#&YCuzmXwoyT7km zaL9Uh|GT|_a7Qj%l#k35@4W`B)S@R1@Y~l*sD-bGCCxa98-63)x?_KJK3iWzPd&CV z7FBsrz9CiHhs~iej3G^^1xVA8#tV_Mv4JZPzDvg5-`GE?kgQF8VV}N_x;p%}@;j2> zQOq~8PNi*n_^tVWM&|$j;!l*i|CzMP@RDODusDo+)f+(ffbyi*b1VL_#m?DM-7bKH zE;1M`b;5k53=rZba+yZ~#Ez<-1#@xf|M9!8WYgwq|LGEvOYm2O<+9c8!XvZqMpAWz z`!8N<1G{Z-dD23^a-#UNUFT^fB|R%s&~!9|TF*v)SoMWy#A@EW?9&(ni5_!_dX=+D zk*^R-(m2P<-9jbO8|9piheR&GMpg?I{;g`Kg0hlJ*$X_?%uY>S242$EBF(I;6M!Cr zrn2rhr>zEM>hjAy=@!o7Uxov`gQBw62+p>lENY{Y-aKJ7%Q+LTs z+@ROhT#X^)9by)|jTnj?X)Mo4`_aylD=q$|VrxT)EMY|%Z%;IE#ZC%x05w08vxO328j#Jo-k2`FP=JOOzoBPwPf zIA%y`d8Z9tqf@>p3kV60!{J!yZ=IwUF?5yqao%?7l#1tZ^B_C2=;Xmj@t{x1j^lK>P-C%$bSPq+GjpKX>9xMF-WT$w zI7@`Kv4ffV0~?`}L84avu_(pN+r~grSjJljtRSW*HET!9p(o=hEYzhPfmtO{Ckpdb zNU0G?hr`|sLVdQr-6l8|3Io-m)n5pI(VWB!Ft$4O;Ka+_KFUQY(DA^$ST_(mwrVvm*+>RyP~P;Yehtb>Ia!e zmGy;LeXc4i_N%$6^>a&!nF7oH(q?b5gBYxNIo0sY*+rY9L}nFRWO7BdR83j)9A!;f zPHCPu>xexXa(Hw%;e39Lqjp1_0REX`xWKfP_ZxTr&`x?n4ysTXz7%D46ejtoL|JKj zqL>m5QEIfMUk}Y_h>nb%Y~SvPF(+J9?%z)dNPMbrB_21Ug#y!J0}g1gydl!^u5z_5 z7fS2#-uiaiF}7DzNqP5kmyP}}-%*ofNQLU7h*#hG>;=90+1qv;557BqKXvnA1qTmq z%F2i2pY01WqYK4 z?`P=JVdF^6l8loKVK|c$OGOdPVG7ZURo1f^G*f&rXF2Und?GSErznlRyFJN9 zunK}kafFLV=ATGoAmK8(xANu4)*1YIqW^*Mo5`4Q}kG!bv& zc6*yi9Q2F8*o0xg(=lKY0D2~ct+}d-SNsfJ5qH|~7oDPdzE>1@#E}Sl$EWO+>Q^PN>U9cD>2I}XU4mofzh^r)YITt^#oO^=Y z(e;ZA_mnUcC795+RTa#y@1b&2P6%@(u6FCuZO} zX18TDLKG@#WE2S>0~Z7sgYlqu6EOR&85cf6M*z;q8&G)o*Rr(qLf~$S&V%ThWEETV zBS~367}H9cS=}MV;JH%Yi8Ho*bfafxJf8ifjQvUe|7HK$=31!U@DozWZ%OG=On&D} zr>1@JuEGvJdgni@SLM&&QgKh04*=sq1NWsS9EA*63y&g4_By7L%+_;G?E4>^+8n!^VdK^Tg&wFPCoRk zjI&bgH2L1XRH__YjHE_88T`%Zb<)snULJpa4B6LZ?TOc?_ZYjVI@WwatN!mF(t*3l z>7_a=bcmaBe>I?$CO36#r5d)J2Kx>n|M>Y-%OdjD%4-|IA9^qLcBv74L?8|*1=lRDtQ1@8z>Ht#OZ_u4i`U-hMV}DX z{76w0u+`|*-+;YUL1=D~G)?KGqZp6Ac{WO;eB<@L8OUFKZ98YrgSMYmH>n5Hq6O_` zU6o;NvHfZf)RvMZh*NZ_t>Kx_su7|I~fLKWRTqNTb=n zm`dZj=v7&PL%Jcjr8uC+hC`Hoi8kfmCI~tc%2+Nj3nKCGsF`x`$P~Sr!O3~qYX$<8 zj(pP&rk!WI>68MnnvfH#Y#~E%qz85vNQ7I9PAoK-NdIhxZECqT-dC=MWghdUXaf$v za(%GE@LNP8z&P;I_c~y3<3ixCrkKg1QfXHgn!fHqQ^)5LK`MAsG?Xv&W0H<;aLN(M`gGZZn0$Q zn3y=w0~5T# z;T^Fx)%$isG_-lm_1r7hlsGH8v-AlevhlFM>9FZi^)I{;CaLFY#nE9U`607_GrH&9 z>-@ZI#5A<5ddFxP{;30YRKNk9-?IW7QgY==7F<-Z@gwTPsh}{Y)Qo$7_KE$6M|3$)fnT?~sdG!4pJtq0ucA7J?h$8Wo*$Qjz%@4Dk2U*VRlIGpq`m3q(;@`7>aDQ#>HGXfV1(?x+hf?E16ai?a zlzZ5IEC903oF-7YGvTY--@kN11IFme4}U4n|G)T`Nk?7&dGzBr@v5Ba1^w1qKL%qiS)BH02XS&@QH>6|c8H!-n zYyEjFv$gD+HMmaT-&Vhc-^htAbQj$56Khi8U#Y$DKM;gNLf{#&fbTMU} zPJK4X!qrln1mEI>9S1fh!NO7xG44PHe5JW<#fYf|{o`04|F8dm^TVslm!=ZcsbOjr zVbPTy4z>%PO;4DDx^ist)GWi)+gfsCpcKF#N3RZcmyVQebaAqgjUG66tuJR__P}yH zCnYlstPganyGauyBjJ=26qN!6c*{)~zFHr_f_BzZk|v4r9B@j;CaFC@fdkjOa(L^Q zD#UdJ$)=JiojUwVLKI2GN2_!pyu3FW2&@3Qv=D7?RO`Q?y*7>zd0;j6wNGX0Q z0!9Xg<~K&_QgmyC+A$2HSf>mJyh$b^k;W23=+?Ydn}{3)N*JyV`ywAXi7rH|Sm)3^ ztXH4tE~|hhoQT6j;gMT=egydm0DW8Fzs!r(RND_hw7HHRaU(vo8aW8Ig-ZDSI2ve% zZaNPSak?Q>+E~rA6D1TP6H;lDDhFwm!kXIQVz#fTrf@SyMpHK|ptYiJB<*RAOK9e* zwBQ4Yona0oD(;M{BN0V(BsqUe&@n_60u_=5C?5 zOM0E}l8-L?HWvVG`_hBEl?nuW>H2`b1P`ST$BZK!3IN;HWT%$!TR#>ABwl(Qk=?Cf z(9Cia-hTS>Vb*leLSmUO9iSdHL20%EdE^C&<4S)YYsn>SY;1dh_K6DqOFB*DpB%Xp7q6EvFj7;nbd8eD7;G;+~ z$t<2QLV+e;n}e)9V=y@R&B{tS+_APqWUMBPIjdI}=GQ5xJH#MV@QdUtWZ?uZfU}LZ z3~a$EQy~wGAh1MGin8c%u90(8CuZXtf|7|7wWesO2jYy9AyH8t(*F;w;*GdI~yzWrb3|Xq95Az(HQ|;=l8ejKiH7%&DzKM@E~%h9&~*DKy2;ObyPOlacnPFQ%?b zIpkd`+Jj$Hr@tqdE2Eag>Miu)oJ?7zK*UFXEWs4pAki11-DJf{Mr=Yjv_N4 zGQNKYKYZ7zj?2{i1xcRf2=NB{;WsJijo#bR%`bY*tBIeg9*E@JU`}vq;Hga%+b3rs zGMKDZ%ui-h_JPX!&X=olfV3|*u6Z(uT?i??Z zHM8}VskGhAs!l3nhRHB@7+4;YpLlfQn;L-svDvWDcEh%3LY<_EALMg!v>4PXy=6mkNiSROGPhQ%2BGRj;K(vdH77wZo<4MJ?s#tHx-iX%O+m@3dWl?`jb^Dct ztVRoWsjocnBzpX=Uo&Bf2zyB?paK<7(m0c}$x&1pv*!ebtRNZJ%I*7pzJH&Si2DQc zn+rOeCg0NL2Mpg`eSyBJzBugSP7I3yo1~#cnLxnM=5!SuLv7_8IYUiLimD+u3wE>4lCfgiuPc4xhaiRoBXg`w*vg!T;2 zuO}UvQC$+eYxeL1 z06#79d^31XeJU5#v^`;g>KsO7WmD!Y1V?QXcw6^~xqxP+X4_oVn0ON^TD@u5#Dc8` zsSqh81|PUXh}#onlC>>%K#VJstVpHr(-KCT60&}aZTvRH_Q)Yfy&EJf5|@T$AnFBF zx2j{M7}jZB#|00(hGA=nZ1bF_>rH~Lp^C}zab0tOwXhnpYoI^(-WAlC8xZ!i}2axlvH%1;704)^b;lCE%zgHzL2mqF6Dwwoje?C~J!^V6V&zxfcK7+G#tdoo^Vj0|x#`j5 zhH44EKr_?N(yu>Nntv`Hvx^D|%ohx!UNUN*UE4S@XX0ib7;{Ymm9JU_=PANA&_pXR zcwlXBWB|tq2v`L%qT=)uVbhdx&F;<;Na#KLbZ8Au`HP%j26L01IiZOta50}Qf)Os= zaYXXZ$e>~-s7d+UMB88_4PLl|1)A_BSn0=ef`K$;T*j$Cb1ILp0f%}(Ei%YU;YFKZ zHX#UdQ+Q(v%CB0rS2bV8nX^NXf%f(^jMR*Bffad~>1T8`%wcdD>zp&C7Vy1nbDhhW z+J5=SVwlo*?MBmJ{g8fe-%=Nu{+?(*DW_M%B3o?Ll#1S6esckdIMn42ME8vQ1i6Kg zt9Rn{5uRoWvpL%VDX-X-mYHGg6FqsVEZ&~!F)ThcvwwVg_T&8e^2t-5WgLlOS5mn6S5B%gDT#O5~_sNbS~LgGy)x`GQ(3;xiv z_yl)SDz#r_1%&Q%)}OJShv;O)NNxmSF?E=pOmcdx^Q_&8dv;F@2{CI1RxN@iGzEDQ zAIein)#3#4xK6qmW;?{>xN@qq^i%3SZR(@d~#}#{^0x3(#eQ?I)!DM%*IXhSQ#|=+2 zw~-tGXJ$C8)Vnh>~V)! zC#(U=e1+nwilx3}lRcIFwACJ&)Ieky<=z1iTL?kwr(fnXw^wr4o&bN4gUaG>WC1EE zd_Qm7Gh>=LtrC2~3I z!S?daUr8ldK3FD`-63PqhfG&>WzN7(Uqoz#aSnFr3#Ozwx^E z`d`TJugbzkLH!NoZ#%j-%ovVyw@?Tm*E$5A=p5<~uiP+eEZTjiwHZKP(kH3?v%k;{#>^UQ~CKxo+qg%O0F8uL3GuTu|q;l%Gq2<>(u!)zn ztuh(N-ygs*S3FDzLg2WP#*nnj0kR0#;@(<#P3{R@-R~mDb!xaioamJ&EP#b`2Azt+ znv;EyRQUn#W~G8C>7N&H8=^I7IJ{G|#$3;_nl@zyJ)omw^_eLT_mut|sAQ%uT)cyTzHW{3 zZSK^E%qlT+&9Sf{3*Y-F4O*S=Yh_X2d=&iRB(@& zY^d!Q1~4%Ptbgf!{ZMcyY*x#OSK6|M*FNOe*a^v^gROdbm`PTjRu|vZ4f+RfV3z%sVE{=he-t#oMGz$c?65nboKzkHM>3UP!ETnbp` zBd}q1<;7=kl!OX5U@MM`#A22d)z^;KGBrZEq=Q60kYvEu=sMp24@N#WmS(;QW) zD>{rK)1(w#oOh{7lQ$_>fn%FVN>qPK`COG0Q}#pqw=h>0prDxexiBV{<9U=Z=B zbWw*niAkOR%Q1BeWxneP|yn{ZYI{=7g~=U90NP){+lr`pGH z3%qjJEVA+oIhe9#RvZY5msJC9Skg8S1c6|5C7sm<$9v7wc?6<~*}L3)!4Z9}Q-8iLxJvU!1E08L>nM?&&C$e8@BKfoutrtN2leFhkDt^T zN)TmJnm=%&5T%){B_K`S%s~NVqS2MvlzadNSmaP=LpA}6XE1mmRpuHMF75MTt<#11 z-xt^2nC&nAOUPCsIFW*D^EH^4S=BObfy6sM^`X>pYUu5EM;sJ&oS|N!U5Ze4iEupw zQtEy?y}QI5tr)H#X$U1*osT;K7@G`aHBn8-iV|*8_C%6v7Wq*57(L#VtW#=~X4}Cv zV!dQR;>GP>3Y$C!?r{-eL3c#AY$sJ0=L_nPIFdBKiSI%?eq(6KMu;Ey9nB<*jp;OV znoW-%9(7)EJd_?De`AV&qi*U8e37t{<}IOBD4|r z_uIz9QUrb`chTjSymPnND+((x%)vaomz zLrQB!9sd(6TpE1D6;h<12!w-j@5Qio43u-XgA%2;))&>$cWSFdg_d`A%&X!1Wd)K{Os178qaxv9R+D45SOIXjz44o-jrA0TM5bvk;vj3565)G#e8;;_QWv7cdD+ z91s`^tXhT=YIf9yG}}9?pN}VXTfeGIbaN_PG&%ysIC&li9 zwt-FU0`La#7rBcI1L}P$-^a2^4Q3G??>FsLV8ARE;)UG__tdbqy|&y~Z7$4K<>D`| zUT!S4yR%yWGs;!Ta<&YV$U|f}QY2nG(`tWq+H#}`xo~yg3SGkqOwU$snAXQPAWlM( z@PHP1G-Yk=|E0u@zyvOQ7X;AJH`a{#O1|SUAvqV+hIh`*t zlr*q6QST$3u{I7Jz4+4iQe9RUa>hcpoxr$6+1sc3nFjBt;PMSxZLkU{YA~E;+I1A` zQZQqRKTyXFh3~^&4=kHQ`;)>y?ygybigx*m`{wS=imZG!_l()yUzz+E9z#foW2lEs zIg+-Z%X|+_;^`AJ_xAL9Bi_ntV*Nml)kf*~4yUNWa`A#X-f^8}x3j!ud)_5?Mydv6 z5$h{~!}mA@MXn1er!cPSA@I~rx}Ubkz<3Zx^(dgy+nsKNiqwE3L_Q3dAfCo3-BEPb zW6CKrDC)RFR#L2l>K+Qu-&8l~@8PAS9aZ`;CYx8Xv9(s)#i44mFUpP%hy)M`+9MYV z&;|IuTV*nJSU;@2o*kQ|SIiYB#XacU%$O7C0EM|k-Sj`UyoN}P)4ANMQgO+XJ%AzO zxp09c^k806oml&ni65VdLh>aU&P9*p2JNZxeYWGW(+CEmq`00Pg})`z4qSGNtxh`M zScPF-X>OK}+>Pq&P1Wq`j}W||u{wDS?<}1~>(K*dK$yt5f_pX|pii2SLXhd2s3xLH zB4mGiFXn>99Kq1iwVNMw1DQJBN7IvaL$v*1Kk8O68+)eVq(#R0mzYhttF{FlBfqyV6*T$**Ue;nrvtVW70cmEPjue=;bo%Jof*ayGi@ zW>ss@Ee^{$fKZNv8D*kM0pHUKV0=sfn`vh{3g9KCIrFXX#vRdhZ&y)9TM!UK;TMBF zu{2PjvDDc~=oG___1B3-yaO*`Fgbz_PqZ`PK<5d5mOu&Ku{F;_A%iAxGajCpiNLaC zSBgwy+&3}#u@5olQG|#!8BGXPQnnmPnvi>w?Iy;VHZK~gczC4xFLz=VK(o!**=l-^ zvlA6a7@u%2!yPm2pV@Q_3Q72Gcn^ z(lfe6KSR!U;@5hmf(I_mAIFWabV{OT zM19S@t>gH|W-q2BF%~vH*Q3dt!Pl|jmzgD{nww|5rSn<8*kLrF`CM?(21Sxe9(o=u zufBABK4}EtnI^BHq-WAiR8BQYv=z|p0MWL6Q>~*e%M5}S`Yr(!3U$F{#_nO$?q|#0 z<9pG|snwt)1|DxE8fEyT0HLHFf(_Un8ICF0ikCDdArj`=n8W%2&~HN4ixs7?uVY=Y z0}dsIud=oWxSa+5rTbH1jwXyI87}BgK29ikECuaD3A}VEp~rT`vVQZb?wJLrE`=F0 zNLz`2LSG45^j8K+DN2AcF&SJBO)Ob7Ubd5jRM4*TAa#f#ToO{Z8^Ww?+fTv(us`5{ zWu9SvM#Fwx%E+Vw??EN%-?p+@*0G;BLr5>p6F)GT7~Ya?lLm3X=vEV35sAES{FwBJ zIY>@OR{!7L*);e5k@d8)S~)rlR(U(k6p(0$MpJWZ>e8s%O;~bFn}1Nwx)PGsufj(k zGON$GUeUkySUHvk?5a|)12OPOaz|NWZFyYP|LKzFoOVYaJTme*m#^N=U-U0;Dk7}Y#h(mfs~*;)V`;=6b9{i3_HyrH24}SMLmYtKpTH+NL??ug zT_tSHWdB3?b=N3kn^Afy7PDwUl>mHwgO{+)6ajodyuQX56GOTo=u`1@`G2^?-ZFfd_aNHhIM^D!nvV(rVH6 zSM|pIvdXF1hyYcz9|7TV8NSb8Na|n@GSE*n!55*qpZJ~id+W0lZu^=$nPY+;=#_~r zlGcDxvWcB@mOM;~M^oxAdEsBnRw`&^05JjXNK>bkE}u*5erqsS+chF09Zxa)ByVrni*0O1AbM zG^&@K_JXadAcj@Sd=F2p+=@lFKU6X|Cjgquh(4Z{nJYJEhxL<`>2W9_g;_Xi>Y zH5qxBX(aV;=SKJiKW?@PhJM*IJ^SRJO`HCCGCRHJ3pCiO`Ejsc1lPaaNXk5nq$Z05 z4;tOuVaJ~GEQqs{F5T1njuFQ_AmH+uYmtT4x-g-TZGC0y%BjP?vdZK=4l-4&+QHOz zayD0F=@|+1xrei^SNigYB8{k$g?K3d#V9`ZiZz=uE@`!t>mBwahY>4Q!Msk(>WvQe z{xf8xj|85UKDE)|ApX=*$B7Ui1^A3{Ys5w!h$z|Ycq%>|L>n#K>S!@L$!$4pkkRaX z8zh);lLu$gC-f0Iun{-k?ec&-ji~!rj+n;r+l4LM$ytl+2nd))lz*&4OmlZ`q}(+& zc1_TXtbuN1%Ym5mZD}6+7^rcK&CUJu&#gx!(T7qC6V`_c{B+@hBjI^ zk2wo``i)~Ema=Nlf4#VL5j4AFtpm$y2PNYeYo3PGyUE4P!j_YJKI~1PwfRp4Z;h`+ ziTxR7tBHp5Y)Wi7`aM0}92?-b`Ov}Y>Zgw@63omJDmHJauKDsUB*Sgajqdud8{PF^ zSGwDrtEVb~jvPbMk)udD@(7ZSJPM{13UGCnGUqbqGG}>^)B2A}SlE^b0;K>u@OYBP=y5{6Vc+#g@+dTg|+uBb~?Nr3Io0Cx*#72seOpGbCo~1lf zwAlLGB^7y-c?=x&#}=3Ex-soV-Nexiw%NR#z#3jPA|2U1v*e!JRP$}Zjp`zk#4_B4#MElt)ZqHv871;sT*IjbuS2#I_hSKO2{9PYEMy~AKW1Sn& zQm`{1*BaA#pq-`YJG9&QxIw{cUtrnP1n?DF#Z7gk(dR0oTZ!rjf}~Xi*BfdrF~WbB zI1!zkWVSOUA_NU=r{qNRE^)+*u$=6bQdZ!UT+W%%Mc-1`+1o&feTodf>q|?YgI726 zpC+jBOU8QpM-Grdd*3@Kqo&v9Uful;Enj!yrF+FT0w9xk)+8n#e%%3Tz2XzSt0hjR z!iRfg`^BXgQ{GF(t5Ri{fCu!|y zQ;u5Obds_1!i#M*w$Ldf&8-(=0*InUe6Ed*wRKo2VVbmojtol~xi(FBU}1>-9Qo-- zNyRSG-i^fwqOaDLBSyu`v)dxx)qvizFL#{ z2g-G#D3srWrozzTP=JFrfv{*fA^Ygx*-!r->0Elo8HIA!hE&?}heL+}{kpq#1)`=6 zSL^+5%IW8#h5Rva&FC6=Wm)R7{B1$vRXg_X+_f}egkl6i)bLv##0Wh==_4-Wm6#_^ zTT-VnYogkJFsI(tdbi_8C`%Iv4!xG}f{-|2d_rh*%4+{?x|ml&TuAAYX_!Bv+A(XW z)6vVZ01}2e#9avTI9In?aDq?A}Uh=i_E#YHFJhw_%c6 zF-{zsRgI_{^9>|v?J~=U%Lor8wb$d9eng(M%vDn3>elkIC7+f`Yb!w)E{iXiDHys| z04yJ#UxW3}MAr$1^DyBdmH5ySXj+*E1Yk56pO`om$; z?#Swff#%a_0_FQk^KTXtgchD-eJ-P1Xm>{tlbN?LlCZ!^VUl43#UdpoPsPdm*TV%C zTb}jRreCqO?0Ft%j2bTN)FP{7fyMkvc(d8gj~%_+EgBk+-7$v@L8jQLuhHXTUs5A- z0134h#F`8MDnQl0m=0`H`KNUlJSTi|5d_0-l{}LW8`1ywd;|FJUcY<4tc8mMvm%W}mgkcHw)#75rHEo2@qVb4 z&zA*e0f|+zlZD0;ZIfI!OWN?r;@!n*6YiB3%Or(VF21E$SX^XN?=aKUxzp{4MJz=X z#9zNtnT>UiU5LU26rBIje03mO$!a_Pes7%4JRrPb*D0Lm@;%nchmihRBD22~H}PA} z8y1_&u>4iOB;m1EY1WyRsd)<}zgA)Srn1F`^P34b!!L=K0*qzGtL41nJBG&Cl=gyg?dWQm=(uKDaaCfzyOG)CvM5 z&VY+s^Y`sRMqC_W*Unf4Auf)wVE3*D+a1R=m0Ggx)UI8$*f{#aT|3K?mbf7K?<5;PBy6xt-RPBxijfUIKQs(>9O)%ULVNnjwK{O-L3Ba(@i%>Lq& zIUFM9xbP{zW~INJ2crjL@L5&}%!U;vgYY4x(|G&?a!e`%)AN%}m3NJr?Y*i4*HP2YW4QKKh%|HU=4o$8@YRY4Zdebxd!th+>p{~Uf z%A&>KV(X~^=kw$p_>4Av8y=W7d0>oZOU=Nnb*@$J?DC=4bqwEhE44++892TM*MqOZ zBe-<0a4$L+u@(2=YVdzNO68J9okvNp8H@AQuWt$mknoo2@RXS`rf6&k+c7Or;KZhP zTr`$J3G$7UCQ+;^^Cox_D~byvoZ(`5K8kM&AKzT|Q26i0WfbKDoJRLW7bOAv$fn8KPfA7fH*- zbl}0SvKt7F+O+AWKMLM+GW7DE$VZFRwDcggrdFc~8ouRBr_s;SynytChK7UgmXOYK zqS1q%N=P2a4g|APau$mbS^t^^Bs4Z0Y|4Qq&b{a2o`N5iE0cLDRRsp|q`KPLoxCpD z&k;E8BVjXEL?fCZsuBB;fDlFQ!!qSwqI#4VO2-3=B zl>MF*n$J;R>Z0MC@@9b%+RPl2+qP7MW2z&=BL|L3DfaZ^p#lm?vz6k0Q;hHP#BPf$ z$cuzyv96|_kx{;KU)RG}hRJr9FrJDRe0Jbqu}B!bIPaf-ERzWrnVZZtc)*m4q04Zn z;Y6f`83yLDzb?<(<+ly7mLnpCKA_ty+mLEcxZ3Rqfv()I-&%*Jm|+m(WuB}f)})IH z0oi>U-L|RcvGi9c3JA+l6>nUaLKxfG(uWgvI(*K&LC`!uD3~fZWI4pc@b^kV3By#fQth?>%j*U zr+TUTo_o~143;re%H>AOBiv+e#*&i?+$^L1N9e6a^_n!apMUtD^6#3xcQOW~yZQn$}`D@ARVCIFZ z#UWOjQ|2fu(eKFb&@0)GNZCA3iWp z%hd6L=(}-5(L1aV(7ovWbjQB1e)s^qAJI=b6Z{Le%bEb1C`;%4uFNm!GuQ-@^#k^IT&4*ub(K_|KL!oC$4!tSt3l( z$#=}Tv`o0r2>@J3!t)^?Q=>Z5hHIhp#TPQ_K024>WnS)|fm10g`4o|hP{eCLRM?rE z!AzjfxGKmVs^;P5!(0i*L0I<@!9Bak>)7R6d>}E|bnEj+I#uNxe+b*ZE|%&Z;rg!1 z8s2+(w4U*ZT)pgOuSY~`)1epE0e@`!MatRr(8(teEfcsoiU~Epc8yn6X*qZ;Gy0M7 z_R+YwHUWr_n>tT(n4Y>iaTA&g>Wo)Pt;AE)G}|L50W==x7cZ&(T&@`Ob!g$>l404xBhC(rB(;)oP}v91T$B)YNX`*46m< zi{~wO?TU-jRT<+s3H3WWGoMK8ckR+>w6J0*EbnhL+joIcv9P<{7iGcRDM@HB23wy? zEX>huYPEjY@IP++-n8A07&^nP@ z5@1gxHj)sLIE4RqX}x_j?YZ3c&BG2)pG{|gw}@)6-u2V(%?ZDgul@epjnFF%R=fZ6 zdwtUH#2de#y9^5r)+b)yu@nCAf7TD}{m)UwcBQLqI}}VZP;7db7p?avcSL`ClPj~_-CfpdNzjU2bA8emds}{Y$r|dYbMfeKu5gu9suST4# zHLiy1pr*DRyWk!4n-R*8;v(TRN!v>>CK&0xq_#0`|G>TQ3{Vmt>Vd6j_ULh3GrD>g z##5*V$_!&~r;X|X;3ctmCS~{F@4%nKo*|6e1@3?kaf{xPp+jR8Gc^9!Bf~|qGoD$iSFD$9 z5J5?h=o7p*D`u)raXn)ECSJdFG+U)|c-2l&wBzh$bSJrmIf=47%Lc!cT$_Xmz-Gn z4Uz-#=r!{G+WKKKy%z^f^VV<6Ya~?{V;z1@~12K z$e#7b)R7D{nVAU@!lJ|0b|ITzIVbh4Ll5VETD~PBI-ZZdP~-G-k?yl_!D*LYSvK3ZvJTCp}D!o2=|zu=578l*Ad` z`u$T6RV`M)-)_GeJF+6nu)P7vUm(fdBKfs}@9E*V%2np%Kg+RxRE@2gun)0lT?9=# zdh{x1fvld!8rc_(Xbc#%U0L|^8lu!($hRLHJrn=moxMjr=NVPImroD7R%|-0Jf?u) z!HMN--)wx@(_J><>AOzmJ!#_$J+;z?*dp-6M>sEPzPd3Y@M&|DIOLL6d5E!GVd({r8N1|b z|2J*+$;hT#oj=-{TCw+sN4wp1NE7b4!KpZ;Pjx7VwR`;6UntiYKlOYZ$0uuq`MR)U*CQUBJAGo~;f)3W z**Jme22zrY5F_(CP`;2WTdK6!ml~G_*~p1X!^0*6`LvcunQhIv|L^o&@Kc8OC=-6V zop=A4pnQqzC#@izyE=rrJ|yUmPE$;4v_b76_ z6XGs5f&ITzQqp+sF;xyeI;cfuAv4uMFOKlejQjIR73x$?lxTVIB7csg(C&KeP6+Su zny{bI_Y<$>o1SckT?EQAk1GGb(Nz#Ab=xM_fBg75;%}W0(=ZA_;Ju z>SfQhhC>iEIQWImuB}<#^bLVW{D$Fh;BLF;b~Eq|j)$WVUUdtM91vgNC@Z9oD1iud zlqNdC-4Tela2(9LvC9&?5hO(E=*5%Q7=a~c#GSgmM9`cPn-5Rg)B!jS{zjGrpf7kb<}IpjyXAv!Q&Vih5Ss5OL5fv;q6H$hn#;3sFiV(}S=(E3QCxzh9RKap z$1w#>W2NvSc_~q`(Qyp%75T@u zDD#s}O{VAov{SAtXVl*2?Dmg{BOO@$GmpQCD1Q{|dd7Ujjnn~qX!Lh`ZpL=nKPiGC zBL|@;ykpIf_aZG8q33cJQbJ#*K|SD^3Xy*`$B*N2<<`2|VAuLOtzq!$T8oPsW(EWX zadX4X?!@~t=g(HA)w1PVxK9OQw`+X-Gg_c5j4N*U&6p!}e##IVF{-m-uVKLJR9c2C zFCw$_+MBUgUly$|-htP|Ej)2RViAUf5lvn$*|!b+j*~O@GsEM;S^HUM^DIbQCyRV$ zf#Z(Y^jL#0p@8{YxzB|fjb_)kJY=@NwYY}H?sV#OckX0%ItT3YU@oF9;)d*tmlfF; znTsq0_9D`z#~03|9di}h3E0b4?swjwvk3wLoOu5P*D!CXFyh*M!sEb!D=zT1;*+)g z)evQaNY0FMw0~fPGg6{24_y5Fz?Z5S-~hr2{kjyeiUXCLRBja0bSU-@aLD^#&Yhd% zuOa-Gz!U-~)mN%P0s3#LK?WXVt}L|`N{RskcNQU}4cHjQv& zhR?Tq_Fly^bA^@sctUx33RlH04&t+L5D;C%|5Xs7@A%C-LGw-*AiM}SF${fuF9ZsS zatdQC5GmXH>rSM#!ECM!@*YFpoemdz%UtFD37j|pe9{o8?ji^Y18(CSK??`2B3>n` z;Oj@i5suSuIq_M5C zUn$)47%>d5SfeG_S^8Y)tiDnMyASK)dyfLV(W9mU~{#z#mnusq&J9+oSY`*6b@Pdq5~mS}8@1EA%@ylIU;G*)pG)zv`xc$=9wt~^&B=1o3(GB9i;V}uuW z@)p@UENm~9Q0)|rEI*vxY;s`=f5~Mg4xSD3r%qlruNGIk5SGfr-@(eLZcF81KBlX; z1S&2&Jz-5Pa;Sh<=8e)=OC(;0Fzj8%(0@qk6}LtKyyLuS4gYDZ@ex-7!w#SdbU1S4 zvnK)-=vSO@@ClQHRwY*?N>d|T)79fK*=vYWbuII|o4tonlx8GdRs&t@Y<9`eTF^RSD0S<6%tC2M00*U4+OfD@pNHjM(~@-wSL2K= z!Mh2redMcsJwlzmWwpFxu?KPbvO32n)(7d6H;5kKcKt`0b9on8=T;q|JaD<6jSyx| zM&ZfFSRFAP(e&BS&2;jzr1kjv1|Cs!(x{FpK#X3_r^LeA1#oCiJ*e}MtN1*av5 zO+SLK{gDL)g9U|yh3i@ccYCXu&H|4p#sT8IQDn3!ymL20BG?$ z6&F(E@-A*J?m^srPA&kRlP27w+>-7G#COE|{JP9kDvZox-&b24Id~3yrmu2jI;}ET zI9EISvTB^SSy(LhspKcTf8T^0K=>jCk&Xw?;pSh?FIjSa$x_^I39xf)Iy(eVjoK4VQWkgZ`>cVQltQ+4ekNG&f*R*>RojQwTZH+A+iRT%7j^lvk< zZS!f|o?~$qb{1w9!2EzzF5O1Ew|9qqxI4)*xomqVEJ?C32^=M$G5q~!8;BG+2^?bU z(BWvMwX!fV@s_GOj8>rQVrc?LQPkObze1nf$>o)#g_K5Sxzp&VQ#qrNypUA6d@|Rk zz;D6XGyPbXM&S2x8i{Y-cR9sUETO%o(mFe5$~GM&43PHhAq_a>!&hL$HK9>o(Q>fD4-sKBj$?%A^yD`IYScK%ITe@I%#x*T;y;#p)uGb!?vm^?jG zv#sO6y(BBRSPb8L=2BwK;tsI7rT!PpN1n#`ru~yC7GlYnGo4m6YJYB?p&Xstv&D1K zeulihHMBe4$VlEkiQdmx+i0)9pyd>-oWsFVs&8g4IVy7WR#)gY&P)yl~79zM*>3ll&0c z?eRlk!SddCPRkGB@mI;6d(_U_+Z(LEY7mhg4GVqxo8`_cp-C^aId!*N*IOe#z30M~ zRFpHjpLyQg4Zgl;ZMAG~vy^ZxW2ZjJ&^)xPXl${;w%^AsJH9UPka&1hoH+gYIF7(0 z;EpftL`C`TTkzGg!@|jFPkEaEuT{dSTo-jtS!r3Env*x>x%GFNUzt0i)1(vOUZ&cd z$MKiOKQ3WPxaC*p%H5D1CLKt40aUZqTfs;c(2H_qu66ZNm&XxKs}u~cVVFiEps!%t z!wxzs=zR>|Bj3u+-dn#pIxR>j8{FrzZx95pl9Kp*YiM?eb^cq?0%NDY|16&!`ab|7 zCHN)o3CBiY_ayjB34moYbfq;3ljvl;K=d}>IwYHD1tsw%b-~$zSy{ZC;4cCN4!ApZ znp&LVm+0-2=$Bl?JpmRkHZd=H$v~{+!>znQUCAbUFGUzb#h~;xQG|(bn@Z|{w-ubf zl0tb|WNuB|>0acQ?30-2o#N+kgY>fZHT=~RdB!dsf7S|9Ic>SGI7>F$^~1ElNd;?t zHoY{KCW=wq-?DIMcOXt{7bUP}>9?|xW-P(>;n(Lu?~eS{CNEgDs6gKK*O7NY&%Zvj zB~CWglP`Gx4PSe%3H7ha+xI+SOz=;XPB^&{ifxivly04(I^J7l3xUY?SPXjE68C5A zJA7i9SCfqNbtTWZeB3m~a_gJq+eKWS)v*^wf#ql4w%rfOg124?d^}>+Ht3Bsm#t6( zZPptUBK}GjVGK%jZ$d#(C+(@wF+$gsc!cp!jRB}v$jp)6gKbtLj{~o44VFQS;AhJN zKYlxA#pV~?PJXl1GRAbg+?dz;l`Jc9Ty|pH>8$-kJC8xuo;bJOdtoD86)< zKXCWb?&v(|9OOYzD$SAo#udi>NF9jOLmSygoptOnb_H$Zb6M6RP;TPx+{bcbb+T6e z|NqMBB<4f5E5DtdUd4B_c-0T14`_wUnu-+znUk)6glw{PblyNun8yO=iMsr9O~KQqlW z?E3wF``H@v%#YeT_brR|-otib>;0Dsm#C62v@G~SWqhiSzGlWu95sR6bZ7CnzQOSj z)hi5%SPTOA$GuE_>zC@TnRxe!SC5JNE+vX-h5iLJ@f(LQor7WlOOTY*8aS+q%c3#o z>X*<5t>%(BCM_#NWanT8Kik$w@3Hb#XTO-4`Z2`e@M4_e_jixKNhk1c86c1@Hr^Fk z+@r%+2nTn(z7YA(p7r|Ww_vGX-S1#a->8E_kzb*M{wv6MY%jtUY^ND}<_48gOZl$5N(G=d(s?|f z2RQnux@eywT3M=m^W#Ud$B)cep6t=ce(q~Y>_=^3+wW+R&0!P1}27_R%OUnn$+;*pZgs&dXwQKl@SJ7%-0S=lM7 zls0)Aklo~5CE)Eq3?M%s1`!{Te0Wth32>+H7m&02sVXd5|JyuNlt6H%gLoKN2~hk* zRDNE5;_KbJ4e~xJviFy8&ISakLB@DH%tG@%B-+n%1IX{jz^&Y{KXOrf z>RrC2QG_H=8Ufxn&_`o^I#=|RcRE5AASDv^I7fHn#}V(j=G*z(O8XY7RQ)0Z{@y#T z`S;?9?FAqp<0s=+?DD{5A`B6kNeE+#(-f^5v8|BWbtP$^t<~Lf*I}olKXeYBhCB!4 z86tPT|B$3;@`nW2e&+_D&ZyqHbM5?WlBZ9jygwWzy8(mhZ7Nr&(33 zDN3fd#`V&t`(8ICrx4p4_w@8r+_fpLXz$a#i0vWj)R;=ar=_OFjv#`bN6(!XIdnM= z3sl4N@$|g35_ebay01E$Okt&cNp688BYg>!#R-cW=87c7Ev5TxW8#~!cclP*=X9LF zcQku}+_k{A8!hZYzA&>3W7R0DRn^KGX2B(ptvqraIg04(#YpUtXGicheTYr3i$@Fh zW*M@w{9_($&7;T9i+keRx|6tTrxfGM^Q8yZp;-9ReW)#`ZS>6^m{J1(La2}&%ZK@T zE0LmEv7D^E>221U*J(NhQOBZk?%eWe#Detv5UIY zvNmhEl-h+h)0-6#2c*Xb+YQo;=s2xlLO7wt(T!+e-t*q?d%x~;@SfA}rHuLuN68pd z8&ChcJ-yoW)TDAx3q^GgV1KA5JMOD|ALmK8t3+H>pL-!iP%PxzC;By+FpEf?)ZV;*s=AS@J#kZeuh5U}RgG zocZvvoxGtAPTEZ&(n!@NR&)IKMH2_pMpFlqi{mFMPgY)jlP1yZM~Ft#Pwmoby52&3 zpLWhY=(p;GbWwpfwC#g8gbsa#p@%1iP7b}ml283D#``r|Ga?{*PWw?RDE%S+FKP9^ z`ilWY!FI9bqW~`*1Nz6^J>8G>V2mJzjF!jR{qGd;+}N2R6|tg(tm;=YBt)5YT+;g$ zK#Xws+jDiD&k7S9u84q$&Ufm}y?7nc=%wq#Wyb9J#_9*bU>4O1jvu0p5_0{z`Q?-5 z^tdm-Ra=8R#DIH)8{cfd-8kP91ZuTE{gWrffZfPT_YX*~P=9(rMMwS9d2G9=qfvH- zKn!oQ`DPx5(COH@>0E>MWg^R#$y$(y#6Em17qjYmA16i${+-b~=K|Wi9A;Xc2l2 z-9m|IdwfC@0D?vcSXVjn4$Xq_NzDC7Ab{Tp9%9&RjPWRLbZNSU!{L&!PpMDyXovHk zT0Win3xbfU$`^+G0k2j_YUTcMCYFZ>2?rn$u-RElN>&7M^T?Pv%zm^Lks1EUetyX% z0_6l8yyR`9w*2!A*XT@@m(L8Ro@(2OKKXd~chz4640rDgZ-iII3|OpPe)e3?M~05!!q8GF%h|vvNOC#pVug3X!>88E49qv4 zrt-FY`EauFfxgB(`I8lA+J0x98r7K^xpFjeMS-)Sx-v|XC_&(w>7IiwL+O1qp3yO0 z8t)90|HtnY+*`UtOqjowoR@^v-Yj=Vf|{8U_wqs=CK0n&oBSv$nF$E{;6Hzo9}aAO z!q7&{zc_UUnk~AUY%s4G(~W6WLkH&B6&buVKfg3y#!CYQ&Wq0Ay%e0wLvQKT9?|aZ z(H`ov4jeeRTLRpDKFM6a8r1gT8dKYd9z}sDy`wWdWWkOd-@VZVQ2X`fVa$w?FepFx6keK_;0zdOI zmQKfBM<+=!N#oW|zeV38{awRt!d)x$9pCCeSGFrX@?Fr!puYU!+otAb6tn!gzQyCW zF}5zhU2PwMUqy4C5?NXr9z+!tMFBkGt6t(|8a`hl6ySplTwdPl-h3X{FohS0N<3RX zeSbdw&@#HDSY`QtV)7rMJD1t!_x9%7-XV;YhL%DlGprZc% zqG0cW&jgXEGPI1Opp?WAqY4WmiF9Q8X3y9cmWBQOg_aFY{iWfhu>By^(x z$4eccEeRmy8HgI>6tV^}jXLzxdQ&J;zu1@h96she21>D$N#24I?$3$e?z}nCZi` zvg0=K#I&F`D(zr&e)VhA42n$$zvi#TcM>?4i+`Q_+IA9SGfHl}|CREZV|)=6-_zsv z+U69-hD|9JSBN@he9L&oIQKRA>-}PKKH=!-=utvGVYkTjI{D-cb9B_C;1+oD(?7s} z^`(8G!$qe~r;Asv%s9;qwB0piz8Jo!DhBhjOsiCG9VWeRi+D?|w(TiHKzL&k*%#J1y8YOfiZ$c|Sv?_ILMK5RoZ3md z>sk^nZXdmfyJeEHI;{WNwPT%tdRopr{uO2a>lWqe8kHo_hLbY*!^%l%ZH=_sfp3%y ze6T{22Y3B`)1P6QKJa>e*8s(o(RJeA*W05_W467SJF%GV{{%TgmRkrjvqTo<8Fi+0 z=@rWc_1=lSWqg{YR&L+q(j8bg;1U8&~n6~2@?O0W1W@Koc`bl4fF7qYJ+^S9>EiNDyU0vm#jGtsH>8 zBR_tNU_p~)qfw$!$H44=24k<&Cp@u+A5T^rw+1Pe@bYp4mME$kPuz@s=)Q5I`@`6q zCmP-BY&!ku>#zeqC;(I~HI4E^rhjhhD4fh7q%POax}lgP&(U&h!32Tqqw`_*z|^!B z0k=O20{1zq)rKqpdgQ;q)R0Ho`nff0=4@ATvg5oo9-dmSxJv>B4!DiKV!gB%K2OOr zs`Y^5)jBO(&XP)<9sX@MX~(Z_Xvx(p&TyEwwJmb2sU=auga~4R?`gZQTXhc=I)xIg2175f%}B z;@Suc1QF(-w5%2PSwtX+Srt(vA|bduT#7*0+rzwq2d;a;jKVJq%whrjsAJ?9zv6 zX)822aJDI?ae9-b9l_y)W%YztOpKRWmNWurpCUuvs786#iOaa%Yx8p5Diz9G-=jT2 zy~omTr623v8`Oi%EaO@yj`H59Lc-#bVB7TDdSHngFzzUzibC1MW2F1!9i$<`G4g%# z8WIoSa<-XJ{k2Eh0olw(2bvu)i!_c8Y+6izmV2c;XWC6*6L^Dd|07g!ZT`sG_cV6{ zFL>?e7i+j+{y)w3T8NED+N#}SR}ipK+3wx0KZTO^+d4l>X}LNeMOiaCy>oo6?P6Vz zR-`;**$kkhr!qRcIyCk&^L$n>A5`O2Gf_6I&RzaKnwRdc8ibb?4dF1Y^i*|As9XHGx?`>0*Z*@aK5Ij(WP;<^&X&hEgcd zX@E-DY(}r&5itHI$Z-${SU}9I7|Yz5pY(fg{zu;o7ps9>Eg7)AK89EQq)m>==@ zbDM2NvKiEbW)#bx4%NWT_;_roA{Sr(K9fqo5Waidvg15w*G+X!EkZt;{hl9dPCIm}-inpw(uUH352ki2& z@Xt@9l&h6dPrt1GlC51>!wtSutl$0?jG2W|+0^!bwqdrtId%*$nsE&EI;O@AI%}AA z7H(qo<~CZoia{X!@ZJ9DW-C)meCdsWyN7SxZT35KMs%ZL%x~Sl`v|lTOXQ<}SAAYUQN1m?(KwIo4 zULLs_)+M?Tbp*bnzl6U*>b~V3{EzJ5*$CgO}FhtB2n8x=;UZb zG$O+_Lg%(<=gv`q%x}{ir_@sx@ah2H$yFva)By0JCuK8|yauQ=tF9Std6Rp^B}msK z2W>(#;Xn05WIH4o-%psFB=l1k%{(ASlcMK9wIAGcpz&lE)nQ}jMri-W4%1G#bAYO) z0=j|&uKg=v0exHAF_T~t)BfhrO;a2Nwc$D9hGp|reoWtr+M4U@EAihppg1NpFyE%S z(a6RVGE6qqG82J~Brj))e}tXZE@4%MW3-*ZK!QyYN}-5vRr7LeB!HQ?p*F)Lp@mfy zW>S%4E!F@D_SY@=%>2$)3#yE^kJ^fDN7d?4GfP31TC?3oY&$R$4OuyJap+8(q{&ol zD>f7R?u#AMo&W41{)=ulrdBt&XGW`x)B>upKMkC$tDlaSn~6;{W{q(YsLmNiErHLKbp`kT56w%4NUyK6UV zFf&x^+_$+3bL9IGOx3f!m$3v(8rBrG{u?CI^x7V(7xg<8?y>1>`Ej~@J&I=H@bGoP zCpoIV6;GV|2CFk6j-*K{ViZAYN4k2GrqL5KWYRhI61qSgzwDv7|v ztESbf8sQETh5->B2Fp-D=wbV%0`S(M#=Zw{r9z=TKu>Z@v{dYo2;{P+#;}zV$eTdw ze{t%#n0N8d6T{Gtm>-5jty*gs8jl$_B*w+WE&M|i0&gN4V?)iIHwq!o1EQtvlVL`A z^Hdwmg;4TM@~RD#IhTK3=3RbEy(5xOBEgq5=Tj(>f!TEeRWsP`l2A}mA~=`$+HQaG zKD$Rt`-=D8D6mM2D6(Aky4$i;Gi^=lbM$>>?WU@Z4U zQbtA+4WDu3ctPh*W`=}D**Sf@$>AIUAhj!t3hnvqtwuAMA`z3dC*}w07;Uc=m@D%;Kb}^d43~l#9!hT;@+Pf)xF@}$>U}bb};{B4^Tt{ zcrA_7$ypMt@H@MU)6MB(e;-}hK4Rl{d-dpy&1gWxTcu)VC^KZ(HY?R;Fkq0USc?da z$5l<38iGk$MjJmwgWza#9tB8)o7QA+%Ic-w&3*pJ_NrlHnolvL0f61ftG17x=ia6E zW@T?$6Wj!(lsu59y`|0b2b?kf*r=w7pXe?a47Zb5Zxg7{;8iU#?=^pn&&O`&nSXST zKA?G~8)%N#RInX4Q6=wGB{naiZlc{}J3S33PKgt!nRt;Z^2toKlglAfEAk5q3{JJH zwlxn*Me-*^_O(zO9mj8LNvMYmn(C;!Le6!{C0|iF=0O#nWvX5l+eQbg@i0|W_1w#6 z{p=Eu$74!DS3Ng*h3>6ZI;u~StTldXkBKu_8DlkT{j4>llj<^+XLT?7mC5r}C806L zk#-4wv*kdzo%MTE!*w>R_vQr^&^*{a6(;p43Ek?)ISj3}4_??~uw06VPCMlqg)?3* z3re-Cal3HYi-}9fPSa>)_~cW4L_K(Y7b(YJt`E5d>|!$WAZ63xl(x&3Ld%5f||IXSM40j z#{9H>_UD1PUFQEquBnp!7jwQ60@1D^KhUAeXHjkXNY&yi1*!0wk<@j}yKhs_4pz!E{z z>>X>K^p8m1$6GS}<*Lbw&5fHYa&jOVrjFwly|k`F}mVsIAn*hwIOT%B@RO zBH#}qz3gY~2WWusKAr$GhZz9X3)qYyQz-m6Wp4S6gf1(|GGOM&bC_f_c*g4rgSwn_ zqY)@*VKmV&Sco-6-h{Fd3m(+MRTk4{W`C?VyUrNUX6v)9l({VB4-o(*3Pmx5v_!iL zO;I`swJa1UWKqFfS5n|mR*BiVbyja$w@gJV8UpV7)7LHy>I}SgCtyin&xX`Au%W;K zcSD_l%Yp`n7ZiIGxDD@gEp{(&I@&ZEbS)X)QQ%hWQ8!d$2-XJ%WCiBbm(|!8ZfoGu&aKP81bfJ`6Ckiq@@8LPP7OjJ4fex*O>ls%7HUmlv z^Vrj6m!=JHI~X=VHwrl;{8sYAOT*7H*1wuRnR2wLh`IX@IH*pf_&>o%!yazETJut zNAi)2JQ*oldewCPM<_t@Noi3Wfr1_vMz70?N+aBnDVda!+HtAlD}!uZE!2azfd7^uvpH$kW^U&OpIIHp zO~~9Wr#q|_s}ZQ9%C}%`TH;U+2N8;nEpQ{P`8LdfaLe$mSO{^DKj;yb_Fg{(nkVx( zQ%7r0YcFe2{=*SfejZ{NmUWcNbih6>F#%~;&>6Of%A_qaFWXtXCvZP(I^o}(R1kE zBpLLtWWnUK!3A&~O6v*NMEVlERmD&-)}>84pD90$HOu;nDWCJhAUlEQUd}B?yc6jC zj=;(7mhKh+x3oe(-=n^MQ`)diK-uKPZ*HqfdsMR3#CTy^6@B@kzk2}$2IH!5f>U1p z=Y)A+hc_JR6WgYLHhNPI+2fFLJcDW9l%ge^kvpkSo9w%LFqRmx&fi>G3lf*}9@4bc z-u?lEjrIrYD!9e0@qYr>9Iver*Kc2+_DD>um02~OU)I1s`&Dm`Nc6&&k5XFw|G{99 zn_E1k3!FUwdM9@QeuoB&4Ukh33!R$N=${W_q;k1?y&*@mHM!4Y@6^pQZ?yRQb#cKO}XsULR(aCC7KS-dij<=gxDoD(RVg}D1@@SF$?-ZjtQW>ajiQ;F;h!@8#Vyn6TSwg(Nj=~xuIt#$m%wLKl%7euN`@6pPitVC|II@fcr&l5c? z^M5Y1x%$^Z`Kr>cJ+#pFPsEjQfu&A_{3$+ zbIm5h@8&Apgl4$Kc#x-oKwBb{Rp}j;J*xL5UUpUP3~d|*F3!JEpkG@xM?``;Xk7T^ibGr zm#uo7S;?mA8OAU;(m~4bY}t&&V*U4{xi-h=2qmLwckhx@b|9P(jM}bCxTk3xxY?~vBdOr+iy~#Dyv`pm6UU2NKTm+5|Wi7Y2iXww_;ys?ru3n=LANl-6F+%@wq)LmV)?IRgpyY|%itiKkYv)Y7Q z`DCB3)4s@s+uE_$zKplFv7?S{a_}YHe)jHFffzadd1|uye;)3G$$KjeQZ#38iZ?T* zq0}ZAFF*mp4L$OJa>E~DwF;WoJf;23mVT~Lf#P7%dEpXKV5B1C#yRAsIbe z;U|nSR$ zbG~s-HQyJZZ$pWWQtdZL3jo1viW>p|VdivlLSiOz7a z<-Fl4-eybz28$`@84mLF`GYvv{n;E$Af|S<#D7Dw&2V6KHP%oVh%|7rM>vRJbR@9K zP{e>3W7$|xk-%Ty%EVR&j;sOlk;a(n!0bbS`Gc6)L)jdpAEtIT+3|)T5HrH7s=`1+ z1=+l+VP3WQ$VmPyuo&_}228cyp6kQxz21Vx+=6D`g67RY1Rrj5V0k%Kuj>QLISi4_ zIlvr}7t&+P^)No#Z*#fdqy6;z{p1`2s+}##-4OU;Mh-KC4AKvoeduU)j!=eYVT^$n z%EngfJqzQUk(xawrw<%xNPQ1}(<1z*Xq#lc(tqSd1t-G<6+Z?spYW2|M=^Bsrj``8BVczks z*W6%m)0bDohW!72+xcJrk^fP4fB5~kci-m25$6JmzXOq192b~uDpKPaEXkGu7CWK) z_~W4iH3NQaw}aC*h6#*`+?1=XX<^<}+nl@VHksp!+}&QP1G~#d>b99LcM(2BM}{%2 za$s1yLTL0`7t}j%IJ-T#yK&+)18mP<)%Amg$Ue5|9oiQ8izgEJ#qc66*z^8P1Pf56 zeJi`o$q3-ABY5`Qpac2#QDr9C$K+jnDLvq$W`?}w0f*#2UdSZBxa_rr)0UIk-ppxz zKt{fTwfA+ocGBjDbEc z`L%6HEdbB_v@;$%mPz% zR+~#9C;`g8U%tGOmyWfU@7{C(%ax+h5P{mqeOOcEJ)NbZ4+y8`8_* ziNylosTa7ZuOl+%;Zr7oXnNBBQ#I^UIO&`uyDwOvx~~6j=S5eiNTV?x{&GbSEX`K# z>HRsY3aHQ4G2hThdBJ}oAv5Wo6g|k1k3W)G+@&|mY(N}B>SYgjfS&Zp7v=;{p+nlg zjt!~t&_?#RfS=jy{kzA421@!+cXE~K8<44*IVcipDmFA`ZDCq2Mj8V?jD>t(ot->+ zk_a@zwt^}fR62&k>rMW!cob3cbGTz2`^n%BKa_cwdrNEfdLti3-J(1FpYt0d;A_?* z3)l|Ng#qxPuA5anbvy=iamc#Mv~Rj5l}wo!aOt!IYe-(VkDJOr8Ewn}23%h=)QIgd z`@Il(p>qLUd)034UC_i-NFDKAtjpSD$XBOLI!TaR;=>Tl%Ql-W=CZE0!UR8k45ra8le36A=4Mwx(81fr4h97e^UdO;dzO6Q(iHD_;0~4m z-@jYu&D;0#hKtyb5w|xF_`c;QznFp@0^K@>deX+B=&0L6w5=Hh2FY%(!VmbRHQ#^R zIA4cm=b;cVCF$&A+Hj+MK|Up*%7j6_-Zb-2FyJS*+BduCv4s8>%PD*t=DI0{UoZe_g;*t*w-*hH+DDb5)KQhFiHPXI zwozm~wKD51F(-c95JZ<~`Nvx5G*4@zcO$YLPOSSQjOdv=1*mQ`#uY@=lH|mmQG=)+ zZ-zx(k8f^bC{Fjmwoet@mA|%!gRneQX8HEe^!#|KOPu;X7NI?%6N;_7&_=uSZj=RW zED4b!x)dFwL3bmNu#ouQHW_5Wg-Jel>T85UL33&DK$NdI#TvRYmC|Cvqzo32<0_H5~qkWJd z+_T^@s*Bntn3j`Q14UzVz_qp{^&Sm`!PP+3x*zBrD|P9?qOJlgBplc?A{a1p)(sRh zeh#=fXC(FB83-%h3{-Re9q7FVnKU-o3wk+)FepMFPWOXV!B2`r_OAW~lN@o?Ag{RNX8v8MFj6pMv-{V+T{YDnKJUTaQA>~b;3h~{*dR(e=6^}BW^ zoVyu4tn$U4pyI>a!d$<27tJ^6Di7VO%aeuT*Y2d@@;^`04bNsuW&NGR-`=+4dcLA+ zx?!HqmhHHnAB0hyq*-2+Ro%2*Ka7{_G%xG6AGiDC`FeiCZM(G1J+f+)#~ zs{MVr*S&6-mhHHnAB0hyq*-2+Ro%2*KaA77tlNH^*ZsWT4}a=ai) zvZ89bVOqB1dVUZ_agt_vQC4-+cKt9;^RjOHabEZHdVjvZKLjHvh7%-3Gc3moq9iM- zrW>YZJ1)1!>+^>KPQx^jr(Dg%u~~w*F>wEQEc5ot1(B_i0=G4AES**Gf?9=TNy?&o zv4SP|V(BEOr445TuCx_8Ip_lR#0tB=!I#}ED~F5KDnCUJBq%5AB3L?E0o`+WhiS8g z)0<7O;!jLi;mOwI*i|}HD??Z~tdhE|-Bi205AiZ@# z4dt=Nd#q~F1vn8S6P0$+)B;eu(Kt7y@E-d3($NV*d6 z1^jqyHIo@<^4cN4-@C1<%qaPdwOPT76(>u#iq>c^82w#BZs&qJk3%mU*_t`FynN7y zYdKeaNRZgld`VZy=#>raLY@r9B;ayod!dM2Vp!jomxoKPgSN3>`Xj833L4|dn6+7n zX~9uBdV3;uhCfrwV$EEKt3Q_hVzs4Xp->|RDHwXDSfo=zTdB}ES@I3Jg?eXwx)l?2 zU~1WX_+AdQjt>R9?JsH3#hmC0n=goTD@YW)ZbkWU#>u6bF2e5>7rx|Pxg-&M9KqpT zNxoILmw`pFF4)xCkY23s{Y10arPYXJU2pvq3f`oO^C=MPeHJ_#u;Gd1b?-36Zi{DMMdLta zySGSnPr~ht$E2d^8{HcrsGklx5{`L3cBCCQloc(mzyKvL!Gw zTtU845;U6lJjNLdy5t-^r5O65xm7wrCeJW5IK5E8$@qTL^GVN9_)$r{&;`Rb`O*a( zt%8x9tMKg2l&7svf1SveMO1`sCvxk@4UQO^f zOWssV5VER2<`RjeD5~9Dq5VNvll|XtP|*heqrZR!)l-sa6^8ORTt~Z@x1OEAa3I0B z8`E7yZ=aBIveJCOuE&kT6#Ix}brg}wrpZgQrsh77K996}hgTPQ6Qz&oWGReSoW?&~ zY|67aNURv@g)KI$4Ozl>X?1~g&qQRf>4c4}pjpU?BbkfJ<#bqD zN%qMKytDrCTsvlolG})Pe{0jf7iwF`{$R~KWedkkunPH1UwJ(f>t0(qzx24ijOmzj zIE7dww`Zuy{y$p}Z!#6ud;R5)MN6(6h^IQ9E5D+hN4y3;gE{a9w+Qy+dT0H)eH5!- z1m|d>E%lD>A;nLWQ>dx`rC9Cb@>_45mAO} block - The list of coordinates forming a white block. - */ -function floodFill( - pixels: Uint8ClampedArray, - width: number, - height: number, - x: number, - y: number, - visited: boolean[][], - block: Array<{ x: number; y: number }> -): void { - let stack: Array<[number, number]> = [[x, y]]; - let directions: Array<[number, number]> = [ - [1, 0], - [-1, 0], - [0, 1], - [0, -1], // 4-directional search (can be extended to 8-directional) - [1, 1], - [-1, -1], - [1, -1], - [-1, 1] // Diagonal directions - ]; - - while (stack.length) { - let [cx, cy] = stack.pop()!; - if ( - cx < 0 || - cy < 0 || - cx >= width || - cy >= height || - visited[cy][cx] || - !isWhite(pixels, cx, cy, width) - ) { - continue; - } - - visited[cy][cx] = true; - block.push({ x: cx, y: cy }); - - directions.forEach(([dx, dy]) => stack.push([cx + dx, cy + dy])); - } -} - -/** - * Extracts all white blocks from an image. - * @param {ImageData} imageData - The image pixel data. - * @returns {Array>} - List of white blocks, each containing pixel coordinates. - */ -function getWhiteBlocks( - imageData: ImageData -): Array> { - const { data, width, height } = imageData; - let visited: boolean[][] = Array.from({ length: height }, () => - new Array(width).fill(false) - ); - let whiteBlocks: Array> = []; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - if (isWhite(data, x, y, width) && !visited[y][x]) { - let block: Array<{ x: number; y: number }> = []; - floodFill(data, width, height, x, y, visited, block); - whiteBlocks.push(block); - } - } - } - return whiteBlocks; -} - -/** - * Loads an image from a file. - * @param {File} file - The image file. - * @returns {Promise} - The loaded image element. - */ -function loadImage(file: string): Promise { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = file; - img.onload = () => resolve(img); - img.onerror = () => reject(new Error('Image loading failed')); - }); -} - -/** - * Processes the image file and extracts white blocks. - * @param {File} file - The uploaded image file. - * @returns {Promise>>} - List of white blocks with pixel coordinates. - */ -async function processImage( - file: string -): Promise>> { - const img = await loadImage(file); - const { canvas, ctx } = createCanvas(img.width, img.height); - ctx.drawImage(img, 0, 0); - - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const whiteBlocks = getWhiteBlocks(imageData); - - URL.revokeObjectURL(img.src); - return whiteBlocks; -} - -export { processImage }; diff --git a/src/components/image-editor/hooks/use-drawing.ts b/src/components/image-editor/hooks/use-drawing.ts deleted file mode 100644 index 1eace8a9..00000000 --- a/src/components/image-editor/hooks/use-drawing.ts +++ /dev/null @@ -1,437 +0,0 @@ -import _ from 'lodash'; -import React, { useCallback, useMemo, useRef } from 'react'; - -type Point = { x: number; y: number; lineWidth: number }; -type Stroke = Point[]; -const COLOR = 'rgba(0, 0, 255, 0.3)'; - -export default function useDrawing(props: { - isDisabled?: boolean; - invertMask: boolean; - maskUpload?: any[]; - lineWidth: number; - translatePos: { current: { x: number; y: number } }; - onSave: (imageData: { mask: string | null; img: string }) => void; -}) { - const { - isDisabled, - invertMask, - maskUpload, - lineWidth, - translatePos, - onSave - } = props; - const mouseDownState = useRef(false); - const canvasRef = useRef(null); - const overlayCanvasRef = useRef(null); - const offscreenCanvasRef = useRef(null); - const currentStroke = useRef([]); - const strokesRef = useRef([]); - const isDrawing = useRef(false); - const cursorRef = useRef(null); - const autoScale = useRef(1); - const baseScale = useRef(1); - const contentPos = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); - const maskStorkeRef = useRef([]); - const isLoadingMaskRef = useRef(false); - - const disabled = useMemo(() => { - return isDisabled || invertMask || !!maskUpload?.length; - }, [isDisabled, invertMask, maskUpload]); - - const setStrokes = (strokes: Stroke[]) => { - strokesRef.current = strokes; - }; - - const setMaskStrokes = (strokes: Stroke[]) => { - maskStorkeRef.current = strokes; - }; - - const inpaintArea = useCallback( - (data: Uint8ClampedArray) => { - for (let i = 0; i < data.length; i += 4) { - const alpha = data[i + 3]; - if (alpha > 0) { - data[i] = 255; // Red - data[i + 1] = 255; // Green - data[i + 2] = 255; // Blue - data[i + 3] = 255; // Alpha - } - } - }, - [] - ); - - const generateImage = useCallback(() => { - const canvas = canvasRef.current!; - return canvas.toDataURL('image/png'); - }, []); - - const generateMask = useCallback(() => { - if (strokesRef.current.length === 0 && maskStorkeRef.current.length === 0) { - return null; - } - const overlayCanvas = overlayCanvasRef.current!; - const maskCanvas = document.createElement('canvas'); - maskCanvas.width = overlayCanvas.width; - maskCanvas.height = overlayCanvas.height; - const maskCtx = maskCanvas.getContext('2d')!; - const overlayCtx = overlayCanvas.getContext('2d')!; - - const imageData = overlayCtx.getImageData( - 0, - 0, - overlayCanvas.width, - overlayCanvas.height - ); - const data = imageData.data; - - inpaintArea(data); - - maskCtx.putImageData(imageData, 0, 0); - - maskCtx.globalCompositeOperation = 'destination-over'; - maskCtx.fillStyle = 'black'; - maskCtx.fillRect(0, 0, maskCanvas.width, maskCanvas.height); - - return maskCanvas.toDataURL('image/png'); - }, []); - - const saveImage = () => { - const mask = generateMask(); - const img = generateImage(); - onSave({ mask, img }); - }; - - const creatOffscreenCanvas = useCallback(() => { - if (!offscreenCanvasRef.current) { - offscreenCanvasRef.current = document.createElement('canvas'); - } - }, []); - - const setTransform = useCallback(() => { - creatOffscreenCanvas(); - const ctx = canvasRef.current?.getContext('2d'); - const overlayCtx = overlayCanvasRef.current?.getContext('2d'); - const offCtx = offscreenCanvasRef.current?.getContext('2d'); - - if (!ctx || !overlayCtx) return; - - ctx!.resetTransform(); - overlayCtx!.resetTransform(); - offCtx!.resetTransform(); - - const { current: scale } = autoScale; - const { x: translateX, y: translateY } = translatePos.current; - ctx!.setTransform(scale, 0, 0, scale, translateX, translateY); - overlayCtx!.setTransform(scale, 0, 0, scale, translateX, translateY); - offCtx!.setTransform(scale, 0, 0, scale, translateX, translateY); - }, []); - - const getTransformedPoint = (offsetX: number, offsetY: number) => { - const { current: scale } = autoScale; - console.log('lineWidth:----------', lineWidth, autoScale.current); - - const { x: translateX, y: translateY } = translatePos.current; - - const transformedX = (offsetX - translateX) / scale; - const transformedY = (offsetY - translateY) / scale; - - return { - x: transformedX, - y: transformedY - }; - }; - - const getTransformLineWidth = (w = 1) => { - console.log('lineWidth:', lineWidth, autoScale.current); - const width = w || lineWidth; - return width / autoScale.current; - }; - - const drawLine = useCallback( - ( - ctx: CanvasRenderingContext2D, - point: Point, - options: { - lineWidth: number; - color: string; - compositeOperation: 'source-over' | 'destination-out'; - } - ) => { - const { lineWidth, color, compositeOperation } = options; - - ctx.lineWidth = getTransformLineWidth(lineWidth); - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.globalCompositeOperation = compositeOperation; - - const { x, y } = getTransformedPoint(point.x, point.y); - - ctx.lineTo(x, y); - if (compositeOperation === 'source-over') { - ctx.strokeStyle = color; - } - ctx.stroke(); - }, - [getTransformLineWidth] - ); - - const drawStroke = useCallback( - ( - ctx: CanvasRenderingContext2D, - stroke: Stroke | Point[], - options: { - lineWidth?: number; - color: string; - compositeOperation: 'source-over' | 'destination-out'; - } - ) => { - const { color, compositeOperation } = options; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.globalCompositeOperation = compositeOperation; - ctx.save(); - - ctx.beginPath(); - - stroke.forEach((point, i) => { - const { x, y } = getTransformedPoint(point.x, point.y); - ctx.lineWidth = getTransformLineWidth(point.lineWidth); - if (i === 0) { - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - if (compositeOperation === 'source-over') { - ctx.strokeStyle = color; - } - ctx.stroke(); - ctx.restore(); - }, - [getTransformLineWidth, getTransformedPoint] - ); - - const draw = (e: React.MouseEvent) => { - if (disabled) { - return; - } - console.log( - 'Drawing:', - isDrawing.current, - currentStroke.current, - strokesRef.current - ); - if (!isDrawing.current || !mouseDownState.current) return; - - const { offsetX, offsetY } = e.nativeEvent; - const currentX = offsetX; - const currentY = offsetY; - console.log('currentStroke:', currentStroke.current); - currentStroke.current.push({ - x: currentX, - y: currentY, - lineWidth - }); - - const ctx = overlayCanvasRef.current!.getContext('2d'); - - ctx!.save(); - - drawLine( - ctx!, - { x: currentX, y: currentY, lineWidth }, - { lineWidth, color: COLOR, compositeOperation: 'destination-out' } - ); - drawLine( - ctx!, - { x: currentX, y: currentY, lineWidth }, - { lineWidth, color: COLOR, compositeOperation: 'source-over' } - ); - - ctx!.restore(); - }; - - const startDrawing = (e: React.MouseEvent) => { - if (disabled) { - return; - } - - isDrawing.current = true; - - currentStroke.current = []; - const { offsetX, offsetY } = e.nativeEvent; - - const currentX = offsetX; - const currentY = offsetY; - - currentStroke.current.push({ - x: currentX, - y: currentY, - lineWidth - }); - - const ctx = overlayCanvasRef.current!.getContext('2d'); - setTransform(); - const { x, y } = getTransformedPoint(currentX, currentY); - ctx!.beginPath(); - ctx!.moveTo(x, y); - - draw(e); - }; - - const endDrawing = (e: React.MouseEvent) => { - if (disabled) { - return; - } - if (!isDrawing.current) { - return; - } - - console.log('End Drawing:', e); - - isDrawing.current = false; - - strokesRef.current.push(_.cloneDeep(currentStroke.current)); - - currentStroke.current = []; - - saveImage(); - }; - - const handleMouseMove = (e: React.MouseEvent) => { - if (disabled) { - return; - } - overlayCanvasRef.current!.style.cursor = 'none'; - cursorRef.current!.style.display = 'block'; - cursorRef.current!.style.top = `${e.clientY - (lineWidth / 2) * autoScale.current}px`; - cursorRef.current!.style.left = `${e.clientX - (lineWidth / 2) * autoScale.current}px`; - }; - - const handleMouseLeave = () => { - if (disabled) { - return; - } - isDrawing.current = false; - overlayCanvasRef.current!.style.cursor = 'default'; - cursorRef.current!.style.display = 'none'; - }; - - const handleMouseEnter = (e: React.MouseEvent) => { - console.log('mouse enter:', mouseDownState.current); - if (disabled) { - overlayCanvasRef.current!.style.cursor = 'default'; - return; - } - // if (mouseDownState.current) { - // isDrawing.current = true; - // } - overlayCanvasRef.current!.style.cursor = 'none'; - cursorRef.current!.style.display = 'block'; - cursorRef.current!.style.top = `${e.clientY - (lineWidth / 2) * autoScale.current}px`; - cursorRef.current!.style.left = `${e.clientX - (lineWidth / 2) * autoScale.current}px`; - }; - - const clearOverlayCanvas = useCallback(() => { - const ctx = overlayCanvasRef.current!.getContext('2d'); - const offCtx = offscreenCanvasRef.current?.getContext('2d'); - - offCtx!.resetTransform(); - ctx!.resetTransform(); - ctx!.clearRect( - 0, - 0, - overlayCanvasRef.current!.width, - overlayCanvasRef.current!.height - ); - offCtx!.clearRect( - 0, - 0, - overlayCanvasRef.current!.width, - overlayCanvasRef.current!.height - ); - }, []); - - const clearCanvas = useCallback(() => { - const canvas = canvasRef.current!; - const ctx = canvasRef.current!.getContext('2d'); - const offCtx = offscreenCanvasRef.current?.getContext('2d'); - ctx!.resetTransform(); - ctx!.clearRect(0, 0, canvas.width, canvas.height); - - offCtx!.resetTransform(); - offCtx!.clearRect(0, 0, canvas.width, canvas.height); - }, []); - - const resetCanvas = useCallback(() => { - const canvas = canvasRef.current!; - const overlayCanvas = overlayCanvasRef.current!; - const offscreenCanvas = offscreenCanvasRef.current!; - const ctx = canvas.getContext('2d'); - const overlayCtx = overlayCanvas.getContext('2d'); - const offCtx = offscreenCanvasRef.current?.getContext('2d'); - - autoScale.current = 1; - baseScale.current = 1; - translatePos.current = { x: 0, y: 0 }; - contentPos.current = { x: 0, y: 0 }; - - canvas.style.transform = 'scale(1)'; - overlayCanvas.style.transform = 'scale(1)'; - offscreenCanvas.style.transform = 'scale(1)'; - - cursorRef.current!.style.width = `${lineWidth}px`; - cursorRef.current!.style.height = `${lineWidth}px`; - - ctx!.resetTransform(); - overlayCtx!.resetTransform(); - offCtx!.resetTransform(); - }, []); - - const fitView = () => { - resetCanvas(); - autoScale.current = baseScale.current; - translatePos.current = { x: 0, y: 0 }; - setTransform(); - overlayCanvasRef.current!.style.transform = `scale(${autoScale.current})`; - canvasRef.current!.style.transform = `scale(${autoScale.current})`; - offscreenCanvasRef.current!.style.transform = `scale(${autoScale.current})`; - }; - - return { - canvasRef, - overlayCanvasRef, - offscreenCanvasRef, - cursorRef, - strokesRef, - currentStroke, - isDrawing, - mouseDownState, - autoScale, - baseScale, - maskStorkeRef, - isLoadingMaskRef, - creatOffscreenCanvas, - setMaskStrokes, - fitView, - setStrokes, - resetCanvas, - draw, - getTransformLineWidth, - getTransformedPoint, - drawStroke, - startDrawing, - endDrawing, - handleMouseMove, - handleMouseLeave, - handleMouseEnter, - saveImage, - generateMask, - generateImage, - setTransform, - clearOverlayCanvas, - clearCanvas - }; -} diff --git a/src/components/image-editor/hooks/use-zoom.ts b/src/components/image-editor/hooks/use-zoom.ts deleted file mode 100644 index bfe9c3a0..00000000 --- a/src/components/image-editor/hooks/use-zoom.ts +++ /dev/null @@ -1,128 +0,0 @@ -import _ from 'lodash'; -import React, { MutableRefObject, useState } from 'react'; - -export default function useZoom(props: { - overlayCanvasRef: any; - canvasRef: any; - offscreenCanvasRef: any; - cursorRef: any; - lineWidth: number; - autoScale: MutableRefObject; - baseScale: MutableRefObject; - translatePos: MutableRefObject<{ x: number; y: number }>; - isLoadingMaskRef: MutableRefObject; -}) { - const MIN_SCALE = 0.2; - const MAX_SCALE = 8; - const ZOOM_SPEED = 0.1; - const { - overlayCanvasRef, - canvasRef, - offscreenCanvasRef, - cursorRef, - lineWidth, - translatePos, - autoScale, - baseScale, - isLoadingMaskRef - } = props; - - const [activeScale, setActiveScale] = useState(1); - - const setCanvasTransformOrigin = (e: React.MouseEvent) => { - if (autoScale.current <= MIN_SCALE) { - return; - } - - if (autoScale.current >= MAX_SCALE) { - return; - } - - const rect = overlayCanvasRef.current!.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - - const originX = mouseX / rect.width; - const originY = mouseY / rect.height; - - overlayCanvasRef.current!.style.transformOrigin = `${originX * 100}% ${originY * 100}%`; - canvasRef.current!.style.transformOrigin = `${originX * 100}% ${originY * 100}%`; - offscreenCanvasRef.current!.style.transformOrigin = `${originX * 100}% ${originY * 100}%`; - }; - - const updateZoom = (scaleChange: number, mouseX: number, mouseY: number) => { - const newScale = _.round(autoScale.current + scaleChange, 2); - - if (newScale < MIN_SCALE || newScale > MAX_SCALE) return; - - const { current: oldScale } = autoScale; - const { x: oldTranslateX, y: oldTranslateY } = translatePos.current; - - const centerX = (mouseX - oldTranslateX) / oldScale; - const centerY = (mouseY - oldTranslateY) / oldScale; - - autoScale.current = newScale; - - const newTranslateX = mouseX - centerX * newScale; - const newTranslateY = mouseY - centerY * newScale; - - translatePos.current = { x: newTranslateX, y: newTranslateY }; - }; - - const applyCanvasTransform = () => { - const scale = autoScale.current; - const transform = `scale(${scale})`; - - overlayCanvasRef.current!.style.transform = transform; - canvasRef.current!.style.transform = transform; - offscreenCanvasRef.current!.style.transform = transform; - }; - - const handleZoom = (event: React.WheelEvent) => { - const scaleChange = event.deltaY > 0 ? -ZOOM_SPEED : ZOOM_SPEED; - - // current mouse position - const canvas = overlayCanvasRef.current!; - const rect = canvas.getBoundingClientRect(); - - const mouseX = event.clientX - rect.left; - const mouseY = event.clientY - rect.top; - applyCanvasTransform(); - - setCanvasTransformOrigin(event); - updateZoom(scaleChange, mouseX, mouseY); - }; - - const updateCursorSize = () => { - cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`; - cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`; - }; - - const updateCursorPosOnZoom = (e: any) => { - cursorRef.current!.style.top = `${e.clientY - (lineWidth / 2) * autoScale.current}px`; - cursorRef.current!.style.left = `${e.clientX - (lineWidth / 2) * autoScale.current}px`; - }; - - const handleOnWheel = (event: any) => { - if (isLoadingMaskRef.current) { - return; - } - // stop - handleZoom(event); - updateCursorSize(); - updateCursorPosOnZoom(event); - setActiveScale(autoScale.current); - }; - - const throttleHandleOnWheel = _.throttle((event: any) => { - handleOnWheel(event); - }, 16); - - return { - handleOnWheel: handleOnWheel, - setActiveScale, - activeScale, - autoScale, - baseScale - }; -} diff --git a/src/components/image-editor/index.less b/src/components/image-editor/index.less deleted file mode 100644 index 68f663f8..00000000 --- a/src/components/image-editor/index.less +++ /dev/null @@ -1,39 +0,0 @@ -.editor-wrapper { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - - .tools { - margin-bottom: 10px; - display: flex; - gap: 10px; - } - - .editor-content { - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - } - - .overlay-canvas:hover { - cursor: none !important; - } - - .overlay-canvas.overlay-canvas--disabled:hover { - cursor: default !important; - } - - .upload-mask { - &:hover { - .close-btn { - display: block; - } - } - } - - .close-btn { - display: none; - } -} diff --git a/src/components/image-editor/index.tsx b/src/components/image-editor/index.tsx deleted file mode 100644 index d68d664f..00000000 --- a/src/components/image-editor/index.tsx +++ /dev/null @@ -1,645 +0,0 @@ -import { Spin } from 'antd'; -import classNames from 'classnames'; -import dayjs from 'dayjs'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useMemo, - useRef, - useState -} from 'react'; -import styled from 'styled-components'; -import useDrawing from './hooks/use-drawing'; -import useZoom from './hooks/use-zoom'; -import './index.less'; -import { ImageActionsBar, ToolsBar } from './tools-bar'; - -const LoadWrapper = styled.div<{ width?: number; height?: number }>` - position: absolute; - top: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - width: ${(props) => `${props.width}px` || '100%'}; - z-index: 100; -`; - -const Loading = (props: { width: number; height: number }) => { - const { width, height } = props; - return ( - - - - ); -}; - -type Point = { x: number; y: number; lineWidth: number }; -type Stroke = Point[]; - -type CanvasImageEditorProps = { - ref?: any; - loading: boolean; - imageSrc: string; - disabled?: boolean; - imguid: string | number; - maskUpload?: any[]; - clearUploadMask?: () => void; - onSave: (imageData: { mask: string | null; img: string }) => void; - onScaleImageSize?: (data: { width: number; height: number }) => void; - handleUpdateImageList: (fileList: any[]) => void; - handleUpdateMaskList: (fileList: any[]) => void; - uploadButton?: React.ReactNode; - accept?: string; - imageStatus: { - isOriginal: boolean; - isResetNeeded: boolean; - width: number; - height: number; - }; -}; - -const COLOR = 'rgba(0, 0, 255, 0.3)'; - -const CanvasImageEditor: React.FC = forwardRef( - ( - { - loading, - imageSrc, - disabled: isDisabled, - imageStatus, - maskUpload, - accept, - onSave, - onScaleImageSize, - handleUpdateImageList, - handleUpdateMaskList - }, - ref - ) => { - const invertWorkerRef = useRef(null); - const loadMaksWorkerRef = useRef(null); - const containerRef = useRef(null); - const [lineWidth, setLineWidth] = useState(60); - const translatePos = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); - const negativeMaskRef = useRef(false); - const [invertMask, setInvertMask] = useState(false); - const timer = useRef(null); - const [loadingSize, setLoadingSize] = useState({ - width: 0, - height: 0, - loading: false - }); - - const { - canvasRef, - overlayCanvasRef, - offscreenCanvasRef, - cursorRef, - strokesRef, - currentStroke, - mouseDownState, - autoScale, - baseScale, - maskStorkeRef, - isLoadingMaskRef, - setMaskStrokes, - draw, - drawStroke, - startDrawing, - endDrawing, - handleMouseMove, - handleMouseLeave, - handleMouseEnter, - saveImage, - resetCanvas, - creatOffscreenCanvas, - generateMask, - getTransformLineWidth, - getTransformedPoint, - setStrokes, - setTransform, - clearOverlayCanvas, - clearCanvas, - fitView - } = useDrawing({ - lineWidth: lineWidth, - translatePos: translatePos, - isDisabled: isDisabled, - invertMask, - maskUpload, - onSave: onSave - }); - - const { handleOnWheel, setActiveScale, activeScale } = useZoom({ - overlayCanvasRef, - canvasRef, - offscreenCanvasRef, - cursorRef, - lineWidth, - translatePos, - autoScale, - baseScale, - isLoadingMaskRef - }); - - const disabled = useMemo(() => { - return isDisabled || invertMask || !!maskUpload?.length; - }, [isDisabled, invertMask, maskUpload]); - - // update the canvas size - const updateCanvasSize = useCallback(() => { - const canvas = canvasRef.current!; - const overlayCanvas = overlayCanvasRef.current!; - const offscreenCanvas = offscreenCanvasRef.current!; - - overlayCanvas.width = canvas.width; - overlayCanvas.height = canvas.height; - offscreenCanvas.width = canvas.width; - offscreenCanvas.height = canvas.height; - }, []); - - const downloadMask = useCallback(() => { - const mask = generateMask(); - - const link = document.createElement('a'); - link.download = `mask_${dayjs().format('YYYYMMDDHHmmss')}.png`; - link.href = mask || ''; - link.click(); - }, [generateMask]); - - const onReset = useCallback(() => { - clearOverlayCanvas(); - setStrokes([]); - setMaskStrokes([]); - saveImage(); - currentStroke.current = []; - console.log('Resetting strokes', currentStroke.current); - }, []); - - const loadMaskPixs = (maskStrokes: Stroke[], mainStrokes?: Stroke[]) => { - try { - if (!maskStrokes.length && !mainStrokes?.length) { - return; - } - - isLoadingMaskRef.current = true; - - const offscreenCanvas = new OffscreenCanvas( - overlayCanvasRef.current!.width, - overlayCanvasRef.current!.height - ); - - if (!loadMaksWorkerRef.current) { - loadMaksWorkerRef.current = new Worker( - new URL('./offscreen-worker.ts', import.meta.url), - { - type: 'module' - } - ); - } - - loadMaksWorkerRef.current!.onmessage = (event: any) => { - if (event.data.type === 'done' && event.data.imageData) { - console.log('load mask done'); - // draw the data to the overlay canvas - const ctx = overlayCanvasRef.current!.getContext('2d')!; - ctx.putImageData(event.data.imageData, 0, 0); - - isLoadingMaskRef.current = false; - saveImage(); - } - }; - - // send offscreen canvas to worker - loadMaksWorkerRef.current?.postMessage( - { canvas: offscreenCanvas, type: 'init' }, - [offscreenCanvas] - ); - - // send draw data to worker - loadMaksWorkerRef.current?.postMessage({ - type: 'draw', - maskStrokes: maskStrokes, - strokes: mainStrokes || [] - }); - } catch (error) { - console.log('error---', error); - } - }; - - const redrawStrokes = async (strokes: Stroke[]) => { - if (!strokes.length && !maskStorkeRef.current.length) { - clearOverlayCanvas(); - return; - } - - loadMaskPixs(maskStorkeRef.current, strokes); - }; - - const undo = useCallback(() => { - if ( - strokesRef.current.length === 0 && - maskStorkeRef.current.length === 0 - ) { - clearOverlayCanvas(); - return; - } - - const lastlength = strokesRef.current.length; - - const newStrokes = strokesRef.current.slice(0, -1); - setStrokes(newStrokes); - - if ( - !newStrokes.length && - lastlength === 0 && - maskStorkeRef.current.length - ) { - setMaskStrokes([]); - } - clearTimeout(timer.current); - timer.current = setTimeout(() => { - redrawStrokes(newStrokes); - }, 100); - }, []); - - const downloadOriginImage = () => { - const canvas = canvasRef.current!; - const link = document.createElement('a'); - link.download = `image_${dayjs().format('YYYYMMDDHHmmss')}.png`; - link.href = canvas.toDataURL('image/png'); - link.click(); - link.remove(); - }; - - const downloadNewImage = () => { - if (!imageSrc) return; - - const img = new Image(); - img.src = imageSrc; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = imageStatus.width; - canvas.height = imageStatus.height; - - const ctx = canvas.getContext('2d')!; - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - - const url = canvas.toDataURL('image/png'); - const filename = `${canvas.width}x${canvas.height}_${dayjs().format('YYYYMMDDHHmmss')}.png`; - - const link = document.createElement('a'); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - link.remove(); - }; - }; - - const download = () => { - if (imageStatus.isOriginal) { - downloadOriginImage(); - } else { - downloadNewImage(); - } - }; - - const drawImage = useCallback(async () => { - if (!containerRef.current || !canvasRef.current) return; - return new Promise((resolve) => { - const img = new Image(); - img.src = imageSrc; - img.onload = () => { - const canvas = canvasRef.current!; - const ctx = canvas!.getContext('2d'); - const container = containerRef.current; - baseScale.current = Math.min( - container!.offsetWidth / img.width, - container!.offsetHeight / img.height, - 1 - ); - - // if need to fit the image to the container, show * baseScale.current - canvas!.width = img.width; - canvas!.height = img.height; - - // fit the image to the container - autoScale.current = 1; - creatOffscreenCanvas(); - updateCanvasSize(); - clearCanvas(); - - ctx!.drawImage(img, 0, 0, canvas!.width, canvas!.height); - resolve(); - }; - }); - }, [imageSrc]); - - const invertPainting = (isChecked: boolean) => { - const ctx = overlayCanvasRef.current!.getContext('2d'); - - if (!ctx) return; - - if (isChecked) { - const canvasWidth = overlayCanvasRef.current!.width; - const canvasHeight = overlayCanvasRef.current!.height; - - clearOverlayCanvas(); - - const offscreenCanvas = new OffscreenCanvas( - overlayCanvasRef.current!.width, - overlayCanvasRef.current!.height - ); - - if (!invertWorkerRef.current) { - invertWorkerRef.current = new Worker( - new URL('./invert-worker.ts', import.meta.url), - { - type: 'module' - } - ); - } - - invertWorkerRef.current.onmessage = (event: any) => { - if (event.data.type === 'done' && event.data.imageData) { - // draw the data to the overlay canvas - const ctx = overlayCanvasRef.current!.getContext('2d')!; - ctx.putImageData(event.data.imageData, 0, 0); - - isLoadingMaskRef.current = false; - saveImage(); - } - }; - - // send offscreen canvas to worker - invertWorkerRef.current?.postMessage( - { canvas: offscreenCanvas, type: 'init' }, - [offscreenCanvas] - ); - - // send draw data to worker - const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight); - invertWorkerRef.current?.postMessage({ - type: 'draw', - width: imageData.width, - height: imageData.height, - strokes: strokesRef.current, - maskStrokes: maskStorkeRef.current - }); - } else { - redrawStrokes(strokesRef.current); - } - }; - - const updateCursorSize = () => { - cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`; - cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`; - }; - - const initializeImage = useCallback(async () => { - await drawImage(); - onScaleImageSize?.({ - width: canvasRef.current!.width, - height: canvasRef.current!.height - }); - - if (imageStatus.isResetNeeded) { - onReset(); - resetCanvas(); - } else if ( - (strokesRef.current.length || maskStorkeRef.current.length) && - imageStatus.isOriginal && - !negativeMaskRef.current - ) { - redrawStrokes(strokesRef.current); - } else if ( - (strokesRef.current.length || maskStorkeRef.current.length) && - imageStatus.isOriginal && - negativeMaskRef.current - ) { - invertPainting(true); - } - console.log('Image status:', imageStatus, negativeMaskRef.current); - - updateCursorSize(); - }, [drawImage, imageStatus.isOriginal, imageStatus.isResetNeeded]); - - const handleFitView = () => { - fitView(); - setActiveScale(autoScale.current); - updateCursorSize(); - }; - - const handleBrushSizeChange = (value: number) => { - setLineWidth(value); - cursorRef.current!.style.width = `${value}px`; - cursorRef.current!.style.height = `${value}px`; - }; - - const handleOnChangeMask = (e: any) => { - negativeMaskRef.current = e.target.checked; - invertPainting(e.target.checked); - setInvertMask(e.target.checked); - }; - - useEffect(() => { - initializeImage(); - }, [initializeImage]); - - useEffect(() => { - const handleUndoShortcut = (e: KeyboardEvent) => { - if ( - (e.ctrlKey || e.metaKey) && - e.key === 'z' && - !negativeMaskRef.current - ) { - undo(); - } - }; - window.addEventListener('keydown', handleUndoShortcut); - - return () => { - window.removeEventListener('keydown', handleUndoShortcut); - }; - }, []); - - useEffect(() => { - const handleMouseDown = (e: MouseEvent) => { - mouseDownState.current = true; - }; - - const handleMouseUp = (e: MouseEvent) => { - mouseDownState.current = false; - }; - - // mouse down - window.addEventListener('mousedown', handleMouseDown); - - // mouse up - window.addEventListener('mouseup', handleMouseUp); - return () => { - clearTimeout(timer.current); - - window.removeEventListener('mousedown', handleMouseDown); - window.removeEventListener('mouseup', handleMouseUp); - }; - }, []); - - const handleDeleteMask = () => { - setInvertMask(false); - redrawStrokes(strokesRef.current); - saveImage(); - }; - - useImperativeHandle(ref, () => ({ - clearMask: handleDeleteMask, - loadMaskPixs: async function (strokes: Stroke[]) { - setMaskStrokes(strokes); - setStrokes([]); - setTransform(); - clearOverlayCanvas(); - loadMaskPixs(strokes); - } - })); - - useEffect(() => { - invertWorkerRef.current = new Worker( - new URL('./invert-worker.ts', import.meta.url), - { - type: 'module' - } - ); - - loadMaksWorkerRef.current = new Worker( - new URL('./offscreen-worker.ts', import.meta.url), - { - type: 'module' - } - ); - - return () => { - invertWorkerRef.current?.terminate(); - loadMaksWorkerRef.current?.terminate(); - }; - }, []); - - useEffect(() => { - if (maskUpload?.length) { - // clear the overlay canvas - clearOverlayCanvas(); - } - }, [maskUpload]); - - useEffect(() => { - if (disabled && cursorRef.current) { - cursorRef.current!.style.display = 'none'; - } - }, [disabled]); - - return ( -

-
- - - -
-
- {loadingSize.loading && ( - - )} - - { - if (isLoadingMaskRef.current) { - return; - } - mouseDownState.current = true; - startDrawing(event); - }} - onMouseUp={(event) => { - if (isLoadingMaskRef.current) { - return; - } - mouseDownState.current = false; - endDrawing(event); - }} - onMouseEnter={handleMouseEnter} - onWheel={handleOnWheel} - onMouseMove={(e) => { - if (isLoadingMaskRef.current) { - return; - } - handleMouseMove(e); - draw(e); - }} - onMouseLeave={(e) => { - if (isLoadingMaskRef.current) { - return; - } - endDrawing(e); - handleMouseLeave(); - }} - /> -
-
-
- ); - } -); - -export default React.memo(CanvasImageEditor); diff --git a/src/components/image-editor/invert-worker.ts b/src/components/image-editor/invert-worker.ts deleted file mode 100644 index 6c95eeaf..00000000 --- a/src/components/image-editor/invert-worker.ts +++ /dev/null @@ -1,54 +0,0 @@ -/// - -let offscreenCanvas: OffscreenCanvas; -let ctx: OffscreenCanvasRenderingContext2D; - -const COLOR = 'rgba(0, 0, 255, 0.3)'; - -type Point = { x: number; y: number; lineWidth: number }; -type Stroke = Point[]; - -self.onmessage = (event) => { - const { width, height, strokes, maskStrokes } = event.data; - - if (event.data.type === 'init') { - offscreenCanvas = event.data.canvas; - ctx = offscreenCanvas!.getContext('2d')!; - return; - } - - if (event.data.type === 'draw') { - ctx.fillStyle = COLOR; - ctx.fillRect(0, 0, width, height); - - ctx.globalCompositeOperation = 'destination-out'; - - [...strokes].forEach((stroke: Stroke) => { - stroke.forEach((point: { x: number; y: number; lineWidth: number }) => { - const lineWidth = point.lineWidth; - ctx.fillStyle = 'rgba(0,0,0,1)'; - ctx.beginPath(); - ctx.arc(point.x, point.y, lineWidth / 2, 0, Math.PI * 2); - ctx.fill(); - }); - }); - - // points - maskStrokes?.forEach((stroke: Stroke) => { - stroke.forEach((point: { x: number; y: number; lineWidth: number }) => { - const lineWidth = 4; - ctx.fillStyle = 'rgba(0,0,0,1)'; - ctx.beginPath(); - ctx.arc(point.x, point.y, lineWidth / 2, 0, Math.PI * 2); - ctx.fill(); - }); - }); - - const newImageData = ctx.getImageData(0, 0, width, height); - self.postMessage({ type: 'done', imageData: newImageData }, [ - newImageData.data.buffer - ]); - } -}; - -export {}; diff --git a/src/components/image-editor/offscreen-worker.ts b/src/components/image-editor/offscreen-worker.ts deleted file mode 100644 index ee093035..00000000 --- a/src/components/image-editor/offscreen-worker.ts +++ /dev/null @@ -1,111 +0,0 @@ -let offscreenCanvas: OffscreenCanvas; -let ctx: OffscreenCanvasRenderingContext2D; - -const COLOR = 'rgba(0, 0, 255, 0.3)'; - -type Point = { x: number; y: number; lineWidth: number }; -type Stroke = Point[]; - -const postDone = () => { - const imageData = ctx.getImageData( - 0, - 0, - offscreenCanvas.width, - offscreenCanvas.height - ); - self.postMessage({ type: 'done', imageData }); -}; - -const drawFillRect = ( - ctx: OffscreenCanvasRenderingContext2D, - stroke: Stroke, - options: any -) => { - const { color } = options; - - stroke?.forEach(({ x, y }) => { - const width = options.lineWidth || 10; - - ctx.save(); - ctx.fillStyle = 'rgba(0,0,0,1)'; - ctx.globalCompositeOperation = 'destination-out'; - ctx.fillRect(x - width / 2, y - width / 2, width, width); - - ctx.globalCompositeOperation = 'source-over'; - ctx.fillStyle = color; - ctx.fillRect(x - width / 2, y - width / 2, width, width); - ctx.restore(); - }); -}; - -const drawStrokes = (strokes: Stroke[]) => { - strokes?.forEach((stroke) => { - drawFillRect(ctx, stroke, { - color: COLOR - }); - }); -}; - -const drawLine = ( - stroke: Stroke | Point[], - options: { - lineWidth?: number; - color: string; - compositeOperation: 'source-over' | 'destination-out'; - } -) => { - const { color, compositeOperation } = options; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.globalCompositeOperation = compositeOperation; - ctx.save(); - - ctx.beginPath(); - - stroke?.forEach((point, i) => { - const { x, y } = point; - ctx.lineWidth = point.lineWidth || 10; - if (i === 0) { - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - if (compositeOperation === 'source-over') { - ctx.strokeStyle = color; - } - ctx.stroke(); - ctx.restore(); -}; - -const drawLines = (strokes: Stroke[]) => { - strokes?.forEach((stroke: Point[], index) => { - drawLine(stroke, { - color: COLOR, - compositeOperation: 'destination-out' - }); - - drawLine(stroke, { - color: COLOR, - compositeOperation: 'source-over' - }); - }); -}; - -self.onmessage = (event) => { - if (event.data.type === 'init') { - offscreenCanvas = event.data.canvas; - ctx = offscreenCanvas!.getContext('2d')!; - return; - } - - if (event.data.type === 'draw') { - ctx?.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height); - const { maskStrokes, strokes } = event.data; - drawStrokes(maskStrokes); - drawLines(strokes); - postDone(); - } -}; - -export {}; diff --git a/src/components/image-editor/tools-bar.tsx b/src/components/image-editor/tools-bar.tsx deleted file mode 100644 index d37ad677..00000000 --- a/src/components/image-editor/tools-bar.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import IconFont from '@/components/icon-font'; -import { KeyMap } from '@/config/hotkeys'; -import UploadImg from '@/pages/playground/components/upload-img'; -import { - ClearOutlined, - DownloadOutlined, - ExpandOutlined, - FormatPainterOutlined, - UndoOutlined -} from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Checkbox, Slider, Tooltip } from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import React from 'react'; - -interface ToolsBarProps { - disabled: boolean; - loading: boolean; - lineWidth: number; - uploadButton?: React.ReactNode; - invertMask?: boolean; - accept?: string; - handleBrushSizeChange: (value: number) => void; - undo: () => void; - onClear: () => void; - handleFitView: () => void; - handleUpdateImageList: (fileList: any[]) => void; - handleUpdateMaskList: (fileList: any[]) => void; -} - -const ToolsBar: React.FC = (props) => { - const { - disabled, - loading, - lineWidth, - invertMask, - accept, - handleBrushSizeChange, - undo, - onClear, - handleFitView, - handleUpdateImageList, - handleUpdateMaskList - } = props; - const intl = useIntl(); - return ( -
- - - {intl.formatMessage({ id: 'playground.image.brushSize' })} - - -
- } - > - - - - [{KeyMap.UNDO.textKeybinding}] - - {intl.formatMessage({ id: 'common.button.undo' })} - - - } - > - - - - - - - } - disabled={loading || invertMask} - handleUpdateImgList={handleUpdateMaskList} - size="middle" - accept={accept} - > - - - -
- ); -}; - -interface ImageActionsBarProps { - disabled: boolean; - maskUpload?: any[]; - isOriginal: boolean; - invertMask: boolean; - handleOnChangeMask: (e: CheckboxChangeEvent) => void; - downloadMask: () => void; - download: () => void; -} - -const ImageActionsBar: React.FC = (props) => { - const intl = useIntl(); - const { - disabled, - isOriginal, - invertMask, - handleOnChangeMask, - downloadMask, - download - } = props; - return ( -
- {isOriginal && ( - <> - - {intl.formatMessage({ - id: 'playground.image.negativeMask.tips' - })} - - } - > - - - {intl.formatMessage({ - id: 'playground.image.negativeMask' - })} - - - - - - - - )} - {!isOriginal && ( - - - - )} -
- ); -}; - -export { ImageActionsBar, ToolsBar }; diff --git a/src/components/label-cell/index.tsx b/src/components/label-cell/index.tsx deleted file mode 100644 index bf0ff7cb..00000000 --- a/src/components/label-cell/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; - -const LabelsWrapper = styled.div` - display: flex; - flex-wrap: wrap; - gap: 6px; -`; - -interface LabelCellProps { - labels: Record; -} - -const LabelsCell: React.FC = ({ labels }) => ( - - {_.map(labels, (value: string, key: string) => ( - - {key} - :{value} - - ))} - -); - -export default LabelsCell; diff --git a/src/components/label-selector/autocomplete-item.tsx b/src/components/label-selector/autocomplete-item.tsx deleted file mode 100644 index bce8bf7b..00000000 --- a/src/components/label-selector/autocomplete-item.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import AutoComplete from '@/components/seal-form/auto-complete'; -import { MinusOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Tooltip } from 'antd'; -import _ from 'lodash'; -import React, { useMemo, useState } from 'react'; -import { useLabelSelectorContext } from './context'; -import './styles/label-item.less'; -interface LabelItemProps { - label: { - key: string; - value: string; - }; - labels?: Record; - labelKey?: string; - labelValue?: string; - keyAddon?: React.ReactNode; - valueAddon?: React.ReactNode; - seperator?: string; - labelList: { key: string; value: string }[]; - disabled?: boolean; - onDelete?: () => void; - onChange?: (params: { key: string; value: string }) => void; - onPaste?: (e: any) => void; - onBlur?: (e: any, type: string) => void; -} -const LabelItem: React.FC = ({ - labels, - label, - labelList, - seperator, - keyAddon, - valueAddon, - disabled, - onChange, - onDelete, - onBlur -}) => { - const intl = useIntl(); - const [open, setOpen] = useState(false); - const { options, placeholder = [] } = useLabelSelectorContext(); - - const keyOptions = useMemo(() => { - return options?.filter( - (item) => !_.has(labels, item.value) || item.value === label.key - ); - }, [labels, options]); - - const valueOptions = useMemo(() => { - return options?.find((item) => item.value === label.key)?.children || []; - }, [label.key, options]); - - const handleOnValueChange = (value: string) => { - onChange?.({ - key: label.key, - value: value - }); - }; - - const handleOnKeyChange = (key: any) => { - onChange?.({ - key, - value: label.value - }); - }; - - const handleKeyOnBlur = (e: any, type: string) => { - const val = e.target.value; - // has duplicate key - const duplicates = _.filter( - labelList, - (item: Global.BaseListItem) => val && val === item.key - ); - if (duplicates.length > 1) { - setOpen(true); - onChange?.({ - key: '', - value: label.value - }); - setTimeout(() => { - setOpen(false); - }, 1000); - } else { - setOpen(false); - } - onBlur?.(e, type); - }; - - return ( -
-
- {keyAddon ?? ( - - handleKeyOnBlur(e, 'key')} - > - - )} -
- {seperator && {seperator}} -
- {valueAddon ?? ( - onBlur?.(e, 'value')} - > - )} -
- {!disabled && ( - - )} -
- ); -}; - -export default LabelItem; diff --git a/src/components/label-selector/context.ts b/src/components/label-selector/context.ts deleted file mode 100644 index d13a49d1..00000000 --- a/src/components/label-selector/context.ts +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -interface LabelSelectorContextProps { - options?: Array<{ - label: string; - value: string | number; - children?: { label: string; value: string | number }[]; - }>; - placeholder?: string[]; - currentData?: Record; -} - -export const LabelSelectorContext = - React.createContext( - {} as LabelSelectorContextProps - ); - -export const useLabelSelectorContext = () => { - const context = React.useContext(LabelSelectorContext); - if (!context) { - throw new Error( - 'useLabelSelectorContext must be used within a LabelSelectorProvider' - ); - } - return context; -}; diff --git a/src/components/label-selector/index.tsx b/src/components/label-selector/index.tsx deleted file mode 100644 index b02180dc..00000000 --- a/src/components/label-selector/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { useIntl } from '@umijs/max'; -import _ from 'lodash'; -import React, { useEffect, useState } from 'react'; -import Inner from './inner'; - -interface LabelSelectorProps { - labels: Record; - label?: string; - btnText?: string; - description?: React.ReactNode; - disabled?: boolean; - isAutoComplete?: boolean; - enablePaste?: boolean; - onChange?: (labels: Record) => void; - onBlur?: (e: any, type: string, index: number) => void; - onDelete?: (index: number) => void; -} - -const LabelSelector: React.FC = ({ - labels, - onChange, - onBlur, - onDelete, - disabled, - label, - btnText, - description, - isAutoComplete, - enablePaste = true -}) => { - const intl = useIntl(); - const [labelsData, setLabelsData] = useState({}); - const [labelList, setLabelList] = useState<{ key: string; value: string }[]>( - [] - ); - - useEffect(() => { - if (!_.isEqual(labels, labelsData)) { - setLabelsData(labels || {}); - const list = _.map(_.keys(labels), (key: string) => { - return { - key, - value: labels[key] - }; - }); - setLabelList(list); - } - }, [labels]); - - const handleLabelListChange = (list: { key: string; value: string }[]) => { - setLabelList(list); - }; - - const handleLabelsChange = (data: Record) => { - console.log('handleLabelsChange', data); - setLabelsData(data); - onChange?.(data); - }; - - const updateLabels = (list: { key: string; value: string }[]) => { - const newLabels = _.reduce( - list, - (result: any, item: any) => { - if (item.key) { - result[item.key] = item.value; - } - return result; - }, - {} - ); - onChange?.(newLabels); - }; - - const handleOnPaste = ( - e: React.ClipboardEvent, - index: number - ) => { - if (!enablePaste) return; - const clipboardText = e.clipboardData.getData('text'); - if (!clipboardText || clipboardText.indexOf('=') === -1) return; - e.preventDefault(); - - const lines = _.split(clipboardText, /\r?\n/) - .map((line: string) => line.trim()) - .filter((line: string) => line && line.includes('=')); - - const parsedData = lines.map((line: string) => { - const [key, value] = line.split(/=(.+)/).map((s) => s.trim()); - return { key, value }; - }); - - const newPairs = [...labelList]; - newPairs.splice(index, 1, ...parsedData); - - updateLabels(newPairs); - setLabelList(newPairs); - }; - - return ( - - ); -}; - -export default LabelSelector; diff --git a/src/components/label-selector/inner.tsx b/src/components/label-selector/inner.tsx deleted file mode 100644 index 3ea172e9..00000000 --- a/src/components/label-selector/inner.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import AutoCompleteItem from './autocomplete-item'; -import LabelItem from './label-item'; -import Wrapper from './wrapper'; -interface LabelSelectorProps { - labels: Record; - label?: string; - btnText?: string; - isAutoComplete?: boolean; - labelList: Array<{ key: string; value: string }>; - onLabelListChange: (list: { key: string; value: string }[]) => void; - onChange?: (labels: Record) => void; - onPaste?: (e: any, index: number) => void; - onBlur?: (e: any, type: string, index: number) => void; - onDelete?: (index: number) => void; - description?: React.ReactNode; - disabled?: boolean; -} - -const Inner: React.FC = ({ - labels, - labelList, - onChange, - onLabelListChange, - onPaste, - onBlur, - onDelete, - disabled, - label, - btnText, - description, - isAutoComplete -}) => { - const updateLabels = (list: { key: string; value: string }[]) => { - const newLabels = _.reduce( - list, - (result: any, item: any) => { - if (item.key) { - result[item.key] = item.value; - } - return result; - }, - {} - ); - onChange?.(newLabels); - }; - const handleOnChange = (index: number, label: any) => { - const list = _.cloneDeep(labelList); - list[index] = label; - onLabelListChange(list); - updateLabels(list); - }; - - const handleAddLabel = () => { - const newLabelList = [ - ...labelList, - { - key: '', - value: '' - } - ]; - onLabelListChange(newLabelList); - updateLabels(newLabelList); - }; - - const handleOnDelete = (index: number) => { - const list = _.cloneDeep(labelList); - list.splice(index, 1); - onLabelListChange(list); - updateLabels(list); - onDelete?.(index); - }; - - return ( - - <> - {isAutoComplete - ? labelList?.map((item: any, index: number) => { - return ( - handleOnDelete(index)} - onChange={(obj) => handleOnChange(index, obj)} - onPaste={(e) => onPaste?.(e, index)} - onBlur={(e: any, type: string) => onBlur?.(e, type, index)} - /> - ); - }) - : labelList?.map((item: any, index: number) => { - return ( - handleOnDelete(index)} - onChange={(obj) => handleOnChange(index, obj)} - onPaste={(e) => onPaste?.(e, index)} - onBlur={(e: any, type: string) => onBlur?.(e, type, index)} - /> - ); - })} - - - ); -}; - -export default Inner; diff --git a/src/components/label-selector/label-item.tsx b/src/components/label-selector/label-item.tsx deleted file mode 100644 index f8425178..00000000 --- a/src/components/label-selector/label-item.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import SealInput from '@/components/seal-form/seal-input'; -import { MinusOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Tooltip } from 'antd'; -import _ from 'lodash'; -import React, { useState } from 'react'; -import './styles/label-item.less'; -interface LabelItemProps { - label: { - key: string; - value: string; - }; - labelKey?: string; - labelValue?: string; - keyAddon?: React.ReactNode; - valueAddon?: React.ReactNode; - seperator?: string; - labelList: { key: string; value: string }[]; - disabled?: boolean; - onDelete?: () => void; - onChange?: (params: { key: string; value: string }) => void; - onPaste?: (e: any) => void; - onBlur?: (e: any, type: string) => void; -} -const LabelItem: React.FC = ({ - label, - labelList, - seperator, - keyAddon, - valueAddon, - disabled, - onChange, - onDelete, - onPaste, - onBlur -}) => { - const intl = useIntl(); - const [open, setOpen] = useState(false); - - const handleOnValueChange = (e: any) => { - const value = e.target.value; - onChange?.({ - key: label.key, - value: value - }); - }; - - const handleOnKeyChange = (e: any) => { - const key = e.target.value; - onChange?.({ - key, - value: label.value - }); - }; - - const handleKeyOnBlur = (e: any, type: string) => { - const val = e.target.value; - // has duplicate key - const duplicates = _.filter( - labelList, - (item: Global.BaseListItem) => val && val === item.key - ); - if (duplicates.length > 1) { - setOpen(true); - onChange?.({ - key: '', - value: label.value - }); - setTimeout(() => { - setOpen(false); - }, 1000); - } else { - setOpen(false); - } - onBlur?.(e, type); - }; - - return ( -
-
- {keyAddon ?? ( - - - handleKeyOnBlur(e, 'key')} - onPaste={onPaste} - > - - - )} -
- {seperator && {seperator}} -
- {valueAddon ?? ( - onBlur?.(e, 'value')} - > - )} -
- {!disabled && ( - - )} -
- ); -}; - -export default LabelItem; diff --git a/src/components/label-selector/styles/label-item.less b/src/components/label-selector/styles/label-item.less deleted file mode 100644 index fab81bfe..00000000 --- a/src/components/label-selector/styles/label-item.less +++ /dev/null @@ -1,29 +0,0 @@ -.label-item { - display: flex; - margin-bottom: 12px; - align-items: center; - justify-content: flex-start; - - .seprator { - display: flex; - flex: none; - width: 12px; - align-items: center; - justify-content: center; - color: var(--ant-color-text-tertiary); - } - - .btn { - width: 24px; - margin-left: 10px; - flex: none; - } - - .label-key { - flex: 1; - } - - .label-value { - flex: 1; - } -} diff --git a/src/components/label-selector/wrapper.tsx b/src/components/label-selector/wrapper.tsx deleted file mode 100644 index 7c1c4ec4..00000000 --- a/src/components/label-selector/wrapper.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import LabelInfo from '@/components/seal-form/components/label-info'; -import { PlusOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -interface WrapperProps { - required?: boolean; - label?: React.ReactNode; - description?: React.ReactNode; - labelExtra?: React.ReactNode; - children: React.ReactNode; - btnText?: string; - disabled?: boolean; - onAdd?: () => void; - styles?: { - wrapper?: React.CSSProperties; - }; - button?: React.ReactNode; -} - -const Container = styled.div` - position: relative; - padding: 14px; - padding-top: 34px; - border: 1px solid var(--ant-color-border); - border-radius: var(--ant-border-radius-lg); - display: flex; - width: 100%; - flex-direction: column; - .label { - position: absolute; - left: 16px; - line-height: 1; - top: 12px; - color: var(--ant-color-text-tertiary); - } -`; - -const ButtonWrapper = styled.div` - margin-top: 8px; -`; - -const Wrapper: React.FC = ({ - required, - children, - label, - description, - labelExtra, - onAdd, - btnText, - disabled, - button, - styles -}) => { - const intl = useIntl(); - return ( - - {label && ( - - - - )} - {children} - {!disabled && ( - - {button || ( - - )} - - )} - - ); -}; - -export default Wrapper; diff --git a/src/components/list-input/hint-input.tsx b/src/components/list-input/hint-input.tsx deleted file mode 100644 index 43995f6f..00000000 --- a/src/components/list-input/hint-input.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import AutoComplete from '@/components/seal-form/auto-complete'; -import _ from 'lodash'; -import React from 'react'; - -interface HintInputProps { - value: string; - label?: string; - onChange: (value: string) => void; - onBlur?: (e: any) => void; - onPaste?: (e: any) => void; - placeholder?: string; - trim?: boolean; - sourceOptions?: Global.HintOptions[]; -} - -const matchReg = /[^=]+=[^=]*$/; - -const HintInput: React.FC = (props) => { - const { - value, - label, - onChange, - onBlur, - onPaste, - sourceOptions, - trim = true - } = props; - const cursorPosRef = React.useRef(0); - const contextBeforeCursorRef = React.useRef(''); - const [options, setOptions] = React.useState< - Array> - >([]); - - const generateOptions = (context: string) => { - if (!context) { - setOptions(sourceOptions || []); - return; - } - const match = context.match(matchReg); - if (!match) { - const list = _.filter(sourceOptions, (item: Global.HintOptions) => - item.label.includes(context) - ); - setOptions(list); - return; - } - const [key, value] = _.split(match[0], '='); - const data = _.find( - sourceOptions, - (item: Global.HintOptions) => item.label === key - ); - if (!data) { - setOptions([]); - return; - } - const list = _.filter(data.opts, (item: Global.BaseOption) => - item.label.includes(value) - ); - setOptions(list); - }; - - const replaceLastEqual = (value: string) => { - const matchStr = contextBeforeCursorRef.current.match(matchReg); - if (matchStr) { - const arr = _.split(matchStr[0], '='); - onChange(`${arr[0]}=${value}`); - } else { - onChange(value?.trim()); - } - }; - - const getContextBeforeCursor = _.debounce((e: any) => { - cursorPosRef.current = e.target.selectionStart; - contextBeforeCursorRef.current = e.target.value.slice( - 0, - cursorPosRef.current - ); - generateOptions(contextBeforeCursorRef.current); - }, 100); - - const handleInput = (e: any) => { - getContextBeforeCursor(e); - onChange(e.target.value); - }; - - const handleOnSelect = (value: string) => { - replaceLastEqual(value); - setOptions([]); - }; - - return ( - - ); -}; - -export default HintInput; diff --git a/src/components/list-input/index.tsx b/src/components/list-input/index.tsx deleted file mode 100644 index 677121c7..00000000 --- a/src/components/list-input/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { parseParamsString } from '@/utils'; -import _ from 'lodash'; -import React, { useEffect } from 'react'; -import Wrapper from '../label-selector/wrapper'; -import ListItem from './list-item'; - -interface ListInputProps { - required?: boolean; - dataList: string[]; - label?: React.ReactNode; - description?: React.ReactNode; - btnText?: string; - options?: Global.HintOptions[]; - placeholder?: string; - labelExtra?: React.ReactNode; - trim?: boolean; - styles?: { - wrapper?: React.CSSProperties; - item?: React.CSSProperties; - }; - onChange: (data: string[]) => void; - onBlur?: (e: any, index: number) => void; - onDelete?: (index: number) => void; - renderItem?: ( - data: any, - props: { - onChange: (value: string) => void; - onBlur?: (e: any) => void; - } - ) => React.ReactNode; -} - -const ListInput: React.FC = (props) => { - const { - dataList, - label, - description, - onChange, - onBlur, - onDelete, - btnText, - options, - labelExtra, - trim = true, - styles, - required, - renderItem - } = props; - const [list, setList] = React.useState<{ value: string; uid: number }[]>([]); - const countRef = React.useRef(0); - - const updateCountRef = () => { - countRef.current = countRef.current + 1; - }; - - const handleOnRemove = (index: number) => { - const values = _.cloneDeep(list); - values.splice(index, 1); - const valueList = _.map(values, 'value').filter((val: string) => !!val); - setList(values); - onChange(valueList); - onDelete?.(index); - }; - - const handleOnChange = (value: string, index: number) => { - const values = _.cloneDeep(list); - values[index].value = value; - const valueList = _.map(values, 'value').filter((val: string) => !!val); - setList(values); - onChange(valueList); - }; - - const handleOnAdd = () => { - updateCountRef(); - const values = _.cloneDeep(list); - values.push({ - value: '', - uid: countRef.current - }); - setList(values); - }; - - const handleOnPaste = (e: any, index: number) => { - const pastedText = e.clipboardData?.getData('text'); - if (!pastedText) return; - - const lines = parseParamsString(pastedText); - - if (lines.length <= 1) { - // if there's only one line, let the default paste behavior handle it - return; - } - - e.preventDefault(); - - const values = _.cloneDeep(list); - - // replace the current item with the first line of the pasted text - values[index].value = trim ? lines[0].trim() : lines[0]; - - // create new list items for the remaining lines - for (let i = 1; i < lines.length; i++) { - updateCountRef(); - values.splice(index + i, 0, { - value: trim ? lines[i].trim() : lines[i], - uid: countRef.current - }); - } - - const valueList = _.map(values, 'value').filter((val: string) => !!val); - setList(values); - onChange(valueList); - }; - - React.useEffect(() => { - const valueList = _.map(list, 'value').filter((val: string) => !!val); - if (!_.isEqual(valueList, dataList)) { - const values = _.map(dataList, (value: string) => { - updateCountRef(); - return { - value, - uid: countRef.current - }; - }); - setList(values); - } - }, [dataList]); - - useEffect(() => { - if (required && list.length === 0) { - handleOnAdd(); - } - }, [required]); - - return ( - - <> - {_.map(list, (item: any, index: number) => { - return ( - onBlur?.(e, index)} - onRemove={() => handleOnRemove(index)} - onChange={(val) => handleOnChange(val, index)} - onPaste={(e) => handleOnPaste(e, index)} - trim={trim} - renderItem={renderItem} - /> - ); - })} - - - ); -}; - -export default ListInput; diff --git a/src/components/list-input/list-item.tsx b/src/components/list-input/list-item.tsx deleted file mode 100644 index 8e768347..00000000 --- a/src/components/list-input/list-item.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { MinusOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; -import React from 'react'; -import HintInput from './hint-input'; -import './styles/list-item.less'; - -interface LabelItemProps { - onRemove: () => void; - onChange: (value: string) => void; - onBlur?: (e: any) => void; - onPaste?: (e: any) => void; - renderItem?: ( - data: any, - props: { - onChange: (value: string) => void; - onBlur?: (e: any) => void; - onPaste?: (e: any) => void; - } - ) => React.ReactNode; - value: string; - label?: string; - placeholder?: string; - options?: Global.HintOptions[]; - trim?: boolean; - data?: any; - required?: boolean; -} - -const ListItem: React.FC = (props) => { - const { - onRemove, - onChange, - onBlur, - onPaste, - label, - value, - options, - trim = true, - data, - required, - renderItem - } = props; - - const handleOnChange = (value: any) => { - onChange(value); - }; - - return ( -
- {renderItem ? ( - renderItem(data, { - onChange: handleOnChange, - onBlur, - onPaste - }) - ) : ( - - )} - {!required && ( -
- ); -}; - -export default ListItem; diff --git a/src/components/list-input/style/list-item.less b/src/components/list-input/style/list-item.less deleted file mode 100644 index 3eb71d85..00000000 --- a/src/components/list-input/style/list-item.less +++ /dev/null @@ -1,15 +0,0 @@ -.list-item { - display: flex; - align-items: center; - justify-content: flex-start; - width: 100%; - margin-bottom: 12px; - - .field-wrapper { - flex: 1; - } - - .btn { - margin-left: 10px; - } -} diff --git a/src/components/list-input/styles/list-item.less b/src/components/list-input/styles/list-item.less deleted file mode 100644 index 3eb71d85..00000000 --- a/src/components/list-input/styles/list-item.less +++ /dev/null @@ -1,15 +0,0 @@ -.list-item { - display: flex; - align-items: center; - justify-content: flex-start; - width: 100%; - margin-bottom: 12px; - - .field-wrapper { - flex: 1; - } - - .btn { - margin-left: 10px; - } -} diff --git a/src/components/logs-viewer/config.ts b/src/components/logs-viewer/config.ts deleted file mode 100644 index f7f52ff2..00000000 --- a/src/components/logs-viewer/config.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g; -export const replaceLineRegex = /\r\n/g; - -export const PageSize = 1000; - -export const throttle = void>( - func: T, - wait: number -): ((this: ThisParameterType, ...args: Parameters) => void) => { - let timeout: ReturnType | null = null; - let previous = Date.now(); - - return function (this: ThisParameterType, ...args: Parameters): void { - const now = Date.now(); - const remaining = wait - (now - previous); - const context = this as ThisParameterType; - - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - previous = now; - func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(() => { - previous = Date.now(); - timeout = null; - func.apply(context, args); - }, remaining); - } - }; -}; diff --git a/src/components/logs-viewer/logs-list.tsx b/src/components/logs-viewer/logs-list.tsx deleted file mode 100644 index 95fe2515..00000000 --- a/src/components/logs-viewer/logs-list.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import useOverlayScroller from '@/hooks/use-overlay-scroller'; -import classNames from 'classnames'; -import _, { throttle } from 'lodash'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState -} from 'react'; -import './styles/logs-list.less'; - -interface LogsListProps { - dataList: any[]; - height?: number; - onScroll?: (data: { isTop: boolean; isBottom: boolean }) => void; - diffHeight?: number; - showNum?: boolean; - ref?: any; -} -const LogsList: React.FC = forwardRef((props, ref) => { - const { dataList, height, showNum, onScroll, diffHeight = 96 } = props; - const { - initialize, - updateScrollerPosition, - updateScrollerPositionToTop, - generateInstance, - scrollEventElement, - instance, - initialized - } = useOverlayScroller({ - options: { - scrollbars: { - theme: 'os-theme-light' - } - } - }); - const [innerHieght, setInnerHeight] = useState( - window.innerHeight - diffHeight - ); - const scroller = useRef({}); - const stopScroll = useRef(false); - - const scrollToBottom = useCallback(() => { - updateScrollerPosition(0); - }, [updateScrollerPosition]); - - const debounceResetStopScroll = _.debounce(() => { - stopScroll.current = false; - }, 30000); - - const scrollToTop = useCallback(() => { - stopScroll.current = true; - updateScrollerPositionToTop(); - debounceResetStopScroll(); - }, [updateScrollerPositionToTop]); - - useImperativeHandle(ref, () => ({ - scrollToBottom, - scrollToTop, - scroller: scroller.current - })); - - const handleOnWheel = (e: any) => { - const scrollTop = scrollEventElement?.current?.scrollTop; - const scrollHeight = scrollEventElement?.current?.scrollHeight; - const clientHeight = scrollEventElement?.current?.clientHeight; - - stopScroll.current = scrollTop + clientHeight <= scrollHeight; - - const isBottom = scrollTop + clientHeight + 150 >= scrollHeight; - // is scroll to top - if (scrollTop <= 10) { - onScroll?.({ - isTop: true, - isBottom: false - }); - } else if (isBottom) { - onScroll?.({ - isTop: false, - isBottom: true - }); - stopScroll.current = false; - } else { - onScroll?.({ - isTop: false, - isBottom: false - }); - } - }; - - const debounceUpdateScrollerPosition = _.debounce(() => { - generateInstance(); - updateScrollerPosition(0); - }, 200); - - useEffect(() => { - const handleResize = throttle(() => { - const viewportHeight = window.innerHeight; - const viewHeight = viewportHeight - diffHeight; - setInnerHeight(viewHeight); - }, 100); - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }, [diffHeight]); - - useEffect(() => { - if (scroller.current) { - initialize(scroller.current); - } - }, [initialize]); - - useEffect(() => { - if (dataList.length && !stopScroll.current && instance) { - updateScrollerPosition(0); - } else if (dataList.length && !stopScroll.current && scroller.current) { - if (!initialized) { - initialize(scroller.current); - } - if (!instance) { - debounceUpdateScrollerPosition(); - } else { - updateScrollerPosition(0); - } - } - }, [dataList, stopScroll.current, instance, scroller.current]); - - return ( -
-
- {_.map(dataList, (item: any, index: number) => { - return ( -
- {item.content} -
- ); - })} -
-
- ); -}); - -export default React.memo(LogsList); diff --git a/src/components/logs-viewer/logs-pagination.tsx b/src/components/logs-viewer/logs-pagination.tsx deleted file mode 100644 index 868ee8dc..00000000 --- a/src/components/logs-viewer/logs-pagination.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { - DownOutlined, - UpOutlined, - VerticalLeftOutlined -} from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Tooltip } from 'antd'; -import React from 'react'; -import './styles/pagination.less'; - -interface LogsPaginationProps { - page: number; - total: number; - pageSize?: number; - onPrev?: () => void; - onNext?: () => void; - onBackend?: () => void; - onToFirst?: () => void; -} - -const LogsPagination: React.FC = (props) => { - const { page, total, pageSize, onNext, onPrev, onBackend, onToFirst } = props; - const intl = useIntl(); - - const handleOnPrev = () => { - onPrev?.(); - }; - - const handleOnNext = () => { - onNext?.(); - }; - - return ( -
- { - <> - - - - - - - - } - - {page} /{' '} - {total} - - {page < total && ( - <> - - - - - - - - )} -
- ); -}; - -export default LogsPagination; diff --git a/src/components/logs-viewer/parse-worker.ts b/src/components/logs-viewer/parse-worker.ts deleted file mode 100644 index 8ed82206..00000000 --- a/src/components/logs-viewer/parse-worker.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { controlSeqRegex } from './config'; - -const removeBrackets = (str: string) => { - return str?.replace?.(/^\(…\)/, ''); -}; - -const removeBracketsFromLine = (row: string) => { - return row.startsWith('(…)') ? row.slice(3) : row; -}; - -interface MessageProps { - inputStr: string; - reset?: boolean; - page?: number; - isComplete?: boolean; - chunked?: boolean; - progress?: number; - percent?: number; - isDownloading?: boolean; -} -class AnsiParser { - private cursorRow: number = 0; - private cursorCol: number = 0; - private screen: string[][] = [['']]; - private rawDataRows: number = 0; - private uid: number = 0; - private isProcessing: boolean = false; - private taskQueue: string[] = []; - private page: number = 1; - private progress: number = 0; - private percent: number = 0; - private isComplete: boolean = false; - private chunked: boolean = true; // true: send data in chunks, false: send all data at once - private reminder: string = ''; - private lines: string[] = []; - isDownloading: boolean = false; - private pageSize: number = 500; - private colorMap = { - '30': 'black', - '31': 'red', - '32': 'green', - '33': 'yellow', - '34': 'blue', - '35': 'magenta', - '36': 'cyan', - '37': 'white' - }; - - constructor() { - this.reset(); - } - - public reset() { - this.cursorRow = 0; - this.cursorCol = 0; - this.screen = [['']]; - this.rawDataRows = 0; - this.uid = this.uid + 1; - this.lines = []; - this.reminder = ''; - this.page = 1; - } - - public setPage(page: number | undefined) { - this.page = page ?? 1; - } - - public setPercent(percent: number | undefined) { - this.percent = percent ?? 0; - } - - public setProgress(progress: number | undefined) { - this.progress = progress ?? 0; - } - - public setIsCompelete(isComplete: boolean) { - this.isComplete = isComplete; - } - - public setChunked(chunked: boolean) { - this.chunked = chunked ?? true; - } - - public setIsDownloading(isDownloading: boolean) { - this.isDownloading = isDownloading; - } - - private setId() { - this.uid += 1; - return this.uid; - } - - private handleText(text: string) { - for (let i = 0; i < text.length; i++) { - let char = text[i]; - if (char === '\r') { - let nextChar = text[i + 1]; - if (nextChar === '\n') { - continue; // windows new line: \r\n - } else { - this.cursorCol = 0; // move to the beginning of the line - } - } else if (char === '\n') { - this.rawDataRows++; - this.cursorRow++; - this.cursorCol = 0; // back to the beginning of the line - if (!this.screen[this.cursorRow]) { - this.screen[this.cursorRow] = ['']; - } - } else { - const currentLine = this.screen[this.cursorRow]; - currentLine[this.cursorCol] = char; - this.cursorCol++; - } - } - } - - private handleAnsiSequence(match: RegExpExecArray, isEnd: boolean) { - const n = parseInt(match[1] || '1', 10); - const m = parseInt(match[2] || '1', 10); - const command = match[3]; - switch (command) { - case 'A': - this.cursorRow = Math.max(0, this.cursorRow - n); - break; - case 'B': - this.cursorRow += n; - break; - case 'C': // move the cursor to the right - this.cursorCol += n; - break; - case 'D': // move the cursor to the left - this.cursorCol = Math.max(0, this.cursorCol - n); - break; - case 'H': // move the cursor to the specified position (n, m) - this.cursorRow = Math.max(0, n - 1); - this.cursorCol = Math.max(0, m - 1); - break; - case 'J': // clear the screen - if (n === 2) { - this.reset(); - } - break; - case 'm': - // if (match[1] === '0') { - // currentStyle = ''; - // } else if (colorMap[match[1]]) { - // currentStyle = `color: ${colorMap[match[1]]};`; - // } - break; - } - - while (this.screen.length <= this.cursorRow && !isEnd) { - this.screen.push(['']); - } - while (this.screen[this.cursorRow].length <= this.cursorCol && !isEnd) { - this.screen[this.cursorRow].push(''); - } - } - - private processInput(input: string) { - let match: RegExpExecArray | null; - let lastIndex = 0; - - while ((match = controlSeqRegex.exec(input)) !== null) { - const textBeforeControl = input.slice(lastIndex, match.index); - this.handleText(textBeforeControl); - - lastIndex = controlSeqRegex.lastIndex; - - this.handleAnsiSequence(match, lastIndex === input.length - 1); - } - - const remainingText = input.slice(lastIndex); - this.handleText(remainingText); - - // const result = this.screen.map((row, index) => ({ - // content: removeBracketsFromLine(row.join('')), - // uid: `${this.page}-${index}` - // })); - const result = this.screen.map((row, index) => - removeBracketsFromLine(row.join('')) - ); - - return { - data: result, - lines: this.rawDataRows, - remainder: '' - }; - } - - private getScreenText() { - const result = this.screen - .map((row) => removeBracketsFromLine(row.join(''))) - .join('\n'); - return result; - } - - private processInputByLine(input: string): { - data: string[]; - lines: number; - remainder: string; - } { - const lines = input?.split(/\r?\n/) || []; - const remainder = lines.pop() || ''; - - // const data = lines.join('\n'); - this.rawDataRows += lines.length; - lines.forEach((line) => { - this.lines.push(line); - }); - - return { - data: this.lines, - lines: this.rawDataRows, - remainder - }; - } - - private getAllLines() { - return this.lines.join('\n'); - } - - private async processQueue(): Promise { - if (this.isProcessing) { - return; - } - - this.isProcessing = true; - - while (this.taskQueue.length > 0) { - let input = ''; - - if (this.isDownloading) { - input = this.taskQueue.shift() || ''; - } else { - input = this.reminder + this.taskQueue.shift(); - } - - if (input) { - try { - const result = this.isDownloading - ? this.processInput(input) - : this.processInputByLine(input); - if (!this.isDownloading) { - this.reminder = result.remainder; - } - - if (this.chunked) { - self.postMessage({ result: result.data, lines: result.lines }); - } else if (!this.isComplete) { - self.postMessage({ - result: '', - percent: this.percent, - isComplete: false - }); - } - } catch (error) { - console.error('Error processing input:', error); - } - } - - await new Promise((resolve) => { - setTimeout(resolve, 0); - }); - } - - this.isProcessing = false; - if (this.taskQueue.length > 0) { - this.processQueue(); - } else if (this.isComplete && !this.chunked) { - self.postMessage({ - result: this.getAllLines(), - percent: this.percent, - isComplete: true - }); - this.reset(); - } - } - - public enqueueData(input: string): void { - this.taskQueue.push(input); - if (!this.isProcessing) { - this.processQueue(); - } - } -} -const parser = new AnsiParser(); - -self.onmessage = function (event: MessageEvent) { - const { - inputStr, - reset, - page, - isComplete = false, - chunked = true, - percent = 0, - isDownloading = false - } = event.data; - - parser.setIsDownloading(isDownloading); - parser.setPage(page); - parser.setIsCompelete(isComplete); - parser.setChunked(chunked); - parser.setPercent(percent); - - if (reset) { - parser.reset(); - } - parser.enqueueData(inputStr); -}; - -self.onerror = function (event) { - console.error('parse logs error===', event); -}; diff --git a/src/components/logs-viewer/styles/index.less b/src/components/logs-viewer/styles/index.less deleted file mode 100644 index e5d33bcc..00000000 --- a/src/components/logs-viewer/styles/index.less +++ /dev/null @@ -1,126 +0,0 @@ -.logs-viewer-wrap-w2 { - position: relative; - - .pg { - position: absolute; - top: 16px; - right: 20px; - width: 40px; - height: 130px; - - .pg-inner { - position: relative; - - &::before { - content: ''; - position: absolute; - top: 0; - bottom: 0; - right: -18px; - // width: 80px; - height: 185px; - - &:hover { - .pagination { - display: flex; - } - } - } - - .pagination { - display: none; - } - - &:hover { - .pagination { - display: flex; - } - } - - &.at-top { - .pagination { - display: flex; - } - } - } - } - - .loading { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 100; - padding-top: 100px; - display: flex; - justify-content: center; - background-color: var(--color-fill-spin-bg); - } - - .copy { - position: absolute; - top: 10px; - right: 10px; - z-index: 100; - - button { - color: rgba(255, 255, 255, 70%); - background-color: rgba(71, 71, 71, 100%); - - &:hover { - color: rgba(255, 255, 255, 90%) !important; - background-color: rgba(71, 71, 71, 100%) !important; - } - } - } - - .wrap { - padding: 5px 0 2px 10px; - background-color: var(--color-logs-bg); - border-radius: var(--border-radius-mini); - font-family: monospace, Menlo, Courier, 'Courier New', Consolas, Monaco, - 'Liberation Mono' !important; - - .content { - word-wrap: break-word; - height: 100%; - padding-right: 2px; - - &.line-break { - word-wrap: break-word; - } - - .text { - min-height: 22px; - } - - color: var(--color-logs-text); - font-size: var(--font-size-small); - line-height: 22px; - white-space: pre-wrap; - background-color: var(--color-logs-bg); - } - } - - .xterm { - .xterm-viewport { - overflow-y: auto !important; - - &::-webkit-scrollbar { - width: var(--scrollbar-size); - height: var(--scrollbar-size); - } - - &::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar-thumb); - border-radius: 4px; - } - - &::-webkit-scrollbar-track { - background-color: var(--color-scrollbar-track); - border-radius: 4px; - } - } - } -} diff --git a/src/components/logs-viewer/styles/logs-list.less b/src/components/logs-viewer/styles/logs-list.less deleted file mode 100644 index 2c4eb669..00000000 --- a/src/components/logs-viewer/styles/logs-list.less +++ /dev/null @@ -1,44 +0,0 @@ -.logs-wrap { - background-color: var(--color-logs-bg); - border-radius: var(--border-radius-mini); - font-family: - monospace, Menlo, Courier, 'Courier New', Consolas, Monaco, - 'Liberation Mono' !important; - - .content { - word-wrap: break-word; - height: 100%; - padding-right: 2px; - - &.line-break { - word-wrap: break-word; - } - - .text { - min-height: 22px; - padding-inline-end: 80px; - - &.numable { - position: relative; - padding-left: 45px; - - .line-num { - display: flex; - align-items: center; - width: 40px; - justify-content: center; - position: absolute; - left: 0; - top: 0; - background-color: rgba(71, 71, 71, 50%); - } - } - } - - color: var(--color-logs-text); - font-size: 12px; - line-height: 22px; - white-space: pre-wrap; - background-color: var(--color-logs-bg); - } -} diff --git a/src/components/logs-viewer/styles/pagination.less b/src/components/logs-viewer/styles/pagination.less deleted file mode 100644 index db723688..00000000 --- a/src/components/logs-viewer/styles/pagination.less +++ /dev/null @@ -1,25 +0,0 @@ -.pagination { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - color: rgba(255, 255, 255, 100%); - gap: 5px; - - .ant-btn:hover { - color: rgba(255, 255, 255, 90%) !important; - background-color: rgba(71, 71, 71, 100%) !important; - } - - .ant-btn { - background-color: rgba(71, 71, 71, 70%) !important; - } - - .pages { - display: flex; - justify-content: center; - align-items: center; - height: 38px; - width: 38px; - } -} diff --git a/src/components/logs-viewer/styles/xterm-viewer.less b/src/components/logs-viewer/styles/xterm-viewer.less deleted file mode 100644 index 058e2b9d..00000000 --- a/src/components/logs-viewer/styles/xterm-viewer.less +++ /dev/null @@ -1,50 +0,0 @@ -.logs-viewer-wrap-w2 { - .wrap { - padding: 5px 0 5px 10px; - background-color: var(--color-logs-bg); - border-radius: var(--border-radius-mini); - overflow: hidden; - - .content { - word-wrap: break-word; - height: 100%; - - &.line-break { - word-wrap: break-word; - } - - .text { - height: 100%; - } - - color: var(--color-logs-text); - font-size: var(--font-size-small); - line-height: 22px; - white-space: pre-wrap; - background-color: var(--color-logs-bg); - } - } - - .xterm { - // height: 100% !important; - - .xterm-viewport { - overflow-y: auto !important; - - &::-webkit-scrollbar { - width: var(--scrollbar-size); - height: var(--scrollbar-size); - } - - &::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar-thumb); - border-radius: 4px; - } - - &::-webkit-scrollbar-track { - background-color: var(--color-scrollbar-track); - border-radius: 4px; - } - } - } -} diff --git a/src/components/logs-viewer/use-logs-pagination.ts b/src/components/logs-viewer/use-logs-pagination.ts deleted file mode 100644 index 06bf7be0..00000000 --- a/src/components/logs-viewer/use-logs-pagination.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useState } from 'react'; -import { PageSize } from './config'; - -const useLogsPagination = () => { - const [pageSize, setPageSize] = useState(PageSize); - const [page, setPage] = useState(1); - const [total, setTotal] = useState(1); - - const nextPage = () => { - setPage(page + 1); - }; - - const prePage = () => { - let newPage = page - 1; - if (newPage < 1) { - newPage = 1; - } - setPage(newPage); - }; - - const resetPage = () => { - setPage(1); - }; - - const setTotalPage = (total: number) => { - setTotal(total); - }; - - return { - nextPage, - resetPage, - prePage, - setPage, - pageSize, - setTotalPage, - page, - totalPage: total - }; -}; - -export default useLogsPagination; diff --git a/src/components/logs-viewer/use-size.ts b/src/components/logs-viewer/use-size.ts deleted file mode 100644 index f1fac2c0..00000000 --- a/src/components/logs-viewer/use-size.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect, useState } from 'react'; - -export const useResizeObserver = (ref: React.RefObject) => { - const [size, setSize] = useState({ width: 0, height: 0 }); - - useEffect(() => { - const element = ref.current; - if (!element) return; - - const updateSize = () => { - const rect = element.getBoundingClientRect(); - setSize((prev) => { - if (prev.width === rect.width && prev.height === rect.height) - return prev; - return { width: rect.width, height: rect.height }; - }); - }; - - const observer = new ResizeObserver(() => { - updateSize(); - }); - - observer.observe(element); - updateSize(); - - return () => { - observer.disconnect(); - }; - }, [ref.current]); - - return size; -}; - -export default useResizeObserver; diff --git a/src/components/logs-viewer/virtual-log-list.tsx b/src/components/logs-viewer/virtual-log-list.tsx deleted file mode 100644 index 9c65371a..00000000 --- a/src/components/logs-viewer/virtual-log-list.tsx +++ /dev/null @@ -1,345 +0,0 @@ -import useSetChunkFetch from '@/hooks/use-chunk-fetch'; -import { useMemoizedFn } from 'ahooks'; -import { Spin } from 'antd'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState -} from 'react'; -import LogsList from './logs-list'; -import LogsPagination from './logs-pagination'; -import './styles/index.less'; -import useLogsPagination from './use-logs-pagination'; - -interface LogsViewerProps { - height?: number; - content?: string; - url: string; - params?: object; - ref?: any; - tail?: number; - enableScorllLoad?: boolean; - diffHeight?: number; - isDownloading?: boolean; -} - -const LogsViewer: React.FC = forwardRef((props, ref) => { - const { - diffHeight, - url, - tail: defaultTail, - enableScorllLoad = true, - isDownloading - } = props; - const { pageSize, page, setPage, setTotalPage, totalPage } = - useLogsPagination(); - const { setChunkFetch } = useSetChunkFetch(); - const chunkRequedtRef = useRef(null); - const [logs, setLogs] = useState([]); - const logParseWorker = useRef(null); - const tail = useRef(defaultTail); - const [loading, setLoading] = useState(false); - const [isAtTop, setIsAtTop] = useState(false); - const [scrollPos, setScrollPos] = useState([]); - const logListRef = useRef(null); - const loadMoreDone = useRef(false); - const pageRef = useRef(page); - const totalPageRef = useRef(totalPage); - const isLoadingMoreRef = useRef(false); - const [currentData, setCurrentPageData] = useState([]); - const scrollPosRef = useRef({ - pos: 'bottom', - page: 1 - }); - const lineCountRef = useRef(0); - const clearScreen = useRef(false); - - useImperativeHandle(ref, () => ({ - abort() { - chunkRequedtRef.current?.current?.abort?.(); - logParseWorker.current?.terminate?.(); - } - })); - - const removeBracketsFromLine = (row: string) => { - return row.startsWith('(…)') ? row.slice(3) : row; - }; - - const setCurrentData = (lines: string[]) => { - const dataList = lines.map((line, index) => { - return { - content: line, - uid: `${pageRef.current}-${index}` - }; - }); - - setCurrentPageData(dataList); - }; - - const debounceLoading = _.debounce(() => { - setLoading(false); - isLoadingMoreRef.current = false; - if (logListRef.current?.scroller) { - logListRef.current.scroller.style['pointer-events'] = 'auto'; - } - }, 1000); - - const getCurrent = useCallback(() => { - if (pageRef.current < 1) { - pageRef.current = 1; - } - const start = (pageRef.current - 1) * pageSize; - const end = pageRef.current * pageSize; - const currentLogs = logs.slice(start, end); - setPage(pageRef.current); - setCurrentData(currentLogs); - }, [logs, pageSize]); - - const getPrePage = useCallback(() => { - pageRef.current = pageRef.current - 1; - - getCurrent(); - - setScrollPos(['bottom', pageRef.current]); - scrollPosRef.current = { - pos: 'bottom', - page: pageRef.current - }; - }, [getCurrent]); - - const getNextPage = useCallback(() => { - pageRef.current = pageRef.current + 1; - - getCurrent(); - - setScrollPos(['top', pageRef.current]); - scrollPosRef.current = { - pos: 'top', - page: pageRef.current - }; - }, [getCurrent]); - - const handleonBackend = useCallback(() => { - pageRef.current = totalPageRef.current; - getCurrent(); - - console.log('pageRef.current', pageRef.current); - setScrollPos(['bottom', pageRef.current]); - scrollPosRef.current = { - pos: 'bottom', - page: pageRef.current - }; - }, [getCurrent]); - - const handleonToFirst = useCallback(() => { - pageRef.current = 1; - getCurrent(); - setScrollPos(['top', pageRef.current]); - scrollPosRef.current = { - pos: 'top', - page: pageRef.current - }; - }, [getCurrent]); - - const updateContent = (data: string) => { - if (isLoadingMoreRef.current) { - setLoading(true); - if (logListRef.current?.scroller) { - logListRef.current.scroller.style['pointer-events'] = 'none'; - } - } - logParseWorker.current.postMessage({ - inputStr: data, - page: pageRef.current, - reset: clearScreen.current, - isDownloading: isDownloading - }); - clearScreen.current = false; - }; - - const createChunkConnection = async () => { - chunkRequedtRef.current?.current?.abort?.(); - chunkRequedtRef.current = setChunkFetch({ - url, - params: { - ...props.params, - tail: tail.current, - watch: true - }, - contentType: 'text', - handler: updateContent - }); - }; - - const handleOnScroll = useMemoizedFn( - async (data: { isTop: boolean; isBottom: boolean }) => { - const { isTop, isBottom } = data; - setIsAtTop(isTop); - if (isBottom) { - scrollPosRef.current = { - pos: 'bottom', - page: page - }; - } else if (isTop) { - scrollPosRef.current = { - pos: 'top', - page: page - }; - } else { - scrollPosRef.current = { - pos: 'middle', - page: page - }; - } - if ( - loading || - (logs.length > 0 && - lineCountRef.current < pageSize - 1 && - !loadMoreDone.current) || - !enableScorllLoad - ) { - return; - } - - if (isTop && !loadMoreDone.current) { - tail.current = undefined; - createChunkConnection(); - loadMoreDone.current = true; - isLoadingMoreRef.current = true; - clearScreen.current = true; - } else if (isTop && page <= totalPage && page > 1) { - // getPrePage(); - } else if (isBottom && page < totalPage) { - // getNextPage(); - } - } - ); - - const debouncedScroll = useCallback( - _.throttle(() => { - if (scrollPos[0] === 'top' && scrollPosRef.current.pos === 'top') { - logListRef.current?.scrollToTop(); - } - if (scrollPosRef.current.pos === 'bottom') { - logListRef.current?.scrollToBottom(); - } - }, 150), - [scrollPos] - ); - - useEffect(() => { - createChunkConnection(); - return () => { - chunkRequedtRef.current?.current?.abort?.(); - }; - }, [url, isDownloading]); - - useEffect(() => { - debouncedScroll(); - }, [scrollPos]); - - useEffect(() => { - logParseWorker.current?.terminate?.(); - - logParseWorker.current = new Worker( - // @ts-ignore - new URL('./parse-worker.ts', import.meta.url), - { - type: 'module' - } - ); - - logParseWorker.current.onmessage = (event: any) => { - const { result, lines } = event.data; - lineCountRef.current = lines; - - if (pageRef.current < 1) { - pageRef.current = 1; - } - - const oldTotalPage = totalPageRef.current; - - totalPageRef.current = Math.ceil(result.length / pageSize); - - if (isLoadingMoreRef.current) { - pageRef.current = totalPageRef.current; - } else if ( - pageRef.current === oldTotalPage && - scrollPosRef.current.pos === 'bottom' - ) { - scrollPosRef.current = { - pos: 'bottom', - page: pageRef.current - }; - pageRef.current = totalPageRef.current; - setScrollPos(['bottom', pageRef.current]); - } - - const start = (pageRef.current - 1) * pageSize; - const end = pageRef.current * pageSize; - const currentLogs = result.slice(start, end); - - setLogs(result); - setTotalPage(totalPageRef.current); - setPage(pageRef.current); - setCurrentData(currentLogs); - debounceLoading(); - }; - - return () => { - if (logParseWorker.current) { - logParseWorker.current.terminate(); - } - }; - }, []); - - return ( -
-
-
- -
- {loading && ( - - )} - {totalPage > 1 && ( -
-
- -
-
- )} -
-
- ); -}); - -export default LogsViewer; diff --git a/src/components/logs-viewer/xterm-viewer.tsx b/src/components/logs-viewer/xterm-viewer.tsx deleted file mode 100644 index cea7e1c4..00000000 --- a/src/components/logs-viewer/xterm-viewer.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import useSetChunkRequest from '@/hooks/use-chunk-request'; -import { FitAddon } from '@xterm/addon-fit'; -import { Terminal } from '@xterm/xterm'; -import '@xterm/xterm/css/xterm.css'; -import classNames from 'classnames'; -import _ from 'lodash'; -import { - forwardRef, - useEffect, - useImperativeHandle, - useRef, - useState -} from 'react'; -import { replaceLineRegex } from './config'; -import './styles/xterm-viewer.less'; -import useSize from './use-size'; - -interface LogsViewerProps { - height: number; - content?: string; - url: string; - ref?: any; - params?: object; -} -const LogsViewer: React.FC = forwardRef((props, ref) => { - const { height, content, url } = props; - const { setChunkRequest } = useSetChunkRequest(); - const chunkRequedtRef = useRef(null); - const scroller = useRef({}); - const termRef = useRef({}); - const termwrapRef = useRef({}); - const fitAddonRef = useRef({}); - const cacheDataRef = useRef(null); - const [logs, setLogs] = useState(''); - const [loading, setLoading] = useState(false); - const size = useSize(scroller); - const logParseWorker = useRef(null); - const lineCountRef = useRef(0); - - useImperativeHandle(ref, () => ({ - abort() { - chunkRequedtRef.current?.current?.abort?.(); - logParseWorker.current?.terminate?.(); - } - })); - - useEffect(() => { - logParseWorker.current?.terminate?.(); - - logParseWorker.current = new Worker( - // @ts-ignore - new URL('./parse-worker.ts', import.meta.url), - { - type: 'module' - } - ); - - logParseWorker.current.onmessage = (event: any) => { - const { result, lines } = event.data; - lineCountRef.current = lines; - // setLogs(result.join('\n')); - console.log('res+++++++++++', result); - const data = result.map((item: any) => item.content); - termRef.current?.write?.(data.join('\n')); - }; - - return () => { - if (logParseWorker.current) { - logParseWorker.current.terminate(); - } - }; - }, []); - - const throttleScroll = _.throttle(() => { - termRef.current?.scrollToBottom?.(); - }, 100); - - const debounceLoading = _.debounce(() => { - setLoading(false); - }, 200); - - const updateContent = (inputStr: string) => { - const data = inputStr.replace(replaceLineRegex, '\n'); - cacheDataRef.current = data; - setLoading(true); - logParseWorker.current.postMessage({ - inputStr: data - }); - debounceLoading(); - }; - - const fitTerm = () => { - fitAddonRef.current?.fit?.(); - }; - - const createChunkConnection = async () => { - chunkRequedtRef.current?.current?.cancel?.(); - chunkRequedtRef.current = setChunkRequest({ - url, - params: { - ...props.params, - watch: true - }, - contentType: 'text', - handler: updateContent - }); - }; - - const initTerm = () => { - termRef.current?.dispose?.(); - termRef.current = new Terminal({ - lineHeight: 1.2, - fontSize: 13, - fontFamily: - "monospace,Menlo,Courier,'Courier New',Consolas,Monaco, 'Liberation Mono'", - disableStdin: true, - convertEol: true, - theme: { - background: '#1e1e1e', - foreground: 'rgba(255,255,255,0.8)' - }, - cursorInactiveStyle: 'none', - smoothScrollDuration: 0 - }); - fitAddonRef.current = new FitAddon(); - termRef.current.loadAddon(fitAddonRef.current); - termRef.current.open(termwrapRef.current); - - // add event - // termRef.current.onLineFeed((e: any) => { - // if (cacheDataRef.current) { - // throttleScroll(); - // } - // }); - }; - - const handleResize = _.throttle(() => { - fitTerm(); - }, 100); - - useEffect(() => { - createChunkConnection(); - return () => { - chunkRequedtRef.current?.current?.cancel?.(); - }; - }, [url, props.params]); - - useEffect(() => { - if (termwrapRef.current) { - initTerm(); - } - return () => { - termRef.current?.dispose?.(); - }; - }, [termwrapRef.current]); - - useEffect(() => { - if (size) { - handleResize(); - } - }, [size]); - - useEffect(() => { - // throttleScroll(); - }, [logs]); - - return ( -
-
-
-
-
-
-
- ); -}); - -export default LogsViewer; diff --git a/src/components/markdown-viewer/code-viewer.tsx b/src/components/markdown-viewer/code-viewer.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/markdown-viewer/full-markdown.tsx b/src/components/markdown-viewer/full-markdown.tsx deleted file mode 100644 index 02cbe753..00000000 --- a/src/components/markdown-viewer/full-markdown.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { sanitizeUrl } from '@braintree/sanitize-url'; -import 'katex/dist/katex.min.css'; -import React, { useMemo } from 'react'; -import ReactMarkdown from 'react-markdown'; -import rehypeKatex from 'rehype-katex'; -import remarkBreaks from 'remark-breaks'; -import remarkGfm from 'remark-gfm'; -import remarkMath from 'remark-math'; -import styled from 'styled-components'; -import HighlightCode from '../highlight-code'; -import './index.less'; -import { escapeBrackets, escapeDollarNumber, escapeMhchem } from './utils'; - -interface FullMarkdownProps { - content: string; - theme?: 'light' | 'dark'; -} - -const Wrapper = styled.div.attrs(() => ({ - className: 'markdown-viewer' -}))``; - -const CodeViewer = (props: any) => { - const { children, className, node, theme, ...rest } = props; - const match = /language-(\w+)/.exec(className || ''); - return match ? ( - - ) : ( - - {children} - - ); -}; - -const FullMarkdown: React.FC = ({ content, theme }) => { - const escapedContent = useMemo(() => { - return escapeMhchem(escapeBrackets(escapeDollarNumber(content))); - }, [content]); - - return ( - - ; - }, - a: ({ href, children, ...props }) => ( - - {children} - - ) - }} - > - {escapedContent} - - - ); -}; - -export default FullMarkdown; diff --git a/src/components/markdown-viewer/index.less b/src/components/markdown-viewer/index.less deleted file mode 100644 index c87fbe9a..00000000 --- a/src/components/markdown-viewer/index.less +++ /dev/null @@ -1,136 +0,0 @@ -.markdown-viewer { - word-break: break-word; - line-height: 2; - font-size: var(--font-size-base); - - .hr { - border: none; - height: 1px; - background-color: var(--ant-color-split); - border-color: var(--ant-color-split); - } - - hr { - border: none; - height: 1px; - background-color: var(--ant-color-split); - border-color: var(--ant-color-split); - } - - p { - margin-bottom: 0; - } - - b { - font-weight: var(--font-weight-bold); - } - - pre { - white-space: pre-wrap; - } - - h1, - h2, - h3, - h4 { - margin-top: 1em; - } - - h1 { - font-size: 20px; - } - - h2 { - font-size: 18px; - } - - h3 { - font-size: 16px; - } - - h4, - h5, - h6 { - font-size: 14px; - } - - .hj-wrapper { - margin-top: 16px; - } - - ul { - margin-bottom: 0; - padding-left: 20px; - } - - ol { - margin-bottom: 0; - padding-left: 20px; - } - - a * { - color: inherit; - } - - table { - width: 100%; - margin-bottom: 1.2em; - font-size: var(--font-size-small); - - th { - font-weight: var(--font-weight-bold); - line-height: 1.5; - padding-inline: 6px; - border: 1px solid var(--ant-color-split); - word-break: break-word; - text-align: left !important; - } - - td { - line-height: 1.5; - padding-inline: 6px; - border: 1px solid var(--ant-color-split); - word-break: break-word; - text-align: left !important; - } - - div { - display: inline; - } - } - - a { - div { - display: inline; - } - } - - img { - max-width: 100%; - height: auto; - } - img[src^="https://img.shields.io/"], img[src*="badge.svg"] - { - margin-bottom: 6px; - width: unset; - max-width: 100%; - } - - video { - max-width: 100%; - height: auto; - border-radius: var(--border-radius-base); - } - - audio { - max-width: 100%; - height: auto; - border-radius: var(--border-radius-base); - } - - .item-token { - color: inherit; - font-size: inherit; - font-weight: inherit; - } -} diff --git a/src/components/markdown-viewer/index.tsx b/src/components/markdown-viewer/index.tsx deleted file mode 100644 index fc815d65..00000000 --- a/src/components/markdown-viewer/index.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { EyeOutlined } from '@ant-design/icons'; -import { sanitizeUrl } from '@braintree/sanitize-url'; -import { Checkbox, Image, Typography } from 'antd'; -import DOMPurify from 'dompurify'; -import { unescape } from 'lodash'; -import { TokensList, marked } from 'marked'; -import React, { Fragment, useCallback, useEffect } from 'react'; -import HighlightCode from '../highlight-code'; -import './index.less'; - -const { Text, Link, Paragraph } = Typography; - -interface MarkdownViewerProps { - content: string; - height?: string; - theme?: 'light' | 'dark'; - generateImgLink?: (src: string) => string; -} - -const dompurifyOptions = { - FORBID_TAGS: ['meta', 'style', 'script', 'iframe'], - FORBID_ATTR: ['onerror', 'onclick', 'onload', 'style', 'autoplay'] -}; - -const cleanHtml = (html: string): string => { - return DOMPurify?.sanitize?.(html, dompurifyOptions); -}; - -const MarkdownViewer: React.FC = ({ - content, - generateImgLink, - height = 'auto', - theme = 'light' -}) => { - const renderer = new marked.Renderer(); - const tokens = marked.lexer(content); - const reDefineTypes = [ - 'code', - 'link', - 'hr', - 'heading', - 'paragraph', - 'codespan', - 'strong', - 'text', - 'image', - 'em', - 'list', - 'list_item', - 'br', - 'html', - 'escape', - 'del', - 'blockquote', - 'checkbox' - // 'bibtex' - ]; - - const isValidURL = useCallback((url: string) => { - const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/; - - return pattern.test(url); - }, []); - - const generateImgSrc = useCallback( - (url: string | null) => { - const src = sanitizeUrl(url || ''); - if (!src) { - return ''; - } - if (generateImgLink) { - return isValidURL(src) ? src : generateImgLink(src); - } - return src; - }, - [generateImgLink] - ); - - const renderItem = useCallback( - (token: any, render: any) => { - if (token.type === 'script' || token.type === 'style') { - return null; - } - - if (!reDefineTypes.includes(token.type)) { - return ( - - ); - } - let htmlstr: any = null; - let child: any = null; - if (token.tokens?.length) { - child = render?.(token.tokens as TokensList, render); - } - const text = child ? child : unescape(token.text); - - if (token.type === 'escape') { - htmlstr = text; - } - - if (token.type === 'html') { - htmlstr = ( -
- ); - } - if (token.type === 'list') { - htmlstr = token.order ? ( -
    {render?.(token.items, render)}
- ) : ( -
    {render?.(token.items, render)}
- ); - } - - if (token.type === 'list_item') { - htmlstr =
  • {text}
  • ; - } - - if (token.type === 'del') { - htmlstr = {text}; - } - - if (token.type === 'blockquote') { - htmlstr =
    {text}
    ; - } - - if (token.type === 'checkbox') { - htmlstr = ; - } - - if (token.type === 'br') { - htmlstr =
    ; - } - - if (token.type === 'em') { - htmlstr = {text}; - } - - if (token.type === 'image') { - let href = generateImgSrc(token.href); - htmlstr = ( - {token.text} - }} - /> - ); - } - if (token.type === 'text') { - htmlstr = text; - } - if (token.type === 'codespan') { - htmlstr = {text}; - } - if (token.type === 'strong') { - htmlstr = {text}; - } - if (token.type === 'heading') { - htmlstr = {text}; - } - if (token.type === 'paragraph') { - htmlstr = {text}; - } - - if (token.type === 'code') { - htmlstr = ; - } - - if (token.type === 'link') { - htmlstr = ( - - {text} - - ); - } - if (token.type === 'hr') { - htmlstr =
    ; - } - - return htmlstr; - }, - [generateImgSrc] - ); - const renderTokens = (tokens: TokensList): any => { - return tokens?.map((token: any, index: number) => { - return {renderItem(token, renderTokens)}; - }); - }; - - useEffect(() => { - if (!content) { - return; - } - const imgs = document.querySelectorAll('.markdown-viewer img'); - const links = document.querySelectorAll('.markdown-viewer a'); - links.forEach((link) => { - // set target blank for all links - link.setAttribute('target', '_blank'); - }); - imgs.forEach((img) => { - const src = img.getAttribute('src'); - img.setAttribute('src', generateImgSrc(src)); - }); - }, [content, generateImgSrc]); - - return ( - <> -
    - {renderTokens(tokens)} -
    - - ); -}; - -export default React.memo(MarkdownViewer); diff --git a/src/components/markdown-viewer/utils.ts b/src/components/markdown-viewer/utils.ts deleted file mode 100644 index a9b367a6..00000000 --- a/src/components/markdown-viewer/utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -const htmlUnescapes: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'" -}; - -const reEscapedHtml = /&(?:amp|lt|gt|quot|#(?:0+)?39);/g; -const reHasEscapedHtml = RegExp(reEscapedHtml.source); - -export const unescape = (str = '') => { - return reHasEscapedHtml.test(str) - ? str.replace(reEscapedHtml, (entity) => htmlUnescapes[entity] || "'") - : str; -}; - -export function escapeDollarNumber(text: string) { - let escapedText = ''; - - for (let i = 0; i < text.length; i += 1) { - let char = text[i]; - const nextChar = text[i + 1] || ' '; - - if (char === '$' && nextChar >= '0' && nextChar <= '9') { - char = '\\$'; - } - - escapedText += char; - } - - return escapedText; -} - -export function escapeBrackets(text: string) { - const pattern = - /(```[\S\s]*?```|`.*?`)|\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g; - return text.replaceAll( - pattern, - (match, codeBlock, squareBracket, roundBracket) => { - if (codeBlock) { - return codeBlock; - } else if (squareBracket) { - return `$$${squareBracket}$$`; - } else if (roundBracket) { - return `$${roundBracket}$`; - } - return match; - } - ); -} - -export function escapeMhchem(text: string) { - return text.replaceAll('$\\ce{', '$\\\\ce{').replaceAll('$\\pu{', '$\\\\pu{'); -} diff --git a/src/components/metadata-list/index.tsx b/src/components/metadata-list/index.tsx deleted file mode 100644 index 3a9045b5..00000000 --- a/src/components/metadata-list/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { MinusOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import Wrapper from '../label-selector/wrapper'; - -const ItemContainer = styled.div` - display: flex; - margin-bottom: 12px; - align-items: center; - justify-content: flex-start; - - .seprator { - display: flex; - flex: none; - width: 12px; - align-items: center; - justify-content: center; - color: var(--ant-color-text-tertiary); - } - - .btn { - width: 24px; - margin-left: 10px; - flex: none; - } -`; - -interface MetadataListProps { - dataList: any[]; - label: React.ReactNode; - description?: React.ReactNode; - disabled?: boolean; - btnText?: string; - styles?: { - wrapper?: React.CSSProperties; - }; - onAdd?: () => void; - onDelete?: (index: number, item: any) => void; - children?: (item: any, index: number) => React.ReactNode; -} - -const MetadataList: React.FC = ({ - dataList, - label, - description, - disabled, - btnText, - children, - onDelete, - onAdd, - styles -}) => { - return ( - - {dataList.map((item, index) => ( - - {children?.(item, index)} - {!disabled && ( - - )} - - ))} - - ); -}; - -export default MetadataList; diff --git a/src/components/modal-footer/index.tsx b/src/components/modal-footer/index.tsx deleted file mode 100644 index 5250590d..00000000 --- a/src/components/modal-footer/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useIntl } from '@umijs/max'; -import { Button, Space } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -type ModalFooterProps = { - onOk?: () => void; - onCancel?: () => void; - cancelText?: string; - okText?: string; - htmlType?: 'button' | 'submit'; - okBtnProps?: any; - cancelBtnProps?: any; - loading?: boolean; - style?: React.CSSProperties; - showOkBtn?: boolean; - showCancelBtn?: boolean; - extra?: React.ReactNode; - form?: any; - description?: React.ReactNode; - styles?: { - wrapper?: React.CSSProperties; - }; -}; - -const Wrapper = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - gap: 20px; -`; - -const ModalFooter: React.FC = ({ - onOk, - onCancel, - cancelText, - okText, - okBtnProps, - cancelBtnProps, - loading, - htmlType = 'button', - style, - showOkBtn = true, - styles, - description, - extra, - showCancelBtn = true, - form -}) => { - const intl = useIntl(); - return ( - -
    {description}
    - - {showCancelBtn && ( - - )} - {extra} - {showOkBtn && ( - - )} - -
    - ); -}; - -export default ModalFooter; diff --git a/src/components/overlay-scroller/index.module.less b/src/components/overlay-scroller/index.module.less deleted file mode 100644 index 3eaa9fc0..00000000 --- a/src/components/overlay-scroller/index.module.less +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - overflow-y: auto; - width: 100%; - padding-inline-start: 8px; - padding-inline-end: 8px; -} diff --git a/src/components/overlay-scroller/index.tsx b/src/components/overlay-scroller/index.tsx deleted file mode 100644 index e4fbcfc1..00000000 --- a/src/components/overlay-scroller/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import useOverlayScroller, { - OverlayScrollerOptions -} from '@/hooks/use-overlay-scroller'; -import { Tooltip, TooltipProps } from 'antd'; -import classNames from 'classnames'; -import React from 'react'; -import s from './index.module.less'; - -// export OverlayScrollerOptions -export type { OverlayScrollerOptions }; - -export const OverlayScroller: React.FC< - OverlayScrollerOptions & { - maxHeight?: number | string; - style?: React.CSSProperties; - styles?: { - wrapper?: React.CSSProperties; - }; - children: React.ReactNode; - onScroll?: (e: React.UIEvent) => void; - } -> = ({ - children, - maxHeight, - scrollbars, - oppositeTheme, - style, - styles, - onScroll -}) => { - const scroller = React.useRef(null); - const { initialize } = useOverlayScroller({ - options: { - scrollbars, - oppositeTheme - } - }); - - React.useEffect(() => { - if (scroller.current) { - initialize(scroller.current); - } - }, []); - - return ( - - ); -}; - -/** - * - * @param maxHeight: use for scrollbars - * @param theme: because this component is used for tooltip, so the default theme always is light - * @returns - */ -export const TooltipOverlayScroller: React.FC< - OverlayScrollerOptions & { - maxHeight?: number; - title?: React.ReactNode; - children: React.ReactNode; - toolTipProps?: TooltipProps; - } -> = ({ - children, - maxHeight, - title, - toolTipProps, - scrollbars, - oppositeTheme -}) => { - const { styles, ...rest } = toolTipProps || {}; - return ( - - {title} - - ) - } - {...rest} - > - {children} - - ); -}; - -export default OverlayScroller; diff --git a/src/components/page-tools/filters-button.less b/src/components/page-tools/filters-button.less deleted file mode 100644 index 5dcb0fd2..00000000 --- a/src/components/page-tools/filters-button.less +++ /dev/null @@ -1,47 +0,0 @@ -.wrapper { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - - .count { - display: flex; - width: 16px; - height: 16px; - justify-content: center; - align-items: center; - border-radius: 50%; - font-size: 12px; - font-weight: 500; - background-color: var(--ant-color-bg-text-active); - color: var(--ant-color-text-secondary); - } -} - -.buttonWrapper { - display: flex; - align-items: center; - justify-content: center; - position: relative; - - .close-btn { - border-radius: 50%; - color: var(--ant-color-text-quaternary); - display: none; - font-size: 16px; - - &:hover { - color: var(--ant-color-text-tertiary); - } - } - - &:hover { - .count { - display: none; - } - - .close-btn { - display: block; - } - } -} diff --git a/src/components/page-tools/index.less b/src/components/page-tools/index.less deleted file mode 100644 index 6420c778..00000000 --- a/src/components/page-tools/index.less +++ /dev/null @@ -1,18 +0,0 @@ -.page-tools { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 70px; - - .left { - display: flex; - align-items: center; - font-size: var(--font-size-middle); - } - - .right { - display: flex; - align-items: center; - font-size: var(--font-size-middle); - } -} diff --git a/src/components/page-tools/index.tsx b/src/components/page-tools/index.tsx deleted file mode 100644 index d3cd338f..00000000 --- a/src/components/page-tools/index.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import DropDownActions from '@/components/drop-down-actions'; -import { - CloseCircleFilled, - DeleteOutlined, - DownOutlined, - PlusOutlined, - SearchOutlined, - SyncOutlined -} from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Input, Space } from 'antd'; -import React, { useMemo } from 'react'; -import IconFont from '../icon-font'; -import BaseSelect from '../seal-form/base/select'; -import filtersButtonCss from './filters-button.less'; -import './index.less'; - -type PageToolsProps = { - left?: React.ReactNode; - right?: React.ReactNode; - marginBottom?: number; - marginTop?: number; - style?: React.CSSProperties; -}; - -export const FiltersButton = ({ - onClick, - onClear, - count -}: { - onClick: () => void; - onClear: () => void; - count?: number; -}) => { - const handleClear = (e: React.MouseEvent) => { - e.stopPropagation(); - onClear(); - }; - const intl = useIntl(); - - return ( -
    - -
    - ); -}; - -const PageTools: React.FC = (props) => { - const { - left, - right, - marginBottom = 0, - marginTop = 30, - style: pageStyle - } = props; - - const newStyle: React.CSSProperties = useMemo(() => { - const style: React.CSSProperties = {}; - style.marginBottom = `${marginBottom}px`; - style.marginTop = `${marginTop}px`; - if (pageStyle) { - Object.assign(style, pageStyle); - } - return style; - }, [marginBottom, marginTop, pageStyle]); - - return ( -
    -
    {left}
    -
    {right}
    -
    - ); -}; - -interface ActionItem { - label: string; - locale: boolean; - value: string; - key: string; - icon: React.ReactNode; - [key: string]: any; -} - -interface FilterBarProps { - handleInputChange: (e: React.ChangeEvent) => void; - handleSelectChange?: (value: any) => void; - handleSearch: () => void; - handleDeleteByBatch?: () => void; - handleClickPrimary?: (item: any) => void; - rowSelection?: any; - actionItems?: ActionItem[]; - selectOptions?: Global.BaseOption[]; - showSelect?: boolean; - buttonText?: string; - buttonIcon?: React.ReactNode; - marginBottom?: number; - marginTop?: number; - inputHolder?: string; - selectHolder?: string; - actionType?: 'dropdown' | 'button'; - showPrimaryButton?: boolean; - showDeleteButton?: boolean; - right?: React.ReactNode; - left?: React.ReactNode; - filtersButtonProps?: { - show: boolean; - count: number; - onClick: () => void; - onClear: () => void; - }; - select?: { - showSearch?: boolean; - }; - widths?: { - input?: number; - select?: number; - }; -} - -export const FilterBar: React.FC = (props) => { - const { - handleInputChange, - handleSelectChange, - handleSearch, - handleDeleteByBatch = null, - handleClickPrimary = null, - rowSelection, - actionItems = [], - selectOptions, - showSelect, - buttonText, - buttonIcon, - select, - actionType = 'button', - marginBottom = 10, - marginTop = 10, - inputHolder, - selectHolder, - right, - left, - widths, - filtersButtonProps - } = props; - const intl = useIntl(); - - const renderLeft = () => { - return ( - - {filtersButtonProps?.show && ( - - )} - - } - placeholder={ - inputHolder || - intl.formatMessage({ - id: 'common.filter.name' - }) - } - style={{ width: widths?.input || 230 }} - allowClear - onChange={handleInputChange} - > - {showSelect && ( - - )} - - - ); - }; - - const renderRight = () => { - if (!handleClickPrimary && !handleDeleteByBatch) { - return null; - } - return ( - - {handleClickPrimary ? ( - actionType === 'dropdown' ? ( - - - - ) : ( - - ) - ) : null} - {handleDeleteByBatch && ( - - )} - - ); - }; - - return ( - - ); -}; - -export default PageTools; diff --git a/src/components/popover/index.less b/src/components/popover/index.less deleted file mode 100644 index a83ddd77..00000000 --- a/src/components/popover/index.less +++ /dev/null @@ -1,27 +0,0 @@ -:local(.seal_custom_popover) { - position: relative; - padding-top: var(--ant-popover-inner-padding); - border-radius: var(--ant-border-radius-lg); - background-color: var(--color-white-1); - box-shadow: var(--ant-box-shadow-secondary); - overflow: hidden; - padding-top: 80px; - :global(.ant-popover-inner) { - box-shadow: none; - padding-top: 0; - } - :global(.ant-popover-content) { - position: static; - } - :global(.ant-popover-title) { - position: absolute; - top: 0; - padding-block: var(--ant-popover-inner-padding) - var(--ant-popover-title-margin-bottom); - margin-bottom: 0; - background: #fff; - padding-left: var(--ant-popover-inner-padding); - left: 0; - width: 100%; - } -} diff --git a/src/components/popover/index.tsx b/src/components/popover/index.tsx deleted file mode 100644 index 99de6674..00000000 --- a/src/components/popover/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Popover } from 'antd'; -import type { PopoverProps } from 'antd/lib/popover'; -import classNames from 'classnames'; -import styles from './index.less'; - -const SealPopover: React.FC = (props) => { - const { className, children, style, ...restProps } = props; - - return ( -
    - - {children} - -
    - ); -}; - -export default SealPopover; diff --git a/src/components/progress-bar/index.tsx b/src/components/progress-bar/index.tsx deleted file mode 100644 index 46bba1d2..00000000 --- a/src/components/progress-bar/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Progress, Tooltip } from 'antd'; -import React, { memo, useEffect, useMemo } from 'react'; - -const RenderProgress = memo( - (props: { - defaultOpen?: boolean; - percent: number; - steps?: number; - download?: boolean; - label?: React.ReactNode; - successPercent?: number; - successColor?: string; - }) => { - const { defaultOpen, percent, download, label, successPercent } = props; - const [open, setOpen] = React.useState(false); - - const strokeColor = useMemo(() => { - if (download) { - return 'var(--ant-color-primary)'; - } - - if (percent <= 50) { - return 'var(--ant-color-success)'; - } - if (percent <= 80) { - return 'var(--ant-color-warning)'; - } - return 'var(--ant-color-error)'; - }, [percent]); - - useEffect(() => { - setOpen(defaultOpen || false); - }, [defaultOpen]); - - const renderProgress = useMemo(() => { - return ( - { - return ( - - {percent}% - - ); - }} - percent={percent} - success={{ - percent: successPercent, - strokeColor: 'var(--ant-geekblue-3)' - }} - strokeColor={strokeColor} - > - ); - }, [percent, successPercent, strokeColor]); - - return ( - <> - {label ? ( - - {renderProgress} - - ) : ( - renderProgress - )} - - ); - } -); - -export default RenderProgress; diff --git a/src/components/radio-buttons/index.less b/src/components/radio-buttons/index.less deleted file mode 100644 index ee4bed59..00000000 --- a/src/components/radio-buttons/index.less +++ /dev/null @@ -1,21 +0,0 @@ -.radio-button-wrap { - .item { - display: flex; - justify-content: center; - align-items: center; - font-size: var(--font-size-base); - border-radius: var(--border-radius-base); - height: 32px; - padding: 0 8px; - border: 1px solid var(--ant-color-border); - cursor: pointer; - - &.active { - background-color: var(--ant-color-fill-secondary); - } - - &:hover { - background-color: var(--ant-color-fill-secondary); - } - } -} diff --git a/src/components/radio-buttons/index.tsx b/src/components/radio-buttons/index.tsx deleted file mode 100644 index c228b9ee..00000000 --- a/src/components/radio-buttons/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Space } from 'antd'; -import classNames from 'classnames'; -import React from 'react'; -import './index.less'; - -interface RadioButtonsProps { - options: { value: any; label: React.ReactNode }[]; - value: string; - gap?: number; - onChange: (value: string) => void; -} -const RadioButtons: React.FC = (props) => { - const { options, value, onChange, gap = 12 } = props; - return ( - - {options.map((option) => ( - onChange({ target: { value: option.value } } as any)} - className={classNames('item', { active: value === option.value })} - > - {option.label} - - ))} - - ); -}; - -export default RadioButtons; diff --git a/src/components/resize-container/index.tsx b/src/components/resize-container/index.tsx deleted file mode 100644 index a45df56b..00000000 --- a/src/components/resize-container/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Col, Row } from 'antd'; -import ResizeObserver from 'rc-resize-observer'; -import useResponsive from './use-responsive'; - -interface ResizeObserverContainerProps { - dataList: any[]; - renderItem: (data: any) => React.ReactNode; - defaultSpan?: number; - resizable?: boolean; -} - -const ResizeContainer: React.FC = ({ - defaultSpan = 8, - dataList, - renderItem, - resizable = true -}) => { - const { span, handleResize } = useResponsive({ defaultSpan }); - - return ( - - - {dataList.map((item: any, index) => { - return ( - - {renderItem(item)} - - ); - })} - - - ); -}; - -export default ResizeContainer; diff --git a/src/components/resize-container/use-responsive.ts b/src/components/resize-container/use-responsive.ts deleted file mode 100644 index 81a0ef0b..00000000 --- a/src/components/resize-container/use-responsive.ts +++ /dev/null @@ -1,23 +0,0 @@ -import breakpoints from '@/config/breakpoints'; -import { useMemoizedFn } from 'ahooks'; -import _ from 'lodash'; -import { useState } from 'react'; - -const useResponsive = ({ defaultSpan = 8 }: { defaultSpan?: number }) => { - const [span, setSpan] = useState(defaultSpan); - - const getSpanByWidth = (width: number) => { - if (width < breakpoints.md) return 24; - if (width < breakpoints.lg) return 12; - return 8; - }; - - const handleResize = useMemoizedFn( - _.throttle((size: { width: number; height: number }) => { - setSpan(getSpanByWidth(size.width)); - }, 100) - ); - return { span, handleResize }; -}; - -export default useResponsive; diff --git a/src/components/scroller-modal/gs-drawer.tsx b/src/components/scroller-modal/gs-drawer.tsx deleted file mode 100644 index 1d7163a9..00000000 --- a/src/components/scroller-modal/gs-drawer.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useEscHint } from '@/hooks/use-esc-hint'; -import { CloseOutlined } from '@ant-design/icons'; -import { Button, Drawer, type DrawerProps } from 'antd'; -import React from 'react'; - -/** - * use ColumnWrapper to wrap content in Drawer with scroller - * 57px is the height of header - * @param props - * @returns - */ -const ScrollerModal = (props: DrawerProps) => { - const { title, closable = true, mask, styles, ...restProps } = props; - const { EscHint } = useEscHint({ - enabled: !props.keyboard && props.open - }); - const handleCancel = (e: React.MouseEvent) => { - props.onClose?.(e); - }; - - return ( - <> - - - {title} - - {closable && ( - - )} -
    - } - > - {restProps.children} - {EscHint} - - - ); -}; - -export default ScrollerModal; diff --git a/src/components/scroller-modal/index.tsx b/src/components/scroller-modal/index.tsx deleted file mode 100644 index ecee9e3e..00000000 --- a/src/components/scroller-modal/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import useBodyScroll from '@/hooks/use-body-scroll'; -import useOverlayScroller from '@/hooks/use-overlay-scroller'; -import { Modal, type ModalProps } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import { ScrollerContext } from './use-scroller-context'; - -const Wrapper = styled.div<{ $maxHeight?: number | string }>` - max-height: ${({ $maxHeight }) => - typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight}; - overflow-y: auto; - width: 100%; -`; - -const Title = styled.div` - display: flex; - align-items: center; - max-width: 360px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -`; - -const ScrollerModal = ( - props: ModalProps & { maxContentHeight?: number | string } -) => { - const scroller = React.useRef(null); - const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); - const { initialize, destroyInstance, scrollToBottom } = useOverlayScroller(); - - React.useEffect(() => { - if (props.open) { - saveScrollHeight(); - } else { - restoreScrollHeight(); - } - }, [props.open]); - - // init scroller, delay to ensure modal is fully open - React.useEffect(() => { - let timeout = null; - if (props.open) { - timeout = setTimeout(() => { - if (scroller.current) { - initialize(scroller.current); - } - }, 100); - } - return () => { - if (timeout) { - clearTimeout(timeout); - } - destroyInstance(); - }; - }, [props.open, initialize]); - - return ( - {props.title}} - destroyOnHidden={true} - styles={{ - container: { - padding: 0 - }, - header: { - padding: 'var(--ant-modal-content-padding)', - paddingBottom: '0' - }, - body: { - padding: '0', - paddingBlockEnd: props.footer ? '0' : '24px' - }, - footer: props.footer - ? { - padding: '12px 24px 24px', - margin: '0' - } - : {} - }} - > - - - - - ); -}; - -export default ScrollerModal; diff --git a/src/components/scroller-modal/use-scroller-context.ts b/src/components/scroller-modal/use-scroller-context.ts deleted file mode 100644 index 6e8b6dff..00000000 --- a/src/components/scroller-modal/use-scroller-context.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createContext, useContext } from 'react'; - -interface ScrollerContextProps { - scrollToBottom: () => void; -} - -export const ScrollerContext = createContext({ - scrollToBottom: () => {} -}); - -export const useScrollerContext = () => useContext(ScrollerContext); diff --git a/src/components/seal-form/auto-complete.tsx b/src/components/seal-form/auto-complete.tsx deleted file mode 100644 index 77c674f8..00000000 --- a/src/components/seal-form/auto-complete.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { LoadingOutlined } from '@ant-design/icons'; -import { AutoComplete, Form, Typography } from 'antd'; -import type { AutoCompleteProps } from 'antd/lib'; -import React, { useEffect, useRef, useState } from 'react'; -import { LoadingContent } from './components/not-found-content'; -import { SealFormItemProps } from './types'; -import Wrapper from './wrapper'; -import SelectWrapper from './wrapper/select'; - -const Link = Typography.Link; - -const SealAutoComplete: React.FC< - AutoCompleteProps & - SealFormItemProps & { - onPaste?: (e: any) => void; - onInput?: (e: Event) => void; - clearSpaceOnBlur?: boolean; - } -> = (props) => { - const { - label, - placeholder, - required, - description, - isInFormItems = true, - trim = true, - onSelect, - onBlur, - checkStatus, - extra, - style, - addAfter, - suffixIcon, - loading, - allowClear, - clearSpaceOnBlur, - showSearch, - ...rest - } = props; - const [isFocus, setIsFocus] = useState(false); - const inputRef = useRef(null); - let status = ''; - if (isInFormItems) { - const statusData = Form?.Item?.useStatus?.(); - status = statusData?.status || ''; - } - - useEffect(() => { - if (props.value) { - setIsFocus(true); - } - }, [props.value]); - - const handleClickWrapper = () => { - if (!props.disabled && !isFocus) { - inputRef.current?.focus?.(); - setIsFocus(true); - } - }; - - const handleChange = (val: string, option: any) => { - console.log('handleChange val:', val); - let value = val; - if (trim) { - value = value?.trim?.(); - } - props.onChange?.(value, option); - }; - - const handleOnFocus = (e: any) => { - setIsFocus(true); - props.onFocus?.(e); - }; - - const handleOnBlur = (e: any) => { - if (!props.value) { - setIsFocus(false); - } - - if (clearSpaceOnBlur) { - e.target.value = e.target.value?.replace(/\s+/g, ''); - props.onChange?.(e.target.value); - } else { - e.target.value = e.target.value?.trim(); - } - props.onBlur?.(e); - }; - - const handleOnSelect = (value: any, option: any) => { - onSelect?.(value, option); - }; - - const handleOnInput = (e: any) => { - if (trim) { - e.target.value = e.target.value?.trim(); - } - props.onInput?.(e); - }; - - const renderAfter = () => { - if (loading) { - return ( - - - - ); - } - return suffixIcon || null; - }; - - const popupRender = (originNode: React.ReactElement): React.ReactElement => { - if (loading) { - return ; - } - return originNode || null; - }; - - return ( - - - {placeholder} - ) : ( - '' - ) - } - allowClear={!loading && allowClear} - suffixIcon={renderAfter()} - // @ts-ignore - status={checkStatus || status} - onSelect={handleOnSelect} - onFocus={handleOnFocus} - onBlur={handleOnBlur} - showSearch={showSearch} - onChange={handleChange} - popupRender={popupRender} - onInput={handleOnInput} - onPaste={props.onPaste} - > - - - ); -}; - -export default SealAutoComplete; diff --git a/src/components/seal-form/base/select.tsx b/src/components/seal-form/base/select.tsx deleted file mode 100644 index 0fe0b72b..00000000 --- a/src/components/seal-form/base/select.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import IconFont from '@/components/icon-font'; -import type { SelectProps } from 'antd'; -import { Select } from 'antd'; -import React, { forwardRef, useImperativeHandle } from 'react'; -import NotFoundContent from '../components/not-found-content'; -import SelectCss from './styles.less'; - -const BaseSelect: React.FC< - SelectProps & { ref?: any; footer?: React.ReactNode } -> = forwardRef((props, ref) => { - const { notFoundContent, loading, ...restProps } = props; - const [isFocus, setIsFocus] = React.useState(false); - const inputRef = React.useRef(null); - - useImperativeHandle(ref, () => ({ - ...(inputRef.current || ({} as any)) - })); - - const handleFocus = (e: React.FocusEvent) => { - setIsFocus(true); - props.onFocus?.(e); - }; - const handleBlur = (e: React.FocusEvent) => { - setIsFocus(false); - props.onBlur?.(e); - }; - const renderSuffixIcon = () => { - if (props.suffixIcon) { - return props.suffixIcon; - } - if (!props.showSearch) { - return ; - } - return !isFocus ? : undefined; - }; - - return ( - -
    - - ); -}; - -export default { - TextArea: SealTextArea, - Input: SealInput, - Password: SealPassword, - Number: SealInputNumber, - Search: SealInputSearch -} as Record< - string, - React.FC ->; diff --git a/src/components/seal-form/seal-select.tsx b/src/components/seal-form/seal-select.tsx deleted file mode 100644 index cf02c4f3..00000000 --- a/src/components/seal-form/seal-select.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { isNotEmptyValue } from '@/utils/index'; -import { useIntl } from '@umijs/max'; -import type { SelectProps } from 'antd'; -import { Form } from 'antd'; -import { cloneDeep } from 'lodash'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import BaseSelect from './base/select'; -import NotFoundContent from './components/not-found-content'; -import { SealFormItemProps } from './types'; -import Wrapper from './wrapper'; -import SelectWrapper from './wrapper/select'; - -const SealSelect: React.FC< - SelectProps & - SealFormItemProps & { footer?: React.ReactNode; alwaysFocus?: boolean } -> = (props) => { - const { - label, - placeholder, - children, - required, - description, - options, - allowNull, - isInFormItems = true, - notFoundContent = null, - loading, - footer, - alwaysFocus = false, - styles, - ...rest - } = props; - const intl = useIntl(); - const [isFocus, setIsFocus] = useState(false); - const inputRef = useRef(null); - - let status = ''; - - // the status can be controlled by Form.Item - if (isInFormItems) { - const statusData = Form?.Item?.useStatus?.(); - status = statusData?.status || ''; - } else { - status = props.status || ''; - } - - const _options = useMemo(() => { - if (!options?.length) { - return []; - } - const list = cloneDeep(options); - return list.map((item: any) => { - if (item.locale) { - item.label = intl.formatMessage({ id: item.label as string }); - } - return item; - }); - }, [options, intl]); - - useEffect(() => { - if ( - isNotEmptyValue(props.value) || - (allowNull && (props.value === null || props.value === undefined)) - ) { - setIsFocus(true); - } - }, [props.value, allowNull]); - - const handleClickWrapper = () => { - if (!props.disabled && !isFocus) { - inputRef.current?.focus?.(); - setIsFocus(true); - } - }; - - const handleChange = (val: any, options: any) => { - if (isNotEmptyValue(val) || (allowNull && val === null)) { - setIsFocus(true); - } else { - setIsFocus(false); - } - props.onChange?.(val || null, options); - }; - - const handleOnFocus = (e: any) => { - setIsFocus(true); - props.onFocus?.(e); - }; - - const handleOnBlur = (e: any) => { - if (allowNull && props.value === null) { - setIsFocus(true); - } else if (!props.value) { - setIsFocus(false); - } - props.onBlur?.(e); - }; - - return ( - - - - } - > - {children} - - - - ); -}; - -export default SealSelect; diff --git a/src/components/seal-form/seal-slider.tsx b/src/components/seal-form/seal-slider.tsx deleted file mode 100644 index 5a74c056..00000000 --- a/src/components/seal-form/seal-slider.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { INPUT_WIDTH } from '@/constants'; -import { Form, InputNumber, Slider, type SliderSingleProps } from 'antd'; -import React from 'react'; -import LabelInfo from './components/label-info'; -import Wrapper from './wrapper'; -import SliderWrapper from './wrapper/slider'; - -interface SealSliderProps extends SliderSingleProps { - required?: boolean; - label?: React.ReactNode; - labelWidth?: number | string; - description?: string; - isInFormItems?: boolean; - inputnumber?: boolean; - checkStatus?: 'success' | 'error' | 'warning' | ''; -} - -const SealSlider: React.FC = (props) => { - const { - label, - value, - required, - description, - isInFormItems = true, - max, - min, - step, - defaultValue, - checkStatus, - inputnumber = false, - labelWidth, - tooltip = { open: false }, - ...rest - } = props; - - let status = ''; - if (isInFormItems) { - const statusData = Form?.Item?.useStatus?.(); - status = statusData?.status || ''; - } - - const handleChange = (value: number) => { - props.onChange?.(value); - }; - - const handleInput = (value: number | null) => { - const newValue = value || 0; - props.onChange?.(newValue); - }; - - const renderLabel = () => { - return ( - - - - {inputnumber ? ( - - ) : ( - {value} - )} - - ); - }; - return ( - - - - - - ); -}; - -export default SealSlider; diff --git a/src/components/seal-form/seal-switch.tsx b/src/components/seal-form/seal-switch.tsx deleted file mode 100644 index b7f24f59..00000000 --- a/src/components/seal-form/seal-switch.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Form, Switch, type SwitchProps } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import LabelInfo from './components/label-info'; -import Wrapper from './wrapper'; - -const Inner = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding-inline: 14px; - .ant-switch { - margin-top: 0 !important; - } -`; - -interface SealSwitchProps extends SwitchProps { - required?: boolean; - label?: React.ReactNode; - labelWidth?: number | string; - description?: string; - isInFormItems?: boolean; - checkStatus?: 'success' | 'error' | 'warning' | ''; -} - -const SealSlider: React.FC = (props) => { - const { - label, - value, - required, - description, - isInFormItems = true, - defaultValue, - checkStatus, - labelWidth, - size = 'small', - ...rest - } = props; - - let status = ''; - if (isInFormItems) { - const statusData = Form?.Item?.useStatus?.(); - status = statusData?.status || ''; - } - - const handleChange = (value: boolean, event: any) => { - props.onChange?.(value, event); - }; - - return ( - - - - - - - ); -}; - -export default SealSlider; diff --git a/src/components/seal-form/seal-textarea.tsx b/src/components/seal-form/seal-textarea.tsx deleted file mode 100644 index c0e9f357..00000000 --- a/src/components/seal-form/seal-textarea.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Form, Input } from 'antd'; -import type { TextAreaProps } from 'antd/es/input/TextArea'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import styled from 'styled-components'; -import { SealFormItemProps } from './types'; -import Wrapper from './wrapper'; -import InputWrapper from './wrapper/input'; - -const LabelWrapper = styled.div.attrs({ - className: 'seal-textarea-label' -})` - display: flex; - align-items: center; - justify-content: flex-start; - padding-bottom: 2px; - background-color: var(--ant-color-bg-container); -`; - -interface InputTextareaProps extends TextAreaProps { - scaleSize?: boolean; - alwaysFocus?: boolean; // it's order to display the placeholder -} - -const SealTextArea: React.FC = ( - props -) => { - const { - label, - placeholder, - onChange, - onFocus, - onBlur, - onInput, - style, - required, - isInFormItems = true, - description, - variant, - extra, - addAfter, - trim, - scaleSize, - alwaysFocus, - ...rest - } = props; - const [isFocus, setIsFocus] = useState(false); - const inputRef = useRef(null); - let status = ''; - if (isInFormItems) { - const statusData = Form?.Item?.useStatus?.(); - status = statusData?.status || ''; - } - - useEffect(() => { - if (props.value) { - setIsFocus(true); - } - }, [props.value]); - - const autoSize = useMemo(() => { - const focusRows = props.autoSize || { minRows: 2, maxRows: 5 }; - - if (scaleSize) { - return isFocus ? focusRows : { minRows: 1, maxRows: 1 }; - } - - return focusRows; - }, [props.autoSize, isFocus, scaleSize]); - - const handleClickWrapper = () => { - if (!props.disabled && !isFocus) { - inputRef.current?.focus?.({ - cursor: 'all' - }); - setIsFocus(true); - } - }; - - const handleChange = (e: any) => { - onChange?.(e); - }; - - const handleOnFocus = (e: any) => { - setIsFocus(true); - onFocus?.(e); - }; - - const handleOnBlur = (e: any) => { - if (!inputRef.current?.resizableTextArea?.textArea?.value) { - setIsFocus(false); - } - onBlur?.(e); - }; - - const handleInput = (e: any) => { - onInput?.(e); - }; - - return ( - - {label}} - isFocus={alwaysFocus || isFocus} - required={required} - description={description} - className="seal-textarea-wrapper" - extra={extra} - disabled={props.disabled} - addAfter={addAfter} - onClick={handleClickWrapper} - > - handleChange(e)} - > - - - ); -}; - -export default SealTextArea; diff --git a/src/components/seal-form/simple-select.tsx b/src/components/seal-form/simple-select.tsx deleted file mode 100644 index 9e4754a9..00000000 --- a/src/components/seal-form/simple-select.tsx +++ /dev/null @@ -1,308 +0,0 @@ -import { useIntl } from '@umijs/max'; -import type { SelectProps } from 'antd'; -import { Checkbox, Tag } from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; -import styled from 'styled-components'; -import AutoTooltip from '../auto-tooltip'; -import BaseSelect from './base/select'; - -const OptionWrapper = styled.div` - display: flex; - align-items: center; - gap: 8px; -`; - -const DropdownWrapper = styled.div` - .ant-select-item { - padding-inline-start: 8px; - &:hover { - background-color: var(--ant-select-option-active-bg); - } - } - .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { - background-color: unset; - font-weight: unset; - &:hover { - background-color: var(--ant-select-option-active-bg); - } - } -`; - -const SelectAllWrapper = styled.div` - margin-bottom: 10px; - padding: 5px 8px; - font-size: 12px; - border-bottom: 1px solid var(--ant-color-split); - .ant-checkbox-wrapper { - color: var(--ant-color-text-tertiary); - } -`; - -const TagWrapper = styled(Tag)` - border-radius: 12px; -`; - -const SimpleSelect: React.FC< - SelectProps & { - ref?: any; - showTags?: boolean; - optionLabelRender?: (option: any) => React.ReactNode; - styles?: { - wrapper?: React.CSSProperties; - select?: React.CSSProperties; - }; - } -> = forwardRef((props, ref) => { - const intl = useIntl(); - const { - options = [], - showTags: tagsVisible, - styles = {}, - optionLabelRender, - maxTagCount: tagCount, - ...restProps - } = props; - - const [allSelection, setAllSelection] = React.useState<{ - checked: boolean; - indeterminate: boolean; - }>({ - checked: false, - indeterminate: false - }); - const [optionsList, setOptionsList] = React.useState(options || []); - const selectRef = React.useRef(null); - const selectorRef = React.useRef(null); - - const showTags = Array.isArray(props.value) && props.value.length === 1; - const maxTagCount = - Array.isArray(props.value) && props.value.length === 1 ? 1 : 0; - - useEffect(() => { - setOptionsList(options || []); - }, [options]); - - const optionRender = (option: any, info: any) => { - const { value, label } = option; - return ( - - {restProps.value?.includes?.(value) ? ( - - ) : ( - - )} - - {optionLabelRender ? optionLabelRender(option) : label} - - - ); - }; - - const handleOnCheckboxChange = (e: CheckboxChangeEvent) => { - const isChecked = e.target.checked; - const allValues = optionsList?.map((opt: any) => opt.value) || []; - - setAllSelection({ - checked: isChecked, - indeterminate: false - }); - - let allSelectedValues = [...(restProps.value || [])]; - - if (isChecked) { - // Select all options - allSelectedValues = Array.from( - new Set([...allSelectedValues, ...allValues]) - ); - } else { - // Deselect all options - allSelectedValues = allSelectedValues.filter( - (value) => !allValues.includes(value) - ); - } - - restProps.onChange?.(allSelectedValues, optionsList || []); - }; - - const dropdownRender = (originPanel: React.ReactNode) => { - return ( - - {restProps.mode === 'multiple' && ( - - - {intl.formatMessage({ id: 'common.checkbox.all' })} - - - )} - {originPanel} - - ); - }; - - const handleOnChange = (value: any, option: any) => { - const selectedValues = Array.isArray(value) ? value : [value]; - const allSelected = optionsList?.map((opt: any) => opt.value) || []; - const isAllSelected = selectedValues.length === allSelected?.length; - - setAllSelection({ - checked: isAllSelected, - indeterminate: !isAllSelected && selectedValues.length > 0 - }); - - restProps.onChange?.(selectedValues, option); - }; - - const filterOption = (inputValue: string, option: any) => { - if (!option || !option.label) return false; - return option.label.toLowerCase().includes(inputValue.toLowerCase()); - }; - - const checkAllSelection = (list: Global.BaseOption[]) => { - if ( - !restProps.value || - !Array.isArray(restProps.value) || - list.length === 0 - ) { - setAllSelection({ - checked: false, - indeterminate: false - }); - return; - } - const selectedValues = new Set(restProps.value); - const allValues = list?.map((opt: any) => opt.value) || []; - - const isAllSelected = allValues.every((val: any) => - selectedValues.has(val) - ); - - const isSomeSelected = allValues.some((val: any) => - selectedValues.has(val) - ); - - setAllSelection({ - checked: isAllSelected, - indeterminate: isSomeSelected && !isAllSelected - }); - }; - - const TagRender = (props: any) => { - const { label } = props; - const count = props.isMaxTag ? label.slice(0, -3).slice(1) : label; - - return ( - - {showTags - ? label - : intl.formatMessage({ id: 'common.select.count' }, { count: count })} - - ); - }; - - const handleOnSearch = (value: string) => { - if (restProps.showSearch?.onSearch) { - restProps.showSearch?.onSearch?.(value); - } else { - const filteredOptions = options?.filter((option: any) => - option.label.toLowerCase().includes(value.toLowerCase()) - ) as Global.BaseOption[]; - setOptionsList(filteredOptions || []); - checkAllSelection(filteredOptions || []); - } - }; - - const handleOnBlur = (e: any) => { - restProps.onBlur?.(e); - }; - - const handleOnFocus = (e: any) => { - restProps.onFocus?.(e); - }; - - const handleOnOpenChange = (open: boolean) => { - if (!open) { - checkAllSelection(options as Global.BaseOption[]); - setOptionsList(options || []); - } - }; - - useEffect(() => { - const input = selectRef.current?.querySelector?.('input'); - - if (!input) return; - - const handler = (event: KeyboardEvent) => { - if ( - event.key === 'Backspace' && - (input as HTMLInputElement).value === '' - ) { - event.stopPropagation(); - event.preventDefault(); - } - }; - - input.addEventListener('keydown', handler); - - return () => { - input.removeEventListener('keydown', handler); - }; - }, [selectRef.current]); - - useImperativeHandle(ref, () => ({ - focus: () => { - selectorRef.current?.focus(); - }, - blur: () => { - selectorRef.current?.blur(); - } - })); - - return ( -
    - - {props.children} - -
    - ); -}); - -export default SimpleSelect; diff --git a/src/components/seal-form/styles/row-textarea.less b/src/components/seal-form/styles/row-textarea.less deleted file mode 100644 index c9794d48..00000000 --- a/src/components/seal-form/styles/row-textarea.less +++ /dev/null @@ -1,138 +0,0 @@ -.row-textarea-wrapper { - position: relative; - - .actions-wrapper { - display: none; - position: absolute; - right: 6px; - bottom: 50%; - transform: translateY(50%); - gap: 8px; - z-index: 10; - align-items: center; - justify-content: flex-end; - } - - .actions { - display: flex; - gap: 4px; - align-items: center; - border-radius: var(--ant-border-radius); - } - - &.show { - display: flex; - bottom: 8px; - transform: unset; - } - - &:hover { - .actions-wrapper { - display: flex; - } - } - - &.dropDownOpen { - .actions-wrapper { - display: flex; - } - } - - &.from-url { - .actions-wrapper { - display: flex; - } - } - - &.expanded { - .actions-wrapper { - display: flex; - bottom: 8px; - transform: unset; - } - } -} - -.row-textarea { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0; - overflow: hidden; - cursor: pointer; - transition: background-color 0.3s ease; - border-radius: var(--border-radius-base); - border: 1px solid var(--ant-color-border); - - .textarea-wrapper { - flex: 1; - width: 100%; - } - - &:hover { - &::before { - content: ''; - display: flex; - position: absolute; - left: 0; - bottom: 0; - right: 0; - top: 0; - background-color: var(--ant-color-fill-tertiary); - z-index: 5; - pointer-events: none; - } - } - - &:focus-within { - background-color: transparent; - - &::before { - background-color: transparent; - } - } - - .content-wrap { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - flex: 1; - width: 100%; - cursor: pointer; - - .content { - margin-right: 60px; - } - } - - textarea.ant-input { - background-color: transparent; - box-shadow: none; - } - - .textarea-label { - position: relative; - top: 4px; - padding-left: 14px; - } - - .content { - flex: 1; - width: 100px; - height: 46px; - line-height: 30px; - padding: 8px 14px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - color: var(--ant-color-text-secondary); - - .title { - font-weight: var(--font-weight-normal); - padding-right: 10px; - color: var(--ant-color-text); - } - } -} diff --git a/src/components/seal-form/styles/slider.less b/src/components/seal-form/styles/slider.less deleted file mode 100644 index d819397c..00000000 --- a/src/components/seal-form/styles/slider.less +++ /dev/null @@ -1,24 +0,0 @@ -:local(.slider-label) { - display: flex; - justify-content: space-between; - align-items: flex-start; - width: 100%; - - :global(.val) { - color: var(--ant-color-text); - } - - :global(.label-val) { - position: absolute !important; - top: -14px; - right: -14px; - width: 80px; - border-radius: var(--border-radius-base); - text-align: center; - border: 1px solid var(--ant-color-border) !important; - - :global(.ant-input-number-input) { - text-align: center !important; - } - } -} diff --git a/src/components/seal-form/switch-input.tsx b/src/components/seal-form/switch-input.tsx deleted file mode 100644 index 76b85652..00000000 --- a/src/components/seal-form/switch-input.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Switch, Tooltip } from 'antd'; -import React, { useState } from 'react'; -import styled from 'styled-components'; -import LabelInfo from '../seal-form/components/label-info'; - -const SwitchContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - gap: 8px; - border: 1px solid var(--ant-color-border); - border-radius: var(--ant-border-radius); - padding: 12px 14px; - min-height: 54px; -`; - -const LabelContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -interface SwitchInputProps { - label?: React.ReactNode; - description?: string; - checked?: boolean; - defaultChecked?: boolean; - onChange?: (checked: boolean) => void; - children?: React.ReactNode; - style?: React.CSSProperties; - alwaysShowChildren?: boolean; - btnTips?: React.ReactNode; - size?: 'small' | 'default'; -} - -const SwitchInput: React.FC = (props) => { - const { - label, - description, - checked, - defaultChecked, - onChange, - children, - style, - alwaysShowChildren = true, - size, - btnTips - } = props; - const [internalChecked, setInternalChecked] = useState( - defaultChecked || false - ); - - const handleChange = (checked: boolean) => { - setInternalChecked(checked); - onChange?.(checked); - }; - - return ( - - - - - - - - {(alwaysShowChildren || internalChecked) && children} - - ); -}; - -export default SwitchInput; diff --git a/src/components/seal-form/types.ts b/src/components/seal-form/types.ts deleted file mode 100644 index be806e37..00000000 --- a/src/components/seal-form/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -export interface SealFormItemProps { - label?: React.ReactNode; - required?: boolean; - isInFormItems?: boolean; - description?: React.ReactNode; - extra?: React.ReactNode; - addAfter?: React.ReactNode; - allowNull?: boolean; - loading?: React.ReactNode; - labelExtra?: React.ReactNode; - trim?: boolean; - checkStatus?: 'success' | 'error' | 'warning' | ''; -} diff --git a/src/components/seal-form/wrapper/auto-complete-label.ts b/src/components/seal-form/wrapper/auto-complete-label.ts deleted file mode 100644 index 3a3d40ab..00000000 --- a/src/components/seal-form/wrapper/auto-complete-label.ts +++ /dev/null @@ -1,28 +0,0 @@ -import styled from 'styled-components'; -import { BGCOLOR, INPUT_INNER_PADDING } from '../config'; - -const AutoCompleteLabel = styled.span` - position: absolute; - left: ${INPUT_INNER_PADDING}px; - height: 20px; - line-height: 20px; - top: 26px; - pointer-events: none; - transition: all 0.2s; - background-color: var(--ant-color-bg-container); - z-index: 10; - &.disabled { - background-color: #f5f5f5; - } - &.isfoucs-has-value { - // display: none; - z-index: -1; - // top: 9px; - // font-size: 12px; - // color: var(--ant-color-text); - // background-color: ${BGCOLOR}; - // padding: 0 4px; - } -`; - -export default AutoCompleteLabel; diff --git a/src/components/seal-form/wrapper/index.tsx b/src/components/seal-form/wrapper/index.tsx deleted file mode 100644 index 309a8ba8..00000000 --- a/src/components/seal-form/wrapper/index.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { theme } from 'antd'; -import classNames from 'classnames'; -import React, { FC } from 'react'; -import styled from 'styled-components'; -import LabelInfo from '../components/label-info'; -import { INPUT_INNER_PADDING } from '../config'; - -interface WrapperProps { - children: React.ReactNode; - label?: React.ReactNode; - noWrapperStyle?: boolean; - isFocus?: boolean; - classList?: string; - status?: string; // error | success | warning - required?: boolean; - description?: React.ReactNode; - className?: string; - disabled?: boolean; - extra?: React.ReactNode; - addAfter?: React.ReactNode; - variant?: string; - hasPrefix?: boolean; - labelExtra?: React.ReactNode; - onClick?: () => void; -} - -// wrapper box -const WrapperBox = styled.div` - position: relative; - display: flex; - justify-content: flex-start; - align-items: center; - height: 54px; - border-width: var(--ant-line-width); - border-style: var(--ant-line-type); - border-color: var(--ant-color-border); - border-radius: var(--border-radius-lg); - background-color: var(--ant-color-bg-container); - &.borderless { - border: none; - box-shadow: none; - } - &.filled { - border: none; - box-shadow: none; - } - &.borderless:focus-within { - border: none; - box-shadow: none; - } - &:hover { - border-color: var(--ant-input-hover-border-color); - transition: all 0.2s ease; - } - &:focus-within:not(.no-focus, .borderless) { - border-color: var(--ant-input-active-border-color); - box-shadow: var(--ant-input-active-shadow); - outline: 0; - background-color: var(--ant-input-active-bg); - } - &.validate-status-error:not(.seal-select-wrapper) { - border-width: var(--ant-line-width); - border-style: var(--ant-line-type); - border-color: var(--ant-color-error); - - &:hover { - border-color: var(--ant-color-error-border-hover); - } - - &:focus-within { - border-color: var(--ant-color-error); - } - } - &.seal-input-wrapper-disabled { - background-color: var(--ant-color-bg-container-disabled); - cursor: not-allowed; - - &:hover { - border-color: var(--ant-color-border); - - .ant-input-search-button { - border-color: var(--ant-color-border) !important; - color: var(--ant-color-text-description) !important; - } - } - } -`; - -// wrapper -const InnerWrapper = styled.div.attrs<{ - $noWrapperStyle?: boolean; - $nolabel?: boolean; -}>((props) => ({ - className: classNames({ - __wrapper__: true, - 'no-wrapper-style': props.$noWrapperStyle, - 'no-label': props.$nolabel - }) -}))` - position: relative; - display: flex; - flex-direction: column; - align-items: flex-start; - flex: 1; - padding-block: 20px 0; - max-width: 100%; - &:hover { - .ant-input-number-handler-wrap { - width: 34px !important; - } - } - &.no-wrapper-style { - padding-block: 0; - } - &.no-label { - padding-block: 0; - } -`; - -// label -export const Label = styled.div.attrs<{ - $isFocus?: boolean; - $hasPrefix?: boolean; -}>((props) => ({ - className: classNames({ - 'isfoucs-has-value': props.$isFocus, - 'blur-no-value': !props.$isFocus, - 'has-prefix': props.$hasPrefix - }) -}))` - position: absolute; - left: ${INPUT_INNER_PADDING}px; - color: rgba(0, 0, 0, 45%); - font-size: var(--font-size-base); - line-height: 1; - pointer-events: all; - display: flex; - width: max-content; - z-index: 5; - - &.isfoucs-has-value { - top: 10px; - transition: all 0.2s var(--seal-transition-func); - } - - &.blur-no-value { - top: 20px; - transition: all 0.2s var(--seal-transition-func); - } - - &.has-prefix { - top: 10px !important; - } -`; - -// inner -const Inner = styled.div.attrs({ - className: '__inner__' -})` - width: 100%; - display: flex; -`; - -const Extra = styled.div` - position: absolute; - right: 12px; - top: 8px; - z-index: 10; -`; - -const AddAfter = styled.div` - position: relative; - border-radius: 0 var(--ant-border-radius) var(--ant-border-radius) 0; - color: var(--ant-color-text-tertiary); - font-size: var(--font-size-base); - padding-inline: calc(var(--ant-padding-sm) - 1px); - height: 100%; - display: flex; - align-items: center; - background-color: var(--ant-color-fill-secondary); -`; - -// for wrapper - -const Wrapper: FC = ({ - children, - label, - isFocus, - status, - className, - disabled, - classList, - description, - required, - extra, - variant, - addAfter, - hasPrefix, - noWrapperStyle, - labelExtra, - onClick -}) => { - const { token } = theme.useToken(); - const wrapperClass = classNames( - status ? `validate-status-${status}` : '', - className, - classList, - variant, - { - 'seal-input-wrapper-addafter': addAfter, - 'seal-input-wrapper-disabled': disabled - } - ); - const wrapperStyle: Record = { - '--ant-line-width': '1px', - '--ant-line-type': 'solid', - '--ant-color-border': token.colorBorder, - '--ant-color-bg-container': token.colorBgContainer, - '--ant-color-bg-container-disabled': token.colorBgContainerDisabled, - '--ant-color-error': token.colorError, - '--ant-input-hover-border-color': token.colorPrimaryHover, - '--ant-color-error-border-hover': token.colorErrorBorderHover, - '--ant-input-active-border-color': token.colorPrimary, - '--ant-input-active-shadow': `0 0 0 2px ${token.controlOutline}`, - '--ant-input-active-bg': token.colorBgContainer - }; - return ( - - - {label && ( - - )} - {extra && {extra}} - {children} - - {addAfter && {addAfter}} - - ); -}; - -export default Wrapper; diff --git a/src/components/seal-form/wrapper/input.ts b/src/components/seal-form/wrapper/input.ts deleted file mode 100644 index fd2a1505..00000000 --- a/src/components/seal-form/wrapper/input.ts +++ /dev/null @@ -1,187 +0,0 @@ -import styled from 'styled-components'; -import { - BGCOLOR, - BORDERRADIUS, - INPUTHEIGHT, - INPUT_INNER_PADDING, - WRAPHEIGHT -} from '../config'; - -const InputWrapper = styled.div` - .seal-input-number { - padding-right: 0; - .isfoucs-has-value { - top: 9px; - } - } - - // ============= input initial ============= - .ant-input-outlined.ant-input-status-error:not(.ant-input-disabled) { - border: none; - box-shadow: none; - } - - .ant-input-outlined.ant-input-status-error:not(.ant-input-disabled):focus, - .ant-input-outlined.ant-input-status-error:not( - .ant-input-disabled - ):focus-within { - border: none; - box-shadow: none; - } - - .ant-input-number-outlined.ant-input-number-status-error:not( - .ant-input-number-disabled - ) { - border: none; - box-shadow: none; - } - - .ant-input-number-outlined.ant-input-number-status-error:not( - .ant-input-number-disabled - ):focus, - .ant-input-number-outlined.ant-input-number-status-error:not( - .ant-input-disabled - ):focus-within { - border: none; - box-shadow: none; - } - // ============= input initial end ============= - - .seal-input-wrapper-disabled { - background-color: var(--ant-color-bg-container-disabled); - cursor: not-allowed; - - &:hover { - border-color: var(--ant-color-border); - .ant-input-search-button { - border-color: var(--ant-color-border) !important; - color: var(--ant-color-text-description) !important; - } - } - } - .ant-input-number-input-wrap { - flex: 1; - } - .ant-input, - .ant-input-password { - flex: 1; - display: flex; - align-items: center; - border: none; - box-shadow: none; - padding-block: 5px; - padding-inline: ${INPUT_INNER_PADDING}px; - background-color: ${BGCOLOR}; - } - .ant-input.seal-textarea { - flex: 1; - overflow-y: auto !important; - } - - .ant-input-number { - flex: 1; - position: static; - display: flex; - align-items: center; - border: none; - box-shadow: none; - padding: 0; - background-color: ${BGCOLOR}; - flex: 1; - - &:hover .ant-input-number-handler-wrap, - &-focused .ant-input-number-handler-wrap { - width: 34px !important; - } - - &:hover { - cursor: text; - } - &.ant-input-number-disabled { - &:hover { - background-color: inherit; - } - } - } - .ant-input-outlined { - display: flex; - align-items: center; - border: none; - box-shadow: none; - padding-block: 5px; - padding-inline: ${INPUT_INNER_PADDING}px; - background-color: transparent; - } - .ant-input.ant-input-disabled { - background-color: transparent; - } - input.ant-input-number-input { - flex: 1; - padding-block: 6px 4px; - padding-inline: ${INPUT_INNER_PADDING}px; - } - .ant-input-group { - position: static; - } - .ant-input-group-addon { - inset-inline-start: unset !important; - border-radius: 0 ${BORDERRADIUS}px ${BORDERRADIUS}px 0; - width: 30px; - background-color: transparent; - border: none; - &:hover { - background-color: transparent !important; - } - } - .ant-input-group-wrapper-disabled { - .ant-input-group-addon { - background-color: transparent; - } - .ant-input-group-addon:hover { - background-color: transparent !important; - } - } - - &:not(.textarea-input-wrapper) { - .ant-input, - .ant-input-password, - .ant-input-number, - .ant-input-outlined, - input.ant-input-number-input { - height: ${INPUTHEIGHT}px !important; - } - } - .ant-input-search-button { - position: absolute; - top: -20px; - right: 1px; - border-radius: 0 ${BORDERRADIUS}px ${BORDERRADIUS}px 0 !important; - overflow: hidden; - border: none; - height: 52px; - border-left: var(--ant-line-width) var(--ant-line-type) - var(--ant-color-border); - } - .ant-input-number-handler-wrap { - top: 0; - height: ${WRAPHEIGHT - 2}px; - } - .ant-input-number-actions { - border-radius: 0 ${BORDERRADIUS}px ${BORDERRADIUS}px 0; - } - .seal-textarea-wrapper { - height: auto; - padding-right: 4px; - textarea { - overflow-y: auto !important; - } - } - .ant-input-textarea-allow-clear.ant-input-affix-wrapper { - padding: 0; - } - .ant-input-number-input:placeholder-shown { - font-size: var(--ant-font-size); - } -`; - -export default InputWrapper; diff --git a/src/components/seal-form/wrapper/select.ts b/src/components/seal-form/wrapper/select.ts deleted file mode 100644 index 928aa993..00000000 --- a/src/components/seal-form/wrapper/select.ts +++ /dev/null @@ -1,213 +0,0 @@ -import styled from 'styled-components'; -import { BORDERRADIUS, INPUT_INNER_PADDING, INPUTHEIGHT } from '../config'; - -const SelectWrapper = styled.div` - flex: 1; - .seal-select-wrapper { - border: none; - box-shadow: none; - - &.dropdown-visible { - .__wrapper__ { - .ant-cascader.ant-select.ant-select-outlined { - border-color: var(--ant-input-active-border-color) !important; - outline: 0; - background-color: var(--ant-input-active-bg); - border-bottom-color: transparent !important; - border-radius: ${BORDERRADIUS}px ${BORDERRADIUS}px 0 0; - transition: all 0.2s ease; - box-shadow: none; - - &::before { - content: ''; - position: absolute; - height: 1px; - margin-inline: 1px; - bottom: 0; - left: 0px; - right: 0; - background-color: var(--ant-color-split); - } - } - - .ant-select-dropdown { - box-shadow: none; - border-width: var(--ant-line-width); - border-style: var(--ant-line-type); - border-color: var(--ant-input-active-border-color); - border-top: none; - } - - &:hover { - .ant-select-dropdown { - border-color: var(--ant-input-active-border-color); - } - } - - &:focus-within { - border-color: var(--ant-input-active-border-color) !important; - outline: 0; - background-color: var(--ant-input-active-bg); - } - } - } - - &:focus-within { - border: none; - box-shadow: none; - } - .__wrapper__ { - padding-block: 0; - - .label { - left: ${INPUT_INNER_PADDING + 1}px !important; - top: 11px; - - &.isfoucs-has-value { - top: 11px; - transition: all 0.2s var(--seal-transition-func); - } - - &.blur-no-value { - top: 21px; - transition: all 0.2s var(--seal-transition-func); - } - - &.has-prefix { - top: 11px !important; - } - } - &.no-label { - padding-block: 0; - - .ant-select-arrow { - top: 50%; - } - - .ant-select .ant-select-input { - top: -5px !important; - } - .ant-select-placeholder { - position: absolute; - top: 1px; - left: 0; - right: 0; - } - .ant-select .ant-select-content { - padding-block: 0px 0; - } - .ant-select-auto-complete { - .ant-select-placeholder { - top: 1px; - } - } - .ant-select.ant-cascader { - .ant-select-placeholder { - top: 0px !important; - } - .ant-select-input { - top: -6px !important; - } - } - } - } - .ant-select-selection-overflow-item > span { - display: flex; - align-items: center; - } - .ant-select { - display: flex; - align-items: center; - height: 54px; - padding-inline: 14px !important; - - .ant-select-selection-wrap { - height: 100%; - } - - &.ant-select-auto-complete { - .ant-select-selection-search { - padding-inline-start: ${INPUT_INNER_PADDING}px; - } - .ant-select-content-value { - display: none; - } - } - &.ant-select-multiple.ant-cascader .ant-select-selection-search { - top: 0 !important; - } - - .ant-select-content { - padding-block: 20px 0; - box-shadow: none !important; - } - &.ant-cascader { - .ant-select-content-item-prefix + .ant-select-content-item-suffix { - margin-inline-start: 0 !important; - } - } - &.seal-cascader-small { - height: 40px; - } - - .ant-select-input { - top: 14px !important; - } - .ant-select-placeholder { - > span { - padding-inline: 0 !important; - } - } - } - - .ant-select-multiple.ant-select-lg { - .ant-select-selection-search { - margin-inline-start: 0 !important; - left: 0 !important; - } - } - - .ant-select-selection-item { - height: ${INPUTHEIGHT}px !important; - padding-block: 5px !important; - line-height: 22px !important; - padding-inline-end: 0 !important; - } - - .ant-select-arrow { - top: 32px; - } - - .ant-select-selection-search-input { - height: ${INPUTHEIGHT}px !important; - } - - &.validate-status-error { - .ant-select-dropdown { - border-color: var(--ant-color-error) !important; - } - .__wrapper__ { - .ant-cascader.ant-select.ant-select-outlined { - border-color: var(--ant-color-error) !important; - } - } - } - &.seal-cascader-wrapper-small { - height: 40px; - .cascader-popup-wrapper { - top: 39px !important; - } - .ant-select-input { - height: 36px !important; - } - .ant-select { - padding-inline: 12px !important; - } - .__wrapper__.no-label .ant-select.ant-cascader .ant-select-placeholder { - top: 50% !important; - } - } - } -`; - -export default SelectWrapper; diff --git a/src/components/seal-form/wrapper/slider.ts b/src/components/seal-form/wrapper/slider.ts deleted file mode 100644 index 6d97ff63..00000000 --- a/src/components/seal-form/wrapper/slider.ts +++ /dev/null @@ -1,57 +0,0 @@ -import styled from 'styled-components'; -import { INPUTHEIGHT, INPUT_INNER_PADDING } from '../config'; - -const SliderWrapper = styled.div` - .__wrapper__ { - height: 100%; - justify-content: center; - } - .label-wrapper { - width: 100%; - } - - .borderless { - background-color: transparent; - } - .ant-slider { - flex: 1; - } - padding-block: 0; - padding-inline: 2px; - input.ant-input-number-input { - text-align: center !important; - flex: 1; - height: ${INPUTHEIGHT}px !important; - padding-block: 5px; - padding-inline: ${INPUT_INNER_PADDING}px; - } - .isfoucs-has-value { - left: 0; - } - .slider-label { - display: flex; - justify-content: space-between; - align-items: flex-start; - width: 100%; - - .val { - color: var(--ant-color-text); - } - - .label-val { - position: absolute !important; - top: -14px; - right: 0px; - width: 80px; - border-radius: var(--border-radius-base); - text-align: center; - border: 1px solid var(--ant-color-border) !important; - - .ant-input-number-input { - text-align: center !important; - } - } - } -`; - -export default SliderWrapper; diff --git a/src/components/seal-table/components/cell-content.tsx b/src/components/seal-table/components/cell-content.tsx deleted file mode 100644 index 7b127341..00000000 --- a/src/components/seal-table/components/cell-content.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { CheckOutlined, FormOutlined, UndoOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Input, InputNumber, Tooltip } from 'antd'; -import _ from 'lodash'; -import React, { useContext, useEffect } from 'react'; -import styled from 'styled-components'; -import RowContext from '../row-context'; -import { CellContentProps } from '../types'; - -const CellContentWrapper = styled.div` - max-width: 100%; - display: flex; - align-items: center; -`; - -interface EditButtonsProps { - isEditing: boolean; - editable?: any; - handleSubmit: () => void; - handleUndo: () => void; - handleEdit: () => void; -} - -interface ContentProps { - isEditing: boolean; - current: any; - editable: any; - onChange: (val: any) => void; - row?: any; - render?: (text: any, record: any) => React.ReactNode; -} - -const EditButtons: React.FC = (props) => { - const intl = useIntl(); - const { isEditing, editable, handleSubmit, handleUndo, handleEdit } = props; - if (!editable) { - return null; - } - - if (isEditing) { - return ( - - - - - - - - - ); - } - return ( - - {editable.title || ''} - ) - } - > - - - - ); -}; - -const Content: React.FC = (props) => { - const { editable, current, isEditing, row, render, onChange } = props; - if (isEditing && editable) { - const isNumType = - typeof editable === 'object' && editable?.valueType === 'number'; - return isNumType ? ( - - ) : ( - onChange(e.target.value)} /> - ); - } - - if (render) { - return render(current, row); - } - return current; -}; - -const CellContent: React.FC = (props) => { - const { row, onCell } = useContext(RowContext); - const { dataIndex, render, editable } = props; - const [isEditing, setIsEditing] = React.useState(false); - const [current, setCurrent] = React.useState(row[dataIndex]); - const cachedValue = React.useRef(null); - - const handleEdit = () => { - setIsEditing(true); - }; - - const handleSubmit = async () => { - cachedValue.current = current; - await onCell?.( - { - ...row, - [dataIndex]: current - }, - { - dataIndex, - newValue: current, - oldValue: row[dataIndex] - } - ); - setIsEditing(false); - }; - - const handleUndo = () => { - setCurrent(cachedValue.current); - setIsEditing(false); - }; - - const handleValueChange = (val: any) => { - setCurrent(val); - }; - - useEffect(() => { - cachedValue.current = row[dataIndex]; - setCurrent(row[dataIndex]); - }, [row[dataIndex]]); - - return ( - - - - - ); -}; - -export default CellContent; diff --git a/src/components/seal-table/components/header-prefix.tsx b/src/components/seal-table/components/header-prefix.tsx deleted file mode 100644 index e324e5fe..00000000 --- a/src/components/seal-table/components/header-prefix.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import IconFont from '@/components/icon-font'; -import { useIntl } from '@umijs/max'; -import { Button, Checkbox } from 'antd'; -import _ from 'lodash'; -import React from 'react'; - -interface HeaderPrefixProps { - expandable?: boolean | React.ReactNode; - enableSelection?: boolean; - onSelectAll?: (e: any) => void; - onExpandAll?: (e: any) => void; - expandAll?: boolean; - indeterminate?: boolean; - selectAll?: boolean; - hasColumns?: boolean; - disabled?: boolean; -} - -const HeaderPrefix: React.FC = (props) => { - const { - hasColumns, - expandable, - enableSelection, - onSelectAll, - onExpandAll, - indeterminate, - selectAll, - expandAll, - disabled - } = props; - - const intl = useIntl(); - - const handleToggleExpand = () => { - onExpandAll?.(!expandAll); - }; - - const handleUnCheckAll = () => { - onSelectAll?.({ - target: { - checked: false - } - }); - }; - - if (!hasColumns) { - return null; - } - if (expandable && enableSelection) { - return ( -
    - - {_.isBoolean(expandable) ? ( - - ) : ( - expandable - )} - - -
    - ); - } - if (expandable) { - return ( -
    - - {_.isBoolean(expandable) ? ( - - ) : ( - expandable - )} - -
    - ); - } - if (enableSelection) { - return ( -
    - {} -
    - ); - } - return null; -}; - -export default HeaderPrefix; diff --git a/src/components/seal-table/components/header.tsx b/src/components/seal-table/components/header.tsx deleted file mode 100644 index 75cad7f7..00000000 --- a/src/components/seal-table/components/header.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Col, Row } from 'antd'; -import React from 'react'; -import { OnSortFn, SealColumnProps, TableOrder } from '../types'; -import TableHeader from './table-header'; - -interface HeaderProps { - columns: SealColumnProps[]; - sortDirections?: ('ascend' | 'descend' | null)[]; - sorterList: TableOrder | Array; - showSorterTooltip?: boolean; - onSort?: OnSortFn; -} - -const Header: React.FC = (props) => { - const { onSort, sortDirections, sorterList, showSorterTooltip } = props; - - return ( - - {props.columns?.map((columnProps, i) => { - const { - title, - dataIndex, - align, - width, - span, - headerStyle, - sortOrder, - sorter, - defaultSortOrder - } = columnProps as SealColumnProps; - return ( - - - - ); - })} - - ); -}; - -export default Header; diff --git a/src/components/seal-table/components/layout.tsx b/src/components/seal-table/components/layout.tsx deleted file mode 100644 index cc1a0e41..00000000 --- a/src/components/seal-table/components/layout.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import styled from 'styled-components'; - -const Row = styled.div.attrs<{ className?: string }>((props) => ({ - className: props.className -}))` - width: 100%; - display: flex; - justify-content: flex-start; - align-items: center; -`; - -const Col = styled.div<{ - $width?: string | number; - $align?: string; - $flexBasis?: string | number; - $maxWidth?: string | number; -}>` - flex: ${(props) => (props.$width ? 'none' : 1)}; - display: flex; - justify-content: ${(props) => props.$align || 'flex-start'}; - align-items: center; - width: ${({ $width }) => - $width ? (typeof $width === 'number' ? `${$width}px` : $width) : '10px'}; - ${({ $flexBasis }) => ($flexBasis ? `flex-basis: ${$flexBasis};` : '')} - ${({ $maxWidth }) => ($maxWidth ? `max-width: ${$maxWidth};` : '')} -`; - -export { Col, Row }; diff --git a/src/components/seal-table/components/pagination.tsx b/src/components/seal-table/components/pagination.tsx deleted file mode 100644 index 271fa0e3..00000000 --- a/src/components/seal-table/components/pagination.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Pagination, type PaginationProps } from 'antd'; - -const PaginationComponent: React.FC = (props) => { - return ; -}; - -export default PaginationComponent; diff --git a/src/components/seal-table/components/row-children.tsx b/src/components/seal-table/components/row-children.tsx deleted file mode 100644 index e02899ea..00000000 --- a/src/components/seal-table/components/row-children.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import '../styles/row-children.less'; - -const RowChildren = (props: any) => { - const { children } = props; - - return
    {children}
    ; -}; - -export default RowChildren; diff --git a/src/components/seal-table/components/row-prefix.tsx b/src/components/seal-table/components/row-prefix.tsx deleted file mode 100644 index 9451c9c0..00000000 --- a/src/components/seal-table/components/row-prefix.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import IconFont from '@/components/icon-font'; -import { Button, Checkbox } from 'antd'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; - -const ButtonWrapper = styled.div` - width: 30px; - margin-right: 5px; - &.disable-expand { - .ant-btn { - display: none; - } - } -`; - -interface RowPrefixProps { - expandable?: boolean | React.ReactNode; - enableSelection?: boolean; - expanded?: boolean; - checked?: boolean; - disableExpand?: boolean; - handleRowExpand?: () => void; - handleSelectChange?: (e: any) => void; -} - -const RowPrefix: React.FC = (props) => { - const { - expandable, - enableSelection, - expanded, - checked, - disableExpand, - handleRowExpand, - handleSelectChange - } = props; - - const isExpanded = useMemo(() => { - return expanded; - }, [expanded]); - - if (expandable && enableSelection) { - return ( -
    - - {_.isBoolean(expandable) ? ( - - ) : ( - expandable - )} - - -
    - ); - } - if (expandable) { - return ( -
    - - {_.isBoolean(expandable) ? ( - - ) : ( - expandable - )} - -
    - ); - } - if (enableSelection) { - return ( -
    - {} -
    - ); - } - return null; -}; - -export default RowPrefix; diff --git a/src/components/seal-table/components/table-body.tsx b/src/components/seal-table/components/table-body.tsx deleted file mode 100644 index 7e3f67b4..00000000 --- a/src/components/seal-table/components/table-body.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Empty } from 'antd'; -import React from 'react'; -import { SealColumnProps } from '../types'; -import TableRow from './table-row'; - -interface TableBodyProps { - dataSource: any[]; - columns: SealColumnProps[]; - rowKey: string; - rowSelection?: any; - expandable?: any; - expandedRowKeys?: any; - onExpand?: any; - childParentKey?: any; - pollingChildren?: any; - watchChildren?: any; - renderChildren?: any; - loadChildren?: any; - loadChildrenAPI?: any; - onCell?: any; - empty?: React.ReactNode; -} - -const TableBody: React.FC = ({ - dataSource, - rowKey, - rowSelection, - expandable, - expandedRowKeys, - onExpand, - childParentKey, - pollingChildren, - watchChildren, - renderChildren, - loadChildren, - loadChildrenAPI, - columns, - onCell, - empty -}) => { - if (!dataSource.length) { - return ( -
    - {empty || } -
    - ); - } - - return ( -
    - {dataSource.map((item, index) => ( - - ))} -
    - ); -}; - -export default TableBody; diff --git a/src/components/seal-table/components/table-cell.tsx b/src/components/seal-table/components/table-cell.tsx deleted file mode 100644 index a52d50bb..00000000 --- a/src/components/seal-table/components/table-cell.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import styled from 'styled-components'; -import { SealColumnProps } from '../types'; -import CellContent from './cell-content'; - -const CellWrapper = styled.div` - padding: var(--ant-table-cell-padding-block) - var(--ant-table-cell-padding-inline); - display: flex; - align-items: center; - justify-content: flex-start; - min-height: 68px; - word-break: break-word; - min-width: 20px; - overflow: hidden; - color: var(--ant-color-text-secondary); - - &.left { - justify-content: flex-start; - } - - &.right { - justify-content: flex-end; - } - - &.center { - justify-content: center; - } -`; - -const TableCell: React.FC = (props) => { - const { dataIndex, render, align, editable, dataField } = props; - - return ( - - - - ); -}; - -export default TableCell; diff --git a/src/components/seal-table/components/table-header.tsx b/src/components/seal-table/components/table-header.tsx deleted file mode 100644 index e9a6f9de..00000000 --- a/src/components/seal-table/components/table-header.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import classNames from 'classnames'; -import React, { useEffect } from 'react'; -import '../styles/header.less'; - -import { TableHeaderProps } from '../types'; - -const TableHeader: React.FC = (props) => { - const { - title, - style, - align, - firstCell, - lastCell, - sortOrder, - sortDirections = ['ascend', 'descend', null], - defaultSortOrder, - onSort, - sorterList, - sorter = false, - width, - dataIndex - } = props; - const intl = useIntl(); - - const [currentSortOrder, setCurrentSortOrder] = React.useState< - 'ascend' | 'descend' | null - >(sortOrder || defaultSortOrder || null); - - const getNextSortOrder = (currentOrder: 'ascend' | 'descend' | null) => { - const index = sortDirections.indexOf(currentOrder); - const nextIndex = (index + 1) % sortDirections.length; - return sortDirections[nextIndex]; - }; - - const nextSortTips = () => { - if (!currentSortOrder) { - return intl.formatMessage({ id: 'common.sorter.tips.ascend' }); - } - if (currentSortOrder === 'ascend') { - return intl.formatMessage({ id: 'common.sorter.tips.descend' }); - } - return intl.formatMessage({ id: 'common.sorter.tips.cancel' }); - }; - - const handleOnSort = () => { - setCurrentSortOrder((prev) => { - const next = getNextSortOrder(prev); - onSort?.( - { - columnKey: dataIndex, - field: dataIndex, - order: next - }, - sorter - ); - return next; - }); - }; - - useEffect(() => { - if (!sorterList) { - setCurrentSortOrder(null); - return; - } - - if (Array.isArray(sorterList)) { - const sortItem = sorterList.find( - (item) => item.columnKey === dataIndex || item.field === dataIndex - ); - setCurrentSortOrder(sortItem?.order || null); - } else { - if ( - sorterList.columnKey === dataIndex || - sorterList.field === dataIndex - ) { - setCurrentSortOrder(sorterList.order); - } else { - setCurrentSortOrder(null); - } - } - }, [sorterList]); - - return ( -
    - {sorter ? ( - - - {title} - - - - - - - ) : ( - - {title} - - )} -
    - ); -}; - -export default TableHeader; diff --git a/src/components/seal-table/components/table-row.tsx b/src/components/seal-table/components/table-row.tsx deleted file mode 100644 index a2ed177b..00000000 --- a/src/components/seal-table/components/table-row.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { createAxiosToken } from '@/hooks/use-chunk-request'; -import { useMemoizedFn } from 'ahooks'; -import { Col, Row, Spin } from 'antd'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import RowContext from '../row-context'; -import TableContext from '../table-context'; -import { RowContextProps, SealTableProps } from '../types'; -import RowPrefix from './row-prefix'; -import TableCell from './table-cell'; - -const TableRow: React.FC< - RowContextProps & - Omit -> = (props) => { - const { - record, - rowIndex, - expandable, - rowSelection, - expandedRowKeys = [], - rowKey, - childParentKey, - columns, - pollingChildren, - watchChildren, - onCell, - onExpand, - renderChildren, - loadChildren, - loadChildrenAPI - } = props; - const tableContext: any = React.useContext<{ - allChildren?: any[]; - setDisableExpand?: (record: any) => boolean; - }>(TableContext); - const [childrenData, setChildrenData] = useState([]); - const [loading, setLoading] = useState(false); - const pollTimer = useRef(null); - const chunkRequestRef = useRef(null); - const childrenDataRef = useRef([]); - childrenDataRef.current = childrenData; - const axiosToken = useRef(null); - const [updateChild, setUpdateChild] = useState(true); - const [currentExpand, setCurrentExpand] = useState(false); - - useEffect(() => { - return () => { - if (pollTimer.current) { - clearInterval(pollTimer.current); - } - chunkRequestRef.current?.current?.cancel?.(); - axiosToken.current?.cancel?.(); - }; - }, []); - - const expanded = useMemo(() => { - return expandedRowKeys?.includes(record[rowKey]); - }, [expandedRowKeys]); - - const checked = useMemo(() => { - return rowSelection?.selectedRowKeys?.includes(record[rowKey]); - }, [rowSelection?.selectedRowKeys, record, rowKey]); - - useEffect(() => { - if (expandedRowKeys?.length === 0) { - setCurrentExpand(false); - } - }, [expandedRowKeys.length]); - - const renderChildrenData = () => { - if (childrenData.length === 0) { - return null; - } - return renderChildren?.(childrenData, { - parent: record, - currentExpanded: currentExpand - }); - }; - - const handlePolling = async () => { - if (pollingChildren) { - try { - const data = await loadChildren?.(record); - setChildrenData(data || []); - } catch (error) { - setChildrenData([]); - } - } - }; - - const handleLoadChildren = useMemoizedFn(async () => { - try { - axiosToken.current?.cancel?.(); - axiosToken.current = createAxiosToken(); - setLoading(true); - const data = await loadChildren?.(record, { - token: axiosToken.current?.token - }); - - setChildrenData(data || []); - setLoading(false); - } catch (error) { - setChildrenData([]); - setLoading(false); - } - }); - - const filterUpdateChildrenHandler = () => { - const dataList = _.filter(tableContext.allChildren, (data: any) => { - return _.get(data, [childParentKey]) === _.get(record, [rowKey]); - }); - setChildrenData(dataList); - }; - - const handleRowExpand = async () => { - onExpand?.(!expanded, record, record[rowKey]); - setCurrentExpand(!expanded); - - if (pollTimer.current) { - clearInterval(pollTimer.current); - } - - if (expanded) { - axiosToken.current?.cancel?.(); - return; - } - - if (pollingChildren) { - await handleLoadChildren(); - pollTimer.current = setInterval(() => { - handlePolling(); - }, 1000); - } else { - handleLoadChildren(); - } - }; - - const handleSelectChange = (e: any) => { - if (e.target.checked) { - // update selectedRowKeys - rowSelection?.onChange( - _.uniq([...rowSelection?.selectedRowKeys, record[rowKey]]), - _.uniqBy([...rowSelection?.selectedRows, record], rowKey) - ); - } else { - // update selectedRowKeys - rowSelection?.onChange( - rowSelection?.selectedRowKeys.filter((key) => key !== record[rowKey]), - rowSelection?.selectedRows.filter( - (row) => row[rowKey] !== record[rowKey] - ) - ); - } - }; - - const disableExpand = useMemo(() => { - return tableContext.setDisableExpand?.(record); - }, [tableContext.setDisableExpand, record]); - - useEffect(() => { - const handleVisibilityChange = async () => { - if (document.visibilityState === 'hidden') { - setUpdateChild(false); - } else { - setUpdateChild(true); - } - }; - - document.addEventListener('visibilitychange', handleVisibilityChange); - - return () => { - document.removeEventListener('visibilitychange', handleVisibilityChange); - }; - }, []); - - useEffect(() => { - if (updateChild) { - // for update watch data - filterUpdateChildrenHandler(); - } - return () => { - chunkRequestRef.current?.current?.cancel?.(); - }; - }, [updateChild, tableContext.allChildren]); - - return ( - -
    -
    - - - {columns?.map(({ key, ...restProps }) => { - return ( - - - - ); - })} - -
    - {expanded && !disableExpand && ( -
    - - {renderChildrenData()} - -
    - )} -
    -
    - ); -}; - -export default TableRow; diff --git a/src/components/seal-table/components/table-skeleton.tsx b/src/components/seal-table/components/table-skeleton.tsx deleted file mode 100644 index ef270179..00000000 --- a/src/components/seal-table/components/table-skeleton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Checkbox } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import '../styles/skeleton.less'; - -const SkeletonItem = () => { - return ( -
    -
    - -
    - ); -}; - -const Wrapper = styled.div` - display: flex; - flex-direction: column; - gap: 20px; -`; - -const TableSkeleton = () => { - const dataSource = Array.from({ length: 5 }); - return ( - - {dataSource.map((item, index) => ( - - ))} - - ); -}; - -export default React.memo(TableSkeleton); diff --git a/src/components/seal-table/index.tsx b/src/components/seal-table/index.tsx deleted file mode 100644 index 58d37204..00000000 --- a/src/components/seal-table/index.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { Pagination, Spin, theme, type PaginationProps } from 'antd'; -import _ from 'lodash'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import Header from './components/header'; -import HeaderPrefix from './components/header-prefix'; -import TableBody from './components/table-body'; -import './styles/index.less'; -import { SealColumnProps, SealTableProps } from './types'; -import useSorter from './use-sorter'; - -const Wrapper = styled.div<{ $token: any }>` - width: 100%; - --ant-table-cell-padding-inline: ${(props) => - props.$token.cellPaddingInline}px; - --ant-table-cell-padding-block: ${(props) => props.$token.cellPaddingBlock}px; - --ant-table-header-border-radius: ${(props) => - props.$token.headerBorderRadius}px; - --ant-table-header-split-color: ${(props) => - props.$token.colorBorderSecondary}; - --ant-table-row-selected-bg: ${(props) => props.$token.rowSelectedBg}; - --ant-table-row-selected-hover-bg: ${(props) => - props.$token.rowSelectedHoverBg}; - --ant-table-row-hover-bg: ${(props) => props.$token.rowHoverBg}; - --ant-table-header-icon-color: ${(props) => - props.$token.tableHeaderIconColor}; - --ant-table-header-icon-hover-color: ${(props) => - props.$token.tableHeaderIconHoverColor}; -`; - -const SealTable: React.FC = ( - props -) => { - const { - columns, - children, - rowKey, - childParentKey, - onExpand, - onExpandAll, - onTableSort, - onCell, - expandedRowKeys, - loading, - loadend, - expandable, - pollingChildren, - watchChildren, - rowSelection, - pagination, - empty, - sortDirections, - showSorterTooltip, - renderChildren, - loadChildren, - loadChildrenAPI - } = props; - const { handleOnTableSort, sorterList } = useSorter({ - onTableSort, - columns - }); - const { token } = theme.useToken(); - const parsedColumns = useMemo(() => { - if (columns) return columns; - - return React.Children.toArray(children) - .filter(React.isValidElement) - .map((child) => { - const column = child as React.ReactElement; - const { title, dataIndex, key, render, ...restProps } = column.props; - - return { - title, - dataIndex, - key: key || dataIndex, - render, - ...restProps - }; - }); - }, [columns, children]); - - const expandAll = useMemo(() => { - if (expandedRowKeys?.length === 0) { - return false; - } - const allKeys = new Set(expandedRowKeys); - const currentDataKeys = props.dataSource.map((record) => record[rowKey]); - return currentDataKeys.every((key) => allKeys.has(key)); - }, [props.dataSource, expandedRowKeys]); - - const selectState = useMemo(() => { - const selectedRowKeys = rowSelection?.selectedRowKeys || []; - const selectedKeys = new Set(selectedRowKeys); - const allRowKeys = props.dataSource.map((record) => record[rowKey]); - if (!selectedRowKeys?.length) { - return { - selectAll: false, - indeterminate: false - }; - } - if (allRowKeys.every((key) => selectedKeys.has(key))) { - return { - selectAll: true, - indeterminate: false - }; - } - return { - selectAll: false, - indeterminate: true - }; - }, [rowSelection?.selectedRowKeys, props.dataSource, rowKey]); - - const selectAllRows = () => { - const allKeys = new Set([ - ...props.dataSource.map((record) => record[rowKey]), - ...(rowSelection?.selectedRowKeys || []) - ]); - const allDatas = _.uniqBy( - [...props.dataSource, ...(rowSelection?.selectedRows || [])], - (record: any) => record[rowKey] - ); - - rowSelection?.onChange([...allKeys], allDatas); - }; - - const deselectAllRows = () => { - const currentKeys = props.dataSource.map((record) => record[rowKey]); - rowSelection?.removeSelectedKeys?.(currentKeys); - }; - - const handleSelectAllChange = (e: any) => { - if (e.target.checked) { - selectAllRows(); - } else { - deselectAllRows(); - } - }; - - const handleExpandAll = (value: boolean) => { - onExpandAll?.(value); - }; - - const handlePageChange = (page: number, pageSize: number) => { - pagination?.onChange?.(page, pageSize); - }; - - const handlePageSizeChange = (current: number, size: number) => { - pagination?.onShowSizeChange?.(current, size); - }; - - console.log('token.table', token); - - return ( - -
    -
    - 0} - > -
    -
    - - - -
    - {pagination && ( -
    - -
    - )} -
    - ); -}; - -export default SealTable; diff --git a/src/components/seal-table/row-context.ts b/src/components/seal-table/row-context.ts deleted file mode 100644 index 86b0e1b6..00000000 --- a/src/components/seal-table/row-context.ts +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -interface RowContextType { - row: Record; - onCell?: ( - record: any, - data: { dataIndex: string; newValue: any; oldValue: any } - ) => any; -} - -const RowContext = React.createContext({} as RowContextType); - -export default RowContext; diff --git a/src/components/seal-table/styles/cell.less b/src/components/seal-table/styles/cell.less deleted file mode 100644 index f6a5260a..00000000 --- a/src/components/seal-table/styles/cell.less +++ /dev/null @@ -1,28 +0,0 @@ -.cell { - padding: var(--ant-table-cell-padding-block) - var(--ant-table-cell-padding-inline); - display: flex; - align-items: center; - justify-content: flex-start; - min-height: 68px; - word-break: break-word; - min-width: 20px; - overflow: hidden; - - .cell-content { - max-width: 100%; - line-height: 18px; - } - - &.left { - justify-content: flex-start; - } - - &.right { - justify-content: flex-end; - } - - &.center { - justify-content: center; - } -} diff --git a/src/components/seal-table/styles/header.less b/src/components/seal-table/styles/header.less deleted file mode 100644 index 9f40955a..00000000 --- a/src/components/seal-table/styles/header.less +++ /dev/null @@ -1,89 +0,0 @@ -.table-header { - position: relative; - min-height: 20px; - display: flex; - align-items: center; - justify-content: flex-start; - height: 50px; - padding-inline: var(--ant-table-cell-padding-inline); - color: var(--color-text-table-header); - - &::before { - position: absolute; - top: 50%; - inset-inline-end: 0; - width: 1px; - height: 1.6em; - background-color: var(--ant-table-header-split-color); - transform: translateY(-50%); - content: ''; - } - - &-last { - border-right: none; - } - - &-cell { - font-weight: 500; - } - - &-left { - justify-content: flex-start; - } - - &-right { - justify-content: flex-end; - } - - &-center { - justify-content: center; - } - - &:hover { - .sorter-header { - .sorter { - .anticon { - color: var(--ant-table-header-icon-hover-color); - } - } - } - } - - .sorter-header { - display: flex; - flex: 1; - width: 100%; - justify-content: space-between; - align-items: center; - cursor: pointer; - - .sorter { - display: flex; - flex-direction: column; - align-items: center; - margin-left: 4px; - - .anticon { - font-size: 12px; - color: var(--ant-table-header-icon-color); - transition: color var(--ant-motion-duration-slow); - } - - .sorter-up { - margin-bottom: -0.125em; - } - - .sorter-down { - margin-top: -0.125em; - } - - .sorter-active { - color: var(--ant-color-primary); - - .anticon { - color: var(--ant-color-primary); - } - } - } - } -} diff --git a/src/components/seal-table/styles/index.less b/src/components/seal-table/styles/index.less deleted file mode 100644 index 89d2ac23..00000000 --- a/src/components/seal-table/styles/index.less +++ /dev/null @@ -1,92 +0,0 @@ -.pagination-wrapper { - display: flex; - justify-content: flex-end; - margin: var(--ant-margin) 0; -} - -.seal-table-container { - display: flex; - flex-direction: column; - width: 100%; - - .header-row-wrapper { - height: 50px; - display: flex; - justify-content: flex-start; - align-items: center; - background-color: var(--ant-color-fill-tertiary); - border-radius: var(--ant-table-header-border-radius) - var(--ant-table-header-border-radius) 0 0; - - .header-row-prefix-wrapper { - padding-left: var(--ant-table-cell-padding-inline); - } - - .row { - flex: 1; - } - } - - .row-box { - overflow: hidden; - border-bottom: 1px solid var(--ant-color-split); - } - - .expanded-row { - padding: 16px; - border-top: 0; - border-radius: 0 0 var(--ant-table-header-border-radius) - var(--ant-table-header-border-radius); - - .ant-empty-image { - height: 30px; - } - } - - .row-wrapper { - display: flex; - justify-content: flex-start; - align-items: center; - transition: all 0.2s ease; - - &:hover { - background-color: var(--ant-table-row-hover-bg); - transition: all 0.2s ease; - } - - &-selected { - transition: all 0.2s ease; - - &:hover { - background-color: var(--ant-table-row-hover-bg); - transition: all 0.2s ease; - } - } - - .row-prefix-wrapper { - display: flex; - align-items: center; - padding-left: var(--ant-table-cell-padding-inline); - } - } - - .seal-table-row { - flex: 1; - } - - .spin { - text-align: center; - display: flex; - justify-content: center; - align-items: center; - margin-top: 20px; - min-height: 100px; - } - - .empty-wrapper { - display: flex; - min-height: 160px; - justify-content: center; - align-items: center; - } -} diff --git a/src/components/seal-table/styles/row-children.less b/src/components/seal-table/styles/row-children.less deleted file mode 100644 index 43fd8f86..00000000 --- a/src/components/seal-table/styles/row-children.less +++ /dev/null @@ -1,13 +0,0 @@ -.row-children { - position: relative; - display: flex; - align-items: center; - height: 54px; - border-radius: var(--border-radius-mdium); - transition: all 0.2s ease; - - &:hover { - background-color: var(--ant-table-row-hover-bg); - transition: all 0.2s ease; - } -} diff --git a/src/components/seal-table/styles/skeleton.less b/src/components/seal-table/styles/skeleton.less deleted file mode 100644 index 0c0cdea6..00000000 --- a/src/components/seal-table/styles/skeleton.less +++ /dev/null @@ -1,13 +0,0 @@ -.row-skeleton { - display: flex; - justify-content: flex-start; - background-color: var(--ant-color-fill-tertiary); - padding: 16px; - height: 68px; - border-radius: var(--border-radius-base); - align-items: center; - - .holder { - width: 33px; - } -} diff --git a/src/components/seal-table/table-context.ts b/src/components/seal-table/table-context.ts deleted file mode 100644 index 496f0b7a..00000000 --- a/src/components/seal-table/table-context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const TableContext = React.createContext({}); - -export default TableContext; diff --git a/src/components/seal-table/types.ts b/src/components/seal-table/types.ts deleted file mode 100644 index 5edb9633..00000000 --- a/src/components/seal-table/types.ts +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; - -export type OnSortFn = ( - order: { - columnKey: string; - field: string; - order: 'ascend' | 'descend' | null; - }, - sorter: boolean | { multiple?: number } -) => void; - -export interface CellContentProps { - dataIndex: string; - render?: (text: any, record: any) => React.ReactNode; - editable?: - | boolean - | { - valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time'; - title?: React.ReactNode; - }; -} - -export interface SealColumnProps { - title: React.ReactNode; - render?: (text: any, record: any) => React.ReactNode; - dataIndex: string; - key?: string; - dataField?: string; // Added dataField property, aviods conflict with dataIndex, because dataIndex maybe used in sorting - width?: number; - span: number; - align?: 'left' | 'center' | 'right'; - headerStyle?: React.CSSProperties; - sorter?: boolean | { multiple?: number }; - defaultSortOrder?: 'ascend' | 'descend'; - editable?: - | boolean - | { - valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time'; - title?: React.ReactNode; - }; - valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time'; - sortOrder?: 'ascend' | 'descend' | null; - [key: string]: any; -} - -export interface TableHeaderProps { - showSorterTooltip?: boolean; - sorterList?: TableOrder | Array; - sorter?: boolean | { multiple?: number }; - sortDirections?: ('ascend' | 'descend' | null)[]; - defaultSortOrder?: 'ascend' | 'descend' | null; - sortOrder?: 'ascend' | 'descend' | null; - dataIndex: string; - onSort?: OnSortFn; - title: React.ReactNode; - style?: React.CSSProperties; - firstCell?: boolean; - lastCell?: boolean; - align?: 'left' | 'center' | 'right'; - width?: number | string; - sortedDataIndexList?: Array<{ - columnKey: string; - field: string; - order: 'ascend' | 'descend' | null; - }>; -} - -export interface RowSelectionProps { - selectedRowKeys: React.Key[]; - selectedRows: any[]; - enableSelection: boolean; - removeSelectedKeys: (rowKeys: React.Key[]) => void; - onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => void; -} - -export type TableOrder = { - columnKey?: string; - field?: string; - order: 'ascend' | 'descend' | null; -}; -export interface SealTableProps { - showSorterTooltip?: boolean; - sortDirections?: ('ascend' | 'descend' | null)[]; - columns?: SealColumnProps[]; - childParentKey?: string; - expandedRowKeys?: React.Key[]; - rowSelection?: RowSelectionProps; - children?: React.ReactElement[]; - empty?: React.ReactNode; - expandable?: React.ReactNode; - dataSource: any[]; - pollingChildren?: boolean; - watchChildren?: boolean; - loading?: boolean; - loadend?: boolean; - onCell?: (record: any, extra: any) => void; - onTableSort?: (order: TableOrder | Array) => void; - onExpand?: (expanded: boolean, record: any, rowKey: any) => void; - onExpandAll?: (expanded: boolean) => void; - renderChildren?: ( - data: any, - options: { parent?: any; [key: string]: any } - ) => React.ReactNode; - loadChildren?: (record: any, options?: any) => Promise; - loadChildrenAPI?: (record: any) => string; - contentRendered?: () => void; - rowKey: string; -} - -export interface RowContextProps { - record: Record; - pollingChildren?: boolean; - rowIndex: number; -} diff --git a/src/components/seal-table/use-sorter.ts b/src/components/seal-table/use-sorter.ts deleted file mode 100644 index 12642313..00000000 --- a/src/components/seal-table/use-sorter.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { isBoolean } from 'lodash'; -import { useRef, useState } from 'react'; -import { SealColumnProps, TableOrder } from './types'; - -const initSorterList = (columns: SealColumnProps[]) => { - const list = columns.filter((col) => col.defaultSortOrder); - if (list.length === 0) { - return []; - } - - return list.map((col) => ({ - columnKey: col.key || col.dataIndex, - field: col.dataIndex, - order: col.defaultSortOrder || null - })); -}; - -export default function useSorter(options: { - onTableSort?: (TableOrder: TableOrder | Array) => void; - columns?: SealColumnProps[]; -}) { - const { onTableSort, columns } = options; - const sorterListRef = useRef>( - initSorterList(columns || []) - ); - const [sorterList, setSorterList] = useState>( - initSorterList(columns || []) - ); - - const handleOnTableSort = ( - order: TableOrder, - sorter: boolean | { multiple?: number } - ) => { - let currentOrder: TableOrder = { ...order }; - - if (order.order === null) { - currentOrder = { - columnKey: undefined, - field: undefined, - order: null - }; - } - // single column sort - if (isBoolean(sorter)) { - setSorterList(currentOrder); - sorterListRef.current = currentOrder; - - onTableSort?.(sorterListRef.current); - return; - } - - // multi column sort - if (sorter && typeof sorter === 'object' && sorter.multiple) { - if (!Array.isArray(sorterListRef.current)) { - if ( - sorterListRef.current.columnKey === currentOrder.columnKey || - sorterListRef.current.field === currentOrder.field - ) { - // remove the sorter if order is null - sorterListRef.current = { - ...currentOrder - }; - } else { - sorterListRef.current = [sorterListRef.current, { ...currentOrder }]; - } - } else if (Array.isArray(sorterListRef.current)) { - const existingIndex = sorterListRef.current.findIndex( - (item) => - item.columnKey === order.columnKey || item.field === order.field - ); - - if (existingIndex !== -1) { - sorterListRef.current.splice(existingIndex, 1); - sorterListRef.current.push({ ...currentOrder }); - } else { - sorterListRef.current.push({ ...currentOrder }); - } - } - } - setSorterList(sorterListRef.current); - - onTableSort?.(sorterListRef.current); - }; - - return { - sorterListRef, - sorterList, - handleOnTableSort - }; -} diff --git a/src/components/segment-line/index.tsx b/src/components/segment-line/index.tsx deleted file mode 100644 index d9f7cb9b..00000000 --- a/src/components/segment-line/index.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Segmented, type SegmentedProps } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -interface ThemeType { - color: string; - itemColorHover: string; - itemSelectedColor: string; - thumbBgColor: string; - fontWeight: number; -} -interface SegmentLineProps extends SegmentedProps { - height?: number; - theme?: 'dark' | 'light'; - showTitle?: boolean; -} - -const darkTheme: ThemeType = { - color: 'var(--color-white-tertiary)', - itemColorHover: 'var(--color-white-secondary)', - itemSelectedColor: 'var(--color-white-primary)', - thumbBgColor: 'var(--color-white-primary)', - fontWeight: 400 -}; - -const lightTheme: ThemeType = { - color: 'var(--ant-segmented-item-color)', - itemColorHover: 'var(--ant-segmented-item-hover-color)', - itemSelectedColor: 'var(--ant-segmented-item-selected-color)', - thumbBgColor: 'var(--ant-color-primary)', - fontWeight: 500 -}; - -const SegmentedQwrapper = styled.div<{ - $height: number; - $theme: ThemeType; -}>` - .ant-segmented.segment-line { - padding: 0; - background-color: transparent; - color: ${(props) => props.$theme.color}; - font-weight: ${(props) => props.$theme.fontWeight}; - border: none; - box-shadow: none; - height: ${(props) => props.$height}px; - display: flex; - align-items: center; - .ant-segmented-group { - gap: 16px; - height: 100%; - } - .ant-segmented-item:hover { - color: ${(props) => props.$theme.itemColorHover}; - } - .ant-segmented-thumb { - height: 2px; - padding: 0px; - bottom: 0px; - top: unset; - background-color: ${(props) => props.$theme.thumbBgColor} !important; - } - .ant-segmented-item-label { - display: flex; - align-items: center; - padding: 0; - font-size: var(--font-size-small); - } - .ant-segmented-item { - background-color: transparent; - padding-bottom: 2px; - display: flex; - align-items: center; - &::after { - background-color: ${(props) => props.$theme.thumbBgColor} !important; - width: 100%; - height: 0px; - border-radius: 2px; - bottom: 0px; - top: unset; - opacity: 1; - } - } - - .ant-segmented-item-selected { - background-color: transparent; - box-shadow: none; - color: ${(props) => props.$theme.itemSelectedColor}; - &::after { - height: 2px; - opacity: 1; - } - } - } - &.with-title .ant-segmented.segment-line { - .ant-segmented-item.ant-segmented-item-selected::after { - display: none; - } - } -`; - -const SegmentLine: React.FC = (props) => { - const { - height = 32, - size = 'small', - options = [], - className = 'segment-line', - theme = 'dark', - showTitle = false, - ...rest - } = props; - - return ( - - - - ); -}; - -export default SegmentLine; diff --git a/src/components/short-cuts/index.less b/src/components/short-cuts/index.less deleted file mode 100644 index c409b0a2..00000000 --- a/src/components/short-cuts/index.less +++ /dev/null @@ -1,42 +0,0 @@ -.short-cuts { - margin-bottom: 10px; - - .ant-table-container .ant-table-content table tr > td { - height: auto; - border-bottom: var(--ant-line-width) var(--ant-line-type) - var(--ant-table-border-color); - } - - .ant-table-container .ant-table-content table { - border-spacing: 0; - - .ant-table-tbody .ant-table-row { - background: transparent; - } - - tr > td:first-child { - border-radius: 0; - } - - tr > td:last-child { - border-radius: 0; - } - - .ant-table-thead > tr > th { - background-color: var(--ant-color-fill-tertiary); - height: 36px; - padding-block: 0; - } - } - - .ant-table-fixed-header { - // .ant-table-body { - // overflow-y: auto !important; - // } - .ant-table-thead > tr > th { - background-color: var(--ant-color-fill-tertiary); - height: 36px; - padding-block: 0; - } - } -} diff --git a/src/components/short-cuts/index.tsx b/src/components/short-cuts/index.tsx deleted file mode 100644 index 0012bfb9..00000000 --- a/src/components/short-cuts/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { SearchOutlined } from '@ant-design/icons'; -import { Input, Table, Tag } from 'antd'; -import _ from 'lodash'; -import React from 'react'; -import './index.less'; -import KeyMapConfig from './keymap'; - -const ShortCuts: React.FC<{ intl: any }> = ({ intl }) => { - const [dataList, setDataList] = React.useState(KeyMapConfig); - - const columns = [ - { - title: 'Scope', - dataIndex: 'scope', - key: 'scope', - width: 160 - }, - { - title: 'Action', - dataIndex: 'command', - key: 'command', - render: (text: string, row: any) => { - return {intl.formatMessage({ id: text })}; - } - }, - { - title: 'Keybinding', - dataIndex: 'keybinding', - key: 'keybinding', - width: 180, - render: (text: string, row: any) => { - return {row.keybinding}; - } - } - ]; - - const handleInputChange = (e: any) => { - const value = e.target.value; - const list = _.filter(KeyMapConfig, (item: any) => { - return ( - item.command.toLowerCase().includes(value.toLowerCase()) || - item.scope.toLowerCase().includes(value.toLowerCase()) - ); - }); - setDataList(list); - }; - - const debounceHandleInputChange = _.debounce(handleInputChange, 300); - return ( -
    -

    - {intl.formatMessage({ id: 'shortcuts.title' })} -

    - - - - } - > - record.command} - columns={columns} - dataSource={dataList} - pagination={false} - scroll={{ y: 450 }} - >
    -
    - ); -}; - -export const modalConfig = { - icon: null, - centered: false, - mask: { - closeable: true - }, - footer: null, - style: { - top: '10%' - }, - width: 700 -}; - -export default ShortCuts; diff --git a/src/components/short-cuts/keymap.ts b/src/components/short-cuts/keymap.ts deleted file mode 100644 index 1415b980..00000000 --- a/src/components/short-cuts/keymap.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { KeyMap } from '@/config/hotkeys'; -export default [ - { - scope: 'playground', - command: 'shortcuts.playground.newmessage', - keybinding: KeyMap.CREATE.iconKeybinding - }, - { - scope: 'playground', - command: 'shortcuts.playground.clearmessage', - keybinding: KeyMap.CLEAR.iconKeybinding - }, - { - scope: 'playground', - command: 'shortcuts.playground.toggleparams', - keybinding: KeyMap.RIGHT.iconKeybinding - }, - { - scope: 'models', - // span: { - // rowSpan: 3, - // colSpan: 1 - // }, - command: 'shortcuts.models.newmodelHF', - keybinding: KeyMap.NEW1.iconKeybinding - }, - { - scope: 'models', - command: 'shortcuts.models.newmodelLM', - keybinding: KeyMap.NEW2.iconKeybinding - // span: { - // rowSpan: 0, - // colSpan: 0 - // } - }, - { - scope: 'models', - command: 'shortcuts.models.search', - keybinding: KeyMap.SEARCH.iconKeybinding - // span: { - // rowSpan: 0, - // colSpan: 0 - // } - }, - { - scope: 'resources', - command: 'shortcuts.resources.addworker', - keybinding: KeyMap.CREATE.iconKeybinding - }, - { - scope: 'API keys', - command: 'shortcuts.apikeys.new', - keybinding: KeyMap.CREATE.iconKeybinding - }, - { - scope: 'users', - command: 'shortcuts.users.new', - keybinding: KeyMap.CREATE.iconKeybinding - } -]; diff --git a/src/components/simple-overlay/index.tsx b/src/components/simple-overlay/index.tsx deleted file mode 100644 index 320ac2d1..00000000 --- a/src/components/simple-overlay/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import useUserSettings from '@/hooks/use-user-settings'; -import React, { useEffect } from 'react'; -import SimpleBar from 'simplebar-react'; -import 'simplebar-react/dist/simplebar.min.css'; -import styled from 'styled-components'; - -const Wrapper = styled.div` - .simplebar-scrollbar::before { - width: var(--scrollbar-size); - background: var(--scrollbar-handle-bg); - } - &.dark { - .simplebar-scrollbar::before { - width: var(--scrollbar-size); - background: var(--scrollbar-handle-light-bg); - } - } -`; - -interface SimpleOverlayProps { - height?: string | number; - children?: React.ReactNode; - style?: React.CSSProperties; - onScrollEnd?: (e: React.UIEvent) => void; - disableTrigger?: boolean; -} - -const SimpleOverlay: React.FC = ({ - height, - children, - style, - disableTrigger, - onScrollEnd -}) => { - const { isDarkTheme } = useUserSettings(); - const simpleBarRef = React.useRef(null); - - useEffect(() => { - // simpleBarRef.current.recalculate(); - - const onScroll = (e: React.UIEvent) => { - const scrollElement = simpleBarRef.current?.getScrollElement(); - const scrollHeight = scrollElement.scrollHeight; - const clientHeight = scrollElement.clientHeight; - const scrollTop = scrollElement.scrollTop; - - // Check if the scrollbar is at the bottom 20px for buffer - const isAtBottom = scrollHeight - clientHeight - scrollTop <= 20; - if (isAtBottom && !disableTrigger) { - onScrollEnd?.(e); - } - }; - simpleBarRef.current - ?.getScrollElement() - .addEventListener('scroll', onScroll); - - return () => { - simpleBarRef.current - ?.getScrollElement() - .removeEventListener('scroll', onScroll); - }; - }, [height, disableTrigger, onScrollEnd]); - return ( - - - {children} - - - ); -}; - -export default SimpleOverlay; diff --git a/src/components/simple-table/cell.tsx b/src/components/simple-table/cell.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/simple-table/header.tsx b/src/components/simple-table/header.tsx deleted file mode 100644 index 793fb71b..00000000 --- a/src/components/simple-table/header.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useIntl } from '@umijs/max'; -import React from 'react'; - -interface TableHeaderProps { - columns: any[]; -} -interface TableHeaderProps { - columns: any[]; -} -const TableHeader = ({ columns }: TableHeaderProps) => { - const intl = useIntl(); - return ( - - {columns.map((column: any, index: number) => { - return ( - - - {column.locale - ? intl.formatMessage({ id: column.title }) - : column.title} - - - ); - })} - - ); -}; -export default React.memo(TableHeader); diff --git a/src/components/simple-table/index.less b/src/components/simple-table/index.less deleted file mode 100644 index 75f13f3c..00000000 --- a/src/components/simple-table/index.less +++ /dev/null @@ -1,50 +0,0 @@ -.simple-table { - table-layout: auto; - width: 100%; - text-align: left; - - &.simple-table-bordered { - border-collapse: collapse; - border-spacing: 0; - - td { - border-bottom: 1px solid var(--color-white-light-1); - } - - th { - border-bottom: 1px solid var(--color-white-light-1); - } - } - - th { - height: 36px; - font-weight: 600; - } - - td { - color: var(--color-white-quaternary); - } - - .cell-span { - display: flex; - padding: 6px; - min-height: 32px; - } - - .cell-header { - font-weight: var(--font-weight-medium); - font-size: 13px; - } - - &.light { - th { - color: var(--ant-color-text); - border-bottom: 1px solid var(--ant-color-split); - } - - td { - color: var(--ant-color-text); - border-bottom: 1px solid var(--ant-color-split); - } - } -} diff --git a/src/components/simple-table/index.tsx b/src/components/simple-table/index.tsx deleted file mode 100644 index 4f8c53a0..00000000 --- a/src/components/simple-table/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import 'simplebar-react/dist/simplebar.min.css'; -import TableHeader from './header'; -import './index.less'; -import TableRow from './row'; - -export interface ColumnProps { - title: string; - key: string; - width?: string | number; - style?: React.CSSProperties; - render?: (data: { - dataIndex: string; - dataList?: any[]; - row: any; - rowIndex?: number; - colIndex?: number; - }) => any; - locale?: boolean; - colSpan?: (params: { - row: any; - rowIndex: number; - colIndex: number; - dataIndex: string; - dataList: any[]; - }) => number; - rowSpan?: (params: { - row: any; - rowIndex: number; - colIndex: number; - dataIndex: string; - dataList: any[]; - }) => number; -} - -interface SimpleTableProps { - theme?: 'dark' | 'light'; - maxHeight?: number | string; - columns: ColumnProps[]; - dataSource: any[]; - bordered?: boolean; - rowKey: string; -} -const SimpleTabel: React.FC = (props) => { - const { columns, dataSource, rowKey, theme, bordered = true } = props; - - return ( -
    - - - - - - {dataSource.map((item: any, index: number) => { - return ( - - ); - })} - -
    -
    - ); -}; - -export default SimpleTabel; diff --git a/src/components/simple-table/info-column.tsx b/src/components/simple-table/info-column.tsx deleted file mode 100644 index 2e36c290..00000000 --- a/src/components/simple-table/info-column.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; - -import { useIntl } from '@umijs/max'; -import { Divider } from 'antd'; - -interface InfoColumnProps { - fieldList: { - label: string; - key: string; - locale?: boolean; - render?: (val: any, data: any) => any; - }[]; - style?: React.CSSProperties; - data: Record; -} -const InfoColumn: React.FC = (props) => { - const { data, fieldList, style } = props; - - const intl = useIntl(); - - return ( - - {fieldList.map((item, index) => { - return ( - - - - {' '} - {item.locale - ? intl.formatMessage({ id: item.label }) - : item.label} - - - {' '} - {item.render?.(data[item.key], data) ?? data[item.key]} - - - {index < fieldList.length - 1 ? ( - - ) : null} - - ); - })} - - ); -}; - -export default InfoColumn; diff --git a/src/components/simple-table/row.tsx b/src/components/simple-table/row.tsx deleted file mode 100644 index 330ca1dc..00000000 --- a/src/components/simple-table/row.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useMemo } from 'react'; - -interface TableRowProps { - row: any; - columns: any; - rowIndex: number; - dataList: any[]; -} - -interface TableCellProps { - row: any; - column: any; - rowIndex: number; - colIndex: number; - dataList: any[]; -} -const TableCell: React.FC = (props: TableCellProps) => { - const { row, column, rowIndex, colIndex, dataList } = props; - - const renderContent = useMemo(() => { - return column.render - ? column.render({ - dataIndex: column.key, - dataList: dataList, - row: row, - rowIndex, - colIndex: colIndex - }) - : row[column.key]; - }, [column, row, rowIndex, colIndex, dataList]); - - if (renderContent === null && (column.colSpan || column.rowSpan)) { - return null; - } - - return ( - - - {column.render - ? column.render({ - dataIndex: column.key, - dataList: dataList, - row: row, - rowIndex, - colIndex: colIndex - }) - : row[column.key]} - - - ); -}; - -const TableRow = ({ row, columns, rowIndex, dataList }: TableRowProps) => { - return ( - - {columns.map((column: any, index: number) => { - return ( - - ); - })} - - ); -}; - -export default TableRow; diff --git a/src/components/speech-content/audio-player.tsx b/src/components/speech-content/audio-player.tsx deleted file mode 100644 index a85738b2..00000000 --- a/src/components/speech-content/audio-player.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import _ from 'lodash'; -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef -} from 'react'; -import WaveSurfer, { WaveSurferOptions } from 'wavesurfer.js'; - -interface AudioPlayerProps { - autoplay: boolean; - audioUrl: string; - speed: number; - ref?: any; - height?: number; - width?: number; - onReady?: (duration: number) => void; - onClick?: (value: number) => void; - onFinish?: () => void; - onAnalyse?: (analyseData: any, frequencyBinCount: any) => void; - onAudioprocess?: (current: number) => void; - onPlay?: () => void; - onPause?: () => void; -} - -const AudioPlayer: React.FC< - AudioPlayerProps & Omit -> = forwardRef((props, ref) => { - const { autoplay, audioUrl, speed = 1, ...rest } = props; - const wavesurfer = useRef(null); - const container = useRef(null); - const audioContext = useRef(null); - const analyser = useRef(null); - const dataArray = useRef(null); - const mediaElement = useRef(null); - - const initAudioContext = useCallback(() => { - audioContext.current = new ( - window.AudioContext || window.webkitAudioContext - )(); - - analyser.current = audioContext.current.createAnalyser(); - analyser.current.fftSize = 512; - dataArray.current = new Uint8Array(analyser.current.frequencyBinCount); - }, []); - - const generateVisualData = useCallback(() => { - const source = audioContext.current.createMediaElementSource( - mediaElement.current - ); - source.connect(analyser.current); - analyser.current.connect(audioContext.current.destination); - }, []); - - const debouncePause = _.debounce(() => { - if (wavesurfer.current?.isPlaying()) { - wavesurfer.current?.pause(); - } - }, 100); - - const listenEvents = () => { - wavesurfer.current?.on('ready', (duration: number) => { - props.onReady?.(duration); - }); - - wavesurfer.current?.on('click', (value) => { - props.onClick?.(value); - }); - wavesurfer.current?.on('finish', () => { - props.onFinish?.(); - }); - wavesurfer.current?.on('audioprocess', (current: number) => { - props.onAudioprocess?.(current); - }); - wavesurfer.current?.on('seeking', (current: number) => { - console.log('seeking', current); - }); - - wavesurfer.current?.on('timeupdate', (current: number) => { - console.log('timeupdate', current); - }); - - wavesurfer.current?.on('play', () => { - analyser.current?.getByteFrequencyData(dataArray.current); - props.onAnalyse?.(dataArray.current, analyser); - props.onPlay?.(); - }); - - wavesurfer.current?.on('pause', () => { - analyser.current?.getByteFrequencyData(dataArray.current); - props.onAnalyse?.(dataArray.current, analyser); - props.onPause?.(); - }); - }; - - const createWavesurfer = () => { - wavesurfer.current?.destroy(); - wavesurfer.current = WaveSurfer.create({ - container: container.current, - url: audioUrl, - autoplay: autoplay, - audioRate: speed, - waveColor: '#4096ff', - progressColor: 'rgb(100, 0, 100)', - height: 60, - barWidth: 2, - barGap: 1, - barRadius: 2, - interact: true, - cursorWidth: 0, - ...rest - }); - - mediaElement.current = wavesurfer.current?.getMediaElement(); - - initAudioContext(); - generateVisualData(); - listenEvents(); - }; - - const destroyWavesurfer = () => { - if (wavesurfer.current) { - wavesurfer.current.destroy(); - } - }; - - const play = async () => { - if (wavesurfer.current) { - wavesurfer.current.play(); - } - }; - const isPlaying = () => { - if (wavesurfer.current) { - wavesurfer.current.isPlaying(); - } - }; - - const playPause = () => { - if (wavesurfer.current) { - wavesurfer.current.playPause(); - } - }; - - const debounceSeekTo = _.debounce((value: number) => { - if (wavesurfer.current) { - wavesurfer.current.seekTo(value); - } - }, 50); - - const debounceSetTime = _.debounce((value: number) => { - if (wavesurfer.current) { - wavesurfer.current.setTime(value); - } - }, 50); - - const seekTo = (value: number) => { - if (wavesurfer.current) { - debounceSeekTo(value); - } - }; - const setTime = (value: number) => { - if (wavesurfer.current) { - debounceSetTime(value); - } - }; - - const seekAndPlay = (value: number) => { - if (wavesurfer.current) { - wavesurfer.current.seekTo(value); - wavesurfer.current.once('seeking', () => { - wavesurfer.current - ?.play() - .catch((error) => console.error('Playback error:', error)); - }); - } - }; - - const duration = () => { - if (wavesurfer.current) { - return wavesurfer.current.getDuration(); - } - return 0; - }; - - const pause = () => { - if (wavesurfer.current) { - wavesurfer.current.pause(); - } - }; - - useImperativeHandle(ref, () => { - return { - play, - pause, - duration, - seekTo, - setTime, - playPause, - isPlaying, - wavesurfer - }; - }); - - useEffect(() => { - if (container.current && audioUrl) { - createWavesurfer(); - } - return () => { - destroyWavesurfer(); - }; - }, [audioUrl, container.current]); - return ( -
    - ); -}); - -export default AudioPlayer; diff --git a/src/components/speech-content/index.tsx b/src/components/speech-content/index.tsx deleted file mode 100644 index 6410954e..00000000 --- a/src/components/speech-content/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import SpeechItem from './speech-item'; - -interface SpeechContentProps { - dataList: any[]; - loading?: boolean; - onPlay?: () => void; - onPause?: () => void; - playerRef?: React.RefObject; - isPlaying?: boolean; - isStream?: boolean; - analyserData?: { - data: Uint8Array; - analyser: any; - }; -} - -const SpeechContent: React.FC = (props) => { - return ( -
    - {props.dataList.map((item) => ( - - ))} -
    - ); -}; - -export default SpeechContent; diff --git a/src/components/speech-content/speech-item.tsx b/src/components/speech-content/speech-item.tsx deleted file mode 100644 index 7c8afef6..00000000 --- a/src/components/speech-content/speech-item.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import AudioAnimation from '@/components/audio-animation'; -import { - DownloadOutlined, - PauseCircleOutlined, - PlayCircleOutlined -} from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Slider, Tooltip } from 'antd'; -import dayjs from 'dayjs'; -import _, { throttle } from 'lodash'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react'; -import RawAudioPlayer from '../audio-player/raw-audio-player'; -import './styles/index.less'; -import './styles/slider-progress.less'; - -const audioFormat = { - 'audio/mpeg': 'mp3', - 'audio/wav': 'wav', - 'audio/ogg': 'ogg', - 'audio/webm': 'webm', - 'audio/aac': 'aac', - 'audio/x-flac': 'flac', - 'audio/pcm': 'pcm', - 'audio/flac': 'flac', - 'audio/x-wav': 'wav', - 'audio/L16': 'pcm', - 'audio/opus': 'opus' -}; - -interface SpeechContentProps { - prompt: string; - autoplay: boolean; - voice: string; - format: string; - speed: number; - audioUrl: string; - onPlay?: () => void; - onPause?: () => void; - isStream?: boolean; - isPlaying?: boolean; - playerRef?: React.RefObject; - analyserData?: { - data: Uint8Array; - analyser: any; - }; -} -const SpeechItem: React.FC = (props) => { - const { isStream } = props; - console.log( - 'Rendering SpeechItem with props:', - props.isStream, - props.analyserData - ); - const intl = useIntl(); - const [isPlay, setIsPlay] = useState(props.autoplay || props.isPlaying); - const [duration, setDuration] = useState(0); - const [animationSize, setAnimationSize] = useState({ width: 900, height: 0 }); - const [currentTime, setCurrentTime] = useState(0); - const [audioChunks, setAudioChunks] = useState({ - data: new Uint8Array(128), - analyser: null - }); - const wrapper = useRef(null); - const ref = useRef(null); - - useEffect(() => { - setIsPlay(props.autoplay || props.isPlaying); - }, [props.autoplay, props.isPlaying]); - - const isPCMStream = useMemo(() => { - return props.audioUrl?.startsWith('pcm-stream://'); - }, [props.audioUrl]); - - // Sync internal ref with external playerRef if provided - React.useEffect(() => { - if (props.playerRef) { - (props.playerRef as any).current = ref.current; - } - }, [props.playerRef, ref.current]); - - const onPause = () => { - ref.current?.pause(); - props.onPause?.(); - }; - - const onPlay = async () => { - await ref.current?.wavesurfer.current?.play(); - props.onPlay?.(); - }; - - const handlePlay = async () => { - try { - if (ref.current?.wavesurfer.current?.isPlaying()) { - onPause(); - setIsPlay(false); - return; - } else { - await onPlay(); - setIsPlay(true); - } - } catch (error) { - console.log('error:', error); - } - }; - - const handleOnAnalyse = useCallback((data: any, analyser: any) => { - setAudioChunks((pre: any) => { - return { - data: data, - analyser: analyser - }; - }); - }, []); - - const handleOnFinish = useCallback(() => { - setIsPlay(false); - }, []); - - const handleOnPlay = useCallback(() => { - setIsPlay(true); - }, []); - - const handleOnPause = useCallback(() => { - setIsPlay(false); - }, []); - - const throttleUpdateCurrentTime = throttle((current: number) => { - setCurrentTime(current); - }, 100); - - const handleOnAudioprocess = (current: number) => { - throttleUpdateCurrentTime(current); - }; - - const handleAnimationResize = useCallback((size: any) => { - setAnimationSize({ - width: size.width, - height: size.height - }); - }, []); - - const debounceSeek = _.debounce((value: number) => { - ref.current?.seekTo(value / duration); - setCurrentTime(value); - }, 200); - - const handleSliderChange = (value: number) => { - debounceSeek(value); - }; - - const handleReady = useCallback((duration: number) => { - setDuration(duration); - }, []); - - const handlOnChangeComplete = useCallback((value: number) => { - ref.current?.seekTo(value / duration); - setCurrentTime(value); - }, []); - - const convertFormat = () => { - if (props.format === 'pcm') { - return 'wav'; - } - return props.format; - }; - - const onDownload = useCallback(() => { - const url = props.audioUrl || ''; - const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${convertFormat()}`; - - const link = document.createElement('a'); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - link.remove(); - }, [props.audioUrl, props.format]); - - console.log('isPCMStream:', isPCMStream, isPlay); - - console.log('props.analyserData:', props.analyserData); - - const renderPlayerActions = () => { - if (isPCMStream) { - return null; - } - return ( -
    - -
    - - {props.format} - - - {_.round(currentTime, 2) || _.round(duration, 2)} - -
    - - - - - - -
    -
    -
    - ); - }; - - return ( -
    -
    -
    - <> - {/* {!isPCMStream && ( - - )} */} - {!isPCMStream && ( - - )} - {isPlay && - (props.analyserData?.analyser?.current || - audioChunks.analyser?.current) && ( - - )} - -
    -
    - {renderPlayerActions()} -
    - ); -}; - -export default SpeechItem; diff --git a/src/components/speech-content/styles/index.less b/src/components/speech-content/styles/index.less deleted file mode 100644 index 9d597344..00000000 --- a/src/components/speech-content/styles/index.less +++ /dev/null @@ -1,105 +0,0 @@ -.speech-item { - display: flex; - justify-content: flex-start; - align-items: center; - width: 100%; - - .voice { - width: 80px; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 5px; - - .text { - display: flex; - padding: 2px 4px; - border-radius: 4px; - border: 1px solid var(--ant-color-border); - } - } - - .wrapper { - flex: 1; - display: flex; - justify-content: flex-start; - align-items: center; - - .audio-container { - flex: 1; - } - - .format { - display: flex; - padding-left: 5px; - } - } - - audio { - flex: 1; - } -} - -.prompt-box { - padding-left: 80px; - - .prompt { - margin-top: 16px; - padding: 10px; - border-radius: var(--border-radius-base); - background-color: var(--ant-color-fill-quaternary); - } -} - -.speech-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 10px; - // padding-left: 80px; - - .actions { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 15px; - - .anticon { - font-size: 14px; - } - } - - .duration { - display: flex; - justify-content: center; - width: 100px; - } - - .tags { - display: flex; - justify-content: flex-start; - align-items: center; - - .item { - display: flex; - justify-content: center; - align-items: center; - padding: 2px; - border-radius: 4px; - color: var(--ant-color-text-tertiary); - } - - .splitor { - display: flex; - width: 1px; - height: 1px; - border-radius: 2px; - margin: 0 8px; - background-color: var(--ant-color-fill-content-hover); - } - } -} - -.audio-container { - pointer-events: none; -} diff --git a/src/components/speech-content/styles/slider-progress.less b/src/components/speech-content/styles/slider-progress.less deleted file mode 100644 index 1db18ce7..00000000 --- a/src/components/speech-content/styles/slider-progress.less +++ /dev/null @@ -1,21 +0,0 @@ -.slider-progress { - &:hover { - .ant-slider-track { - background-color: var(--ant-blue-5); - } - } - - .ant-slider-rail { - border-radius: var(--border-radius-base); - height: 2px; - } - - .ant-slider-track { - background-color: var(--ant-blue-5); - height: 2px; - } - - .ant-slider-handle { - display: none; - } -} diff --git a/src/components/status-icon/error.tsx b/src/components/status-icon/error.tsx deleted file mode 100644 index dce49ac6..00000000 --- a/src/components/status-icon/error.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { CloseOutlined } from '@ant-design/icons'; -import { createStyles } from 'antd-style/lib/functions'; - -const useStyles = createStyles(({ token, css }) => ({ - errorIcon: css` - display: flex; - align-items: center; - justify-content: center; - width: 64px; - height: 64px; - border-radius: 50%; - background-color: ${token.colorErrorBgActive}; - color: ${token.colorError}; - ` -})); - -const ErrorIcon: React.FC<{ size?: number }> = ({ size }) => { - const { styles } = useStyles(); - return ( -
    - -
    - ); -}; - -export default ErrorIcon; diff --git a/src/components/status-tag/copy-btn.less b/src/components/status-tag/copy-btn.less deleted file mode 100644 index f0b3b9c1..00000000 --- a/src/components/status-tag/copy-btn.less +++ /dev/null @@ -1,27 +0,0 @@ -:local(.status-content-wrapper) { - &:hover { - :global(.copy-button-wrapper) { - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - } - } - - :global { - .copy-button-wrapper { - display: none; - background-color: rgba(255, 255, 255, 20%); - position: absolute; - z-index: 10; - right: 0; - top: 0; - padding: 2px; - border-radius: 0 4px; - } - - .simplebar-content { - width: max-content; - } - } -} diff --git a/src/components/status-tag/index.less b/src/components/status-tag/index.less deleted file mode 100644 index 22954e9a..00000000 --- a/src/components/status-tag/index.less +++ /dev/null @@ -1,53 +0,0 @@ -.status-tag { - position: relative; - display: flex; - justify-content: center; - align-items: center; - padding-inline: 7px; - border-radius: 20px; - min-width: 76px; - width: max-content; - height: 24px; - font-size: 12px; - font-weight: 400; - overflow: hidden; - - .txt { - display: flex; - align-items: center; - gap: 4px; - - &.err { - cursor: default; - } - } - - &.download { - border: 1px solid var(--ant-color-success-border-hover) !important; - } - - .download { - display: flex; - justify-content: center; - align-items: center; - position: absolute; - left: 0; - top: 0; - bottom: 0; - height: 100%; - background-color: var(--ant-color-success-border); - } - - .progress { - position: relative; - z-index: 10; - color: var(--ant-color-success); - } -} - -.tooltip-scrollbar { - .simplebar-scrollbar::before { - width: var(--scrollbar-size); - background: var(--scrollbar-handle-light-bg); - } -} diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx deleted file mode 100644 index 6728666d..00000000 --- a/src/components/status-tag/index.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { StatusColorMap } from '@/config'; -import { StatusType } from '@/config/types'; -import { InfoCircleOutlined } from '@ant-design/icons'; -import { Button, Divider, Tooltip } from 'antd'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React, { useMemo } from 'react'; -import 'simplebar-react/dist/simplebar.min.css'; -import styled from 'styled-components'; -import CopyButton from '../copy-button'; -import { TooltipOverlayScroller } from '../overlay-scroller'; -import CopyStyle from './copy-btn.less'; -import './index.less'; - -const Text = styled.span` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const linkReg = /(.*?)<\/a>/g; -export const StatusMaps = { - transitioning: 'blue', - error: 'red', - warning: 'orange', - success: 'success', - inactive: 'inactive' -}; - -type StatusTagProps = { - style?: React.CSSProperties; - statusValue: { - status: StatusType; - text: string; - message?: string; - }; - type?: 'tag' | 'circle'; - download?: { - percent: number; - }; - suffix?: React.ReactNode; - maxTooltipWidth?: number; - extra?: React.ReactNode; - actions?: { - label: string; - icon?: React.ReactNode; - key: string; - onClick?: () => void; - render?: () => React.ReactNode; - }[]; -}; - -const StatusTag: React.FC = ({ - style, - statusValue, - download, - extra, - actions = [], - maxTooltipWidth = 250, - suffix -}) => { - const { text, status } = statusValue; - - const statusColor = useMemo<{ - text: string; - bg: string; - border?: string; - }>(() => { - return StatusColorMap[status]; - }, [status]); - - const hasLink = useMemo(() => { - if (!statusValue.message) return false; - return linkReg.test(statusValue.message || ''); - }, [statusValue.message]); - - const statusMessage = useMemo(() => { - if (!statusValue.message) return ''; - const link = statusValue.message?.match(linkReg); - if (link) { - return statusValue.message?.replace( - linkReg, - `$2` - ); - } - return statusValue.message; - }, [statusValue.message]); - - const renderContent = () => { - const percent = download?.percent || 0; - - if (download && percent > 0 && percent <= 100) { - return ( - <> - - {_.round(download?.percent, 0) || 0}% - - - - ); - } - return {text}; - }; - - const renderTitle = useMemo(() => { - return ( -
    -
    - - {actions?.map((item) => { - return ( -
    - - - - -
    - ); - })} -
    - -
    - {hasLink ? ( - - ) : ( - statusMessage - )} - {extra && {extra}} -
    -
    - ); - }, [statusValue]); - - return ( - - {statusValue.message ? ( - - - - {renderContent()} - {suffix && {suffix}} - - - ) : ( - - {renderContent()} - {suffix && {suffix}} - - )} - - ); -}; - -export default StatusTag; diff --git a/src/components/tags-wrapper/index.less b/src/components/tags-wrapper/index.less deleted file mode 100644 index c87c10b5..00000000 --- a/src/components/tags-wrapper/index.less +++ /dev/null @@ -1,33 +0,0 @@ -.tags-wrapper { - display: flex; - width: 100%; - overflow: hidden; - - .more { - display: flex; - align-items: center; - justify-content: center; - padding: 2px 6px; - border-radius: 4px; - font-size: 12px; - height: 22px; - opacity: 0.7; - } - - .tags-content { - display: flex; - } -} - -.tags-wrapper-dropdown { - .ant-dropdown-menu { - .ant-dropdown-menu-item { - background-color: transparent; - cursor: default; - - &:hover { - background-color: transparent; - } - } - } -} diff --git a/src/components/tags-wrapper/index.tsx b/src/components/tags-wrapper/index.tsx deleted file mode 100644 index 6c85b653..00000000 --- a/src/components/tags-wrapper/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { MoreOutlined } from '@ant-design/icons'; -import { Dropdown, Tag } from 'antd'; -import _ from 'lodash'; -import ResizeObserver from 'rc-resize-observer'; -import React, { useEffect, useRef, useState } from 'react'; -import './index.less'; - -interface TagsWrapperProps { - gap?: number; - dataList: any[]; - renderTag: (item: any, index?: number) => React.ReactNode; -} - -const TagsWrapper: React.FC = (props) => { - const { gap = 0, dataList, renderTag } = props; - const tagsContentRef = useRef(null); - const [hiddenIndices, setHiddenIndices] = useState({ - start: 0, - end: dataList.length - }); - const wrapperRef = useRef(null); - const moreBtnRef = useRef(null); - const moreButtonWidth = useRef(0); - const nodeSizeList = useRef([]); - - const calculateHiddenIndices = () => { - const wrapperWidth = wrapperRef.current?.offsetWidth || 0; - const childNodes = tagsContentRef.current?.childNodes; - let sizeList = nodeSizeList.current; - - if (!sizeList.length && childNodes?.length) { - sizeList = _.map(childNodes, (node: HTMLDivElement, index: number) => { - if (index === childNodes.length - 1) { - return node?.offsetWidth; - } - return node?.offsetWidth + gap; - }); - nodeSizeList.current = sizeList; - } - - if (wrapperWidth === 0 || !sizeList.length) return; - - // cache more button width - if (moreBtnRef.current?.offsetWidth) { - moreButtonWidth.current = moreBtnRef.current?.offsetWidth + gap; - } - - let totalWidth = 0; - let start = 0; - let end = dataList.length; - - for (let i = 0; i < sizeList.length; i++) { - const nodeWidth = sizeList[i]; - - if (totalWidth + moreButtonWidth.current >= wrapperWidth) { - end = i - 1 < 0 ? 0 : i - 1; - break; - } - totalWidth += nodeWidth; - - if (totalWidth >= wrapperWidth) { - end = i; - break; - } - } - - if (hiddenIndices.start !== start || hiddenIndices.end !== end) { - setHiddenIndices({ - start, - end - }); - } - }; - - const handleClick = (data: any) => { - data.domEvent?.stopPropagation(); - }; - - useEffect(() => { - if (tagsContentRef.current) { - calculateHiddenIndices(); - } - }, [dataList, gap]); - - const handleContentResize = _.throttle(() => { - calculateHiddenIndices(); - }, 200); - - return ( - -
    - -
    - {_.map( - _.slice(dataList, hiddenIndices.start, hiddenIndices.end), - (item: any, index: number) => { - return {renderTag?.(item, index)}; - } - )} -
    -
    - {hiddenIndices.end < dataList.length && ( - { - return { - label: renderTag?.(item, index), - key: index, - onClick: handleClick - }; - } - ) - }} - > - - - - - )} -
    -
    - ); -}; - -export default TagsWrapper; diff --git a/src/components/tags-wrapper/more-dropdown.tsx b/src/components/tags-wrapper/more-dropdown.tsx deleted file mode 100644 index 6634cc39..00000000 --- a/src/components/tags-wrapper/more-dropdown.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MoreOutlined } from '@ant-design/icons'; -import { Dropdown, Tag } from 'antd'; -import React from 'react'; - -interface MoreDropdownProps { - items: any[]; -} - -const MoreDropdown: React.FC = (props) => { - const { items } = props; - return ( - <> - {items && items.length > 0 ? ( - - - - - - ) : null} - - ); -}; - -export default React.memo(MoreDropdown); diff --git a/src/components/tags-wrapper/theme-tag.tsx b/src/components/tags-wrapper/theme-tag.tsx deleted file mode 100644 index 81749124..00000000 --- a/src/components/tags-wrapper/theme-tag.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import useUserSettings from '@/hooks/use-user-settings'; -import { Tag, TagProps } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -const TagWrapper = styled(Tag)` - display: flex; - align-items: center; - justify-content: center; - padding: 2px 6px; - border-radius: 4px; - font-size: 12px; - height: 22px; - opacity: 0.7; - margin: 0; -`; - -const ThemeTag: React.FC = ({ - opacity, - style, - children, - ...restProps -}) => { - const { userSettings } = useUserSettings(); - const { isDarkTheme } = userSettings; - return ( - - {children} - - ); -}; - -ThemeTag.displayName = 'ThemeTag'; - -export default ThemeTag; diff --git a/src/components/templates/card-list.tsx b/src/components/templates/card-list.tsx index 6311b588..37974e47 100644 --- a/src/components/templates/card-list.tsx +++ b/src/components/templates/card-list.tsx @@ -1,7 +1,9 @@ -import ResizeContainer from '@/components/resize-container'; -import CardSkeleton from '@/components/templates/card-skelton'; -import InfiniteScroller from '@/pages/_components/infinite-scroller'; -import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context'; +import { + InfiniteScroller, + ResizeContainer, + TemplateCardSkeleton +} from '@gpustack/core-ui'; +import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context'; import { Spin } from 'antd'; import React from 'react'; import styled from 'styled-components'; @@ -49,7 +51,7 @@ const ListSkeleton: React.FC<{ root: 'skelton-wrapper' }} > - {isFirst && } + {isFirst && } )} diff --git a/src/components/templates/card-skelton.tsx b/src/components/templates/card-skelton.tsx deleted file mode 100644 index ffbf81a2..00000000 --- a/src/components/templates/card-skelton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Skeleton } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import ResizeContainer from '../resize-container'; - -const SkeletonWrapper = styled.div` - .ant-skeleton-paragraph { - margin-bottom: 0; - } -`; - -interface CatalogSkeltonProps { - skeletonProps?: any; - skeletonStyle?: React.CSSProperties; -} - -const CardSkelton: React.FC = (props) => { - return ( - ( - - - - )} - > - ); -}; - -export default CardSkelton; diff --git a/src/components/templates/card.tsx b/src/components/templates/card.tsx deleted file mode 100644 index c93edb07..00000000 --- a/src/components/templates/card.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import styled from 'styled-components'; - -interface CardProps { - height?: string | number; - className?: string; - children?: React.ReactNode; - clickable?: boolean; - ghost?: boolean; - header?: React.ReactNode; - footer?: React.ReactNode; - icon?: React.ReactNode; - active?: boolean; - hoverable?: boolean; - disabled?: boolean; - onClick?: () => void; -} - -const CardWrapper = styled.div.attrs({ - className: 'template-card-wrapper' -})` - overflow: hidden; - display: flex; - padding: 16px 16px; - justify-content: flex-start; - align-items: center; - border: 1px solid var(--ant-color-border); - border-radius: var(--ant-border-radius-lg); - cursor: default; - width: 100%; - &.clickable:hover:not(.disabled) { - background-color: var(--ant-color-fill-tertiary); - transition: background-color 0.2s ease; - } - - &.hoverable:hover:not(.disabled) { - background-color: var(--ant-color-fill-tertiary); - transition: background-color 0.2s ease; - } - - &.ghost { - background-color: transparent; - } - - &.active { - background-color: var(--ant-color-fill-tertiary); - } - - &.clickable:not(.disabled) { - cursor: pointer; - } - &.disabled { - cursor: default; - opacity: 0.6; - pointer-events: none; - border-style: dashed; - } -`; - -const CardContent = styled.div.attrs({ - className: 'template-card-content' -})` - width: 100%; - flex: 1; - color: var(--ant-color-text-tertiary); -`; - -const Inner = styled.div.attrs({ - className: 'template-card-inner' -})` - display: flex; - width: 100%; - flex-direction: column; - justify-content: flex-start; - gap: 8px; - height: 100%; -`; - -const Icon = styled.div.attrs({ - className: 'template-card-icon' -})` - display: flex; - align-items: center; - margin-right: 16px; - font-size: 32px; -`; - -const Header = styled.div.attrs({ - className: 'template-card-header' -})` - font-weight: bold; - font-size: var(--font-size-base); - display: flex; - align-items: center; - justify-content: space-between; -`; - -const Card: React.FC = (props) => { - const { - className, - height, - children, - clickable = true, - ghost = false, - header, - footer, - icon, - active, - disabled, - hoverable, - onClick - } = props; - - const handleClick = () => { - if (disabled || !clickable) return; - onClick?.(); - }; - - return ( - - {icon && {icon}} - - {header &&
    {header}
    } - {children && {children}} - {footer} -
    -
    - ); -}; - -export default Card; diff --git a/src/components/theme-toggle/theme-drop-actions.tsx b/src/components/theme-toggle/theme-drop-actions.tsx index fff87157..27558d89 100644 --- a/src/components/theme-toggle/theme-drop-actions.tsx +++ b/src/components/theme-toggle/theme-drop-actions.tsx @@ -1,9 +1,9 @@ import useUserSettings from '@/hooks/use-user-settings'; import { MoonOutlined, SunOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Dropdown } from 'antd'; import { createStyles } from 'antd-style'; -import IconFont from '../icon-font'; const useStyles = createStyles(({ token, css }) => ({ inner: css` diff --git a/src/components/tooltip-list/index.tsx b/src/components/tooltip-list/index.tsx deleted file mode 100644 index f9b1a37a..00000000 --- a/src/components/tooltip-list/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useIntl } from '@umijs/max'; -import React from 'react'; -import styled from 'styled-components'; - -const UL = styled.ul` - list-style: none; - padding-left: 0; - margin: 0; - display: flex; - flex-direction: column; - li { - display: flex; - flex-direction: column; - .title { - font-weight: 600; - color: var(--ant-color-text-light-solid); - } - .content { - color: var(--color-white-tertiary); - display: inline-flex; - } - } -`; - -interface TooltipListProps { - list: { title: any; tips: string }[]; -} - -const TooltipList: React.FC = (props) => { - const intl = useIntl(); - const { list } = props; - return ( -
      - {list.map((item, index: number) => { - return ( -
    • - - {item.title?.locale - ? intl.formatMessage({ id: item.title?.text || '' }) - : item.title} - : - - - {intl.formatMessage({ id: item.tips })} - -
    • - ); - })} -
    - ); -}; - -export default TooltipList; diff --git a/src/components/transition/index.less b/src/components/transition/index.less deleted file mode 100644 index c5114c25..00000000 --- a/src/components/transition/index.less +++ /dev/null @@ -1,45 +0,0 @@ -.transition-wrapper { - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - - &.bordered { - border-width: var(--ant-line-width); - border-style: var(--ant-line-type); - border-color: var(--ant-color-border); - background-color: var(--color-white-1); - } - - &.filled { - border-color: var(--ant-color-border); - - .transition-content-wrapper { - background-color: var(--color-fill-1); - } - } - - .header { - background-color: var(--color-fill-1); - cursor: pointer; - padding: 8px 16px; - } - - .transition-content-wrapper { - overflow: hidden; - transition: all 300ms ease-in-out; - - .transition-content { - height: max-content; - background-color: var(--color-white-1); - border-radius: var(--border-radius-base); - } - } - - .ant-input { - &::-webkit-scrollbar-track { - width: 0 !important; - color: transparent; - } - } -} diff --git a/src/components/transition/index.tsx b/src/components/transition/index.tsx deleted file mode 100644 index 8c611915..00000000 --- a/src/components/transition/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import classNames from 'classnames'; -import { - forwardRef, - useEffect, - useImperativeHandle, - useRef, - useState -} from 'react'; -import './index.less'; - -interface TransitionWrapProps { - minHeight?: number; - header?: React.ReactNode; - variant?: 'bordered' | 'filled'; - children: React.ReactNode; - setCollapsed?: (val: boolean) => void; - ref?: any; -} -const TransitionWrapper: React.FC = forwardRef( - (props, ref) => { - const { - minHeight = 50, - header, - variant = 'bordered', - children, - setCollapsed - } = props; - const [isOpen, setIsOpen] = useState(true); - const [height, setHeight] = useState(0); - const contentRef = useRef(null); - - useEffect(() => { - if (isOpen) { - setHeight(contentRef?.current?.scrollHeight || 0); - } else { - setHeight(0); - } - }, [isOpen]); - - const toggleOpen = () => { - setIsOpen(!isOpen); - setCollapsed?.(!isOpen); - }; - - const setHeightByContent = () => { - setHeight(contentRef?.current?.scrollHeight || 0); - }; - - useImperativeHandle(ref, () => { - return { - setHeightByContent - }; - }); - - return ( -
    -
    - {header} -
    -
    -
    {children}
    -
    -
    - ); - } -); - -export default TransitionWrapper; diff --git a/src/components/type-word-effect/index.tsx b/src/components/type-word-effect/index.tsx deleted file mode 100644 index 275fa057..00000000 --- a/src/components/type-word-effect/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect, useState } from 'react'; - -const TypingEffect: React.FC<{ text?: string }> = ({ text = '' }) => { - const [displayedText, setDisplayedText] = useState(''); - - useEffect(() => { - let index = 0; - const intervalId = setInterval(() => { - setDisplayedText((prev) => prev + text[index]); - index += 1; - if (index === text.length) { - clearInterval(intervalId); - } - }, 20); - - return () => clearInterval(intervalId); - }, [text]); - - return
    {displayedText}
    ; -}; - -export default TypingEffect; diff --git a/src/components/upload-audio/index.tsx b/src/components/upload-audio/index.tsx deleted file mode 100644 index 19d79599..00000000 --- a/src/components/upload-audio/index.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { UploadOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Tooltip, Upload, message } from 'antd'; -import React from 'react'; -import { convertFileSize } from '../../utils'; - -interface UploadAudioProps { - accept?: string; - maxCount?: number; - type?: 'text' | 'primary' | 'default'; - icon?: React.ReactNode; - size?: 'small' | 'middle' | 'large'; - shape?: 'circle' | 'round' | 'default'; - maxFileSize?: number; // in bytes - onChange?: (data: { file: any; fileList: any[] }) => void; -} - -const UploadAudio: React.FC = (props) => { - const [messageApi, contextHolder] = message.useMessage(); - const { icon, accept, type, size = 'large', shape = 'circle' } = props; - const intl = useIntl(); - const beforeUpload = (file: any) => { - return false; - }; - - const isFileSizeValid = (file: File) => { - if (props.maxFileSize && file.size > props.maxFileSize) { - messageApi.open({ - type: 'warning', - content: intl.formatMessage( - { id: 'playground.uploadfile.sizeError' }, - { size: `${convertFileSize(props.maxFileSize)}` } // Convert bytes to MB - ) - }); - return false; - } - return true; - }; - - const handleOnChange = (data: { file: any; fileList: any }) => { - if (!isFileSizeValid(data.file)) { - return; - } - props.onChange?.(data); - }; - - return ( - - -
    - -
    -
    - {contextHolder} -
    - ); -}; - -export default UploadAudio; diff --git a/src/components/util-bar/index.less b/src/components/util-bar/index.less deleted file mode 100644 index 37604d7f..00000000 --- a/src/components/util-bar/index.less +++ /dev/null @@ -1,17 +0,0 @@ -.util-bar-box { - width: 100%; - height: 100%; - display: flex; - justify-content: space-between; - flex-direction: column; - align-items: center; - - .title { - font-weight: var(--font-weight-medium); - padding: 20px 0; - } - - .ant-progress.ant-progress-circle .ant-progress-text { - font-size: 20px; - } -} diff --git a/src/components/util-bar/index.tsx b/src/components/util-bar/index.tsx deleted file mode 100644 index 2d1c172f..00000000 --- a/src/components/util-bar/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Progress } from 'antd'; -import './index.less'; - -interface UitilBarProps { - title?: string; - percent: number; - steps?: number; - gapDegree?: number; - strokeWidth?: number; - size?: number; - railColor?: string; - strokeColor?: string; -} -const UitilBar: React.FC = (props) => { - const { - percent, - steps = 10, - gapDegree = 170, - strokeWidth = 10, - title, - size = 150, - strokeColor, - railColor = 'rgba(221,221,221,.5)' - } = props; - - const strokeColorFunc = (percent: number) => { - if (percent <= 50) { - return 'var(--color-chart-green)'; - } - if (percent <= 80) { - return 'var(--color-chart-glod)'; - } - return 'var(--color-chart-red)'; - }; - return ( -
    - {title && {title}} - -
    - ); -}; - -export default UitilBar; diff --git a/src/components/x-terminal/index.tsx b/src/components/x-terminal/index.tsx deleted file mode 100644 index 2b7791f1..00000000 --- a/src/components/x-terminal/index.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import useUserSettings from '@/hooks/use-user-settings'; -import { FitAddon } from '@xterm/addon-fit'; -import { Terminal } from '@xterm/xterm'; -import '@xterm/xterm/css/xterm.css'; -import { createStyles } from 'antd-style'; -import _ from 'lodash'; -import qs from 'query-string'; -import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; - -const useStyles = createStyles(({ token, css }) => ({ - wrap: css` - position: relative; - text-align: left; - .xterm { - height: 100%; - .xterm-viewport { - overflow-y: auto; - } - } - `, - terminal: css` - height: 100%; - padding: 5px; - overflow: hidden; - overflow: hidden; - background-color: var(--color-logs-bg); - border-radius: 0 0 8px 8px; - ` -})); - -const terminalEnvList = ['bash', 'sh', 'powershell', 'pwsh', 'cmd', 'bash']; - -const RECONNECT_MSG = '--- press Y to reconnect! ---'; - -const colorBg2 = 'rgb(33, 36, 39)'; - -interface XTerminalProps { - height: number; - url: string; -} - -const XTerminal: React.FC = forwardRef((props, ref) => { - const { height, url } = props; - const { styles } = useStyles(); - const { userSettings } = useUserSettings(); - const terminalRef = React.useRef(null); - const WrapperRef = React.useRef(null); - const terminalInstance = React.useRef(null); - const wssURL = React.useRef(''); - const terminalEnvIndex = React.useRef(0); - const terminalSocket = React.useRef(null); - const retryCount = React.useRef(5); - const totalRetry = React.useRef(5); - const first = React.useRef(true); - const loading = React.useRef(false); - const timer = React.useRef(null); - const toRetry = React.useRef(false); - const conReadyState = React.useRef(0); - const [statusCode, setStatusCode] = React.useState(0); - - const fitAddon = new FitAddon(); - - const setWssUrl = (flag?: boolean) => { - if (terminalEnvIndex.current >= terminalEnvList.length - 1) return; - if (flag) { - terminalEnvIndex.current = 0; - } else { - terminalEnvIndex.current += 1; - } - const params = qs.stringify({ - shell: terminalEnvList[terminalEnvIndex.current] - }); - wssURL.current = `${url}&${params}`; - }; - - // readyState: 0 1 2 3 - const isWsOpen = () => { - const readyState = - terminalSocket.current && terminalSocket.current.readyState; - return readyState === 1; - }; - - const resizeRemoteTerminal = () => { - const { cols, rows } = terminalInstance.current || {}; - if (isWsOpen()) { - terminalSocket.current?.send(`#{"width":${cols},"height":${rows}}#`); - } - }; - - const fitTerm = () => { - fitAddon.fit(); - resizeRemoteTerminal(); - }; - - const onResize = _.throttle(() => fitTerm(), 100); - - const removeResizeListener = () => { - window.removeEventListener('resize', onResize); - }; - - const destoryedTerm = () => { - removeResizeListener(); - terminalSocket.current?.close?.(); - }; - - const setData = (data: string) => { - return `${data}\x1B[1;3;31m\x1B[0m`; - }; - - const setErrorData = (data: string) => { - return `\x1b[31m${data}\x1b[m`; - }; - - const runRealTerminal = () => { - terminalInstance.current?.clear?.(); - loading.current = false; - toRetry.current = false; - }; - - const onWSReceive = (message: MessageEvent) => { - if (first.current === true) { - first.current = false; - resizeRemoteTerminal(); - } - - const data = { Data: message.data }; - conReadyState.current = terminalSocket.current?.readyState || 0; - - // const data = JSON.parse(message.data) || ''; - if (terminalInstance.current?.element) terminalInstance.current.focus(); - const output = data.Data; - terminalInstance.current?.write?.(setData(output)); - }; - - const closeRealTerminal = (data: any) => { - setStatusCode(_.get(data, 'code')); - conReadyState.current = terminalSocket.current?.readyState || 0; - if ([1011, 1006, 1000].includes(statusCode)) { - toRetry.current = true; - if (first.current) { - terminalInstance.current?.reset?.(); - } - if (!loading.current) { - terminalInstance.current?.write?.( - setData(`(${statusCode})${data.reason}\r\n`) - ); - terminalInstance.current?.write?.(setErrorData(`\r${RECONNECT_MSG}`)); - } - first.current = true; - } else if (data.reason) { - terminalInstance.current?.write?.( - setData(`(${statusCode})${data.reason}\r\n`) - ); - } - loading.current = false; - }; - - const errorRealTerminal = (ex: any) => { - let { message } = ex; - if (!message) { - message = 'disconnected!'; - toRetry.current = true; - first.current = true; - loading.current = false; - } - conReadyState.current = terminalSocket.current!.readyState; - terminalInstance.current?.write?.(setErrorData(`\r${message}`)); - }; - - const createWS = () => { - if (!props.url) return; - terminalInstance.current?.write?.(''); - setStatusCode(0); - terminalSocket.current = new WebSocket(wssURL.current); - terminalSocket.current.onopen = runRealTerminal; - terminalSocket.current.onmessage = onWSReceive; - terminalSocket.current.onclose = closeRealTerminal; - terminalSocket.current.onerror = errorRealTerminal; - }; - - const initWS = () => { - if (!terminalSocket.current) { - createWS(); - return; - } - terminalSocket.current?.close?.(); - createWS(); - }; - - const retry = () => { - loading.current = true; - terminalInstance.current?.reset?.(); - initWS(); - }; - - const registerTermHandler = () => { - terminalInstance.current?.onData((data: string) => { - if (isWsOpen()) { - terminalSocket.current?.send(data); - } - }); - terminalInstance.current?.onKey((e: any) => { - if (toRetry.current && e.domEvent.code === 'KeyY') { - retry(); - } - }); - }; - - const onTerminalResize = () => { - window.addEventListener('resize', onResize); - }; - - const initTerm = () => { - terminalInstance.current?.dispose?.(); - terminalInstance.current = new Terminal({ - lineHeight: 1.2, - fontSize: 12, - fontFamily: - "monospace,Menlo,Courier,'Courier New',Consolas,Monaco, 'Liberation Mono'", - theme: { - background: userSettings.theme === 'realDark' ? colorBg2 : '#181d28', - foreground: - userSettings.theme === 'realDark' - ? 'rgba(255, 255, 255, 0.7)' - : '#fff' - }, - cursorBlink: true, - cursorStyle: 'underline', - scrollback: 100, - tabStopWidth: 4 - }); - terminalInstance.current?.open?.(terminalRef.current!); - terminalInstance.current?.loadAddon?.(fitAddon); - fitAddon.fit(); - }; - - const init = () => { - setWssUrl(); - initWS(); - initTerm(); - registerTermHandler(); - onTerminalResize(); - }; - - const debounceCall = _.debounce(() => { - first.current = true; - loading.current = true; - setWssUrl(true); - initWS(); - }, 100); - - useEffect(() => { - if (!url) { - terminalInstance.current?.reset?.(); - terminalSocket.current?.close?.(); - terminalSocket.current = null; - } else { - // reset retry count - retryCount.current = totalRetry.current; - clearTimeout(timer.current); - - terminalInstance.current?.reset?.(); - debounceCall(); - } - return () => { - destoryedTerm(); - removeResizeListener(); - }; - }, [url]); - - useImperativeHandle(ref, () => ({ - fit: () => { - fitAddon.fit(); - } - })); - - return ( -
    -
    -
    - ); -}); - -export default XTerminal; diff --git a/src/hooks/use-chunk-request.ts b/src/hooks/use-chunk-request.ts index fc1d3216..38d59eee 100644 --- a/src/hooks/use-chunk-request.ts +++ b/src/hooks/use-chunk-request.ts @@ -143,7 +143,7 @@ const useSetChunkRequest = () => { total.current = e.total || 0; if (contentType === 'json') { - let currentRes = sliceData(response, e.loaded, loadedSize); + const currentRes = sliceData(response, e.loaded, loadedSize); workerRef.current.postMessage(currentRes); } else { handler(response); diff --git a/src/hooks/use-download-logs.tsx b/src/hooks/use-download-logs.tsx index 7b5cb90a..ba2db93b 100644 --- a/src/hooks/use-download-logs.tsx +++ b/src/hooks/use-download-logs.tsx @@ -1,5 +1,5 @@ import { HandlerOptions } from '@/hooks/use-chunk-fetch'; -import useDownloadStream from '@/hooks/use-download-stream'; +import { useDownloadStream } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Progress, notification } from 'antd'; import dayjs from 'dayjs'; diff --git a/src/layouts/error-boundary.tsx b/src/layouts/error-boundary.tsx index 66c23195..7e586e7d 100644 --- a/src/layouts/error-boundary.tsx +++ b/src/layouts/error-boundary.tsx @@ -2,7 +2,6 @@ import type { ErrorInfo } from 'react'; import React from 'react'; import ErrorResult from './error-result'; -// eslint-disable-next-line @typescript-eslint/ban-types class ErrorBoundary extends React.Component< { children?: React.ReactNode }, { hasError: boolean; errorInfo: string } @@ -15,7 +14,7 @@ class ErrorBoundary extends React.Component< componentDidCatch(error: any, errorInfo: ErrorInfo) { // You can also log the error to an error reporting service - // eslint-disable-next-line no-console + console.log(error, errorInfo); } diff --git a/src/layouts/extraRender.tsx b/src/layouts/extraRender.tsx index 1ec80d9d..1c49de78 100644 --- a/src/layouts/extraRender.tsx +++ b/src/layouts/extraRender.tsx @@ -1,6 +1,4 @@ import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user'; -import DropDownActions from '@/components/drop-down-actions'; -import IconFont from '@/components/icon-font'; import VersionInfo, { modalConfig } from '@/components/version-info'; import externalLinks from '@/constants/external-links'; import useBodyScroll from '@/hooks/use-body-scroll'; @@ -12,6 +10,7 @@ import { HomeOutlined, ReadOutlined } from '@ant-design/icons'; +import { DropdownActions, IconFont } from '@gpustack/core-ui'; import { history, useIntl, useNavigate } from '@umijs/max'; import { Avatar, Button, Divider, Modal } from 'antd'; import { useAtom } from 'jotai'; @@ -292,7 +291,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => { )} - + { style={{ color: 'var(--ant-color-text-tertiary)' }} /> - - + + { icon={} /> - + ); }; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 5958106a..765fe7a3 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -1,21 +1,30 @@ import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache'; import { userAtom } from '@/atoms/user'; import DarkMask from '@/components/dark-mask'; -import IconFont from '@/components/icon-font'; import routeCachekey from '@/config/route-cachekey'; -import { DEFAULT_ENTER_PAGE } from '@/config/settings'; +import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useUserSettings from '@/hooks/use-user-settings'; import useAddResource from '@/pages/dashboard/hooks/use-add-resource'; import { logout } from '@/pages/login/apis'; +import { + readColumnSettings, + readState, + writeColumnSettings, + writeState +} from '@/utils/localstore'; import { useAccessMarkedRoutes } from '@@/plugin-access'; import { useModel } from '@@/plugin-model'; import { ProLayout } from '@ant-design/pro-components'; +import { CoreUIProvider, IconFont } from '@gpustack/core-ui'; import { Outlet, dropByCacheKey, + getAllLocales, history, matchRoutes, + request, + setLocale, useAppData, useIntl, useLocation, @@ -23,7 +32,6 @@ import { type IRoute } from '@umijs/max'; import { Button, ConfigProvider, Modal, theme } from 'antd'; -import 'driver.js/dist/driver.css'; import { useAtom } from 'jotai'; import 'overlayscrollbars/overlayscrollbars.css'; import { useEffect, useMemo, useRef } from 'react'; @@ -73,7 +81,7 @@ const filterRoutes = ( return []; } - let newRoutes: NewRoute[] = []; + const newRoutes: NewRoute[] = []; for (const route of routes) { const newRoute = { ...route }; if (filterFn(route)) { @@ -158,7 +166,7 @@ export default (props: any) => { }; const dropRouteCache = (pathname: string) => { - for (let key of routeCache.keys()) { + for (const key of routeCache.keys()) { if (key !== pathname && !routeCache.get(key) && routeCachekey[key]) { dropByCacheKey(key); routeCache.delete(key); @@ -329,8 +337,6 @@ export default (props: any) => { }); }; - console.log('userConfig==========title', userConfig.title); - return ( { } }} > - - : } - menuContentRender={menuContentRender} - {...runtimeConfig} - ErrorBoundary={ErrorBoundary} > - + : } + menuContentRender={menuContentRender} + {...runtimeConfig} + ErrorBoundary={ErrorBoundary} > - {isNoContainerPage ? ( - - ) : ( - -
    - -
    -
    - )} -
    - {NoResourceModal} - {contextHolder} -
    + + {isNoContainerPage ? ( + + ) : ( + +
    + +
    +
    + )} +
    + {NoResourceModal} + {contextHolder} + +
    ); }; diff --git a/src/layouts/sider-menu.tsx b/src/layouts/sider-menu.tsx index dbe4257a..5a9bd0f1 100644 --- a/src/layouts/sider-menu.tsx +++ b/src/layouts/sider-menu.tsx @@ -1,5 +1,5 @@ -import IconFont from '@/components/icon-font'; import { CaretDownOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { Link, useLocation } from '@umijs/max'; import { Tooltip } from 'antd'; import { createStyles } from 'antd-style'; diff --git a/src/pages/_components/category-select.tsx b/src/pages/_components/category-select.tsx index 670ef10e..a04a9a49 100644 --- a/src/pages/_components/category-select.tsx +++ b/src/pages/_components/category-select.tsx @@ -1,4 +1,4 @@ -import SealSelect from '@/components/seal-form/seal-select'; +import { Select as SealSelect } from '@gpustack/core-ui'; import React from 'react'; import { categoryConfig } from './model-tag'; diff --git a/src/pages/_components/collapse-panel/index.tsx b/src/pages/_components/collapse-panel/index.tsx index 4f5edd2d..cb4f4705 100644 --- a/src/pages/_components/collapse-panel/index.tsx +++ b/src/pages/_components/collapse-panel/index.tsx @@ -1,4 +1,4 @@ -import IconFont from '@/components/icon-font'; +import { IconFont } from '@gpustack/core-ui'; import { Collapse, CollapseProps } from 'antd'; import React from 'react'; import styled from 'styled-components'; diff --git a/src/pages/_components/column-settings.tsx b/src/pages/_components/column-settings.tsx index 42f28bce..4984a9e0 100644 --- a/src/pages/_components/column-settings.tsx +++ b/src/pages/_components/column-settings.tsx @@ -1,9 +1,9 @@ -import OverlayScroller from '@/components/overlay-scroller'; import { readColumnSettings, writeColumnSettings } from '@/utils/localstore/index'; import { SettingOutlined } from '@ant-design/icons'; +import { OverlayScroller } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Col, Popover, Row, Tooltip } from 'antd'; import React, { useEffect } from 'react'; diff --git a/src/pages/_components/command-viewer/index.tsx b/src/pages/_components/command-viewer/index.tsx deleted file mode 100644 index 775e5333..00000000 --- a/src/pages/_components/command-viewer/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import CopyButton from '@/components/copy-button'; -import EditorWrap from '@/components/editor-wrap'; -import HighlightCode from '@/components/highlight-code'; -import SegmentLine from '@/components/segment-line'; -import React from 'react'; -import styled from 'styled-components'; - -interface ViewerProps { - code: string; - copyText?: string; - options?: Global.BaseOption[]; - defaultValue?: string; - headerHeight?: number; - showTitle?: boolean; - height?: number; - lang?: string; - onChange?: (value: string | number) => void; -} - -const Header = styled.div` - width: 100%; - padding-inline: 12px 10px; - display: flex; - align-items: center; - justify-content: space-between; - background-color: var(--color-editor-header-bg); -`; - -const CommandViewer: React.FC = (props) => { - const { - code, - copyText = '', - defaultValue, - options = [], - headerHeight = 40, - height = 380, - showTitle = false, - lang, - onChange - } = props || {}; - - const [value, setValue] = React.useState(defaultValue || ''); - - const handlOnChange = (value: string | number) => { - setValue(value as string); - onChange?.(value); - }; - - return ( - - - - - - } - > - - - ); -}; - -export default CommandViewer; diff --git a/src/pages/_components/doc-link.tsx b/src/pages/_components/doc-link.tsx index 672f8f14..1004fba6 100644 --- a/src/pages/_components/doc-link.tsx +++ b/src/pages/_components/doc-link.tsx @@ -1,4 +1,4 @@ -import IconFont from '@/components/icon-font'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import React from 'react'; diff --git a/src/pages/_components/filter-form/index.tsx b/src/pages/_components/filter-form/index.tsx index 6e68fb58..ff9c108f 100644 --- a/src/pages/_components/filter-form/index.tsx +++ b/src/pages/_components/filter-form/index.tsx @@ -1,5 +1,5 @@ -import OverlayScroller from '@/components/overlay-scroller'; import { CloseOutlined } from '@ant-design/icons'; +import { OverlayScroller } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; import classNames from 'classnames'; diff --git a/src/pages/_components/form-drawer.tsx b/src/pages/_components/form-drawer.tsx index bcc5dba7..e27f16d8 100644 --- a/src/pages/_components/form-drawer.tsx +++ b/src/pages/_components/form-drawer.tsx @@ -1,6 +1,4 @@ -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; +import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { Tag } from 'antd'; import React from 'react'; diff --git a/src/pages/_components/model-tag.tsx b/src/pages/_components/model-tag.tsx index 8dee5275..9dd8c440 100644 --- a/src/pages/_components/model-tag.tsx +++ b/src/pages/_components/model-tag.tsx @@ -1,9 +1,9 @@ -import IconFont from '@/components/icon-font'; import { AudioOutlined, PictureOutlined, WechatWorkOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { Tag } from 'antd'; import { modelCategoriesMap } from '../llmodels/config'; diff --git a/src/pages/_components/page-breadcrumb/index.tsx b/src/pages/_components/page-breadcrumb/index.tsx index 0179eb02..7cdaa23e 100644 --- a/src/pages/_components/page-breadcrumb/index.tsx +++ b/src/pages/_components/page-breadcrumb/index.tsx @@ -1,4 +1,4 @@ -import IconFont from '@/components/icon-font'; +import { IconFont } from '@gpustack/core-ui'; import { Breadcrumb, type BreadcrumbProps } from 'antd'; import React from 'react'; import styled from 'styled-components'; diff --git a/src/pages/_components/pill-button-group/index.tsx b/src/pages/_components/pill-button-group/index.tsx index 150716cd..2b3f67ec 100644 --- a/src/pages/_components/pill-button-group/index.tsx +++ b/src/pages/_components/pill-button-group/index.tsx @@ -1,7 +1,6 @@ -import AutoTooltip from '@/components/auto-tooltip'; +import { AutoTooltip } from '@gpustack/core-ui'; import React from 'react'; import pillButtonCss from './styles.less'; - type Option = { label: string; value: string | number | null; diff --git a/src/pages/_components/scroll-spy-tabs/index.tsx b/src/pages/_components/scroll-spy-tabs/index.tsx index f3fb5596..881e96cb 100644 --- a/src/pages/_components/scroll-spy-tabs/index.tsx +++ b/src/pages/_components/scroll-spy-tabs/index.tsx @@ -1,4 +1,4 @@ -import SegmentLine from '@/components/segment-line'; +import { SegmentLine } from '@gpustack/core-ui'; import { useMemoizedFn } from 'ahooks'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle } from 'react'; diff --git a/src/pages/_components/select-panel/index.tsx b/src/pages/_components/select-panel/index.tsx deleted file mode 100644 index 85c17065..00000000 --- a/src/pages/_components/select-panel/index.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { SearchOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Button, Checkbox, Empty, Input } from 'antd'; -import React, { useEffect, useMemo, useState } from 'react'; -import styled from 'styled-components'; -import List from './list'; -import SelectedList from './selected-list'; - -const PanelWrapper = styled.div<{ $maxHeight?: number; $leftWidth?: number }>` - border: 1px solid var(--ant-color-border); - border-radius: var(--ant-border-radius); - overflow-y: auto; - max-height: ${({ $maxHeight }) => - $maxHeight ? `${$maxHeight + 2}px` : 'auto'}; -`; - -const Left = styled.div` - padding: 0; -`; -const Right = styled.div``; - -const Header = styled.div` - padding: 8px 12px 8px; - display: flex; - gap: 8px; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid var(--ant-color-split); - background-color: var(--ant-color-fill-alter); -`; - -interface SelectPanelProps { - searchPlaceholder?: string; - height?: number; - leftWidth?: number; - options: Array<{ key: string; title: string }>; - selectedKeys: string[]; - notFoundContent?: React.ReactNode; - onSelectChange: (selectedKeys: string[]) => void; - styles?: { - container?: React.CSSProperties; - left?: React.CSSProperties; - right?: React.CSSProperties; - header?: React.CSSProperties; - }; -} - -const SelectPanel: React.FC = ({ - height = 300, - leftWidth = 260, - options, - selectedKeys, - searchPlaceholder, - notFoundContent, - styles, - onSelectChange -}) => { - const intl = useIntl(); - const [indeterminate, setIndeterminate] = React.useState(false); - const [checkAll, setCheckAll] = React.useState(false); - const [searchText, setSearchText] = useState(''); - - const showOptions = useMemo(() => { - return options.filter((item) => - item.title.toLowerCase().includes(searchText.toLowerCase()) - ); - }, [options, searchText]); - - const handleSearch = (e: React.ChangeEvent) => { - setSearchText(e.target.value); - }; - - const handleOnUnselect = ( - key: string, - newSelectedKeys: { key: string; title: string }[] - ) => { - onSelectChange(newSelectedKeys.map((item) => item.key)); - }; - - const handleCheckAllChange = (e: any) => { - const checked = e.target.checked; - setCheckAll(checked); - setIndeterminate(false); - if (checked) { - const allKeys = options - .filter((item) => - item.title.toLowerCase().includes(searchText.toLowerCase()) - ) - .map((item) => item.key); - onSelectChange(Array.from(new Set([...selectedKeys, ...allKeys]))); - } else { - const filteredKeys = options - .filter((item) => - item.title.toLowerCase().includes(searchText.toLowerCase()) - ) - .map((item) => item.key); - const newSelectedKeys = selectedKeys.filter( - (key) => !filteredKeys.includes(key) - ); - onSelectChange(newSelectedKeys); - } - }; - - const updateCheckStatus = (newSelectedKeys: string[]) => { - if (options.length === 0) { - setIndeterminate(false); - setCheckAll(false); - return; - } - const filteredOptions = options.filter((item) => - item.title.toLowerCase().includes(searchText.toLowerCase()) - ); - const filteredKeys = filteredOptions.map((item) => item.key); - const selectedFilteredKeys = newSelectedKeys.filter((key) => - filteredKeys.includes(key) - ); - setIndeterminate( - selectedFilteredKeys.length > 0 && - selectedFilteredKeys.length < filteredKeys.length - ); - setCheckAll(selectedFilteredKeys.length === filteredKeys.length); - }; - - const handleSelectChange = (newSelectedKeys: string[]) => { - onSelectChange(newSelectedKeys); - updateCheckStatus(newSelectedKeys); - }; - - const handleClearSelection = () => { - onSelectChange([]); - setCheckAll(false); - setIndeterminate(false); - }; - - useEffect(() => { - updateCheckStatus(selectedKeys); - }, [selectedKeys, options]); - - const renderRight = () => { - return ( - -
    - ({selectedKeys.length}) selected - -
    - - selectedKeys.includes(item.key) - )} - onUnselect={handleOnUnselect} - /> -
    - ); - }; - - return ( - - -
    - - - - {intl.formatMessage( - { id: 'common.select.count' }, - { count: selectedKeys.length } - )} - - - - } - size="small" - allowClear - status={'null' as any} - placeholder={searchPlaceholder} - style={{ - width: 300, - height: 32, - borderRadius: 4, - backgroundColor: 'var(--ant-color-bg-container) !important' - }} - onChange={handleSearch} - /> -
    - {showOptions.length > 0 ? ( - - ) : ( - - )} -
    -
    - ); -}; - -export default SelectPanel; diff --git a/src/pages/_components/select-panel/list.tsx b/src/pages/_components/select-panel/list.tsx deleted file mode 100644 index 48a92116..00000000 --- a/src/pages/_components/select-panel/list.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { OverlayScroller } from '@/components/overlay-scroller'; -import { Checkbox } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; -import AutoTooltip from '../../../components/auto-tooltip'; - -interface ListProps { - maxHeight?: number; - dataList: Array<{ key: string; title: string }>; - selectedKeys: string[]; - renderTitle?: (item: { key: string; title: string }) => React.ReactNode; - onSelectChange: (selectedKeys: string[]) => void; -} - -const UL = styled.ul` - list-style: none; - margin: 0; - padding: 0; -`; - -const LI = styled.li<{ selected: boolean }>` - display: flex; - align-items: center; - padding: 5px 12px; - cursor: pointer; - border-radius: 2px; - gap: 8px; - &:hover { - background-color: var(--ant-control-item-bg-hover); - } -`; - -const List: React.FC = ({ - maxHeight, - dataList, - selectedKeys, - onSelectChange, - renderTitle -}) => { - const handleClickItem = (item: { key: string; title: string }) => { - const itemKey = item.key; - const newSelectedKeys = selectedKeys.includes(itemKey) - ? selectedKeys.filter((key) => key !== itemKey) - : [...selectedKeys, itemKey]; - onSelectChange(newSelectedKeys); - }; - - return ( - -
      - {dataList.map((item) => ( -
    • handleClickItem(item)} - > - - {renderTitle ? ( - renderTitle(item) - ) : ( - {item.title} - )} -
    • - ))} -
    -
    - ); -}; - -export default List; diff --git a/src/pages/_components/select-panel/selected-list.tsx b/src/pages/_components/select-panel/selected-list.tsx deleted file mode 100644 index 2830fcb4..00000000 --- a/src/pages/_components/select-panel/selected-list.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import { OverlayScroller } from '@/components/overlay-scroller'; -import { Tag } from 'antd'; -import React from 'react'; -import styled from 'styled-components'; - -const TagInner = styled(Tag)` - border-radius: 12px; - margin: 0; -`; - -const Content = styled.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 8px 0; -`; - -interface SelectedProps { - maxHeight?: number; - selectedList: { key: string; title: string }[]; - onUnselect: ( - key: string, - newSelectedKeys: { key: string; title: string }[] - ) => void; -} - -const SelectedList: React.FC = ({ - maxHeight, - selectedList, - onUnselect -}) => { - const handleOnUnselect = (key: string) => { - const newSelectedKeys = selectedList.filter((item) => item.key !== key); - onUnselect(key, newSelectedKeys); - }; - - return ( - - - {selectedList.map((item) => ( - { - e.preventDefault(); - handleOnUnselect(item.key); - }} - closable - > - {item.title} - - ))} - - - ); -}; - -export default SelectedList; diff --git a/src/pages/_components/terminal-tabs/tabs.tsx b/src/pages/_components/terminal-tabs/tabs.tsx index d823d88c..0c3dc6a6 100644 --- a/src/pages/_components/terminal-tabs/tabs.tsx +++ b/src/pages/_components/terminal-tabs/tabs.tsx @@ -1,5 +1,5 @@ -import XTerminal from '@/components/x-terminal'; import { CloseOutlined } from '@ant-design/icons'; +import { XTerminal } from '@gpustack/core-ui'; import { Button, Tabs } from 'antd'; import { throttle } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; diff --git a/src/pages/_components/yaml-editor/editor.tsx b/src/pages/_components/yaml-editor/editor.tsx index a4889240..28860747 100644 --- a/src/pages/_components/yaml-editor/editor.tsx +++ b/src/pages/_components/yaml-editor/editor.tsx @@ -1,5 +1,5 @@ -import EditorWrap from '@/components/editor-wrap'; import { LoadingOutlined } from '@ant-design/icons'; +import { EditorWrap } from '@gpustack/core-ui'; import Editor, { loader } from '@monaco-editor/react'; import * as monaco from 'monaco-editor'; import { yamlDefaults } from 'monaco-yaml'; diff --git a/src/pages/api-keys/components/add-apikey-modal/allow-models.tsx b/src/pages/api-keys/components/add-apikey-modal/allow-models.tsx index 6a805c45..877eaaf7 100644 --- a/src/pages/api-keys/components/add-apikey-modal/allow-models.tsx +++ b/src/pages/api-keys/components/add-apikey-modal/allow-models.tsx @@ -1,8 +1,8 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; -import SelectPanel from '@/pages/_components/select-panel'; import { queryMyModels } from '@/pages/llmodels/apis'; +import { SelectPanel } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Checkbox, Divider, Form, Radio } from 'antd'; import { useEffect, useState } from 'react'; diff --git a/src/pages/api-keys/components/add-apikey-modal/form.tsx b/src/pages/api-keys/components/add-apikey-modal/form.tsx index b6c3a05f..e98305f0 100644 --- a/src/pages/api-keys/components/add-apikey-modal/form.tsx +++ b/src/pages/api-keys/components/add-apikey-modal/form.tsx @@ -1,8 +1,10 @@ -import Password from '@/components/seal-form/password'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; +import { + Input as CInput, + Password, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; @@ -34,12 +36,12 @@ const APIKeyForm: React.FC<{ } ]} > - + > @@ -66,10 +68,10 @@ const APIKeyForm: React.FC<{ > name="description" rules={[{ required: false }]}> - + > {action === PageAction.CREATE && ( <> diff --git a/src/pages/api-keys/components/add-apikey-modal/index.tsx b/src/pages/api-keys/components/add-apikey-modal/index.tsx index 06806198..c57629af 100644 --- a/src/pages/api-keys/components/add-apikey-modal/index.tsx +++ b/src/pages/api-keys/components/add-apikey-modal/index.tsx @@ -1,11 +1,13 @@ -import AlertBlockInfo from '@/components/alert-info/block'; -import CopyButton from '@/components/copy-button'; -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; -import SealInput from '@/components/seal-form/seal-input'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; +import { + AlertBlockInfo, + Input as CInput, + ColumnWrapper, + CopyButton, + GSDrawer, + ModalFooter +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, Tag } from 'antd'; import dayjs from 'dayjs'; @@ -296,7 +298,7 @@ const AddModal: React.FC = ({ {intl.formatMessage({ id: 'apikeys.table.save.tips' })} - = ({ type="text" > } - > + > )} diff --git a/src/pages/api-keys/hooks/use-keys-columns.tsx b/src/pages/api-keys/hooks/use-keys-columns.tsx index a11dca29..fa38c5eb 100644 --- a/src/pages/api-keys/hooks/use-keys-columns.tsx +++ b/src/pages/api-keys/hooks/use-keys-columns.tsx @@ -1,15 +1,13 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import icons from '@/components/icon-font/icons'; import { tableSorter } from '@/config/settings'; +import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import { useIntl } from '@umijs/max'; import { Tag } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { ListItem } from '../config/types'; - interface ColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; sortOrder: string[]; diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx index 25078e55..a6304f1b 100644 --- a/src/pages/api-keys/index.tsx +++ b/src/pages/api-keys/index.tsx @@ -1,12 +1,10 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import { PaginationKey } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; import useQueryUserList from '@/pages/users/services/use-query-user-list'; import { useModel } from '@@/plugin-model'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import { ConfigProvider, Table } from 'antd'; @@ -191,7 +189,7 @@ const APIKeys: React.FC = () => { rowKey="id" onChange={handleTableChange} pagination={{ - size: 'default', + size: 'middle', showSizeChanger: true, pageSize: queryParams.perPage, current: queryParams.page, diff --git a/src/pages/backends/community/add-community-modal.tsx b/src/pages/backends/community/add-community-modal.tsx index e7f397fe..a93f19f4 100644 --- a/src/pages/backends/community/add-community-modal.tsx +++ b/src/pages/backends/community/add-community-modal.tsx @@ -1,7 +1,6 @@ import { enabledBackendsAtom } from '@/atoms/backend'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; import Separator from '@/pages/llmodels/components/separator'; +import { ColumnWrapper, GSDrawer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useAtom } from 'jotai'; import React, { useEffect } from 'react'; diff --git a/src/pages/backends/community/backend-detail.tsx b/src/pages/backends/community/backend-detail.tsx index 23cdec27..6a9ae093 100644 --- a/src/pages/backends/community/backend-detail.tsx +++ b/src/pages/backends/community/backend-detail.tsx @@ -1,13 +1,11 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import FullMarkdown from '@/components/markdown-viewer/full-markdown'; import { BulbOutlined } from '@ant-design/icons'; +import { AutoTooltip, FullMarkdown } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Tag } from 'antd'; import _ from 'lodash'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { generateIcon } from '../components/backend-card'; - const Title = styled.div` position: sticky; top: 0; diff --git a/src/pages/backends/community/commnity-backends.tsx b/src/pages/backends/community/commnity-backends.tsx index b800e2ae..5b6113aa 100644 --- a/src/pages/backends/community/commnity-backends.tsx +++ b/src/pages/backends/community/commnity-backends.tsx @@ -1,8 +1,7 @@ import { enabledBackendsAtom } from '@/atoms/backend'; -import IconFont from '@/components/icon-font'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import useTableFetch from '@/hooks/use-table-fetch'; import { SearchOutlined } from '@ant-design/icons'; +import { IconFont, ThemeTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Input, Tooltip } from 'antd'; import { useAtom } from 'jotai'; diff --git a/src/pages/backends/components/add-modal.tsx b/src/pages/backends/components/add-modal.tsx index 0ad11867..bc5881a5 100644 --- a/src/pages/backends/components/add-modal.tsx +++ b/src/pages/backends/components/add-modal.tsx @@ -1,10 +1,12 @@ -import AlertBlockInfo from '@/components/alert-info/block'; -import IconFont from '@/components/icon-font'; -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; -import SegmentLine from '@/components/segment-line'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; +import { + AlertBlockInfo, + GSDrawer, + IconFont, + ModalFooter, + SegmentLine +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tabs } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/backends/components/backend-card.tsx b/src/pages/backends/components/backend-card.tsx index 96304bbe..8e4e7158 100644 --- a/src/pages/backends/components/backend-card.tsx +++ b/src/pages/backends/components/backend-card.tsx @@ -1,9 +1,11 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import DropDownActions from '@/components/drop-down-actions'; -import IconFont from '@/components/icon-font'; -import TagWrapper from '@/components/tags-wrapper'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; -import Card from '@/components/templates/card'; +import { + AutoTooltip, + DropdownActions, + IconFont, + TagsWrapper, + TemplateCard, + ThemeTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Tag } from 'antd'; import _ from 'lodash'; @@ -22,8 +24,7 @@ import { TagColorMap } from '../config'; import { ListItem } from '../config/types'; - -const StyledCard = styled(Card)` +const StyledCard = styled(TemplateCard)` &:hover { .operations { background-color: var(--ant-color-fill-tertiary); @@ -231,11 +232,11 @@ const BackendCard: React.FC = ({ {intl.formatMessage({ id: 'backend.availableFrameworks' })}:{' '} - + > ); }; @@ -279,7 +280,7 @@ const BackendCard: React.FC = ({ actionsRenderer(data) ) : ( - = ({ size="small" type="text" > - + ); }; diff --git a/src/pages/backends/components/backend-list.tsx b/src/pages/backends/components/backend-list.tsx index 2514f247..aa326ad2 100644 --- a/src/pages/backends/components/backend-list.tsx +++ b/src/pages/backends/components/backend-list.tsx @@ -1,7 +1,9 @@ -import ResizeContainer from '@/components/resize-container'; -import CardSkeleton from '@/components/templates/card-skelton'; -import InfiniteScroller from '@/pages/_components/infinite-scroller'; -import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context'; +import { + InfiniteScroller, + ResizeContainer, + TemplateCardSkeleton +} from '@gpustack/core-ui'; +import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context'; import { Spin } from 'antd'; import React from 'react'; import backendListCss from '../styles/backend-list.less'; @@ -38,7 +40,7 @@ const ListSkeleton: React.FC<{ > {isFirst && (
    - + >
    )} diff --git a/src/pages/backends/components/import-yaml.tsx b/src/pages/backends/components/import-yaml.tsx index 627a288b..0ae373f3 100644 --- a/src/pages/backends/components/import-yaml.tsx +++ b/src/pages/backends/components/import-yaml.tsx @@ -1,6 +1,6 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import YamlEditor from '@/pages/_components/yaml-editor'; +import { YamlEditor } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import React, { forwardRef, diff --git a/src/pages/backends/components/version-info-modal.tsx b/src/pages/backends/components/version-info-modal.tsx index b0604fbc..8969fd11 100644 --- a/src/pages/backends/components/version-info-modal.tsx +++ b/src/pages/backends/components/version-info-modal.tsx @@ -1,5 +1,5 @@ -import ScrollerModal from '@/components/scroller-modal'; import { PlusOutlined } from '@ant-design/icons'; +import { ScrollerModal } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/backends/config/index.ts b/src/pages/backends/config/index.ts index 3c68d8f5..0c07d3f9 100644 --- a/src/pages/backends/config/index.ts +++ b/src/pages/backends/config/index.ts @@ -2,12 +2,12 @@ import MindIELogo from '@/assets/logo/ascend.png'; import SGLangLogo from '@/assets/logo/sglang.png'; import vLLMLogo from '@/assets/logo/vllm.png'; import VoxBoxLogo from '@/assets/logo/voxbox.png'; -import icons from '@/components/icon-font/icons'; import { backendOptionsMap } from '@/pages/llmodels/constants/backend-parameters'; import { GPUDriverMap, ManufacturerMap } from '@/pages/resources/config/gpu-driver'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import jsYaml from 'js-yaml'; import { trim } from 'lodash'; diff --git a/src/pages/backends/forms/basic.tsx b/src/pages/backends/forms/basic.tsx index db890d2b..55f83e44 100644 --- a/src/pages/backends/forms/basic.tsx +++ b/src/pages/backends/forms/basic.tsx @@ -1,9 +1,11 @@ -import LabelSelector from '@/components/label-selector'; -import ListInput from '@/components/list-input'; -import SealInput from '@/components/seal-form/seal-input'; -import SealTextArea from '@/components/seal-form/seal-textarea'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; +import { + Input as CInput, + LabelSelector, + ListInput, + Textarea as SealTextArea +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { useEffect } from 'react'; @@ -39,7 +41,7 @@ const BasicForm = () => { } ]} > - { disabled={action === PageAction.EDIT} label={intl.formatMessage({ id: 'common.table.name' })} required - > + > hidden name="backend_source"> - + {backendSource !== BackendSourceValueMap.BUILTIN && ( <> @@ -58,11 +60,11 @@ const BasicForm = () => { name="health_check_path" rules={[{ required: false }]} > - + > { name="description" rules={[{ required: false }]}> - + > ); diff --git a/src/pages/backends/forms/version-info.tsx b/src/pages/backends/forms/version-info.tsx index a896e163..acaf8a56 100644 --- a/src/pages/backends/forms/version-info.tsx +++ b/src/pages/backends/forms/version-info.tsx @@ -1,7 +1,9 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import CopyButton from '@/components/copy-button'; -import BaseSelect from '@/components/seal-form/base/select'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; +import { + AutoTooltip, + BaseSelect, + CopyButton, + ThemeTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Empty, Typography } from 'antd'; import { useState } from 'react'; @@ -14,7 +16,6 @@ import { TagColorMap } from '../config'; import { VersionListItem } from '../config/types'; - const ItemWrapper = styled.div` display: flex; flex-direction: column; diff --git a/src/pages/backends/forms/versions-config.tsx b/src/pages/backends/forms/versions-config.tsx index 646d6966..2fafa591 100644 --- a/src/pages/backends/forms/versions-config.tsx +++ b/src/pages/backends/forms/versions-config.tsx @@ -1,12 +1,14 @@ -import CollapsibleContainer from '@/components/collapse-container'; -import LabelSelector from '@/components/label-selector'; -import BaseSelect from '@/components/seal-form/base/select'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; -import SealTextArea from '@/components/seal-form/seal-textarea'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; +import { + BaseSelect, + Input as CInput, + CollapseContainer, + LabelSelector, + Select as SealSelect, + Textarea as SealTextArea +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form, Tag } from 'antd'; import React, { useEffect, useMemo } from 'react'; @@ -14,7 +16,6 @@ import styled from 'styled-components'; import { BackendSourceValueMap, frameworks } from '../config'; import { useFormContext } from '../config/form-context'; import { ListItem } from '../config/types'; - // version must be endwith '-custom' const Box = styled.div` @@ -263,7 +264,7 @@ const VersionsForm: React.FC = ({ border: '1px solid var(--ant-color-split)' }} > - = ({ )} } - onToggle={(open) => onToggle(open, name)} + onToggle={(open: boolean) => onToggle(open, name)} deleteBtn={false} right={
    @@ -328,13 +329,13 @@ const VersionsForm: React.FC = ({ } ]} > - + > = ({ } ]} > - = ({ { content: 'vllm/vllm-openai:v0.12.0' } )} label={intl.formatMessage({ id: 'backend.imageName' })} - > + > = ({ /> - = ({ label={intl.formatMessage({ id: 'backend.replaceEntrypoint' })} - > + > = ({ } > - +
    )); }} diff --git a/src/pages/backends/hooks/use-create-backend.tsx b/src/pages/backends/hooks/use-create-backend.tsx index ff1a2a97..8084015f 100644 --- a/src/pages/backends/hooks/use-create-backend.tsx +++ b/src/pages/backends/hooks/use-create-backend.tsx @@ -1,7 +1,7 @@ -import IconFont from '@/components/icon-font'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useBodyScroll from '@/hooks/use-body-scroll'; +import { IconFont } from '@gpustack/core-ui'; import { ListItem } from '../config/types'; import useCommunityBackend from './use-community-backend'; import useCustomBackend from './use-custom-backend'; diff --git a/src/pages/backends/index.tsx b/src/pages/backends/index.tsx index 91f28a3b..76b26c81 100644 --- a/src/pages/backends/index.tsx +++ b/src/pages/backends/index.tsx @@ -1,8 +1,6 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import useTableFetch from '@/hooks/use-table-fetch'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import _ from 'lodash'; diff --git a/src/pages/benchmark/components/add-benchmark-modal.tsx b/src/pages/benchmark/components/add-benchmark-modal.tsx index e46f4ec3..91685666 100644 --- a/src/pages/benchmark/components/add-benchmark-modal.tsx +++ b/src/pages/benchmark/components/add-benchmark-modal.tsx @@ -1,5 +1,5 @@ import { PageActionType } from '@/config/types'; -import FormDrawer from '@/pages/_components/form-drawer'; +import { FormDrawer } from '@gpustack/core-ui'; import React, { useRef } from 'react'; import { FormData, BenchmarkListItem as ListItem } from '../config/types'; diff --git a/src/pages/benchmark/components/detail-content.tsx b/src/pages/benchmark/components/detail-content.tsx index 2c1ca27f..88b64788 100644 --- a/src/pages/benchmark/components/detail-content.tsx +++ b/src/pages/benchmark/components/detail-content.tsx @@ -1,4 +1,4 @@ -import IconFont from '@/components/icon-font'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl, useSearchParams } from '@umijs/max'; import { Tabs, TabsProps } from 'antd'; import React, { useState } from 'react'; diff --git a/src/pages/benchmark/components/detail-modal.tsx b/src/pages/benchmark/components/detail-modal.tsx index 036d9825..cff4a023 100644 --- a/src/pages/benchmark/components/detail-modal.tsx +++ b/src/pages/benchmark/components/detail-modal.tsx @@ -1,4 +1,4 @@ -import FormDrawer from '@/pages/_components/form-drawer'; +import { FormDrawer } from '@gpustack/core-ui'; import { BenchmarkListItem } from '../config/types'; import DetailContent from './detail-content'; diff --git a/src/pages/benchmark/components/environment/index.tsx b/src/pages/benchmark/components/environment/index.tsx index 1acee7e0..52a5dbbc 100644 --- a/src/pages/benchmark/components/environment/index.tsx +++ b/src/pages/benchmark/components/environment/index.tsx @@ -1,7 +1,9 @@ -import RowChildren from '@/components/seal-table/components/row-children'; -import SealTable from '@/components/seal-table/index'; -import TableContext from '@/components/seal-table/table-context'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; +import { + RowChildren, + Table as SealTable, + TableProvider +} from '@gpustack/core-ui'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import { Col, Row } from 'antd'; import _ from 'lodash'; @@ -175,7 +177,7 @@ const Environment: React.FC = () => { return ( - { columns={workerColumns} expandable={true} > - + ); }; diff --git a/src/pages/benchmark/components/environment/use-gpu-columns.tsx b/src/pages/benchmark/components/environment/use-gpu-columns.tsx index 301b287f..aefdc35c 100644 --- a/src/pages/benchmark/components/environment/use-gpu-columns.tsx +++ b/src/pages/benchmark/components/environment/use-gpu-columns.tsx @@ -1,7 +1,6 @@ -import AutoTooltip from '@/components/auto-tooltip'; import { convertFileSize } from '@/utils'; +import { AutoTooltip } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; - export default function useGPUColumns(): { title: string; dataIndex: string; diff --git a/src/pages/benchmark/components/environment/use-worker-columns.tsx b/src/pages/benchmark/components/environment/use-worker-columns.tsx index c8e1be4f..84f95375 100644 --- a/src/pages/benchmark/components/environment/use-worker-columns.tsx +++ b/src/pages/benchmark/components/environment/use-worker-columns.tsx @@ -1,8 +1,7 @@ -import AutoTooltip from '@/components/auto-tooltip'; import { convertFileSize } from '@/utils'; +import { AutoTooltip } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tag } from 'antd'; - export default function useWorkerColumns(): { title: string; dataIndex: string; diff --git a/src/pages/benchmark/components/left-actions.tsx b/src/pages/benchmark/components/left-actions.tsx index 2bff3045..40889baa 100644 --- a/src/pages/benchmark/components/left-actions.tsx +++ b/src/pages/benchmark/components/left-actions.tsx @@ -1,6 +1,6 @@ -import { FiltersButton } from '@/components/page-tools'; import { modelCategoriesMap } from '@/pages/llmodels/config'; import { SearchOutlined, SyncOutlined } from '@ant-design/icons'; +import { FiltersButton } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Input, Space } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/benchmark/components/logs/index.tsx b/src/pages/benchmark/components/logs/index.tsx index 32acab4d..0c643673 100644 --- a/src/pages/benchmark/components/logs/index.tsx +++ b/src/pages/benchmark/components/logs/index.tsx @@ -1,4 +1,4 @@ -import LogsViewer from '@/components/logs-viewer/virtual-log-list'; +import { LogsViewer } from '@gpustack/core-ui'; import React, { useRef } from 'react'; import { BENCHMARKS_API } from '../../apis'; import { useDetailContext } from '../../config/detail-context'; diff --git a/src/pages/benchmark/components/right-actions.tsx b/src/pages/benchmark/components/right-actions.tsx index 7da00fc5..cc875c89 100644 --- a/src/pages/benchmark/components/right-actions.tsx +++ b/src/pages/benchmark/components/right-actions.tsx @@ -1,6 +1,5 @@ -import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { DropdownButtons, IconFont } from '@gpustack/core-ui'; import { Button, Space } from 'antd'; import React from 'react'; diff --git a/src/pages/benchmark/components/row-actions.tsx b/src/pages/benchmark/components/row-actions.tsx index 41cbd966..0c6b5cb2 100644 --- a/src/pages/benchmark/components/row-actions.tsx +++ b/src/pages/benchmark/components/row-actions.tsx @@ -1,8 +1,7 @@ -import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; -import icons from '@/components/icon-font/icons'; import useDownloadLogs from '@/hooks/use-download-logs'; import { DownloadOutlined } from '@ant-design/icons'; +import { DropdownButtons, IconFont } from '@gpustack/core-ui'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import { BENCHMARKS_API } from '../apis'; import { BenchmarkStatusValueMap } from '../config'; import { BenchmarkListItem as ListItem } from '../config/types'; diff --git a/src/pages/benchmark/components/summary/instance.tsx b/src/pages/benchmark/components/summary/instance.tsx index c941b4ff..de9cd86b 100644 --- a/src/pages/benchmark/components/summary/instance.tsx +++ b/src/pages/benchmark/components/summary/instance.tsx @@ -1,9 +1,8 @@ -import AutoTooltip from '@/components/auto-tooltip'; +import { AutoTooltip } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Descriptions, Flex, Tag } from 'antd'; import React, { useMemo } from 'react'; import { useDetailContext } from '../../config/detail-context'; - const Instance: React.FC = () => { const intl = useIntl(); const { detailData } = useDetailContext(); diff --git a/src/pages/benchmark/components/summary/section.tsx b/src/pages/benchmark/components/summary/section.tsx index 2e2462fb..052455c0 100644 --- a/src/pages/benchmark/components/summary/section.tsx +++ b/src/pages/benchmark/components/summary/section.tsx @@ -1,5 +1,4 @@ -import HeadlessCollapse from '@/components/collapse-container/headless-collapse'; -import IconFont from '@/components/icon-font'; +import { HeadlessCollapse, IconFont } from '@gpustack/core-ui'; import { useState } from 'react'; import styled from 'styled-components'; diff --git a/src/pages/benchmark/components/view-logs-modal.tsx b/src/pages/benchmark/components/view-logs-modal.tsx index 76e2bcb8..958296be 100644 --- a/src/pages/benchmark/components/view-logs-modal.tsx +++ b/src/pages/benchmark/components/view-logs-modal.tsx @@ -1,4 +1,4 @@ -import LogsViewer from '@/components/logs-viewer/virtual-log-list'; +import { LogsViewer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Modal } from 'antd'; import React, { useCallback, useEffect } from 'react'; diff --git a/src/pages/benchmark/details.tsx b/src/pages/benchmark/details.tsx index 414bdce4..c6f437c2 100644 --- a/src/pages/benchmark/details.tsx +++ b/src/pages/benchmark/details.tsx @@ -1,5 +1,4 @@ -import DeleteModal from '@/components/delete-modal'; -import BaseSelect from '@/components/seal-form/base/select'; +import { BaseSelect, DeleteModal } from '@gpustack/core-ui'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import React, { useEffect, useRef } from 'react'; @@ -18,7 +17,6 @@ import useQueryBenchmarkList from './services/use-query-benchmarks'; import useQueryDetail from './services/use-query-detail'; import useQueryProfiles from './services/use-query-profiles'; import useStopBenchmark from './services/use-stop-benchmark'; - const Details: React.FC = () => { const modalRef = useRef(null); const navigate = useNavigate(); diff --git a/src/pages/benchmark/filters/index.tsx b/src/pages/benchmark/filters/index.tsx index e38fa043..054fc23d 100644 --- a/src/pages/benchmark/filters/index.tsx +++ b/src/pages/benchmark/filters/index.tsx @@ -1,13 +1,11 @@ -import BaseSelect from '@/components/seal-form/base/select'; -import FilterForm from '@/pages/_components/filter-form'; import { modelCategoriesMap } from '@/pages/llmodels/config'; import { SearchOutlined } from '@ant-design/icons'; +import { BaseSelect, FilterForm } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, Input } from 'antd'; import { forwardRef, useImperativeHandle, useRef } from 'react'; import styled from 'styled-components'; import { profileOptions } from '../config'; - const Content = styled.div` display: flex; flex-direction: column; diff --git a/src/pages/benchmark/forms/basic.tsx b/src/pages/benchmark/forms/basic.tsx index e55c0d14..402aab77 100644 --- a/src/pages/benchmark/forms/basic.tsx +++ b/src/pages/benchmark/forms/basic.tsx @@ -1,9 +1,8 @@ -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { modelNameReg, PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; +import { Input as CInput, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { useEffect } from 'react'; @@ -60,10 +59,10 @@ const BasicForm: React.FC = () => { } ]} > - + > name="cluster_id" @@ -82,22 +81,22 @@ const BasicForm: React.FC = () => { > name="model_name" hidden={true}> - + name="model_id" hidden={true}> - + name="model_instance_name" hidden={true}> - + name="description"> - + > ); diff --git a/src/pages/benchmark/forms/dataset.tsx b/src/pages/benchmark/forms/dataset.tsx index 2b0e11bc..92ca0e2e 100644 --- a/src/pages/benchmark/forms/dataset.tsx +++ b/src/pages/benchmark/forms/dataset.tsx @@ -1,7 +1,6 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; +import { AutoTooltip, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, Select } from 'antd'; import _ from 'lodash'; @@ -10,7 +9,6 @@ import { ProfileValueMap } from '../config'; import { useFormContext } from '../config/form-context'; import { FormData } from '../config/types'; import RandomSettingsForm from './random-settings'; - const DatasetForm: React.FC = () => { const intl = useIntl(); const form = Form.useFormInstance(); diff --git a/src/pages/benchmark/forms/index.tsx b/src/pages/benchmark/forms/index.tsx index 2e373374..2b808c6b 100644 --- a/src/pages/benchmark/forms/index.tsx +++ b/src/pages/benchmark/forms/index.tsx @@ -1,9 +1,7 @@ -import IconFont from '@/components/icon-font'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import CollapsePanel from '@/pages/_components/collapse-panel'; -import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; -import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; +import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui'; +import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { diff --git a/src/pages/benchmark/forms/labels.tsx b/src/pages/benchmark/forms/labels.tsx index 96e28703..cf067bb4 100644 --- a/src/pages/benchmark/forms/labels.tsx +++ b/src/pages/benchmark/forms/labels.tsx @@ -1,4 +1,4 @@ -import LabelSelector from '@/components/label-selector'; +import { LabelSelector } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/benchmark/forms/model-instance.tsx b/src/pages/benchmark/forms/model-instance.tsx index 74d39431..bc2ac427 100644 --- a/src/pages/benchmark/forms/model-instance.tsx +++ b/src/pages/benchmark/forms/model-instance.tsx @@ -1,4 +1,3 @@ -import SealCascader from '@/components/seal-form/seal-cascader'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; import { @@ -9,6 +8,7 @@ import { import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; import { useQueryModelInstancesList } from '@/pages/llmodels/services/use-query-model-instances'; import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list'; +import { Cascader as SealCascader } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, Tooltip } from 'antd'; import React, { useEffect } from 'react'; diff --git a/src/pages/benchmark/forms/random-settings.tsx b/src/pages/benchmark/forms/random-settings.tsx index 578b42b3..bd8a3afa 100644 --- a/src/pages/benchmark/forms/random-settings.tsx +++ b/src/pages/benchmark/forms/random-settings.tsx @@ -1,7 +1,9 @@ -import SealInputNumber from '@/components/seal-form/input-number'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; +import { + InputNumber as CInputNumber, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { useMemo } from 'react'; @@ -62,14 +64,14 @@ const RandomSettingsForm: React.FC<{ } ]} > - + > name="dataset_output_tokens" @@ -83,24 +85,24 @@ const RandomSettingsForm: React.FC<{ } ]} > - + > name="dataset_seed" getValueProps={(value) => ({ value: value || null })} > - + > )} @@ -114,11 +116,11 @@ const RandomSettingsForm: React.FC<{ } ]} > - + > name="total_requests" @@ -129,12 +131,12 @@ const RandomSettingsForm: React.FC<{ } ]} > - + > ); diff --git a/src/pages/benchmark/hooks/use-benchmark-columns.tsx b/src/pages/benchmark/hooks/use-benchmark-columns.tsx index 2354dcb9..f7647f56 100644 --- a/src/pages/benchmark/hooks/use-benchmark-columns.tsx +++ b/src/pages/benchmark/hooks/use-benchmark-columns.tsx @@ -1,13 +1,12 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; import { tableSorter } from '@/config/settings'; +import { AutoTooltip } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Typography } from 'antd'; import { ColumnsType } from 'antd/es/table'; import { useMemo } from 'react'; import RowActions from '../components/row-actions'; import { BenchmarkListItem as ListItem } from '../config/types'; - const useBenchmarkColumns = (params: { sortOrder: string[]; columns: ColumnsType; diff --git a/src/pages/benchmark/hooks/use-column-settings.tsx b/src/pages/benchmark/hooks/use-column-settings.tsx index e16e950f..bd5b9c6f 100644 --- a/src/pages/benchmark/hooks/use-column-settings.tsx +++ b/src/pages/benchmark/hooks/use-column-settings.tsx @@ -1,8 +1,10 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import InfoColumn from '@/components/simple-table/info-column'; -import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; -import ColumnSettings from '@/pages/_components/column-settings'; +import { + AutoTooltip, + ColumnSettings, + InfoColumn, + StatusTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Typography } from 'antd'; import dayjs from 'dayjs'; @@ -14,7 +16,6 @@ import { BenchmarkStatusValueMap } from '../config'; import { BenchmarkListItem as ListItem } from '../config/types'; - // sort by this order const allFields = [ 'cluster_id', diff --git a/src/pages/benchmark/index.tsx b/src/pages/benchmark/index.tsx index be86fa1e..08daddf5 100644 --- a/src/pages/benchmark/index.tsx +++ b/src/pages/benchmark/index.tsx @@ -1,11 +1,9 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useTableFetch from '@/hooks/use-table-fetch'; import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn, useToggle } from 'ahooks'; import { ConfigProvider, Table, message } from 'antd'; diff --git a/src/pages/cluster-management/cluster-create.tsx b/src/pages/cluster-management/cluster-create.tsx index 91c56d84..a84191af 100644 --- a/src/pages/cluster-management/cluster-create.tsx +++ b/src/pages/cluster-management/cluster-create.tsx @@ -1,7 +1,7 @@ import { systemConfigAtom } from '@/atoms/system'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; +import { ColumnWrapper } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useAtom } from 'jotai'; import _ from 'lodash'; diff --git a/src/pages/cluster-management/cluster-detail.tsx b/src/pages/cluster-management/cluster-detail.tsx index 777ac896..f6df61b2 100644 --- a/src/pages/cluster-management/cluster-detail.tsx +++ b/src/pages/cluster-management/cluster-detail.tsx @@ -1,8 +1,8 @@ import { clusterDetailAtom } from '@/atoms/clusters'; -import IconFont from '@/components/icon-font'; import Deployments from '@/pages/llmodels/deployments'; import GPUList from '@/pages/resources/components/gpus'; import WorkerList from '@/pages/resources/components/workers'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { Tabs } from 'antd'; import { useAtomValue } from 'jotai'; diff --git a/src/pages/cluster-management/cluster-modal.tsx b/src/pages/cluster-management/cluster-modal.tsx index 94148c31..5a7b11d2 100644 --- a/src/pages/cluster-management/cluster-modal.tsx +++ b/src/pages/cluster-management/cluster-modal.tsx @@ -1,5 +1,5 @@ -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageAction } from '@/config'; +import { GSDrawer } from '@gpustack/core-ui'; import React from 'react'; import ClusterCreate from './cluster-create'; diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 107e57f2..7edca203 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -1,16 +1,18 @@ import { clusterSessionAtom, expandKeysAtom } from '@/atoms/clusters'; -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; -import SealTable from '@/components/seal-table'; -import TableContext from '@/components/seal-table/table-context'; -import { TableOrder } from '@/components/seal-table/types'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableFetch from '@/hooks/use-table-fetch'; import useWatchList from '@/hooks/use-watch-list'; +import { + DeleteModal, + FilterBar, + IconFont, + Table as SealTable, + TableProvider +} from '@gpustack/core-ui'; +import { TableOrder } from '@gpustack/core-ui/lib/components/table/types'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { message } from 'antd'; @@ -354,7 +356,7 @@ const Clusters: React.FC = () => { > } > - { onChange: handlePageChange }} > - + = ({ } ]} > - + > {provider === ProviderValueMap.DigitalOcean && ( <> @@ -90,7 +89,7 @@ const AddModal: React.FC = ({ } ]} > - = ({ }} > } - > + > )} name="description" rules={[{ required: false }]}> - + > diff --git a/src/pages/cluster-management/components/add-pool.tsx b/src/pages/cluster-management/components/add-pool.tsx index 06d6d008..9953567c 100644 --- a/src/pages/cluster-management/components/add-pool.tsx +++ b/src/pages/cluster-management/components/add-pool.tsx @@ -1,5 +1,5 @@ import { PageActionType } from '@/config/types'; -import FormDrawer from '@/pages/_components/form-drawer'; +import { FormDrawer } from '@gpustack/core-ui'; import React, { useEffect, useRef } from 'react'; import { ProviderType } from '../config'; import { diff --git a/src/pages/cluster-management/components/add-worker-command.tsx b/src/pages/cluster-management/components/add-worker-command.tsx index 7ed26bee..ce04dad7 100644 --- a/src/pages/cluster-management/components/add-worker-command.tsx +++ b/src/pages/cluster-management/components/add-worker-command.tsx @@ -1,5 +1,5 @@ -import HighlightCode from '@/components/highlight-code'; import { addWorkerGuide } from '@/pages/resources/config'; +import { HighlightCode } from '@gpustack/core-ui'; import React from 'react'; type ViewModalProps = { diff --git a/src/pages/cluster-management/components/add-worker/index.tsx b/src/pages/cluster-management/components/add-worker/index.tsx index 907242a5..fe73747d 100644 --- a/src/pages/cluster-management/components/add-worker/index.tsx +++ b/src/pages/cluster-management/components/add-worker/index.tsx @@ -1,7 +1,6 @@ -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { createAxiosToken } from '@/hooks/use-chunk-request'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message'; +import { ColumnWrapper, GSDrawer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Alert } from 'antd'; import React, { useEffect } from 'react'; diff --git a/src/pages/cluster-management/components/add-worker/select-cluster.tsx b/src/pages/cluster-management/components/add-worker/select-cluster.tsx index 708c96b9..cfa94599 100644 --- a/src/pages/cluster-management/components/add-worker/select-cluster.tsx +++ b/src/pages/cluster-management/components/add-worker/select-cluster.tsx @@ -1,4 +1,4 @@ -import BaseSelect from '@/components/seal-form/base/select'; +import { BaseSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Spin, Typography } from 'antd'; import { useEffect, useState } from 'react'; @@ -6,7 +6,6 @@ import { useAddWorkerContext } from './add-worker-context'; import { AddWorkerStepProps, StepNamesMap } from './config'; import { Title } from './constainers'; import StepCollapse from './step-collapse'; - const SelectCluster: React.FC = ({ disabled }) => { const { clusterList, diff --git a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx index 1cfaa448..29076f4c 100644 --- a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx +++ b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx @@ -1,5 +1,5 @@ -import AlertInfoBlock from '@/components/alert-info/block'; import { ExclamationCircleFilled } from '@ant-design/icons'; +import { AlertBlockInfo as AlertInfoBlock } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Input, Switch, Typography } from 'antd'; import React, { useEffect } from 'react'; diff --git a/src/pages/cluster-management/components/add-worker/step-collapse.tsx b/src/pages/cluster-management/components/add-worker/step-collapse.tsx index 3721f3bf..9a0934bb 100644 --- a/src/pages/cluster-management/components/add-worker/step-collapse.tsx +++ b/src/pages/cluster-management/components/add-worker/step-collapse.tsx @@ -1,4 +1,4 @@ -import CollapsibleContainer from '@/components/collapse-container'; +import { CollapseContainer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import React from 'react'; @@ -72,7 +72,7 @@ const StepCollapse: React.FC = ({ collapseKey?.has(name) ? 'step-collapse-open' : 'step-collapse' } > - = ({ )} - + ); }; diff --git a/src/pages/cluster-management/components/add-worker/vendor-notes.tsx b/src/pages/cluster-management/components/add-worker/vendor-notes.tsx index b37e21b1..b5c3c16f 100644 --- a/src/pages/cluster-management/components/add-worker/vendor-notes.tsx +++ b/src/pages/cluster-management/components/add-worker/vendor-notes.tsx @@ -1,5 +1,5 @@ -import AlertInfoBlock from '@/components/alert-info/block'; import { ExclamationCircleFilled } from '@ant-design/icons'; +import { AlertBlockInfo } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useAddWorkerContext } from './add-worker-context'; import { NotesWrapper } from './constainers'; @@ -15,7 +15,7 @@ const VendorNotes = () => { } as { label: string; notes: string[] }); return ( - { ) : null } - > + > ); }; diff --git a/src/pages/cluster-management/components/check-env-command.tsx b/src/pages/cluster-management/components/check-env-command.tsx index 14d348b0..9dc43929 100644 --- a/src/pages/cluster-management/components/check-env-command.tsx +++ b/src/pages/cluster-management/components/check-env-command.tsx @@ -1,5 +1,5 @@ -import HighlightCode from '@/components/highlight-code'; import { addWorkerGuide } from '@/pages/resources/config'; +import { HighlightCode } from '@gpustack/core-ui'; import React from 'react'; import { ProviderType } from '../config'; diff --git a/src/pages/cluster-management/components/cloud-options.tsx b/src/pages/cluster-management/components/cloud-options.tsx index 1d762865..7d65f475 100644 --- a/src/pages/cluster-management/components/cloud-options.tsx +++ b/src/pages/cluster-management/components/cloud-options.tsx @@ -1,6 +1,7 @@ -import DropDownActions from '@/components/drop-down-actions'; -import ListMap from '@/components/dynamic-form/components/list-map'; -import { FieldSchema } from '@/components/dynamic-form/config/types'; +import { DropdownActions } from '@gpustack/core-ui'; +import ListMap from '@gpustack/core-ui/lib/components/dynamic-form/components/list-map'; +import { FieldSchema } from '@gpustack/core-ui/lib/components/dynamic-form/config/types'; + import { PlusOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; @@ -87,14 +88,14 @@ const CloudOptions: React.FC<{ return ( <> - <DropDownActions menu={menu}> + <DropdownActions menu={menu}> <Button variant="filled" color="default"> <PlusOutlined /> <span> {intl.formatMessage({ id: 'clusters.workerpool.cloudOptions' })} </span> </Button> - </DropDownActions> + </DropdownActions> {fieldList.length > 0 && fieldList.map((field) => ( diff --git a/src/pages/cluster-management/components/cloud-provider-form.tsx b/src/pages/cluster-management/components/cloud-provider-form.tsx index 2c57a7e0..a436ce8e 100644 --- a/src/pages/cluster-management/components/cloud-provider-form.tsx +++ b/src/pages/cluster-management/components/cloud-provider-form.tsx @@ -1,9 +1,8 @@ import { fromClusterCreationAtom } from '@/atoms/clusters'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; +import { Input as CInput, Select as SealSelect } from '@gpustack/core-ui'; import { Link, useIntl } from '@umijs/max'; import { Form } from 'antd'; import { useAtom } from 'jotai'; @@ -193,14 +192,14 @@ const CloudProvider: React.FC = (props) => { } ]} > - + > ); diff --git a/src/pages/cluster-management/components/cluster-detail-modal.tsx b/src/pages/cluster-management/components/cluster-detail-modal.tsx index 7a46c976..201ee870 100644 --- a/src/pages/cluster-management/components/cluster-detail-modal.tsx +++ b/src/pages/cluster-management/components/cluster-detail-modal.tsx @@ -1,4 +1,4 @@ -import GSDrawer from '@/components/scroller-modal/gs-drawer'; +import { GSDrawer } from '@gpustack/core-ui'; import React from 'react'; import { ClusterListItem } from '../config/types'; import ClusterDetailContent from './cluster-metrics'; diff --git a/src/pages/cluster-management/components/cluster-form.tsx b/src/pages/cluster-management/components/cluster-form.tsx index ddaf821b..fe954b15 100644 --- a/src/pages/cluster-management/components/cluster-form.tsx +++ b/src/pages/cluster-management/components/cluster-form.tsx @@ -1,9 +1,11 @@ -import SealInput from '@/components/seal-form/seal-input'; -import SealTextArea from '@/components/seal-form/seal-textarea'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import CollapsePanel from '@/pages/_components/collapse-panel'; import { json2Yaml, yaml2Json } from '@/pages/backends/config'; +import { + Input as CInput, + CollapsePanel, + Textarea as SealTextArea +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; @@ -164,11 +166,11 @@ const ClusterForm: React.FC = forwardRef( } ]} > - + > {provider === ProviderValueMap.DigitalOcean && ( { System Load - + - + - + - + - + - + - + - + diff --git a/src/pages/cluster-management/components/detail/cluster-basic.tsx b/src/pages/cluster-management/components/detail/cluster-basic.tsx index 127ef561..590383f9 100644 --- a/src/pages/cluster-management/components/detail/cluster-basic.tsx +++ b/src/pages/cluster-management/components/detail/cluster-basic.tsx @@ -1,7 +1,5 @@ -import CardWrapper from '@/components/card-wrapper'; -import IconFont from '@/components/icon-font'; -import StatusTag from '@/components/status-tag'; import { StarFilled } from '@ant-design/icons'; +import { CardWrapper, IconFont, StatusTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import type { DescriptionsProps } from 'antd'; import { Descriptions, Tooltip } from 'antd'; diff --git a/src/pages/cluster-management/components/detail/cluster-system-load.tsx b/src/pages/cluster-management/components/detail/cluster-system-load.tsx index c307e138..ba28bcac 100644 --- a/src/pages/cluster-management/components/detail/cluster-system-load.tsx +++ b/src/pages/cluster-management/components/detail/cluster-system-load.tsx @@ -1,4 +1,4 @@ -import CardWrapper from '@/components/card-wrapper'; +import { CardWrapper } from '@gpustack/core-ui'; import { Col, Progress, Row, Tag } from 'antd'; import { round } from 'lodash'; import React, { useEffect } from 'react'; diff --git a/src/pages/cluster-management/components/k8s-provider-form.tsx b/src/pages/cluster-management/components/k8s-provider-form.tsx index 2e56de42..1f5667fe 100644 --- a/src/pages/cluster-management/components/k8s-provider-form.tsx +++ b/src/pages/cluster-management/components/k8s-provider-form.tsx @@ -1,4 +1,4 @@ -import SealSelect from '@/components/seal-form/seal-select'; +import { Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; diff --git a/src/pages/cluster-management/components/k8s-volume-mount.tsx b/src/pages/cluster-management/components/k8s-volume-mount.tsx index 0fb62749..1b3d879b 100644 --- a/src/pages/cluster-management/components/k8s-volume-mount.tsx +++ b/src/pages/cluster-management/components/k8s-volume-mount.tsx @@ -1,12 +1,14 @@ -import CollapsibleContainer from '@/components/collapse-container'; -import SealCheckbox from '@/components/seal-form/seal-checkbox'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; -import SealSwitch from '@/components/seal-form/seal-switch'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; +import { + Input as CInput, + CollapseContainer, + Checkbox as SealCheckbox, + Select as SealSelect, + Switch as SealSwitch +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Flex, Form } from 'antd'; import React, { useEffect, useState } from 'react'; @@ -157,11 +159,11 @@ const VolumeMountsForm: React.FC<{ action: PageActionType }> = ({ action }) => { borderRadius: 'var(--ant-border-radius-lg)' }} > - onToggle(open, name)} + onToggle={(open: boolean) => onToggle(open, name)} styles={{ body: collapseKey.has(name) ? { padding: 16 } : {}, content: { paddingTop: 0 }, @@ -203,13 +205,12 @@ const VolumeMountsForm: React.FC<{ action: PageActionType }> = ({ action }) => { } ]} > - + > {/* Mount Path */} @@ -232,7 +233,7 @@ const VolumeMountsForm: React.FC<{ action: PageActionType }> = ({ action }) => { } ]} > - = ({ action }) => { label={intl.formatMessage({ id: 'clusters.volume.mountPath' })} - > + > @@ -307,7 +308,7 @@ const VolumeMountsForm: React.FC<{ action: PageActionType }> = ({ action }) => { } ]} > - = ({ action }) => { label={intl.formatMessage({ id: 'clusters.volume.sourceType.hostPath' })} - > + > @@ -364,7 +365,7 @@ const VolumeMountsForm: React.FC<{ action: PageActionType }> = ({ action }) => { } ]} > - = ({ action }) => { } ]} > - = ({ action }) => { )} - + ); }); diff --git a/src/pages/cluster-management/components/pool-form.tsx b/src/pages/cluster-management/components/pool-form.tsx index eec1278f..78e563f4 100644 --- a/src/pages/cluster-management/components/pool-form.tsx +++ b/src/pages/cluster-management/components/pool-form.tsx @@ -2,20 +2,21 @@ import { regionInstanceTypeListAtom, regionOSImageListAtom } from '@/atoms/clusters'; -import CollapsibleContainer, { - CollapsibleContainerProps -} from '@/components/collapse-container'; -import IconFont from '@/components/icon-font'; -import LabelSelector from '@/components/label-selector'; -import AutoComplete from '@/components/seal-form/auto-complete'; -import SealInputNumber from '@/components/seal-form/input-number'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useAppUtils from '@/hooks/use-app-utils'; import { CardContainer } from '@/pages/llmodels/components/gpu-card'; import { DeleteOutlined } from '@ant-design/icons'; +import { + AutoComplete, + Input as CInput, + InputNumber as CInputNumber, + CollapseContainer, + IconFont, + LabelSelector, + Select as SealSelect +} from '@gpustack/core-ui'; +import { type CollapseContainerProps } from '@gpustack/core-ui/lib/components/collapse-container'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, Form } from 'antd'; @@ -143,7 +144,7 @@ type AddModalProps = { onFinish: (values: FormData) => void; onDelete?: () => void; showDelete?: boolean; - collapseProps?: CollapsibleContainerProps; + collapseProps?: CollapseContainerProps; }; const InstanceSpecData: React.FC<{ instanceSpec: Record }> = ({ @@ -155,7 +156,7 @@ const InstanceSpecData: React.FC<{ instanceSpec: Record }> = ({ .filter(([key, value]) => value) .map(([key, value]) => ( ))} @@ -305,7 +306,7 @@ const PoolForm: React.FC = forwardRef((props, ref) => { })); return ( - = forwardRef((props, ref) => { } ]} > - + > name="instance_type" @@ -394,12 +395,12 @@ const PoolForm: React.FC = forwardRef((props, ref) => { } ]} > - + > name="batch_size" @@ -413,7 +414,7 @@ const PoolForm: React.FC = forwardRef((props, ref) => { } ]} > - = forwardRef((props, ref) => { id: 'clusters.workerpool.batchSize' })} required - > + > @@ -486,12 +487,12 @@ const PoolForm: React.FC = forwardRef((props, ref) => { name="os_image" hidden> - + - + ); }); diff --git a/src/pages/cluster-management/components/pool-rows.tsx b/src/pages/cluster-management/components/pool-rows.tsx index 3cd44ecf..7ec8379f 100644 --- a/src/pages/cluster-management/components/pool-rows.tsx +++ b/src/pages/cluster-management/components/pool-rows.tsx @@ -1,9 +1,11 @@ -import DeleteModal from '@/components/delete-modal'; -import CellContent from '@/components/seal-table/components/cell-content'; -import RowChildren from '@/components/seal-table/components/row-children'; -import RowContext from '@/components/seal-table/row-context'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; +import { + CellContent, + DeleteModal, + RowChildren, + TableRowProvider +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Col, message, Row } from 'antd'; @@ -120,7 +122,7 @@ const PoolRows: React.FC = ({ key={data.id} style={{ borderRadius: 'var(--ant-table-header-border-radius)' }} > - + {columns.map((col: Record) => { @@ -139,7 +141,7 @@ const PoolRows: React.FC = ({ })} - + ); })} diff --git a/src/pages/cluster-management/components/provider-catalog.tsx b/src/pages/cluster-management/components/provider-catalog.tsx index 6bfebd3d..474b648e 100644 --- a/src/pages/cluster-management/components/provider-catalog.tsx +++ b/src/pages/cluster-management/components/provider-catalog.tsx @@ -1,5 +1,4 @@ -import IconFont from '@/components/icon-font'; -import Card from '@/components/templates/card'; +import { IconFont, TemplateCard } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import React, { useMemo } from 'react'; @@ -147,7 +146,7 @@ const ProviderCatalog: React.FC = ({ key={action.key} > - onSelect?.(action.key as string, action)} active={current === action.key} @@ -155,7 +154,7 @@ const ProviderCatalog: React.FC = ({ clickable={clickable} header={renderTitle(action)} icon={action.icon} - > + > ))} diff --git a/src/pages/cluster-management/components/register-cluster-inner.tsx b/src/pages/cluster-management/components/register-cluster-inner.tsx index 0f8cf15f..5dc74002 100644 --- a/src/pages/cluster-management/components/register-cluster-inner.tsx +++ b/src/pages/cluster-management/components/register-cluster-inner.tsx @@ -1,4 +1,4 @@ -import HighlightCode from '@/components/highlight-code'; +import { HighlightCode } from '@gpustack/core-ui'; import React, { useMemo } from 'react'; import { generateK8sRegisterCommand } from '../config'; diff --git a/src/pages/cluster-management/components/support-gpus.tsx b/src/pages/cluster-management/components/support-gpus.tsx index 60be47b3..4709e953 100644 --- a/src/pages/cluster-management/components/support-gpus.tsx +++ b/src/pages/cluster-management/components/support-gpus.tsx @@ -7,12 +7,12 @@ import mooreLogo from '@/assets/logo/moore-logo.png'; import nvidiaLogo from '@/assets/logo/nvidia.png'; import theadLogoEN from '@/assets/logo/t-head-en.png'; import theadLogoZH from '@/assets/logo/t-head-zh.png'; -import IconFont from '@/components/icon-font'; import useUserSettings from '@/hooks/use-user-settings'; import { AddWorkerDockerNotes, GPUDriverMap } from '@/pages/resources/config/gpu-driver'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import styled from 'styled-components'; import ProviderCatalog from './provider-catalog'; diff --git a/src/pages/cluster-management/components/trend-chart.tsx b/src/pages/cluster-management/components/trend-chart.tsx index c62428c3..79903605 100644 --- a/src/pages/cluster-management/components/trend-chart.tsx +++ b/src/pages/cluster-management/components/trend-chart.tsx @@ -1,4 +1,4 @@ -import LineChart from '@/components/echarts/line-chart'; +import { LineChart } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import dayjs from 'dayjs'; import _ from 'lodash'; diff --git a/src/pages/cluster-management/components/volumes-config.tsx b/src/pages/cluster-management/components/volumes-config.tsx index 42941399..1e1a62b7 100644 --- a/src/pages/cluster-management/components/volumes-config.tsx +++ b/src/pages/cluster-management/components/volumes-config.tsx @@ -1,6 +1,6 @@ -import ListMap from '@/components/dynamic-form/components/list-map'; -import { statusType } from '@/components/dynamic-form/config/types'; -import useValidateFields from '@/components/dynamic-form/hooks/use-validate-fields'; +import ListMap from '@gpustack/core-ui/lib/components/dynamic-form/components/list-map'; +import { statusType } from '@gpustack/core-ui/lib/components/dynamic-form/config/types'; +import useValidateFields from '@gpustack/core-ui/lib/components/dynamic-form/hooks/use-validate-fields'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { forwardRef, useState } from 'react'; diff --git a/src/pages/cluster-management/components/worker-pools.tsx b/src/pages/cluster-management/components/worker-pools.tsx index 94a621f2..79a98d13 100644 --- a/src/pages/cluster-management/components/worker-pools.tsx +++ b/src/pages/cluster-management/components/worker-pools.tsx @@ -1,7 +1,6 @@ -import DeleteModal from '@/components/delete-modal'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import useTableFetch from '@/hooks/use-table-fetch'; +import { DeleteModal, FilterBar } from '@gpustack/core-ui'; import { useIntl, useSearchParams } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { message, Table } from 'antd'; diff --git a/src/pages/cluster-management/config/cloud-options-config.ts b/src/pages/cluster-management/config/cloud-options-config.ts index 608f17b9..4cc73e64 100644 --- a/src/pages/cluster-management/config/cloud-options-config.ts +++ b/src/pages/cluster-management/config/cloud-options-config.ts @@ -1,4 +1,4 @@ -import { FieldSchema } from '@/components/dynamic-form/config/types'; +import { FieldSchema } from '@gpustack/core-ui/lib/components/dynamic-form/config/types'; export const fields = { volumes: { diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index 6e6c2a2f..47d897f4 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -1,8 +1,8 @@ -import icons from '@/components/icon-font/icons'; import { StatusMaps } from '@/config'; import { GPUSTACK_API_BASE_URL } from '@/config/settings'; import { StatusType } from '@/config/types'; import { GPUsConfigs } from '@/pages/resources/config/gpu-driver'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; export const ClusterStatusValueMap = { Provisioning: 'provisioning', diff --git a/src/pages/cluster-management/config/providers.ts b/src/pages/cluster-management/config/providers.ts index 2fc9a42e..b6ad2272 100644 --- a/src/pages/cluster-management/config/providers.ts +++ b/src/pages/cluster-management/config/providers.ts @@ -1,4 +1,4 @@ -import icons from '@/components/icon-font/icons'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import React from 'react'; import { ProviderValueMap } from '.'; diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index 4dc474a2..77360b32 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -1,11 +1,9 @@ import { clusterSessionAtom, fromClusterCreationAtom } from '@/atoms/clusters'; -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { ConfigProvider, Table, message } from 'antd'; diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index 1f59c915..34a44b4a 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -1,13 +1,15 @@ // columns.ts import { systemConfigAtom } from '@/atoms/system'; -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import icons from '@/components/icon-font/icons'; -import { SealColumnProps } from '@/components/seal-table/types'; -import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; -import GrafanaIcon from '@/pages/_components/grafana-icon'; import { StarFilled } from '@ant-design/icons'; +import { + AutoTooltip, + DropdownButtons, + GrafanaIcon, + StatusTag +} from '@gpustack/core-ui'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; +import { ColumnProps as SealColumnProps } from '@gpustack/core-ui/lib/components/table/types'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import dayjs from 'dayjs'; @@ -20,7 +22,6 @@ import { ProviderValueMap } from '../config'; import { ClusterListItem } from '../config/types'; - const clusterActionList = [ { key: 'edit', diff --git a/src/pages/cluster-management/hooks/use-credential-columns.tsx b/src/pages/cluster-management/hooks/use-credential-columns.tsx index 0a140f09..94749694 100644 --- a/src/pages/cluster-management/hooks/use-credential-columns.tsx +++ b/src/pages/cluster-management/hooks/use-credential-columns.tsx @@ -1,14 +1,12 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; import { tableSorter } from '@/config/settings'; +import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { ProviderLabelMap, credentialActionList } from '../config'; import { CredentialListItem as ListItem } from '../config/types'; - const useCredentialColumns = ( sortOrder: string[], handleSelect: (val: string, record: ListItem) => void diff --git a/src/pages/cluster-management/hooks/use-pools-columns.tsx b/src/pages/cluster-management/hooks/use-pools-columns.tsx index 6430dddf..0ee0d660 100644 --- a/src/pages/cluster-management/hooks/use-pools-columns.tsx +++ b/src/pages/cluster-management/hooks/use-pools-columns.tsx @@ -1,14 +1,12 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import { SealColumnProps } from '@/components/seal-table/types'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui'; +import { ColumnProps } from '@gpustack/core-ui/lib/components/table/types'; import { useIntl } from '@umijs/max'; import dayjs from 'dayjs'; import _ from 'lodash'; import { useMemo } from 'react'; import { RenderOption } from '../components/pool-form'; import { NodePoolListItem as ListItem } from '../config/types'; - const actionItems = [ { key: 'edit', @@ -29,7 +27,7 @@ const actionItems = [ const usePoolsColumns = ( handleSelect: (val: string, record: ListItem) => void, sortOrder?: string[] -): SealColumnProps[] => { +): ColumnProps[] => { const intl = useIntl(); return useMemo(() => { diff --git a/src/pages/cluster-management/step-forms/advance-config.tsx b/src/pages/cluster-management/step-forms/advance-config.tsx index 7392cdb9..7806dd3b 100644 --- a/src/pages/cluster-management/step-forms/advance-config.tsx +++ b/src/pages/cluster-management/step-forms/advance-config.tsx @@ -1,8 +1,6 @@ -import IconFont from '@/components/icon-font'; -import SealInput from '@/components/seal-form/seal-input'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import YamlEditor from '@/pages/_components/yaml-editor'; +import { Input as CInput, IconFont, YamlEditor } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; @@ -71,7 +69,7 @@ const ClusterAdvanceConfig: React.FC<{ } ]} > - + {provider === ProviderValueMap.Kubernetes && ( diff --git a/src/pages/cluster-management/step-forms/worker-pools-form.tsx b/src/pages/cluster-management/step-forms/worker-pools-form.tsx index 69fd6d14..3ea71d30 100644 --- a/src/pages/cluster-management/step-forms/worker-pools-form.tsx +++ b/src/pages/cluster-management/step-forms/worker-pools-form.tsx @@ -1,6 +1,6 @@ -import PageTools from '@/components/page-tools'; import { PageActionType } from '@/config/types'; import { PlusOutlined } from '@ant-design/icons'; +import { PageTools } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, FormInstance } from 'antd'; import { diff --git a/src/pages/dashboard/components/active-table.tsx b/src/pages/dashboard/components/active-table.tsx index 82151e22..b6882cf5 100644 --- a/src/pages/dashboard/components/active-table.tsx +++ b/src/pages/dashboard/components/active-table.tsx @@ -1,12 +1,10 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import PageTools from '@/components/page-tools'; import { modelCategoriesMap } from '@/pages/llmodels/config'; import { convertFileSize } from '@/utils'; +import { AutoTooltip, PageTools } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Col, Row, Table } from 'antd'; import { useContext } from 'react'; import { DashboardContext } from '../config/dashboard-context'; - const NACategories = [ modelCategoriesMap.llm, modelCategoriesMap.embedding, diff --git a/src/pages/dashboard/components/resource-utilization.tsx b/src/pages/dashboard/components/resource-utilization.tsx index a801e6c4..4347d6a3 100644 --- a/src/pages/dashboard/components/resource-utilization.tsx +++ b/src/pages/dashboard/components/resource-utilization.tsx @@ -1,4 +1,4 @@ -import LineChart from '@/components/echarts/line-chart'; +import { LineChart } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import dayjs from 'dayjs'; import _ from 'lodash'; diff --git a/src/pages/dashboard/components/system-load.tsx b/src/pages/dashboard/components/system-load.tsx index d2e90e52..c26a70dc 100644 --- a/src/pages/dashboard/components/system-load.tsx +++ b/src/pages/dashboard/components/system-load.tsx @@ -1,15 +1,16 @@ -import CardWrapper from '@/components/card-wrapper'; -import GaugeChart from '@/components/echarts/gauge'; -import PageTools from '@/components/page-tools'; -import BaseSelect from '@/components/seal-form/base/select'; import { queryClusterList } from '@/pages/cluster-management/apis'; +import { + BaseSelect, + CardWrapper, + GaugeChart, + PageTools +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Col, Row } from 'antd'; import _ from 'lodash'; import { useContext, useEffect, useMemo, useState } from 'react'; import { DashboardContext } from '../config/dashboard-context'; import ResourceUtilization from './resource-utilization'; - const smallChartHeight = 190; const largeChartHeight = 400; const resourceChartHeight = 400; diff --git a/src/pages/dashboard/components/usage-inner/export-data.tsx b/src/pages/dashboard/components/usage-inner/export-data.tsx index cb424bb1..68be9295 100644 --- a/src/pages/dashboard/components/usage-inner/export-data.tsx +++ b/src/pages/dashboard/components/usage-inner/export-data.tsx @@ -1,7 +1,5 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import ModalFooter from '@/components/modal-footer'; -import ScrollerModal from '@/components/scroller-modal'; import { exportJsonToExcel } from '@/utils/excel-reader'; +import { AutoTooltip, ModalFooter, ScrollerModal } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Table, TableColumnType } from 'antd'; import dayjs from 'dayjs'; @@ -10,7 +8,6 @@ import { DASHBOARD_USAGE_API } from '../../apis'; import { TableRow } from '../../config/types'; import FilterBar from './filter-bar'; import useUsageData from './use-usage-data'; - const ExportData: React.FC<{ open: boolean; onCancel: () => void; diff --git a/src/pages/dashboard/components/usage-inner/filter-bar.tsx b/src/pages/dashboard/components/usage-inner/filter-bar.tsx index c7f7fe09..416ea4ae 100644 --- a/src/pages/dashboard/components/usage-inner/filter-bar.tsx +++ b/src/pages/dashboard/components/usage-inner/filter-bar.tsx @@ -1,8 +1,10 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import SealCascader from '@/components/seal-form/seal-cascader'; -import SimpleSelect from '@/components/seal-form/simple-select'; import ProviderLogo from '@/pages/maas-provider/components/provider-logo'; import { DownloadOutlined } from '@ant-design/icons'; +import { + AutoTooltip, + Cascader as SealCascader, + SimpleSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, DatePicker, Tooltip } from 'antd'; import dayjs from 'dayjs'; @@ -10,7 +12,6 @@ import React from 'react'; import { DASHBOARD_STATS_API } from '../../apis'; import useRangePickerPreset from '../../hooks/use-rangepicker-preset'; import FilterBarCss from '../../styles/filter-bar.less'; - const DefaultDateConfig = { maxRange: 60, defaultRange: 29 diff --git a/src/pages/dashboard/components/usage-inner/request-token-inner.tsx b/src/pages/dashboard/components/usage-inner/request-token-inner.tsx index 3b01e140..216b7022 100644 --- a/src/pages/dashboard/components/usage-inner/request-token-inner.tsx +++ b/src/pages/dashboard/components/usage-inner/request-token-inner.tsx @@ -1,7 +1,5 @@ -import CardWrapper from '@/components/card-wrapper'; -import { SimpleCard } from '@/components/card-wrapper/simple-card'; -import MixLineBar from '@/components/echarts/mix-line-bar'; import { formatLargeNumber } from '@/utils'; +import { CardWrapper, MixLineBarChart, SimpleCard } from '@gpustack/core-ui'; import { Button } from 'antd'; import dayjs from 'dayjs'; import React, { useMemo } from 'react'; @@ -103,7 +101,7 @@ const RequestTokenInner: React.FC = (props) => { - = (props) => { smooth={false} legendData={legendData} labelFormatter={labelFormatter} - > + > ); diff --git a/src/pages/dashboard/components/usage-inner/top-user.tsx b/src/pages/dashboard/components/usage-inner/top-user.tsx index 3dfbb21d..c7b3c209 100644 --- a/src/pages/dashboard/components/usage-inner/top-user.tsx +++ b/src/pages/dashboard/components/usage-inner/top-user.tsx @@ -1,5 +1,4 @@ -import CardWrapper from '@/components/card-wrapper'; -import HBar from '@/components/echarts/h-bar'; +import { CardWrapper, HBarChart } from '@gpustack/core-ui'; import React from 'react'; interface TopUserProps { @@ -13,12 +12,12 @@ const TopUser: React.FC = (props) => { return ( - + > ); }; diff --git a/src/pages/dashboard/hooks/use-add-resource.tsx b/src/pages/dashboard/hooks/use-add-resource.tsx index 409a9600..3fe0936a 100644 --- a/src/pages/dashboard/hooks/use-add-resource.tsx +++ b/src/pages/dashboard/hooks/use-add-resource.tsx @@ -1,9 +1,8 @@ import { clusterSessionAtom } from '@/atoms/clusters'; import { hideModalTemporarilyAtom } from '@/atoms/settings'; -import IconFont from '@/components/icon-font'; -import ScrollerModal from '@/components/scroller-modal/index'; import useUserSettings from '@/hooks/use-user-settings'; import useClusterList from '@/pages/cluster-management/hooks/use-cluster-list'; +import { IconFont, ScrollerModal } from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { Button } from 'antd'; import { useAtom } from 'jotai'; diff --git a/src/pages/llmodels/catalog.tsx b/src/pages/llmodels/catalog.tsx index fc3e4507..f6df02ae 100644 --- a/src/pages/llmodels/catalog.tsx +++ b/src/pages/llmodels/catalog.tsx @@ -1,12 +1,14 @@ import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import useBodyScroll from '@/hooks/use-body-scroll'; import useTableFetch from '@/hooks/use-table-fetch'; -import { ScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context'; import { IS_FIRST_LOGIN, writeState } from '@/utils/localstore/index'; import { SearchOutlined } from '@ant-design/icons'; +import { + FilterBar, + IconFont, + InfiniteScrollerProvider +} from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { message } from 'antd'; import { useAtom } from 'jotai'; @@ -143,7 +145,7 @@ const Catalog: React.FC = () => { buttonIcon={} widths={{ input: 230, select: 200 }} > - { title={intl.formatMessage({ id: 'noresult.catalog.title' })} subTitle={intl.formatMessage({ id: 'noresult.catalog.subTitle' })} > - + = { [modelCategoriesMap.embedding]: { api: EMBEDDING_API diff --git a/src/pages/llmodels/components/catalog/catalog-item.tsx b/src/pages/llmodels/components/catalog/catalog-item.tsx index 47f3b8f1..6b93eff5 100644 --- a/src/pages/llmodels/components/catalog/catalog-item.tsx +++ b/src/pages/llmodels/components/catalog/catalog-item.tsx @@ -1,8 +1,6 @@ import fallbackImg from '@/assets/images/img.png'; -import AutoTooltip from '@/components/auto-tooltip'; -import IconFont from '@/components/icon-font'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { categoryConfig } from '@/pages/_components/model-tag'; +import { AutoTooltip, IconFont, ThemeTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Typography } from 'antd'; import classNames from 'classnames'; @@ -11,7 +9,6 @@ import React, { useCallback, useMemo } from 'react'; import { modelCategories } from '../../config'; import { CatalogItem as CatalogItemType } from '../../config/types'; import '../../style/catalog-item.less'; - interface CatalogItemProps { activeId: number; data: CatalogItemType; diff --git a/src/pages/llmodels/components/catalog/catalog-list.tsx b/src/pages/llmodels/components/catalog/catalog-list.tsx index 2ba2a2a3..8f0d1fd7 100644 --- a/src/pages/llmodels/components/catalog/catalog-list.tsx +++ b/src/pages/llmodels/components/catalog/catalog-list.tsx @@ -1,7 +1,9 @@ -import ResizeContainer from '@/components/resize-container'; -import CatalogSkelton from '@/components/templates/card-skelton'; -import InfiniteScroller from '@/pages/_components/infinite-scroller'; -import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context'; +import { + InfiniteScroller, + ResizeContainer, + TemplateCardSkeleton +} from '@gpustack/core-ui'; +import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context'; import { Spin } from 'antd'; import React from 'react'; import styled from 'styled-components'; @@ -45,7 +47,7 @@ const ListSkeleton: React.FC<{ root: 'skelton-wrapper' }} > - {isFirst && } + {isFirst && } )} diff --git a/src/pages/llmodels/components/compatible-alert.tsx b/src/pages/llmodels/components/compatible-alert.tsx index 379fa0d4..d2e906d5 100644 --- a/src/pages/llmodels/components/compatible-alert.tsx +++ b/src/pages/llmodels/components/compatible-alert.tsx @@ -1,5 +1,5 @@ -import AlertBlockInfo from '@/components/alert-info/block'; import { CloseOutlined } from '@ant-design/icons'; +import { AlertBlockInfo } from '@gpustack/core-ui'; import { Button } from 'antd'; import { isArray } from 'lodash'; import React, { useCallback, useMemo } from 'react'; diff --git a/src/pages/llmodels/components/deployment/deploy-builtin-modal.tsx b/src/pages/llmodels/components/deployment/deploy-builtin-modal.tsx index f5c34e97..3a17dd81 100644 --- a/src/pages/llmodels/components/deployment/deploy-builtin-modal.tsx +++ b/src/pages/llmodels/components/deployment/deploy-builtin-modal.tsx @@ -1,9 +1,7 @@ -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import { createAxiosToken } from '@/hooks/use-chunk-request'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; +import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, message } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/llmodels/components/deployment/deploy-modal.tsx b/src/pages/llmodels/components/deployment/deploy-modal.tsx index 61e99663..ba8d5033 100644 --- a/src/pages/llmodels/components/deployment/deploy-modal.tsx +++ b/src/pages/llmodels/components/deployment/deploy-modal.tsx @@ -1,9 +1,8 @@ import { getRequestId } from '@/atoms/models'; -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import useDeferredRequest from '@/hooks/use-deferred-request'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; +import { GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button } from 'antd'; @@ -349,7 +348,7 @@ const AddModal: FC = (props) => { updateSelectedModel(item); - let warningStatus: MessageStatus = { + const warningStatus: MessageStatus = { show: true, title: '', type: 'transition', @@ -496,7 +495,7 @@ const AddModal: FC = (props) => { source: source }); } else { - let backend = checkOnlyAscendNPU(gpuOptions) + const backend = checkOnlyAscendNPU(gpuOptions) ? backendOptionsMap.ascendMindie : backendOptionsMap.vllm; diff --git a/src/pages/llmodels/components/deployment/update-modal.tsx b/src/pages/llmodels/components/deployment/update-modal.tsx index 6d5fa713..40d9c542 100644 --- a/src/pages/llmodels/components/deployment/update-modal.tsx +++ b/src/pages/llmodels/components/deployment/update-modal.tsx @@ -1,8 +1,6 @@ -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; +import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import _ from 'lodash'; import React, { useEffect, useRef } from 'react'; diff --git a/src/pages/llmodels/components/download/index.tsx b/src/pages/llmodels/components/download/index.tsx index 308a5499..5ec29f6d 100644 --- a/src/pages/llmodels/components/download/index.tsx +++ b/src/pages/llmodels/components/download/index.tsx @@ -1,6 +1,5 @@ -import ModalFooter from '@/components/modal-footer'; -import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { ProviderValueMap } from '@/pages/cluster-management/config'; +import { GSDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { debounce } from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; diff --git a/src/pages/llmodels/components/download/target-form.tsx b/src/pages/llmodels/components/download/target-form.tsx index 3c7cd3f8..7c2a9707 100644 --- a/src/pages/llmodels/components/download/target-form.tsx +++ b/src/pages/llmodels/components/download/target-form.tsx @@ -1,9 +1,11 @@ -import SealCascader from '@/components/seal-form/seal-cascader'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; -import TooltipList from '@/components/tooltip-list'; import useAppUtils from '@/hooks/use-app-utils'; import { ModelFileFormData as FormData } from '@/pages/resources/config/types'; +import { + Input as CInput, + Cascader as SealCascader, + Select as SealSelect, + TooltipList +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -131,12 +133,12 @@ const TargetForm: React.FC = forwardRef((props, ref) => { } ]} > - } - > + > ); @@ -222,7 +224,7 @@ const TargetForm: React.FC = forwardRef((props, ref) => { } ]} > - = forwardRef((props, ref) => { label={intl.formatMessage({ id: 'resources.modelfiles.form.localdir' })} - > + > )} diff --git a/src/pages/llmodels/components/gpu-card.tsx b/src/pages/llmodels/components/gpu-card.tsx index 7e7f1d5b..2454f780 100644 --- a/src/pages/llmodels/components/gpu-card.tsx +++ b/src/pages/llmodels/components/gpu-card.tsx @@ -1,11 +1,10 @@ -import AutoTooltip from '@/components/auto-tooltip'; import { convertFileSize } from '@/utils'; +import { AutoTooltip } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import _ from 'lodash'; import React from 'react'; import styled from 'styled-components'; import '../style/gpu-card.less'; - const CardWrapper = styled.div` display: flex; gap: 8px; diff --git a/src/pages/llmodels/components/incompatiable-info.tsx b/src/pages/llmodels/components/incompatiable-info.tsx index d7d666b0..b9d13348 100644 --- a/src/pages/llmodels/components/incompatiable-info.tsx +++ b/src/pages/llmodels/components/incompatiable-info.tsx @@ -1,5 +1,5 @@ -import { TooltipOverlayScroller } from '@/components/overlay-scroller'; import { LoadingOutlined, WarningOutlined } from '@ant-design/icons'; +import { TooltipOverlayScroller } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tag, Tooltip } from 'antd'; import React from 'react'; diff --git a/src/pages/llmodels/components/instance-cells/actions-cell.tsx b/src/pages/llmodels/components/instance-cells/actions-cell.tsx index db644f93..cf610c61 100644 --- a/src/pages/llmodels/components/instance-cells/actions-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/actions-cell.tsx @@ -1,9 +1,11 @@ -import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; -import { HandlerOptions } from '@/hooks/use-chunk-fetch'; -import useDownloadStream from '@/hooks/use-download-stream'; import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons'; +import { + DropdownButtons, + IconFont, + useDownloadStream +} from '@gpustack/core-ui'; +import { HandlerOptions } from '@gpustack/core-ui/lib/hooks/use-chunk-fetch'; import { useIntl } from '@umijs/max'; import { Progress, notification } from 'antd'; import dayjs from 'dayjs'; diff --git a/src/pages/llmodels/components/instance-cells/cpu-offloading-cell.tsx b/src/pages/llmodels/components/instance-cells/cpu-offloading-cell.tsx index e81514c0..0ff45337 100644 --- a/src/pages/llmodels/components/instance-cells/cpu-offloading-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/cpu-offloading-cell.tsx @@ -1,6 +1,5 @@ -import InfoColumn from '@/components/simple-table/info-column'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { InfoCircleOutlined } from '@ant-design/icons'; +import { InfoColumn, ThemeTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/llmodels/components/instance-cells/distribute-info-cell.tsx b/src/pages/llmodels/components/instance-cells/distribute-info-cell.tsx index 5cead539..9966c4ce 100644 --- a/src/pages/llmodels/components/instance-cells/distribute-info-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/distribute-info-cell.tsx @@ -1,9 +1,12 @@ -import { TooltipOverlayScroller } from '@/components/overlay-scroller'; -import SimpleTabel, { ColumnProps } from '@/components/simple-table'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { convertFileSize } from '@/utils'; import { InfoCircleOutlined } from '@ant-design/icons'; +import { + SimpleTable, + ThemeTag, + TooltipOverlayScroller, + type ColumnProps +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import _ from 'lodash'; import React from 'react'; @@ -114,11 +117,11 @@ const DistributedServerList: React.FC = ({ return (
    - + >
    ); }; diff --git a/src/pages/llmodels/components/instance-cells/downloading-status-cell.tsx b/src/pages/llmodels/components/instance-cells/downloading-status-cell.tsx index 92223f71..348726e7 100644 --- a/src/pages/llmodels/components/instance-cells/downloading-status-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/downloading-status-cell.tsx @@ -1,6 +1,6 @@ -import SimpleTabel, { ColumnProps } from '@/components/simple-table'; -import StatusTag from '@/components/status-tag'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; +import { SimpleTable, StatusTag } from '@gpustack/core-ui'; +import { type ColumnProps } from '@gpustack/core-ui/lib/components/simple-table'; import { Progress, Tooltip } from 'antd'; import _ from 'lodash'; import { InstanceStatusMap, status } from '../../config'; @@ -102,21 +102,21 @@ const DownloadingTips = (props: { return (
    {severList.length > 0 && ( - + > )} {draftModelList.length > 0 && ( - + > )}
    ); diff --git a/src/pages/llmodels/components/instance-cells/instance-status-cell.tsx b/src/pages/llmodels/components/instance-cells/instance-status-cell.tsx index e7cd653c..31121ea2 100644 --- a/src/pages/llmodels/components/instance-cells/instance-status-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/instance-status-cell.tsx @@ -1,4 +1,4 @@ -import StatusTag from '@/components/status-tag'; +import { StatusTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import React from 'react'; diff --git a/src/pages/llmodels/components/instance-cells/name-cell.tsx b/src/pages/llmodels/components/instance-cells/name-cell.tsx index 47f3120c..1bf98ca8 100644 --- a/src/pages/llmodels/components/instance-cells/name-cell.tsx +++ b/src/pages/llmodels/components/instance-cells/name-cell.tsx @@ -1,5 +1,3 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import IconFont from '@/components/icon-font'; import { convertFileSize } from '@/utils'; import { HddFilled, @@ -7,13 +5,13 @@ import { PieChartFilled, ThunderboltFilled } from '@ant-design/icons'; +import { AutoTooltip, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import _ from 'lodash'; import React, { useEffect } from 'react'; import { ModelInstanceListItem } from '../../config/types'; import '../../style/instance-item.less'; - export interface NameCellProps { record: ModelInstanceListItem; modelData: any; diff --git a/src/pages/llmodels/components/instance/instance-item.tsx b/src/pages/llmodels/components/instance/instance-item.tsx index fd199657..7ebad176 100644 --- a/src/pages/llmodels/components/instance/instance-item.tsx +++ b/src/pages/llmodels/components/instance/instance-item.tsx @@ -1,6 +1,5 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import RowChildren from '@/components/seal-table/components/row-children'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; +import { AutoTooltip, RowChildren } from '@gpustack/core-ui'; import { Col, Row } from 'antd'; import dayjs from 'dayjs'; import React from 'react'; @@ -12,7 +11,6 @@ import DistributeInfoCell from '../instance-cells/distribute-info-cell'; import DownloadingStatusCell from '../instance-cells/downloading-status-cell'; import InstanceStatusCell from '../instance-cells/instance-status-cell'; import NameCell from '../instance-cells/name-cell'; - interface InstanceItemProps { instanceData: ModelInstanceListItem; workerList: WorkerListItem[]; diff --git a/src/pages/llmodels/components/left-filters.tsx b/src/pages/llmodels/components/left-filters.tsx index dcdc8ec6..f720b666 100644 --- a/src/pages/llmodels/components/left-filters.tsx +++ b/src/pages/llmodels/components/left-filters.tsx @@ -1,5 +1,5 @@ -import { FiltersButton } from '@/components/page-tools/index'; import { SearchOutlined, SyncOutlined } from '@ant-design/icons'; +import { FiltersButton } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Input, Space } from 'antd'; import React from 'react'; diff --git a/src/pages/llmodels/components/model-item.tsx b/src/pages/llmodels/components/model-item.tsx index f1436928..ccdd7750 100644 --- a/src/pages/llmodels/components/model-item.tsx +++ b/src/pages/llmodels/components/model-item.tsx @@ -1,8 +1,10 @@ -import IconFont from '@/components/icon-font'; -import StatusTag from '@/components/status-tag'; -import TagWrapper from '@/components/tags-wrapper'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; -import Card from '@/components/templates/card'; +import { + IconFont, + StatusTag, + TagsWrapper, + TemplateCard, + ThemeTag +} from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { Button } from 'antd'; import _ from 'lodash'; @@ -163,7 +165,7 @@ const ModelItem: React.FC<{ return ( - onClick(model)} clickable={false} @@ -222,11 +224,11 @@ const ModelItem: React.FC<{ {model.meta?.voices?.length > 0 && ( <> - + > )} @@ -243,7 +245,7 @@ const ModelItem: React.FC<{ - + ); }; diff --git a/src/pages/llmodels/components/model-source/hf-model-file.tsx b/src/pages/llmodels/components/model-source/hf-model-file.tsx index a5ab148d..3957904f 100644 --- a/src/pages/llmodels/components/model-source/hf-model-file.tsx +++ b/src/pages/llmodels/components/model-source/hf-model-file.tsx @@ -1,6 +1,5 @@ import { getRequestId } from '@/atoms/models'; -import BaseSelect from '@/components/seal-form/base/select'; -import SimpleOverlay from '@/components/simple-overlay'; +import { BaseSelect, SimpleOverlay } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Empty, Spin } from 'antd'; import _ from 'lodash'; @@ -22,7 +21,6 @@ import '../../style/hf-model-file.less'; import TitleWrapper from '../title-wrapper'; import FileSkeleton from './file-skeleton'; import ModelFileItem from './model-file-item'; - const ItemFileWrapper = styled.div` display: flex; flex-direction: column; diff --git a/src/pages/llmodels/components/model-source/hf-model-item.tsx b/src/pages/llmodels/components/model-source/hf-model-item.tsx index a388553d..a7bf01c2 100644 --- a/src/pages/llmodels/components/model-source/hf-model-item.tsx +++ b/src/pages/llmodels/components/model-source/hf-model-item.tsx @@ -1,6 +1,6 @@ -import IconFont from '@/components/icon-font'; import { formatNumber } from '@/utils'; import { DownloadOutlined, HeartOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import classNames from 'classnames'; import dayjs from 'dayjs'; import React from 'react'; diff --git a/src/pages/llmodels/components/model-source/model-card.tsx b/src/pages/llmodels/components/model-source/model-card.tsx index 42a618ec..63ae1a27 100644 --- a/src/pages/llmodels/components/model-source/model-card.tsx +++ b/src/pages/llmodels/components/model-source/model-card.tsx @@ -1,7 +1,3 @@ -import IconFont from '@/components/icon-font'; -import MarkdownViewer from '@/components/markdown-viewer'; -import SimpleOverlay from '@/components/simple-overlay'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { GPUSTACK_API_BASE_URL } from '@/config/settings'; import useRequestToken from '@/hooks/use-request-token'; import { @@ -9,6 +5,12 @@ import { FileMarkdownOutlined, RightOutlined } from '@ant-design/icons'; +import { + IconFont, + MarkdownViewer, + SimpleOverlay, + ThemeTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Empty, Spin, Tooltip } from 'antd'; import { some } from 'lodash'; @@ -131,7 +133,7 @@ const ModelCard: React.FC<{ }; const removeMetadata = useCallback((str: string) => { - let indexes = []; + const indexes = []; let index = str.indexOf('---'); while (index !== -1) { diff --git a/src/pages/llmodels/components/model-source/model-file-item.tsx b/src/pages/llmodels/components/model-source/model-file-item.tsx index c1c6e395..cc9cca46 100644 --- a/src/pages/llmodels/components/model-source/model-file-item.tsx +++ b/src/pages/llmodels/components/model-source/model-file-item.tsx @@ -1,7 +1,6 @@ -import { TooltipOverlayScroller } from '@/components/overlay-scroller'; -import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { convertFileSize } from '@/utils'; import { InfoCircleOutlined } from '@ant-design/icons'; +import { ThemeTag, TooltipOverlayScroller } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import classNames from 'classnames'; import _ from 'lodash'; diff --git a/src/pages/llmodels/components/model-source/search-input.tsx b/src/pages/llmodels/components/model-source/search-input.tsx index 3f488180..15b7d07c 100644 --- a/src/pages/llmodels/components/model-source/search-input.tsx +++ b/src/pages/llmodels/components/model-source/search-input.tsx @@ -1,6 +1,6 @@ -import IconFont from '@/components/icon-font'; import hotkeys from '@/config/hotkeys'; import { SearchOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Input } from 'antd'; import React, { useRef } from 'react'; diff --git a/src/pages/llmodels/components/model-source/search-model.tsx b/src/pages/llmodels/components/model-source/search-model.tsx index 7b271533..33dad8b5 100644 --- a/src/pages/llmodels/components/model-source/search-model.tsx +++ b/src/pages/llmodels/components/model-source/search-model.tsx @@ -1,7 +1,6 @@ import { getRequestId, setRquestId } from '@/atoms/models'; -import BaseSelect from '@/components/seal-form/base/select'; import { createAxiosToken } from '@/hooks/use-chunk-request'; -import ColumnWrapper from '@/pages/_components/column-wrapper'; +import { BaseSelect, ColumnWrapper } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Pagination } from 'antd'; import _ from 'lodash'; @@ -23,7 +22,6 @@ import useRecognizeAudio from '../../hooks/use-recognize-audio'; import SearchStyle from '../../style/search-result.less'; import SearchInput from './search-input'; import SearchResult from './search-result'; - const filterOptions = [ { label: 'FP8', value: 'fp8' }, { label: 'AWQ', value: 'awq' }, @@ -173,7 +171,7 @@ const SearchModel: React.FC = (props) => { if (getRequestId() !== currentSearchId) { throw 'new request has been sent'; } - let list = _.map(data || [], (item: any) => { + const list = _.map(data || [], (item: any) => { return { ...item, value: item.name, @@ -210,28 +208,31 @@ const SearchModel: React.FC = (props) => { throw 'new request has been sent'; } - let list = _.map(_.get(data, 'Data.Model.Models') || [], (item: any) => { - return { - path: item.Path, - name: `${item.Path}/${item.Name}`, - downloads: item.Downloads, - id: `${item.Path}/${item.Name}`, - updatedAt: item.LastUpdatedTime * 1000, - likes: item.Stars, - value: item.Name, - label: item.Name, - revision: item.Revision, - task: item.Tasks?.map((sItem: any) => sItem.Name).join(','), - tags: item.Tags, - libraries: item.Libraries, - avatar: item.Avatar, - isGGUF: checkIsGGUF({ + const list = _.map( + _.get(data, 'Data.Model.Models') || [], + (item: any) => { + return { + path: item.Path, + name: `${item.Path}/${item.Name}`, + downloads: item.Downloads, + id: `${item.Path}/${item.Name}`, + updatedAt: item.LastUpdatedTime * 1000, + likes: item.Stars, + value: item.Name, + label: item.Name, + revision: item.Revision, + task: item.Tasks?.map((sItem: any) => sItem.Name).join(','), tags: item.Tags, - libraries: item.Libraries - }), - source: modelSource - }; - }); + libraries: item.Libraries, + avatar: item.Avatar, + isGGUF: checkIsGGUF({ + tags: item.Tags, + libraries: item.Libraries + }), + source: modelSource + }; + } + ); setPaginationInfo((prev) => { return { diff --git a/src/pages/llmodels/components/model-source/search-result.tsx b/src/pages/llmodels/components/model-source/search-result.tsx index ceaab557..665244fa 100644 --- a/src/pages/llmodels/components/model-source/search-result.tsx +++ b/src/pages/llmodels/components/model-source/search-result.tsx @@ -1,5 +1,5 @@ -import IconFont from '@/components/icon-font'; import { SearchOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Empty, Spin } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/llmodels/components/scaling.tsx b/src/pages/llmodels/components/scaling.tsx deleted file mode 100644 index a0926d37..00000000 --- a/src/pages/llmodels/components/scaling.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import SealInputNumber from '@/components/seal-form/input-number'; -import useAppUtils from '@/hooks/use-app-utils'; -import { QuestionCircleOutlined } from '@ant-design/icons'; -import { useIntl } from '@umijs/max'; -import { Checkbox, Form, Tooltip } from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import React from 'react'; -import { useFormContext } from '../config/form-context'; - -const scheduleTypeTips = [ - { - title: { - text: 'models.form.scheduletype.auto', - locale: true - }, - tips: 'models.form.scheduletype.auto.tips' - }, - { - title: { - text: 'models.form.scheduletype.manual', - locale: true - }, - tips: 'models.form.scheduletype.manual.tips' - } -]; - -const CheckboxField: React.FC<{ - title: string; - label: string; - checked?: boolean; - onChange?: (e: CheckboxChangeEvent) => void; -}> = ({ title, label, checked, onChange }) => { - return ( - - - {label} - - - - ); -}; - -const Scaling: React.FC = () => { - const intl = useIntl(); - const { onValuesChange } = useFormContext(); - const { getRuleMessage } = useAppUtils(); - const form = Form.useFormInstance(); - - return ( - <> -
    - - name="enable_auto_scaling" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - noStyle - > - - -
    - - name="min_replicas" - rules={[ - { - required: true - } - ]} - > - - - - name="max_replicas" - rules={[ - { - required: true - } - ]} - > - - - - name="scale_to_zero_window" - rules={[ - { - required: true - } - ]} - > - - - - name="scale_down_window" - rules={[ - { - required: true - } - ]} - > - - - - name="scale_up_window" - rules={[ - { - required: true - } - ]} - > - - - - ); -}; - -export default Scaling; diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index c1d9b8c5..739b78bc 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -1,10 +1,4 @@ import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models'; -import DeleteModal from '@/components/delete-modal'; -import DropDownActions from '@/components/drop-down-actions'; -import DropdownButtons from '@/components/drop-down-buttons'; -import PageTools from '@/components/page-tools'; -import SealTable from '@/components/seal-table'; -import { TableOrder } from '@/components/seal-table/types'; import { PageAction } from '@/config'; import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { PageActionType } from '@/config/types'; @@ -19,6 +13,14 @@ import useOpenPlayground from '@/pages/model-routes/hooks/use-open-playground'; import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link'; import { handleBatchRequest } from '@/utils'; import { DownOutlined } from '@ant-design/icons'; +import { + DeleteModal, + DropdownActions, + DropdownButtons, + PageTools, + Table as SealTable +} from '@gpustack/core-ui'; +import { TableOrder } from '@gpustack/core-ui/lib/components/table/types'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useMemoizedFn, useToggle } from 'ahooks'; import { Button, Space, message } from 'antd'; @@ -164,7 +166,6 @@ const Models: React.FC = ({ type: 'model' }); - const [openLogModal, setOpenLogModal] = useState(false); const [openDeployModal, setOpenDeployModal] = useState<{ show: boolean; width: number | string; @@ -599,23 +600,21 @@ const Models: React.FC = ({ right={ {ActionButton()} - {page !== 'clusters' && ( - + - - )} + {intl?.formatMessage?.({ id: 'models.button.deploy' })} + + { }; const handleOnSortChange = (order: TableOrder | Array) => { - let orderList = Array.isArray(order) ? order : [order]; + const orderList = Array.isArray(order) ? order : [order]; if (orderList[0].columnKey === 'replicas') { orderList.push({ columnKey: 'ready_replicas', @@ -439,7 +439,7 @@ const Models = forwardRef((props, ref) => { })); return ( - { deleteIds={dataSource.deletedIds} filterValues={filterValues} > - + ); }); diff --git a/src/pages/llmodels/filters/index.tsx b/src/pages/llmodels/filters/index.tsx index 844b0ad0..ae32f717 100644 --- a/src/pages/llmodels/filters/index.tsx +++ b/src/pages/llmodels/filters/index.tsx @@ -1,12 +1,10 @@ -import BaseSelect from '@/components/seal-form/base/select'; -import FilterForm from '@/pages/_components/filter-form'; +import { BaseSelect, FilterForm } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { forwardRef, useImperativeHandle, useRef } from 'react'; import styled from 'styled-components'; import { modelCategories } from '../config'; import useFilterStatus from '../hooks/use-filter-status'; - const Content = styled.div` display: flex; flex-direction: column; diff --git a/src/pages/llmodels/forms/advance-config.tsx b/src/pages/llmodels/forms/advance-config.tsx index f52b8374..200c26ae 100644 --- a/src/pages/llmodels/forms/advance-config.tsx +++ b/src/pages/llmodels/forms/advance-config.tsx @@ -1,9 +1,11 @@ -import LabelSelector from '@/components/label-selector'; -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import DocLink from '@/pages/_components/doc-link'; import { genericReferLink } from '@/pages/model-routes/config'; +import { + CheckboxField, + LabelSelector, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/llmodels/forms/backend-parameters-list.tsx b/src/pages/llmodels/forms/backend-parameters-list.tsx index 4d26eb4d..91d57f7f 100644 --- a/src/pages/llmodels/forms/backend-parameters-list.tsx +++ b/src/pages/llmodels/forms/backend-parameters-list.tsx @@ -1,4 +1,4 @@ -import ListInput from '@/components/list-input'; +import { ListInput } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/llmodels/forms/backend.tsx b/src/pages/llmodels/forms/backend.tsx index c3656401..69fbe069 100644 --- a/src/pages/llmodels/forms/backend.tsx +++ b/src/pages/llmodels/forms/backend.tsx @@ -1,7 +1,6 @@ -import SealSelect from '@/components/seal-form/seal-select'; -import TooltipList from '@/components/tooltip-list'; import useAppUtils from '@/hooks/use-app-utils'; import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import { Select as SealSelect, TooltipList } from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { Form, Select } from 'antd'; import React, { useMemo } from 'react'; diff --git a/src/pages/llmodels/forms/basic.tsx b/src/pages/llmodels/forms/basic.tsx index f58acc2d..71894c7d 100644 --- a/src/pages/llmodels/forms/basic.tsx +++ b/src/pages/llmodels/forms/basic.tsx @@ -1,6 +1,3 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { modelNameReg, PageAction } from '@/config'; import { OPENAI_COMPATIBLE } from '@/config/settings'; import useAppUtils from '@/hooks/use-app-utils'; @@ -8,6 +5,11 @@ import { ClusterStatusLabelMap, ClusterStatusValueMap } from '@/pages/cluster-management/config'; +import { + AutoTooltip, + Input as CInput, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { useMemo } from 'react'; @@ -21,7 +23,6 @@ import CustomBackend from './custom-backend'; import LocalPathSource from './local-path-source'; import ModeField from './mode-field'; import OnlineSource from './online-source'; - const ClusterOption = styled.span` display: flex; padding: 8px 0; @@ -173,14 +174,14 @@ const BasicForm: React.FC = (props) => { } ]} > - + > {fields.includes('source') && ( @@ -240,7 +241,7 @@ const BasicForm: React.FC = (props) => { } ]} > - = (props) => { { api: `${window.location.origin}/${OPENAI_COMPATIBLE}` } )} min={0} - > + > name="description"> - + > ); diff --git a/src/pages/llmodels/forms/catalog.tsx b/src/pages/llmodels/forms/catalog.tsx index 29043503..bb921aa7 100644 --- a/src/pages/llmodels/forms/catalog.tsx +++ b/src/pages/llmodels/forms/catalog.tsx @@ -1,5 +1,5 @@ -import SealInput from '@/components/seal-form/seal-input'; import useAppUtils from '@/hooks/use-app-utils'; +import { Input as CInput } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; @@ -32,10 +32,10 @@ const CatalogForm: React.FC = () => { } ]} > - + > ); diff --git a/src/pages/llmodels/forms/custom-backend.tsx b/src/pages/llmodels/forms/custom-backend.tsx index 1222afc2..6d57b493 100644 --- a/src/pages/llmodels/forms/custom-backend.tsx +++ b/src/pages/llmodels/forms/custom-backend.tsx @@ -1,6 +1,5 @@ -import SealInput from '@/components/seal-form/seal-input'; -import SealTextArea from '@/components/seal-form/seal-textarea'; import useAppUtils from '@/hooks/use-app-utils'; +import { Input as CInput, Textarea as SealTextArea } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; @@ -44,12 +43,12 @@ const CustomBackend: React.FC = () => { } ]} > - + > name="run_command" diff --git a/src/pages/llmodels/forms/envs-override-popover.tsx b/src/pages/llmodels/forms/envs-override-popover.tsx index 1a02cc43..f0f48f5c 100644 --- a/src/pages/llmodels/forms/envs-override-popover.tsx +++ b/src/pages/llmodels/forms/envs-override-popover.tsx @@ -1,6 +1,9 @@ -import OverlayScroller from '@/components/overlay-scroller'; -import SimpleTabel, { ColumnProps } from '@/components/simple-table'; import { WarningOutlined } from '@ant-design/icons'; +import { + OverlayScroller, + SimpleTable, + type ColumnProps +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Alert, Button, Popover, Radio } from 'antd'; import React, { useMemo } from 'react'; @@ -128,7 +131,7 @@ const EnvsOverridePopover: React.FC = (props) => { } }} > - = forwardRef((props, ref) => { // generate the data is available for the backend including the gpu_ids const handleOk = async (formdata: FormData) => { - let data = _.cloneDeep(formdata); + const data = _.cloneDeep(formdata); data.categories = data.categories ? [data.categories] : []; const gpuSelector = generateGPUIds(data); const allValues = { diff --git a/src/pages/llmodels/forms/kv-cache.tsx b/src/pages/llmodels/forms/kv-cache.tsx index 99af680b..3c16c0b0 100644 --- a/src/pages/llmodels/forms/kv-cache.tsx +++ b/src/pages/llmodels/forms/kv-cache.tsx @@ -1,5 +1,4 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealInputNumber from '@/components/seal-form/input-number'; +import { CheckboxField, InputNumber as CInputNumber } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -100,7 +99,7 @@ const KVCacheForm = () => { {kvCacheEnabled && ( <> name={['extended_kv_cache', 'ram_ratio']}> - handleRamRatioChange(value, 'ram_ratio')} label={intl.formatMessage({ id: 'models.form.ramRatio' })} description={intl.formatMessage({ @@ -112,7 +111,7 @@ const KVCacheForm = () => { /> name={['extended_kv_cache', 'ram_size']}> - handleRamSizeInput(value)} label={intl.formatMessage({ id: 'models.form.ramSize' })} description={intl.formatMessage( @@ -127,7 +126,7 @@ const KVCacheForm = () => { /> name={['extended_kv_cache', 'chunk_size']}> - handleRamRatioChange(value, 'chunk_size')} label={intl.formatMessage({ id: 'models.form.chunkSize' })} description={intl.formatMessage({ diff --git a/src/pages/llmodels/forms/local-path-source.tsx b/src/pages/llmodels/forms/local-path-source.tsx index afcf087e..66a0b53d 100644 --- a/src/pages/llmodels/forms/local-path-source.tsx +++ b/src/pages/llmodels/forms/local-path-source.tsx @@ -1,6 +1,5 @@ -import SealInput from '@/components/seal-form/seal-input'; -import TooltipList from '@/components/tooltip-list'; import useAppUtils from '@/hooks/use-app-utils'; +import { Input as CInput, TooltipList } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -91,14 +90,14 @@ const LocalPathForm: React.FC = () => { } ]} > - } - > + > ); diff --git a/src/pages/llmodels/forms/mode-field.tsx b/src/pages/llmodels/forms/mode-field.tsx index 5680e461..b5f10945 100644 --- a/src/pages/llmodels/forms/mode-field.tsx +++ b/src/pages/llmodels/forms/mode-field.tsx @@ -1,11 +1,9 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import SealSelect from '@/components/seal-form/seal-select'; +import { AutoTooltip, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, Select } from 'antd'; import React from 'react'; import { DeployFormKeyMap } from '../config'; import { useCatalogFormContext, useFormContext } from '../config/form-context'; - const Performance: React.FC = () => { const intl = useIntl(); const { formKey } = useFormContext(); diff --git a/src/pages/llmodels/forms/online-source.tsx b/src/pages/llmodels/forms/online-source.tsx index d8741566..753ec08d 100644 --- a/src/pages/llmodels/forms/online-source.tsx +++ b/src/pages/llmodels/forms/online-source.tsx @@ -1,6 +1,6 @@ -import SealInput from '@/components/seal-form/seal-input'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; +import { Input as CInput } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; @@ -44,22 +44,22 @@ const HuggingFaceForm: React.FC = () => { } ]} > - + > {isGGUF && ( name="huggingface_filename" key="huggingface_filename" > - + > )} @@ -75,22 +75,22 @@ const HuggingFaceForm: React.FC = () => { } ]} > - + > {isGGUF && ( name="model_scope_file_path" key="model_scope_file_path" > - + > )} diff --git a/src/pages/llmodels/forms/schedule-type.tsx b/src/pages/llmodels/forms/schedule-type.tsx index ca97c246..2c225005 100644 --- a/src/pages/llmodels/forms/schedule-type.tsx +++ b/src/pages/llmodels/forms/schedule-type.tsx @@ -1,9 +1,11 @@ -import LabelSelector from '@/components/label-selector'; -import { LabelSelectorContext } from '@/components/label-selector/context'; -import SealCascader from '@/components/seal-form/seal-cascader'; -import SealSelect from '@/components/seal-form/seal-select'; -import TooltipList from '@/components/tooltip-list'; import useAppUtils from '@/hooks/use-app-utils'; +import { + LabelSelector, + LabelSelectorProvider, + Cascader as SealCascader, + Select as SealSelect, + TooltipList +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, InputNumber } from 'antd'; import _ from 'lodash'; @@ -258,9 +260,7 @@ const ScheduleTypeForm: React.FC = () => { } > - + name="worker_selector" rules={[ @@ -308,7 +308,7 @@ const ScheduleTypeForm: React.FC = () => { } > - + )} diff --git a/src/pages/llmodels/forms/speculative-decode.tsx b/src/pages/llmodels/forms/speculative-decode.tsx index fc65d03e..aa298cde 100644 --- a/src/pages/llmodels/forms/speculative-decode.tsx +++ b/src/pages/llmodels/forms/speculative-decode.tsx @@ -1,9 +1,11 @@ -import AutoComlete from '@/components/seal-form/auto-complete'; -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealInputNumber from '@/components/seal-form/input-number'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import useAppUtils from '@/hooks/use-app-utils'; +import { + AutoComplete, + CheckboxField, + Input as CInput, + InputNumber as CInputNumber, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -154,7 +156,7 @@ const SpeculativeDecode = () => { } ]} > - { }} onBlur={handleOnDraftBlur} onSelect={handleDraftSelect} - > + > )} name={['speculative_config', 'num_draft_tokens']} > - { name={['speculative_config', 'ngram_min_match_length']} > - { name={['speculative_config', 'ngram_max_match_length']} > - { +}: ModelsColumnsHookProps & { targetList: any[] }): ColumnProps[] => { const intl = useIntl(); const systemConfig = useAtomValue(systemConfigAtom); diff --git a/src/pages/llmodels/hooks/use-no-resource-result.tsx b/src/pages/llmodels/hooks/use-no-resource-result.tsx index 714ac119..d0158aa5 100644 --- a/src/pages/llmodels/hooks/use-no-resource-result.tsx +++ b/src/pages/llmodels/hooks/use-no-resource-result.tsx @@ -1,6 +1,5 @@ import { clusterSessionAtom } from '@/atoms/clusters'; -import IconFont from '@/components/icon-font'; -import NoResult from '@/pages/_components/no-result'; +import { IconFont, NoResult } from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { useAtom } from 'jotai'; diff --git a/src/pages/llmodels/hooks/use-view-instance-logs.ts b/src/pages/llmodels/hooks/use-view-instance-logs.ts index d7b03d9a..161db099 100644 --- a/src/pages/llmodels/hooks/use-view-instance-logs.ts +++ b/src/pages/llmodels/hooks/use-view-instance-logs.ts @@ -1,5 +1,5 @@ -import { PageSize } from '@/components/logs-viewer/config'; import useBodyScroll from '@/hooks/use-body-scroll'; +import { PageSize } from '@gpustack/core-ui/lib/components/logs-viewer/config'; import { useState } from 'react'; import { MODEL_INSTANCE_API } from '../apis'; import { InstanceRealtimeLogStatus } from '../config'; diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index d6eea53e..92cea477 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -1,5 +1,5 @@ -import IconFont from '@/components/icon-font'; import { PageContainerInner } from '@/pages/_components/page-box'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Segmented, Tabs } from 'antd'; diff --git a/src/pages/llmodels/instance-view/index.tsx b/src/pages/llmodels/instance-view/index.tsx index 0560b728..30a7dcc6 100644 --- a/src/pages/llmodels/instance-view/index.tsx +++ b/src/pages/llmodels/instance-view/index.tsx @@ -1,10 +1,7 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useTableFetch from '@/hooks/use-table-fetch'; -import NoResult from '@/pages/_components/no-result'; import PageBox from '@/pages/_components/page-box'; +import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { ConfigProvider, Table } from 'antd'; diff --git a/src/pages/llmodels/instance-view/left-filters.tsx b/src/pages/llmodels/instance-view/left-filters.tsx index 653543d4..dbb0b3af 100644 --- a/src/pages/llmodels/instance-view/left-filters.tsx +++ b/src/pages/llmodels/instance-view/left-filters.tsx @@ -1,11 +1,10 @@ -import BaseSelect from '@/components/seal-form/base/select'; import { ListItem as workerListItem } from '@/pages/resources/config/types'; import { SearchOutlined, SyncOutlined } from '@ant-design/icons'; +import { BaseSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Input, Space } from 'antd'; import React from 'react'; import useFilterStatus from '../hooks/use-filter-status'; - interface LeftFiltersProps { handleNameChange: (e: React.ChangeEvent) => void; handleClusterChange: (value: number) => void; diff --git a/src/pages/llmodels/instance-view/use-instance-columns.tsx b/src/pages/llmodels/instance-view/use-instance-columns.tsx index 8e8a5d8e..e4f951b8 100644 --- a/src/pages/llmodels/instance-view/use-instance-columns.tsx +++ b/src/pages/llmodels/instance-view/use-instance-columns.tsx @@ -1,10 +1,9 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; -import IconFont from '@/components/icon-font'; import { tableSorter } from '@/config/settings'; import { ListItem as workerListItem } from '@/pages/resources/config/types'; import { convertFileSize } from '@/utils'; import { ThunderboltFilled } from '@ant-design/icons'; +import { AutoTooltip, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; @@ -19,7 +18,6 @@ import NameCell, { } from '../components/instance-cells/name-cell'; import { ModelInstanceListItem as ListItem } from '../config/types'; import { calcTotalVram } from '../utils'; - const WorkerInfoContent: React.FC = ({ record, modelData }) => { let workerIp = '-'; if (record.worker_ip) { diff --git a/src/pages/llmodels/user-models.tsx b/src/pages/llmodels/user-models.tsx index 489eb5b5..87d00ef4 100644 --- a/src/pages/llmodels/user-models.tsx +++ b/src/pages/llmodels/user-models.tsx @@ -1,15 +1,17 @@ -import IconFont from '@/components/icon-font'; -import PageTools from '@/components/page-tools'; -import BaseSelect from '@/components/seal-form/base/select'; -import CardList from '@/components/templates/card-list'; import useTableFetch from '@/hooks/use-table-fetch'; import { SyncOutlined } from '@ant-design/icons'; +import { + BaseSelect, + IconFont, + InfiniteScrollerProvider, + NoResult, + PageTools, + TemplateCardList +} from '@gpustack/core-ui'; import { useIntl, useNavigate } from '@umijs/max'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import { Button, Input, Space } from 'antd'; import React, { useCallback, useMemo } from 'react'; -import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context'; -import NoResult from '../_components/no-result'; import PageBox from '../_components/page-box'; import { MY_MODELS_API, queryMyModels } from './apis'; import ModelItem from './components/model-item'; @@ -19,7 +21,6 @@ import { MyModelsStatusValueMap } from './config'; import { categoryToPathMap } from './config/button-actions'; - const Dot = ({ color }: { color: string }) => { return ( { } > - { refresh: loadMore }} > - + > { title={intl.formatMessage({ id: 'noresult.mymodels.title' })} subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })} > - + ); }; diff --git a/src/pages/login/components/local-user-form.tsx b/src/pages/login/components/local-user-form.tsx index ea97fd68..683ec509 100644 --- a/src/pages/login/components/local-user-form.tsx +++ b/src/pages/login/components/local-user-form.tsx @@ -1,11 +1,10 @@ -import HighlightCode from '@/components/highlight-code'; -import SealInput from '@/components/seal-form/seal-input'; import externalLinks from '@/constants/external-links'; import { InfoCircleOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import { Input as CInput, HighlightCode } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Divider, Form, FormInstance } from 'antd'; import { createStyles } from 'antd-style'; @@ -93,7 +92,7 @@ const LocalUserForm: React.FC = (props) => { } ]} > - } @@ -114,7 +113,7 @@ const LocalUserForm: React.FC = (props) => { } ]} > - } label={intl.formatMessage({ id: 'common.form.password' })} diff --git a/src/pages/login/components/password-form.tsx b/src/pages/login/components/password-form.tsx index c0010c82..34a02089 100644 --- a/src/pages/login/components/password-form.tsx +++ b/src/pages/login/components/password-form.tsx @@ -1,6 +1,5 @@ import { initialPasswordAtom, userAtom } from '@/atoms/user'; import { resetStorageUserSettings } from '@/atoms/utils'; -import SealInput from '@/components/seal-form/seal-input'; import { PasswordReg } from '@/config'; import { CRYPT_TEXT, @@ -8,6 +7,7 @@ import { writeState } from '@/utils/localstore/index'; import { LockOutlined } from '@ant-design/icons'; +import { Input as CInput } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form, message } from 'antd'; import CryptoJS from 'crypto-js'; @@ -88,7 +88,7 @@ const PasswordForm: React.FC = () => { } ]} > - } label={intl.formatMessage({ id: 'users.form.newpassword' })} @@ -118,7 +118,7 @@ const PasswordForm: React.FC = () => { }) ]} > - } label={intl.formatMessage({ id: 'users.password.confirm' })} diff --git a/src/pages/maas-provider/components/add-provider-modal.tsx b/src/pages/maas-provider/components/add-provider-modal.tsx index ce768da8..6678d177 100644 --- a/src/pages/maas-provider/components/add-provider-modal.tsx +++ b/src/pages/maas-provider/components/add-provider-modal.tsx @@ -1,5 +1,5 @@ import { PageActionType } from '@/config/types'; -import FormDrawer from '@/pages/_components/form-drawer'; +import { FormDrawer } from '@gpustack/core-ui'; import React, { useRef } from 'react'; import { FormData, MaasProviderItem as ListItem } from '../config/types'; diff --git a/src/pages/maas-provider/components/provider-models.tsx b/src/pages/maas-provider/components/provider-models.tsx index 0baf7be8..db8cdb6a 100644 --- a/src/pages/maas-provider/components/provider-models.tsx +++ b/src/pages/maas-provider/components/provider-models.tsx @@ -1,13 +1,11 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import OverlayScroller from '@/components/overlay-scroller'; import { CheckCircleOutlined } from '@ant-design/icons'; +import { AutoTooltip, OverlayScroller } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Flex, Popover, Tag } from 'antd'; import _ from 'lodash'; import React from 'react'; import { categoryConfig } from '../../_components/model-tag'; import { ProviderModel } from '../config/types'; - interface ProviderModelProps { dataList: ProviderModel[]; } diff --git a/src/pages/maas-provider/config/index.ts b/src/pages/maas-provider/config/index.ts index e4232ec3..34627311 100644 --- a/src/pages/maas-provider/config/index.ts +++ b/src/pages/maas-provider/config/index.ts @@ -1,6 +1,6 @@ -import icons from '@/components/icon-font/icons'; import { StatusMaps } from '@/config'; import { StatusType } from '@/config/types'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import { ProviderEnum } from './providers'; export { maasProviderLabelMap, maasProviderOptions } from './providers'; diff --git a/src/pages/maas-provider/forms/advance-config.tsx b/src/pages/maas-provider/forms/advance-config.tsx index 4cc361fe..6f5f1948 100644 --- a/src/pages/maas-provider/forms/advance-config.tsx +++ b/src/pages/maas-provider/forms/advance-config.tsx @@ -1,7 +1,5 @@ -import IconFont from '@/components/icon-font'; -import SealInput from '@/components/seal-form/seal-input'; import { PageActionType } from '@/config/types'; -import YamlEditor from '@/pages/_components/yaml-editor'; +import { Input as CInput, IconFont, YamlEditor } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; import React, { forwardRef, useImperativeHandle } from 'react'; @@ -46,7 +44,7 @@ const AdvanceConfig: React.FC<{ } ]} > - + { const intl = useIntl(); const { getRuleMessage } = useAppUtils(); diff --git a/src/pages/maas-provider/forms/basic.tsx b/src/pages/maas-provider/forms/basic.tsx index c00ea244..e6ac46fd 100644 --- a/src/pages/maas-provider/forms/basic.tsx +++ b/src/pages/maas-provider/forms/basic.tsx @@ -1,8 +1,10 @@ -import Password from '@/components/seal-form/password'; -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; +import { + Input as CInput, + Password, + Select as SealSelect +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import ProviderLogo from '../components/provider-logo'; @@ -10,7 +12,6 @@ import { useFormContext } from '../config/form-context'; import { maasProviderOptions } from '../config/providers'; import { FormData } from '../config/types'; import ProviderConfigs from './provider-configs'; - const Basic: React.FC<{ onAPIKeyBlur?: (e: any) => void; }> = ({ onAPIKeyBlur }) => { @@ -49,7 +50,7 @@ const Basic: React.FC<{ } ]} > - name="description"> - + > ); diff --git a/src/pages/maas-provider/forms/index.tsx b/src/pages/maas-provider/forms/index.tsx index e6f0947b..aa940d1b 100644 --- a/src/pages/maas-provider/forms/index.tsx +++ b/src/pages/maas-provider/forms/index.tsx @@ -1,12 +1,10 @@ -import IconFont from '@/components/icon-font'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import CollapsePanel from '@/pages/_components/collapse-panel'; -import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; -import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; -import useFinishFailed from '@/pages/_components/scroll-spy-tabs/use-finish-failed'; -import useScrollActiveChange from '@/pages/_components/scroll-spy-tabs/use-scroll-active-change'; import { json2Yaml, yaml2Json } from '@/pages/backends/config'; +import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui'; +import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context'; +import useFinishFailed from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-finish-failed'; +import useScrollActiveChange from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-scroll-active-change'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/maas-provider/forms/model-item.tsx b/src/pages/maas-provider/forms/model-item.tsx index e3321e60..def8f480 100644 --- a/src/pages/maas-provider/forms/model-item.tsx +++ b/src/pages/maas-provider/forms/model-item.tsx @@ -1,5 +1,3 @@ -import AutoComplete from '@/components/seal-form/auto-complete'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { categoryOptions, modelCategoriesMap } from '@/pages/llmodels/config'; import { @@ -7,6 +5,7 @@ import { LoadingOutlined, WarningFilled } from '@ant-design/icons'; +import { AutoComplete, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form, Tooltip } from 'antd'; import React, { useMemo } from 'react'; diff --git a/src/pages/maas-provider/forms/provider-configs.tsx b/src/pages/maas-provider/forms/provider-configs.tsx index 99d0f5ab..40e58221 100644 --- a/src/pages/maas-provider/forms/provider-configs.tsx +++ b/src/pages/maas-provider/forms/provider-configs.tsx @@ -1,10 +1,8 @@ -import Password from '@/components/seal-form/password'; -import SealInput from '@/components/seal-form/seal-input'; +import { Input as CInput, Password } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { useFormContext } from '../config/form-context'; import { FormData } from '../config/types'; - const ProviderConfigs = () => { const intl = useIntl(); const form = Form.useFormInstance(); @@ -35,12 +33,12 @@ const ProviderConfigs = () => { key={item.name} > {item.type === 'Input' && ( - + > )} {item.type === 'Password' && ( { } ]} > - + > - + > )} diff --git a/src/pages/maas-provider/forms/supported-models.tsx b/src/pages/maas-provider/forms/supported-models.tsx index 0aea1f7a..50ca99ae 100644 --- a/src/pages/maas-provider/forms/supported-models.tsx +++ b/src/pages/maas-provider/forms/supported-models.tsx @@ -1,5 +1,5 @@ -import MetadataList from '@/components/metadata-list'; import { PageAction } from '@/config'; +import { MetadataList } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/maas-provider/hooks/use-provider-columns.tsx b/src/pages/maas-provider/hooks/use-provider-columns.tsx index 6d494e6a..b8ab34b2 100644 --- a/src/pages/maas-provider/hooks/use-provider-columns.tsx +++ b/src/pages/maas-provider/hooks/use-provider-columns.tsx @@ -1,7 +1,6 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; import { tableSorter } from '@/config/settings'; +import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tag } from 'antd'; import { ColumnsType } from 'antd/lib/table'; @@ -12,7 +11,6 @@ import ProviderModels from '../components/provider-models'; import { rowActionList } from '../config'; import { maasProviderLabelMap } from '../config/providers'; import { MaasProviderItem, ProviderModel } from '../config/types'; - const useProviderColumns = ( handleSelect: (val: string, record: MaasProviderItem) => void, onCellClick?: (record: MaasProviderItem, dataIndex: string) => void diff --git a/src/pages/maas-provider/hooks/use-query-provider-models.tsx b/src/pages/maas-provider/hooks/use-query-provider-models.tsx index 4654a8d8..3cf13182 100644 --- a/src/pages/maas-provider/hooks/use-query-provider-models.tsx +++ b/src/pages/maas-provider/hooks/use-query-provider-models.tsx @@ -1,5 +1,5 @@ import { createAxiosToken } from '@/hooks/use-chunk-request'; -import ErrorMessageContent from '@/pages/_components/error-message-content'; +import { ErrorMessage } from '@gpustack/core-ui'; import { useRequest } from 'ahooks'; import { message } from 'antd'; import { CancelTokenSource } from 'axios'; @@ -112,9 +112,9 @@ export const useTestProviderModel = () => { if (!response?.accessible) { message.error({ content: ( - + > ) }); } @@ -122,9 +122,9 @@ export const useTestProviderModel = () => { onError: (error) => { message.error({ content: ( - + > ) }); } diff --git a/src/pages/maas-provider/index.tsx b/src/pages/maas-provider/index.tsx index 3a713c4b..5a5afe63 100644 --- a/src/pages/maas-provider/index.tsx +++ b/src/pages/maas-provider/index.tsx @@ -1,9 +1,7 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useTableFetch from '@/hooks/use-table-fetch'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { ConfigProvider, message, Table } from 'antd'; diff --git a/src/pages/model-routes/components/add-route-modal.tsx b/src/pages/model-routes/components/add-route-modal.tsx index c82b13c9..1bc1d5ee 100644 --- a/src/pages/model-routes/components/add-route-modal.tsx +++ b/src/pages/model-routes/components/add-route-modal.tsx @@ -1,7 +1,5 @@ -import AlertBlockInfo from '@/components/alert-info/block'; -import ModalFooter from '@/components/modal-footer'; import { PageActionType } from '@/config/types'; -import FormDrawer from '@/pages/_components/form-drawer'; +import { AlertBlockInfo, FormDrawer, ModalFooter } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import React, { useRef } from 'react'; import { FormData, RouteItem as ListItem } from '../config/types'; diff --git a/src/pages/model-routes/components/route-targets.tsx b/src/pages/model-routes/components/route-targets.tsx index 36ab49c2..3e2ad207 100644 --- a/src/pages/model-routes/components/route-targets.tsx +++ b/src/pages/model-routes/components/route-targets.tsx @@ -1,9 +1,11 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import RowChildren from '@/components/seal-table/components/row-children'; -import StatusTag from '@/components/status-tag'; import ProviderLogo from '@/pages/maas-provider/components/provider-logo'; import { DeleteOutlined } from '@ant-design/icons'; +import { + AutoTooltip, + DropdownButtons, + RowChildren, + StatusTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Col, Row } from 'antd'; import dayjs from 'dayjs'; @@ -11,7 +13,6 @@ import React from 'react'; import styled from 'styled-components'; import { TargetStatus, TargetStatusLabelMap } from '../config'; import { RouteTarget } from '../config/types'; - const CellContent = styled.div` display: flex; align-items: center; diff --git a/src/pages/model-routes/config/index.ts b/src/pages/model-routes/config/index.ts index 90e6c3e6..5a4f6945 100644 --- a/src/pages/model-routes/config/index.ts +++ b/src/pages/model-routes/config/index.ts @@ -1,6 +1,6 @@ -import icons from '@/components/icon-font/icons'; import { StatusMaps } from '@/config'; import { StatusType } from '@/config/types'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; export const TargetStatusValueMap: Record = { Active: 'active', diff --git a/src/pages/model-routes/forms/basic.tsx b/src/pages/model-routes/forms/basic.tsx index fd96f944..25cdbab7 100644 --- a/src/pages/model-routes/forms/basic.tsx +++ b/src/pages/model-routes/forms/basic.tsx @@ -1,9 +1,8 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealInput from '@/components/seal-form/seal-input'; import useAppUtils from '@/hooks/use-app-utils'; import CategorySelect from '@/pages/_components/category-select'; import DocLink from '@/pages/_components/doc-link'; import { categoryOptions } from '@/pages/llmodels/config'; +import { CheckboxField, Input as CInput } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { genericReferLink } from '../config'; @@ -25,7 +24,7 @@ const Basic = () => { } ]} > - @@ -56,12 +55,12 @@ const Basic = () => { > - + > name="generic_proxy" diff --git a/src/pages/model-routes/forms/index.tsx b/src/pages/model-routes/forms/index.tsx index 2daf1175..38076a8c 100644 --- a/src/pages/model-routes/forms/index.tsx +++ b/src/pages/model-routes/forms/index.tsx @@ -1,12 +1,10 @@ -import IconFont from '@/components/icon-font'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import CollapsePanel from '@/pages/_components/collapse-panel'; -import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; -import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; -import useFinishFailed from '@/pages/_components/scroll-spy-tabs/use-finish-failed'; -import useScrollActiveChange from '@/pages/_components/scroll-spy-tabs/use-scroll-active-change'; import { modelCategoriesMap } from '@/pages/llmodels/config'; +import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui'; +import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context'; +import useFinishFailed from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-finish-failed'; +import useScrollActiveChange from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-scroll-active-change'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -87,7 +85,7 @@ const AccessForm: React.FC = forwardRef((props, ref) => { const formatTargets = (values: FormData) => { let targetList = [...(values.targets || [])]; - let fallbackTarget = values.fallback_target; + const fallbackTarget = values.fallback_target; if (fallbackTarget) { const exsitinged = targetList.find((ep) => { diff --git a/src/pages/model-routes/forms/meta-data.tsx b/src/pages/model-routes/forms/meta-data.tsx index 073fbfc7..09030f89 100644 --- a/src/pages/model-routes/forms/meta-data.tsx +++ b/src/pages/model-routes/forms/meta-data.tsx @@ -1,8 +1,10 @@ -import SingleImage from '@/components/auto-image/single-image'; -import ListInput from '@/components/list-input'; -import SealInputNumber from '@/components/seal-form/input-number'; -import SealInput from '@/components/seal-form/seal-input'; import UploadImg from '@/pages/playground/components/upload-img'; +import { + Input as CInput, + InputNumber as CInputNumber, + ListInput, + SingleImage +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; import styled from 'styled-components'; @@ -62,26 +64,26 @@ const MetaData = () => { name={['meta', 'size']} normalize={(v) => (v === 0 ? null : v)} > - + > name={['meta', 'activated_size']} normalize={(v) => (v === 0 ? null : v)} > - + > name={['meta', 'max_tokens']}> - { { id: 'common.help.eg' }, { content: 'context/128k' } )} - > + > name={['meta', 'dimensions']} normalize={(v) => (v === 0 ? null : v)} > - + > name={['meta', 'release_date']}> - { { id: 'common.help.eg' }, { content: '2025-05-19' } )} - > + > name={['meta', 'tags']} data-field="metadata"> void, onCellClick?: (record: RouteItem, dataIndex: string) => void -): SealColumnProps[] => { +): ColumnProps[] => { const intl = useIntl(); const filterActions = (record: RouteItem) => { diff --git a/src/pages/model-routes/index.tsx b/src/pages/model-routes/index.tsx index 695ca2ff..6f63398e 100644 --- a/src/pages/model-routes/index.tsx +++ b/src/pages/model-routes/index.tsx @@ -1,17 +1,19 @@ import { expandKeysAtom } from '@/atoms/clusters'; import { registerRouteConfigAtom } from '@/atoms/routes'; -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; -import SealTable from '@/components/seal-table'; -import TableContext from '@/components/seal-table/table-context'; -import { TableOrder } from '@/components/seal-table/types'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableFetch from '@/hooks/use-table-fetch'; import useWatchList from '@/hooks/use-watch-list'; import APIAccessInfoModal from '@/pages/llmodels/components/api-access-info'; +import { + DeleteModal, + FilterBar, + IconFont, + Table as SealTable, + TableProvider +} from '@gpustack/core-ui'; +import { TableOrder } from '@gpustack/core-ui/lib/components/table/types'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { message } from 'antd'; @@ -291,7 +293,7 @@ const ModelRoutes: React.FC = () => { handleDeleteByBatch={handleDeleteBatch} handleClickPrimary={handleClickDropdown} > - { onChange: handlePageChange }} > - + = forwardRef( const imgPromises: Promise[] = []; for (let i = 0; i < items.length; i++) { - let item = items[i]; + const item = items[i]; if (item.kind === 'file' && item.type.indexOf('image') !== -1) { const file = item.getAsFile(); diff --git a/src/pages/playground/components/model-select.tsx b/src/pages/playground/components/model-select.tsx index 9b2ad921..1e49051e 100644 --- a/src/pages/playground/components/model-select.tsx +++ b/src/pages/playground/components/model-select.tsx @@ -1,4 +1,4 @@ -import SealSelect from '@/components/seal-form/seal-select'; +import { Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; diff --git a/src/pages/playground/components/multiple-chat/content-item.tsx b/src/pages/playground/components/multiple-chat/content-item.tsx index 7eba67b3..3d72b7c5 100644 --- a/src/pages/playground/components/multiple-chat/content-item.tsx +++ b/src/pages/playground/components/multiple-chat/content-item.tsx @@ -1,5 +1,5 @@ -import IconFont from '@/components/icon-font'; import { StopOutlined } from '@ant-design/icons'; +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import React, { useMemo } from 'react'; diff --git a/src/pages/playground/components/multiple-chat/message-actions.tsx b/src/pages/playground/components/multiple-chat/message-actions.tsx index 7a02df88..5d49a33b 100644 --- a/src/pages/playground/components/multiple-chat/message-actions.tsx +++ b/src/pages/playground/components/multiple-chat/message-actions.tsx @@ -1,5 +1,3 @@ -import CopyButton from '@/components/copy-button'; -import UploadAudio from '@/components/upload-audio'; import { audioTypeMap, convertFileToBase64, @@ -10,6 +8,7 @@ import { EditOutlined, MinusCircleOutlined } from '@ant-design/icons'; +import { CopyButton, UploadAudio } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Tooltip } from 'antd'; import classNames from 'classnames'; diff --git a/src/pages/playground/components/multiple-chat/message-body.tsx b/src/pages/playground/components/multiple-chat/message-body.tsx index b823a071..64fb88bf 100644 --- a/src/pages/playground/components/multiple-chat/message-body.tsx +++ b/src/pages/playground/components/multiple-chat/message-body.tsx @@ -1,5 +1,4 @@ -import SimpleAudio from '@/components/audio-player/simple-audio'; -import FullMarkdown from '@/components/markdown-viewer/full-markdown'; +import { FullMarkdown, SimpleAudio } from '@gpustack/core-ui'; import { Input } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; @@ -78,7 +77,7 @@ const MessageBody: React.FC = forwardRef( const imgPromises: Promise[] = []; for (let i = 0; i < items.length; i++) { - let item = items[i]; + const item = items[i]; if (item.kind === 'file' && item.type.indexOf('image') !== -1) { const file = item.getAsFile(); diff --git a/src/pages/playground/components/multiple-chat/model-item.tsx b/src/pages/playground/components/multiple-chat/model-item.tsx index 821dd594..1f85df17 100644 --- a/src/pages/playground/components/multiple-chat/model-item.tsx +++ b/src/pages/playground/components/multiple-chat/model-item.tsx @@ -1,13 +1,15 @@ -import AutoTooltip from '@/components/auto-tooltip'; -import IconFont from '@/components/icon-font'; -import OverlayScroller from '@/components/overlay-scroller'; -import BaseSelect from '@/components/seal-form/base/select'; import { ClearOutlined, DeleteOutlined, MoreOutlined, SettingOutlined } from '@ant-design/icons'; +import { + AutoTooltip, + BaseSelect, + IconFont, + OverlayScroller +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Dropdown, Popover, Spin } from 'antd'; import _ from 'lodash'; @@ -38,7 +40,6 @@ import ReferenceParams from '../reference-params'; import ViewCommonCode from '../view-common-code'; import MessageContent from './message-content'; import SystemMessage from './system-message'; - interface ModelItemProps { model: string; modelList: ModelSelectionItem[]; diff --git a/src/pages/playground/components/multiple-chat/think-content.tsx b/src/pages/playground/components/multiple-chat/think-content.tsx index 095b4ba4..4ee34e9f 100644 --- a/src/pages/playground/components/multiple-chat/think-content.tsx +++ b/src/pages/playground/components/multiple-chat/think-content.tsx @@ -1,4 +1,4 @@ -import FullMarkdown from '@/components/markdown-viewer/full-markdown'; +import { FullMarkdown } from '@gpustack/core-ui'; import React from 'react'; import '../../style/think-content.less'; diff --git a/src/pages/playground/components/multiple-chat/think-parser.ts b/src/pages/playground/components/multiple-chat/think-parser.ts index 00fa84fb..9704d5cf 100644 --- a/src/pages/playground/components/multiple-chat/think-parser.ts +++ b/src/pages/playground/components/multiple-chat/think-parser.ts @@ -13,8 +13,8 @@ class ThinkParser { parse(chunk: string) { while (this.lastCheckedIndex < chunk.length) { - let startIndex = chunk.indexOf('', this.lastCheckedIndex); - let endIndex = chunk.indexOf('', this.lastCheckedIndex); + const startIndex = chunk.indexOf('', this.lastCheckedIndex); + const endIndex = chunk.indexOf('', this.lastCheckedIndex); if (!this.collecting) { if (endIndex !== -1 && (startIndex === -1 || endIndex < startIndex)) { diff --git a/src/pages/playground/components/params-fields.tsx b/src/pages/playground/components/params-fields.tsx index 3e171b27..789d4395 100644 --- a/src/pages/playground/components/params-fields.tsx +++ b/src/pages/playground/components/params-fields.tsx @@ -1,4 +1,4 @@ -import FieldComponent from '@/components/seal-form/field-component'; +import { FieldComponent } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/playground/components/thumb-img.tsx b/src/pages/playground/components/thumb-img.tsx index 356037d1..1d447c5b 100644 --- a/src/pages/playground/components/thumb-img.tsx +++ b/src/pages/playground/components/thumb-img.tsx @@ -1,4 +1,4 @@ -import SingleImage from '@/components/auto-image/single-image'; +import { SingleImage } from '@gpustack/core-ui'; import { Col, Row } from 'antd'; import _ from 'lodash'; import React, { useCallback, useMemo } from 'react'; diff --git a/src/pages/playground/components/view-code-buttons.tsx b/src/pages/playground/components/view-code-buttons.tsx index dd82c13a..b12a4cec 100644 --- a/src/pages/playground/components/view-code-buttons.tsx +++ b/src/pages/playground/components/view-code-buttons.tsx @@ -1,7 +1,7 @@ +import { IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Space } from 'antd'; import React from 'react'; -import IconFont from '../../../components/icon-font'; const ViewCodeButtons: React.FC<{ handleViewCode: () => void; diff --git a/src/pages/playground/components/view-common-code.tsx b/src/pages/playground/components/view-common-code.tsx index a70ef318..96d28887 100644 --- a/src/pages/playground/components/view-common-code.tsx +++ b/src/pages/playground/components/view-common-code.tsx @@ -1,8 +1,8 @@ import { BulbOutlined } from '@ant-design/icons'; +import { CommandViewer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Modal } from 'antd'; import React, { useEffect, useState } from 'react'; -import CommandViewer from '../../_components/command-viewer'; type ViewModalProps = { title?: string; diff --git a/src/pages/playground/embedding/forms/advance-config.tsx b/src/pages/playground/embedding/forms/advance-config.tsx index 27872db0..d1368223 100644 --- a/src/pages/playground/embedding/forms/advance-config.tsx +++ b/src/pages/playground/embedding/forms/advance-config.tsx @@ -1,4 +1,4 @@ -import SealInputNumber from '@/components/seal-form/input-number'; +import { InputNumber as CInputNumber } from '@gpustack/core-ui'; import { Form } from 'antd'; import _ from 'lodash'; import React from 'react'; @@ -11,11 +11,11 @@ const AdvanceConfig: React.FC = () => { <> {modelMeta?.n_ctx && modelMeta?.n_slot && ( - + > )} diff --git a/src/pages/playground/embedding/page.tsx b/src/pages/playground/embedding/page.tsx index e4d0fb56..f864aebd 100644 --- a/src/pages/playground/embedding/page.tsx +++ b/src/pages/playground/embedding/page.tsx @@ -1,17 +1,19 @@ -import AlertInfo from '@/components/alert-info'; -import ScatterChart from '@/components/echarts/scatter'; -import HighlightCode from '@/components/highlight-code'; -import IconFont from '@/components/icon-font'; -import SealInputNumber from '@/components/seal-form/input-number'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useRequestToken from '@/hooks/use-request-token'; -import ResizeContainer from '@/pages/_components/terminal-tabs/resize-container'; import { ClearOutlined, PlusOutlined, QuestionCircleOutlined, SendOutlined } from '@ant-design/icons'; +import { + AlertInfo, + InputNumber as CInputNumber, + HighlightCode, + IconFont, + ResizeContainer, + ScatterChart +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, Checkbox, Form, Segmented, Spin, Tabs, Tooltip } from 'antd'; @@ -406,11 +408,11 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { if (modelMeta?.n_ctx && modelMeta?.n_slot) { return ( - + > ); } diff --git a/src/pages/playground/hooks/use-add-image.tsx b/src/pages/playground/hooks/use-add-image.tsx index 8ab58d0c..1afea8fb 100644 --- a/src/pages/playground/hooks/use-add-image.tsx +++ b/src/pages/playground/hooks/use-add-image.tsx @@ -1,10 +1,10 @@ -import DropDownActions from '@/components/drop-down-actions'; import { DeleteOutlined, LinkOutlined, PictureOutlined, UploadOutlined } from '@ant-design/icons'; +import { DropdownActions } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Input, Tooltip } from 'antd'; import { useRef, useState } from 'react'; @@ -131,7 +131,7 @@ const useAddImage = (options: { ) : null; const UploadImageButton = ( - - + ); return { diff --git a/src/pages/playground/hooks/use-text-image.ts b/src/pages/playground/hooks/use-text-image.ts index 58cd3d96..ec109b73 100644 --- a/src/pages/playground/hooks/use-text-image.ts +++ b/src/pages/playground/hooks/use-text-image.ts @@ -54,7 +54,7 @@ export default function useTextImage(props: any) { }; const setImageSize = (parameters: any) => { - let size: Record = { + const size: Record = { span: 12 }; if (parameters.n === 1) { @@ -102,7 +102,7 @@ export default function useTextImage(props: any) { _.toNumber(item) ); - let newImageList = Array(parameters.n) + const newImageList = Array(parameters.n) .fill({}) .map((item, index: number) => { return { diff --git a/src/pages/playground/images/create.tsx b/src/pages/playground/images/create.tsx index d76972a2..05218d8c 100644 --- a/src/pages/playground/images/create.tsx +++ b/src/pages/playground/images/create.tsx @@ -1,10 +1,9 @@ import { setRouteCache } from '@/atoms/route-cache'; -import AlertInfo from '@/components/alert-info'; -import IconFont from '@/components/icon-font'; import routeCachekey from '@/config/route-cachekey'; import ThumbImg from '@/pages/playground/components/thumb-img'; import { generateRandomNumber } from '@/utils'; import { FileImageOutlined } from '@ant-design/icons'; +import { AlertInfo, IconFont } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Tooltip } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/playground/images/edit.tsx b/src/pages/playground/images/edit.tsx index 24b1853b..7641c087 100644 --- a/src/pages/playground/images/edit.tsx +++ b/src/pages/playground/images/edit.tsx @@ -1,12 +1,14 @@ import { setRouteCache } from '@/atoms/route-cache'; -import AlertInfo from '@/components/alert-info'; -import SingleImage from '@/components/auto-image/single-image'; -import IconFont from '@/components/icon-font'; -import CanvasImageEditor from '@/components/image-editor'; -import { processImage } from '@/components/image-editor/extract-image-colors'; import routeCachekey from '@/config/route-cachekey'; import UploadImg from '@/pages/playground/components/upload-img'; import { base64ToFile, generateRandomNumber } from '@/utils'; +import { + AlertInfo, + ImageEditor as CanvasImageEditor, + IconFont, + SingleImage +} from '@gpustack/core-ui'; +import { processImage } from '@gpustack/core-ui/lib/components/image-editor/extract-image-colors'; import { useIntl } from '@umijs/max'; import { Divider } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/playground/images/forms/index.tsx b/src/pages/playground/images/forms/index.tsx index b49f81b2..0340d72b 100644 --- a/src/pages/playground/images/forms/index.tsx +++ b/src/pages/playground/images/forms/index.tsx @@ -1,5 +1,4 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealInputNumber from '@/components/seal-form/input-number'; +import { InputNumber as CInputNumber, CheckboxField } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; @@ -99,10 +98,10 @@ const ParamsSettings: React.FC = forwardRef( > - + > { <> {modelMeta?.n_ctx && modelMeta?.n_slot && ( - + > )} diff --git a/src/pages/playground/rerank/page.tsx b/src/pages/playground/rerank/page.tsx index ed4840de..4da9a6b1 100644 --- a/src/pages/playground/rerank/page.tsx +++ b/src/pages/playground/rerank/page.tsx @@ -1,4 +1,3 @@ -import AlertInfo from '@/components/alert-info'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useRequestToken from '@/hooks/use-request-token'; import { @@ -7,6 +6,7 @@ import { QuestionCircleOutlined, SendOutlined } from '@ant-design/icons'; +import { AlertInfo } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Input, Spin, Tag, Tooltip, Typography } from 'antd'; import _ from 'lodash'; diff --git a/src/pages/playground/speech/forms/stt-form.tsx b/src/pages/playground/speech/forms/stt-form.tsx index dacba76f..2d02c7d8 100644 --- a/src/pages/playground/speech/forms/stt-form.tsx +++ b/src/pages/playground/speech/forms/stt-form.tsx @@ -1,5 +1,4 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; -import SealSelect from '@/components/seal-form/seal-select'; +import { CheckboxField, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl, useSearchParams } from '@umijs/max'; import { Form } from 'antd'; import React, { diff --git a/src/pages/playground/speech/forms/tts-advance.tsx b/src/pages/playground/speech/forms/tts-advance.tsx index 6d68cef0..bd36b2a3 100644 --- a/src/pages/playground/speech/forms/tts-advance.tsx +++ b/src/pages/playground/speech/forms/tts-advance.tsx @@ -1,10 +1,12 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; -import InputNumber from '@/components/seal-form/input-number'; -import SealInput from '@/components/seal-form/seal-input'; -import UploadAudio from '@/components/upload-audio'; import useAppUtils from '@/hooks/use-app-utils'; import { convertFileToBase64 } from '@/utils/load-audio-file'; import { CloseCircleFilled } from '@ant-design/icons'; +import { + CheckboxField, + Input as CInput, + InputNumber, + UploadAudio +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { useEffect } from 'react'; @@ -110,14 +112,14 @@ const TTSAdvanceConfig: React.FC = () => { } ]} > - + > { getValueProps={(value) => ({ value: fileName ? fileName : value })} dependencies={['task_type']} > - { id: 'playground.params.refAudio.tips' })} label={intl.formatMessage({ id: 'playground.params.refAudio' })} - > + > { dependencies={['task_type', 'x_vector_only_mode']} rules={[atLeastOneValidator('x_vector_only_mode')]} > - + > { } ]} > - + > name="new_password" @@ -76,12 +75,12 @@ const Profile: React.FC = () => { } ]} > - + > { }) ]} > - { record.resolved_paths?.[0] ); - let name = _.toLower( + const name = _.toLower( _.split( record.huggingface_repo_id || record.ollama_library_model_name || diff --git a/src/pages/resources/components/script-install.tsx b/src/pages/resources/components/script-install.tsx index 28a4cdcb..28f758e7 100644 --- a/src/pages/resources/components/script-install.tsx +++ b/src/pages/resources/components/script-install.tsx @@ -1,4 +1,4 @@ -import HighlightCode from '@/components/highlight-code'; +import { HighlightCode } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import React, { useMemo } from 'react'; import { addWorkerGuide } from '../config'; diff --git a/src/pages/resources/components/update-labels.tsx b/src/pages/resources/components/update-labels.tsx index de2f0aca..30ae4c12 100644 --- a/src/pages/resources/components/update-labels.tsx +++ b/src/pages/resources/components/update-labels.tsx @@ -1,7 +1,4 @@ -import LabelSelector from '@/components/label-selector'; -import ModalFooter from '@/components/modal-footer'; -import ScrollerModal from '@/components/scroller-modal'; -import SealInput from '@/components/seal-form/seal-input'; +import { LabelSelector, ModalFooter, ScrollerModal } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -66,7 +63,7 @@ const UpdateLabels: React.FC = (props) => { }} > name="name"> - - + > ); }; diff --git a/src/pages/resources/hooks/use-worker-columns.tsx b/src/pages/resources/hooks/use-worker-columns.tsx index d88a1ac6..c25671ae 100644 --- a/src/pages/resources/hooks/use-worker-columns.tsx +++ b/src/pages/resources/hooks/use-worker-columns.tsx @@ -1,14 +1,6 @@ import { systemConfigAtom } from '@/atoms/system'; import { GPUStackVersionAtom } from '@/atoms/user'; -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; -import LabelsCell from '@/components/label-cell'; -import ProgressBar from '@/components/progress-bar'; -import InfoColumn from '@/components/simple-table/info-column'; -import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; -import GrafanaIcon from '@/pages/_components/grafana-icon'; import { convertFileSize } from '@/utils'; import { DeleteOutlined, @@ -18,6 +10,16 @@ import { SafetyOutlined, ToolOutlined } from '@ant-design/icons'; +import { + AutoTooltip, + DropdownButtons, + GrafanaIcon, + IconFont, + InfoColumn, + LabelCell, + ProgressBar, + StatusTag +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import { ColumnsType } from 'antd/lib/table'; @@ -29,7 +31,6 @@ import semverGt from 'semver/functions/gt'; import { status, WorkerStatusMap, WorkerStatusMapValue } from '../config'; import { Filesystem, GPUDeviceItem, ListItem } from '../config/types'; import workerCss from '../styles/worker.less'; - const ActionList = [ { label: 'common.button.edit', key: 'edit', icon: }, { @@ -400,7 +401,7 @@ const useWorkerColumns = ({ title: intl.formatMessage({ id: 'resources.table.labels' }), dataIndex: 'labels', width: 200, - render: (_, record) => + render: (_, record) => }, { title: intl.formatMessage({ id: 'clusters.title' }), diff --git a/src/pages/resources/hooks/use-worker-maintenance.tsx b/src/pages/resources/hooks/use-worker-maintenance.tsx index e743fdfe..5014cb78 100644 --- a/src/pages/resources/hooks/use-worker-maintenance.tsx +++ b/src/pages/resources/hooks/use-worker-maintenance.tsx @@ -1,7 +1,8 @@ -import ModalFooter from '@/components/modal-footer'; -import ScrollerModal from '@/components/scroller-modal'; -import SealInput from '@/components/seal-form/seal-input'; -import SealTextArea from '@/components/seal-form/seal-textarea'; +import { + ModalFooter, + ScrollerModal, + Textarea as SealTextArea +} from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form, message } from 'antd'; import React from 'react'; @@ -90,7 +91,7 @@ const useWorkerMaintenance = ({ fetchData }: { fetchData: () => void }) => { }} > - = ({ } ]} > - + > name="full_name" rules={[{ required: false }]}> - + >
    @@ -150,11 +152,11 @@ const AddModal: React.FC = ({ } ]} > - + > diff --git a/src/pages/users/hooks/use-users-columns.tsx b/src/pages/users/hooks/use-users-columns.tsx index 1c9d303d..1f15510c 100644 --- a/src/pages/users/hooks/use-users-columns.tsx +++ b/src/pages/users/hooks/use-users-columns.tsx @@ -1,9 +1,7 @@ // columns.ts -import AutoTooltip from '@/components/auto-tooltip'; -import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; -import icons from '@/components/icon-font/icons'; import { tableSorter } from '@/config/settings'; +import { AutoTooltip, DropdownButtons, IconFont } from '@gpustack/core-ui'; +import icons from '@gpustack/core-ui/lib/components/icon-font/icons'; import { useIntl, useModel } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Tag } from 'antd'; @@ -11,7 +9,6 @@ import { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { ListItem } from '../config/types'; - interface ColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; sortOrder: string[]; diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 648e0f99..db966a16 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -1,10 +1,8 @@ -import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; -import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; +import { DeleteModal, FilterBar, IconFont } from '@gpustack/core-ui'; import { useIntl, useModel } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { ConfigProvider, message, Table } from 'antd'; diff --git a/tsconfig.json b/tsconfig.json index f00d0f9c..93ab3233 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,15 +17,22 @@ "paths": { "@/*": ["./src/*"], "@@/*": ["./src/.umi/*"], - "@@test/*": ["./src/.umi-test/*"] + "@@test/*": ["./src/.umi-test/*"], + "@gpustack/core-ui": ["../core-ui/src/index.ts"], + "@gpustack/core-ui/*": ["../core-ui/src/*"], + "@gpustack/core-ui/hooks": ["../core-ui/src/lib/hooks"], + "@gpustack/core-ui/utils": ["../core-ui/src/lib/utils"], + "@gpustack/core-ui/types": ["../core-ui/src/lib/types"], + "@gpustack/core-ui/components": ["../core-ui/src/lib/components"], + "@gpustack/core-ui/lib/*": ["../core-ui/src/lib/*"] } }, "include": [ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "src/components/logs-viewer/parse-worker.ts", - "src/components/image-editor/invert-worker.ts", - "src/components/image-editor/offscreen-worker.ts" + "@gpustack/core-ui/lib/components/logs-viewer/parse-worker.ts", + "@gpustack/core-ui/lib/components/image-editor/invert-worker.ts", + "@gpustack/core-ui/lib/components/image-editor/offscreen-worker.ts" ] }